Mach-Oのシンボルテーブルを読み込む
前回に引き続き共有ライブラリの仕組を知るための準備としてMach-Oの構造を調べる。今回はシンボルテーブルを読み込んでみる。環境はx86_64。
シンボルとは
私達がソースを書く時には関数や変数に
int i; i = foo();
の様に文字列の名前を付ける。しかし、CPUが関数や変数を参照するときはアドレスを使う。この間を取り持つオブジェクトをシンボルと言う。具体的にはこのようなもの。/usr/include/mach-o/nlist.hで定義されている。
struct nlist_64 { union { uint32_t n_strx; } n_un; uint8_t n_type; uint8_t n_sect; uint16_t n_desc; uint64_t n_value; };
n_strxが対応する文字列を指し、n_valueが値(アドレス)を示す。例えばこのシンボルが関数foo()を指すものだとするとn_strxからfooと言う文字列が、n_valueからfoo()のアドレスを知ることができる。必ずしもそうではないが、今日のところはこれで良しとする(n_typeの値によってn_valueの意味は異なる)。
シンボルテーブルとストリングテーブル
Mach-Oファイルにはファイル内で定義したり参照したシンボルのリストが含まれていて、これをシンボルテーブルと言う。シンボルテーブルの位置や、要素の数は LC_SYMTAB というロードコマンドに記述されている。シンボルテーブルの要素は先に触れた nlist_64 構造体。シンボルに対応する文字列はシンボルテーブルとは別にリストされておりこちらはストリングテーブルと言う。構造は NULL 文字(\0)終端の文字列を連結したもの。n_strx はストリングテーブル先頭からのオフセットで文字列を示す。ストリングテーブルの先頭は必ず NULL 文字なので、n_strx=0 とすると空文字列""を表すことになる。
シンボルテーブルの構造は mach-o/loder.h で定義されている。
struct symtab_command
{
uint_32 cmd;
uint_32 cmdsize;
uint_32 symoff;
uint_32 nsyms;
uint_32 stroff;
uint_32 strsize;
};
ファイルの先頭からのオフセットsymoffの位置に、nsyms個のnlist_64構造体が並んでいる。この構造体にはストリングテーブルの位置も含まれていて、ファイル先頭からのオフセットstroffの位置にstrsize Byteの文字列テーブルが有る。
シンボルテーブルを読み込んでみる
前回使用したhoge1.sで単純なMach-Oを作ってそこから読み込む。
.file "hoge1.s" .text .globl start start: movq $0x2000001, %rax // exit movq hoge(%rip), %rdi syscall .data hoge: .byte 0x5
$ as -o hoge1.o hoge1.s && ld -o hoge1 hoge1.o
前回のロードコマンドを読込むコードにシンボルテーブルとストリングテーブルを読み込むコードを追加する。
ソースコード抜粋(ml2.c)
・ ・ #include <string.h> #include <mach-o/nlist.h> ・ ・ static struct symtab_command symtab_command; static struct nlist_64 *nlists; static char *string_table; ・ ・ int load_symtab_command(FILE *f, struct symtab_command *symtab_cmd) { fread(&symtab_cmd->symoff, sizeof(uint32_t), 1, f); fread(&symtab_cmd->nsyms, sizeof(uint32_t), 1, f); fread(&symtab_cmd->stroff, sizeof(uint32_t), 1, f); fread(&symtab_cmd->strsize, sizeof(uint32_t), 1, f); printf("symoff = 0x%08x\n", symtab_cmd->symoff); printf("nsyms = 0x%08x\n", symtab_cmd->nsyms); printf("stroff = 0x%08x\n", symtab_cmd->stroff); printf("strsize = 0x%08x\n", symtab_cmd->strsize); return 0; } int load_nlist_64(FILE *f, struct nlist_64 *nl_64) { fread(&nl_64->n_un.n_strx, sizeof(uint32_t), 1, f); fread(&nl_64->n_type, sizeof(uint8_t), 1, f); fread(&nl_64->n_sect, sizeof(uint8_t), 1, f); fread(&nl_64->n_desc, sizeof(uint16_t), 1, f); fread(&nl_64->n_value, sizeof(uint64_t), 1, f); printf("n_un.n_strx = 0x%08x\n", nl_64->n_un.n_strx); printf("n_type = 0x%02x\n", nl_64->n_type); printf("n_sect = 0x%02x\n", nl_64->n_sect); printf("n_desc = 0x%04x\n", nl_64->n_desc); printf("n_value = 0x%016llx\n", nl_64->n_value); return 0; } int main(int argc, char** argv) { ・ ・ load_commands = (struct load_command *) malloc(sizeof(struct load_command) * mach_header.ncmds); // load load commands for (i=0; i < mach_header.ncmds; i++) { fread(&load_commands[i].cmd, sizeof(uint32_t), 1, f); fread(&load_commands[i].cmdsize, sizeof(uint32_t), 1, f); printf("=== cmd = 0x%08x, cmdsize=0x%08x ===\n", load_commands[i].cmd, load_commands[i].cmdsize); // next load command location next_pos = ftell(f) + load_commands[i].cmdsize - (sizeof(uint32_t) * 2); if (load_commands[i].cmd == LC_SYMTAB) { // symtab_command printf("=== LC_SYMTAB ===\n"); symtab_command.cmd = load_commands[i].cmd; symtab_command.cmdsize = load_commands[i].cmdsize; load_symtab_command(f, &symtab_command); } fseek(f, next_pos, SEEK_SET); } // load string_table string_table = (char *) malloc(symtab_command.strsize); fseek(f, symtab_command.stroff, SEEK_SET); fread(string_table, symtab_command.strsize, 1, f); // dump string table printf("=== string table ===\n"); p = string_table; for (i = 0; i <= symtab_command.nsyms; i++) { printf("offset = 0x%08x, string_table[%d] = %s\n", (unsigned) (p - string_table), i, p); p = strchr(p, 0) + 1; } // load nlist_64 nlists = (struct nlist_64 *) malloc(sizeof(struct nlist_64) * symtab_command.nsyms); fseek(f, symtab_command.symoff, SEEK_SET); for (i = 0; i < symtab_command.nsyms; i++) { printf("=== nlist_64[%d] ===\n", i); load_nlist_64(f, &nlists[i]); printf("(string = %s)\n", string_table + nlists[i].n_un.n_strx); } ・ ・ }
実行結果(抜粋)
$ ./ml2 hoge1.o === cmd = 0x00000002, cmdsize=0x00000018 === === LC_SYMTAB === symoff = 0x0000018c nsyms = 0x00000002 stroff = 0x000001ac strsize = 0x0000000c === string table === offset = 0x00000000, string_table[0] = offset = 0x00000001, string_table[1] = start offset = 0x00000007, string_table[2] = hoge === nlist_64[0] === n_un.n_strx = 0x00000007 n_type = 0x0e n_sect = 0x02 n_desc = 0x0000 n_value = 0x0000000000000010 (string = hoge) === nlist_64[1] === n_un.n_strx = 0x00000001 n_type = 0x0f n_sect = 0x01 n_desc = 0x0000 n_value = 0x0000000000000000 (string = start)
nlist_64にstringと言うメンバは無いけど、読みやすさのためにストリングテーブルから引っ張ってきた文字列を()内に記述した。答え合わせをするには otool と nm コマンドを使用する。
symtab_commandの内容
$ otool -l hoge1.o hoge1.o: (略) Load command 1 cmd LC_SYMTAB cmdsize 24 symoff 396 nsyms 2 stroff 428 strsize 12 (略)
シンボルテーブル
$ nm -x hoge1.o 0000000000000010 0e 02 0000 0000000000000007 hoge 0000000000000000 0f 01 0000 0000000000000001 start
(nm -x で n_value, n_type, n_sect, n_desc, n_un.strx, 文字列 という順で表示される)
ml2.cで読み込んだ値と一致していることがわかる。フラグ等の細かい意味は今は気にしない。ここではMach-Oの中でシンボルが具体的にどのように格納されているのかが概ねわかれば十分。
Mach-Oの中での位置を図示するとこんな感じ。
MachPortsでbinutilsをインストールしておくとgobjdumpコマンドが使える。
$ sudo port install binutils
このコマンドを使用してシンボルの値がどこを指しているのか確かめてみる。
$ gobjdump -d hoge1.o hoge1.o: file format mach-o-le Disassembly of section .text: 0000000000000000: 0: 48 c7 c0 01 00 00 02 mov $0x2000001,%rax 7: 48 8b 3d 00 00 00 00 mov 0x0(%rip),%rdi # e e: 0f 05 syscall Disassembly of section .data: 0000000000000010 : 10: 05 .byte 0x5
hoge1.s と見比べてみると、ラベル start 部分の命令は仮想メモリアドレス 0x0 へ、ラベル hoge 部分のデータは仮想メモリアドレス 0x10 へ配置されている。これらはそれぞれ、nlist_64[1]、nlist_64[0]の値と一致している。
hoge1.s で hoge(%rip) と記述した部分が 0x2(%rip)ではなく、0x0(%rip) となっている。これはいったい・・・。(hoge と 0x7のmov命令とのrip相対アドレスは、ripは次の命令のアドレスを保持するので 0x10 - 0xe = 0x2)
これにはリロケーションと言うものが絡んでいる。リロケーションに関する事は次回調べよう。
まとめ
参考文献
- Mac OS X ABI Mach-O File Format Reference
- linkers and loaders ↓の作者のが自分のページで公開しているもの
- 作者: JohnR. Levine,榊原一矢,ポジティブエッジ
- 出版社/メーカー: オーム社
- 発売日: 2001/09/01
- メディア: 単行本
- 購入: 7人 クリック: 181回
- この商品を含むブログ (54件) を見る