iOS最新外部符号加载

介绍

iOS外部符号加载方式有两种:懒加载和非懒加载。

  • 懒加载:首次调用该符号才加载。

  • 非懒加载:app启动时加载。

非懒加载

默认加载方式。比如下面的代码,外部符号调用会先替换成一个桩函数,在__TEXT,__stubs段会生成该桩函数。

int main(int argc, const char * argv[]) {
	MMFWHeaderTest();
}
.....
-> 0x100003c93 <+99>:  callq  0x100003cea

我们接着打断点进去0x100003cea这个桩函数看看。

-> 0x100003cea <+0>: jmpq   *0x310(%rip)  

先在MachOView看看这个段的内容__TEXT,__stubs

注意看Data的规律,都是FF25开头,MMFWHeaderTest的FF25后面是1003,考虑到MacOS是小端,所以1003正对应了0x310

这也是为什么桩函数在__TEXT段,FF25是跳转指令,后面的都是跳转的长度。
0x100003cea <+0>: jmpq *0x310(%rip)的意思是,从下个地址开始,加上0x310,取出该地址存的值,跳转到这。

桩函数这里每个函数大小是0x6,所以是0x100003cea + 0x6 + 0x310 = 0x0000000100004000

看看0x0000000100004000在mach-o文件是哪个段,通过image list得到偏移是0x0000000100000000,相对地址就是0x0000000100004000-0x0000000100000000=0x4000

取出0x0000000100004000存的数据,跳转到这个地址。

(lldb) x/gx 0x0000000100004000
0x100004000: 0x00000001000a9f60
(lldb) dis -a 0x00000001000a9f60
MMFW`MMFWHeaderTest:
    0x1000a9f60 <+0>: pushq  %rbp
    0x1000a9f61 <+1>: movq   %rsp, %rbp
    0x1000a9f64 <+4>: popq   %rbp
    0x1000a9f65 <+5>: retq   

总结

  • 非懒加载会将外部函数替换成一个桩函数,桩函数保存在__TEXT,__stubs,桩函数指令是取出__DATA_CONST,__got段指定位置的值,然后跳转到该地址。
  • __DATA_CONST,__got保存非懒加载符号,会在启动时写入对应外部函数的具体地址。

懒加载

Other Link Flag指定-undefined dynamic_lookup

查看汇编指令

//调用MMFWHeaderTest
0x100003c18 <+104>: movq   0x4831(%rip), %rdi
......

//桩函数
0x100003c6a <+0>: jmpq   *0x4390(%rip)
......

(lldb) p/x 0x100003c6a + 0x4390 + 0x6
(long) $0 = 0x0000000100008000
(lldb) x/gx 0x0000000100008000
0x100008000: 0x00000001000a9f60
(lldb) dis -a 0x00000001000a9f60
MMFW`MMFWHeaderTest:
    0x1000a9f60 <+0>: pushq  %rbp
    0x1000a9f61 <+1>: movq   %rsp, %rbp
    0x1000a9f64 <+4>: popq   %rbp
    0x1000a9f65 <+5>: retq   

奇怪了,好像跟之前没什么不一样,也是一个桩函数,然后桩函数调到一个函数指针上。但是跟网上其他文章说的不一样呀?

再看看文件mach-o文件。


发现外部函数指针从__DATA__CONST,__got移到了__DATA,__la_symbol_ptr,除此之外没有其他改变。

那没办法了,只能去看dyld源码了。

dyld

首先要确定从哪个函数开始,这里在OC类里的+load方法里打个断点。

那就先从dyld4::prepare函数看起,我也是第一次看,不熟悉。

那就不看细节,就从名称来看,看哪个像。

可以调试dyld的汇编代码,在一些可能的指令callq前后打断点,执行 x/gx {符号指针},看执行完哪个函数后,函数指针里有正确的函数地址了,再用dis -a验证。

你别说,这还真让我找到了

for ( const Loader* ldr : state.loaded ) {
			......
            ldr->applyFixups(fixupDiag, state, cacheDataConst, true);
			......
        }

applyFixups执行几次后,我这边的外部函数指针就有正确的地址了。

