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

2024-01-11

ADRP http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0802a/ADRP.html

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

ADRL http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0802b/CIHDDDEI.html

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

具体来说,

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

What do ADRP and ADRL指令吗?更重要的是,如何以及ADRP随后是一个ADD构造一个PC相对地址?


ADR

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

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

adr x0, #1

然后执行该指令后x0现在包含值 0x4001。在 GitHub 上,带有可运行断言 https://github.com/cirosantilli/linux-kernel-module-cheat/blob/059a7ef9d9c378a6d1d327ae97d90b78183680b2/userland/arch/aarch64/adr.S#L19.

我们可以尝试这样做:

mov x0, #0x4001

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

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

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

    尽管我们无法存储完整的地址,但我们可以通过 PC 的相对地址来引用其中的一些地址(适合编码的地址),这对于许多应用程序来说通常就足够了,因为我们通常只跳转到附近的代码位置。

    这里的基本原理类似于ldr =伪指令:为什么在 ARM 汇编中使用 LDR 而不是 MOV(反之亦然)? https://stackoverflow.com/questions/14046686/why-use-ldr-over-mov-or-vice-versa-in-arm-assembly

  • 它允许位置独立的代码,这对于避免共享库在内存中发生冲突至关重要,而且对于主文本段也很有用,以启用ASLR https://en.wikipedia.org/wiki/Address_space_layout_randomization, 也可以看看:gcc 和 ld 中与位置无关的可执行文件的 -fPIE 选项是什么? https://stackoverflow.com/questions/2463150/what-is-the-fpie-option-for-position-independent-executables-in-gcc-and-ld/51308031#51308031

  • 生成的代码更小

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

在 ARmv7/aarch32 中,ADR 有时可以通过 PC 上的 ADD 和 SUB 来实现,如ARMv7 DDI 0406C.d 手册 https://static.docs.arm.com/ddi0406/cd/DDI0406C_d_armv7ar_arm.pdfD9.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 https://sourceware.org/binutils/docs-2.31/as/ARM-Opcodes.html#ARM-Opcodes

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

然而,在 ARMv8 aarch64 中,PC 不能像通用寄存器一样在每条指令中使用,因此 ADR 实际上在那里很重要,并且具有单独的编码:如何在arm asm上编写PC相对寻址? https://stackoverflow.com/questions/28638981/howto-write-pc-relative-adressing-on-arm-asm/54480999#54480999

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 上,带有可运行断言 https://github.com/cirosantilli/arm-assembly-cheat/blob/https://github.com/cirosantilli/linux-kernel-module-cheat/blob/059a7ef9d9c378a6d1d327ae97d90b78183680b2/userland/arch/aarch64/adrp.S#L7/v8/adrp.S.

请注意,:lo12:只提取低12位myvariable立即而言,链接器生成的最终指令只是一条add x0, x0, #<immediate>, 也可以看看:AArch64 重定位前缀 https://stackoverflow.com/questions/38570495/aarch64-relocation-prefixes/38608738#38608738 and 链接器有什么作用? https://stackoverflow.com/questions/3322911/what-do-linkers-do/33690144#33690144.

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

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

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

ADRP 的另一个限制是,与 ADR 不同,如果您将代码加载到内存中相对于原始链接器偏移量未偏移 4K 倍数的位置(例如由于 ASLR),它就会中断。例如,如果向上移动一点,目标地址可能会落在下一页上,而 PC 位置仍保留在旧页面上,从而使 ADRP 指向错误的页面。然而,依赖ADRP的可执行文件仍然被认为是PIE,并且诸如动态链接器/ASLR之类的系统只能在内存中重定位4K的倍数,相关:Linux 中 PIE 可执行文件的文本部分的地址是如何确定的? https://stackoverflow.com/questions/51343596/how-is-the-address-of-the-text-section-of-a-pie-executable-determined-in-linux

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

The ARMv8 DDI 0487C.a 手册 https://static.docs.arm.com/ddi0487/ca/DDI0487C_a_armv8_arm.pdf表示Page只是4KB的助记符,并不反映实际的页面大小,可以配置为其他大小。 C3.3.5“PC相对地址计算”:

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

ADRL

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

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

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

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 at https://patchwork.kernel.org/patch/9883301/ https://patchwork.kernel.org/patch/9883301/TODO 理解原理。

备择方案

