assembly 为什么使用adr和ldr x2,=var来获取变量的地址?

xbp102n0  于 2022-11-13  发布在  其他
关注(0)|答案(2)|浏览(181)
.data
varA: .quad 25
.text
.global main
//main function
main:
    adr x0,varA
    ldr x1, [x0]
    ldr x2,=varA
    ldr x3,[x2]

x0和x2会一样吗?只是记忆体存取的不同。
但是为什么我们需要再次使用[]赋值给相同或不同的寄存器呢?2这不是主代码的第一行吗?

cgfeq70w

cgfeq70w1#

原则上,adr reg, labelldr reg, =label都会将label的地址放入寄存器reg中。因此,问题中的代码将导致x1m4 n1,和x1, x3都包含varA的地址,并且都包含值25。
然而,它们在引擎盖下的工作方式不同,这一点很好理解,因为这会导致每种方法的某些限制和权衡。

  • adr reg, label将把指令自身地址与label地址之间的位移量组装到指令中。在运行时,它将此位移量与pc的值相加,并将结果放入reg中,类似于add reg, pc, #(label - .)(如果存在这样的指令)。这具有位置无关的优点;如果将整个二进制代码加载到存储器中的任意地址,则只要.text.data部分保持在相同的 * 相对 * 位置,则相同的机器代码仍然工作。

限制是指令空间只能编码19位位移,因此如果程序太大,标签和指令在任一方向上的距离超过1 MB,则程序将无法链接。您可以使用adrpadd的双指令序列将其增加到4 GB,请参阅了解ARM重定位(示例:字符串x 0,[临时,#:lo 12:zbi_paddr])。

  • ldr reg, =label会将label的绝对位址组合到内存中某个邻近的位置(称为常值集区),并发出load指令以从该处撷取位址。相当于:
ldr reg, literal_pool
...
literal_pool:
.quad label

其中,ldr reg, literal_pool是“文字加载”:它自己的地址和literal_pool的地址之间的位移被编码在指令中,在运行时它把位移加到pc上,然后从结果地址加载。位移必须小于1 MB,但是汇编程序通常可以安排把一个文字池放在这个范围内。
优点是,原则上,它不受label相对位置的限制,可以位于64位地址空间的任何位置。缺点是需要额外的内存:除了X1 M22 N1 X指令本身的4个字节之外,文字池中的X1 M23 N1 X的绝对地址还需要另外的8个字节。
另外,从上面可以看出,ldr reg, =label不是位置无关的,因为它需要label的 * 绝对 * 地址才能进入文本池。如果代码最终被加载到链接器假定的地址之外的地址,比如address space layout randomization,那么当程序被加载时,label的存储地址将不得不被更新。在某些情况下,操作系统会自动为您执行此操作(例如Linux),但它确实会使程序的加载花费一些额外的周期,而且必要的元数据会使二进制文件稍大。其他操作系统不支持这一点,例如MacOS(Why can't I assemble absolute addresses in the .text section on ARM64 MacOS?),在这种情况下,ldr reg, =label是完全不可用的。如果你是在裸机上运行,而不是在现有的操作系统下,你必须在加载器中编写额外的代码来完成这个重定位。
因此,尽管ldr reg, =label看起来是最简单的方法,但它并不是最有效或可移植的。编译器通常使用上面提到的adrp/add组合,它提供了一个位置无关的解决方案,也适用于相当大的程序。

c8ib6hqw

c8ib6hqw2#

我终于明白了这个概念。
x0、x2只保存地址,然后我们从地址中将值分配给x1、x3。

相关问题