20

ADRP

PC 相对偏移处的 4KB 页的地址。

ADRL

将 PC 相对地址加载到寄存器中。它类似于 ADR 指令。ADRL 可以加载比 ADR 更广的地址范围,因为它生成两条数据处理指令。

具体来说,

ADRL 汇编成两条指令,一条 ADRP 后跟 ADD。如果汇编器不能在两条指令中构造地址,它会产生一个重定位。然后链接器生成正确的偏移量。ADRL 产生与位置无关的代码,因为地址是相对于 PC 计算的。

做什么ADRPADRL指示做什么?更重要的是,如何ADRP构造ADD一个PC相对地址?

4

1 回答 1

35

ADR

ADR 是一个简单的 PC 相对地址计算:你给它一个直接的偏移量,它在寄存器中存储相对于当前 PC 的地址。

例如,如果将以下 ADR 指令放置在内存中的位置 0x4000:

adr x0, #1

那么在这条指令执行后x0现在包含值 0x4001。在带有可运行断言的 GitHub 上

我们可以尝试这样做:

mov x0, #0x4001

但是PC相对寻址有以下优点:

  • 所有 ARMv7 / ARMv8 指令都是 4 个字节长。这与指令宽度可变的 x86 形成了鲜明的对比。

    这简化了很多事情,但它有一个不幸的含义:你不能在一条指令中编码完整的地址(4 / 8 字节),因为我们需要一些位来编码指令本身。

    即使我们不能存储完整地址,我们也可以通过相对于 PC 的地址来引用其中的一些(那些适合编码的),这对于许多应用程序来说通常已经足够了,因为我们通常只跳转到附近的代码位置。

    这里的基本原理类似于ldr =伪指令的存在:为什么在 ARM 汇编中使用 LDR 而不是 MOV(反之亦然)?

  • 它允许位置无关的代码,这是避免共享库在内存中发生冲突的基础,但对于启用ASLR的主文本段也很有用,另请参见:What is the -fPIE option for position-independent executables in gcc 和ld?

  • 生成的代码更小

ADR 指令使用 21 位立即数作为偏移量,允许 +-1MiB 跳转(符号为 20 位 + 1)。

在 ARMv7/aarch32 中,ADR 有时可以通过 ADD 和 SUB 与 PC 实现,如ARMv7 DDI 0406C.d 手册D9.4“在 ARM 指令中显式使用 PC”中所述:

ADR 指令的某些形式可以表示为 ADD 或 SUB 的形式,PC 为 Rn。这些形式的 ADD 和 SUB 是允许的,并且不被弃用。

TODO 什么时候不能实现ADD?GNU GAS 建议 ADR 只是一个伪操作,总是组装成 ADD 或 SUB:https ://sourceware.org/binutils/docs-2.31/as/ARM-Opcodes.html#ARM-Opcodes

该指令将标签的地址加载到指定的寄存器中。根据标签所在的位置,该指令将评估为 PC 相关的 ADD 或 SUB 指令。如果标签超出范围,或者它没有在与 ADR 指令相同的文件(和节)中定义,则会产生错误。该指令不会使用文字池。

然而,在 ARMv8 aarch64 中,PC 不能像通用寄存器那样在每条指令中使用,因此 ADR 在那里实际上很重要,并且有一个单独的编码:Howto write PC relative addressing on arm asm?

ADRP

ADRP 类似于 ADR,但它:

  • 相对于当前页面而不是字节移动页面(4KiB,ADRP 中的 P 代表页面)
  • 清零低 12 位

例如,如果将以下 ADRP 指令放置在内存中的位置 0x4050:

adrp x0, #0x1000

然后在执行此指令后,x0现在包含值 0x5000(+ 0x1000 并将前 12 位清零)。

但是请注意,上述语法仅具有教育意义,因为 GNU GAS 似乎不接受文字整数常量作为参数,只接受符号。(或者它将 0x1000 视为符号并且链接失败,沿着这些思路,现在没有时间完全理解它 TODO)。

由于低 12 位被清零,为了计算完整地址,ADRP 通常与 ADD +:lo12:重定位一起使用,如下所示:

