符号
在iOS开发中,只要涉及到 函数名、变量名、方法名,编译完之后,就会生成符号表,符号表之间也有区别,分为内部符号
和外部符号
:
- 1,内部函数方法,变量名称是内部符号
- 2,
外部符号
又称为间接符号表
,当我们调用外部函数(本Mach-O之外)
方法时,例如NSLog
,编译器是不知道NSLog
的函数地址的,就将该符号放到间接符号表中。
在 内部符号中,又分为全局符号
和本地符号
:
- 1,全局符号是可以全局调用函数,例如我们在写SDK的时候,提供给外界使用的函数。
- 2,本地符号只限于本文件的符号。
在Mach-O
文件中:
Symbols
存放了我们项目中所有的符号表,Indirect Symbols
中存放的是所有的间接符号表。
符号绑定
我们新建一个工程,只有两个NSLog
函数
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"第一次外部函数的调用!");
NSLog(@"第二次外部函数的调用!");
}
复制代码
桩
Mach-O静态分析符号表Data
在第一个NSLog调用的时候,我们查看其汇编代码
- 1, 首先我们使用
image list
先查看其ASLR
为0x00000001001f8000
(lldb) image list
[ 0] 88C36EBA-D614-344F-AEA4-3D9F52E6C102 0x00000001001f8000 /Users/bel/Library/Developer/Xcode/DerivedData/symbolDemo-fgjddtxgssnvjpadkexjmgqpbbun/Build/Products/Debug-iphoneos/symbolDemo.app/symbolDemo
复制代码
这样我们就得到NSLog
在Mach-O
中的偏移值 0x1001fe50c - 0x00000001001f8000 = 0x650C
我们在Mach-O
文件中查看该处的Data
值
,在Symbol Stubs
中 NSLog符号
的Data
值为1F2003D590D7025800021FD6
这是我们从Mach-O
文件分析到的,接下来我们使用LLDB
动态的去验证
LLDB动态查看符号表的data
在 该处,我们按住 control + step in
进入该函数
进入函数
查看该处内存值
(lldb) x 0x104c6650c
0x104c6650c: 1f 20 03 d5 90 d7 02 58 00 02 1f d6 1f 20 03 d5 . .....X..... ..
0x104c6651c: 70 d7 02 58 00 02 1f d6 1f 20 03 d5 50 d7 02 58 p..X..... ..P..X
复制代码
我们可以看到 1f 20 03 d5 90 d7 02 58 00 02 1f d6
和 F2003D590D7025800021FD6
是完全相等的
在这里 F2003D590D7025800021FD6
是一串代码,是 NSLog
符号的Data
值,我们称其为桩
。
bl -> 地址
就相当于 bl -> 桩
,就是执行符号表里面的地址值(Data)
。
符号绑定函数
接着上面的函数执行,当执行到 br x16
时,我们读取x16
的值
读取 x16
的值
(lldb) register read x16
x16 = 0x0000000104c665c0
复制代码
通过计算我们来得到其在MachO中的偏移值
0x0000000104c665c0 - ASLR = 0x65C0
,在 offset == 0x65C0
处,我们可以看到,其又跳转到 0x65A8
处执行了一段代码
那 0x65A8的函数是什么意思呢?
我们回到LLDB
环境中,点击 step into
,直到看到 0x65a8
处的汇编代码为止
0x104f3a5a8: adr x17, #0x6f10 ; _dyld_private
0x104f3a5ac: nop
0x104f3a5b0: stp x16, x17, [sp, #-0x10]!
0x104f3a5b4: nop
0x104f3a5b8: ldr x16, #0x1a48 ; (void *)0x00000001ba1b435c: dyld_stub_binder
0x104f3a5bc: br x16
0x104f3a5c0: ldr w16, 0x104f3a5c8
0x104f3a5c4: b 0x104f3a5a8
复制代码
通过 LLDB
调试,我们可知, 该汇编代码,调用了 dyld_stub_binder
函数,这个函数是用来绑定符号的。
⚠️⚠️⚠️
小结:
通过以上分析,我们可以知道,NSLog
是一个外部函数
,Text段
中的Symbol Stubs表
存放的是 外部函数的桩。第一次调用 NSLog
的时候,bl
跳转到函数的桩
里面,然后调用了 dyld_stub_binder
函数,对NSLog
进行符号绑定。
非懒加载符号表和懒加载符号表
通过查看 MachO
,我们可以看到 dyld_stub_binder
符号存放在 Text段
的Non-Lazy Symbol
中
我们可以看到在编译时期 Data值为0,其 offset
为0x8000
,在运行时,我们来查看
其值
(lldb) image list
[ 0] 88C36EBA-D614-344F-AEA4-3D9F52E6C102 0x00000001009d8000 /Users/bel/Library/Developer/Xcode/DerivedData/symbolDemo-fgjddtxgssnvjpadkexjmgqpbbun/Build/Products/Debug-iphoneos/symbolDemo.app/symbolDemo
(lldb) p/x 0x00000001009d8000 + 0x8000
(long) $0 = 0x00000001009e0000
(lldb) x 0x00000001009e0000
0x1009e0000: 5c 43 1b ba 01 00 00 00 d0 cf b8 04 02 00 00 00 \C..............
0x1009e0010: d0 07 00 00 00 00 00 00 e6 f3 9d 00 01 00 00 00 ................
(lldb) dis -s 0x01ba1b435c
libdyld.dylib`dyld_stub_binder:
0x1ba1b435c <+0>: stp x29, x30, [sp, #-0x10]!
0x1ba1b4360 <+4>: mov x29, sp
0x1ba1b4364 <+8>: sub sp, sp, #0xf0 ; =0xf0
0x1ba1b4368 <+12>: stp x0, x1, [x29, #-0x10]
0x1ba1b436c <+16>: stp x2, x3, [x29, #-0x20]
0x1ba1b4370 <+20>: stp x4, x5, [x29, #-0x30]
0x1ba1b4374 <+24>: stp x6, x7, [x29, #-0x40]
0x1ba1b4378 <+28>: stp x8, x9, [x29, #-0x50]
复制代码
我们可以看到,在运行时的时候,dyld_stub_binder
符号表里面的Data
值不为 0了,为真实的函数地址值。
非懒加载的符号表,在App一启动的时候,就会将真实的地址值写到该符号的Data
值中,对符号进行绑定。
在懒加载符号表
中,里面的值都是 函数桩
的值,NSLog符号
存在懒加载符号表中,调用该函数的时候,执行 符号表里面的地址值。在第一次调用的时候,符号表的Data值,指向
dyld_stub_binder
函数,进行符号绑定。
符号绑定之后
在第二次执行NSLog的时候,我们来查看其Data值:
通过 ASLR
和 NSLog的offset
,我们可以直接定位到NSLog符号表的Data值:
(lldb) p/x 0x0000000102238000 + 0xc000
(long) $0 = 0x0000000102244000
(lldb) x 0x0000000102244000
0x102244000: 10 e7 78 ba 01 00 00 00 00 38 78 ba 01 00 00 00 ..x......8x.....
0x102244010: 58 4c 46 be 01 00 00 00 d8 e5 23 02 01 00 00 00 XLF.......#.....
(lldb) dis -s 0x01ba78e710
Foundation`NSLog:
0x1ba78e710 <+0>: sub sp, sp, #0x20 ; =0x20
0x1ba78e714 <+4>: stp x29, x30, [sp, #0x10]
0x1ba78e718 <+8>: add x29, sp, #0x10 ; =0x10
0x1ba78e71c <+12>: adrp x8, 267230
0x1ba78e720 <+16>: ldr x8, [x8, #0xe20]
0x1ba78e724 <+20>: ldr x8, [x8]
0x1ba78e728 <+24>: str x8, [sp, #0x8]
0x1ba78e72c <+28>: add x8, x29, #0x10 ; =0x10
复制代码
从这里我们可以看出,此时符号表里面的Data值,为 Foundation
里面的NSLog的地址值
。
总结
懒加载符号表和非懒加载符号表都存储在Data
段中,是可读可写
的。
- 非懒加载符号表在程序一启动就会进行符号绑定,比如
dyld_stub_binder
函数。 - 懒加载符号表,在第一次调用该方法的时候,才会进行和函数地址进行绑定,在编译期,其符号表里面的一串代码,我们称为
函数的桩
,桩里面的代码是去符号表里面的地址执行
, 在第一次调用的时候,桩
里面的代码指向dyld_stub_binder
函数,对符号进行绑定,在第二次调用的时候,桩
里面的代码指向绑定后的指针。