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 | MOV R2,#0x4F |
运算该减法时,首先取减数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 | LDR R0,=0xF62562FA |
该程序计算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 | MOV R1,#0x6E |
如上述代码,将立即数0减去R1,得到R1的补码0xFFFFFF92
RSC
RSC的指令格式为
1 | RSC Rd,Rn,Op2 |
该指令可用于获取的64位数据的补码
1 | LDR R0,=0xF62562FA |
上述代码将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是真实的一条指令