iOS RE 4 beginners 2 - 静态链接&&动态链接

ENV

macos11.4 + iphone6 iOS 12.2

静态链接

静态链接:输入多个目标文件,输出一个文件(一般是可执行文件)。这个过程中,把多个目标文件里相同性质的段合并到一起。

过程

  • 地址和空间分配 (Address and Storage Allocation)
  • 符号决议 (Symbol Resolution) / 符号绑定 (Symbol Binding)
  • 重定位 (Relocation)

%E9%93%BE%E6%8E%A5%E4%B8%8Edyld%E5%8A%A0%E8%BD%BD%E6%B5%81%E7%A8%8B%E4%BB%A5%E5%8F%8Afishhook%E5%8E%9F%E7%90%86%20notes%20ae172b61c1044420a207538366c9d075/Untitled.png

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
~/study/ios_re_link/static_link  cat main.c

extern int global_var;

int foo(int i);

int main(void){

int ret = foo(42 + global_var);

return 0;
}
~/study/ios_re_link/static_link  cat foo.c
int global_var = 0x1337;

int foo(int i){
return i + global_var;
}
1
2
~/study/ios_re_link/static_link  xcrun -sdk iphoneos clang -c main.c foo.c -target arm64-apple-ios12.2
~/study/ios_re_link/static_link  xcrun -sdk iphoneos clang main.o foo.o -o main -target arm64-apple-ios12.2

两个模块(main.o 和 foo.o) 通过静态链接组合成了一个可执行文件(main)

模块&&产物

main.o

通过machoview可以看到重定位段有三条信息,意味着程序中有三处需要重定位处理:

%E9%93%BE%E6%8E%A5%E4%B8%8Edyld%E5%8A%A0%E8%BD%BD%E6%B5%81%E7%A8%8B%E4%BB%A5%E5%8F%8Afishhook%E5%8E%9F%E7%90%86%20notes%20ae172b61c1044420a207538366c9d075/Untitled%201.png

%E9%93%BE%E6%8E%A5%E4%B8%8Edyld%E5%8A%A0%E8%BD%BD%E6%B5%81%E7%A8%8B%E4%BB%A5%E5%8F%8Afishhook%E5%8E%9F%E7%90%86%20notes%20ae172b61c1044420a207538366c9d075/Untitled%202.png

这个图是hopper反汇编的main函数,可以看到对于引用到其他模块(foo.o)重的变量/函数的地方看起来“正常”,但是点击 bl _foo 就会发现跳转到了:

%E9%93%BE%E6%8E%A5%E4%B8%8Edyld%E5%8A%A0%E8%BD%BD%E6%B5%81%E7%A8%8B%E4%BB%A5%E5%8F%8Afishhook%E5%8E%9F%E7%90%86%20notes%20ae172b61c1044420a207538366c9d075/Untitled%203.png

根据<macho/reloc.h>的定义,可以看到reloc段的结构:

1
2
3
4
5
6
7
8
9
10
struct relocation_info {
int32_t r_address; /* offset in the section to what is being
relocated */
uint32_t r_symbolnum:24, /* symbol index if r_extern == 1 or section
ordinal if r_extern == 0 */
r_pcrel:1, /* was relocated pc relative already */
r_length:2, /* 0=byte, 1=word, 2=long, 3=quad */
r_extern:1, /* does not include value of sym referenced */
r_type:4; /* if not 0, machine specific relocation type */
};

结合上面的图来看(以_foo符号为例):

  • r_address : 0x28
  • r_symbolnum(24bits): 指向_foo 字符串
  • 剩下的8bits是标志位

对应到汇编里就是,main函数的0x28行引用了 _foo 符号,reloc段把这个信息告知linker,这样在链接的时候linker就会处理这条信息,把对应的符号做替换处理。

foo.o

%E9%93%BE%E6%8E%A5%E4%B8%8Edyld%E5%8A%A0%E8%BD%BD%E6%B5%81%E7%A8%8B%E4%BB%A5%E5%8F%8Afishhook%E5%8E%9F%E7%90%86%20notes%20ae172b61c1044420a207538366c9d075/Untitled%204.png

%E9%93%BE%E6%8E%A5%E4%B8%8Edyld%E5%8A%A0%E8%BD%BD%E6%B5%81%E7%A8%8B%E4%BB%A5%E5%8F%8Afishhook%E5%8E%9F%E7%90%86%20notes%20ae172b61c1044420a207538366c9d075/Untitled%205.png

