iOS符号绑定

1,196 阅读6分钟

符号

在iOS开发中,只要涉及到 函数名、变量名、方法名,编译完之后,就会生成符号表,符号表之间也有区别,分为内部符号外部符号

  • 1,内部函数方法,变量名称是内部符号
  • 2,外部符号又称为间接符号表,当我们调用外部函数(本Mach-O之外)方法时,例如NSLog,编译器是不知道NSLog的函数地址的,就将该符号放到间接符号表中。

在 内部符号中,又分为全局符号本地符号

  • 1,全局符号是可以全局调用函数,例如我们在写SDK的时候,提供给外界使用的函数。
  • 2,本地符号只限于本文件的符号。

Mach-O文件中:

symbols.jpg

Symbols存放了我们项目中所有的符号表,Indirect Symbols中存放的是所有的间接符号表。

符号绑定

我们新建一个工程,只有两个NSLog函数

- (void)viewDidLoad {
    [super viewDidLoad];

    NSLog(@"第一次外部函数的调用!");

    
    NSLog(@"第二次外部函数的调用!");
}

Mach-O静态分析符号表Data

在第一个NSLog调用的时候,我们查看其汇编代码 NSLog1.jpg

  • 1, 首先我们使用image list 先查看其 ASLR0x00000001001f8000
(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 

这样我们就得到NSLogMach-O中的偏移值 0x1001fe50c - 0x00000001001f8000 = 0x650C 我们在Mach-O 文件中查看该处的Data

Mach-O 0x650C.jpg ,在Symbol StubsNSLog符号Data值为1F2003D590D7025800021FD6 这是我们从Mach-O文件分析到的,接下来我们使用LLDB动态的去验证

LLDB动态查看符号表的data

在 该处,我们按住 control + step in进入该函数 NSLog1.jpg 进入函数

NSLogDis.png

查看该处内存值

(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 d6F2003D590D7025800021FD6是完全相等的

在这里 F2003D590D7025800021FD6是一串代码,是 NSLog符号的Data值,我们称其为bl -> 地址 就相当于 bl -> 桩,就是执行符号表里面的地址值(Data)

符号绑定函数

接着上面的函数执行,当执行到 br x16时,我们读取x16的值

br x16.jpg 读取 x16的值

(lldb) register read x16
     x16 = 0x0000000104c665c0  

通过计算我们来得到其在MachO中的偏移值 0x0000000104c665c0 - ASLR = 0x65C0,在 offset == 0x65C0处,我们可以看到,其又跳转到 0x65A8处执行了一段代码

Mach O 65C0.png

Mach-O 65A8.png 那 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

Non-Lazy Symbol.png 我们可以看到在编译时期 Data值为0,其 offset0x8000,在运行时,我们来查看 其值

(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值中,对符号进行绑定。

Lazy Symbol.png懒加载符号表中,里面的值都是 函数的值,NSLog符号存在懒加载符号表中,调用该函数的时候,执行 符号表里面的地址值。在第一次调用的时候,符号表的Data值,指向 dyld_stub_binder函数,进行符号绑定。

符号绑定之后

在第二次执行NSLog的时候,我们来查看其Data值:

第二次调用NSLog.png 通过 ASLRNSLog的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函数,对符号进行绑定,在第二次调用的时候,里面的代码指向绑定后的指针。