这里我们讨论的是iOS是如何懒加载调用外部函数的,比如说:NSLog
这里主要涉及到__stubs 、__stub_helper、__la_symbol_ptr、__got.
__stubs 桩代码,存放的是懒加载外部函数的十六进制指令,通过https://armconverter.com/?dis... 网页可以转换hex code和arm64汇编代码,这里一般是三行汇编,举例:

NOP
ldr x16,#0x12bc //(这里会根据当前指令地址+0x12bc计算得到地址跳转到__la_symbol_ptr)
br x16

__la_symbol_ptr 存储的懒加载函数指针,编译时存储的是__stub_helper地址,第一次懒加载完成之后,存储的值会被修改成真正的函数地址

__stub_helper 桩辅助指令,主要是去获取函数地址后,调用_dyld_stub_binder函数
绑定到__la_symbol_ptr上

__got 非懒加载函数指针,_dyld_stub_binder就是在此地方,会在程序启动的时机立马绑定.

下面我以一个demo为例在xcode里和MachOView里来探寻一下,懒加载函数调用的过程:

NSLog(@"\nClass:%@ \naddress:%p\nContent:%@",object_getClass(str2),str2,str2);
NSLog(@"songxuhua");

第一个NSLog调用

我们在两个NSLog处打上断点,xcode运行程序,在Debug->Debug Workflow选择Always show disassembly,可以在运行时展示汇编,下图展示第一个断点处的汇编

如上图所示,可知是bl到stub里的NSLog的地址,打开MachOView,查看__stubs发现存储的是一段数据,经由arm汇编和数据互转转换得到汇编如下

nop 
ldr x16, #0x1c74
br x16

让我们看看xcode里,按住ctrl+step into键,进入指令级的调试

进到NSLog __stubs的指令,发现和上面MachOView转换的汇编好像差了4个字节,不要着急,是因为MachOView里的offset是63AC,而xcode里的应该是做过偏移修正的(因为在xcode里的地址是ldr作为当前指令,而MachOView是nop为当前指令),image list -o -f得知ASLR偏移为d4c000,d523ac-d4c000=63ac,可知确实是跳入了stubs里

(lldb) image list -o -f |grep TestForObjc
[  0] 0x0000000000d4c000 /Users/ijm/Library/Developer/Xcode/DerivedData/TestForObjc-agwmebvcxfuznzcevpmyvcrnwcuj/Build/Products/Debug-iphoneos/TestForObjc.app/TestForObjc


好,下面来确定下这段汇编做什么的,首先第一行nop,除了占4个byte位并无其他作用,
ldr x16,#0x1c70 大概是相当于从当前指令的地址(63b0)+偏移0x1c70 =0x8020
br x16 跳转执行0x8020,我们在MachOView找找0x8020是干啥的,发现指到了__la_symbol_ptr

__la_symbol_ptr里的数据位64d8,这里就是__stub__helper的地方了

对比下xcode里也是一样的,只是加了ASLR偏移,d524d8-d4c000=64d8,刚好

我们来看看_stub_helper干了些啥,首先ldr w16,0x1000064e0读取64e0的指存储在w16寄存器,然后b 0x1000064c0,跳转到了_stub_helper顶部

可以看到的是跳转到_dyld_stub_binder函数做绑定,ldr x16,#0x1b40,ldr里地址带#表示相对当前地址偏移量,0xd524d0-0xd4c000+0x1b40=0x8010,0x8010就到了_got里,改地址存放着dyld_stub_binder的地址,静态的时候是00000...,在运行时启动的时候,non lazy加载绑定上真正的函数地址

第二次NSLog调用

调试到第二个NSLog调用,按住ctrl+step into键,进入指令级的调试

继续ctrl+step,发现其最终跳入了Foundation NSLog的地址,说明在_la_symbol_ptr已经绑定上了NSLog函数地址,直接br调用了

由此我们总结如下:第一次调用外部函数,会由stubs-->la_symbol_ptr-->_stub_helper-->dyld_stub_binder绑定后才能调用,以后调用都说stubs-->la_symbol_ptr就能调用到指定函数

知识点总结:
1.ldr相对地址,带#立即数的表示从当前地址偏移,例如:ldr x16,#0x1b40 ,表示从当前指令地址+0x1b40的地址处读取值付给x16寄存器
2.ldr绝对地址,带立即数无#前缀,表示绝对地址,例如:ldr w16,0x1000064e0,表示从0x1000064e0地址处读取值复制给w16寄存器
3.ctrl+ step into可以进入指令级的调试
4.汇编里也可以打断点


宋冬野
32 声望4 粉丝