ARM Assembly(03)-算术和逻辑运算

本文对ARM汇编中的算术运算指令进行介绍,包括加法、减法和乘法运算

CPSR Flags

ARM在执行算术运算时的一个特殊性在于,是否更新标志是可选的,默认不带后缀的指令不会更新标志位,而在x86的设计中,算术运算会自动更新标志位。以下是算术运算的二进制机器码示意图

二进制机器码中bit20用来标识指令是否更新CPSR的标志位,指令带后缀S时,编译生成的二进制码中S位为1,则该指令被CPU执行时结果会影响CPSR

可带S后缀的算术运算指令如下表

Instruction(noflags) desc Instruction(flags) desc
ADD 加法 ADDS 加法并设置标志位
ADC 进位加法 ADCS 进位加法并设置标志位
SUB 减法 SUBS 减法并设置标志位
SBC 借位减法 SBCS 借位减法并设置标志位
MUL 乘法 MULS 乘法并设置标志位
UMULL 长乘法 UMULLS 长乘法并设置标志位
RSB 反减法 RSBS 反减法并设置标志位
SRC 进位反减 SRCS 进位反减并设置标志位

ADDS

来看一个设置标志位的加法例子

qemu+gdb调试,单步运行到第7行时,查看CPSR的值

可以看到CPSR为0x400001D3,Z=1

执行完第7行加法运算后,查看CPSR的值

可以看到CPSR为0x600001D3,Z=1,C=1,因为R2和R3运算发生了进位,且运算结果R1为0,因此C和Z标志都为1

单步运行到第12行时,查看CPSR的值

可以看到CPSR无变化

执行完第12行加法运算后,查看CPSR的值

可以看到CPSR为0x200001D3,Z=0,C=1,因为R2和R3运算发生了进位,且运算结果R1不为0,因此C为1,Z为0

加法实现自增

ARM汇编中没有专门的指令用于寄存器的自增,可以使用加法指令完成

1
ADDS Rn,Rn,#1

ADC

ADC指令用于大于32位的数据加法,指令格式如下

1
ADC Rd,Rn,Op2

ADC指令在进行加法运算时,会额外将进位标志C添加到运算结果中,因此Rd=Rn+Op2+C。使用ADC进行大于32位数据加法时,通常是ADDS和ADC结合来用,看以下例子

在以上程序中,将R1、R2、R3、R4、R5相加到R8和R9中,R8负责低32位的数据存放,R9负责高位的数据存放,每次相加时都将数据与R8累加,因为使用了ADDS,发生进位会将C置位,每加完一个数,R9都使用ADC与0进行进位相加,其作用是将每次R8的进位累加起来,运算完成后的结果如图

R8为0x486CED00,R9为0x3,因此加法的最终结果为0x3486CED00,与计算器得到的结果相同

以下是每一步的寄存器状态与运算过程

SUBS

1
SUBS Rd,Rn,Op2

ARM没有使用专用的减法器,对于减法运算,将其转换为加法运算,减法运算步骤如下

  • 取操作数Op2的补码,0xFFFFFFFF-Op2+1

  • 将补码与被减数Rn相加运算

  • 结果放入Rd

  • 如果加法发生进位,则设置标志位C

以下是一个减法例子

1
2
3
MOV  R2,#0x4F
MOV R3,#0x39
SUBS R4,R2,R3

运算该减法时,首先取减数R3的补码,0xFFFFFFFF-0x39+1=0xFFFFFFC7,然后将0xFFFFFFC7与0x4F相加,结果发生了进位,C=1。程序中可以利用C标志来判断减法结果的正负,如果C为1,表明结果为正,如果C为0,表明结果为负

SBC

SBC指令格式如下

1
SBC Rd,Rn,Op2

该指令用于大于32位数字的减法,Rd=Rn-Op2-C。与ADC类似,SBC与SUBS结合使用,可以计算大于32位的数据,下面以一个例子进行展示

1
2
3
4
5
6
LDR R0,=0xF62562FA
LDR R1,=0xF412963B
MOV R2,#0x21
MOV R3,#0x35
SUBS R5,R1,R0
SBC R6,R3,R2

该程序计算0x35F412963B-0x21F62562FA的值,将数据分为了高32位和低32位,低32位使用SUBS带进位减法,因为0xF412963B<0xF62562FA,因此低位减法结果为负,C=0

RSB

RSB指令格式如下

1
RSB Rd,Rn,Op2

该指令与SUB的区别在于SUB是Rd=Rn-Op2,而RSB是Rd=Op2-Rn,该指令的一个用途是得到一个数据的补码

1
2
MOV R1,#0x6E
RSB R0,R1,#0

如上述代码,将立即数0减去R1,得到R1的补码0xFFFFFF92

RSC

RSC的指令格式为

1
RSC Rd,Rn,Op2

该指令可用于获取的64位数据的补码

1
2
3
4
LDR R0,=0xF62562FA
LDR R1,=0xF812963B
RSB R5,R0,#0
RSC R6,R1,#0

上述代码将0xF812963BF62562FA分为高低32位进行单独求补码,高位求补码时使用RSC获得进位标志C

MUL

MUL指令格式如下

1
MUL Rd,Rn,Op2

该指令将Rn与Op2相乘,结果放入Rd。注意该指令结果只能是32bit之内,超过32位的部分将被舍弃

UMULL

UMULL指令格式如下

1
UMULL RdLo,RdHi,Rn,Op2

UMULL使用两个寄存器RdLo和RdHi来存放低32位和高32位结果

MLA

在某些场景中,需要将两数相乘再与另一个数相加,ARM提供了MLA指令

1
MLA Rd,Rm,Rs,Rn

该指令计算方式为Rd = Rm x Rs + Rn

UMLAL

MLA也存在无法计算结果大于32位的情况,需要使用UMLAL

1
UMLAL RdLo,RdHi,Rn,Op2

与UMULL类似,使用了RdLo和RdHi来存放低32位和高32位结果

逻辑运算和算术运算类似,如果想要主动更新CPSR,需要添加后缀

AND

AND指令格式为

1
AND Rd,Rn,Op2

该指令将对操作数Rn和Op2执行逐位逻辑与运算,并将结果放入目标寄存器Rd中。当Op2为立即数时,数值范围限制到0xFF

ORR

ORR指令格式为

1
ORR Rd,Rn,Op2

该指令将对操作数Rn和Op2执行逐位逻辑或运算,并将结果放入目标寄存器Rd中。当Op2为立即数时,数值范围限制到0xFF

EOR

EOR指令格式如下

1
EOR Rd,Rn,Op2

该指令将对操作数Rn和Op2执行逐位逻辑异或运算(相同为0,不同为1),并将结果放入目标寄存器Rd中。当Op2为立即数时,数值范围限制到0xFF

BIC

BIC指令格式如下

1
BIC Rd,Rn,Op2

BIC指令用于清除Rn寄存器中由Op2选定的位,Op2中值为1的位,在Rn寄存器中将被清零,结果放入Rd中

MVN

MVN指令格式如下

1
MVN Rd,Rn

MVN指令用于生成Rn的补码。有一点需要注意的是,当需要把0xFFFFFFFF立即数放入一个寄存器时,可以使用MVN Rd,#0来实现,这种方式相比LDR Rd,=0xFFFFFFFF的好处在于,LDR赋值寄存器立即数属于一种伪指令,真正实现的时候会转换为好几条指令,而MVN是真实的一条指令