简述
看了很久的关于动态库链接加载的知识,但对其中的一些细节一直似懂非懂的,所以决定实践一下加深一下印象。本文主要介绍动态库符号的lazy bind 过程。
Demo
先实现一个简单的动态库
// .h
int add(int a,int b);
int sub(int a,int b);
// .c
#import "*.h"
int add(int a,int b) {
return a + b;
}
int sub(int a,int b) {
return a - b;
}
实现一个简单的app
// ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
int result = add(3, 4);
// void * addPoint = add;
// void * subPoint = sub;
result = sub(3, 4);
NSLog(@"********");
}
符号表
![](https://img.haomeiwen.com/i2031664/237472ec52952385.png)
- 符号名: _add
- 类型: 未定义
- 来源: adder lib
间接符号表
所有需要间接访问的符号都存放在这里,包括lazy和no_lazy符号,类似于重定位表的格式
![](https://img.haomeiwen.com/i2031664/b17164c9d0b6607a.png)
- 符号名
- 段名
- 地址
lazy bind 符号表
存放着lazy bind 符号地址信息, 例如: _add 符号地址0x100003038, 它的value 是多少呢?0x100001a80
![](https://img.haomeiwen.com/i2031664/515a2646ece33af6.png)
0x100001a80 指向的是一段汇编代码:
0000000100001a70 lea r11, qword [ds:0x100003008] ; 0x100003008 (imp___nl_symbol_ptr_dyld_stub_binder + 0x8), XREF=0x100000170, 0x100001a85, 0x100001a8f, 0x100001a99, 0x100001aa3, 0x100001aad, 0x100001ab7, 0x100001ac1, 0x100001acb, 0x100001ad5, 0x100001adf
0000000100001a77 push r11
0000000100001a79 jmp qword [ds:imp___nl_symbol_ptr_dyld_stub_binder]
0000000100001a7f nop
0000000100001a80 push 0x3f ; XREF=imp___la_symbol_ptr__add
0000000100001a85 jmp 0x100001a70
整理一下就是:
0000000100001a80 push 0x3f
0000000100001a70 lea r11, qword [ds:0x100003008] ; 0x100003008 (imp___nl_symbol_ptr_dyld_stub_binder + 0x8), XREF=0x100000170, 0x100001a85, 0x100001a8f, 0x100001a99, 0x100001aa3, 0x100001aad, 0x100001ab7, 0x100001ac1, 0x100001acb, 0x100001ad5, 0x100001adf
0000000100001a77 push r11
0000000100001a79 jmp qword [ds:imp___nl_symbol_ptr_dyld_stub_binder]
稍后再解释它的作用
验证lazy bind
我现在要校验一下add函数调用之后, _add 符号的间接地址指针是否被修改了
- 程序启动前main函数地址: 0x100001740
- 运行时地址main函数地址: 0x1023c6740
- 因此得到全局偏移量 offset: 0x1023c6740 - 0x100001740
- 计算得到_add 符号间接寻址地址: offset + 0x100003038 = 0x1023c8038
-
查看 0x1023c8038 内存: 0x00000001026b8f40
屏幕快照 2019-11-04 下午2.39.16.png
- log add 函数地址: 0x00000001026b8f40
![](https://img.haomeiwen.com/i2031664/3d020e08300b59a8.png)
这样就成功校验了,lazy bind 理论是正确的
注: 观察完整的lazy bind 其实还有一步,就是在没有调用add符号之前,查看间接符号的值, 这个值等于 0x100001a80 + offset, 即指向上面提到的那段汇编代码
lazy bind 过程
-
调用 stub_add 函数
屏幕快照 2019-11-04 下午3.14.31.png
-
stub_add 会跳转到 间接符号表所指向的地址(间接寻址的关键指令)
屏幕快照 2019-11-04 下午3.15.30.png
![](https://img.haomeiwen.com/i2031664/f9756f36dd2a7f86.png)
此时 :imp___la_symbol_ptr__add 指向的就是上述的那段汇编代码的地址, 静态地址: 0x100001a80 ,动态地址 : 0x100001a80 + offset
下面分别使用MachOView 和 Hopper disassembler 分析一下source code
![](https://img.haomeiwen.com/i2031664/1e5c8b9f1a041839.png)
![](https://img.haomeiwen.com/i2031664/29715c8b5f7b63f9.png)
imp___nl_symbol_ptr_dyld_stub_binder 所处的section:
![](https://img.haomeiwen.com/i2031664/7dbc3e2cc2edc2ed.png)
该段代码最终会调用 imp___nl_symbol_ptr_dyld_stub_binder 函数,由动态链接库dyld.lib的stub_binde函数完成符号绑定操作。 imp___nl_symbol_ptr_dyld_stub_binder 需要两个参数
- lazy binder point address: 0x100003008
- symbol flag: 主要靠这个识别是哪个函数需要bind,至于为什么是0x3f 具体是怎么编码的还没研究过
imp___nl_symbol_ptr_dyld_stub_binder
属于no_lazy_symbol point
,这个在程序启动后就已经初始化完毕,它指向dyld.lib 的 stub_binder
函数地址, stub_binder完成查找_add函数的地址,并且写入间接符号表中,最终写入地址( 0x100001a80 + offset), 之后就不再需要绑定了
- 第一次调用add函数: imp___la_symbol_ptr__add 指向 stub_bind 代码块,触发bind操作
- 第二次调用add函数: imp___la_symbol_ptr__add 指向add函数真实地址,直接调用add函数
总结
程序加载过程
- LC_SEGMENT_64 : 加载代码段、数据段
- LC_LOAD_DYLINKER: 加载动态库链接器
- LC_LOAD_DYLIB: 加载动态库
- 完成 no lazy bind
- 程序启动后, 在第一调用动态库函数时完成 lazy bind 操作
网友评论