macOSのシステムコールメモ
macOSのシステムコールメモ
macOSのシステムコール番号は https://opensource.apple.com/source/xnu/xnu-4903.221.2/bsd/kern/syscalls.master.auto.html に書かれているが,64-bitではこの通りにsyscallしてもbus error となってしまう。
実際には,システムコール番号に0x2000000を足さないといけない。
つまり,mov rax, 1 のようなコードをmov rax, 0x2000001 のようにしないといけない。
その理由は, https://opensource.apple.com/source/xnu/xnu-4903.221.2/osfmk/mach/i386/syscall_sw.h.auto.html の以下のコメントで説明されている。
/* * Syscall classes for 64-bit system call entry. * For 64-bit users, the 32-bit syscall number is partitioned * with the high-order bits representing the class and low-order * bits being the syscall number within that class. * The high-order 32-bits of the 64-bit syscall number are unused. * All system classes enter the kernel via the syscall instruction. * * These are not #ifdef'd for x86-64 because they might be used for * 32-bit someday and so the 64-bit comm page in a 32-bit kernel * can use them. */
64-bitでの32-bitのシステムコール番号は上位ビットのclassと下位ビットのclass内のシステムコール番号で分割される。
classの構成は,先ほどのsyscall_sw.h内のマクロ定義にある。
#define SYSCALL_CLASS_SHIFT 24 #define SYSCALL_CLASS_MASK (0xFF << SYSCALL_CLASS_SHIFT) #define SYSCALL_NUMBER_MASK (~SYSCALL_CLASS_MASK) #define I386_SYSCALL_CLASS_MASK SYSCALL_CLASS_MASK #define I386_SYSCALL_ARG_BYTES_SHIFT (16) #define I386_SYSCALL_ARG_DWORDS_SHIFT (I386_SYSCALL_ARG_BYTES_SHIFT + 2) #define I386_SYSCALL_ARG_BYTES_NUM (64) /* Must be <= sizeof(uu_arg) */ #define I386_SYSCALL_ARG_DWORDS_MASK ((I386_SYSCALL_ARG_BYTES_NUM >> 2) -1) #define I386_SYSCALL_ARG_BYTES_MASK (((I386_SYSCALL_ARG_BYTES_NUM -1)&~0x3) << I386_SYSCALL_ARG_BYTES_SHIFT) #define I386_SYSCALL_NUMBER_MASK (0xFFFF) #define SYSCALL_CLASS_NONE 0 /* Invalid */ #define SYSCALL_CLASS_MACH 1 /* Mach */ #define SYSCALL_CLASS_UNIX 2 /* Unix/BSD */ #define SYSCALL_CLASS_MDEP 3 /* Machine-dependent */ #define SYSCALL_CLASS_DIAG 4 /* Diagnostics */ #define SYSCALL_CLASS_IPC 5 /* Mach IPC */ /* Macros to simpllfy constructing syscall numbers. */ #define SYSCALL_CONSTRUCT_MACH(syscall_number) \ ((SYSCALL_CLASS_MACH << SYSCALL_CLASS_SHIFT) | \ (SYSCALL_NUMBER_MASK & (syscall_number))) #define SYSCALL_CONSTRUCT_UNIX(syscall_number) \ ((SYSCALL_CLASS_UNIX << SYSCALL_CLASS_SHIFT) | \ (SYSCALL_NUMBER_MASK & (syscall_number))) #define SYSCALL_CONSTRUCT_MDEP(syscall_number) \ ((SYSCALL_CLASS_MDEP << SYSCALL_CLASS_SHIFT) | \ (SYSCALL_NUMBER_MASK & (syscall_number))) #define SYSCALL_CONSTRUCT_DIAG(syscall_number) \ ((SYSCALL_CLASS_DIAG << SYSCALL_CLASS_SHIFT) | \ (SYSCALL_NUMBER_MASK & (syscall_number)))
exit, read, writeなどのシステムコールはUnix-classに含まれるので,SYSCALL_CONSTRUCT_UNIX が使われる。実際にSYSCALL_CONSTRUCT_UNIX(2)
とすると,0x2000002 となる。
macOSでのクロスコンパイラのビルド方法
macOSでのクロスコンパイラのビルド方法について書いていく。
gcc
この記事の執筆時点での最新バージョンのgcc9.1.0をビルドするが,よほど古くない限りは同じ方法でできるはずだ。
gcc-9.1.0のダウンロードと解凍:
$ wget http://ftp.tsukuba.wide.ad.jp/software/gcc/releases/gcc-9.1.0/gcc-9.1.0.tar.gz $ tar -zxvf gcc-9.1.0.tar.gz && rm -f gcc-9.1.0.tar.gz
そして,gcc-buildディレクトリを作ってconfigureする。ここで,Homebrewでbinutilsをインストールしている場合,ビルドに失敗することがあるため,brew unlink binutilsを実行する。それでもまだ失敗する場合は一時的にbrew uninstall binutilsでアンインストールして,rm -rf ./*でgcc-build以下の全てのファイルを削除する(削除しないと,再configureに失敗する)。
$ mkdir gcc-build $ cd gcc-build
gccのビルドに必要なgmp, mpfr, mpcはいずれもbrew installでインストールできるため,ない場合はインストールする。
gmp, mpfr, mpcのあるディレクトリのPathをbrew --prefix パッケージ名で表示し,そのPathをconfigure時に-with-パッケージ名="ディレクトリのPath"で引数として渡す。
configure:
$ ../gcc-9.1.0/configure --prefix=インストール先 --target="ターゲット名(例: x86_64-elf)" --with-gmp="gmpのPath" --with-mpfr="mpfrのPath" --with-mpc="mpcのPath" AR="/usr/bin/ar" RANLIB="/usr/bin/ranlib"
AR="/usr/bin/ar" RANLIB="/usr/bin/ranlib"の部分は,Homebrewでインストールされたbinutilsが影響を及ぼしている可能性があるため,/usr/bin以下のarとranlibを指定している。
そして,ビルド&インストールを実行する。
このとき,make all-gccに引数として,-jと,その後ろにCPUのコア数を渡すと,CPUに応じてビルドが最適化される。
$ make all-gcc $ make install-gcc
binutils
binutilsも執筆時点での最新バージョンのbinutils2.32をビルドする。
binutils-2.32のダウンロードと解凍:
$ wget http://ftp.jaist.ac.jp/pub/GNU/binutils/binutils-2.32.tar.gz $ tar -zxvf binutils-2.32.tar.gz && rm -f binutils-2.32.tar.gz
gccのビルドと同じように,binutils-buildディレクトリを作って,そこでビルドを行う。
$ mkdir binutils-build $ cd binutils-build
そして,configureを行う。
$ ../binutils-2.32/configure --prefix="インストール先" --target="ターゲット名(例: x86_64-elf)"
ビルドは,gccとは違い,all-binutilsなどつけなくて良い。binutilsのビルドも,make -jコア数でCPUに対して最適化できる。
$ make $ make install
macOSにてgccのクロスコンパイラのビルドが失敗する
UEFIを試してみるため,MacBookProにgccのmingw-w64版のビルドをしようとしたところ,make install-gcc時に,以下のエラーが発生した。
ld: warning: ignoring file ../libiberty/libiberty.a, file was built for archive which is not the architecture being linked (x86_64): ../libiberty/libiberty.a
Undefined symbols for architecture x86_64:
"__sch_istable", referenced from:
_main in fixincl.o
_initialize in fixincl.o
_process in fixincl.o
_char_macro_def_fix in fixfixes.o
_char_macro_use_fix in fixfixes.o
_format_fix in fixfixes.o
_wrap_fix in fixfixes.o
...
"__sch_toupper", referenced from:
_wrap_fix in fixfixes.o
_gnu_type_fix in fixfixes.o
"_fdopen_unlocked", referenced from:
_process in fixincl.o
_load_file in fixincl.o
_create_file in fixincl.o
_proc2_fopen in procopen.o
"_freopen_unlocked", referenced from:
_main in fixincl.o
_initialize in fixincl.o
"_xcalloc", referenced from:
_run_compiles in fixincl.o
_run_shell in server.o
"_xmalloc", referenced from:
_process in fixincl.o
_wrap_fix in fixfixes.o
_run_shell in server.o
"_xmalloc_set_program_name", referenced from:
_initialize in fixincl.o
"_xrealloc", referenced from:
_run_shell in server.o
_load_file_data in fixlib.o
"_xregcomp", referenced from:
_compile_re in fixlib.o
_mn_get_regexps in fixlib.o
"_xregerror", referenced from:
_compile_re in fixlib.o
_mn_get_regexps in fixlib.o
"_xregexec", referenced from:
_process in fixincl.o
_machine_name_test in fixtests.o
_char_macro_def_fix in fixfixes.o
_char_macro_use_fix in fixfixes.o
_format_fix in fixfixes.o
_machine_name_fix in fixfixes.o
_wrap_fix in fixfixes.o
...
"_xstrdup", referenced from:
_run_shell in server.o
"_xstrerror", referenced from:
_initialize in fixincl.o
_process in fixincl.o
_load_file in fixincl.o
_create_file in fixincl.o
_chain_open in procopen.o
_load_file_data in fixlib.o
ld: symbol(s) not found for architecture x86_64
その後調べると,homebrewでインストールしたbinutilsがエラーを引き起こしているようだった。
なので,思い切ってbrew uninstall binutilsでアンインストールして,以下のコマンドでconfigureしてみた。
../gcc-9.1.0/configure --prefix=**** --target=x86_64-w64-mingw32 --with-gmp=/usr/local/opt/gmp --with-mpfr=/usr/local/opt/mpfr --with-mpc=/usr/local/Cellar/libmpc/1.1.0 AR="/usr/bin/ar" RANLIB="/usr/bin/ranlib"
そしてmake all-gcc -j4とmake install-gccでコンパイル&インストールができた。
Mach-Oの構造メモ
Mach-Oの情報をどこかに書かないと忘れてしまいそうだったため。
Mach-OのリファレンスはAppleの開発者用サイトから削除されてしまったようだが,githubにミラーがある。 Mirror of OS X ABI Mach-O File Format Reference
Mach-Oのリファレンスは誤記がいくつかあるようなので,注意する。
Mach-Oには大きく分けて,Header, Load Commands, Data という三つの領域がある。
Header
Headerは,ELFやPEなどと同じように,マジックナンバーがあり,そのあとにCPUの種類,ファイルの種類などが列挙されている。 以下はHeaderの構造体だ。
/* * The 32-bit mach header appears at the very beginning of the object file for * 32-bit architectures. */ struct mach_header { uint32_t magic; /* mach magic number identifier */ cpu_type_t cputype; /* cpu specifier */ cpu_subtype_t cpusubtype; /* machine specifier */ uint32_t filetype; /* type of file */ uint32_t ncmds; /* number of load commands */ uint32_t sizeofcmds; /* the size of all the load commands */ uint32_t flags; /* flags */ }; /* Constant for the magic field of the mach_header (32-bit architectures) */ #define MH_MAGIC 0xfeedface /* the mach magic number */ #define MH_CIGAM 0xcefaedfe /* NXSwapInt(MH_MAGIC) */ /* * The 64-bit mach header appears at the very beginning of object files for * 64-bit architectures. */ struct mach_header_64 { uint32_t magic; /* mach magic number identifier */ cpu_type_t cputype; /* cpu specifier */ cpu_subtype_t cpusubtype; /* machine specifier */ uint32_t filetype; /* type of file */ uint32_t ncmds; /* number of load commands */ uint32_t sizeofcmds; /* the size of all the load commands */ uint32_t flags; /* flags */ uint32_t reserved; /* reserved */ };
mach_headerが32bit,mach_header_64が64bit用だ。
これらのMach-Oの構造体などは,自分の環境(MacBook Pro 2018, macOS Mojave 10.14.5)の場合,/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/mach-o以下のloader.hにあった。
Load Commands
Load CommandsはHeaderのすぐ後ろにある。以下,Load Commandsのことを"LC"とする。
HeaderのncmdsがLCの数,sizeofcmdsがLC全体のサイズである。
Mach-Oでは,各LCの先頭はコマンドの種類とサイズになる。load_command構造体で,それらを読み込むことができる。
struct load_command { uint32_t cmd; /* type of load command */ uint32_t cmdsize; /* total size of command in bytes */ };
Segment Commandは,プログラムを読み込むためのコマンドだ。 以下にSegment Commandの構造体を示す。
struct segment_command { /* for 32-bit architectures */ uint32_t cmd; /* LC_SEGMENT */ uint32_t cmdsize; /* includes sizeof section structs */ char segname[16]; /* segment name */ uint32_t vmaddr; /* memory address of this segment */ uint32_t vmsize; /* memory size of this segment */ uint32_t fileoff; /* file offset of this segment */ uint32_t filesize; /* amount to map from the file */ vm_prot_t maxprot; /* maximum VM protection */ vm_prot_t initprot; /* initial VM protection */ uint32_t nsects; /* number of sections in segment */ uint32_t flags; /* flags */ }; /* * The 64-bit segment load command indicates that a part of this file is to be * mapped into a 64-bit task's address space. If the 64-bit segment has * sections then section_64 structures directly follow the 64-bit segment * command and their size is reflected in cmdsize. */ struct segment_command_64 { /* for 64-bit architectures */ uint32_t cmd; /* LC_SEGMENT_64 */ uint32_t cmdsize; /* includes sizeof section_64 structs */ char segname[16]; /* segment name */ uint64_t vmaddr; /* memory address of this segment */ uint64_t vmsize; /* memory size of this segment */ uint64_t fileoff; /* file offset of this segment */ uint64_t filesize; /* amount to map from the file */ vm_prot_t maxprot; /* maximum VM protection */ vm_prot_t initprot; /* initial VM protection */ uint32_t nsects; /* number of sections in segment */ uint32_t flags; /* flags */ };
Segment Commandの後にはSectionが存在する。 Sectionにはプログラムなどのデータの情報が書かれている。 以下にSectionの構造体を示す。
struct section { /* for 32-bit architectures */ char sectname[16]; /* name of this section */ char segname[16]; /* segment this section goes in */ uint32_t addr; /* memory address of this section */ uint32_t size; /* size in bytes of this section */ uint32_t offset; /* file offset of this section */ uint32_t align; /* section alignment (power of 2) */ uint32_t reloff; /* file offset of relocation entries */ uint32_t nreloc; /* number of relocation entries */ uint32_t flags; /* flags (section type and attributes)*/ uint32_t reserved1; /* reserved (for offset or index) */ uint32_t reserved2; /* reserved (for count or sizeof) */ }; struct section_64 { /* for 64-bit architectures */ char sectname[16]; /* name of this section */ char segname[16]; /* segment this section goes in */ uint64_t addr; /* memory address of this section */ uint64_t size; /* size in bytes of this section */ uint32_t offset; /* file offset of this section */ uint32_t align; /* section alignment (power of 2) */ uint32_t reloff; /* file offset of relocation entries */ uint32_t nreloc; /* number of relocation entries */ uint32_t flags; /* flags (section type and attributes)*/ uint32_t reserved1; /* reserved (for offset or index) */ uint32_t reserved2; /* reserved (for count or sizeof) */ uint32_t reserved3; /* reserved */ };
Mach-OのSegment Commandの情報を表示するCプログラムを以下に示す。
readmacho.c:
#include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <mach-o/loader.h> static const char *usage = "Usage: readmacho ¥"filename¥"¥n"; void *read(FILE *fp, size_t size) { void *data = calloc(1, size); fread(data, size, 1, fp); return data; } void *load(FILE *fp, int offset, size_t size) { void *data = calloc(1, size); fseek(fp, offset, SEEK_SET); fread(data, size, 1, fp); return data; } void print_sections(FILE *fp, int nsects, int *current) { for (int i = 0; i < nsects; i++) { size_t sect_size = sizeof(struct section_64); struct section_64 *sect = read(fp, sect_size); *current += sect_size; printf(" section: %s¥n", sect->sectname); printf(" offset: %x¥n", sect->offset); free(sect); } } int main(int argc, char **argv) { if (argc < 2) { fprintf(stderr, "%s¥n", usage); return 1; } char *name = argv[1]; FILE *fp; if ((fp = fopen(name, "rb")) == NULL) { fprintf(stderr, "Can't open the file: %s¥n", name); return 1; } uint32_t *magic = load(fp, 0, sizeof(uint32_t)); if (*magic != MH_MAGIC_64) { fprintf(stderr, "%s is not a mach-o file¥n", name); free(magic); return 1; } free(magic); size_t header_size = sizeof(struct mach_header_64); struct mach_header_64 *header = load(fp, 0, header_size); int current = header_size; for (int i = 0; i < header->ncmds; i++) { size_t lc_size = sizeof(struct load_command); struct load_command *lc = load(fp, current, lc_size); if (lc->cmd == LC_SEGMENT_64) { size_t seg_size = sizeof(struct segment_command_64); struct segment_command_64 *seg = load(fp, current, seg_size); current += seg_size; printf("segment: %s¥n", seg->segname); print_sections(fp, seg->nsects, ¤t); free(seg); } else { current += lc->cmdsize; } free(lc); } free(header); fclose(fp); return 0; }
試しに自分自身の情報を表示してみる。
./readmacho readmacho
出力は以下のようになる。
segment: __PAGEZERO
segment: __TEXT
section: __text
offset: a82
section: __stubs
offset: df4
section: __stub_helper
offset: e24
section: __cstring
offset: e84
section: __eh_frame
offset: f08
segment: __DATA
section: __nl_symbol_ptr
offset: 1000
section: __got
offset: 1010
section: __la_symbol_ptr
offset: 1018
section: __data
offset: 1058
segment: __LINKEDIT
Section以下のoffsetは,そのSectionのデータが書かれている位置だ。
Data
Load Commandsの後ろにあるData領域は,その名の通りプログラムやシンボルテーブル,リロケーション情報などが書かれている。 試しに以下のCプログラムをコンパイルして,先ほどのreadmachoで情報を表示してみる。
main.c:
#include <stdio.h> int main(void) { printf("hello, world¥n"); return 0; }
gcc -c main.c ./readmacho main.o
すると出力は以下のようになる。
segment:
section: __text
offset: 1d8
section: __cstring
offset: 1ef
section: __eh_frame
offset: 200
__textSectionにはプログラムデータ,__cstringSectionには文字列データ,__eh_franeは,調査不足だが,恐らく例外処理に使われる。
何らかのバイナリエディタでmain.oを開き,__cstringのoffset(ここでは1efだが,環境によって異なるだろう)に飛んでみる。
そこには,hello, worldのasciiコードが書かれているはずだ。
データの横に文字列が表示されるタイプのバイナリエディタであれば,そこにはhello, worldと表示されているはずだ。
編集履歴
Mach-Oに関してはまだ調査不足なので,この記事は随時更新となる。
2019/07/01: 最初のバージョンを公開。
2019/07/02: 誤字の修正。
2019/07/06: リファレンスに関する注意書きを追加。