ARM64 v8架构下获取overwriteFunc地址为何返回PLT表地址而非真实地址
问题解答
现象本质
你获取到的0x423A90是**过程链接表(PLT, Procedure Linkage Table)**中overwriteFunc对应的表项地址,并非函数本身的实现地址,这是类Unix系统动态链接机制的标准设计,和ARM64架构本身的指令特性也有关,不属于异常问题。
具体原因拆解
1. PLT存在的核心作用
当overwriteFunc定义在外部动态共享库中时,编译期编译器无法确定其运行时的真实内存地址,因此引入PLT作为中间跳转层:
- 所有对外部函数的调用、地址获取都会先指向对应PLT项,保证编译阶段生成的代码是位置无关的,不需要修改共享库本身的代码就能加载到任意内存地址运行。
- 配合GOT(全局偏移表)实现延迟绑定:只有函数第一次被调用时才会触发动态链接器解析函数真实地址,并回填到GOT对应表项中,后续调用直接读取GOT里缓存的地址跳转即可,大幅降低程序启动时的重定位开销。
2. 对应PLT汇编逻辑解析
0x0000000000423a90 <+0>: adrp x16, 0x513000 ; 计算GOT表所在页的基地址 0x0000000000423a94 <+4>: ldr x17, [x16,#1640] ; 从GOT对应表项加载函数地址 0x0000000000423a98 <+8>: add x16, x16, #0x668 ; 计算重定位参数的地址,供第一次解析用 0x0000000000423a9c <+12>: br x17 ; 跳转到加载的地址
第一次调用时GOT表项中存储的是PLT公共解析 stub 的地址,br x17会跳转到解析逻辑完成地址解析和回填;后续调用时GOT表项已经存了overwriteFunc的真实地址,直接跳转即可。
3. 为什么需要两次跳转而非一次
这个设计是动态链接需求和ARM64指令集特性共同决定的:
- 首先是延迟绑定的需求:必须有中间层来判断是否已经完成地址解析,第一次调用需要跳转到解析逻辑,后续调用跳转到真实函数,两次跳转是实现这个分支逻辑的必要结构。
- 其次是ARM64指令集的限制:ARM64采用定长4字节指令,单条指令无法直接加载64位的立即数地址,也无法完成跨大地址范围的直接跳转,必须通过多次读取内存、寄存器中转才能完成跳转,因此PLT的实现天然需要多条指令完成地址加载+跳转的逻辑。
如果不需要延迟绑定,可以在编译时添加-fno-plt参数关闭PLT机制,编译器会直接通过GOT表调用外部函数,此时函数地址访问只需要一次跳转即可。
内容的提问来源于stack exchange,提问作者Qiyang Li
相关产品推荐
相关产品推荐