其实都是对 global_var的引用

%E9%93%BE%E6%8E%A5%E4%B8%8Edyld%E5%8A%A0%E8%BD%BD%E6%B5%81%E7%A8%8B%E4%BB%A5%E5%8F%8Afishhook%E5%8E%9F%E7%90%86%20notes%20ae172b61c1044420a207538366c9d075/Untitled%206.png

在foo.o模块中,是 0x20处的data,这个信息也要告诉linker,在link的阶段做替换。

main

最终的可执行文件main,可以看到没有重定位信息,而且mian和foo函数中改替换的符号都已经完成了替换,可以顺利的索引到想要使用的符号(foo和global_var)。

%E9%93%BE%E6%8E%A5%E4%B8%8Edyld%E5%8A%A0%E8%BD%BD%E6%B5%81%E7%A8%8B%E4%BB%A5%E5%8F%8Afishhook%E5%8E%9F%E7%90%86%20notes%20ae172b61c1044420a207538366c9d075/Untitled%207.png

%E9%93%BE%E6%8E%A5%E4%B8%8Edyld%E5%8A%A0%E8%BD%BD%E6%B5%81%E7%A8%8B%E4%BB%A5%E5%8F%8Afishhook%E5%8E%9F%E7%90%86%20notes%20ae172b61c1044420a207538366c9d075/Untitled%208.png

对比两者符号表:

%E9%93%BE%E6%8E%A5%E4%B8%8Edyld%E5%8A%A0%E8%BD%BD%E6%B5%81%E7%A8%8B%E4%BB%A5%E5%8F%8Afishhook%E5%8E%9F%E7%90%86%20notes%20ae172b61c1044420a207538366c9d075/Untitled%209.png

以foo符号为例 :

Type 从 N_UNDF → NSECT

Value 从0 → 0x100007f90

CleanShot 2021-07-20 at 15.17.57@2x

符号表结构:

1
2
3
4
5
6
7
8
9
struct nlist_64 {
union {
uint32_t n_strx; /* index into the string table */
} n_un;
uint8_t n_type; /* type flag, see below */
uint8_t n_sect; /* section number or NO_SECT */
uint16_t n_desc; /* see <mach-o/stab.h> */
uint64_t n_value; /* value of this symbol (or stab offset) */
};

foo 符号的话

  • string table index : 指向符号的字符串
  • n_sect : 改符号在第几个section
  • n_value : 符号具体值(地址)

举个🌰

这里以demo中 global_var 使用的代码举例子。

源码中:

1
int ret = foo(42 + global_var);

如果对应到汇编里应该是:

1
2
3
4
5
6
7

