前回は概要とセクション情報表示程度だったので、今回はもうすこしロードコマンドの表示をすすめて見ようと思います。
ロードコマンドの種別によって、そのコマンドのサイズと型が決まります。
あとはこれに応じてメンバの表示を行ってみたものが下記の結果です。
ロードコマンドの型の準備と表示が面倒なだけなので、プログラムコードは割愛します。
Mach-O 64bit CPU TYPE : x86_x64 CPU SUB TYPE : i386 ALL(Lib64) FileType : MH_EXECUTE Cmd(s) : 16 CmdSize : 1296 Flag(s) : MH_NOUNDEFS MH_TWOLEVEL MH_PIE (200085) Load Command(s) [ 0] : LC_SEGMENT_64 __PAGEZERO vmaddr=0x0000000000000000, vmsize=0x0000000100000000 fileoff=0x0000000000000000, filesize=0x0000000000000000 maxprot=0x00000000, initprot=0x00000000 nSect(s)=0 [ 1] : LC_SEGMENT_64 __TEXT vmaddr=0x0000000100000000, vmsize=0x0000000000001000 fileoff=0x0000000000000000, filesize=0x0000000000001000 maxprot=0x00000007, initprot=0x00000005 nSect(s)=6 section[0] : __TEXT [__text] section[1] : __TEXT [__stubs] section[2] : __TEXT [__stub_helper] section[3] : __TEXT [__cstring] section[4] : __TEXT [__unwind_info] section[5] : __TEXT [__eh_frame] [ 2] : LC_SEGMENT_64 __DATA vmaddr=0x0000000100001000, vmsize=0x0000000000001000 fileoff=0x0000000000001000, filesize=0x0000000000001000 maxprot=0x00000007, initprot=0x00000003 nSect(s)=2 section[0] : __DATA [__nl_symbol_ptr] section[1] : __DATA [__la_symbol_ptr] [ 3] : LC_SEGMENT_64 __LINKEDIT vmaddr=0x0000000100002000, vmsize=0x0000000000001000 fileoff=0x0000000000002000, filesize=0x0000000000000130 maxprot=0x00000007, initprot=0x00000001 nSect(s)=0 [ 4] : LC_DYLD_INFO_ONLY rebaseOff=8192 rebaseSize=8 bindOff=8200 bindSize=24 weakBindOff=0 weakBindSize=0 lazyBindOff=8224 lazyBindSize=16 exportOff=8240 exportSize=48 [ 5] : LC_SYMTAB symOff=8360 nSyms=4 strOff=8440 strSize=56 [ 6] : LC_DYSYMTAB ilocalSym=0 nLocalSym=0 iExtdefSym=0 nExtdefSym=2 iUndefSym=2 nUndefSym=2 tocOff=0 nToc=0 modtabOff=0 nModtab=0 extrefsymOff=0 nExtrefSyms=0 indirectSymOff=8424 nIndirectSyms=4 extrelOff=0 nExtrel=0 locrelOff=0 nlocrel=0 [ 7] : LC_LOAD_DYLINKER name: /usr/lib/dyld [ 8] : LC_UUID uuid: 69016DA8-F21836CE-98122CF8-DDD270E2 [ 9] : LC_VERSION_MIN_MACOSX version = 10.8.0 [10] : LC_SOURCE_VERSION version=0.0.0.0 [11] : LC_MAIN entryOff=3856 stackSize=0 [12] : LC_LOAD_DYLIB /usr/lib/libSystem.B.dylib [13] : LC_FUNCTION_STARTS dataOff=8288 dataSize=8 [14] : LC_DATA_IN_CODE dataOff=8296 dataSize=0 [15] : LC_DYLIB_CODE_SIGN_DRS dataOff=8296 dataSize=64
セグメントやセクション以外にも色々と興味深いものがあります。
”otool -L”コマンドで表示される依存関係は LC_LOAD_DYLIB の情報を表示していることがわかります(予想できます)。上記の物は若干otoolの結果と違うので見直してみます。
LC_LOAD_DYLIBのロードコマンド構造体の中身は以下のようになっています。上記では手抜き?してnameだけ表示していました。
これをcurrent_versionやcompatibility_versionについても表示してみます。
struct dylib { union lc_str name; uint32_t timestamp; uint32_t current_version; uint32_t compatibility_version; }; struct dylib_command { uint32_t cmd; // LC_ID_DYLIB, LC_LOAD_DYLIB, LC_LOAD_WEAK_DYLIB uint32_t cmdSize; struct dylib dylib; }; void dispLCLoadDylib( dylib_command* lc ) { printf( "LC_LOAD_DYLIB\n" ); printf( " %s (compatibility ver %u.%u.%u, current ver %u.%u.%u)\n", (char*)lc + lc->dylib.name.offset, (lc->dylib.compatibility_version >> 16) &0xFFu, (lc->dylib.compatibility_version >> 8)&0xFFu, (lc->dylib.compatibility_version & 0xFFu), (lc->dylib.current_version >> 16) & 0xFFu, (lc->dylib.current_version >> 8) & 0xFFu, (lc->dylib.current_version & 0xFFu) ); }
これによりバージョンの情報が出るようになって、より結果が期待するものに近づきました。
[12] : LC_LOAD_DYLIB /usr/lib/libSystem.B.dylib (compatibility ver 1.0.0, current ver 169.3.0)
シンボルテーブルの中身表示
まずはLC_SYMTABの中身を表示してみます。このロードコマンドの構造体は以下のようになっています。
struct symtab_commmand { uint32_t cmd; // LC_SYMTAB uint32_t cmdSize; uint32_t symOff; uint32_t nSyms; uint32_t strOff; uint32_t strSize; };
symOffやstrOffは先頭からのオフセットが入っています。そして nSyms がシンボル数を示しています。
symOffの位置からシンボルの情報が配列として格納されています。この配列要素の構造が nlist.h, stab.h にあるらしいです。Windowsなのでこの型を自前で定義すると以下のようになりました。
struct nlist { union { uint32_t n_strx; } n_un; uint8_t nType; uint8_t nSect; uint16_t nDesc; uint32_t nValue; }; struct nlist_64 { union { uint32_t n_strx; } n_un; uint8_t nType; uint8_t nSect; uint16_t nDesc; uint64_t nValue; };
そしてファイルの先頭からのオフセットを足してシンボルテーブルやストリングテーブルの位置を確定させ、シンボル名を表示する部分は以下のようになってます。
// 表示する部分 void dispSymbolTable(void* fileHead, load_command* cmdHead, int cmds) { printf( "---- Symbol table(s) ----\n" ); nlist* tblSymbols = (nlist*)( (char*)fileHead + lc_symtab->symOff ); char* tblStrings = (char*)( (char*)fileHead + lc_symtab->strOff ); int symbolCount = lc_symtab->nSyms; for( int i = 0; i < symbolCount; ++i ) { nlist* l = &(tblSymbols[i]); uint32_t pos = l->n_un.n_strx; char* symbolName = tblStrings + pos; printf( " %s\n", symbolName ); } }
これによりサンプルのプログラムでは以下のように表示されました。
---- Symbol table(s) ---- __mh_execute_header _main _printf dyld_stub_binder
ここまでの関係を図示するとこんな感じになります。
シンボル(関数とか)の位置を表示してみる
ここまででシンボルの名前情報は取得できるようになったので、次にそのシンボルがどこにあるのかを調べてみます。
シンボルに関しては nlist構造体で情報が定義されています。
struct nlist { union { uint32_t n_strx; } n_un; uint8_t nType; uint8_t nSect; uint16_t nDesc; uint32_t nValue; }; struct nlist_64 { union { uint32_t n_strx; } n_un; uint8_t nType; uint8_t nSect; uint16_t nDesc; uint64_t nValue; };
nSectメンバはそのシンボルがあるセクションのインデックスです。nDescについてはシンボルの参照に関する追加情報といったところです。
大事なのは nTypeメンバで、この値によって、nValueの値が意味する内容が変化します。そしてこのnTypeが取り得る値は以下の項目のOR結合となっているようです。
// nlistのnTypeメンバのフラグ値 #define NTYPE_FIELD_UNDF (0x0) #define NTYPE_FIELD_ABS (0x02) #define NTYPE_FIELD_SECT (0x0e) #define NTYPE_FIELD_PBUD (0x0c) #define NTYPE_FIELD_INDR (0x0a) // nlistのnTypeメンバ用マスク値 #define NTYPE_FIELD_STAB_MASK (0xe0) #define NTYPE_FIELD_PEXT_MASK (0x10) #define NTYPE_FIELD_EXT_MASK (0x01) #define NTYPE_FIELD_TYPE_MASK (0x0e)
これらの情報を解釈して、もうすこしシンボルに関して情報を出すようにしてみました。シンボルが示す関数が自身のものの場合にはその仮想アドレスを表示するようにしています。
void dispSymbolTables( void* fileHead, load_command* cmdHead, int cmds ) { symtab_commmand* lc_symtab = findSymtabCommand( cmdHead, cmds ); if( lc_symtab == NULL ) { return; } printf( "---- Symbol table(s) ----\n" ); nlist* tblSymbols = (nlist*)( (char*)fileHead + lc_symtab->symOff ); char* tblStrings = (char*)( (char*)fileHead + lc_symtab->strOff ); int symbolCount = lc_symtab->nSyms; for( int i = 0; i < symbolCount; ++i ) { nlist* l = &(tblSymbols[i]); uint32_t pos = l->n_un.n_strx; char* symbolName = tblStrings + pos; printf( " " ); printf( " %s", symbolName ); if( (l->nType & NTYPE_FIELD_TYPE_MASK) == NTYPE_FIELD_SECT ) { printf( " vaddr=0x%08x ", l->nValue ); printf( " (defined at sect%d) ", l->nSect ); if( (l->nType & NTYPE_FIELD_EXT_MASK) != 0 ) { printf( " [Public] " ); } } if( (l->nType & NTYPE_FIELD_TYPE_MASK) == NTYPE_FIELD_UNDF ) { printf( " (undefined.) " ); // 別モジュールで定義されているシンボルで本モジュールからは参照している. } printf( "\n"); } }
64bit版も同様に解釈部分を実装して表示させると以下のようにシンボルの情報が表示されます。
Symbol table(s) __mh_execute_header vaddr=0x100000000 (defined at sect1) [Public] _main vaddr=0x100000f10 (defined at sect1) [Public] _printf (undefined.) dyld_stub_binder (undefined.)
まとめ
ロードコマンドの中身を表示することができるようになりました。そしてその際にシンボルテーブルが格納されていることがわかったので、シンボルテーブルの中身を解釈して、シンボル名やアドレス情報を確認することができるようになりました。