jason--liu/Blog

aarch64体系结构与编程1--常用汇编指令

Opened this issue · 0 comments

加载指令

ldr指令寻址之1:地址偏移模式(unsigned offset)

地址偏移模式常常使用寄存器的值来表示一个地址,或者基于寄存器的值做一些偏移来计算出内存地址,并且把这个内存地址的值加载到通用寄存器中。偏移量可以是正数,也可以是负数。
LDR Xd, [Xn, $offset]
首先在Xn寄存器的内容上加一个offset偏移量后作为内存地址,加载此
地址的内容到Xd寄存器。

ldr指令寻址之2:变基模式

变基模式主要有两种。

  • 前变基模式(pre-index模式),先更新偏移地址然后再访问内存
  • 后变基模式(post-index模式),先访问内存地址然后再更新偏移地址
    LDR X0, [X1, #8]! //前变基模式。先更新X1的值为X1+8,然后以新的X1值为地址,加载内存的值到X0
    LDR X0, [X1], #8 //后变基模式。以X1的值为地址,加载该内存地址的值到X0,然后再更新X1寄存器为X1+8

ldr指令寻址之3:标签(literal)

ldr指令有一个加载标签的模式. 读取 (PC值 + label offset) 这个地址的值

ldr伪指令

  • 指令:每一条指令都对应一种CPU操作。
  • 伪指令:对编译器发出的命令,它是在对源程序汇编期间由汇编程序处理的操作,它们可以完成如处理器选择、定义程序模式、定义数据、分配存储区、指示程序结束等功能,总之,可以分解为几条指令的集合。
  • ldr指令既可以是大范围的地址读取伪指令,也可以内存访问指令。当它的第二个参数前面有“=”时,表示伪指令,否则表示内存访问指令。
    ldr x6, MY_LABEL //内存访问指令
    ldr x7, =MY_LABEL //ldr伪指令
    dr伪指令没有立即数范围的限制

加载指令4 mov指令

mov指令加载
image
image

  • 16为立即数
  • 或者16位立即数左移16, 32, 48位

加载指令5 多字节的加载和存储指令ldp和stp指令

  • 在A32指令集中提供LDM和STM指令来实现多字节内存加载和存储,到了A64指令集,不再提供LDM和STM指令,而是采用LDP和STP指令
  • ldp和stp可以一条指令加载和存储16个字节,效率提高一倍
LDP X3, X7, [X0] //以X0的值为地址,加载此地址的值到X3寄存器,以X0+8为地址,
加载此地址的值到X7寄存器
STP X1, X2, [X4] //存储X1的值到地址为X4的内存中,然后存储X2的值到地址为X4+8
的内存中。

存储指令之变种

根据数据大小,例如1个字节,2个字节,有符号数等ldr和str指令有如下变种

指令 说明
LDR 数据加载指令
LDRSW 有符号的数据加载指令,大小为字(word)
LDRB 数据加载指令,大小为字节
LDRSB 有符号的加载指令,大小为字节
LDRH 数据加载指令,大小为半字(HalfWord)
LDRSH 有符号的数据加载指令,大小为半字(HalfWord)
STRB 数据存储指令, 大小为字节
STRH 数据存储指令, 大小为半字

注意:访问和存储4个字节和8个字节都是用ldr和str,只不过目标寄存器使用wn或者xn。

算术和移位指令

ADD加法指令

➢ 普通的加法指令add
➢ adds指令 – 影响条件标志位(进位)
✓ 1. 使用寄存器的加法
✓ 2. 使用立即数的加法
✓ 3. 使用移位操作的加法
image
主要影响C标志位

sub减法指令

  • 普通的减法指令sub
  • subs指令 - 影响条件标志位(C标志位)
    带进位的加法指令
    adc指令
    image
    Rd = Rn + Rm + C where R is either w or x.
    带进位的减法指令
    sbc指令
    image
    Rd = Rn - Rm - 1 + C
    cmp指令
    比较两个数的大小,内部使用subs指令来实现,影响C标志位。
    等同于:
SUBS XZR, <Xn>, # <imm>

1.subs指令,s表示计算结果会影响 NCVZ标志位
2.subs xd,x1,x2的话,计算公式 xd= x1 + NOT(x2) +1
3.把xd,写成xzr,目的就是不要 xd的结果,只要 NCZV的 结果
image

cmp x1, x2
当x1 >= x2时, C=1
当x1 < x2 时, C=0

移位操作

  • lsl:逻辑左移指令
  • lsr:逻辑右移指令
  • asr:算术右移
  • ror:循环右移
    注意:
  1. 逻辑左移 = 算术左移, arm64没有单独设置一个算术左移的指令, 最高位会被抛弃
  2. 逻辑右移和算术右移需要考虑 符号问题。

例如: 1010101010, 右移的话:
逻辑右移一位:[0]101010101 (最高一位,永远补0)
算数右移一位:[1]101010101 (算数右移,左边添加的数和符号有关)

按位与操作

  • and:与 操作
  • ands:带条件标志位的与操作,影响Z标志位
    按位与操作
    AND Xd, Xn
    指令结果: Xd = Xd & Xn

按位或/异或操作

  • orr: 或操作
  • eor: 异或操作
    按位或操作
    ORR Xd, Xn
    指令结果: Xd = Xd | Xn
    按位异或操作
    EOR Xd, Xn
    指令结果: Xd = Xd ^ Xn
    异或的特点:
  • 0^0=0,0^1=1, 0异或任何数=任何数
  • 1^0=1,1^1=0, 1异或任何数-任何数取反
  • 任何数异或自己=把自己置0
    异或的几个小妙用
    (1)使某些特定的位翻转
    例如想把10100001的第2位和第3位翻转,则可以将该数与00000110进行按位异或运算。
    10100001^00000110 = 10100111
    (2)交换两个数.
    例如交换两个整数a=10100001,b=00000110的值,可通过下列语句实现:
    a = a^b;//a=10100111
    b = b^a;//b=10100001
    a = a^b;//a=00000110
    (3)在汇编里让变量设置为0
    eor x0, x0
    (4)判断两个是否相等
    return ((a ^ b) == 0)

按位清除操作

  • bic:位清零指令

位段插入操作

  • bfi: 位段(bitfield)插入指令
    image
    BFI Xd, Xn, #lsb, #width
    用Xn中的Bit[0: width] 替换 Xd中的从 lsb 开始的 width位,Xd其他位不变

位段提取操作

  • UBFX: 无符号数的位段提取指令
  • SBFX:有符号数的位段提取指令
    SBFX Xd, Xn, #lsb, #width ;
    从Xn寄存器提取位段,位段从第lsb位开始,位宽为width,然后结果写入到Xd寄存器最低比特位中。
    注意:
  • UBFX: 其他比特位是填充0
  • SBFX:其他比特位全是填充f

零计数指令clz

clz:计算最高为1的比特位前面有几个0
X1 = 0x0fffffffffffffff
clz x2, x1
结果为4

比较和跳转指令

比较指令

  • cmp比较两个数
  • cmn:负向比较(把一个数跟另外一个数的二进制补码相比较)
    cmp x1, x2 => x1 – x2
    cmn x1, x2 => x1 + x2
    条件操作后缀
    image

条件选择指令

  • csel:条件选择指令
  • cset:条件置位指令
  • csinc:条件选择并增加指令
    搭配cmp指令来使用:
    image

基本跳转指令

b:跳转指令, 无条件的跳转指令,不返回。
跳转范围:PC +/- 128MB

有条件的跳转指令 b.cnd

  • b.cnd:有条件的跳转指令, 不返回。
  • cnd为条件操作后缀
  • 跳转范围:PC +/- 1MB

bx指令

bx:跳转到寄存器指定的地址处,不返回。

带返回地址的跳转指令

  • bl:带返回地址(PC+4 => x30),适用于call 子函数
  • 返回地址:保存到x30中,=>保存的是父函数的PC+4
  • 跳转范围:PC +/- 128MB

blx指令

  • blx:跳转到寄存器指定的地址处,可以返回
  • 返回地址:保存到x30中,=>保存的是父函数的PC+4

ret指令

ret:从子函数返回。

eret指令

eret:从当前的异常模式返回。
通常可以实现模式切换,例如EL1切换到EL0
image
在遇到嵌套调用bl时候,需要在父函数里,先把x30寄存器保存到一个临时寄存器。在父函数ret返回时,从临时寄存器中恢复x30寄存器的值,再ret返回

比较并跳转指令

  • cbz:比较xt寄存器是否为0,为0则跳转到label标签处,跳转范围为+/- 1MB
  • cbnz:比较xt寄存器是否为0,不为0则跳转到label标签处,跳转范围为+/- 1MB
  • tbz:测试寄存器中某个比特位是否为0,为0则跳转, 跳转范围为+/- 32KB
  • tbnz:测试寄存器中某个比特位是否为0,不为0则跳转, 跳转范围为+/- 32KB

其他重要指令

PC相对地址加载指令

  • adr指令:加载PC相对地址的label的地址,范围为+/- 1MB
  • adrp指令:加载PC相对地址的label的地址,它只加载label所属的4KB对齐的地址,范围为 +/- 4GB
    adr x0, label => 读取label的地址,相对PC的
    adrp x0, lable => 读取label所在的4KB的地址,相对PC的
    读取详细的地址:add x0, x0, #:lo12:lable
    image

内存独占加载和存储指令

  • ldxr指令:内存独占加载指令。从内存中以 独占exclusive的方式加载内存的地址到通用寄存器。
  • stxr指令:内存独占存储指令。
    ldxr是加载内存,不过它会通过独占监视器来监视这个内存的访问,监视器会把这个内存地址标记为独占访问,保证其独占的方式来访问
    Stxr是有条件地存储内存,刚才ldxr标记的内存地址被独占的方式 存储了
    ldxr xd,[xn | sp] => 独占的读取xn或者sp地址的内容到xd寄存器
    stxr wd,xt,[xn | sp] => 独占地方式把xt的内容写入到xn或者sp地址
    Wd为0表示 成功
    Wd为1表示不成功
  • ldxr指令和stxr指令通常需要配对使用
  • Linux内核常常用来实现 atomic的访问,例如atomic_write(), atomic_set_bit()
  • spinlock机制可以简单地使用ldxr和stxr指令来实现

异常处理指令

指令 描述
SVC 系统调用指令 svc #imm运行应用程序通过SVC指令来自陷到操作系统里, 通常会进入到EL1异常
HVC 虚拟化系统调用指令
SMC 安全监控系统调用指令

系统寄存器访问指令

指令 描述
MRS 读取系统寄存器到通用寄存器
MSR 更新系统寄存器

内存屏障指令

(1)数据存储屏障(Data Memory Barrier,DMB) 指令
保证的是内存屏障前后的内存访问指令的执行顺序,不会保证内存访问指令在内存屏障指令之前必须完成
(2)数据同步屏障(Data synchronization Barrier,DSB)指令
任何指令都要等待DSB前面的存储访问完成
(3)指令同步屏障(Instruction synchronization Barrier,ISB)指令
冲洗流水线(Flush Pipeline)和预取buffers后,才会从高速缓存或者内存中预取ISB指令之后的指令
DMB和DSB指令还可以带参数,来指定内存屏障指令的顺序以及共享属性等信息
image