在上面几篇已经提到了一些数学运算功能,这篇再详细介绍之,数学运算无非是一些加减乘除,移位等等,就是这些简单的操作造就了丰富多彩的程序世界,可以说程序的根本就是计算。
指令介绍
加减法
首先说加减法,其实加减法本身没什么好说,但是还是跟mov指令一样,AT&T和MASM语法的操作数相反,造成了混乱。对AT&T来说:
addx source, destination
subx source, destination
其中x为操作数长度,与mov相同,这里的含义相当于 destination += source; 和 destination -= source
对MASM来说,语法格式为
add destination, source
sub destination, source
add 和 sub 指令都会影响进位,零标志,符号标志,溢出标识辅助进位和奇偶标志,方便使用条件控制。
inc和dec指令分别对寄存器或内存进行加一和减一操作,对于AT&T的指令,需要增加操作数长度指令b,l,w,q等,格式为:
inc operand
dec operand
相反数
neg 指令求寄存器和内存的相反数:
neg reg
neg mem
进位加减法,adc和sbb,这两个指令的格式与add和sub完全相同,所不同的是adc把源操作数,目的操作数和进位标识相加。sbb同样也减去进位标识,这两个指令的存在主要是为了进行大整数运算。
乘法
乘法mul,imul: 其中mul为无符号乘法,乘法的格式也比较简单,而imul为有符号乘法, 却有三种语法格式。语法如下:
以下是AT&T语法格式
mul source
imul source
imul source, destination
imul imm, source, destination
以下是MASM语法格式
mul source
imul source
imul destination, source
imul destination, source, imm
可以看到mul只有源操作数,那目的操作数呢?其实这里是隐含操作数。mul的操作数如下:
被乘数 | 乘数 | 积 |
---|---|---|
AL | r/m8 | AX |
AX | r/m16 | DX:AX |
EAX | r/m32 | EDX:EAX |
最所以积比被乘数宽度大一倍,是因为两个32位的乘数乘起来可能是64位的!
除法div,idiv 除法与乘法相反,mul的操作数表反过来便是div的操作数表,如下表所示:
被除数 | 除数 | 商 | 余数 |
---|---|---|---|
AX | r/m8 | AL | AH |
DX:AX | r/m16 | AX | DX |
EDX:EAX | r/m32 | EAX | EDX |
这里跟乘法不同的是64位的操作数除以32位的操作数以后结果还可能大于32位,如此一来便会溢出,所以计算除法要千万小心,确保你的操作数不会出现溢出问题。 语法格式如下:
; 因为只有一个操作数,所以AT&T语法格式和MASM相同
div divisor
div divisor
移位运算
先说一下算术移位和逻辑移位,二者对于左移来说没有什么区别,都是在空位上补0,但对于右移,对于无符号的应该补零,对于有符号的应该补符号位,所以就有了算术和逻辑,算术右移补符号位,一般用于有符号数,逻辑右移不论符号位是什么,一律补0。
循环移位,比如左移,先把所有位向左移一位,把最高位补到最低位的空缺,顺便把CF标志设置为最高位的值。 带进位的循环移位,跟循环移位相比,循环移位CF本身不参与移位,仅仅是记录最高位的值,而带进位的循环移位CF标志也参与移位,即CF放入对地位,高位填充至CF标志。
还有一种移位为双精度移位,拿左移为例,改指令把目的操作数左移指定的位数,左移空出来的位用源操作数的高位来填充,源操作数不变,相当于循环移位但是是对不同的操作数来讲的。
shl 逻辑左移
shr 逻辑右移
sal 算术左移
sar 算术右移
rol 循环左移
ror 循环右移
rcl 带进位循环左移
rcr 带进位循环右移
shld 双精度左移
shrd 双精度右移
以下是各种语法规则(imm8为8位立即数)
以下是AT&T语法格式
; shl shr sal sar rol ror rcl rcr 指令格式相同
shlx destination
shlx imm8, destination
shlx %cl, destination
以下是MASM语法格式
; shl shr sal sar rol ror rcl rcr 指令格式相同
shl destination, imm8
shl destination, cl
; shld shrd 格式相同
shld destination, source, cl/imm8
与或非运算
对AT&T语法格式 x位操作数宽度
andx source, destination
orx source, destination
xorx source, destination
notx source, destination
MASM 语法格式
and destination, source
or destination, source
xor destination, source
not reg/mem
test指令
test 指令与 and 指令相同,不过test指令不改变操作数的值,仅仅置eflags标志。
cmp指令
cmp 指令与 sub 指令相同,唯一的区别是cmp只置标记不保存结果,也就是cmp操作的源、目的操作数不变。
例子
这里由于指令过多,而且写的比较匆忙,没有一一举例,仅仅做了一个简单总结,我的一位挚友对此文每种指令都做了尝试,并指出一些错误,在次表示感谢!以下测试代码由这位挚友提供,由于没有输出,可由ollydbg调试观察结果。
.386
.model flat, stdcall
option casemap:none
include windows.inc
include kernel32.inc
includelib kernel32.lib
.data
res db 5
res2 dw 0
res3 dd 0
.code
start:
add res, 5
sub res, 3
inc res
dec res
neg res
;单操作数乘法
mov al, 4 ;被乘数根据位数不同分别默认存放于al(8)、ax(16)、eax(32)
mov ah, 8
mul ah
mov res2, ax ;积根据位数不同分别默认存放于ax(16)、dx:ax(32)、edx:eax(64)
;32位积为什么不能直接存放于eax中呢?是因为刚开始计算机只有16位所以就按这种高位低位的方式来存放
;后面发展成为32位后为了保持兼容性故还是这样存放
;双操作数乘法
mov eax, 4
mov ebx, 8
imul eax, ebx
mov res3, eax
;三操作数乘法
mov eax, 4
mov ebx, 8
imul eax, ebx, 32 ;所谓三操作数就是在最后多乘了一个立即数
mov res3, eax
;除法
mov ax, 33
mov dx, 0
mov bx, 4
div bx
mov res, al ;商
mov res, ah ;余数
;移位运算
shl res, 5
shr res, 2
sal res, 1
sar res, 2
rol res, 2
ror res, 2
rcl res, 3
rcr res, 4
;此指令前两个寄存器必须是大于8位的,左边操作数可以是内存,最后一个必须为8位
shld res2, ax, 3
shrd res2, ax, 3
;逻辑运算
mov ax, 0ffffh
mov res2, 1
and res2, ax
or res2, ax
mov ax, res2
xor res2, ax
not res2
;test, cmp指令
mov ax, 0
test res2, ax
jz label1
label1:
inc res2
label2:
invoke ExitProcess, NULL
end start