動的ライブラリの観察その4
前回は、動的ライブラリの関数へのシンボル参照は、/usr/lib/libSystem.B.dylib の中の dyld_stub_binder の中で解決された〜、というところまで見た。今回は dyld_stub_binder の中で何が起こっているのかをもう少し見てみる。
ソースは前回と同じ。
動的ライブラリはこれ
$ cat libfoo1.c int do_foo(int i) { return i; }
$ gcc -dynamiclib -o libfoo1.dylib libfoo1.c
動的ライブラリを呼び出す方はこれ
$ cat hoge1.c int do_foo(int); int main(int argc, char **argv) { do_foo(5); do_foo(6); return 0; }
$ gcc -L./ -lfoo1 -o hoge1 hoge1.c
gdb で hoge1 を実行。
$gdb hoge1
前回までのおさらい・・・・は前回、前々回参照ということで。dyld_stub_binder は dyld_stub_binder.s で定義されているアセンブリコード。
最初の do_foo(int) 呼び出し直後の dyld_stub_binder でブレイクしてディスアセンブルしてみる。
こんな感じ(抜粋)
0x00007fff8020bfa8 <dyld_stub_binder+0>: push %rbp 0x00007fff8020bfa9 <dyld_stub_binder+1>: mov %rsp,%rbp 0x00007fff8020bfac <dyld_stub_binder+4>: sub $0xc0,%rsp (レジスタの退避) 0x00007fff8020c011 <dyld_stub_binder+105>: mov 0x8(%rbp),%rdi 0x00007fff8020c015 <dyld_stub_binder+109>: mov 0x10(%rbp),%rsi 0x00007fff8020c019 <dyld_stub_binder+113>: callq 0x7fff8010fc4f <_Z21_dyld_fast_stub_entryPvl> 0x00007fff8020c01e <dyld_stub_binder+118>: mov %rax,%r11 (レジスタの復帰) 0x00007fff8020c07f <dyld_stub_binder+215>: add $0xc0,%rsp 0x00007fff8020c086 <dyld_stub_binder+222>: pop %rbp 0x00007fff8020c087 <dyld_stub_binder+223>: add $0x10,%rsp 0x00007fff8020c08b <dyld_stub_binder+227>: jmpq *%r11
前回はこの _Z21_dyld_fast_stub_entryPvl の中で do_foo(int)のアドレスが解決されて戻り値として戻ってきているなと言うところまでしか見ていなかった。今回はこの先をもうちょっと観てみよう。
このときのvmmapは以下のように成っている。
__TEXT 0000000100000000-0000000100001000 [ 4K] r-x/rwx SM=COW /Users/teru/Documents/low_level/blog_dylib2/hoge1 __DATA 0000000100001000-0000000100002000 [ 4K] rw-/rwx SM=PRV /Users/teru/Documents/low_level/blog_dylib2/hoge1 __LINKEDIT 0000000100002000-0000000100003000 [ 4K] r--/rwx SM=COW /Users/teru/Documents/low_level/blog_dylib2/hoge1 __TEXT 0000000100003000-0000000100004000 [ 4K] r-x/rwx SM=COW /Users/teru/Documents/low_level/blog_dylib2/libfoo1.dylib __LINKEDIT 0000000100004000-0000000100005000 [ 4K] r--/rwx SM=COW /Users/teru/Documents/low_level/blog_dylib2/libfoo1.dylib STACK GUARD 0000000100005000-0000000100006000 [ 4K] ---/rwx SM=NUL MALLOC (admin) 0000000100006000-0000000100007000 [ 4K] rw-/rwx SM=COW STACK GUARD 0000000100007000-0000000100009000 [ 8K] ---/rwx SM=NUL MALLOC (admin) 0000000100009000-0000000100014000 [ 44K] rw-/rwx SM=COW STACK GUARD 0000000100014000-0000000100016000 [ 8K] ---/rwx SM=NUL MALLOC (admin) 0000000100016000-0000000100021000 [ 44K] rw-/rwx SM=PRV STACK GUARD 0000000100021000-0000000100022000 [ 4K] ---/rwx SM=NUL MALLOC (admin) 0000000100022000-0000000100023000 [ 4K] r--/rwx SM=COW MALLOC_TINY 0000000100100000-0000000100200000 [ 1024K] rw-/rwx SM=COW DefaultMallocZone_0x100006000 STACK GUARD 00007fff5bc00000-00007fff5f400000 [ 56.0M] ---/rwx SM=NUL Stack 00007fff5f400000-00007fff5fbfd000 [ 8180K] rw-/rwx SM=ZER Stack 00007fff5fbfd000-00007fff5fbfe000 [ 4K] rw-/rwx SM=PRV Stack 00007fff5fbfe000-00007fff5fbff000 [ 4K] rw-/rwx SM=ZER Stack 00007fff5fbff000-00007fff5fc00000 [ 4K] rw-/rwx SM=COW thread 0 __TEXT 00007fff5fc00000-00007fff5fc04000 [ 16K] r-x/rwx SM=COW /usr/lib/dyld __TEXT 00007fff5fc04000-00007fff5fc05000 [ 4K] r-x/rwx SM=PRV /usr/lib/dyld __TEXT 00007fff5fc05000-00007fff5fc0a000 [ 20K] r-x/rwx SM=COW /usr/lib/dyld __TEXT 00007fff5fc0a000-00007fff5fc0b000 [ 4K] r-x/rwx SM=PRV /usr/lib/dyld __TEXT 00007fff5fc0b000-00007fff5fc3c000 [ 196K] r-x/rwx SM=COW /usr/lib/dyld __DATA 00007fff5fc3c000-00007fff5fc41000 [ 20K] rw-/rwx SM=COW /usr/lib/dyld __DATA 00007fff5fc41000-00007fff5fc43000 [ 8K] rw-/rwx SM=ZER /usr/lib/dyld __DATA 00007fff5fc43000-00007fff5fc44000 [ 4K] rw-/rwx SM=COW /usr/lib/dyld __DATA 00007fff5fc44000-00007fff5fc7b000 [ 220K] rw-/rwx SM=ZER /usr/lib/dyld __LINKEDIT 00007fff5fc7b000-00007fff5fc8f000 [ 80K] r--/rwx SM=COW /usr/lib/dyld __DATA 00007fff7001a000-00007fff7003d000 [ 140K] rw-/rwx SM=COW /usr/lib/libSystem.B.dylib __TEXT 00007fff80103000-00007fff8020b000 [ 1056K] r-x/r-x SM=COW /usr/lib/libSystem.B.dylib __TEXT 00007fff8020b000-00007fff8020c000 [ 4K] r-x/rwx SM=COW /usr/lib/libSystem.B.dylib __TEXT 00007fff8020b000-00007fff803cb000 [ 1792K] r-x/r-x SM=COW /usr/lib/libSystem.B.dylib __TEXT 00007fff86b99000-00007fff86b9e000 [ 20K] r-x/r-x SM=COW /usr/lib/system/libmathCommon.A.dylib __LINKEDIT 00007fff870f6000-00007fff88627000 [ 21.2M] r--/r-- SM=COW /usr/lib/system/libmathCommon.A.dylib
_Z21_dyld_fast_stub_entryPvl のアドレス 0x7fff8010fc4f は libSystem.B.dylib に含まれていることがわかる。_Z21_dyld_fast_stub_entryPvl というのはデマングルすると _dyld_fast_stub_entry(void*, long) というC++の関数で dyldAPIsInLibSystem.cpp で定義されている。
dyld_stub_binder.s もそうだが、dyldのソースに記述されているコードがなぜ libSystem.B.dylib に含まれているのかは謎。どこかで LibSystem は Umbrella Framework だからどうのこうのという記述を見た気がするが・・・。まぁ、そのうち調べよう。
C++の関数はこう
#if __i386__ || __x86_64__ __attribute__((visibility("hidden"))) void* _dyld_fast_stub_entry(void* loadercache, long lazyinfo) { DYLD_NO_LOCK_THIS_BLOCK; static void* (*p)(void*, long) = NULL; if(p == NULL) _dyld_func_lookup("__dyld_fast_stub_entry", (void**)&p); return p(loadercache, lazyinfo); } #endif
実行中の物をディスアセンブルしたものはこう
(gdb) b _Z21_dyld_fast_stub_entryPvl Breakpoint 4 at 0x7fff8010fc5f (gdb) c Continuing. Breakpoint 4, 0x00007fff8010fc5f in _dyld_fast_stub_entry () (gdb) disas Dump of assembler code for function _Z21_dyld_fast_stub_entryPvl: 0x00007fff8010fc4f <_Z21_dyld_fast_stub_entryPvl+0>: push %rbp 0x00007fff8010fc50 <_Z21_dyld_fast_stub_entryPvl+1>: mov %rsp,%rbp 0x00007fff8010fc53 <_Z21_dyld_fast_stub_entryPvl+4>: mov %rbx,-0x10(%rbp) 0x00007fff8010fc57 <_Z21_dyld_fast_stub_entryPvl+8>: mov %r12,-0x8(%rbp) 0x00007fff8010fc5b <_Z21_dyld_fast_stub_entryPvl+12>: sub $0x10,%rsp 0x00007fff8010fc5f <_Z21_dyld_fast_stub_entryPvl+16>: mov %rdi,%r12 0x00007fff8010fc62 <_Z21_dyld_fast_stub_entryPvl+19>: mov %rsi,%rbx 0x00007fff8010fc65 <_Z21_dyld_fast_stub_entryPvl+22>: cmpq $0x0,-0x100d9525(%rip) # 0x7fff70036748 <_Zz21_dyld_fast_stub_entryPvlE1p> 0x00007fff8010fc6d <_Z21_dyld_fast_stub_entryPvl+30>: jne 0x7fff8010fc82 <_Z21_dyld_fast_stub_entryPvl+51> 0x00007fff8010fc6f <_Z21_dyld_fast_stub_entryPvl+32>: lea -0x100d952e(%rip),%rsi # 0x7fff70036748 <_ZZ21_dyld_fast_stub_entryPvlE1p> 0x00007fff8010fc76 <_Z21_dyld_fast_stub_entryPvl+39>: lea 0x143f93(%rip),%rdi # 0x7fff80253c10 <commonCryptoVersionString+96> 0x00007fff8010fc7d <_Z21_dyld_fast_stub_entryPvl+46>: callq 0x7fff80109ffa <_dyld_func_lookup> 0x00007fff8010fc82 <_Z21_dyld_fast_stub_entryPvl+51>: mov %rbx,%rsi 0x00007fff8010fc85 <_Z21_dyld_fast_stub_entryPvl+54>: mov %r12,%rdi 0x00007fff8010fc88 <_Z21_dyld_fast_stub_entryPvl+57>: mov -0x100d9547(%rip),%r11 # 0x7fff70036748 <_ZZ21_dyld_fast_stub_entryPvlE1p> 0x00007fff8010fc8f <_Z21_dyld_fast_stub_entryPvl+64>: mov (%rsp),%rbx 0x00007fff8010fc93 <_Z21_dyld_fast_stub_entryPvl+68>: mov 0x8(%rsp),%r12 0x00007fff8010fc98 <_Z21_dyld_fast_stub_entryPvl+73>: leaveq 0x00007fff8010fc99 <_Z21_dyld_fast_stub_entryPvl+74>: jmpq *%r11 End of assembler dump.
余談だが、_Z21_dyld_fast_stub_entryPvl+22 にちょっと注目。ここは C++ の if(p == NULL) に該当するところだが、0x7fff70036748 というアドレスから、static なローカル変数 p はスタックではなくて libSystem.B.dylib の __DATA セグメントに配置されている。static(静的)ってこういう事なんですね〜。
_dyld_func_lookup は dyldLibSystemGlue.c で定義されている
struct __DATA__dyld { long lazy; int (*lookup)(const char*, void**); }; static struct __DATA__dyld myDyldSection __attribute__ ((section ("__DATA,__dyld"))) = { 0, NULL }; __attribute__((weak, visibility("hidden"))) int _dyld_func_lookup(const char* dyld_func_name, void **address) { return (*myDyldSection.lookup)(dyld_func_name, address); }
実行中の物をディスアセンブルしたものはこう。アドレスからこれも libSystem.B.dylib 内のコードだな。
0x00007fff80109ffa <_dyld_func_lookup+0>: push %rbp 0x00007fff80109ffb <_dyld_func_lookup+1>: mov %rsp,%rbp 0x00007fff80109ffe <_dyld_func_lookup+4>: mov -0x100ecb9d(%rip),%r11 # 0x7fff7001d468 <Mydyldsection+8> 0x00007fff8010a005 <_dyld_func_lookup+11>: leaveq 0x00007fff8010a006 <_dyld_func_lookup+12>: jmpq *%r11 0x00007fff8010a009 <_dyld_func_lookup+15>: nop
Cのソースと見比べると myDyldSection.lookup のアドレスは 0x7fff7001d468 だ。
そこにはどんな値が入っているのかというと
(gdb) x /1xg 0x7fff7001d468 0x7fff7001d468: 0x00007fff5fc01008
vmmapから /usr/lib/dyld 内のアドレスで有ることがわかる。このあたりから制御がdyldに移るようだ。内容はこう。
(gdb) disas 0x00007fff5fc01008 Dump of assembler code for function __dyld_dyld_func_lookup: 0x00007fff5fc01008 <__dyld_dyld_func_lookup+0>: jmpq 0x7fff5fc07e43 <__dyld__Z18lookupDyldFunctionPKcPm> 0x00007fff5fc0100d <__dyld_dyld_func_lookup+5>: nop 0x00007fff5fc0100e <__dyld_dyld_func_lookup+6>: nop 0x00007fff5fc0100f <__dyld_dyld_func_lookup+7>: nop
該当するソースは dyldStartup.s というアセンブリコード。このアドレスがどの様にして上記の myDyldSection.lookup に代入されたのかは謎。処理内容は __dyld__Z18lookupDyldFunctionPKcPm、つまり lookupDyldFunction(char const*, unsigned long*) へジャンプしてるだけ。
lookupDyldFunction(char const*, unsigned long*) は dyldAPIs.cpp でこのように定義されている。
bool lookupDyldFunction(const char* name, uintptr_t* address) { for (const dyld_func* p = dyld_funcs; p->name != NULL; ++p) { if ( strcmp(p->name, name) == 0 ) { if( p->implementation == unimplemented ) dyld::log("unimplemented dyld function: %s\n", p->name); *address = (uintptr_t)p->implementation; return true; } } *address = 0; return false; }
dyld_funcs は dyldAPIs.cpp で定義されているこのようなテーブルで、 dyld が提供するAPI名と実装する関数のアドレスを対応付けるテーブルだ。
struct dyld_func { const char* name; void* implementation; }; static struct dyld_func dyld_funcs[] = { {"__dyld_register_func_for_add_image", (void*)_dyld_register_func_for_add_image }, {"__dyld_register_func_for_remove_image", (void*)_dyld_register_func_for_remove_image }, ・・・ #if !__arm__ {"__dyld_find_unwind_sections", (void*)client_dyld_find_unwind_sections }, #endif #if __i386__ || __x86_64__ {"__dyld_fast_stub_entry", (void*)dyld::fastBindLazySymbol }, #endif {"__dyld_image_path_containing_address", (void*)dyld_image_path_containing_address }, ・・・ {NULL, 0} };
で、今は何というAPIを探してるんでしたっけ?というと _dyld_fast_stub_entry(void* loadercache, long lazyinfo) の中で _dyld_func_lookup("__dyld_fast_stub_entry", (void**)&p) というふうに呼び出されているので __dyld_fast_stub_entry だ。
一応確認。関数の第一引数はrdiレジスタなので、lookupDyldFunction() まで実行してから以下のように確認。
(gdb) i reg rip rip 0x7fff5fc07e43 0x7fff5fc07e43 <__dyld__Z18lookupDyldFunctionPKcPm> (gdb) x /1s $rdi 0x7fff80253c10: "__dyld_fast_stub_entry"
確かに __dyld_fast_stub_entry を探している。
上記のテーブルによると、その実装は dyld::fastBindLazySymbol となっている。これもデバッガで確認してみる。lookupDyldFunction() をディスアセンブルするとこうなる。
(gdb) disas Dump of assembler code for function __dyld__Z18lookupDyldFunctionPKcPm: 0x00007fff5fc07e43 <__dyld__Z18lookupDyldFunctionPKcPm+0>: push %rbp 0x00007fff5fc07e44 <__dyld__Z18lookupDyldFunctionPKcPm+1>: mov %rsp,%rbp 0x00007fff5fc07e47 <__dyld__Z18lookupDyldFunctionPKcPm+4>: push %r14 0x00007fff5fc07e49 <__dyld__Z18lookupDyldFunctionPKcPm+6>: push %r13 0x00007fff5fc07e4b <__dyld__Z18lookupDyldFunctionPKcPm+8>: push %r12 0x00007fff5fc07e4d <__dyld__Z18lookupDyldFunctionPKcPm+10>: push %rbx 0x00007fff5fc07e4e <__dyld__Z18lookupDyldFunctionPKcPm+11>: mov %rdi,%r13 0x00007fff5fc07e51 <__dyld__Z18lookupDyldFunctionPKcPm+14>: mov %rsi,%r14 0x00007fff5fc07e54 <__dyld__Z18lookupDyldFunctionPKcPm+17>: lea 0x35945(%rip),%r12 # 0x7fff5fc3d7a0 <__dyld__ZL10dyld_funcs> 0x00007fff5fc07e5b <__dyld__Z18lookupDyldFunctionPKcPm+24>: jmp 0x7fff5fc07e9e <__dyld__Z18lookupDyldFunctionPKcPm+91> 0x00007fff5fc07e5d <__dyld__Z18lookupDyldFunctionPKcPm+26>: mov %r13,%rsi 0x00007fff5fc07e60 <__dyld__Z18lookupDyldFunctionPKcPm+29>: mov %rbx,%rdi 0x00007fff5fc07e63 <__dyld__Z18lookupDyldFunctionPKcPm+32>: callq 0x7fff5fc22fb0 <__dyld_strcmp> 0x00007fff5fc07e68 <__dyld__Z18lookupDyldFunctionPKcPm+37>: test %eax,%eax 0x00007fff5fc07e6a <__dyld__Z18lookupDyldFunctionPKcPm+39>: jne 0x7fff5fc07e9a <__dyld__Z18lookupDyldFunctionPKcPm+87> 0x00007fff5fc07e6c <__dyld__Z18lookupDyldFunctionPKcPm+41>: lea 0x23b(%rip),%rax # 0x7fff5fc080ae <__dyld__ZL13unimplementedv> 0x00007fff5fc07e73 <__dyld__Z18lookupDyldFunctionPKcPm+48>: cmp %rax,0x8(%r12) 0x00007fff5fc07e78 <__dyld__Z18lookupDyldFunctionPKcPm+53>: jne 0x7fff5fc07e8b <__dyld__Z18lookupDyldFunctionPKcPm+72> 0x00007fff5fc07e7a <__dyld__Z18lookupDyldFunctionPKcPm+55>: mov %rbx,%rsi 0x00007fff5fc07e7d <__dyld__Z18lookupDyldFunctionPKcPm+58>: lea 0x1e184(%rip),%rdi # 0x7fff5fc26008 0x00007fff5fc07e84 <__dyld__Z18lookupDyldFunctionPKcPm+65>: xor %eax,%eax 0x00007fff5fc07e86 <__dyld__Z18lookupDyldFunctionPKcPm+67>: callq 0x7fff5fc02973 <__dyld__ZN4dyld3logEPKcz> 0x00007fff5fc07e8b <__dyld__Z18lookupDyldFunctionPKcPm+72>: mov 0x8(%r12),%rax 0x00007fff5fc07e90 <__dyld__Z18lookupDyldFunctionPKcPm+77>: mov %rax,(%r14) 0x00007fff5fc07e93 <__dyld__Z18lookupDyldFunctionPKcPm+80>: mov $0x1,%eax 0x00007fff5fc07e98 <__dyld__Z18lookupDyldFunctionPKcPm+85>: jmp 0x7fff5fc07eb0 <__dyld__Z18lookupDyldFunctionPKcPm+109> 0x00007fff5fc07e9a <__dyld__Z18lookupDyldFunctionPKcPm+87>: add $0x10,%r12 0x00007fff5fc07e9e <__dyld__Z18lookupDyldFunctionPKcPm+91>: mov (%r12),%rbx 0x00007fff5fc07ea2 <__dyld__Z18lookupDyldFunctionPKcPm+95>: test %rbx,%rbx 0x00007fff5fc07ea5 <__dyld__Z18lookupDyldFunctionPKcPm+98>: jne 0x7fff5fc07e5d <__dyld__Z18lookupDyldFunctionPKcPm+26> 0x00007fff5fc07ea7 <__dyld__Z18lookupDyldFunctionPKcPm+100>: movq $0x0,(%r14) 0x00007fff5fc07eae <__dyld__Z18lookupDyldFunctionPKcPm+107>: xor %eax,%eax 0x00007fff5fc07eb0 <__dyld__Z18lookupDyldFunctionPKcPm+109>: pop %rbx 0x00007fff5fc07eb1 <__dyld__Z18lookupDyldFunctionPKcPm+110>: pop %r12 0x00007fff5fc07eb3 <__dyld__Z18lookupDyldFunctionPKcPm+112>: pop %r13 0x00007fff5fc07eb5 <__dyld__Z18lookupDyldFunctionPKcPm+114>: pop %r14 0x00007fff5fc07eb7 <__dyld__Z18lookupDyldFunctionPKcPm+116>: leaveq 0x00007fff5fc07eb8 <__dyld__Z18lookupDyldFunctionPKcPm+117>: retq End of assembler dump.
C++のソースと見比べると
*address = (uintptr_t)p->implementation;
にあたる部分は
0x00007fff5fc07e8b <__dyld__Z18lookupDyldFunctionPKcPm+72>: mov 0x8(%r12),%rax 0x00007fff5fc07e90 <__dyld__Z18lookupDyldFunctionPKcPm+77>: mov %rax,(%r14) 0x00007fff5fc07e93 <__dyld__Z18lookupDyldFunctionPKcPm+80>: mov $0x1,%eax
の部分なのでこのraxの値を確認してみる。(__dyld_fast_stub_entry を探しているのでこのコードを通ることは明らか。)
(gdb) b *0x00007fff5fc07e93 Breakpoint 6 at 0x7fff5fc07e93 (gdb) c Continuing. Breakpoint 6, 0x00007fff5fc07e93 in __dyld__Z18lookupDyldFunctionPKcPm () (gdb) i reg rax rax 0x7fff5fc04721 140734799824673 (gdb) x /3i $rax 0x7fff5fc04721 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm>: push %rbp 0x7fff5fc04722 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+1>: mov %rsp,%rbp 0x7fff5fc04725 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+4>: push %rbx
という感じで、dyld::fastBindLazySymbol(ImageLoader**, unsigned long) のアドレスが予想通り取得されている。で、このアドレスが _dyld_fast_stub_entry() の p に代入されると。p は static 変数なので以降の呼び出しでは この dyld_func_lookup() の呼び出しは行われない。
ここでちょっと整理。dyld_stub_binder → _dyld_fast_stub_entry(void*, long) → _dyld_func_lookup("__dyld_fast_stub_entry", (void**)&p) で p に dyld::fastBindLazySymbol(ImageLoader**, unsigned long) のアドレスが取得される ← いまここ
で、_dyld_fast_stub_entry(void*, long) の
return p(loadercache, lazyinfo);
という部分で、dyld::fastBindLazySymbol(ImageLoader**, unsigned long) が呼び出される。
dyld::fastBindLazySymbol(ImageLoader**, unsigned long) は dyld.cpp で次のように定義されている。
#if COMPRESSED_DYLD_INFO_SUPPORT uintptr_t fastBindLazySymbol(ImageLoader** imageLoaderCache, uintptr_t lazyBindingInfoOffset) { uintptr_t result = 0; // get image if ( *imageLoaderCache == NULL ) { // save in cache *imageLoaderCache = dyld::findMappedRange((uintptr_t)imageLoaderCache); if ( *imageLoaderCache == NULL ) { const char* message = "fast lazy binding from unknown image"; dyld::log("dyld: %s\n", message); halt(message); } } // bind lazy pointer and return it try { result = (*imageLoaderCache)->doBindFastLazySymbol(lazyBindingInfoOffset, gLinkContext); } catch (const char* message) { dyld::log("dyld: lazy symbol binding failed: %s\n", message); halt(message); } // return target address to glue which jumps to it with real parameters restored return result; } #endif // COMPRESSED_DYLD_INFO_SUPPORT
ここで、imageLoaderCache と lazyBindingInfoOffset がどのような値であったかを復習。_dyld_fast_stub_entry() の中で dyld::fastBindLazySymbol が呼び出される場所は
0x00007fff8010fc82 <_Z21_dyld_fast_stub_entryPvl+51>: mov %rbx,%rsi 0x00007fff8010fc85 <_Z21_dyld_fast_stub_entryPvl+54>: mov %r12,%rdi 0x00007fff8010fc88 <_Z21_dyld_fast_stub_entryPvl+57>: mov -0x100d9547(%rip),%r11 # 0x7fff70036748 <_ZZ21_dyld_fast_stub_entryPvlE1p>
の部分なので _Z21_dyld_fast_stub_entryPvl+57 でブレイクして rdi(第一引数) と rsi(第二引数) を確認。
(gdb) i reg rip rip 0x7fff8010fc85 0x7fff8010fc85 <_dyld_fast_stub_entry(void*, long)+54> (gdb) i reg rdi rsi rdi 0x100001008 4294971400 rsi 0x0 0
この値はどこから来ているかというと、以下のようなスタブヘルパで設定されたもの。(詳しくは前回、前々回を参照)
0x100000f3a < stub helpers>: lea 0xc7(%rip),%r11 # 0x100001008 0x100000f41 < stub helpers+7>: push %r11 0x100000f43 < stub helpers+9>: jmpq *0xb7(%rip) # 0x100001000 0x100000f49 < stub helpers+15>: nop 0x100000f4a < stub helpers+16>: pushq $0x0 0x100000f4f < stub helpers+21>: jmpq 0x100000f3a < stub helpers> 0x100000f54 < stub helpers+26>: pushq $0xe 0x100000f59 < stub helpers+31>: jmpq 0x100000f3a < stub helpers>
この stub helpers+16 でプッシュされた 0x0 が lazyBindingInfoOffset、 stub helpers+7 でプッシュされた 0x100001008 が imageLoaderCache となっている。0x100001008 と言うのは hoge1 の __nl_symbol_ptr セクション内の2個目のノンレジーポインタのアドレス。
では、dyld::fastBindLazySymbol を観てみよう。実行中のものをディスアセンブルしたものはこれ。
Dump of assembler code for function __dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm: 0x00007fff5fc04721 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+0>: push %rbp 0x00007fff5fc04722 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+1>: mov %rsp,%rbp 0x00007fff5fc04725 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+4>: push %rbx 0x00007fff5fc04726 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+5>: sub $0x8,%rsp 0x00007fff5fc0472a <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+9>: lea 0x3c26f(%rip),%rcx # 0x7fff5fc409a0 <__dyld__ZN4dyldL18sMappedRangesStartE> 0x00007fff5fc04731 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+16>: cmpq $0x0,(%rdi) 0x00007fff5fc04735 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+20>: jne 0x7fff5fc04770 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+79> 0x00007fff5fc04737 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+22>: jmpq 0x7fff5fc047c1 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+160> 0x00007fff5fc0473c <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+27>: mov (%rax,%rcx,1),%rdx 0x00007fff5fc04740 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+31>: test %rdx,%rdx 0x00007fff5fc04743 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+34>: je 0x7fff5fc04753 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+50> 0x00007fff5fc04745 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+36>: cmp 0x8(%rax,%rcx,1),%rdi 0x00007fff5fc0474a <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+41>: jb 0x7fff5fc04753 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+50> 0x00007fff5fc0474c <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+43>: cmp 0x10(%rax,%rcx,1),%rdi 0x00007fff5fc04751 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+48>: jb 0x7fff5fc0476d <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+76> 0x00007fff5fc04753 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+50>: add $0x18,%rax 0x00007fff5fc04757 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+54>: cmp $0x2580,%rax 0x00007fff5fc0475d <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+60>: jne 0x7fff5fc0473c <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+27> 0x00007fff5fc0475f <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+62>: mov 0x2580(%rcx),%rcx 0x00007fff5fc04766 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+69>: test %rcx,%rcx 0x00007fff5fc04769 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+72>: jne 0x7fff5fc047c1 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+160> 0x00007fff5fc0476b <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+74>: jmp 0x7fff5fc047c8 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+167> 0x00007fff5fc0476d <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+76>: mov %rdx,(%rdi) 0x00007fff5fc04770 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+79>: mov (%rdi),%rdi 0x00007fff5fc04773 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+82>: mov (%rdi),%rax 0x00007fff5fc04776 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+85>: lea 0x3bd23(%rip),%rdx # 0x7fff5fc404a0 <__dyld__ZN4dyld12gLinkContextE> 0x00007fff5fc0477d <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+92>: callq *0x110(%rax) 0x00007fff5fc04783 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+98>: jmp 0x7fff5fc047f0 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+207> 0x00007fff5fc04785 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+100>: mov %rax,%rbx 0x00007fff5fc04788 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+103>: dec %rdx 0x00007fff5fc0478b <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+106>: jne 0x7fff5fc047b9 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+152> 0x00007fff5fc0478d <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+108>: mov %rax,%rdi 0x00007fff5fc04790 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+111>: callq 0x7fff5fc19751 <__dyld___cxa_begin_catch> 0x00007fff5fc04795 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+116>: mov %rax,%rbx 0x00007fff5fc04798 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+119>: mov %rax,%rsi 0x00007fff5fc0479b <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+122>: lea 0x2119e(%rip),%rdi # 0x7fff5fc25940 0x00007fff5fc047a2 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+129>: xor %eax,%eax 0x00007fff5fc047a4 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+131>: callq 0x7fff5fc02973 <__dyld__ZN4dyld3logEPKcz> 0x00007fff5fc047a9 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+136>: mov %rbx,%rdi 0x00007fff5fc047ac <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+139>: callq 0x7fff5fc046cb <__dyld__ZN4dyld4haltEPKc> 0x00007fff5fc047b1 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+144>: mov %rax,%rbx 0x00007fff5fc047b4 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+147>: callq 0x7fff5fc197cf <__dyld___cxa_end_catch> 0x00007fff5fc047b9 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+152>: mov %rbx,%rdi 0x00007fff5fc047bc <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+155>: callq 0x7fff5fc24b26 <__dyld__Unwind_Resume> 0x00007fff5fc047c1 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+160>: xor %eax,%eax 0x00007fff5fc047c3 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+162>: jmpq 0x7fff5fc0473c <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+27> 0x00007fff5fc047c8 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+167>: movq $0x0,(%rdi) 0x00007fff5fc047cf <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+174>: lea 0x21192(%rip),%rsi # 0x7fff5fc25968 0x00007fff5fc047d6 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+181>: lea 0x21154(%rip),%rdi # 0x7fff5fc25931 0x00007fff5fc047dd <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+188>: xor %eax,%eax 0x00007fff5fc047df <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+190>: callq 0x7fff5fc02973 <__dyld__ZN4dyld3logEPKcz> 0x00007fff5fc047e4 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+195>: lea 0x2117d(%rip),%rdi # 0x7fff5fc25968 0x00007fff5fc047eb <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+202>: callq 0x7fff5fc046cb <__dyld__ZN4dyld4haltEPKc> 0x00007fff5fc047f0 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+207>: add $0x8,%rsp 0x00007fff5fc047f4 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+211>: pop %rbx 0x00007fff5fc047f5 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+212>: leaveq 0x00007fff5fc047f6 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+213>: retq End of assembler dump.
*imageLoaderCacheの値は dyld::fastBindLazySymbol のアドレスである 0x7fff5fc04721 まで実行したところで次のようにして確認。
(gdb) i reg rip rip 0x7fff5fc04721 0x7fff5fc04721 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm> (gdb) x /1xg $rdi 0x100001008: 0x0000000000000000
つまり *imageLoaderCache は NULL ですね。NULL なので dyld::findMappedRange が呼び出される。アセンブリを見てみると、この dyld::findMappedRange はインライン展開されていることがわかる。
dyld::findMappedRrange は dyld.cpp で次のように定義されている。
ImageLoader* findMappedRange(uintptr_t target) { for (MappedRanges* p = &sMappedRangesStart; p != NULL; p = p->next) { for (int i=0; i < MappedRanges::count; ++i) { if ( p->array[i].image != NULL ) { if ( (p->array[i].start <= target) && (target < p->array[i].end) ) return p->array[i].image; } } } return NULL; }
MappedRanges, sMappedRangesStart と言うのは dyld.cpp で次のように定義されている。
// The MappedRanges structure is used for fast address->image lookups. // The table is only updated when the dyld lock is held, so we don't // need to worry about multiple writers. But readers may look at this // data without holding the lock. Therefore, all updates must be done // in an order that will never cause readers to see inconsistent data. // The general rule is that if the image field is non-NULL then // the other fields are valid. // struct MappedRanges { enum { count=400 }; struct { ImageLoader* image; uintptr_t start; uintptr_t end; } array[count]; MappedRanges* next; }; static MappedRanges sMappedRangesStart;
あくまで推測だが、実行形式や動的ライブラリがロードされている場合、あるアドレス範囲へファイルをロードした ImageLoader というものが有って、それがこの構造体に格納されているということなのかもしれない。
であるとすれば、ここでの findMappedRange() 呼び出しは hoge1 の __DATA,__la_symbol_ptr をロードした ImageLoader を取得しているということになる。
sMappedRangesStart のアドレス、つまり __dyld__ZN4dyldL18sMappedRangesStartE のアドレスはディスアセンブル結果から 0x7fff5fc409a0 とわかるので少し内容を覗いてみる。
(gdb) x /30xg 0x7fff5fc409a0 0x7fff5fc409a0 <__dyld__ZN4dyldL18sMappedRangesStartE>: 0x00007fff5fc43c18 0x0000000100000000 0x7fff5fc409b0 <__dyld__ZN4dyldL18sMappedRangesStartE+16>: 0x0000000100003000 0x00007fff5fc43cb8 0x7fff5fc409c0 <__dyld__ZN4dyldL18sMappedRangesStartE+32>: 0x0000000100003000 0x0000000100005000 0x7fff5fc409d0 <__dyld__ZN4dyldL18sMappedRangesStartE+48>: 0x00007fff5fc43d88 0x00007fff80103000 0x7fff5fc409e0 <__dyld__ZN4dyldL18sMappedRangesStartE+64>: 0x00007fff802c3000 0x00007fff5fc43d88 0x7fff5fc409f0 <__dyld__ZN4dyldL18sMappedRangesStartE+80>: 0x00007fff7001a000 0x00007fff7003d000 0x7fff5fc40a00 <__dyld__ZN4dyldL18sMappedRangesStartE+96>: 0x00007fff5fc43d88 0x00007fff870f6000 0x7fff5fc40a10 <__dyld__ZN4dyldL18sMappedRangesStartE+112>: 0x00007fff889d9000 0x00007fff5fc43e20 0x7fff5fc40a20 <__dyld__ZN4dyldL18sMappedRangesStartE+128>: 0x00007fff86b99000 0x00007fff86b9e000 0x7fff5fc40a30 <__dyld__ZN4dyldL18sMappedRangesStartE+144>: 0x00007fff5fc43e20 0x00007fff870f6000 0x7fff5fc40a40 <__dyld__ZN4dyldL18sMappedRangesStartE+160>: 0x00007fff889d9000 0x0000000000000000 0x7fff5fc40a50 <__dyld__ZN4dyldL18sMappedRangesStartE+176>: 0x0000000000000000 0x0000000000000000 0x7fff5fc40a60 <__dyld__ZN4dyldL18sMappedRangesStartE+192>: 0x0000000000000000 0x0000000000000000 0x7fff5fc40a70 <__dyld__ZN4dyldL18sMappedRangesStartE+208>: 0x0000000000000000 0x0000000000000000 0x7fff5fc40a80 <__dyld__ZN4dyldL18sMappedRangesStartE+224>: 0x0000000000000000 0x0000000000000000
MappedRanges構造体として見ると ImageLoaderのアドレス、ロードした範囲の開始、終了、がそれっぽい数字で並んでいる。今は 0x100001008 をロードしたImageLoaderを探しているので、0x00007fff5fc43c18 が取得されるはず。
C++の
result = (*imageLoaderCache)->doBindFastLazySymbol(lazyBindingInfoOffset, gLinkContext);
は、アセンブリではここに該当。
0x00007fff5fc04770 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+79>: mov (%rdi),%rdi 0x00007fff5fc04773 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+82>: mov (%rdi),%rax 0x00007fff5fc04776 <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+85>: lea 0x3bd23(%rip),%rdx # 0x7fff5fc404a0 <__dyld__ZN4dyld12gLinkContextE> 0x00007fff5fc0477d <__dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm+92>: callq *0x110(%rax)
C++ではインスタンスメソッド呼び出しの第一引数(rdi)はインスタンスのアドレスなので callq 時の rdi の値を見れば取得された ImageLoader のアドレスを確認できる。
(gdb) x /1x $rdi 0x100001008: 0x00007fff5fc43c18 (gdb) b *0x00007fff5fc0477d Breakpoint 8 at 0x7fff5fc0477d (gdb) c Continuing. Breakpoint 8, 0x00007fff5fc0477d in __dyld__ZN4dyld18fastBindLazySymbolEPP11ImageLoaderm () (gdb) i reg rdi rdi 0x7fff5fc43c18 140734800083992
予想通り 0x7fff5fc43c18 が取得されている。
次に、 ImageLoader::doBindFastLazySymbol の呼出なわけだが、これは ImageLoader.h で次のように定義されている。
// called at runtime when a fast lazily bound function is first called virtual uintptr_t doBindFastLazySymbol(uint32_t lazyBindingInfoOffset, const LinkContext& context) = 0;
純粋仮想関数。上記アセンブリでraxをゴニョゴニョしているのは、この実装を呼び出すために仮想関数テーブルがうんたらな処理をしているからだとおもう(仮想関数の仕組はまだよくわかりません。そのうち調べよう ^^;)
なのでここはサクっとgdbに頼って次へ進む。callq 先はこうなった。
0x00007fff5fc168a0 in __dyld__ZN26ImageLoaderMachOCompressed20doBindFastLazySymbolEjRKN11ImageLoader11LinkContextE ()
ImageLoaderMachOCompressed::doBindFastLazySymbol(unsigned int, ImageLoader::LinkContext const&) が呼び出されている。これは ImageLoaderMachOCompressed.cpp で次のように定義されている。
uintptr_t ImageLoaderMachOCompressed::doBindFastLazySymbol( uint32_t lazyBindingInfoOffset, const LinkContext& context) { const uint8_t* const start = fLinkEditBase + fDyldInfo->lazy_bind_off; const uint8_t* const end = &start[fDyldInfo->lazy_bind_size]; if (lazyBindingInfoOffset > fDyldInfo->lazy_bind_size) throw "fast lazy bind offset out of range"; uint8_t type = BIND_TYPE_POINTER; uintptr_t address = 0; const char* symbolName = NULL; uint8_t symboFlags = 0; int libraryOrdinal = 0; bool done = false; uintptr_t result = 0; const uint8_t* p = &start[lazyBindingInfoOffset]; while (!done && (p < end)) { uint8_t immediate = *p & BIND_IMMEDIATE_MASK; uint8_t opcode = *p & BIND_OPCODE_MASK; ++p; switch (opcode) { case BIND_OPCODE_DONE: done = true; break; case BIND_OPCODE_SET_DYLIB_ORDINAL_IMM: libraryOrdinal = immediate; break; case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB: libraryOrdinal = read_uleb128(p, end); break; case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM: // the special ordinals are negative numbers if (immediate == 0) libraryOrdinal = 0; else { int8_t signExtended = BIND_OPCODE_MASK | immediate; libraryOrdinal = signExtended; } break; case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM: symbolName = (char*) p; symboFlags = immediate; while (*p != '\0') ++p; ++p; break; case BIND_OPCODE_SET_TYPE_IMM: type = immediate; break; case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB: if (immediate > fSegmentsCount) dyld::throwf( "BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB has segment %d which is too large (%d)\n", immediate, fSegmentsCount); address = segActualLoadAddress(immediate) + read_uleb128(p, end); break; case BIND_OPCODE_DO_BIND: result = this->bindAt(context, address, type, symbolName, 0, 0, libraryOrdinal, "lazy ", NULL); break; case BIND_OPCODE_SET_ADDEND_SLEB: case BIND_OPCODE_ADD_ADDR_ULEB: case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB: case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED: case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB: default: dyld::throwf("bad lazy bind opcode %d", *p); } } return result; }
LinkContext、fLinkEditBase、fDyldInfo とは一体どういうデータなのか・・・。これを知るためにはローディングの仕組を理解する必要があるのかなぁ。もう少しで、動的ライブラリの関数がバインドされる瞬間を見れそうな気がするのだが・・・とりあえず今日はここまで。