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
__text
Sectionにはプログラムデータ,__cstring
Sectionには文字列データ,__eh_frane
は,調査不足だが,恐らく例外処理に使われる。
何らかのバイナリエディタでmain.oを開き,__cstring
のoffset(ここでは1efだが,環境によって異なるだろう)に飛んでみる。
そこには,hello, worldのasciiコードが書かれているはずだ。
データの横に文字列が表示されるタイプのバイナリエディタであれば,そこにはhello, worldと表示されているはずだ。
編集履歴
Mach-Oに関してはまだ調査不足なので,この記事は随時更新となる。
2019/07/01
: 最初のバージョンを公開。
2019/07/02
: 誤字の修正。
2019/07/06
: リファレンスに関する注意書きを追加。