ARM64汇编/目标文件中链接器解析机制及跳转指令修复疑问
38: 00000007 .word 0x00000007
3c: 00000000 .word 0x00000000
我大致理解`8: 58000141 ldr x1, 30 <again+0x2c>`对应PC相对寻址到字面池,但对`20: 54ffff21 b.ne 4 <again>`对应的`b.ne #0xffffffffffffffe8`感到困惑,想请教: 1. 我的指令理解是否有误? 2. 链接器如何修复这类指令? 3. 多目标文件链接时是否会调整机器码? --- ## 解答 ### 1. 你的指令理解没有问题 ARM64的分支指令(如`b.ne`)采用**PC相对寻址**,偏移量是相对于当前指令执行时的PC值计算的——注意ARM64流水线特性,当前PC指向的是**当前指令的下一条指令地址**(即当前指令地址+4)。 以你反汇编里的`b.ne`为例: - 指令位于地址`0x20`,执行时PC值为`0x20 + 4 = 0x24` - 目标地址是`0x4`(`again`标签的位置) - 计算相对偏移:`0x4 - 0x24 = -0x20`,转换成64位补码就是`0xffffffffffffffe8`,这就是你看到的偏移值。 这个偏移逻辑是正确的,你的理解没问题。 ### 2. 链接器对指令的修复逻辑 链接器的核心工作之一是**重定位**,针对不同类型的指令处理方式不同: #### (1)同文件内的分支指令(如你的`b.ne again`) 这类指令的目标符号在同一个目标文件内,汇编器已经能计算出准确的相对偏移,生成的机器码本身就是正确的,链接时不需要修改——因为不管代码段最终加载到哪个虚拟地址,当前PC和目标地址的相对距离始终不变。 #### (2)跨文件的分支/调用指令(如`bl other_func`,`other_func`在另一个.o文件) 汇编器无法知道`other_func`的最终地址,会在目标文件中留下一个**重定位条目**,记录需要修改的指令位置和符号信息。链接时: 1. 链接器确定所有符号的最终虚拟地址 2. 计算当前指令PC与目标符号地址的相对偏移 3. 将偏移值除以4(ARM64指令是4字节对齐,偏移量以"指令条数"为单位),填充到分支指令的偏移字段中,完成机器码修改。 #### (3)全局变量/符号的加载指令(如你的`ldr x1, =msg`) `ldr x1, =msg`是汇编伪指令,汇编器会将其转换为: - 在代码段末尾的**字面池**中预留一个8字节位置,用来存储`msg`的地址 - 生成一条PC相对的`ldr`指令,从字面池加载地址 汇编时字面池里的内容是占位符,链接器会将`msg`的最终虚拟地址写入这个占位符位置,完成重定位。 ### 3. 多目标文件链接时会调整机器码 会的,主要在以下场景修改机器码: - **跨文件符号引用**:当指令引用其他目标文件中的函数或变量时,链接器会计算最终的相对偏移或绝对地址,修改指令本身或字面池中的数据。 - **段地址合并调整**:链接器会将多个目标文件的代码段、数据段合并到连续的虚拟地址空间,对于依赖绝对地址的指令(如直接加载全局变量地址),会更新对应的地址值。 - **动态链接相关重定位**:如果生成的是动态链接可执行文件,链接器会生成PLT(过程链接表)和GOT(全局偏移表),并修改相关指令指向这些表项,让程序在运行时动态绑定符号地址。 --- 内容的提问来源于stack exchange,提问作者Luk




