ARM汇编中ADRP和ADRL指令的语义是什么? [英] What are the semantics of ADRP and ADRL instructions in ARM assembly?

查看:123
本文介绍了ARM汇编中ADRP和ADRL指令的语义是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

ADRP

<块引用>

相对于 PC 偏移的 4KB 页地址.

ADRL

<块引用>

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

具体来说,

<块引用>

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

ADRPADRL 指令有什么作用?更重要的是,ADRPADD 是如何构造 PC 相对地址的?

解决方案

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 over MOV(反之亦然)?

  • 它允许位置无关代码,这是避免共享库在内存中发生冲突的基础,而且对于主文本段启用 ASLR,另见:gcc 和 ld 中位置无关的可执行文件的 -fPIE 选项是什么?

  • 生成的代码更小

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 在那里实际上很重要并且具有单独的编码:如何在 arm asm 上编写 PC 相对地址?

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添加 x0, x0, :lo12:myvariable

在带有可运行断言的 GitHub 上.

注意:lo12:只是将myvariable的低12位提取为一个立即数,链接器产生的最终指令只是一个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.5PC相对地址计算":

<块引用>

ADRP 描述中使用的术语 page 是 4KB 内存区域的简写,与虚拟内存区域无关记忆翻译粒度.

ADRL

ADRL 不是实际指令,只是伪指令",即发出实际指令的汇编快捷方式.

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

因此,我将重点关注 GNU AS 实现,该实现在 https://sourceware.org/binutils/docs-2.31/as/ARM-Opcodes.html#ARM-Opcodes 下 ARM 特定功能:

<块引用>

adrl <注册><标签>

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

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

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

 adr r0,标签10478: e28f0008 添加 r0, pc, #8adrl r2,标签10480: e28f2000 添加 r2, pc, #010484: e1a00000 nop ;(移动 r0, r0)

TODO:长地址示例.最大长度是多少?仅是 ADD/ADR 的 2 倍?

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

错误:未知助记符`adrl' -- `adrl r6,.Llabel'

Linux 内核还在 https://patchwork.kernel.org/patch/9883301/ TODO 了解基本原理.

替代方案

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

ADRP

Address of 4KB page at a PC-relative offset.

ADRL

Load a PC-relative address into a register. It is similar to the ADR instruction. ADRL can load a wider range of addresses than ADR because it generates two data processing instructions.

Specifically,

ADRL assembles to two instructions, an ADRP followed by ADD. If the assembler cannot construct the address in two instructions, it generates a relocation. The linker then generates the correct offsets. ADRL produces position-independent code, because the address is calculated relative to PC.

What do ADRP and ADRL instructions do? More importantly, how does and ADRP followed by an ADD construct a PC-relative address?

解决方案

ADR

ADR is a simple PC-relative address calculation: you give it an immediate offset, and it stores in the register the address relative to the current PC.

For example, if the following ADR instruction is placed at position 0x4000 in memory:

adr x0, #1

then after this instruction is executed x0 now contains the value 0x4001. On GitHub with runnable assertion.

We could instead of that just try to do:

mov x0, #0x4001

but PC-relative addressing has the following advantages:

  • all ARMv7 / ARMv8 instructions are 4 bytes long. This in in large contrast to x86 where instruction widths are variable.

    This simplifies a lot of things, but it has one unfortunate implication: you cannot encode full addresses (4 / 8 bytes) in a single instruction, since we need some bits to encode the instruction itself.

    Even though we cannot store full addresses, we can refer to some of them (those that fit into the encoding) by the relative address to the PC, which is often enough for many applications, since we are often only jumping to nearby code locations.

    The rationale here is analogous to that of the existence of the ldr = pseudo instruction: Why use LDR over MOV (or vice versa) in ARM assembly?

  • it allows for position independent code, which is fundamental to avoid shared libraries from clashing in memory, and but is also useful for the main text segment to enable ASLR, see also: What is the -fPIE option for position-independent executables in gcc and ld?

  • the generated code is smaller

The ADR instruction uses a 21-bit immediate for the offset, which allows for +-1MiB jumps (20-bits + 1 for the sign).

In ARmv7/aarch32, ADR can sometimes be achieved with ADD and SUB with PC as documented on the ARMv7 DDI 0406C.d manual D9.4 "Explicit use of the PC in ARM instructions":

Some forms of the ADR instruction can be expressed as forms of ADD or SUB, with the PC as Rn. Those forms of ADD and SUB are permitted, and not deprecated.

TODO when can it not be achieved with ADD? GNU GAS suggests that ADR is just a pseudo-op that always assembles into ADD or SUB: https://sourceware.org/binutils/docs-2.31/as/ARM-Opcodes.html#ARM-Opcodes

