iOS:懒加载符号绑定流程

1. 桩函数的出现

首先在 main 函数中打断点:

image.png

开启汇编调试,运行代码,结果如下:

image.png

上图可知调用 NSLog 实际都是 callq 0x10659f788。在该地址上 的调用地址上打断点:

(lldb) breakpoint set -a 0x10659f788
Breakpoint 8: where = XKLibTest`symbol stub for: NSLog, address = 0x000000010659f788

执行代码,进入到对应断点,只有一句代码:

image.png

这里是一个关键点,慢慢看;

我们先来看 0x10659f788 这个值怎么来的

首先使用 image list找出偏移:

image.png

然后看下图:

image.png

到这里,我们知道了 NSLog 在被调用时,是直接跳转到了 __stubs 对应位置,然后执行该位置存储的机器码,这里猜测 FF25 就是 jmpq 的机器码指令,而 9A18、9C18 等等就是 jmpq 后面跟的参数,也就是和符号顺序相关的参数;

至此,可以得出结论:

  • 在静态编译时期,外部符号的实际调用代码会被替换成桩函数的调用(call stub);

2. 桩函数的具体含义

到这里,我们还是有疑问,0x000000010659f804 这个是怎么来的, NSLog 最终又是怎么被调用的呢?

我们继续看 jmpq *addr(%rip)

这个汇编代码的意思是:

  1. address = Current address + Value before (%rip) + Current Instruction Size;
  2. 取出 1 中计算得到的 address 中的值;

或者可以直接理解成:下一段汇编代码的地址 + rip 前的地址,然后取出这个指针的值进行跳转;

这里我们来算一下,从 Mach-O 文件中可以看到:

  1. 下一个指令的地址为:278E
  2. 指令集大小为 6 ;

如图:


image.png

所以可以这么算:

(lldb) p/x 0x10659f78e + 0x189a
(long) $3 = 0x00000001065a1028

然后我们来看看 0x00000001065a1028 中存储的是什么:

image.png

即:0x000000010659f804,和汇编代码的注释一致;

至此,我们又可以得出一个结论:

  • __stubs 表中存储的是机器码,其形式为:跳转指令 + 参数。

这个肯定是和不同的架构相关的,所以针对不同架构要区别对待。因为在 mac 中查看 Mach-O 、系统动态库时比较方便,所以当前使用的是模拟器,即 x86 架构;

3. 桩函数和懒加载符号表

这里其实还有一个关键点,0x000000010659f804 存储在哪里?这就需要看看 0x00000001065a1028 在 Mach-O 文件中的表现了,减去偏移之后如下:

image.png

这里其实我们也可以得出一个结论:

  • 桩函数总是去懒加载符号表中取出符号对应的指针,以此来完成符号函数的调用,只不过懒加载符号在静态编译时,其指针指向和 binder 相关的机器码位置有关;

4. stub_helper 函数

至此,我们知道了懒加载符号表中初始化时,存储的是 0x000000010659f804 这个地址。其实到这里,我们基本可以猜出来 0x000000010659f804 这个地址应该就是和懒加载符号绑定相关函数的地址了。

所以,我们需要看看这个地址在 Mach-O 中的表现,减去偏移看下结果:

(lldb) p/x 0x000000010659f804 - 0x10659d000
(long) $5 = 0x0000000000002804

对比下 Mach-O 文件来看下:

image.png

也就是说,懒加载符号表初始化时存储的这个地址指向 __stub_helper

从上图可以看出,0x000000010659f804 这个地址就是执行了 push + jmp 的指令,jmp 的位置都是一样的,push 的值不一样,而 push 的值肯定就是和懒加载符号顺序相关;

大概可以参数来 0x1000027f4 是 binder 函数的真正逻辑代码,而 push 就是传递符号在符号表中位置的参数;

为了验证自己的猜测,先打个断点看看实际的汇编代码吧,应该和 Mach-O 中的大差不差:

image.png

0x10659f7f4 就是 mach-o 文件的地址加上了偏移之后的结果,实际上都是指向 000027f4 这个地址,我们来看看这个地址存储的东西是否和 Mach-O 一致:

image.png

如上图,取 7 个字节转换大小端之后的结果为:0x051d8d4c + 0x180000 -> 0x4C8D1D05180000,和 Mach-o 中存储的值一致。

总结:

  • 懒加载符号表中的指针初始化时,指向 stub_helper 函数。stub_helper 的作用和 stub 函数类似,都是一个代理或者说统筹代码的接口。其逻辑就是调用 binder 函数并传递符号位置参数,参数连同汇编指令(push)一起固定在了 __stup_helper 中;

5. binder 函数

根据上文,binder 函数实际的机器码其实在 mach-O 中就已经能看到了(lea r11......),但是作为辅助验证,我们来打个断点看看实际的调用:

image.png

从注释中可以看到,这里就出现了一个关键的函数:dyld_stub_binder 。先不慌,既然这里又出现了jmpq *xxx(%rip) 这种汇编,我们来算一下 0x00007fff2025cbb4 这个地址怎么来的,也算是作为复习了:

0x10659f7fd 下一个指令的地址图上直接可以看到,即为 0x10659f803,所以:

(lldb) p/x 0x10659f803 + 0x181d
(long) $8 = 0x00000001065a1020
(lldb) x/4xw 0x00000001065a1020
0x1065a1020: 0x2025cbb4 0x00007fff 0x0659f804 0x00000001

验证成功;

接着,因为 binder 函数时从 0x00000001065a1020 这个地址取出来的,我们来看看 0x00000001065a1020 在 Mach-O 中的表现:

image.png

和运行时的汇编代码完美匹配。

至此,可以得到一个结论:

  • 静态编译时期或者说懒加载符号还未被加载之前,懒加载符号表中指针指向 stub_help 函数,这个函数传递符号的位置参数,最终调用 dyld_stub_binder 函数执行懒加载符号的绑定流程;

其实,这里还有个知识点。我们可以看看重定向表:

重定向表

如上图,重定向表中有三种符号,即 __stub、__nl_symbol_ptr、__la_symbol_ptr;懒加载和非懒加载符号好理解,__stub 为什么也会出现在重定向表中呢?

其实也好理解,懒加载符号在动态编译时不会去寻址,而是被替换成了对应的桩函数。非懒加载符号会直接替换成实际函数的指针,而懒加载符号的调用都是通过桩函数来调用。

第一次调用之后也不是直接去替换桩函数(也没办法在运行时去把__TEXT 中的代码改掉),所以必须有一个桩函数表和一个懒加载符号表。

桩函数中的地址在链接时(静态链接 or 动态链接?)确认并写入 __TEXT 段;当程序运行时,__TEXT 段变成只读模式。此时对懒加载符号的修改操作作用在 __la_symbol_ptr 中。而 stub 函数中的逻辑是每次都去 __la_symbol_ptr 中寻址,第一次的指针指向 binder_helper 函数,第二次则指向真实的函数地址。就这样 __stub 和 __la_symbol_ptr 相互配合完成了懒加载的功能;

也正是这个原因,在重定向表中的懒加载符号会有两份,__stub 一份,__la_symbol_ptr 一份:

懒加载符号

从这个现象来看,感觉更像是静态链接时生成桩函数,动态链接时将符号替换成了桩函数。操作完成后对 __TEXT 段 protect ,设置权限,变成只读模式;

6. binder 函数汇编

到这里,接下来就应该看看 dyld_stub_binder 函数了,打个断点:

image.png

这里的汇编代码有点多,以后再具体分析吧;

7. 第二次调用

继续看,第二次调用 NSLog 时是这样的:

image.png

看注释可以知道这里指针存储的位置就是实际的 NSLog 的函数地址了。

也就是说,0x00000001065a1028 这个地址原本存储的是 stub_help 函数相关的位置,并传递了符号的位置信息,最终调用了 binder 函数。而现在被替换成了真实的函数地址,也就是在 binder 函数调用之后,真实的函数地址被替换到了懒加载符号表中,即:

image.png

至此,懒加载符号的绑定流程就结果了,流程图如下:

image.png

ps:其实真实逻辑是没有上图中的”已绑定“的判断的,都是直接调用,上图这么画是为了加深理解;

8. 总结

总结:

  1. 懒加载函数的调用代码都会被替换成对应桩函数的调用代码;
  2. 每个懒加载符号都有对应的桩函数,存储于 __stubs 中。符号的不同本质上是桩函数参数的不同,这个参数和符号在懒加载符号表中的位置有关,而且这个参数(位置)编译时期就确定了,以机器指令的形式(FF25xxxx0000)固定在 __stubs 中;
  3. 懒加载符号表的初始指针指向 __stub_help 函数,在 binder 完毕之后被替换成实际函数的地址。而__stub_help 函数的逻辑和桩函数一样,异常简单,通过 push 传递符号位置参数,然后直接调用 binder 函数;
  4. 桩函数不关心 binder 具体逻辑,每次调用外部函数都是通过桩函数进行调用。而它只是按部就班的每次都去懒加载符号表中取出该符号的指针指向的地址,然后进行跳转。所以第一次调用懒加载符号跳转到了 binder 函数,而第二次调用则跳转到了真实的函数地址;
  5. 非懒加载符号在动态链接时就在非懒加载符号表中写入了函数的具体地址,且非懒加载符号的调用会被直接替换成实际函数的指针,不通过桩函数调用(桩函数的存在是为了实现懒加载的功能);

一句话总结:

桩函数每次调用都会去懒加载符号表中取符号对应的函数地址进行调用。静态编译时期,懒加载符号的指针指向 dyld_stub_binder,所以第一次调用时都会执行外部函数 dyld_stub_binder 来对懒加载符号进行绑定,而 dyld_stub_binder 是非懒加载符号,在动态链接时就已经绑定。dyld_stub_binder 调用完毕之后替换懒加载符号中的指针,指向了实际的函数地址,所以第二次调用仍然是利用桩函数去懒加载符号表中取出指针,但此时的指针已经指向实际的函数地址,可以直接调用;

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,496评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,407评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,632评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,180评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,198评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,165评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,052评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,910评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,324评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,542评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,711评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,424评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,017评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,668评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,823评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,722评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,611评论 2 353