簡述
看了很久的關于動態(tài)庫鏈接加載的知識乐设,但對其中的一些細節(jié)一直似懂非懂的畏纲,所以決定實踐一下加深一下印象。本文主要介紹動態(tài)庫符號的lazy bind 過程敏晤。
Demo
先實現(xiàn)一個簡單的動態(tài)庫
// .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;
}
實現(xiàn)一個簡單的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(@"********");
}
符號表
- 符號名: _add
- 類型: 未定義
- 來源: adder lib
間接符號表
所有需要間接訪問的符號都存放在這里桨吊,包括lazy和no_lazy符號威根,類似于重定位表的格式
- 符號名
- 段名
- 地址
lazy bind 符號表
存放著lazy bind 符號地址信息, 例如: _add 符號地址0x100003038视乐, 它的value 是多少呢洛搀?0x100001a80
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
我現(xiàn)在要校驗一下add函數(shù)調用之后, _add 符號的間接地址指針是否被修改了
- 程序啟動前main函數(shù)地址: 0x100001740
- 運行時地址main函數(shù)地址: 0x1023c6740
- 因此得到全局偏移量 offset: 0x1023c6740 - 0x100001740
- 計算得到_add 符號間接尋址地址: offset + 0x100003038 = 0x1023c8038
-
查看 0x1023c8038 內存: 0x00000001026b8f40
- log add 函數(shù)地址: 0x00000001026b8f40
這樣就成功校驗了佑淀,lazy bind 理論是正確的
注: 觀察完整的lazy bind 其實還有一步留美,就是在沒有調用add符號之前,查看間接符號的值伸刃, 這個值等于 0x100001a80 + offset谎砾, 即指向上面提到的那段匯編代碼
lazy bind 過程
-
調用 stub_add 函數(shù)
-
stub_add 會跳轉到 間接符號表所指向的地址(間接尋址的關鍵指令)
此時 :imp___la_symbol_ptr__add 指向的就是上述的那段匯編代碼的地址, 靜態(tài)地址: 0x100001a80 捧颅,動態(tài)地址 : 0x100001a80 + offset
下面分別使用MachOView 和 Hopper disassembler 分析一下source code
imp___nl_symbol_ptr_dyld_stub_binder 所處的section:
該段代碼最終會調用 imp___nl_symbol_ptr_dyld_stub_binder 函數(shù),由動態(tài)鏈接庫dyld.lib的stub_binde函數(shù)完成符號綁定操作景图。 imp___nl_symbol_ptr_dyld_stub_binder 需要兩個參數(shù)
- lazy binder point address: 0x100003008
- symbol flag: 主要靠這個識別是哪個函數(shù)需要bind,至于為什么是0x3f 具體是怎么編碼的還沒研究過
imp___nl_symbol_ptr_dyld_stub_binder
屬于no_lazy_symbol point
,這個在程序啟動后就已經(jīng)初始化完畢碉哑,它指向dyld.lib 的 stub_binder
函數(shù)地址挚币, stub_binder完成查找_add函數(shù)的地址,并且寫入間接符號表中扣典,最終寫入地址( 0x100001a80 + offset), 之后就不再需要綁定了
- 第一次調用add函數(shù): imp___la_symbol_ptr__add 指向 stub_bind 代碼塊妆毕,觸發(fā)bind操作
- 第二次調用add函數(shù): imp___la_symbol_ptr__add 指向add函數(shù)真實地址,直接調用add函數(shù)
總結
程序加載過程
- LC_SEGMENT_64 : 加載代碼段贮尖、數(shù)據(jù)段
- LC_LOAD_DYLINKER: 加載動態(tài)庫鏈接器
- LC_LOAD_DYLIB: 加載動態(tài)庫
- 完成 no lazy bind
- 程序啟動后笛粘, 在第一調用動態(tài)庫函數(shù)時完成 lazy bind 操作