我在锆石核启动找到了这条生产线
str x0, [tmp, #:lo12:zbi_paddr]
为了ARM64。我还发现zbi_paddr
是在C++中定义的:
extern paddr_t zbi_paddr;
所以我开始研究#:lo12:
是什么意思。
我找到了https://stackoverflow.com/a/38608738/6655884,这看起来是一个很好的解释,但它并没有解释最基本的:什么是重新定位以及为什么需要一些东西。
我猜想,由于zbi_paddrr
是在start.S
中定义并在C++代码中使用的,因为start.S
在对象文件中生成地址从0开始,所以链接过程必须将那里的所有地址重新分配到最终可执行文件中的地址。
为了跟踪需要重新定位的符号,ELF存储这些结构,如答案中所述:
typedef struct
{
Elf64_Addr r_offset; /* Address of reference */
Elf64_Xword r_info; /* Symbol index and type of relocation */
} Elf64_Rel;
typedef struct
{
Elf64_Addr r_offset; /* Address of reference */
Elf64_Xword r_info; /* Symbol index and type of relocation */
Elf64_Sxword r_addend; /* Constant part of expression */
} Elf64_Rela;
例如,r_offset
会将zbi_paddr
的地址存储在最终的可执行文件中。然后,当程序加载时,加载程序将查看这些结构,然后从zbi_paddr
代码中填充C++地址。
在那之后,我完全忽略了对S
、A
、P
、X
、abs_g0_s
和lo12
这样的东西的需求。他说,这与指令无法将64位插入寄存器有关。有人能给我更多的背景吗?我不明白,已经有方法可以在寄存器中插入64位。这和重新分配有什么关系?
发布于 2020-11-15 04:03:53
根本的问题是ARM64指令都是32位大小的,这就限制了可以在任何一条指令中编码的即时数据的位数。您当然不能编码64位地址,甚至32位。
内核的代码和静态数据预计在4GB以下,因此为了将数据存储在静态变量zbi_paddr
中,程序员可以编写以下两条指令(包括前面省略的、但非常重要的指令)。请注意,tmp
是上面定义为x9
的宏,因此代码扩展到:
adrp x9, zbi_paddr
str x0, [x9, #:lo12:zbi_paddr]
现在,当链接发生时,链接器将知道整个内核的布局,以及所有符号的相对位置。该方案支持位置无关的代码,因此绝对地址不需要知道,但我们肯定会知道zbi_paddr
和上面的adrp
指令之间的位移,这将适合于一个有符号的32位值,以及zbi_paddr
在其4KB页面中的偏移量(因为内核必须加载在一个对页地址上)。
因此,比特12和更高的位移将被编码到adrp
指令中,该指令有一个21位的直接字段。adrp
将对其进行签名-扩展,将其添加到程序计数器的相应位,并将结果放入x9
中。然后x9
将包含zbi_paddr
绝对地址的63-12位,低12位为零。
zbi_paddr
页面中的12位偏移量将被编码到str
指令的12位直接字段中。它立即将此添加到x9
中的值中,然后生成zbi_paddr
的地址,并将x0
存储在该地址上。因此,我们只使用两个指令就可以在zbi_paddr
中存储一个值。
为了支持这一点,通过组装代码生成的对象文件需要指示链接器,即需要将位移量的32-12位插入到adrp
指令中,而zbi_paddr
地址的11-0位需要插入到str
指令中。这些对链接器的指令是重定位;它们将包含对要编码其地址的符号(此处为zbi_paddr
)的引用,以及具体要对其进行的操作。ELF支持专门为这些指令设计的重定位,即在指令字的正确位置放置正确的位元。
的确,还有其他方法可以将64位值输入寄存器。例如,它可以放置在文字池中,这是一个足够接近相应代码的数据区域,可以用单个ldr
指令(具有PC相对位移)到达。您可以进行重新定位,让链接器在文字池中插入zbi_paddr
的绝对地址。但是加载它需要额外的内存访问,这比adrp
慢;此外,8字节的文字加上ldr
,再加上实际执行存储的str
,总共需要16字节的内存。adrp/str
方法只需要8,并且与位置无关的代码工作得更好,其中链接器可能实际上不知道zbi_paddr
的绝对地址。
如果您不喜欢内存中的加载,您可以将zbi_paddr
的绝对地址用最多4个mov/movk
指令输入寄存器,一次加载16位。对此也有重新定位。但是对于最终的str
,我们将消耗多达20字节的代码;执行五条指令需要比两个时钟周期更长的时间;并且与位置无关的代码仍然存在问题。
因此,adrp/str
和:lo12:
一样,是访问全局变量或静态变量的标准接受方法。如果希望加载而不是存储,则使用adrp/ldr
。如果你想把zbi_paddr
的地址放在一个寄存器里,你可以
adrp x9, zbi_paddr
add x9, x9, #:lo12:zbi_paddr
add
指令也支持12位的即时指令,正是为了这个目的.
这些特性在GNU汇编程序手册中得到了解释。
https://stackoverflow.com/questions/64838776
复制相似问题