当 PC 偏移量太长而无法编码到指令中时,一种主要的替代方法是使用 movk / movw / movt,请参阅:ARMv6 汇编中=label(等号)和[label](括号)有什么区别? https://stackoverflow.com/questions/17214962/what-is-the-difference-between-label-equals-sign-and-label-brackets-in-ar/54043398#54043398

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • 如何关闭MIPS-GCC自动指令重排序?

    继这个问题之后 使用跳转 和链接 指令的奇怪 MIPS 汇编器行为 https stackoverflow com questions 3807480 weird mips assembler behavior with jump and
  • 不知道如何一次打印整个日历[关闭]

    Closed 这个问题需要细节或清晰度 help closed questions 目前不接受答案 cseg segment assume cs cseg ds cseg org 100H begin mov es cs video mov
  • 用于读/写 XMM 和 YMM 寄存器的内联汇编代码?

    我有 2 个变量来模拟 X86 XMM 和 YMM 如下所示 uint64 t xmm value 2 uint64 t ymm value 4 现在我想使用内联汇编来读取和写入 XMM YMM 寄存器 如何编写GCC内联汇编来复制xmm
  • 什么是微编码指令?

    我看过很多参考微编码指令的文献 这些是什么以及为什么使用它们 CPU 读取机器代码并将其解码为内部控制信号 将正确的数据发送到正确的执行单元 大多数指令映射到一个内部操作 并且可以直接解码 例如 在 x86 上 add eax edx只是将
  • 内联汇编 - cdecl 和准备堆栈

    我最近一直在尝试通过使用缓冲区和不同汇编运算符的原始十六进制等效项来实现 C 中的动态函数 为了说明一个简单的跳转 byte buffer new buffer 5 buffer 0xE9 Hex for jump uint buffer
  • 我试图在 AAM 指令之后使用 AX 中存储的值将其除以 2,为什么它不适用于 2 位数字输出?

    英语不是我的母语 请原谅输入错误 我将在此处显示的代码是一项作业 我真的需要了解发生了什么事 我在 DosBox 0 74 和 TASM 汇编器中使用 Intel 8086 语法 当我必须除以 2 时 代码的问题在于三角形的面积 注意 程序
  • 汇编语言中的全局_start是什么?

    这是我的汇编级代码 section text global start start mov eax 4 mov ebx 1 mov ecx mesg mov edx size int 0x80 exit mov eax 1 int 0x80
  • MASM 字符串反转

    好吧 我正在讨论这个问题 可能是一个非常复杂的解决方案 但这是我脑海中浮现的第一件事 我需要编写一个汇编语言程序来反转 源 字符串 而不使用 目标 字符串 临时变量 这是我的尝试 INCLUDE Irvine32 inc data sour
  • 为什么 i2c_smbus 函数不可用? (I2C——嵌入式Linux)

    有很多参考使用i2c smbus 开发嵌入式 Linux 软件时在 I2C 总线上进行通信的函数 什么时候i2c smbus函数如i2c smbus read word data在软件项目中引用了 ARM8 处理器错误 例如 i2c smb
  • 使用 gdb 调试反汇编库

    在Linux和Mac OS X中可以使用strapi和next来调试应用程序而无需调试信息 在 Mac OS X 上 gdb 显示在库内部调用的函数 尽管有时会在每个 stepi 指令中推进多个汇编程序指令 在 Linux 上 当我进入动态
  • 为什么这个 C++ 包装类没有被内联掉?

    EDIT 我的构建系统出了问题 我还在弄清楚到底是什么 但是gcc产生了奇怪的结果 尽管它是 cpp文件 但是一旦我使用了g 然后它按预期工作 对于我一直遇到麻烦的事情来说 这是一个非常精简的测试用例 其中使用数字包装类 我认为会内联 使我
  • 编写 AMD64 SysV 程序集时使用哪些寄存器作为临时寄存器?

    我正在使用实现一个功能cpuid根据 AMD64 SysV ABI 进行组装 我需要在函数本身中使用 2 个临时寄存器 第一个用于累积返回值 第二个用作计数器 我的功能目前如下所示 zero argument function some c
  • 使用 Android NDK 使用 -fsigned-char 进行构建安全吗?

    为了与其他平台保持一致 我需要使用signed char在我正在处理的一些本机代码中 但默认情况下在Android NDK上char类型是unsigned 我尝试明确使用signed char类型 但它生成太多警告differ in sig
  • x86-64 上这个语句有什么问题?

    该函数的目的是获取堆栈的起始地址 unsigned long find start void asm movq rsp eax 当我编译它时 出现错误 Error suffix or operands invalid for movq mo
  • 遍历内存编辑每个字节

    我正在编写汇编代码 提示用户输入一串小写字符 然后输出包含所有大写字符的相同字符串 我的想法是迭代从特定地址开始的字节 并从每个字节中减去 20H 将小写变为大写 直到到达具有特定值的字节 我对 Assembly 相当缺乏经验 所以我不确定
  • 了解 ctags 文件格式

    我使用 Exhuberant ctags 来索引我的 c 项目中的所有标签 c project 是 Cortex M7 微控制器的嵌入式软件 结果是一个标签文件 我正在尝试阅读该文件并理解所写的内容 根据我找到的 ctags 和 Exhub
  • 为什么不能执行 mov [eax], [ebx] [重复]

    这个问题在这里已经有答案了 我可以做这个 mov eax ebx 和这个 mov eax ebx 甚至这个 mov eax ebx 但不是这个 错误C2415 mov eax ebx 只是wtf 为什么 它与 ptr1 ptr2 相同 为什
  • 为什么 Solaris 汇编器生成的机器代码与 GNU 汇编器在这里不同?

    我为 amd64 编写了这个小汇编文件 对于这个问题来说 代码的作用并不重要 globl fib fib mov edi ecx xor eax eax jrcxz 1f lea 1 rax ebx 0 add rbx rax xchg r
  • NASM 轮班操作员

    您将如何在寄存器上进行 NASM 中的位移位 我读了手册 它似乎只提到了这些操作员 gt gt lt lt 当我尝试使用它们时 NASM 抱怨移位运算符处理标量值 您能解释什么是标量值并举例说明如何使用 gt gt and lt lt 另外
  • Visual Studio 2017 上的简单装配程序

    386 model flat c stack 100h printf PROTO arg1 Ptr Byte data msg1 byte Hello World 0Ah 0 code main proc INVOKE printf ADD

随机推荐