接着进入到JustInTimeLoader::applyFixups。看到里面有几行代码在打印日子。

        if ( state.config.log.fixups ) {
            const char* targetLoaderName = target.targetLoader ? target.targetLoader->leafName() : "<none>";
            state.log("<%s/bind#%lu> -> %p (%s/%s)\n", this->leafName(), bindTargets.count(), targetAddr, targetLoaderName, target.targetSymbolName);
        }

如果启动时设置一些环境变量,dyld是能打印一些信息的。所以问一下那个男人,man dyld。找到了一个可能的变量。

DYLD_PRINT_BINDINGS
              If set, causes dyld to print a line each time a symbolic name is
              bound.

再把DYLD_PRINT_BINDINGS在代码里一搜。

this->fixups         = security.allowEnvVarsPrint && process.environ("DYLD_PRINT_BINDINGS");

估计就是这个了,设置好环境变量,看到输出栏里打印了

dyld[28319]: <MM/bind#0> -> 0x1000ae0b8 (MMFW/_OBJC_CLASS_$_MMFWHeader)

那么基本就可以断定了,现在懒加载符号也是在启动时绑定好符号了。

既然都下载源码了,那顺便看看dyld_stub_binder这个懒加载辅助函数的源码吧(可以在__DATA_CONST,__got找到)。

     // dyld_stub_binder is no longer used, but needed by old binaries to link
    .align 4
    .globl dyld_stub_binder
dyld_stub_binder:
#if __x86_64__ || __i386__
    jmp __dyld_missing_symbol_abort
#else
    b   __dyld_missing_symbol_abort
#endif

这里说dyld_stub_binder不再使用了,保留这个函数是为了兼容老版本的二进制,因为里面有依赖到。现在实际上是跳转到__dyld_missing_symbol_abort,abort这个词一听就知道,执行这个函数程序要流产、停止。

那我再试试声明一个不存在的函数调用,因为-undefined dynamic_lookup的存在,所以不会报错。

void MMFWHeaderTestUndefined(void);
int main(int argc, const char * argv[]) {
	MMFWHeaderTestUndefined();
}

//汇编
->  0x100003bf8 <+104>: callq  0x100003c56               ; symbol stub for: MMFWHeaderTestUndefined
    ......
->  0x100003c56 <+0>: jmpq   *0x43ac(%rip)             ; (void *)0x00007ff8005d1caa: _dyld_missing_symbol_abort

果然,本来应该调用dyld_stub_binder,但是这个函数执行的是__dyld_missing_symbol_abort,程序结束了。

总结

  • 懒加载现在跟非懒加载一样,只是外部符号位置保存在__DATA,__la_symbol_ptr
  • 调用未定义的函数会走dyld_stub_binder,但dyld_stub_binder实际上会走__dyld_missing_symbol_abort终止程序,也就是说现在不存在实际意义的懒加载了。
相关推荐
袁代码11 小时前
Swift 开发教程系列 - 第10章:泛型
开发语言·ios·swift·ios开发
袁代码11 小时前
Swift 开发教程系列 - 第12章:协议与协议扩展
开发语言·ios·swift·ios开发
袁代码14 小时前
SwiftUI开发教程系列 - 第1章:简介与环境配置
开发语言·ios·swiftui·swift·ios开发
西瓜本瓜@20 小时前
iPhone 17版本的开发者权限如何开启?
开发语言·ios·iphone
海绵波波10720 小时前
Webserver(5.4)项目整体
c++·ios·iphone
HH思️️无邪20 小时前
iOS SmartCodable 替换 HandyJSON 适配记录
ios·swift
chaosama1 天前
禁止uni小程序ios端上下拉伸(橡皮筋效果)
ios·小程序
Zender Han1 天前
Flutter自定义矩形进度条实现详解
android·flutter·ios
S0linteeH1 天前
iOS 18.2 六大新功能外媒實測|ChatGPT進化版SIRI、自製Genmoji
ios
DisonTangor2 天前
苹果发布iOS 18.2首个公测版:Siri接入ChatGPT、iPhone 16拍照按钮有用了
ios·chatgpt·iphone