adrp x0, myvariable
add x0, x0, :lo12:myvariable

在带有可运行断言的 GitHub 上

请注意,:lo12:只是将 的低 12 位提取myvariable到立即数,链接器生成的最终指令只是一个add x0, x0, #<immediate>,另请参见:AArch64 重定位前缀链接器做什么?.

ADRP 优于 ADR 的优势在于我们可以跳得更远(+-4GiB),代价是需要在 ADRP 之后进行额外的 ADD 以设置低 12 位。ARMv8 手册说:

ADR 指令将带符号的 21 位立即数加到获取该指令的程序计数器的值上,然后将结果写入通用寄存器。这允许计算当前 PC 的 ±1MB 范围内的任何字节地址。

ADRP 指令将带符号的 21 位立即数左移 12 位,将其与程序计数器的值相加,低 12 位清零,然后将结果写入通用寄存器。这允许在 4KB 对齐的内存区域计算地址。结合 ADD(立即数)指令或具有 12 位立即数偏移量的加载/存储指令,这允许计算或访问当前 PC ±4GB 范围内的任何地址。

ADRP 的另一个限制是,与 ADR 不同的是,如果您将代码加载到内存中相对于原始链接器偏移量(例如,由于 ASLR)未偏移 4K 倍数的位置,它会中断。例如,如果您稍微上移,目标地址可能会落在下一页,而 PC 位置会停留在旧地址上,从而使 ADRP 指向错误的页面。但是,依赖ADRP的可执行文件仍然被认为是PIE,而动态链接器/ASLR等系统在内存中只能重定位4K的倍数,相关:Linux中PIE可执行文件的文本部分的地址是如何确定的?

ADRP 仅存在于 ARMv8 中,不存在于 ARMv7 中。

ARMv8 DDI 0487C.a手册说Page只是4KB的助记词,并不反映实际的页面大小,可以配置成其他大小。C3.3.5“PC相对地址计算”:

ADRP 描述中使用的术语 page 是 4KB 内存区域的简写,与虚拟内存转换粒度大小无关。

ADRL

ADRL 不是实际指令,只是“伪指令”,即发出真实指令的汇编快捷方式。

因此,在 v7 手册中没有提到它,并且在 v8 手册中的“阅读 PC 的说明”中只提到了一次,但我在手册中找不到任何解释它的地方,所以也许它只是文档错误?

因此,我将专注于 GNU AS 实现,该实现在 ARM 特定功能下的https://sourceware.org/binutils/docs-2.31/as/ARM-Opcodes.html#ARM-Opcodes中记录了它:

adrl <register> <label>

该指令将标签的地址加载到指定的寄存器中。根据标签所在的位置,该指令将评估为一个或两个 PC 相关的 ADD 或 SUB 指令。如果不需要第二条指令,则会在其位置生成一条 NOP 指令,因此该指令始终为 8 字节长。

因此它似乎能够扩展到多个 ADD/SUB,大概是为了允许从 PC 进行更大的跳跃。

Objdump 确认了 GNU 手册对短地址的说明:

    adr r0, label
   10478:       e28f0008        add     r0, pc, #8

    adrl r2, label
   10480:       e28f2000        add     r2, pc, #0
   10484:       e1a00000        nop                     ; (mov r0, r0)

TODO:长地址的例子。最大长度是多少?只是 ADD/ADR 的 2 倍?

尝试在 aarch64 上使用它失败了,因为根据 GNU GAS 手册,它是 ARMv7 特定的功能。GNU GAS 2.29.1 上的错误消息是:

Error: unknown mnemonic `adrl' -- `adrl r6,.Llabel' 

Linux 内核还定义了一个宏adr_l,称为https://patchwork.kernel.org/patch/9883301/ TODO 了解基本原理。

备择方案

当 PC 偏移量太长而无法编码到指令中时,一种主要替代方法是使用 movk / movw / movt,请参阅:在 ARMv6 程序集中 =label(等号)和 [label](括号)有什么区别?

于 2019-01-04T16:39:19.203 回答