这里咱们探讨的是iOS是如何懒加载调用内部函数的,比如说:NSLog
这里次要波及到__stubs 、__stub_helper、__la_symbol_ptr、__got.
__stubs 桩代码,寄存的是懒加载内部函数的十六进制指令,通过https://armconverter.com/?dis... 网页能够转换hex code和arm64汇编代码,这里个别是三行汇编,举例:
NOPldr 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, #0x1c74br 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.汇编里也能够打断点