# ARMv8 - ADRP指令详解

**URL:** <https://sysec.cc/t/topic/30>\
**Category:** 嵌入式\
**Created:** [2026 年8 月 16 日 12:06 UTC](https://sysec.cc/t/topic/30 "2026-08-16T12:06:37Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

作者： ![EBK](https://sysec.cc/user_avatar/sysec.cc/ebk/32/7_2.png) [@EBK](https://sysec.cc/u/EBK)\
发布日期： [2026 年8 月 16 日 12:06 UTC](https://sysec.cc/t/topic/30/1 "2026-08-16T12:06:37Z")

</div>

# ADRP指令

- 作用
  - 将当前指令所在页的基地址加/减去字节差，并写入目标寄存器
    - 字节差：与目标地址页基地址的间隔字节数，其为PAGE\_SIZE的整数倍，而PAGE\_SIZE通常为4KB
    - 此时的字节差就是指令所操作的立即数

  - 该指令通常配合add指令来向目标寄存器写入完整的地址
  - 最后通过br、blr实现长跳转

* * *

# ADRP指令字节码

- 案例
  - `0x90 0xFF 0xFF 0xE1`

- 对应二进制
  - `1001 0000 1111 1111 1111 1111 1110 0001`

* * *

# 字节码按位拆分

- 对照表

```auto
   1 00 10000 1111 1111 1111 1111 111 00001
  |31| 30-29 | 28-24 | 23-5 | 4-0 |
  |sf| immlo | | immhi | rd |

```

- 寄存器位 4-0
  - `0 0001` → 表示x1寄存器
  - 如果是 `0 0000` → 则表示x0寄存器
  - 以此类推
    - `0 0010` → x2
    - `0 0011` → x3
    - `. ....` → x…

- 立即数位（高19位） 23-5
  - `1111 1111 1111 1111 111`

- adr指令位 28-24
  - `10000`
  - 至于为什么是adr指令位而不是adrp指令位，是因为我发现两者是相同的
  - adr与adrp的差异在于第31位是否为1

- 立即数位（低2位） 30-29
  - `00`

- 64位操作位 31
  - `1`
  - 当该位为1时表示adrp指令，当为0时表示adr指令

* * *

# 指令操作数的推导过程

- 在ARMv8指令手册中是这么写的
  - `SignExtend(immhi:immlo:Zeros(12), 64);`

- 字面理解就是
  - `有符号扩展（操作数高位：操作数低位：12个0，总长64位）`

- 根据上述案例配合表达式进行计算
  - 拼接结果
    - = `1111 1111 1111 1111 111 00 0000 0000 0000`
    - = `0x1 FFFF C000`

  - 符号扩展步骤及结果
    - = `0x1 FFFF C000 << 31 >> 31`
    - = `0xFFFF FFFF FFFF C000`
    - = `-16384`

  - 也就是说，要在当前指令所在页基地址基础上减去16384字节
  - -16384/PAGE\_SIZE = -4， 也就是4页的大小

* * *

# 指令的执行

- 经过对案例的推导，得出操作数为-16384
- 那么该指令执行时就会进行以下步骤
  - 首先会获取当前指令所在地址
    - 注意其与`b`、`bl`等指令一致，获取的均是当前指令字节码的起始地址，非下条指令也非本指令末尾处

  - 将当前指令所在地址`&=~0xFFF`，以抹去低12位，得到指令所在页基地址
  - 将获取到的基地址减去16384，得到目标页基地址
  - 将目标页基地址写入指定的寄存器中，本案例为x1寄存器

* * *

# 答疑解惑

- 看到此处，或许有些人会疑惑
  - `adrp`的操作数是从哪来的？我明明在asm嵌入的是一个符号（函数、变量）啊？

- 对此就不得不提到`ld`了
  - 源码编译过`gcc`以及熟悉编译过程的朋友肯定知道这个工具
  - 其负责的是整个编译环节中的链接过程，比如常见的`symbol ******* is not define/found`编译错误就是它抛出来的
  - `adrp`的操作数应该是多少，在链接环节才会被确定，因为你利用asm嵌入的符号，在链接阶段才能确认其在字节码中的相对位置
  - 所以`adrp`的操作数是`ld`链接器负责计算并将偏移量结果静态写入的
  - 看到这里可能有人又要问：为什么要写入一个相对偏移量，既然已经知道了符号地址，直接将地址写入不就好了，写偏移地址岂不是脱裤子放屁
    - 这个问题就得从二进制文件的编译讲起了
    - 当你反汇编一个二进制文件时，会毫不意外的发现他们的地址都是0x0开始（非入口点地址）
    - 但是0x0地址往往都是系统内核在使用，那么每个程序运行时都真的在内存中的0x0作为起始地址吗？
    - 非也非也，不然每个程序一运行起来，不就把内核的生存空间抢走了吗，内核都嗝屁了程序还运行个毛啊
    - 所以内核会为每个应用分配一个虚拟地址空间，也就是说 程序运行时的地址 = 程序静态地址 + 虚拟基地址
    - 故而链接器`ld`，它才会使用字节偏移量而不是静态地址，因为它怎么可能知道后续程序在运行时分配的虚拟基地址是什么

- 注意：此(`gcc-ld`/`ld`)链接器非彼(`ld-linux`)链接器
  - 前者是编译时的链接，在编译时查找有无缺失的符号，完善位置相关的代码
  - 后者是运行时的链接，在run-time遍历并查找需要的符号

- 至此打住… 不然话题又扯远了@\_@…
