ADD (immediate)
将 Xn 与 imm 相加,结果赋值给 Xd,imm 是无符号数,范围为 0 - 4095。
shift 是对 imm 进行移位,shift 为 0 的时候,表示左移 0 位,即不变。shift 为 1 的时候,表示左移12 位:
由于 imm 有效位只有 12 位,所以无法表示 0x1234 之类的数,但是可以表示 0x123。且有左移操作,所以也可以表示 0x123000 之类的数。
那么,add指令的立即数,可以描述负数么,也是可以的,但是就有点奇怪了,这本是减法指令做的事情:
可以看到,与整数指令相比,只是 op 位发生了变化:
00 8c 04 91
00 8c 04 d1
9 -> 1001
d -> 1101
所以,我们也可以猜测, sub 指令与 add 的指令的区别只在 op 位:
这样,我们只需要学习 add 指令,sub指令是对应的。
ADD (shifted register)
将 Xn 与 Xm 相加,结果放到 Xd 里面。
shift 是移位的方式:
移动的位数由 imm6 决定。
例子一:
00 00 01 8B ADD X0, X0, X1
汇编指令展开:
8B 01 00 00 ->
10001011 00000001 00000000 00000000 ->
1 0 0 01011 00 0 00001 000000 00000 00000
Xd 是 0, Xm 是1 ,Xn 是0,shift 是 0,imm6 是 0。
例子二:
20 FC 02 8B ADD X0, X1, X2,LSL#63
这个指令表示在相加前,需要将 X2 的值逻辑左移63 位。 imm6 是 6 位,刚刚好表示 0 ~ 63 的移位。
汇编指令展开:
8B 02 FC 20 ->
10001011 00000010 11111100 00100000 ->
1 0 0 01011 00 0 00010 111111 00001 00000
Xd 是 0,Xn 是 1,Xm 是 2,shift 是 0,imm6 是 0x3F(63)。
ADD (extended register)
这个指令稍微要复杂一点,它根据 option 的类型,来决定如何对 Xm 寄存器做扩展。
option 有8种取值类型,每种都有不同的意思,有遇到可去查资料了解。
举个简单的例子:
add w0, w1, w2,sxtw#2
SXTW/SXTH/SXTB:Sign-extend single-word / half-word / byte。
将 w2 寄存器按”单字“做符号位扩展。后面的 #2,只有在 LSL 模式下才有用,所以可以忽略,猜测一般编译器不会生成这种指令,应该会是 add w0, w1, w2,sxtw
这样的。
ADD还有加S的指令,表示会影响标志位,就不介绍了。
ADR
adr
指令根据PC的偏移地址计算目标地址。偏移地址是一个21位(immhi:immlo
)的有符号数,加上当前的PC地址得到目标地址。adr
可以获取当前PC地址±1MB范围内的地址。
看一个例子,ADR X0, 4
生成汇编指令如下:
.text:0000000000000878 20 00 00 10 ADR X0, loc_87C ; Keypatch modified this from:
.text:0000000000000878 ; MOV W1, W19
.text:0000000000000878
.text:000000000000087C
.text:000000000000087C loc_87C ; DATA XREF: main+44↑o
.text:000000000000087C E2 03 14 2A MOV W2, W20
ADR 就是将标号 loc_87C 的地址赋值给 X0 寄存器。
在 IDA 中,我们可能会经常看到一个伪指令:
00 00 00 90 00 60 23 91 ADRL X0, aADBD
该指令用于获取基于PC相对偏移+/- 4 GB内的符号地址,毕竟有时候可能 1M 不够用。
ADRP
ADRP 在 plt 表中会经常看到。
.plt:0000000000000760 ; int rand(void)
.plt:0000000000000760 .rand ; CODE XREF: main+2C↓p
.plt:0000000000000760 10 00 00 B0 ADRP X16, #rand_ptr@PAGE
.plt:0000000000000764 11 DA 47 F9 LDR X17, [X16,#rand_ptr@PAGEOFF]
.plt:0000000000000768 10 C2 3E 91 ADD X16, X16, #rand_ptr@PAGEOFF
.plt:000000000000076C 20 02 1F D6 BR X17
首先adrp
将一个21位有符号立即数左移12位,得到一个33位的有符号数(最高位为符号位),接着将PC地址的低12位清零,这样就得到了当前PC地址所在页的地址,然后将当前PC地址所在页的地址加上33位的有符号数,就得到了目标页地址,最后将目标页地址写入通用寄存器。
在 ida 中,就是将 rand_ptr@PAGE 的值赋值给 X16,rand_ptr@PAGE 的值已经做过地址对齐了。
SUB 系列
这个与ADD是一样的,就一个 op 位不一样。
SUB 也有 S 后缀,而且带 S 后缀的话,就等同于 CMP 指令。
AND
按位与,将第二个操作数与立即数按位与的结果放到第一个操作数里面。
说起来很简单,但是实际上它支持的 mask 是有限的,而生成 mask 的规则相当的难懂。
立即数的描述由3部分构成。
有篇文章,有兴趣的去研究:
https://dinfuehr.github.io/blog/encoding-of-immediate-values-on-aarch64/
AND 也可以加 S 后缀,等同于 TST 指令。
BIC (shifted register)
格式:
BIC <Xd>, <Xn>, <Xm>{, <shift> #<amount>}
BIC 其实与 AND 的作用是一样的,只不过,它会将 Xm 的值按位取反,取反后再与 Xn 做 and 操作,将结果放到 Xd 中。
那为啥有了 and 指令,还需要 bic 指令呢?是因为 and 指令支持的 mask 有限,所以 bic 相当于是一个扩展。
BIC 也支持 S 后缀。
ORR
与AND差不多,不过是按位或。
EOR
与AND差不多,异或。
移位指令
LSL:逻辑左移
40 00 41 D3 LSL X0, X2, #0x3F
11010011 01000001 00000000 01000000 ->
1 10 100110 1 000001 000000 00010 00000
注意,imms 不用关心,LSL是UBFM 指令的一种特殊形式,UBFM会用到imms。
在例子中,immr 的值算出来是 1,是因为最终计算的时候还需要使用#(-<shift> MOD 64)
这个表达式来计算,所以算出来的值是 63。
LSR:逻辑右移
ASR:算数右移