汇编基础介绍——ARMv8指令集(四)
一、CMP 指令
CMP 指令用来比较两个数的大小。在 A64 指令集的实现中,CMP 指令内部调用 SUBS 指令来实现。
1.1、使用立即数的 CMP 指令
使用立即数的 CMP 指令的格式如下。
CMP <Xn|SP>, #<imm>{, <shift>}
上述指令等同于如下指令。
SUBS XZR, <Xn|SP>, #<imm> {, <shift>}
1.2、使用寄存器的 CMP 指令
使用寄存器的 CMP 指令的格式如下。
CMP <Xn|SP>, <R><m>{, <extend> {#<amount>}}
上述指令等同于如下指令。
SUBS XZR, <Xn|SP>, <R><m>{, <extend> {#<amount>}}
1.3、使用移位操作的 CMP 指令
使用移位操作的 CMP 指令的格式如下。
CMP <Xn>, <Xm>{, <shift> #<amount>}
上述指令等同于如下指令。
SUBS XZR, <Xn>, <Xm> {, <shift> #<amount>}
1.4、CMP 指令与条件操作后缀
CMP 指令常常和跳转指令与条件操作后缀搭配使用,例如条件操作后缀 CS 表示是否发生了无符号数溢出,即 C 标志位是否置位,0 表示 C 标志位没有置位。
如下代码使用 CMP 指令来比较如下两个寄存器。
cmp x1, x2
b.cs label
CMP 指令判断两个寄存器是否触发无符号溢出的计算公式与 SUBS 指令类似:
X1 + NOT(X2) + 1
如果上述过程中发生了无符号数溢出,那么 C 标志位会置 1,则 b.cs 指令将会跳转到 label 处。
下面的代码用来比较 3 和 2 两个立即数。
my_test:mov x1, #3mov x2, #21:cmp x1, x2b.cs 1bret
至于如何比较,需要根据 b 指令后面的条件操作后缀来定。CS 表示判断是否发生无符号数溢出。根据 CMP 指令的运算公式可得,3 + NOT(2) +1,其中 NOT(2)把立即数 2 按位取反,取反后为 0xFFFFFFFFFFFFFFFD。 3 + 0xFFFFFFFFFFFFFFFD + 1 的最终结果为 1,这个过程中发生了无符号数溢出,C 标志位为 1。所以,b.cs 的判断条件成立,跳转到标签 1 处,继续执行。
二、移位指令
常见的移位指令如下。
- LSL:逻辑左移指令,最高位会被丢弃,最低位补 0,如下(a)图所示。
- LSR:逻辑右移指令,最高位补 0,最低位会被丢弃,如下(b)图所示。
- ASR:算术右移指令,最低位会被丢弃,最高位会按照符号进行扩展,如下(c)图所示。
- ROR:循环右移指令,最低位会移动到最高位,如下(d)图所示。
如下代码使用了 ASR 和 LSR 指令。
ldr w1, =0x8000008a
asr w2, w1, 1
lsr w3, w1, 1
在上述代码中,ASR 是算术右移指令,把 0x8000008A 右移一位并且对最高位进行有符号扩展,最后结果为 0xC0000045。LSR 是逻辑右移指令,把 0x8000008A 右移一位并且在最高位补 0,最后结果为 0x40000045。
三、位操作指令
3.1、与操作指令
与操作主要有两条指令。
- AND:按位与操作。
- ANDS:带条件标志位的与操作,影响 Z 标志位。
3.1.1、AND 指令
AND 指令的格式如下。
AND <Xd|SP>, <Xn>, #<imm>
AND <Xd>, <Xn>, <Xm>{, <shift> #<amount>}
AND 指令支持两种方式。
- 立即数方式:对 Xn 寄存器的值和立即数 imm 进行与操作,把结果写入 Xd/SP 寄存器中。
- 寄存器方式:先对 Xm 寄存器的值移位操作,然后再与 Xn 寄存器的值进行与操作,把结果写入 Xd/SP 寄存器中。
指令参数说明如下:
shift 表示移位操作,支持 LSL、LSR、ASR 以及 ROR。
amount 表示移位数量,取值范围为 0~63。
3.1.2、ANDS指令
ANDS 指令的格式如下。
ANDS <Xd>, <Xn>, #<imm>
ANDS <Xd>, <Xn>, <Xm>{, <shift> #<amount>}
ANDS 指令支持两种方式。
- 立即数方式:对 Xn 寄存器的值和立即数 imm 进行与操作,把结果写入 Xd/SP 寄存器中。
- 寄存器方式:先对 Xm 寄存器的值做移位操作,然后再与 Xn 寄存器的值进行与操作,把结果写入 Xd/SP 寄存器中。
指令参数说明如下。
shift 表示移位操作,支持 LSL、LSR、ASR 以及 ROR。
amount 表示移位数量,取值范围为 0~63。
与 AND 指令不一样的地方是它会根据计算结果来影响 PSTATE 寄存器的 N、 Z、 C、 V 标志位。
如下代码使用 ANDS 指令来对 0x3 和 0 做“与”操作。
mov x1, #0x3
mov x2, #0ands x3, x1, x2
mrs x0, nzcv
“与”操作的结果为 0。通过读取 NZCV 寄存器,我们可以看到其中的 Z 标志位置位了。
3.2、或操作指令
3.2.1、ORR指令
ORR(或)操作指令的格式如下。
ORR <Xd|SP>, <Xn>, #<imm>
ORR <Xd>, <Xn>, <Xm>{, <shift> #<amount>}
ORR 指令支持两种方式。
- 立即数方式:对 寄存器的值与立即数 Xn imm 进行或操作。
- 寄存器方式:先对 Xm 寄存器的值做移位操作,然后再与 Xn 寄存器的值进行或操作。
指令参数说明如下。
shift 表示移位操作,支持 LSL 、LSR 、ASR 以及 ROR。
amount 表示移位数量,取值范围为 0~63。
3.2.2、EOR指令
EOR (异或)操作指令的格式如下。
EOR <Xd|SP>, <Xn>, #<imm>
EOR <Xd>, <Xn>, <Xm>{, <shift> #<amount>}
EOR 指令支持两种方式。
- 立即数方式:对 Xn 寄存器的值与立即数 imm 进行异或操作。
- 寄存器方式:先对 Xm 寄存器的值做移位操作,然后再与 Xn 寄存器的值进行异或操作。
指令参数说明如下。
shift 表示移位操作,支持 LSL 、LSR 、ASR 以及 ROR。
amount 表示移位数量,取值范围为 0~63。
异或操作的真值表如下。
0 ^ 0 = 0
0 ^ 1 = 1
1 ^ 0 = 1
1 ^ 1 = 0
从上述真值表可以发现 3 个特点。
- 0 异或任何数 = 任何数。
- 1 异或任何数 = 任何数取反。
- 任何数异或自己都等于 。
利用上述特点,异或操作有如下几个非常常用的场景。
使某些特定的位翻转。
例如, 想把 0b10100001 的第 2 位和第 3 位翻转, 则可以对该数与 0b00000110 进行按位异或运算。
10100001 ^ 00000110 = 10100111
交换两个数。
例如,交换两个整数 a=0b10100001 和 b=0b00000110 的值可通过下列语句实现。
a = a^b; //a=10100111
b = b^a; //b=10100001
a = a^b; //a=00000110
在汇编程序里把变量设置为 。
eor x0, x0
判断两个整数是否相等。
bool is_identical(int a, int b)
{return ((a ^ b) == 0);
}
3.3、位清除操作指令
BIC (位清除操作)指令指令用于将寄存器中的某些位清零,而保持其他位不变。其核心作用是选择性地清除(置 0)特定位置的位,常用于权限屏蔽、标志位清除等场景。其格式如下。
BIC <Xd>, <Xn>, <Xm>{, <shift> #<amount>}
BIC 指令支持寄存器方式: 先对 Xm 寄存器的值做移位操作, 然后再与 Xn 寄存器的值进行位清除操作。
指令参数说明如下。
shift 表示移位操作,支持 LSL 、LSR 、ASR 以及 ROR。
amount 表示移位数量,取值范围为 0~63。
BIC 指令的本质是:
Xd = Xn & (~Xm)
- 将掩码 Xm 按位取反。
- 将源寄存器(
Xn
)与取反后的掩码进行按位与操作。
掩码中为 1 的位会清除对应位,为 0 的位保持不变。
3.4、CLZ 指令
CLZ 指令的格式如下。
CLZ <Xd>, <Xn>
CLZ 指令计算为 1 的最高位前面有几个为 0 的位。
如下代码使用了 CLZ 指令。
ldr x1, =0x1100000034578000
clz x0, x1
X1 寄存器里为 1 的最高位是第 60 位,前面还有 3 个为 0 的位,最终 X0 寄存器的值为 3。
四、位段操作指令
4.1、位段插入操作指令
BFI 指令的格式如下。
BFI <Xd>, <Xn>, #<lsb>, #<width>
BFI 指令的作用是用 Xn 寄存器中的 Bit[0, width - 1]替换 Xd 寄存器中的 Bit[lsb, lsb + width - 1], 寄存器中的其他位不变。
BFI 指令常用于设置寄存器的字段。
如下代码将寄存器 X1 的低 4 位插入到寄存器 X0 的第 8~11 位(共 4 位)。
BFI X0, X1, #8, #4 ; 将X1的低4位插入到X0的第8~11位
步骤解析:
- 假设初始值:
-
X0 = 0xFFFFFFFFFFFFFFFF
(全 1)。X1 = 0x000000000000000F
(低 4 位为 1,其余为 0)。
- 提取源位段:
-
X1
的低 4 位为0b1111
。
- 清除目标位段:
-
X0
的第 8~11 位被清零,变为0xFFFFFFF0FFFFFF
。
- 插入位段:
-
- 源位段
0b1111
左移 8 位后为0x0000000000000F00
。 - 合并后
X0 = 0xFFFFFFF0FFFFFF | 0x0000000000000F00 = 0xFFFFFFF0FFFFFFFF
。
- 源位段
4.2、位段提取操作指令
UBFX 指令的格式如下。
UBFX <Xd>, <Xn>, #<lsb>, #<width>
UBFX 指令的作用是提取 Xn 寄存器的 Bit[lsb, lsb + width - 1],然后存储到 Xd 寄存器中。
UBFX 还有一个变种指令 SBFX,它们之间的区别在于:SBFX 会进行符号扩展,例如,如果 Bit[lsb + width - 1] 为 1,那么写到 Xd 寄存器之后,所有的高位都必须写 1,以实现符号扩展。
UBFX 和 SBFX 指令常常用于读取寄存器的某些字段。
如下代码从寄存器 X1 的第 8~15 位(共 8 位)提取值,并零扩展到 X0。
UBFX X0, X1, #8, #8 ; 提取X1的第8~15位,存入X0
步骤解析:
- 假设初始值:
-
X1 = 0x0000000000FF00FF
(二进制0b0000...0000111111110000000011111111
)。
- 提取位段:
-
- 第 8~15 位为
0b00000000
(值为 0)。
- 第 8~15 位为
- 零扩展:
-
- 提取的 8 位右移至最低位,左侧补零,结果为
0x0000000000000000
。
- 提取的 8 位右移至最低位,左侧补零,结果为
- 存入目标寄存器:
-
X0 = 0x0000000000000000
。