Mach-Oのセグメント&セクション定義を読み込む
今回も引き続き共有ライブラリの仕組を知るための準備としてMach-Oの構造を学習。今回はセグメントとセクションに関する情報を読み込んでみる。環境はx86_64
セグメントとセクション
図1のように、Mach-Oファイルにはロードコマンドに続いてセグメントと呼ばれるデータ領域が一つ以上有る。また、セグメントはセクションと呼ばれる領域に分かれている。セクションは1個も含まれないことも有る。
セグメント名はアンダースコア2個に続けて大文字で表す(例: __TEXT, __DATA) セクション名はアンダースコア2個に続けて小文字で表す(例: __text, __data)
Mach-Oファイルには様々なテーブルが含まれていて、その中でセクションが番号で参照される。セクション番号は 1 から始まる。セクション番号はセグメントを跨いだ通し番号となる(図1参照)。
セグメントを定義するロードコマンドはLC_SEGMENT_64で、ロードコマンドの構造は mach-o/loader.h で定義されている segment_command_64 構造体。
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 */ };
Mach-Oファイルのどの部分をプロセス仮想メモリのどこにロードするかや、ロードされた先の仮想メモリの保護属性もセグメントコマンドで定義される。セグメントは常に仮想メモリのページサイズでアラインされる。セグメントの仮想メモリ上のサイズはMach-Oファイルのサイズよりも大きくなることが有る。例えば __PAGEZEROセグメントはMach-Oファイル上でのサイズは 0 バイトだが、仮想メモリ上でのサイズは 1ページ(= 4096 byte @ x86_64)となる。
■Mach-Oでの標準的なセグメントの例。(詳しくはこちら)
- __PAGEZERO
- NULL(=0)ポインタ経由のアクセスに対して、直ちにプログラムを異常終了させるためのセグメント
- __TEXT
- コードとリードオンリーデータが格納されているセグメント
- __DATA
- ライタブルデータが格納されているセグメント
- __LINKEDIT
- ダイナミックリンカが使用する情報が格納されているセグメント
セクション
セグメントのロードコマンドに続いてセクションを定義するsection_64構造体がnsects個続いている。
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 */ };
Mac OS X ABI Mach-O File Format Referenceにはreserved3が無かったが実際のMach-Oファイルを見てみると確かにreserved3に相当するデータは有った。
■セクションの例 (詳しくはこちら)
- __text
- 実行可能なコードが格納されているセクション。__TEXTセグメントに含まれる。
- __cstring
- コンスタント文字列。非'\0'のバイトの並びが格納されているセクション。__TEXTセグメントに含まれる。
- __data
- 初期化済み(int i=3; など)の可変なデータが格納されているセクション。__DATAセグメントに含まれる
- __bss
- 未初期化のスタティック変数(static int i; など)の可変なデータが格納されているセクション。__DATAセグメントに含まれる。
セグメントとセクションの定義を読み込む
前回のソース(ml2.c)にセグメントとセクションの定義を読み込むコードを追加する。読み込むMach-Oファイルは前回作成のhoge1.o
ソース抜粋(ml3.c)
int load_segment_command_64(FILE *stream, struct segment_command_64 *seg_cmd_64) { fread(seg_cmd_64->segname, 16, 1, stream); fread(&seg_cmd_64->vmaddr, sizeof(uint64_t), 1, stream); fread(&seg_cmd_64->vmsize, sizeof(uint64_t), 1, stream); fread(&seg_cmd_64->fileoff, sizeof(uint64_t), 1, stream); fread(&seg_cmd_64->filesize, sizeof(uint64_t), 1, stream); fread(&seg_cmd_64->maxprot, sizeof(vm_prot_t), 1, stream); fread(&seg_cmd_64->initprot, sizeof(vm_prot_t), 1, stream); fread(&seg_cmd_64->nsects, sizeof(uint32_t), 1, stream); fread(&seg_cmd_64->flags, sizeof(uint32_t), 1, stream); printf("cmd = 0x%08x\n", seg_cmd_64->cmd); printf("cmdsize = 0x%08x\n", seg_cmd_64->cmdsize); printf("segname = %s\n", seg_cmd_64->segname); printf("vmaddr = %016llx\n", seg_cmd_64->vmaddr); printf("vmsize = %016llx\n", seg_cmd_64->vmsize); printf("fileoff = %016llx\n", seg_cmd_64->fileoff); printf("filesize = %016llx\n", seg_cmd_64->filesize); printf("maxprot = %016x\n", seg_cmd_64->maxprot); printf("initprot = %016x\n", seg_cmd_64->initprot); printf("nsects = %08x\n", seg_cmd_64->nsects); printf("flags = %08x\n", seg_cmd_64->flags); return 0; } int load_section_64(FILE *f, struct section_64 *sect64) { fread(sect64->sectname, 16, 1, f); fread(sect64->segname, 16, 1, f); fread(§64->addr, sizeof(uint64_t), 1, f); fread(§64->size, sizeof(uint64_t), 1, f); fread(§64->offset, sizeof(uint32_t), 1, f); fread(§64->align, sizeof(uint32_t), 1, f); fread(§64->reloff, sizeof(uint32_t), 1, f); fread(§64->nreloc, sizeof(uint32_t), 1, f); fread(§64->flags, sizeof(uint32_t), 1, f); fread(§64->reserved1, sizeof(uint32_t), 1, f); fread(§64->reserved2, sizeof(uint32_t), 1, f); fread(§64->reserved3, sizeof(uint32_t), 1, f); printf("sectname = %s\n", sect64->sectname); printf("setgname = %s\n", sect64->segname); printf("addr = 0x%016llx\n", sect64->addr); printf("size = 0x%016llx\n", sect64->size); printf("offset = 0x%08x\n", sect64->offset); printf("align = 0x%08x\n", sect64->align); printf("reloff = 0x%08x\n", sect64->reloff); printf("nreloc = 0x%08x\n", sect64->nreloc); printf("flags = 0x%08x\n", sect64->flags); printf("reserved1 = 0x%08x\n", sect64->reserved1); printf("reserved2 = 0x%08x\n", sect64->reserved2); printf("reserved3 = 0x%08x\n", sect64->reserved3); } int main(int argc, char **argv) { // テキトーにsegment_command_64とsection_64の配列を作ってそこに上記関数でロード。 }
実行結果(抜粋)
$ ml3 hoge1.o (略) === LC_SEGMENT_64[0] === cmd = 0x00000019 cmdsize = 0x000000e8 segname = vmaddr = 0000000000000000 vmsize = 0000000000000011 fileoff = 0000000000000170 filesize = 0000000000000011 maxprot = 0000000000000007 initprot = 0000000000000007 nsects = 00000002 flags = 00000000 === section[0] === sectname = __text setgname = __TEXT addr = 0x0000000000000000 size = 0x0000000000000010 offset = 0x00000170 align = 0x00000000 reloff = 0x00000184 nreloc = 0x00000001 flags = 0x80000400 reserved1 = 0x00000000 reserved2 = 0x00000000 reserved3 = 0x00000000 === section[1] === sectname = __data setgname = __DATA addr = 0x0000000000000010 size = 0x0000000000000001 offset = 0x00000180 align = 0x00000000 reloff = 0x00000000 nreloc = 0x00000000 flags = 0x00000000 reserved1 = 0x00000000 reserved2 = 0x00000000 reserved3 = 0x00000000 (略)
中間形式のMach-Oでは容量削減のためにセグメントは一つしか無い。セクションには最終形式のMach-Oでの格納先となるセグメント名が記述されている。
otoolで答え合わせ。
$ otool -l hoge1.o (略) Load command 0 cmd LC_SEGMENT_64 cmdsize 232 segname vmaddr 0x0000000000000000 vmsize 0x0000000000000011 fileoff 368 filesize 17 maxprot 0x00000007 initprot 0x00000007 nsects 2 flags 0x0 Section sectname __text segname __TEXT addr 0x0000000000000000 size 0x0000000000000010 offset 368 align 2^0 (1) reloff 388 nreloc 1 flags 0x80000400 reserved1 0 reserved2 0 Section sectname __data segname __DATA addr 0x0000000000000010 size 0x0000000000000001 offset 384 align 2^0 (1) reloff 0 nreloc 0 flags 0x00000000 reserved1 0 reserved2 0 (略)
よさげ。
まとめ
- セグメントとセクションのMach-Oファイル内での構造を理解できた