Win32汇编:算数运算指令总结
整理复习汇编语言的知识点,以前在学习《Intel汇编语言程序设计 - 第五版》时没有很认真的整理笔记,主要因为当时是以学习理解为目的没有整理的很详细,这次是我第三次阅读此书,每一次阅读都会有新的收获,这次复习,我想把书中的重点,再一次做一个归纳与总结(注:16位汇编部分跳过),并且继续尝试写一些有趣的案例,这些案例中所涉及的指令都是逆向中的重点,一些不重要的我就直接省略了,一来提高自己,二来分享知识,转载请加出处,敲代码备注挺难受的。
汇编中常用的运算符,加减乘除等,另外包括了移位运算等,移位又分为,算数移位,逻辑移位,循环移位,双精度移位等。
ADD/SUB指令: ADD/SUB指令将将同尺寸的源操作数和目的操作数相加,且不改变原操作数,相加后的结果存入目的操作数中.
.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib.dataMyList DWORD 10h,20h,30h,40h.codemain PROC; 将eax于ebx 两数相加: 将相加后的结果放入eaxmov eax,1024mov ebx,2048add eax,ebx; 同样两数相减,将结果放到eax中mov eax,1024sub eax,512; 针对数组的相加同样可以mov esi,offset MyList ; 获取到首地址mov eax,0mov ebx,0mov eax,dword ptr ds:[esi] ; 找到第一个元素mov ebx,dword ptr ds:[esi + 1 * 4] ; 找到第二个元素add eax,ebx ; 相加操作invoke ExitProcess,0main ENDP
END main
NEG 取反指令: 该指令通过将数字转换为对应的补码而求出其值的相反数,结合上面的加法与减法案例,我们来模拟编译器处理特定语句的写法Rval = -Xvar + (Yvar - Zvar)
.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib.dataRval SDWORD ?Xval SDWORD 26Yval SDWORD 30Zval SDWORD 40
.codemain PROC; 1.写出: Rval = -Xvar + (Yvar - Zvar) 汇编格式; 首先将Xval的值通过neg取反mov eax,dword ptr ds:[Xval]neg eax; 然后将Yval与Zval相减后复制到Yvalmov ebx,dword ptr ds:[Yval]sub ebx,dword ptr ds:[Zval];最后将两个子项相加后放入到Rval中add eax,ebxmov dword ptr ds:[Rval],eax; 2.写出: Rval = (Xval+Yval) - (Yval+Zval)mov eax,dword ptr ds:[Xval]add eax,dword ptr ds:[Yval]mov dword ptr ds:[Rval],eaxmov ebx,dword ptr ds:[Yval]add ebx,dword ptr ds:[Zval]sub dword ptr ds:[Rval],ebxinvoke ExitProcess,0main ENDP
END main
AND/OR/XOR 布尔指令: AND指令是对数据进行与运算,OR指令则是对数据进行或运算,XOR则是异或,这三个指令都是将操作值保存在目的操作数中,需要注意的是这些运算符都是针对二进制数进行操作的.
.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib.dataRval DWORD ?String BYTE "hello LYSHARK",0dh,0h.codeToString procmov ecx,lengthof Stringmov esi,offset Strings: ;and byte ptr [esi],11011111b ; 清除第五位,小写变大写or byte ptr [esi],00100000b ; 设置第五位,大写变小写inc esiloop sretToString endpmain PROC; and 逻辑与运算xor eax,eaxmov al,00111011band al,00001111b ; 运算后去除al中的0mov eax,00401024hand eax,0ffh ; eax = 00000024hmov eax,00401024hand eax,0ffffh ; eax = 00001024hmov eax,00401024hand eax,0ffff0000h ; eax = 00400000h; or 逻辑或运算xor edx,edxmov dl,5 ; 二进制值or dl,30h ; 转为有ASCII码; xor 异或运算mov eax,0ffhxor eax,0ffh ; eax = 0mov eax,4xor eax,5 ; eax = 1mov eax,0401000hxor eax,0400000h ; eax = 00001000hmov eax,0401000hxor eax,01000h ; eax = 00400000h; 异或可用于检查标志位xor eax,eaxmov eax,00001111hxor eax,0 ; 检查基偶标志mov eax,00100101hxor eax,0 ; 影响PF标志call ToStringinvoke ExitProcess,0main ENDP
END main
SHL/SHR 逻辑移位: SHL常用于对目标操作数执行逻辑左移(无符号数)操作,其左移后最低位以0填充,而移动出去的最高位则会送入CF(进位标志)中,而SHR则相反,对目标操作数执行逻辑右移(无符号数)操作,移出的数据位用0代替,最低位被复制到CF(进位标志)中,原来的进位标志位丢失.
Intel处理器中定义,执行移位的源操作数的范围必须在0-255
之间,在任何处理器上都可以使用CL寄存器
存放移位位数,例如在下面的指令中,AL寄存器
被左移一位,最高位被复制到了进位标志中,最低位被清零:
01251006 | B3 8F | mov al,10001111b | AL = 10001111b
01251008 | D0E3 | shl al,1 | CF = 1,AL = 00011110b01251006 | B0 01 | mov al,10000000b | AL = 10000000b
01251008 | C0E0 02 | shl al,2 | CF = 0,AL = 00000000b01251006 | B0 01 | mov al,10000000b | AL = 10000000b
01251008 | C0E0 01 | shl al,1 | CF = 1,AL = 00000000b01251006 | B0 01 | mov al,10100000b | AL = 10100000b
01251008 | C0E0 03 | shl al,2 | CF = 0,AL = 10000000b
另外使用SHL指令还可以进行2的次幂的高速乘法
运算,任何操作数左移动N位,就相当于该操作数乘以2的N次方,如下例子:
01311002 | B0 05 | mov al,5 | AL 左移动1位
01311004 | D0E0 | shl al,1 | al * 2 = 1001311007 | B0 05 | mov al,5 | AL左移2位
01311009 | C0E0 02 | shl al,2 | al * 4 = 2001311007 | B0 05 | mov al,5 | AL左移3位
01311009 | C0E0 03 | shl al,3 | al * 8 = 40
下面是一个左移计算的案例,我们通过汇编来计算Rval = ((Xval + Yval) - (Yval + Zval)) * 8
的结果.
.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib.dataRval SDWORD ?Xval SDWORD 50Yval SDWORD 100Zval SDWORD 10
.codemain PROC; Rval = ((Xval + Yval) - (Yval + Zval)) * 8mov eax,dword ptr ds:[Xval]add eax,dword ptr ds:[Yval]mov ebx,dword ptr ds:[Yval]add ebx,dword ptr ds:[Zval]sub eax,ebx; 乘以8也就是左移3位 1=>2 2=>4 3=>8 4=>16 5=>32 6=>64 7=>128shl eax,3mov dword ptr ds:[Rval],eaxinvoke ExitProcess,0main ENDP
END main
对目标操作数执行SHR逻辑右移(无符号数)
操作,移出的数据位用0代替,最低位被复制到CF进位标志
中,原来的进位标志位丢失.
0131100D | B0 01 | mov al,10001111b | AL = 10001111b
0131100F | D0E8 | shr al,1 | CF = 1,AL = 01000111b0131100D | B0 01 | mov al,10001111b | AL = 10001111b
0131100F | D0E8 | shr al,2 | CF = 1,AL = 00100011b
另外任何无符号操作数逻辑右移N位,就相当于该操作数除以2的N次方
,如下例子:
01311012 | B2 20 | mov dl,20 | DL 右移1位
01311014 | D0EA | shr dl,1 | dl/2 = 1001311012 | B2 20 | mov dl,20 | DL 右移2位
01311014 | D0EA | shr dl,2 | dl/4 = 5
下面是一个右移计算的案例,我们通过汇编来计算Rval = (Xval / 8) + (Yval * 16) - (Zval * 4)
的结果.
.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib.dataRval SDWORD ?Xval SDWORD 200Yval SDWORD 50Zval SDWORD 10
.codemain PROC; Rval = (Xval / 8) + (Yval * 16) - (Zval * 4); 混合移位计算: 1=>2 2=>4 3=>8 4=>16 5=>32 6=>64 7=>128; 8=>256 9=>512 10=>1024 11=>2048 12=>4096 13=>8192 14=>16384; 先通过右移3位,计算除法mov eax,dword ptr ds:[Xval] ; Xval / 8shr eax,3; 再通过左移4位和2位分别计算乘法mov ebx,dword ptr ds:[Yval] ; Yval * 16shl ebx,4mov ecx,dword ptr ds:[Zval] ; Zval * 4shl ecx,2add eax,ebxsub eax,ecxmov dword ptr ds:[Rval],eaxinvoke ExitProcess,0main ENDP
END main
上面的这种计算方式属于乘数刚好是2的次幂,如果不是2的次幂则需要拆分后计算,如下案例,为了计算无符号乘以36,可以把36分解成2的5次方和2的2次方,然后利用移位命令高效计算.
.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib.dataRval SDWORD ?
.codemain PROC; 次方表: 1=>2 2=>4 3=>8 4=>16 5=>32 6=>64 7=>128; 次方表: 8=>256 9=>512 10=>1024 11=>2048 12=>4096 13=>8192 14=>16384; 计算 123 * 36; 等式拆分 EAX * 36 => EAX * (32 + 4) => (EAX * 32) + (EAX * 4)mov eax,123mov ebx,eax ; 拷贝出一份shl eax,5 ; 计算 (EAX * 32)shl ebx,2 ; 计算 (EAX * 4)add eax,ebx ; 最后相加mov dword ptr ds:[Rval],eax; 计算 123 * 24; 等式拆分: EAX * 24 => EAX * (16 + 8) => (EAX * 16) + (EAX * 8)mov eax,123mov ebx,eaxshl eax,4 ; 计算 (EAX * 16)shl ebx,3 ; 计算 (EAX * 8)add eax,ebxmov dword ptr ds:[Rval],eax; 计算 123 * 21; 等式拆分: EAX * 21 => EAX * (16 + 4 + 1) => (EAX * 16) + (EAX * 4) + (EAX * 1)mov eax,123mov ebx,eaxmov ecx,eax ; 得到 (EAX * 1)shl eax,4 ; 计算 (EAX * 16)shl ebx,2 ; 计算 (EAX * 4)add eax,ebxadd eax,ecxmov dword ptr ds:[Rval],eaxinvoke ExitProcess,0main ENDP
END main
下面是我通过寻找一些规律,能够在不查表的情况下逆推出其左移或者是右移中乘数或除数的具体值,如果比较复杂的话还是直接查表来的容易一些,此处只是一种思考方式.
.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib.dataRval DWORD ?
.codemain PROC; 次方表: 1=>2 2=>4 3=>8 4=>16 5=>32 6=>64 7=>128; 次方表: 8=>256 9=>512 10=>1024 11=>2048 12=>4096 13=>8192 14=>16384; 乘法逆推: 28/7=4mov eax,7 ; eax=7shl eax,2 ; 求0x2是乘以几,乘以4mov dword ptr ds:[Rval],eax ; eax = 28 计算出: 0x2 => 28/7=4; 乘法逆推: 96/6 = 16 => 4*4=16mov eax,6 ; eax = 6shl eax,2 ; 4shl eax,2 ; 4mov dword ptr ds:[Rval],eax ; eax = 96; 乘法逆推: 4*4*8mov eax,4 ; eax = 4shl eax,2 ; 运行到此处 eax=16 通过16/4 = 4 故乘以4shl eax,3 ; 运行到此处 eax =128 通过 128/4=32 , 32/4=8 故乘以8mov dword ptr ds:[Rval],eax; 除法逆推: 7/1.75 = 4mov eax,7 ; eax = 7shr eax,2 ; 此处乘以4mov dword ptr ds:[Rval],eax ; eax = 1.75 => 7/1.75=4invoke ExitProcess,0main ENDP
END main
SAL/SAR 算数移位: SAL指令与SHL指令等价,SAR指令可以对有符号数进行快速除以2的次幂操作,也可以将一个AX寄存器中的值进行扩展,扩展成EAX.
.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib.codemain PROC; SAR => 算数右移mov al,0f0h ; AL = 11110000b (-16)sar al,1 ; AL = 11111000b (-8); SAR => 有符号除法 计算-128的,2的3次方; 2次方 => -32 3次方 => -16 4次方 => -8 5次方 => -4mov eax,-128 ; AL = 10000000bsar eax,3 ; AL = 11110000b EAX = -16; SAR => 符号扩展AX扩展到EAX; 先左移EAX 16位,然后算术右移EAX 16位mov ax,-128 ; EAX = ????FF80hshl eax,16 ; EAX = FF800000hsar eax,16 ; EAX = FFFFFF80hinvoke ExitProcess,0main ENDP
END main
ROL/ROR 循环移位: ROL位循环左移,其左移1位后会把最高位同时复制到进位标志位和最低位中,而ROR则是循环右移,其右移1位后,把最低位同时复制到进位标志位和最高位中.
循环移位和普通移位不同之处在于前者并不会丢失任何数据位,从一端走的数据位会从另一端出现,如循环左移会将高位复制到低位中,循环右移则将低位复制到高位中,但需要注意不论是左移/右移,都是对二进制格式进行操作的.
.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib.codemain PROC; ROL => 循环左移mov al,40h ; AL = 01000000brol al,1 ; AL = 10000000b , CF = 0rol al,1 ; AL = 00000001b , CF = 1rol al,1 ; AL = 00000010b , CF = 0mov al,00100000brol al,3 ; AL = 00000001b , CF = 1; ROR => 循环右移mov al,01h ; AL = 00000001bror al,1 ; AL = 10000000b , CF = 1ror al,1 ; AL = 01000000b , CF = 0mov al,00000100bror al,3 ; AL = 10000000b , CF = 1; ROL => 循环左移,交换位组; 交换 高半部分(位4-7) 低半部分(位0-3)mov al,26hrol al,4 ; rol 与 ror 结果一致ror al,4invoke ExitProcess,0main ENDP
END main
RCL/RCR 标志移位: RCL指令在每位左移1位后,把CF进位标志复制到最低有效位中,最高有效位复制到进位标志中, RCR则相反,右移后把CF进位标志复制到最高有效位中,并把最低有效位复制到进位标志中.
.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib.codemain PROC; RCL 左移clc ; 将CF进位标志置0mov bl,88h ; CF = 0 , BL = 10001000brcl bl,1 ; CF = 1 , BL = 00010000brcl bl,1 ; CF = 0 , BL = 00100001b; RCR 右移stc ; 将CF进位标志置1mov ah,10h ; CF = 1 , ah = 00010000hrcr ah,1 ; CF = 1 , ah = 10001000hinvoke ExitProcess,0main ENDP
END main
让数组整体左移或右移.
.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib.dataRval SDWORD ?ArraySize = 3Array DWORD ArraySize DUP(99999999h)ByteArray BYTE 81h,20h,33hWordArray WORD 810dh,0c064h,93abh
.codemain PROC; 多双字同时右移mov esi,0shr Array[esi + 8],1 ; 高双字rcr Array[esi + 4],1 ; 中间双字rcr Array[esi],1 ; 低双字; 多双字同时左移mov esi,0shl Array[esi+8],1rcl Array[esi+4],1rcl Array[esi],1; 让数组整体右移 (从高字节到低字节)shr [ByteArray + 2],1rcr [ByteArray + 1],1rcr [ByteArray],1; 让数组整体左移 (从低字节到高字节)shl [ByteArray],1rcl [ByteArray + 2],1rcl [ByteArray + 4],1invoke ExitProcess,0main ENDP
END main
MUL/IMUL 乘法指令: MUL/IMUL分别可进行有符号与无符号的乘法运算,通常该指令都接受寄存器操作数,也接受内存操作数,但是不接受立即数,且乘数与被乘数大小必须相同,乘基尺寸是乘数/被乘数的两倍.
MUL乘法指令有三种格式: 第一种将8位操作数与AL相乘,结果放入AX中,第二种将16位操作数与AX相乘,结果的高16位放入DX低16位放入AX,第三种将32位操作数与EAX相乘,结果高32位放入EDX第32位放入EAX中.
.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib.dataRval DWORD ?VarWordA WORD 2000hVarWordB WORD 0100h
.codemain PROC; 执行8位乘法运算 al = al*blmov al,5hmov bl,10hmul blmov byte ptr ds:[Rval],al; 执行16位乘法运算xor eax,eaxxor edx,edxmov ax,word ptr ds:[VarWordA]mul word ptr ds:[VarWordB]mov word ptr ds:[Rval],ax ; 低半部分mov word ptr ds:[Rval],dx ; 高半部分 DX:AX = 00000020h; 执行32位乘法运算xor eax,eaxxor edx,edxmov eax,12345hmov ebx,1000hmul ebxmov dword ptr ds:[Rval],eax ; 低半部分mov dword ptr ds:[Rval],edx ; 高半部分 EDX:EAX = 0000000012345000hinvoke ExitProcess,0main ENDP
END main
IMUL指令主要用于执行有符号整数的乘法运算,并保留乘积的符号位,且在32位汇编中有三种格式:但操作数格式,双操作数格式,三操作数格式,首先是单操作数模式,该模式把乘积存储在累加器AX中,或者将符号位放入EDX将结果放入EAX中.
.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib.dataEDX_Rval DWORD ?EAX_Rval DWORD ?
.codemain PROC; 执行8位乘法运算: 48*8 得到的积+192 溢出xor eax,eaxmov al,48mov bl,4imul bl ; CF 进位 = 1 OF 溢出 = 1xor eax,eaxmov al,-4mov bl,4imul bl ; AX=FFF0h OF=0; 执行16位乘法运算: 48*4 得到的积 +192xor eax,eaxmov ax,48mov bx,4imul bxmov word ptr ds:[EDX_Rval],dxmov word ptr ds:[EAX_Rval],ax ; DX:AX = 000000C0h OF=0; 执行32位乘法运算: +4823424 *(-423)xor eax,eaxxor ebx,ebxmov eax,+4823424mov ebx,-423imul ebxmov dword ptr ds:[EDX_Rval],edx ; EDX为符号位mov dword ptr ds:[EAX_Rval],eax ; EDX:EAX = FFFFFFFF86635D80h OF=0invoke ExitProcess,0main ENDP
END main
接着就是乘法语句的双操作数与三操作数模式了,在双操作数中第一个操作数必须是寄存器,第二个操作数可以是寄存器或内存等,在三操作数模式中,把乘积存储在第一个操作数中,其他与双操作数类似.
.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib.datawordp SWORD 4dwordp SDWORD 4Rval DWORD ?
.codemain PROC; 双操作数乘法运算xor eax,eaxxor ebx,ebxmov ax,-16 ; ax = -16mov bx,2 ; bx = 2imul bx,ax ; bx = bx * aximul bx,2 ; bx = bx * 2imul bx,word ptr ds:[wordp] ; bx = bx * wordpmov word ptr ds:[Rval],bx ; 放入变量中保存; 三操作数乘法运算xor eax,eaxxor ebx,ebximul bx,wordp,-16 ; bx = wordp * -16imul ebx,dwordp,-16 ; ebx = dwordp * -16imul ebx,dwordp,-20 ; ebx = dwordp * -20mov dword ptr ds:[Rval],ebx ; 放入变量中invoke ExitProcess,0main ENDP
END main
到此为止我们学会了通过移位的方式实现快速乘法
运算,也学过使用MUL指令进行乘法计算,接下来我们可以编写两个案例分别通过移位和MUL计算EAX与36相乘的结果,看看哪一个效率更高一些.
.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib.codeshl_proc procmov ecx,10s:push eaxmov ebx,eaxshl eax,5shl eax,2add eax,ebxpop eaxloop sretshl_proc endpmul_proc procmov ecx,10s:push eaxmov ebx,36mul ebxpop eaxloop sretmul_proc endpmain PROCmov eax,10call shl_procmov eax,10call mul_procinvoke ExitProcess,0main ENDP
END main
DIV/IDIV 除法指令: DIV是无符号除法指令,该指令支持8/16/32位无符号整数的除法运算,指令中唯的寄存器或内存操作数是除数,IDIV则是有符号除法指令,该指令与无符号除法几乎一致,唯一的不同在于有符号除法在进行相除操作时需要符号扩展.
首先我们先来学习一下DIV无符号除法运算的使用技巧,分别演示8/16/32位无符号除法的使用方式.
.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib.dataRval DWORD ?DivIdend QWORD 0000000800300020hDivIsor DWORD 00000100hDiv_Eax DWORD ?Div_Edx DWORD ?
.codemain PROC; 执行8位除法: 83/2 商al是41h 余数ah是1xor eax,eaxmov ax,0083h ; 被除数mov bl,2 ; 除数div bl ; ax = ax / blmov byte ptr ds:[Rval],ah ; ah = 01hmov byte ptr ds:[Rval],al ; al = 41h; 执行16位除法: 8003h/100h 商是80h 余数是3xor edx,edx ; 清除edx寄存器mov ax,8003h ; 被除数mov cx,100h ; 除数div cx ; ax = ax / cxmov word ptr ds:[Rval],ax ; ax = 0080hmov word ptr ds:[Rval],dx ; dx = 0003h; 执行32位除法mov edx,dword ptr DivIdend + 4 ; 高双字mov eax,dword ptr DivIdend ; 低双字div DivIsor ; 与被除数相除mov dword ptr ds:[Div_Eax],eax ; eax = 08003000hmov dword ptr ds:[Div_Edx],edx ; edx = 00000020hinvoke ExitProcess,0main ENDP
END main
针对IDIV有符号数的除法运算,需要对被除数进行除法操作之前,对其进行符号扩展,汇编中有三条扩展命令.
CBW 指令 将字节符号扩展至字,扩展AL的符号位至AH中,保留了数字的符号.
CWD 指令 将字符号扩展至双字,指令扩展AX的符号位至DX中.
CDQ 指令 双字符号扩展至八字节,指令扩展EAX的符号位至EDX中.
当使用符号扩展指令扩展后,寄存器就可以被用来计算有符号除法了,代码如下所示:
.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib.dataByteVal SBYTE -48WordVal SWORD -5000DworeVal SDWORD +50000Rval DWORD ?
.codemain PROC; 字节扩展至字 -48/5xor eax,eaxmov al,byte ptr ds:[ByteVal] ; al = D0 取出 -48cbw ; ax = FFD0 将al扩展至axmov bl,5 ; bl = 05idiv bl ; ax=ax/blmov word ptr ds:[Rval],ax ; 结果: ax = FDF7mov byte ptr ds:[Rval],al ; AL保存商 -9mov byte ptr ds:[Rval],ah ; AH保存余数 -3; 字扩展至双字xor eax,eaxmov ax,word ptr ds:[WordVal] ; 除数cwd ; 扩展至双字(扩展AX至DX)mov bx,+256 ; 被除数idiv bx ; ax = ax/bxmov word ptr ds:[Rval],ax ; 商AX=-19mov word ptr ds:[Rval],dx ; 余数DX=-136; 双字符号扩展至八字节mov eax,dword ptr ds:[DworeVal]cdq ; 扩展EAX到EDXmov ebx,-256idiv ebxmov dword ptr ds:[Rval],eax ; 商 EAX = -195mov dword ptr ds:[Rval],edx ; 余数 EDX = +80invoke ExitProcess,0main ENDP
END main
学习了前面的这几种计算方式以后,我们就可以将其总结起来实现计算复杂的表达式了,先来三个练手的.
.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib.dataRval DWORD ?var1 DWORD 10var2 DWORD 15var3 DWORD 20var4 DWORD 25
.codemain PROC; 实现计算: var4 = (var1 * 5) / (var2 - 3)mov eax,dword ptr ds:[var1] ; 先计算左边 (var1 * 5)mov ebx,5mul dword ptr ds:[ebx] ; EDX:EAX 乘积mov ebx,dword ptr ds:[var2] ; 计算右边 (var2 - 3)sub ebx,3div dword ptr ds:[ebx] ; 计算两者的除法mov dword ptr ds:[var4],eax ; 最后赋值操作; 实现计算: var4 = (var1+var2) * var3mov eax,dword ptr ds:[var1]add eax,dword ptr ds:[var2] ; 计算前半部分mov ebx,dword ptr ds:[var3] ; 计算后半部分mul ebxmov dword ptr ds:[var4],eax ; 最后赋值操作; 实现计算: var1 = (var2/var3) * (var1+var2)mov eax,var2cdq ; 扩展为EDX:EAXidiv dword ptr ds:[var3] ; 计算除法,存入eaxmov ebx,dword ptr ds:[var1] ; 计算加法add ebx,dword ptr ds:[var2]imul dword ptr ds:[ebx] ; 最后计算乘法mov dword ptr ds:[var1],eax ; 在eax中取值invoke ExitProcess,0main ENDP
END main
最后我们来实现一个相对复杂的案例,总体的复习一下,该案例计算var4 = (var1 * -5) / (-var2 % var3)
返回值,我们可以从右边开始计算,并把右边的值存储到EBX中,然后把被除数符号扩展到EDX,最后使用IDIV计算除法.
.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib.dataRval DWORD ?var1 DWORD 54var2 DWORD 56var3 DWORD 5var4 DWORD 52
.codemain PROC; 实现计算: var4 = (var1 * -5) / (-var2 % var3)mov eax,dword ptr ds:[var2]neg eax ; 将var2反转cdq ; 将被除数符号扩展idiv var3 ; 除以var3 则 EDX=余数mov ebx,edx ; 将余数给EBXmov eax,-5imul var1 ; 计算 var1 * -5 结果给EAXidiv ebx ; eax = eax/ebxmov dword ptr ds:[var4],eax ; 最后将结果给var4invoke ExitProcess,0main ENDP
END main
ADC/SBB 扩展加减法: 扩展加减法是指任意尺寸大小数字的加减法,其中ADC指令主要用户实现带进位加法,SBB指令则实现带进位减法,起作用都是将源操作数与目的操作数以及进位等相加减.
以扩展加法为例,计算两个8位整数相加(FFh+FFh)
产生的16位结果将被存放在DL:AL (01feh)
中,如果是计算两个32位整数相加(FFFFFFFFh+FFFFFFFFh)
,则会在EDX和EAX中分别存放00000001h:FFFFFFFEh
这两个值,扩展SBB同理.
.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include kernel32.inc
includelib kernel32.lib.dataRval DWORD ?
.codemain PROC; 计算8位加法操作xor edx,edxxor eax,eaxmov dl,0 ; 清0mov al,0ffh ; 设置加数add al,0ffh ; al = al + 0ffhadc dl,0 ; 进位加法mov byte ptr ds:[Rval],dl ; 存放高位mov byte ptr ds:[Rval],al ; 存放低位; 计算32位加法操作xor edx,edxxor eax,eaxmov edx,0mov eax,0ffffffffhadd eax,0ffffffffhadc edx,0mov dword ptr ds:[Rval],edx ; 存放高位mov dword ptr ds:[Rval],eax ; 存放低位; 计算32位减法操作xor edx,edxxor eax,eaxmov edx,1 ; 设置高半部分mov eax,0 ; 设置低半部分sub eax,1 ; eax减去1sbb edx,0 ; 减去1则高半部分为0mov dword ptr ds:[Rval],edx ; 存放高位mov dword ptr ds:[Rval],eax ; 存放低位invoke ExitProcess,0main ENDP
END main
LEA指令计算: Lea指令的使用初衷是取出某个内存的地址,但在汇编手册中可以发现其不止可以取地址同样可以实现算数运算,但这个运算与移位运算符一样只能计算2的次幂,当需要计算一个非次幂数字是,则需要对其进行分析与拆分,来实现对算数的计算.
.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.libinclude msvcrt.inc
includelib msvcrt.lib.datax DWORD ?y DWORD ?szFmt BYTE '计算结果: %d',0dh,0ah,0
.codemain PROC; 针对加法的lea指令优化mov dword ptr ds:[x],5mov dword ptr ds:[y],3mov eax,dword ptr ds:[x]mov ebx,dword ptr ds:[y]lea eax,dword ptr ds:[eax + 3] ; eax = edx + 3invoke crt_printf,addr szFmt,eaxlea eax,dword ptr ds:[eax + ebx + 2] ; eax = eax + ebx + 2invoke crt_printf,addr szFmt,eax; 针对减法的lea指令优化mov dword ptr ds:[x],6mov eax,dword ptr ds:[x]lea eax,dword ptr ds:[eax - 2] ; eax = eax - 2invoke crt_printf,addr szFmt,eax; 针对乘法的lea指令优化mov dword ptr ds:[x],5mov dword ptr ds:[y],3mov eax,dword ptr ds:[x]xor ebx,ebxlea ebx,dword ptr ds:[eax * 8 + 2] ; ebx = eax * 8 + 2invoke crt_printf,addr szFmt,ebx; 如果使用lea计算乘法,则乘数必须是2/4/8mov eax,dword ptr ds:[y] ; eax = 3 => 计算 15 * eaxlea edx,dword ptr ds:[eax * 4 + eax] ; edx = 4eax + eax => 5eaxlea edx,dword ptr ds:[edx * 2 + edx] ; edx = 5eax * 2 + 5eax => 15eaxinvoke crt_printf,addr szFmt,edx ; edx = eax * 15 = 45; 如果计算乘法时乘数非2的次幂,则此时需要减mov eax,dword ptr ds:[y] ; eax = 3 => 计算 eax * 7 + 10lea edx,dword ptr ds:[eax * 8] ; edx = eax * 8sub edx,eax ; edx = edx - 1eaxadd edx,10 ; edx = edx + 10invoke crt_printf,addr szFmt,edx ; edx = eax * 7 + 10mov eax,dword ptr ds:[y] ; eax = 3 => 计算 eax * 3 - 7lea edx,dword ptr ds:[eax * 2] ; edx = eax * 2add edx,eax ; edx = edx + eaxsub edx,7 ; edx = edx - 7invoke crt_printf,addr szFmt,edx ; edx = eax * 3 - 7invoke ExitProcess,0main ENDP
END main
除法变乘法: 相比较于乘法运算,除法运算则显得略微复杂些,当计算中被除数为正数时,则可以直接使用sar(算数右移)
快速计算除法,如果被除数为负数,则需要使用cdq
符号扩展后,然后and edx,xxx
之后才能进行相除运算.
.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.libinclude msvcrt.inc
includelib msvcrt.lib.datax DWORD ?y DWORD ?z DWORD ?szFmt BYTE '计算结果: %d',0dh,0ah,0
.codemain PROCmov dword ptr ds:[x],5mov dword ptr ds:[y],-3mov dword ptr ds:[z],-10; 除数为2的优化方式; 被除数为正数(无需扩展): eax => 5 / 2 = 2mov eax,dword ptr ds:[x] ; 被除数sar eax,1 ; 算数右移invoke crt_printf,addr szFmt,eax; 被除数为负数(需要扩展): eax => -3 / 2 = -1mov eax,dword ptr ds:[y] ; 被除数cdq ; 符号扩展sub eax,edx ; 被除数减去edx符号扩展sar eax,1 ; 算数右移invoke crt_printf,addr szFmt,eax; 除数为4的优化方式; 被除数为正数(无需扩展): eax => 5 / 4 = 1mov eax,dword ptr ds:[x]sar eax,2invoke crt_printf,addr szFmt,eax; 被除数为负数(需要扩展): eax => -10 / 4 = -2; 抽取出公式来: (x + y - 1) / ymov eax,dword ptr ds:[z] ; eax = xcdqand edx,3 ; edx = y-1add eax,edx ; eax = eax + edx => x + (y - 1)sar eax,2 ; 2 => yinvoke crt_printf,addr szFmt,eax; 除数为8的优化方式; 被除数为正数(无需扩展): eax => 5 / 8 = 1mov eax,dword ptr ds:[x]sar eax,3invoke crt_printf,addr szFmt,eax; 被除数为负数(需要扩展): eax => -10 / 8 = -1mov eax,dword ptr ds:[z]cdqand edx,7add eax,edxsar eax,3invoke crt_printf,addr szFmt,eaxinvoke ExitProcess,0main ENDP
END main
除法中的除数与被除数都可以分为有符号与无符号,两种计算方式均有一定差异,其差异如下所示.
.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.libinclude msvcrt.inc
includelib msvcrt.lib.datax DWORD ?y DWORD ?z DWORD ?szFmt BYTE '计算结果: %d',0dh,0ah,0
.codemain PROCmov dword ptr ds:[x],5mov dword ptr ds:[y],10mov dword ptr ds:[z],-10; 除数为(无符号)正2的次幂的计算过程mov eax,dword ptr ds:[x]shr eax,1 ; eax = 5 / 2mov eax,dword ptr ds:[x]shr eax,2 ; eax = 5 / 4mov eax,dword ptr ds:[x]shr eax,3 ; eax = 5 / 8; 被除数为(有符号)的计算过程mov eax,dword ptr ds:[z]cdqsub eax,edxsar eax,1 ; eax = -10 / 2;neg eax ; 将eax取反invoke crt_printf,addr szFmt,eaxmov eax,dword ptr ds:[z]cdqand edx,3add eax,edxsar eax,2 ; eax = -10 / 4;neg eaxinvoke crt_printf,addr szFmt,eaxmov eax,dword ptr ds:[z]cdqand edx,7add eax,edxsar eax,3 ; eax = -10 / 8;neg eaxinvoke crt_printf,addr szFmt,eax; 除数为(有符号)负2的次幂的计算过程mov eax,dword ptr ds:[y] ; y = 10cdqsub eax,edxsar eax,1 ; eax = 10 / -2neg eax ; 将正数 eax翻转为负数 = -5invoke crt_printf,addr szFmt,eaxmov eax,dword ptr ds:[y] ; y = 10cdqand edx,3add eax,edxsar eax,2 ; eax = 10 / -4neg eax ; eax = -2invoke crt_printf,addr szFmt,eaxmov eax,dword ptr ds:[z] ; z = -10 cdqand edx,7add eax,edxsar eax,3 ; eax = -10 / -8 neg eax ; eax = 1 (负负得正)invoke crt_printf,addr szFmt,eaxinvoke ExitProcess,0main ENDP
END main
上方的除法运算被除数均为2的次幂,除数的范围也被限定在了2/4/8
这样的范围之内,如果是计算非2的次幂该怎么写呢,如下是计算非2的次幂的计算方式,通常情况下编译器会将除法运算转换为乘法,如果需要知道除数是多少则可以使用公式2^(32+n) / M
计算后得出.
.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.libinclude msvcrt.inc
includelib msvcrt.lib.datax DWORD ?y DWORD ?z DWORD ?szFmt BYTE '计算结果: %d',0dh,0ah,0
.codemain PROCmov dword ptr ds:[x],5mov dword ptr ds:[y],10mov dword ptr ds:[z],-10; 除法(有符号)非2的幂转换为乘法mov ecx,dword ptr ds:[y] ; 被除数 ecx = 10 / 3 = 3mov eax,055555556h ; eax = M值 1431655766imul ecxmov eax,edx ; edx = n 计算: 2^(32+n) / Mshr eax,01fh ; 计算出除数为 2.9999 => 3add edx,eaxinvoke crt_printf,addr szFmt,edxmov ecx,dword ptr ds:[y] ; ecx = 10 / 5 = 2mov eax,066666667h ; 此处的M模值是编译器计算后得到的imul ecxsar edx,1 ; 想要知道除数是多少,只需要mov eax,edx ; 2^(32 + edx) / M = 2^33 / 66666667 = 4.999shr eax,01fhadd edx,eaxinvoke crt_printf,addr szFmt,edxmov ecx,dword ptr ds:[y] ; ecx = 10 / 6 = 1mov eax,02AAAAAABh ; eax = 715827883imul ecxmov eax,edx ; 2^(32 + edx) / M = 2^32 / 2AAAAAAB = 6shr eax,01fhadd edx,eaxinvoke crt_printf,addr szFmt,edxmov ecx,dword ptr ds:[y] ; ecx = 10 / 9 = 1mov eax,038E38E39h ; eax = 954437177 imul ecxsar edx,1 ; 2^(32 + edx) / M = 2^33 / 38E38E39 = 9mov ecx,edxshr ecx,01fhadd edx,ecxinvoke crt_printf,addr szFmt,edxinvoke ExitProcess,0main ENDP
END main
上方代码中的除法计算是针对有符号数进行的,如果是针对无符号数
则需要以下方式计算.
.386p.model flat,stdcalloption casemap:noneinclude windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.libinclude msvcrt.inc
includelib msvcrt.lib.datax DWORD ?y DWORD ?z DWORD ?szFmt BYTE '计算结果: %d',0dh,0ah,0
.codemain PROCmov dword ptr ds:[x],-5mov dword ptr ds:[y],10mov dword ptr ds:[z],20; 除法(无符号)非2的次幂(正数)转换为乘法xor edx,edxmov ecx,dword ptr ds:[y] ; ecx = 10mov eax,0AAAAAAABh ; ecx / 3 = 3mul ecxshr edx,1invoke crt_printf,addr szFmt,edx; 还原除数: 2 ^(32 + n) / M => 2 ^ (32+2) / 0CCCCCCCDh = 5xor edx,edxmov ecx,dword ptr ds:[y] ; ecx = 10 => 计算: 10/5mov eax,0CCCCCCCDh ; eax = Mmul ecxshr edx,2 ; edx= ninvoke crt_printf,addr szFmt,edx; 还原除数: 2 ^(32 + n) / M => 2 ^ (32+2) / 0AAAAAAABh = 6xor edx,edxmov ecx,dword ptr ds:[y] ; ecx = 10 => 计算:10/6mov eax,0AAAAAAABh ; eax = Mmul ecxshr edx,2 ; edx = ninvoke crt_printf,addr szFmt,edx;还原除数: 2 ^(32 + n) / M => 2 ^ 33 / 038E38E39h = 9xor edx,edxmov ecx,dword ptr ds:[z] ; ecx = 20 => 计算: 20/9mov eax,038E38E39h ; eax = Mmul ecxshr edx,1 ; edx = ninvoke crt_printf,addr szFmt,edx; 除法(无符号)非2的次幂(负数)转换为乘法; 还原除数: 2 ^(32 + n) / M => 2 ^ 33 / 0AAAAAAABh = nge(3) => -3xor edx,edxmov ecx,dword ptr ds:[z] ; ecx = 20 => 计算: 20/-3mov eax,0AAAAAAABh ; eax = Mmul ecxshr edx,1 ; edx = n neg edx ; edx=6 结果neg取反invoke crt_printf,addr szFmt,edx; 还原除数: 2 ^(32 + n) / M => 2 ^ 63 / 080000003h = nge(5) => -5xor edx,edxmov ecx,dword ptr ds:[x] ; ecx = -5 => 计算: -5 / -5mov eax,080000003h ; eax = Mmul ecxshr edx,01fh ; edx = ninvoke crt_printf,addr szFmt,edxinvoke ExitProcess,0main ENDP
END main
Win32汇编:算数运算指令总结相关推荐
- 【Win32汇编】学习Win32汇编
学习Win32汇编(Windows下的32位汇编) 第一个程序Hello world 控制台输出(新建Console App工程) Debug输出(新建Win32 App工程) 后面代码的头文件在这里 ...
- 微机原理——指令系统——算数运算指令(ADD、ADC、SUB、SBB、INC、DEC、NEG、CMP、MUL、IMUL、DIV、IDIV、CBW、CWD、BCD调整)
博主联系方式: QQ:1540984562 QQ交流群:892023501 群里会有往届的smarters和电赛选手,群里也会不时分享一些有用的资料,有问题可以在群里多问问. 算数运算指令 1.加减法 ...
- 【win32汇编】0x01 开篇一些乱七八糟的话
之前弄完了16位汇编,现在正式学习win32汇编,首先就是一些原理了,其实就是在16位的基础上增加了一些内容,其程序主要的结构记录如下 (1)指令集 .386 这是指定指令集 必须工作 ...
- Win32汇编_基础
Win32汇编_基础 包含全部段的源程序结构: .386 .model flat, stdcall Option casemap:none ;<一些include语句> .stack [堆 ...
- 选择“Win32汇编”的三大理由?
选择Win32汇编的理由是什么呢? 在DOS时代,学习汇编就是学习系统底层编程的代名词,仅要成为一名入门级的汇编程序员,就需要学习从CPU结构.CPU工作方式.各种硬件的编程方法到DOS工作方式等范围 ...
- win32汇编实现拼接SQL语句
字符串合并,在汇编语言,一般是用loop循环和cx寄存器,自己编程实现: 如果是win32汇编,可以使用movsb指令: 一般开发应用程序都会碰到拼接SQL语句,在C#这些语言用字符串连接的加号就可以 ...
- Win32汇编获取和设置文本框的内容
看一下Win32汇编如何获取和设置文本框的内容: 资源文件如下: #include <resource.h>#define ICO_MAIN 0x1000 //图标 #define DLG ...
- Win32汇编基本编程框架
Win32汇编编程框架如下: .386 .model flat,stdcall option casemap:none <一些include语句> .stack [堆栈段的大小] .dat ...
- Win32 汇编环境和入门程序图解
一 安装 在安装的最后,执行VBS脚本,在桌面建立快捷方式:执行快捷方式,会调入Win32汇编的编辑器qeditor.exe,进入到开发环境 二 构建Hello World程序 ;.386是伪指令 告 ...
- Win32 汇编语句模板
Win32 汇编语句模板 一 变量 ;句柄 hInstance dd 0 hWnd dd 0 hPen dd 0 hPend dd 0 oldPen dd 0;过程变量 hInst :DWORD hP ...
最新文章
- 什么是Tomcat响应静态资源?
- 【并查集】【图论】旅行(ssl 1312)
- JDK源码解析之 java.lang.Thread
- 如何测试软件的性能瓶颈,性能测试如何定位瓶颈
- 图像特效之浮雕与雕刻
- Linux的Sysfs文件系统简要说明
- 第二部分 python基础 day10\11\12 运算符与基本数据类型
- 操作系统笔记(含王道计算机考研——操作系统课件)
- 彻底修改 Windows 系统用户名
- timeroc 最优最佳截断值特异度(specificity)与灵敏度(sensitivity)
- 微信小程序:we重师
- MTK LK阶段 display框架
- 第11章:使用Apriori算法进行关联分析(从频繁项集中挖掘关联规则)
- chmod 777 授权无效
- Python 取模运算(取余)%误区及详解
- Docker搭建、启动、关闭MySQL服务
- 微信公众号签到,签到后在活动大屏中实时展示签到人信息,也可以导出签到人信息用于抽奖
- M41ST85W_3.0/3.3 V I²C组合实时时钟、NVRAM监控器和微处理器监控——科时进商城
- 数据结构第二次实验-赫夫曼编码及其应用
- 【文献阅读4】Position-Based User-Centric Radio Resource Management in 5G UDN for URLLC Vehicular Commu