跳转到主要内容

ISA: ARM Instructions Set

Arm

Abstract

本文章作为一个 ARM 指令的速查表使用。

MNEMONIC{S}{condition} {Rd}, Operand1, Operand2

上面就是 ARM 汇编指令的一个通用的格式说明,下面对每一个字段进行具体的说明:

  • MNEMONIC: 指令的助记符,如 ADD
  • S: 可选的扩展位,如果指令后带了这个,将根据计算结果更新 CPSR 寄存器中相应的 FLAG
  • condition: 执行条件,如果没有指定,则默认位 AL(无条件执行)
  • Operand1: 第一个操作数,可以是寄存器或者立即数
  • Operand2: 第二个操作数,可变的,可以是一个寄存器或者立即数,甚至带移位操作的寄存器

对于 Operand2 的解释和研究举例:

#123		@ - 立即数
Rx			@ - 寄存器,如 R1
Rx, ASR n	 @ - 对寄存器中的值进行算术右移 n 位后的值
Rx RRX		@ - 对寄存器中的值进行带扩展的循环右移 1 位后的值

Branch

InstructionExampleRemark
SUB不进位的减法

sub

减法指令,并且是不进位的减法。

b

(branch)跳转到某地址(无返回), 不会改变 lr (x30) 寄存器的值;一般是本方法内的跳转,如 while 循环,if else 等 ,如:

b LBB0_1      ; 直接跳转到标签 ‘LLB0_1’ 处开始执行

b 指令的一些变体1

  • bl: 跳转到标号出执行

  • b.le :判断上面cmp的值是小于等于 执行标号,否则直接往下走

  • b.ge 大于等于 执行地址 否则往下

  • b.lt 判断上面camp的值是 小于 执行后面的地址中的方法 否则直接往下走

  • b.gt 大于 执行地址 否则往下

  • b.eq 等于 执行地址 否则往下

  • b.hi 比较结果是无符号大于,执行地址中的方法,否则不跳转

  • b.hs 指令是判断是否无符号小于

  • b.ls 指令是判断是否无符号大于

  • b.lo 指令是判断是否无符号大于等于

我们总结了一些常见的跳转指令的集合,如下所示:

[
 'b.pl', 'b.ge', 'b.ls', 'b.vs', 'tbnz', 'b.gt', 
 'b', 'cbnz', 'svc', 'b.mi', 'b.lo', 'tbz', 
 'b.ne', 'b.hi', 'br', 'b.le', 'b.eq', 'ret', 
 'bl', 'b.lt', 'blr', 'b.hs', 'cbz'
]

tst

把一个寄存器的内容和另一个寄存器的内容进行按位与操作,并根据结果更新 CPSR 中条件标志位的值,当前运算结果为 1, 则 Z=0, 反之 Z=1.

fcvtz

浮点数转换为定点数。

cbz

和 0 比较(Compare),如果结果为零(Zero)就转移(只能跳到后面的指令);

CBZ Rn, label

Rn: is the register holding the operand.

label: is the branch destination.

同样的,还有不为 0 的时候跳转:

CBNZ Rn, label

tbnz

TBNZ X1, #3, label

上述汇编的含义为,如果 x1 寄存器的第 3 位不为 0, 则跳转到 label.

还有用法如下:

tbnz w16, #0, #+0xc (addr 0x1baecc)

按照上述的例子可以推断,判断 w16 的第 0 位是否为 0, 如果不为 0, 则跳转到上述地址。

sxtw

sxtw 指令的使用方法如下:

sxtw x7, w6

其含义为将 w6 进行符号位扩展,并传给 x7; w6x7 的低 32 位.

这个博客上面展示了一个 sxtw 导致的惨案:一个include引起的惨案

内存读写

ARM 使用加载存储模型进行内存访问,这意味着只有加载/存储(LDR 和 STR)指令才能访问内存。在 x86 上,大多数指令都可以直接对内存中的数据进行操作,而在ARM上,必须先将内存中的数据从内存移到寄存器中,然后再进行操作。这意味着递增ARM上特定内存地址上的 32 位值将需要三种类型的指令(加载,递增和存储),以便首先将特定地址上的值加载到寄存器中,在寄存器中递增值,以及将其从寄存器存储回存储器2

