iOS最新外部符号加载

embedded/2024/9/23 1:56:16/

介绍

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

懒加载

默认加载方式。比如下面的代码,外部符号调用会先替换成一个桩函数,在__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  %rbp0x1000a9f61 <+1>: movq   %rsp, %rbp0x1000a9f64 <+4>: popq   %rbp0x1000a9f65 <+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  %rbp0x1000a9f61 <+1>: movq   %rsp, %rbp0x1000a9f64 <+4>: popq   %rbp0x1000a9f65 <+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_BINDINGSIf set, causes dyld to print a line each time a symbolic name isbound.

再把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
#elseb   __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终止程序,也就是说现在不存在实际意义的懒加载了。

http://www.ppmy.cn/embedded/3568.html

相关文章

储蓄比特币:4年复合年化增长率72%

原创 | 刘教链 要说这大多数的散户&#xff0c;甚至包括一些机构&#xff0c;都是追涨杀跌来着。这一点从现货比特币ETF的流入数据就能看到一二。这几日BTC&#xff08;比特币&#xff09;接连下挫&#xff0c;短短10天工夫&#xff0c;就从72k速降1万刀至62k&#xff0c;并一度…

盲人安全导航技巧:科技赋能让出行更自如

作为一名资深记者&#xff0c;长期关注并报道无障碍领域的发展动态。今日&#xff0c;我将聚焦盲人安全导航技巧&#xff0c;探讨这一主题下科技如何赋能视障人士实现更为安全、独立的出行。一款融合了实时避障、拍照识别物体及场景功能的盲人出行辅助应用叫做蝙蝠避障&#xf…

广播变量在spark中的用法以及数据倾斜问题的解决方法

1. spark中的广播变量 应用场景&#xff1a;广播变量用于在集群的各个节点的executor 中高效的分发一个只读的变量副本 操作原理&#xff1a;创建一个广播变量时&#xff0c;spark会将变量序列化并发送到每一个executor&#xff0c;每一个executor存一个副本&#xff0c;而不需…

使用 Docker 部署 instantbox 轻量级 Linux 系统

1&#xff09;instantbox 介绍 GitHub&#xff1a;https://github.com/instantbox/instantbox instantbox 是一款非常实用的项目&#xff0c;它能够让你在几秒内启动一个主流的 Linux 系统&#xff0c;随起随用&#xff0c;支持 Ubuntu&#xff0c;CentOS&#xff0c; Arch Li…

Jmeter 测试Dubbo接口-实例

1、Dubbo插件准备 ①把jmeter-plugins-dubbo-2.7.4.1-jar-with-dependencies.jar包放在D:\apache-jmeter-5.5\lib\ext目录 ②重新打开Jmeter客户端 在线程组-添加-取样器-dubbo simple&#xff0c;添加dubbo接口请求 2、Jmeter测试lottery接口 ①配置zookeeper参数 由于dub…

K8s: 在Pod中将configmap数据注入容器

configMap 概述 文档: https://kubernetes.io/zh-cn/docs/concepts/configuration/configmap/ Kubernetes 为我们提供了 ConfigMap&#xff0c;可以方便的配置一些变量 是一个存储键值对 key-value 对象的 创建一个可以包含多个键值对的 ConfigMap, 以下是&#xff1a;mul-c…

OpenHarmony实战开发-如何使用text组件的enableDataDetector属性实现文本特殊文字识别。

介绍 本示例介绍使用text组件的enableDataDetector属性实现文本特殊文字识别。 效果图预览 使用说明 1.进入页面&#xff0c;输入带有特殊文字的信息并发送&#xff0c;对话列表中文本会自动识别并标识特殊文字。目前支持识别的类型包括电话号码、链接、邮箱和地址&#xff…

【鸿蒙开发】第二十一章 Media媒体服务(二)--- 音频播放和录制

1 AVPlayer音频播放 使用AVPlayer可以实现端到端播放原始媒体资源&#xff0c;本开发指导将以完整地播放一首音乐作为示例&#xff0c;向开发者讲解AVPlayer音频播放相关功能。 以下指导仅介绍如何实现媒体资源播放&#xff0c;如果要实现后台播放或熄屏播放&#xff0c;需要…