.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这不是主代码的第一行吗?
cgfeq70w1#
原则上,adr reg, label和ldr reg, =label都会将label的地址放入寄存器reg中。因此,问题中的代码将导致x1m4 n1,和x1, x3都包含varA的地址,并且都包含值25。然而,它们在引擎盖下的工作方式不同,这一点很好理解,因为这会导致每种方法的某些限制和权衡。
adr reg, label
ldr reg, =label
label
reg
x1, x3
varA
pc
add reg, pc, #(label - .)
.text
.data
限制是指令空间只能编码19位位移,因此如果程序太大,标签和指令在任一方向上的距离超过1 MB,则程序将无法链接。您可以使用adrp和add的双指令序列将其增加到4 GB,请参阅了解ARM重定位(示例:字符串x 0,[临时,#:lo 12:zbi_paddr])。
adrp
add
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组合,它提供了一个位置无关的解决方案,也适用于相当大的程序。
ldr reg, literal_pool
literal_pool
adrp/add
c8ib6hqw2#
我终于明白了这个概念。x0、x2只保存地址,然后我们从地址中将值分配给x1、x3。
2条答案
按热度按时间cgfeq70w1#
原则上,
adr reg, label
和ldr 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,则程序将无法链接。您可以使用
adrp
和add
的双指令序列将其增加到4 GB,请参阅了解ARM重定位(示例:字符串x 0,[临时,#:lo 12:zbi_paddr])。ldr reg, =label
会将label
的绝对位址组合到内存中某个邻近的位置(称为常值集区),并发出load指令以从该处撷取位址。相当于:其中,
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
组合,它提供了一个位置无关的解决方案,也适用于相当大的程序。c8ib6hqw2#
我终于明白了这个概念。
x0、x2只保存地址,然后我们从地址中将值分配给x1、x3。