了解 ARM 重定位(示例:str x0, [tmp, #:lo12:zbi_paddr])

Pap*_*ika 3 assembly arm linker-scripts arm64

我在zircon kernel start.S中找到了这行汇编代码

str     x0, [tmp, #:lo12:zbi_paddr]
Run Code Online (Sandbox Code Playgroud)

对于ARM64。我还发现zbi_paddrC++ 中定义了:

extern paddr_t zbi_paddr;
Run Code Online (Sandbox Code Playgroud)

所以我开始研究这#:lo12:意味着什么。

我发现/sf/answers/2702611691/看起来像是一个很好的解释,但它没有解释非常基本的内容:什么是重新分配以及为什么需要一些东西。

我猜想,由于在 C++ 代码中zbi_paddrr定义并使用,由于在地址从 0 开始的目标文件上生成,因此链接过程必须将其中的所有地址重新分配到最终可执行文件中的地址。start.Sstart.Sstart.o

为了跟踪需要重新分配的符号,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;
Run Code Online (Sandbox Code Playgroud)

例如,将存储最终可执行文件中r_offset的地址。然后,当加载程序时,加载器会查找这些结构,然后从 C++ 代码中zbi_paddr填充地址。zbi_paddr

S从那以后,我完全错过了对、APXabs_g0_s之类的东西的需要lo12。他说这与指令无法将 64 位插入寄存器有关。有人可以给我更多背景信息吗?我无法理解,已经有方法可以将 64 位插入寄存器。这与重新分配有何关系?

Nat*_*dge 8

根本问题是 ARM64 指令的大小都是 32 位,这限制了任何一条指令中可以编码的立即数据的位数。您当然无法编码 64 位地址,甚至无法编码 32 位。

内核的代码和静态数据预计会在 4 GB 以下,因此为了在 static 变量中存储数据zbi_paddr,程序员可以编写以下两条指令(包括您省略但至关重要的前一条指令)。请注意tmp是上面定义的宏x9,因此代码扩展为:

adrp    x9, zbi_paddr
str     x0, [x9, #:lo12:zbi_paddr]
Run Code Online (Sandbox Code Playgroud)

现在,当链接发生时,链接器将知道整个内核的布局以及所有符号的相对位置。该方案支持与位置无关的代码,因此不需要知道绝对地址,但我们肯定会知道上面的指令之间的位移,这将适合有符号的32位值,以及其4KB内的偏移量页(因为内核必须加载到页对齐的地址)。zbi_paddradrpzbi_paddr

因此,该位移的第 12 位及更高位将被编码到adrp具有 21 位立即数字段的指令中。 adrp将对它进行符号扩展,将其添加到程序计数器的相应位,并将结果放入x9. 然后x9将包含 的绝对地址的第 63-12 位zbi_paddr,其中低 12 位被清零。

其页内的 12 位偏移量zbi_paddr将被编码到指令的 12 位立即数字段中str。它将这个立即数添加到 中的值中x9,然后生成 的地址zbi_paddr,并将其存储x0在该地址处。zbi_paddr因此,我们只需两条指令就可以存储一个值。

为了支持这一点,通过汇编代码生成的目标文件需要指示链接器需要将位移的位 32-12 插入到指令中adrp,并且需要将地址的位 11-0zbi_paddr插入到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 位。也有为此进行的搬迁。但对于final str,我们使用了最多20字节的代码;执行五个指令比执行两个指令需要更多的时钟周期;并且与位置无关的代码仍然存在问题。

因此,adrp/str如前所述:lo12:, 是访问全局或静态变量的标准接受方法。如果您想加载而不是存储,请使用adrp/ldr. zbi_paddr如果你想要寄存器中的地址,你可以

adrp x9, zbi_paddr
add x9, x9, #:lo12:zbi_paddr
Run Code Online (Sandbox Code Playgroud)

add指令还支持 12 位立即数,正是出于此目的。

GNU 汇编器手册中对这些功能进行了解释。