ARM汇编中ADRP和ADRL指令的语义是什么?

时间:2017-01-28 05:35:46

标签: assembly arm opcode

ADRP

  

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

ADRL

  

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

具体地,

  

ADRL汇编为两条指令,ADRP后跟ADD。如果   汇编程序无法在两条指令中构造地址   生成搬迁。然后链接器生成正确的偏移量。   ADRL生成与位置无关的代码,因为地址是   相对于PC计算。

ADRPADRL说明有何作用?更重要的是,ADRP后跟ADD如何构建与PC相关的地址?

1 个答案:

答案 0 :(得分:5)

ADR

根本问题是所有ARMv7 / ARMv8指令的长度均为4个字节。

这简化了很多事情,但是有一个不幸的含义:由于我们需要一些位来对指令本身进行编码,因此您无法在单个指令中对完整地址(4/8字节)进行编码。

这是ADR指令进入的地方。即使我们无法存储完整的地址,我们也可以通过PC的相对地址来引用其中的一些地址(适合编码的地址),这通常足以满足许多应用程序的需要。

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

这里的原理类似于ldr =伪指令:Why use LDR over MOV (or vice versa) in ARM assembly?

有时ARMv7 DDI 0406C.d manual D9.4“ ARM指令中PC的明确使用”中所述,使用PC的ADD和SUB可以实现ADR:

  

某些形式的ADR指令可以表示为ADD或SUB形式,而PC为Rn。允许使用那些形式的ADD和SUB,并且   不推荐使用。

何时使用ADD无法做到? GNU GAS建议ADR只是伪运算,它总是组合为ADD或SUB:https://sourceware.org/binutils/docs-2.31/as/ARM-Opcodes.html#ARM-Opcodes

  

该指令将标签的地址加载到指定的寄存器中。该指令将根据标签所在的位置求值为PC相对ADD或SUB指令。如果标签超出范围,或者未在与ADR指令相同的文件(和部分)中定义标签,则将生成错误。该指令将不使用文字池。

请注意,在ARMv8上,不能像通用寄存器那样在所有指令中使用PC,因此ADR实际上在那里很重要,并且具有单独的编码:Howto write PC relative adressing on arm asm?

代码示例:

    adr r0, label
    ldr r1, =label
label:
    /* r0 == r1 */

On GitHub with runnable assertion

ADRP

ADRP与ADR类似,但它会将12位低位清零,并相对于当前PC页面而不是字节相对于页面进行移位。

这样,我们可以跳得更远(+ -4GiB),其代价是需要在ADRP之后设置额外的ADD来设置较低的12位。 ARMv8手册说:

  

ADR指令将一个带符号的21位立即数添加到获取该指令的程序计数器的值,然后将结果写入通用寄存器。这样就可以计算当前PC的±1MB之内的任何字节地址。

     

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

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

ARMv8 DDI 0487C.a manual表示Page只是4KB的助记符,并不反映实际的页面大小,可以将其配置为其他大小。 C3.3.5“相对于PC的地址计算”:

  

ADRP描述中使用的术语页面是4KB内存区域的简写,与虚拟内存无关。   记忆翻译颗粒大小。

代码示例:

    adrp x0, label
    adr x1, label
    /* Clear the lower 12 bits. */
    bic x1, x1, #0xFF
    bic x1, x1, #0xF00
    /* x0 == x1 */
label:

On GitHub with runnable assertion

ADRL

ADRL不是实际的指令,只是伪指令。

因此,在v7手册中没有提到它,而在v8手册中“读取PC的指令”中只提到了一个,但是我在解释它的手册中找不到任何地方,所以也许只是文档错误?

因此,我将重点介绍GNU AS实现,该实现在https://sourceware.org/binutils/docs-2.31/as/ARM-Opcodes.html#ARM-Opcodes的ARM特定功能下进行了记录:

adrl <register> <label>
     

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

因此,似乎可以达到与ADR相同的结果,但是使用不同的指令? 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:ADRL与ADR相比有什么优势?

尝试使用它在aarch64上失败,因为根据GNU GAS手册,它是ARMv7的特定功能。

替代

当PC偏移量太长而无法编码为指令时,一种主要替代方法是使用movk / movw / movt,请参见:What is the difference between =label (equals sign) and [label] (brackets) in ARMv6 assembly?