mach-o編もずいぶんと進んできたので、PEの時にやったように依存するライブラリ情報だけでなく、関数名を表示したいを思って色々を調べてみました。その結果、条件限定かもしれませんがうまく表示できるようになりました。今回はその内容をメモとして公開しようと思います。正しい方法とか指摘してくれると幸いです。
セクションについて
今までのセクション情報で表示できていない部分(わかっていなかった部分)がありました。それは __stubs, __nl_symbol_ptr, __la_symbol_ptr といった一部のセクションでは reserve1, reserve2のメンバに有効な値が入っているということでした。たとえば、 __stubs セクションでは reserve1に インダイレクトシンボルのインデックス開始点が格納され、reserve2にスタブ1要素のバイト数が格納されていました。他の場合でも reserve1にインダイレクトシンボルの開始点が格納されていたりします。
主に動的ライブラリ用の情報を主に調べていくと、情報として必要になるのは symtab, dysymtabのロードコマンドの情報と、__stubs, __nl_symbol_ptr, __la_symbol_ptr のセクション、そして各シンボルテーブルといったところです。含まれているシンボルを一覧する際には symtabの中身を表示すればよい程度でしたが、動的ライブラリの中に含まれる関数(&シンボル)を使っている場合、ここまで簡単なものではなかったです。
__stubsについて
ここの中身は名前の通り外への関数のスタブです。elfバイナリでいうところの PLTっぽい印象を受けました。とりあえずここに含まれる関数の情報を出力するとそのバイナリが使用している外部関数の情報が一覧として出力できそうです。
この関数情報を辿る場合には、まず reserve1で開始インデックス点を取得し、サイズと1要素の関係から含まれる個数を求めます。その後インダイレクトテーブルからインデックスを取得します。このインデックス値がシンボルテーブルのインデックス値となるようなので、シンボルの文字列テーブルからシンボル名を取得することができるようになります。
__nl_symbol_ptrセクション
このセクションのreserve1にはインダイレクトテーブル内での開始インデックス値が格納されています。そこから参照するするインデックス値がテーブル内から求まるので、シンボルテーブルにアクセスします。注意点としては、インダイレクトテーブルからテーブル引きした値が特定の値かどうかチェックする必要があるということです。ある値ではシンボルテーブルへアクセスすると範囲外アクセスとなってしまいます(INDIRECT_SYMBOL_LOCAL, INDIRECT_SYMBOL_ABS)。
__la_symbol_ptrセクション
このセクションもまた __nl_symbol_ptrセクションと同じように処理をします。
アクセスのコードとか
これらの情報にアクセスしている部分のコード抜粋(というか擬似コード?)を掲載します。
// ロードコマンド. symtab_commmand* symtab; dysymtab_command* dysym; section_64* sect_stub = findSection( "__stubs" ); section_64* sect_stubhelper = findSection( "__stub_helper" ); section_64* sect_data_nl_symbol_ptr = findSection( "__nl_symbol_ptr" ); section_64* sect_data_la_symbol_ptr = findSection( "__la_symbol_ptr" ); // シンボルのストリングテーブル void* str_table = (char*)fileHead + symtab->strOff; // シンボルテーブル nlist_64* sym_table = (nlist_64*)( (char*)fileHead + symtab->symOff ); // インダイレクトシンボルテーブル void* indirectTable = (char*)fileHead + dysym->indirectSymOff; // スタブに含まれる個数を求める int stubCount = sect_stub->size / sect_stub->reserved2; // スタブが参照するインダイレクトシンボルテーブル開始インデックス int stubUseIndirectSymIndex = sect_stub->reserved1; void* nl_symbol_ptr_section = (char*)fileHead + sect_data_nl_symbol_ptr->offset; int nlsymbolPtrCount = sect_data_nl_symbol_ptr->size / sizeof(uint64_t); printf( "nl_symbol_ptr\n" ); for( int i = 0; i < nlsymbolPtrCount; ++i ) { uint32_t indirectIndexOff = sect_data_nl_symbol_ptr->reserved1; uint32_t indirectIndex = ( (uint32_t*)indirectTable)[ indirectIndexOff+i ]; nlist_64* symbol = NULL; char* symbolName = NULL; if( indirectIndex == INDIRECT_SYMBOL_LOCAL || indirectIndex == INDIRECT_SYMBOL_ABSOLUTE ) { symbolName = "ABSOLUTE"; // とりあえず. } else { symbol = &(sym_table[ indirectIndex ]); symbolName = (char*)str_table + symbol->n_un.n_strx; } uint32_t offset = sect_data_nl_symbol_ptr->offset+sizeof(uint64_t) * i; printf( " offset:%08X data: %016llX (%s)\n", offset, *(uint64_t*)((char*)fileHead+offset), symbolName ); } void* la_symbol_ptr_section = (char*)fileHead + sect_data_la_symbol_ptr->offset; int lasymbolPtrCount = sect_data_la_symbol_ptr->size / sizeof(uint64_t); printf( "la_symbol_ptr\n" ); for( int i = 0; i < lasymbolPtrCount; ++i ) { uint32_t indirectIndexOff = sect_data_la_symbol_ptr->reserved1; uint32_t indirectIndex = ((uint32_t*)indirectTable)[indirectIndexOff+i]; nlist_64* symbol = NULL; char* symbolName = NULL; if( indirectIndex == INDIRECT_SYMBOL_LOCAL || indirectIndex == INDIRECT_SYMBOL_ABSOLUTE ) { symbolName = "ABSOLUTE"; // とりあえず } else { symbol = &(sym_table[ indirectIndex ]); symbolName = (char*)str_table + symbol->n_un.n_strx; } uint32_t offset = sect_data_la_symbol_ptr->offset + sizeof(uint64_t)*i; printf( " offset:%08X data: %016llX (%s)\n", offset, *(uint64_t*)((char*)fileHead+offset), symbolName ); } printf( "*** import func(?) ***\n" ); for( int i = 0; i < stubCount; ++i ) { int idx = stubUseIndirectSymIndex + i; uint32_t indirectIndex = ((uint32_t*)indirectTable)[ idx ]; nlist_64* indirectFunc = &sym_table[ indirectIndex ]; uint64_t addr = sect_stub->addr + sect_stub->reserved2 * i; char* funcName = (char*)str_table + indirectFunc->n_un.n_strx; printf( "%d : %s (%llx)\n", i, funcName, addr ); }
このコードにより今まで使用しているサンプルプログラム(C)の実行体を表示させてみると以下のようになります。オフセットや中身のデータは正しそうですし、一応関数名(シンボル)もまた表示されているのでうまくいっていそうです。
nl_symbol_ptr offset:00001000 data: 0000000000000000 (dyld_stub_binder) offset:00001008 data: 0000000000000000 (ABSOLUTE) la_symbol_ptr offset:00001010 data: 0000000100000F54 (_printf) *** import func(?) *** 0 : _printf (100000f3e)
もうすこし複雑なプログラムにしてみようと思います。以下に示すようにCの標準関数とC++の関数を使用してみました。これならば依存ライブラリは2つになりますので、さっきよりは多くの情報が出力されるのが期待できます。
#include#include int main() { std::cout << "Hello,world(C++)" << std::endl; printf( "Hello,world(C)\n" ); return 0; }
これに対する実行結果は以下のようになりました。
nl_symbol_ptr offset:00001000 data: 0000000000000000 (dyld_stub_binder) offset:00001008 data: 0000000000000000 (ABSOLUTE) la_symbol_ptr offset:00001028 data: 0000000100000ECC (__ZNSolsEPFRSoS_E) offset:00001030 data: 0000000100000ED6 (__ZNSt8ios_base4InitC1Ev) offset:00001038 data: 0000000100000EE0 (__ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc) offset:00001040 data: 0000000100000EEA (___cxa_atexit) offset:00001048 data: 0000000100000EF4 (_printf) *** import func(?) *** 0 : __ZNSolsEPFRSoS_E (100000e9c) 1 : __ZNSt8ios_base4InitC1Ev (100000ea2) 2 : __ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc (100000ea8) 3 : ___cxa_atexit (100000eae) 4 : _printf (100000eb4)
C++のマングルが入っています。明らかにC++のランタイムへの参照が見て取れます。また先ほどと同様のC関数もまた含まれています。importの項目は依存している関数や変数の情報だけを表示しているようにも見え、__stubsの情報が今求めているImport関数一覧と近いものという考えが合っているように思います。
ただ実際のところ、dysymの情報にある undef extsymbol の情報を表示させてみると以下のようになり、__stubsによる一覧よりも多いことがわかります。より正確にはこちらの情報の方がImportの一覧として正しい気もしてきます。
*** undef extsymbol *** 0 : __ZNSolsEPFRSoS_E 1 : __ZNSt8ios_base4InitC1Ev 2 : __ZNSt8ios_base4InitD1Ev 3 : __ZSt4cout 4 : __ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_ 5 : __ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc 6 : ___cxa_atexit 7 : _printf 8 : dyld_stub_binder