【问题标题】:Accessing global variables in ARM64 position independent assembly code在 ARM64 位置独立汇编代码中访问全局变量
【发布时间】:2023-02-17 07:32:46
【问题描述】:
我正在为 macOS 编写一些 ARM64 汇编代码,它需要访问一个全局变量。
我尝试使用this SO answer 中的解决方案,如果我按原样调用该函数,它可以正常工作。但是,我的应用程序需要修补这个函数的一些指令,而我这样做的方式是,函数在进程中被移动到内存中的其他地方。请注意,adrp/ldr 对在修补过程中未受影响。
但是,如果我在将函数移动到内存中的其他位置后尝试运行该函数,它将不再返回正确的结果。即使我只是按原样 memcpy() 代码而不打补丁,也会发生这种情况。使用调试器跟踪后,我将问题隔离到全局值的地址被adrp/ldr 对错误加载(奇怪的是,ldr 被组装为add,如@所示987654329@ 编译二进制文件后直接——不确定它是否与这里的问题有某种关系。)
加载全局变量的正确方法是什么,以便它在被复制到其他地方并从那里运行的函数中幸存下来?
【问题讨论】:
标签:
macos
linker
arm64
dynamic-linking
relocation
【解决方案1】:
请注意,adrp/ldr 对在修补过程中未受影响。”
有问题。如果您从二进制文件中提取代码,那么您实际上需要重新链接它。
有两种处理方法:
-
如果您可以完全控制段布局,那么您可以拥有一个包含所有程序集的可执行段,紧挨着它的是一个包含代码需要的所有地址的段,并确保该程序集仅引用了上面的内容那个页面。然后无论你在哪里复制你的程序集,你也会复制它旁边的数据页。这将使您能够使用在加载二进制文件时由动态链接器重新设置基址的静态地址。这可能看起来像:
.section __ASM,__asm,regular
.globl _asm_stub
.p2align 2
_asm_stub:
adrp x0, _some_ref@PAGE
ldr x0, [x0, _some_ref@PAGEOFF]
ret
.section __REF,__ref
.globl _some_ref
.p2align 3
_some_ref:
.8byte _main
用-Wl,-segprot,__ASM,rx,rx 编译它,你将得到一个可执行文件__ASM 和一个可写的__REF 段。这两个在被复制时必须保持彼此的相对位置。
(请注意,在 arm64 macOS 上你不能将符号引用放入可执行段中,以便动态链接器变基,因为它会在尝试这样做时出错和崩溃,即使它能够做到这一点,也会使代码签名无效。)
-
您充当链接器,扫描与 PC 相关的指令并在进行时重新链接它们。 arm64 中与 PC 相关的指令列表非常短,因此工作量应该是可行的:
-
adr和adrp
-
b和bl
-
b.cond(以及带有 FEAT_HBC 的 bc.cond)
-
cbz和cbnz
-
tbz和tbnz
-
ldr 和ldrsw(字面意思)
-
ldr(SIMD 和 FP 文字)
-
prfm(字面意思)
对于其中的每一个,您都必须检查它们的目标地址是否在正在复制的范围内。如果是这样,那么您将单独保留该指令(除非您将代码复制到 4K 页面中与之前不同的偏移量,在这种情况下,您必须修复 adrp 指令)。如果不是,那么您将不得不重新计算偏移量并发出一条新指令。一些指令的最大偏移量非常低 (tbz/tbnz ±32KiB)。但通常唯一的跨函数边界引用地址的指令是adr、adrp、b、bl和ldr。如果页面上的所有代码都是您编写的,那么您可以执行 adrp+add 而不是 adr 和 adrp+ldr 而不仅仅是 ldr,并且如果您有编译器生成的代码在那里,所有adr和ldr的前后都会有一个nop,你可以用它把它们变成一个adrp组合。这应该使您的最大参考范围达到 ±128MiB。