0000000000000014 adrp x9, #0x0 ; 0x68@PAGE
0000000000000018 ldr x9, [x9, #0x68] ; 0x68@PAGEOFF
000000000000001c ldr w10, [x9]
0000000000000020 add w0, w10, #0x2a
0000000000000024 str w8, [sp, #0x10 + var_C]
0000000000000028 bl _foo

可知 w0 是参数,w10是global_var的值,来自x9

w10 = [x9 + 0x68] (未重定位修复)

最开始索引x9的时候可以发现是把0赋给了x9,因为这里还没有重定位,所以用0代替。

最终的产物中可以看到:

1
2
3
4
5
6
0000000100007f64         adrp       x9, #0x100008000                            ; 0x100008000@PAGE
0000000100007f68 add x9, x9, #0x0 ; 0x100008000@PAGEOFF, _global_var
0000000100007f6c ldr w10, [x9] ; _global_var
0000000100007f70 add w0, w10, #0x2a
0000000100007f74 str w8, [sp, #0x10 + var_C]
0000000100007f78 bl _foo

把0替换成了 0x100008000,这个地址恰好指向global_var。

可以看到经过linker的处理,可以正确找到global_var,符号foo同理

动态链接

debug set up

CleanShot 2021-07-20 at 15.18.24@2x

应该是签名有问题,最终解决方案:

1
2
3
4
5
6
7
8
9
10
11
/usr/bin/security find-identity -v -p codesigning
# get : A64593A4DDFA3557CCEFF47FC8E688DCD3E6E455

codesign -s "A64593A4DDFA3557CCEFF47FC8E688DCD3E6E455" --entitlements entitlements.xml -f libFoo.dylib
codesign -s "A64593A4DDFA3557CCEFF47FC8E688DCD3E6E455" --entitlements entitlements.xml -f main

# scp ....
# ssh ....
mude-iPhone:/tmp root# ./main
magic is : 4919
4920

debug lazy binding process

CleanShot 2021-07-20 at 15.19.30@2x

可以看到,第一次调用 printf的时候,bl跳过去并不是 printf函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Target 0: (main) stopped.
(lldb) s
Process 1453 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = instruction step into
frame #0: 0x000000010089bf7c main
-> 0x10089bf7c: br x16
0x10089bf80: ldr w16, 0x10089bf88
0x10089bf84: b 0x10089bf68
0x10089bf88: udf #0x0
Target 0: (main) stopped.
(lldb) re re x16
x16 = 0x00000001d858080c libdyld.dylib`dyld_stub_binder
(lldb) re re x0
x0 = 0x000000010089bfa4 "magic is : %d\n"
(lldb) re re x1
x1 = 0x0000000000001337

通过 dyld_stub_binderprintf的地址,把找到的地址写回到 DATA,__la_symbol_ptr

CleanShot 2021-07-20 at 15.20.15@2x

第二次调用printf的时候就可以看到,这个地方printf函数地址已经被写过来了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(lldb) x/10i $pc
-> 0x100dcff60: 0x58000610 ldr x16, #0xc0 ; (void *)0x00000001d860e14c: printf
0x100dcff64: 0xd61f0200 br x16
0x100dcff68: 0x10000611 adr x17, #0xc0 ; _dyld_private
0x100dcff6c: 0xd503201f nop
0x100dcff70: 0xa9bf47f0 stp x16, x17, [sp, #-0x10]!
0x100dcff74: 0xd503201f nop
0x100dcff78: 0x58000490 ldr x16, #0x90 ; (void *)0x00000001d858080c: dyld_stub_binder
0x100dcff7c: 0xd61f0200 br x16
0x100dcff80: 0x18000050 ldr w16, 0x100dcff88
0x100dcff84: 0x17fffff9 b 0x100dcff68
(lldb) x/3i $pc
-> 0x100dcff60: 0x58000610 ldr x16, #0xc0 ; (void *)0x00000001d860e14c: printf
0x100dcff64: 0xd61f0200 br x16
0x100dcff68: 0x10000611 adr x17, #0xc0 ; _dyld_private
(lldb) x/gx $pc+0xc0
0x100dd0020: 0x00000001d860e14c
(lldb) memory region 0x00000001d860e14c

所以这里就可以直接获取到地址,然后直接跳转过去就行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(lldb) s
Process 1453 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = instruction step into
frame #0: 0x000000010089bf60 main`printf + 4
main`printf:
-> 0x10089bf60 <+4>: ldr x16, #0xc0 ; (void *)0x00000001d860e14c: printf
0x10089bf64 <+8>: br x16
0x10089bf68: adr x17, #0xc0 ; _dyld_private
0x10089bf6c: nop
Target 0: (main) stopped.
(lldb) s
Process 1453 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = instruction step into
frame #0: 0x000000010089bf64 main`printf + 8
main`printf:
-> 0x10089bf64 <+8>: br x16
0x10089bf68: adr x17, #0xc0 ; _dyld_private
0x10089bf6c: nop
0x10089bf70: stp x16, x17, [sp, #-0x10]!
Target 0: (main) stopped.
(lldb) re re x16
x16 = 0x00000001d860e14c libsystem_c.dylib`printf
(lldb)

libdyld.dylib`dyld_stub_binder

dyld-852的代码:

CleanShot 2021-07-20 at 15.20.35@2x

因为我目标环境是iOS12.2,所以具体汇编代码有一些差别:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Target 0: (main) stopped.
(lldb) x/30i $pc
-> 0x1d858080c: 0xa9bf7bfd stp x29, x30, [sp, #-0x10]!
0x1d8580810: 0x910003fd mov x29, sp
0x1d8580814: 0xd103c3ff sub sp, sp, #0xf0 ; =0xf0
0x1d8580818: 0xa93f07a0 stp x0, x1, [x29, #-0x10]
0x1d858081c: 0xa93e0fa2 stp x2, x3, [x29, #-0x20]
0x1d8580820: 0xa93d17a4 stp x4, x5, [x29, #-0x30]
0x1d8580824: 0xa93c1fa6 stp x6, x7, [x29, #-0x40]
0x1d8580828: 0xa93b27a8 stp x8, x9, [x29, #-0x50]
0x1d858082c: 0xad3c07a0 stp q0, q1, [x29, #-0x80]
0x1d8580830: 0xad3b0fa2 stp q2, q3, [x29, #-0xa0]
0x1d8580834: 0xad3a17a4 stp q4, q5, [x29, #-0xc0]
0x1d8580838: 0xad391fa6 stp q6, q7, [x29, #-0xe0]
0x1d858083c: 0xf9400fa0 ldr x0, [x29, #0x18]
0x1d8580840: 0xf9400ba1 ldr x1, [x29, #0x10]
0x1d8580844: 0x940004e4 bl 0x1d8581bd4 ; _dyld_fast_stub_entry(void*, long)
0x1d8580848: 0xaa0003f0 mov x16, x0
0x1d858084c: 0xa97f07a0 ldp x0, x1, [x29, #-0x10]
0x1d8580850: 0xa97e0fa2 ldp x2, x3, [x29, #-0x20]
0x1d8580854: 0xa97d17a4 ldp x4, x5, [x29, #-0x30]
0x1d8580858: 0xa97c1fa6 ldp x6, x7, [x29, #-0x40]
0x1d858085c: 0xa97b27a8 ldp x8, x9, [x29, #-0x50]
0x1d8580860: 0xad7c07a0 ldp q0, q1, [x29, #-0x80]
0x1d8580864: 0xad7b0fa2 ldp q2, q3, [x29, #-0xa0]
0x1d8580868: 0xad7a17a4 ldp q4, q5, [x29, #-0xc0]
0x1d858086c: 0xad791fa6 ldp q6, q7, [x29, #-0xe0]
0x1d8580870: 0x910003bf mov sp, x29
0x1d8580874: 0xa8c17bfd ldp x29, x30, [sp], #0x10
0x1d8580878: 0x910043ff add sp, sp, #0x10 ; =0x10
0x1d858087c: 0xd61f0200 br x16
0x1d8580880: 0xd10103ff sub sp, sp, #0x40 ; =0x40

但是本质上是差不多的,影响不大。

下面看看怎么一步一步调用进去,找到所需要的符号

1. call dyld_stub_binder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
0000000100007f98         ldr        w16, =0x6967616d0000001a
0000000100007f9c b 0x100007f68

....

0000000100007f68 adr x17, #0x100008028 ; CODE XREF=0x100007f84, 0x100007f90, 0x100007f9c
// x17-> _dyld_private

0000000100007f6c nop
0000000100007f70 stp x16, x17, [sp, #-0x10]!

0000000100007f74 nop
0000000100007f78 ldr x16, #dyld_stub_binder_100008008

0000000100007f7c br x16 // call dyld_stub_binder

个人猜测:0x000000000000001a 应该是 类似 linux下elf lazy binding的时候那个index参数的东西,每个符号都不一样 。

初始化好需要的参数就调用进去dyld中去做符号绑定操作了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(lldb) s
Process 1465 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = instruction step into
frame #0: 0x0000000100fb7f7c main
-> 0x100fb7f7c: br x16
0x100fb7f80: ldr w16, 0x100fb7f88
0x100fb7f84: b 0x100fb7f68
0x100fb7f88: udf #0x0
Target 0: (main) stopped.
(lldb) re re x16
x16 = 0x00000001d858080c libdyld.dylib`dyld_stub_binder
(lldb) x/10gx $sp
0x16ee4f5a0: 0x000000000000001a 0x0000000100fb8028
0x16ee4f5b0: 0x0000000000001337 0x0000000000000000
0x16ee4f5c0: 0x0000000000000000 0x0000000000000001
0x16ee4f5d0: 0x000000016ee4f5f0 0x00000001d857e8e0
0x16ee4f5e0: 0x00000001d857e8e0 0x0000000000000000
(lldb) re re x0
x0 = 0x0000000100fb7fa4 "magic is : %d\n"
(lldb) re re x1
x1 = 0x0000000000001337
(lldb) re re x2
x2 = 0x00000000000120a8

2. call dyld::fastBindLazySymbol(loadercache, lazyinfo)

保存栈帧,保存当前的寄存器信息(一大堆stp指令,后面符号绑定完成后,ldp会恢复,这些是成对的),然后设置好参数,就直接转到 dyld::fastBindLazySymbol

(函数前面的保存操作看起来和x86上函数开头的保存栈帧 抬高栈給临时变量预留空间的操作差不多)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Process 1465 resuming
Process 1465 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 3.1
frame #0: 0x00000001d8580844 libdyld.dylib`dyld_stub_binder + 56
libdyld.dylib`dyld_stub_binder:
-> 0x1d8580844 <+56>: bl 0x1d8581bd4 ; _dyld_fast_stub_entry(void*, long)
0x1d8580848 <+60>: mov x16, x0
0x1d858084c <+64>: ldp x0, x1, [x29, #-0x10]
0x1d8580850 <+68>: ldp x2, x3, [x29, #-0x20]
Target 0: (main) stopped.
(lldb) re re x0
x0 = 0x0000000100fb8028 _dyld_private
(lldb) re re x1
x1 = 0x000000000000001a

调用的是 : fastBindLazySymbol(0x0000000100fb8028, 0x1a)

1
2
3
4
5
6
7
8
9
10
11
//  LINK_EDIT seg
const uint8_t* const start = fLinkEditBase + fDyldInfo->lazy_bind_off;
const uint8_t* const end = &start[fDyldInfo->lazy_bind_size];

// ....

do{
if ( ! getLazyBindingInfo(lazyBindingInfoOffset, start, end, &segIndex, &segOffset, &libraryOrdinal, &symbolName, &doneAfterBind) )
dyld::throwf("bad lazy bind info");

}while (!doneAfterBind && !context.strictMachORequired);

对应汇编中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(lldb) c
Process 1465 resuming
Process 1465 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 4.1
frame #0: 0x0000000100fe5e6c dyld`ImageLoaderMachOCompressed::doBindFastLazySymbol(unsigned int, ImageLoader::LinkContext const&, void (*)(), void (*)()) + 136
dyld`ImageLoaderMachOCompressed::doBindFastLazySymbol:
-> 0x100fe5e6c <+136>: bl 0x100fe1d98 ; ImageLoaderMachO::getLazyBindingInfo(unsigned int&, unsigned char const*, unsigned char const*, unsigned char*, unsigned long*, int*, char const**, bool*)
0x100fe5e70 <+140>: tbz w0, #0x0, 0x100fe5f80 ; <+412>
0x100fe5e74 <+144>: ldrb w1, [sp, #0x43]
0x100fe5e78 <+148>: ldrb w8, [x20, #0x74]
Target 0: (main) stopped.
(lldb) re re x1
x1 = 0x0000000100fbc030
(lldb) re re x2
x2 = 0x0000000100fbc058
(lldb) memory region 0x0000000100fbc030
[0x0000000100fbc000-0x0000000100fc0000) r-- __LINKEDIT

(lldb)

这里用到了 我这个可执行文件的LINK_EDIT 段去做符号绑定工作:

1
2
3
4
(lldb) image lookup -va $x1
Address: main[0x000000010000c030] (main.__LINKEDIT + 48)
Summary:
Module: file = "/private/var/tmp/main", arch = "arm64"

3. ImageLoaderMachO::getLazyBindingInfo

根据不同的opcode,走不同分支:

1
2
3
4
5
6
7
8
9
10
11
12
13
if ( lazyBindingInfoOffset > (lazyInfoEnd-lazyInfoStart) )
return false;
bool done = false;
const uint8_t* p = &lazyInfoStart[lazyBindingInfoOffset];
while ( !done && (p < lazyInfoEnd) ) {
uint8_t immediate = *p & BIND_IMMEDIATE_MASK;
uint8_t opcode = *p & BIND_OPCODE_MASK;
++p;
switch (opcode) {

}

....

获取目标符号相关的信息 :

1
&segIndex, &segOffset, &libraryOrdinal, &symbolName, &doneAfterBind

然后根据这些信息,获取该符号的地址:

1
uintptr_t address = segActualLoadAddress(segIndex) + segOffset;
1
2
3
4
5
6
7
8
9
10
11
12
13
// dyld版本不一致,实现的函数有些差别,但是本质是一样的
(lldb) n
Process 1465 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = instruction step over
frame #0: 0x0000000100fe5ee4 dyld`ImageLoaderMachOCompressed::doBindFastLazySymbol(unsigned int, ImageLoader::LinkContext const&, void (*)(), void (*)()) + 256
dyld`ImageLoaderMachOCompressed::doBindFastLazySymbol:
-> 0x100fe5ee4 <+256>: mov x26, x0
0x100fe5ee8 <+260>: mov x0, x20
0x100fe5eec <+264>: bl 0x100fe1fb0 ; ImageLoaderMachO::imageBaseAddress() const
0x100fe5ef0 <+268>: mov x1, x0
Target 0: (main) stopped.
(lldb) re re x0
x0 = 0x00000001d860e14c libsystem_c.dylib`printf

执行符号绑定:

1
result = bindAt(context, this, address, BIND_TYPE_POINTER, symbolName, 0, 0, libraryOrdinal,NULL, "lazy ", patcher, NULL, true);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 调试:
frame #0: 0x0000000100fe5f28 dyld`ImageLoaderMachOCompressed::doBindFastLazySymbol(unsigned int, ImageLoader::LinkContext const&, void (*)(), void (*)()) + 324
dyld`ImageLoaderMachOCompressed::doBindFastLazySymbol:
-> 0x100fe5f28 <+324>: bl 0x100fe0664 ; ImageLoaderMachO::bindLocation(ImageLoader::LinkContext const&, unsigned long, unsigned long, unsigned long, unsigned char, char const*, long, char const*, char const*, char const*, ImageLoaderMachO::ExtraBindData*, unsigned long)
0x100fe5f2c <+328>: ldrb w8, [sp, #0x27]
0x100fe5f30 <+332>: ldrb w9, [x21, #0x139]
0x100fe5f34 <+336>: orr w8, w8, w9
Target 0: (main) stopped.
(lldb) re re x0
x0 = 0x00000001010235e0 dyld::gLinkContext
(lldb) re re x1
x1 = 0x0000000100000000
(lldb) re re x2
x2 = 0x0000000100fb8020 (void *)0x0000000100fb7f98
(lldb) re re x3
x3 = 0x00000001d860e14c libsystem_c.dylib`printf
(lldb) re re x4
x4 = 0x0000000000000001
(lldb) re re x5
x5 = 0x0000000100fbc04e
(lldb) re re x6
x6 = 0x0000000000000000
(lldb)

执行之后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(lldb) n
Process 1465 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = instruction step over
frame #0: 0x0000000100fe5f2c dyld`ImageLoaderMachOCompressed::doBindFastLazySymbol(unsigned int, ImageLoader::LinkContext const&, void (*)(), void (*)()) + 328
dyld`ImageLoaderMachOCompressed::doBindFastLazySymbol:
-> 0x100fe5f2c <+328>: ldrb w8, [sp, #0x27]
0x100fe5f30 <+332>: ldrb w9, [x21, #0x139]
0x100fe5f34 <+336>: orr w8, w8, w9
0x100fe5f38 <+340>: cbz w8, 0x100fe5e4c ; <+104>
Target 0: (main) stopped.
(lldb) x/gx 0x0000000100fb8020
0x100fb8020: 0x00000001d860e14c
(lldb) image lookup -va 0x00000001d860e14c
Address: libsystem_c.dylib[0x00000001809a614c] (libsystem_c.dylib.__TEXT.__text + 263364)
Summary: libsystem_c.dylib`printf
Module: file = "/Users/muhe/Library/Developer/Xcode/iOS DeviceSupport/12.2 (16E227)/Symbols/usr/lib/system/libsystem_c.dylib", arch = "arm64"
Symbol: id = {0x00000617}, range = [0x00000001d860e14c-0x00000001d860e1a8), name="printf"

可以看到符号地址已经被写过去了(0x0000000100fb8020)

至此,符号绑定过程完成。

reference

《程序员的自我修养-链接、装载和库》

https://juejin.cn/post/6844903912147795982

https://juejin.cn/post/6844903922654511112#heading-10

https://bbs.pediy.com/thread-263907.htm

https://iosre.com/t/ios-12-4-killed-9/15633