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终止程序,也就是说现在不存在实际意义的懒加载了。
相关推荐
若水无华2 天前
fiddler 配置ios手机代理调试
ios·智能手机·fiddler
Aress"2 天前
【ios越狱包安装失败?uniapp导出ipa文件如何安装到苹果手机】苹果IOS直接安装IPA文件
ios·uni-app·ipa安装
Jouzzy2 天前
【iOS安全】Dopamine越狱 iPhone X iOS 16.6 (20G75) | 解决Jailbreak failed with error
安全·ios·iphone
瓜子三百克2 天前
采用sherpa-onnx 实现 ios语音唤起的调研
macos·ios·cocoa
左钦杨2 天前
IOS CSS3 right transformX 动画卡顿 回弹
前端·ios·css3
努力成为包租婆2 天前
SDK does not contain ‘libarclite‘ at the path
ios
安和昂3 天前
【iOS】Tagged Pointer
macos·ios·cocoa
I烟雨云渊T3 天前
iOS 阅后即焚功能的实现
macos·ios·cocoa
struggle20253 天前
适用于 iOS 的 开源Ultralytics YOLO:应用程序和 Swift 软件包,用于在您自己的 iOS 应用程序中运行 YOLO
yolo·ios·开源·app·swift
Unlimitedz3 天前
iOS视频编码详细步骤(视频编码器,基于 VideoToolbox,支持硬件编码 H264/H265)
ios·音视频