前回は概要とセクション情報表示程度だったので、今回はもうすこしロードコマンドの表示をすすめて見ようと思います。
ロードコマンドの種別によって、そのコマンドのサイズと型が決まります。
あとはこれに応じてメンバの表示を行ってみたものが下記の結果です。
ロードコマンドの型の準備と表示が面倒なだけなので、プログラムコードは割愛します。
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.)
まとめ
ロードコマンドの中身を表示することができるようになりました。そしてその際にシンボルテーブルが格納されていることがわかったので、シンボルテーブルの中身を解釈して、シンボル名やアドレス情報を確認することができるようになりました。