ldr

加载一个寄存器:

  • 32 位常量
  • 地址

用于从存储器中将一个 32 位的字数据传送到目的寄存器中。

  • 将寄存器 x1 的值作为地址,取该内存地址的值放入寄存器 x0 中:x0 <- [x1]

    ldr x0, [x1]
  • 将栈内存 [sp + 0x8] 处的值读取到 w8 寄存器中

    ldr w8, [sp, #0x8]
  • 将寄存器 x1 的值加上 4 作为内存地址, 取该内存地址的值放入寄存器 x0 中, 然后将寄存器 x1 的值加上 4 放入寄存器 x1 中: x0 <- [x1 + 4]; x1 <- x1 + 4

    ldr x0, [x1, #4]!
  • 将寄存器 x1 的值作为内存地址,取内该存地址的值放入寄存器 x0 中, 再将寄存器 x1 的值加上 4 放入寄存器 x1 中

    ldr x0, [x1], #4
  • 将寄存器 x1 和寄存器 x2 的值相加作为地址,取该内存地址的值放入寄存器 x0 中

    ldr x0, [x1, x2]

ldur

ldr 一样,只不过,ldur 后面的立即数是负数。

ldur w16, [x5, #-8]

ldp

举例来说:

ldp	x20, x19, [sp, #0x150] 

简单可以理解为将栈弹出到 x20, x19 中。

ldrb

和下文中的 strb 的含义一样,将内存中的值读入寄存器中,并且只读取一个字节,也就是说把取到的数据放在目的寄存器的低 8 位,然后将高 24 位填充位 0。

ldrb w2, [x5, x2]

读取 x5 + x2 内存的值并且存储其低 8 位到 w2 中。

关于其硬件原理的介绍,也可以参考这篇博客3ARM的STRB和LDRB指令分析

ldrh

ldrhldrb 一样,不同之处在于 ldrh 会读入半个字长,就是 4 位。

ldrh w2, [x5, x2, lsl #1]

x5 + (x2 << 1) 的地址对应的值放入寄存器 w2 中,注意只放入读取到的值的最低 4 位,剩余的高 28 位填 0.

ldrsw

在 ARM 架构中,LDRSW 指令是用于从内存中读取一个 32 位带符号整数到寄存器的指令。LDRSW 的全称是“Load Register Signed Word”,其中的 S 表示的是 Signed,即有符号类型。

具体来说,LDRSW 指令的语法如下所示:

LDRSW Xt, [Xn{,#0|,#Imm}] 

其中:

  • Xt:目标寄存器,用于存储从内存中读取的带符号整数。
  • Xn:基地址寄存器,用于存储待读取数据的内存地址。
  • #Imm:偏移量,用于计算实际要读取的内存地址,可以是 1~4 字节的立即数,根据指令变体的不同,也可能存在其他可选的偏移量格式。

执行 LDRSW 指令时,它会从指定的内存地址中取得一个 32 位带符号整数,将其符号位扩展(sign extension)至 64 位,并将结果存储到目标寄存器中。需要注意的是,LDRSW 指令只能读取 32 位的有符号整数,如果需要读取其它类型的数据,则需要使用其他类型的 Load 指令。

总之,LDRSW 指令是 ARM 架构中的一种用于从内存中读取带符号整数的指令,可以广泛应用于各种需要使用 32 位有符号整数的场景中。

adrp

adrp x23, #-0x3ed000 (addr -0x234000)

adrp 一般用于获得地址,就我个人的理解而言,adrp 将当前 PC 所在的页的基地址计算得到并存储到寄存器中,后续根据这个寄存器中的基地址进行偏移运算。

引用一个博客中的一段描述4

adrp指令根据 PC 的偏移地址计算目标页地址。首先 adrp 将一个 21 位有符号立即数左移 12 位,得到一个 33 位的有符号数(最高位为符号位),接着将 PC地址的低 12 位清零,这样就得到了当前 PC 地址所在页的地址,然后将当前 PC 地址所在页的地址加上 33 位的有符号数,就得到了目标页地址,最后将目标页地址写入通用寄存器。此处页大小为 4KB,只是为了得到更大的地址范围,和虚拟内存的页大小没有关系。通过 adrp 指令,可以获取当前 PC 地址 ±4GB范围内的地址。通常的使用场景是先通过 adrp 获取一个基地址,然后再通过基地址的偏移地址获取具体变量的地址。

从上面的描述中我们可以看出,adrp 的结果是与当前的 PC 有关的,通过当前 PC 地址的偏移地址计算目标地址,因此属于位置无关码;在示例中括号也给出了最终的偏移地址。

stp

入栈指令,store pair

str

(store register) 将寄存器中的值写入到内存中,如:

str w9, [sp, #0x8] 

将寄存器 w9 中的值保存到栈内存 [sp + 0x8] 处。

strb

(store register byte) 将寄存器中的值写入到内存中(只存储一个字节),如:

strb w8, [sp, #7] 

将寄存器 w8 中的低 1 字节的值保存到栈内存 [sp + 7]

stlxr

在 ARM 架构中,STLXR 指令是原子性的存储、条件执行和即时跳转指令。该指令用于在多核创建共享内存并发访问的场景中,对数据进行原子性操作,以保证数据的一致性和正确性。

具体来说,STLXR 指令的含义如下:

  • STLXR: Store Exclusive Register
  • Rd: 目标寄存器,用于存储“存储操作”是否成功,取值为 0 或者 1。
  • Rt: 源寄存器,其中存储“写入数据”。
  • Rn: 目标地址寄存器,其中存储需要写入数据的内存地址。

当执行 STLXR 指令时,它会将目标存储地址(Rn)处的数据与当前处理器正在执行的 CPU 的标识进行比较。如果这个位置的值与标识符相同,则将源寄存器(Rt)中的数据写入该位置,并将目标寄存器(Rd)设置为 1,表示存储成功;否则,将目标寄存器设置为 0,表示存储失败。

可以看出,STLXR 指令实现了一种快速锁定和释放内存地址的机制,使得在多核场景中,多个 CPU 可以同时读取和修改共享内存的数据,而不会出现资源竞争的情况。

位操作

ubfx

举例说明:

ubfx	x10, x3, #3, #29

含义为从 x3 寄存器的第 3 位开始,提取 29 位到 x10 寄存器中。剩余高位用 0 填充,即 无符号位域提取指令。

UBFX 指令一般有两种用法:

UBFX Wd, Wn, #lsb, #width ; 32-bit
UBFX Xd, Xn, #lsb, #width ; 64-bit

and

AND 为按位与操作。

我们结合一个 AND 指令的指令编码来分析一下 AND 指令中的细节。

指令如下:

d2ffffe9 	mov	x9, #-281474976710656

二进制编码如下:

1011 0010 0100 1111 1111 1011 1110 1001

结合 arm v8 手册,我们 @todo,以后研究该命令。

lsl

lsl 为逻辑左移指令。

lsl	w9, w11, w9

左移指令分两种,可以给定寄存器或者立即数进行移位:

LSL <Wd>, <Wn>, #<shift> ; 32-bit
LSL <Xd>, <Xn>, #<shift> ; 64-bit

or

LSL <Wd>, <Wn>, <Wm> ; 32-bit
LSL <Xd>, <Xn>, <Xm> ; 64-bit

lsr

lsr 为右移指令,用法和 lsl 相似。

Footnotes

  1. https://juejin.cn/post/6978137866152968222

  2. https://azeria-labs.com/memory-instructions-load-and-store-part-4/

  3. ARM的STRB和LDRB指令分析

  4. https://blog.csdn.net/u011037593/article/details/121877496

修改历史13 次提交