Armv8 - ADRP指令详解

ADRP指令

  • 作用
    • 将当前指令所在页的基地址加/减去字节差,并写入目标寄存器
      • 字节差:与目标地址页基地址的间隔字节数,其为PAGE_SIZE的整数倍
      • 此时的字节差就是指令所操作的立即数
    • 该指令通常配合add指令来向目标寄存器写入完整的地址
    • 最后通过br、blr实现长跳转

ADRP指令字节码

  • 案例
    • 0x90 0xFF 0xFF 0xE1
  • 对应二进制
    • 1001 0000 1111 1111 1111 1111 1110 0001

字节码按位拆分

  • 对照表
   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遍历并查找需要的符号
  • 至此打住... 不然话题又扯远了@_@...

posted on 2024-08-04 20:26  书生执笔画浮沉  阅读(79)  评论(0编辑  收藏  举报

导航