This instruction will load the address of label into the indicated register. The instruction will evaluate to a PC relative ADD or SUB instruction depending upon where the label is located. If the label is out of range, or if it is not defined in the same file (and section) as the ADR instruction, then an error will be generated. This instruction will not make use of the literal pool.

In ARMv8 aarch64 however, the PC cannot be used in every instruction like a general purpose register, therefore ADR is actually important there and has a separate encoding: Howto write PC relative adressing on arm asm?

ADRP

ADRP is similar to ADR, but it:

  • shifts pages (4KiB, P in ADRP stands for Page) relative to the current pages instead of just bytes
  • zeroes out the 12 lower bits

For example, if the following ADRP instruction is placed at position 0x4050 in memory:

adrp x0, #0x1000

then after this instruction is executed x0 now contains the value 0x5000 (+ 0x1000 and zero out the first 12 bits).

Note however that the above syntax is only educational, as GNU GAS does not appear to accept literal integer constants as arguments, only symbols. (or it treats 0x1000 as a symbol and link fails, something along those lines, no time to understand it fully now TODO).

Since the lower 12 bits are zeroed out, to calculate a full address, ADRP is normally used together with an ADD + :lo12: relocation as in:

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

On GitHub with runnable assertion.

Note that the :lo12: just extracts the lower 12 bits of myvariable to an immediate, the final instruction produced by the linker is just an add x0, x0, #<immediate>, see also: AArch64 relocation prefixes and What do linkers do?.

The advantage of ADRP over ADR is that we can jump much further (+-4GiB), at the cost of needing to do an extra ADD after ADRP to set the lower 12-bits. The ARMv8 manual says:

The ADR instruction adds a signed, 21-bit immediate to the value of the program counter that fetched this instruction, and then writes the result to a general-purpose register. This permits the calculation of any byte address within ±1MB of the current PC.

The ADRP instruction shifts a signed, 21-bit immediate left by 12 bits, adds it to the value of the program counter with the bottom 12 bits cleared to zero, and then writes the result to a general-purpose register. This permits the calculation of the address at a 4KB aligned memory region. In conjunction with an ADD (immediate) instruction, or a Load/Store instruction with a 12-bit immediate offset, this allows for the calculation of, or access to, any address within ±4GB of the current PC.

Another limitation of ADRP is that unlike ADR, it would break if you were to load the code to memory at a position that is not offset by a multiple of a 4K relative to the original linker offset (e.g. due to ASLR) . For example, if you shift a little bit up, the target address could fall on the next page, while the PC location stays on the old one, making ADRP point to the wrong page. However, executables that rely on ADRP are still considered PIE, and systems such as the dynamic linker/ASLR can only relocate in memory by multiples of 4K, related: How is the address of the text section of a PIE executable determined in Linux?

ADRP only exists in ARMv8, not in ARMv7.

The ARMv8 DDI 0487C.a manual says that Page is just a mnemonic for 4KB, and does not reflect the actual page size, which is configurable to other sizes. C3.3.5 "PC-relative address calculation":

The term page used in the ADRP description is short-hand for the 4KB memory region, and is not related to the virtual memory translation granule size.

ADRL

ADRL is is not an actual instruction, just a "pseudo-instruction", i.e. an assembler shortcut that emits real instructions.

As such, it is not mentioned in the v7 manual, and there is just one mention on the v8 manual at "Instructions that read the PC", but I can't find anywhere in the manual that explains it, so maybe it is just a documentation mistake?

I will therefore focus on the GNU AS implementation which documents it at https://sourceware.org/binutils/docs-2.31/as/ARM-Opcodes.html#ARM-Opcodes under ARM specific features:

adrl <register> <label>

This instruction will load the address of label into the indicated register. The instruction will evaluate to one or two PC relative ADD or SUB instructions depending upon where the label is located. If a second instruction is not needed a NOP instruction will be generated in its place, so that this instruction is always 8 bytes long.

Therefore it appears to be able to expand to multiple ADD/SUB, presumably to allow for a larger jump from the PC.

Objdump confirms what the GNU manual says for short addresses:

    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: example of long addresses. What is the maximum length? Just 2x that of ADD/ADR?

Trying to use it on aarch64 fails, since it is an ARMv7 specific features according to the GNU GAS manual. The error message on GNU GAS is 2.29.1 is:

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

The Linux kernel has also defined a macro called adr_l at https://patchwork.kernel.org/patch/9883301/ TODO understand rationale.

Alternatives

One main alternative for when the PC offset is too long to encode into the instruction either, is to use movk / movw / movt, see: What is the difference between =label (equals sign) and [label] (brackets) in ARMv6 assembly?

这篇关于ARM汇编中ADRP和ADRL指令的语义是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