本章介绍了各种表达式的汇编形式以及汇编时代码优化的方法,如遇到数学模型无法理解,可跳过,后续将有结论;
运行环境:
操作系统: Windows 7家庭版
编译器:VC6 VS2013
Debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。Release 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用。 在VC++6.0中常用的优化方案有O1方案(生成文件占用空间最小,空间上),O2方案(执行效率最快,时间上),Release通常默认O2模式,Debug使用Od+ZI选项。
表达式 SIG 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 md %1_objs copy %1.lib %1_objs pushd %1_objs for /f %%i in ('link -lib /list %1.lib') do link -lib /extract:%%i %1.lib for %%i in (*.obj) do pcf -g0 %%i sigmake -n"%1.lib" *.pat %1.sig if exist %1.exc for %%i in (%1.exc) do find /v ";" %%i > abc.exc if exist %1.exc for %%i in (%1.exc) do > abc.exc more +2 "%%i" copy abc.exc %1.exc del abc.exc sigmake -n"%1.lib" *.pat %1.sig copy %1.sig ..\%1.sig popd del %1_objs /s /q rd %1_objs
可直接提取符号,但是需要自己找到LIBC.LIB,然后将提取的SIG放入IDA的SIG文件中;
算数运算和赋值 各种算数工作形式 1)加法
加法对应的汇编指令为ADD。针对不同的操作数,转换的指令也不同,编译器会自动优化;
常见的方式为:变量+变量、变量+常量、常量+常量;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 9: /*变量定义*/ 10: int nVarOne = 0; 00401028 mov dword ptr [ebp-4],0 11: int nVarTwo = 0; 0040102F mov dword ptr [ebp-8],0 12: /*变量加常量*/ 13: nVarOne = nVarOne + 1; 00401036 mov eax,dword ptr [ebp-4] 00401039 add eax,1 0040103C mov dword ptr [ebp-4],eax 14: /*常量加常量*/ 15: nVarOne = 1 + 2; 0040103F mov dword ptr [ebp-4],3 ;常量加常量在编译期间直接给出结果 16: /*变量加变量*/ 17: nVarOne = nVarTwo + nVarOne; 00401046 mov ecx,dword ptr [ebp-8] 00401049 add ecx,dword ptr [ebp-4] 0040104C mov dword ptr [ebp-4],ecx
当编译选项修改为Release后的汇编代码:
常量传递
将编译期间可计算出结果的变量转换为常量,这样就减少了变量的使用;
1 2 3 4 5 6 7 8 int main () { int nVar = 1 ; printf ("nVar = %d\n" ,nVar); printf ("nVar = %d\n" ,1 ); system("pause" ); return 0 ; }
2)减法
减法运算对应的汇编指令SUB,虽然计算机只会做加法,但是可以通过补码的形式将 减法转换为加法来计算;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 29: /*变量定义*/ 30: int nVarOne = argc; 0040F2A8 mov eax,dword ptr [ebp+8] 0040F2AB mov dword ptr [ebp-4],eax 31: int nVarTwo = 0; 0040F2AE mov dword ptr [ebp-8],0 32: /*获取变量nVarOne的数据,防止scanf变量被常量化*/ 33: scanf("%d",&nVarTwo); 0040F2B5 lea ecx,[ebp-8] 0040F2B8 push ecx 0040F2B9 push offset string "%d" (004250d4) 0040F2BE call scanf (004114f0) 0040F2C3 add esp,8 34: //常量减变量的减法运算 35: nVarOne = nVarOne - 100; 0040F2C6 mov edx,dword ptr [ebp-4] 0040F2C9 sub edx,64h 0040F2CC mov dword ptr [ebp-4],edx 36: //加法与减法混合运算 37: nVarOne = nVarOne + 5 - nVarTwo; 0040F2CF mov eax,dword ptr [ebp-4] 0040F2D2 add eax,5 0040F2D5 sub eax,dword ptr [ebp-8] 0040F2D8 mov dword ptr [ebp-4],eax
3)乘法
乘法运算对应的汇编指令有imul(有符号)和mul(无符号)两种;由于乘法指令周期较长,在编译过程中,编译器首先会尝试将乘法转换为加法或位移等指令周期较短的指令;
Debug版本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 48: /*变量定义*/ 49: int nVarOne = argc; 00401178 mov eax,dword ptr [ebp+8] 0040117B mov dword ptr [ebp-4],eax 50: int nVarTwo = argc; 0040117E mov ecx,dword ptr [ebp+8] 00401181 mov dword ptr [ebp-8],ecx 51: 52: printf("nVarOne * 15 = %d\n",nVarOne * 15); 00401184 mov edx,dword ptr [ebp-4] 00401187 imul edx,edx,0Fh //使用有符号乘法,imul结果存入edx中 0040118A push edx 0040118B push offset string "nVarOne * 15 = %d\n" (0042709c) 00401190 call printf (00401350) 00401195 add esp,8 53: 54: 55: 56: printf("nVarOne * 16 = %d\n",nVarOne * 16); 00401198 mov eax,dword ptr [ebp-4] 0040119B shl eax,4 //左移操作符代替乘法运算 0040119E push eax 0040119F push offset string "nVarOne * 16 = %d\n" (00427084) 004011A4 call printf (00401350) 004011A9 add esp,8 57: 58: 59: printf("2 * 2 = %d\n",2 * 2); 004011AC push 4 //常量做运算直接在编译时得到结果 004011AE push offset string "2 * 2 = %d\n" (00427074) 004011B3 call printf (00401350) 004011B8 add esp,8 60: 61: 62: printf("nVarOne * 4 + 5 = %d\n",nVarOne * 4 + 5); 004011BB mov ecx,dword ptr [ebp-4] 004011BE lea edx,[ecx*4+5] //使用lea指令完成组合运算 004011C5 push edx 004011C6 push offset string "nVarOne * 4 + 5 = %d\n" (00427058) 004011CB call printf (00401350) 004011D0 add esp,8 63: 64: printf("nVarOne * nVarTwo = %d\n",nVarOne * nVarTwo); 004011D3 mov eax,dword ptr [ebp-4] 004011D6 imul eax,dword ptr [ebp-8] //直接使用有符号乘法 004011DA push eax 004011DB push offset string "nVarOne * nVarTwo = %d\n" (0042703c) 004011E0 call printf (00401350) 004011E5 add esp,8
Release版本:
4)除法
除法算数约定
除法运算对应的汇编指令一般为div和idiv两种。除法指令周期较长,效率也很低,所以编译器会在编译期间用其他指令代替除法指令;
向下取整
根据整数的取值范围,如3.5向下取整即为3,-3.5向下取整则为-4
向上取整
向零取整
符号是由除数决定
Debug版本的除法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 .text:00401010 _main_0 proc near ; CODE XREF: _main↑j .text:00401010 .text:00401010 var_48 = byte ptr -48h .text:00401010 var_8 = dword ptr -8 .text:00401010 var_4 = dword ptr -4 .text:00401010 arg_0 = dword ptr 8 .text:00401010 .text:00401010 push ebp .text:00401011 mov ebp, esp .text:00401013 sub esp, 48h .text:00401016 push ebx .text:00401017 push esi .text:00401018 push edi .text:00401019 lea edi, [ebp+var_48] .text:0040101C mov ecx, 12h .text:00401021 mov eax, 0CCCCCCCCh .text:00401026 rep stosd .text:00401028 mov eax, [ebp+arg_0] .text:0040102B mov [ebp+var_4], eax .text:0040102E mov ecx, [ebp+arg_0] .text:00401031 mov [ebp+var_8], ecx .text:00401034 mov eax, [ebp+var_4] .text:00401037 cdq .text:00401038 idiv [ebp+var_8] ; 两个变量做除法,可以直接使用除法指令idiv .text:0040103B push eax .text:0040103C push offset aNvaroneNvartwo ; "nVarOne / nVarTwo = %d\n" .text:00401041 call _printf .text:00401046 add esp, 8 .text:00401049 mov eax, [ebp+var_4] .text:0040104C cdq .text:0040104D sub eax, edx ; 自身减去扩展的高位 .text:0040104F sar eax, 1 ; 右移指令,2的1次幂 .text:00401051 push eax .text:00401052 push offset aNvarone2D ; "nVarOne / 2 = %d\n" .text:00401057 call _printf .text:0040105C add esp, 8 .text:0040105F mov eax, [ebp+var_8] .text:00401062 cdq .text:00401063 mov ecx, 7 .text:00401068 idiv ecx ; 无优化,直接除以常量7 .text:0040106A push eax .text:0040106B push offset aNvartwo7D ; "nVarTwo / 7 = %d\n" .text:00401070 call _printf .text:00401075 add esp, 8 .text:00401078 mov eax, [ebp+var_8] .text:0040107B cdq .text:0040107C mov ecx, 7 .text:00401081 idiv ecx ; 无优化,直接使用常量7 .text:00401083 push edx .text:00401084 push offset aNvarone7D ; "nVarOne % 7 = %d\n" .text:00401089 call _printf .text:0040108E add esp, 8 .text:00401091 mov eax, [ebp+var_4] .text:00401094 cdq .text:00401095 and edx, 7 ; 扩展EAX高位到EDX,EAX中为负数,则EDX为0xFFFFFFFF .text:00401098 add eax, edx ; 使用EAX加EDX,若EAX为负数则加7 .text:0040109A sar eax, 3 ; 右移2的3次幂 .text:0040109D push eax .text:0040109E push offset aNvarone8D ; "nVarOne / 8 = %d\n" .text:004010A3 call _printf .text:004010A8 add esp, 8 .text:004010AB xor eax, eax .text:004010AD pop edi .text:004010AE pop esi .text:004010AF pop ebx .text:004010B0 add esp, 48h .text:004010B3 cmp ebp, esp .text:004010B5 call __chkesp .text:004010BA mov esp, ebp .text:004010BC pop ebp .text:004010BD retn .text:004010BD _main_0 endp .text:004010BD
Release版本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 .text:00401000 _main proc near ; CODE XREF: start+AF↓p .text:00401000 .text:00401000 argc = dword ptr 4 .text:00401000 argv = dword ptr 8 .text:00401000 envp = dword ptr 0Ch .text:00401000 .text:00401000 push esi .text:00401001 mov esi, [esp+4+argc] .text:00401005 mov eax, esi .text:00401007 cdq .text:00401008 idiv esi .text:0040100A push eax .text:0040100B push offset aNvaroneNvartwo ; "nVarOne / nVarTwo = %d\n" .text:00401010 call sub_401080 .text:00401015 mov eax, esi .text:00401017 cdq .text:00401018 sub eax, edx .text:0040101A sar eax, 1 .text:0040101C push eax .text:0040101D push offset aNvarone2D ; "nVarOne / 2 = %d\n" .text:00401022 call sub_401080 .text:00401027 mov eax, 92492493h .text:0040102C imul esi .text:0040102E add edx, esi .text:00401030 sar edx, 2 ;右移两位 .text:00401033 mov eax, edx .text:00401035 shr eax, 1Fh ;右移31次 .text:00401038 add edx, eax .text:0040103A push edx .text:0040103B push offset aNvartwo7D ; "nVarTwo / 7 = %d\n" .text:00401040 call sub_401080 .text:00401045 mov eax, esi .text:00401047 mov ecx, 7 .text:0040104C cdq .text:0040104D idiv ecx .text:0040104F push edx .text:00401050 push offset aNvarone7D ; "nVarOne % 7 = %d\n" .text:00401055 call sub_401080 .text:0040105A mov eax, esi .text:0040105C cdq .text:0040105D and edx, 7 .text:00401060 add eax, edx .text:00401062 sar eax, 3 .text:00401065 push eax .text:00401066 push offset aNvarone8D ; "nVarOne / 8 = %d\n" .text:0040106B call sub_401080 .text:00401070 add esp, 28h .text:00401073 xor eax, eax .text:00401075 pop esi .text:00401076 retn .text:00401076 _main endp .text:00401060 add eax, edx .text:00401062 sar eax, 3 .text:00401065 push eax .text:00401066 push offset aNvarone8D ; "nVarOne / 8 = %d\n" .text:0040106B call sub_401080 .text:00401070 add esp, 28h .text:00401073 xor eax, eax .text:00401075 pop esi .text:00401076 retn .text:00401076 _main endp .text:00401076
5)取模
无符号数模2的幂 还原定式:and reg,2^n - 1
1 2 3 4 5 高级代码: printf("%d\n", argc % 8) 汇编代码: and reg 7
无符号数模非2的幂 VC++6.0没有优化,VS2013开始release版本做了优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 r:余数 a:被除数 q:商 b:除数 r = a - qb 汇编指令: ;计算商部分 mov eax, m mul esi mov ecx, esi sub ecx, edx shr ecx, n1 add ecx, edx shr ecx, 2 ;商乘除数部分 lea eax, [ecx * 8] //argc / 7 = argc * 8 - argc sub eax, ecx ;被除数 - 商乘除数得到余数 sub esi, eax
有符号模非2的幂 和无符号模非2的幂定式一样,区别在于计算商部分改为有符号的除法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 汇编指令: ;计算商部分 mov eax, m imul esi add edx, esi sar edx, n mov ecx, edx shr ecx, 1Fh add ecx, edx ;商乘除数部分 lea eax, [ecx * 8] sub eax, ecx ;被除数 - 商乘除数得到余数 sub esi, eax
有符号模2的幂
先取被除数的符号为和低3位(and), 判断最高位是否为1(正数)
如果是正数,走正常流程,
如果是负数,or修正数值(除最高位和低3位以外填符号位)
1 2 3 4 5 6 7 mov ebp, esp mov eax, [ebp + arg_0] and eax, 80000007h jns offset dec eax or eax, 0fffffff8h dinc eax
有符号模-2的幂 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 数学模型: 此处A为变量,C为常量 当:C < 0时 有:A % C 可以:|A| % |C| = R 所以:当A < 0, 取 -R 定式 mov eax, argc cdq xor eax, edx sub eax, edx ;等于把eax取绝对值 and argc-1 ;等于把eax取模 xor eax, edx ;等于对eax取反 sub eax, edx ;等于eax+1 解决方法: and 之后的参数取反+1 abs(eax) ;如果eax是正数,edx = 0 ;如果eax是负数,edx = 1 cdq ;eax为正数,eax xor 0 = eax ;eax为负数,eax xor ffffffff = not eax xor eax, edx ;eax为正数,eax - 0 = eax ;eax为正数,eax - ffffffff = eax + 1 sub eax, edx 结论: 如果eax为正数,eax的值不变 如果eax为负数,eax的值取反+1 A xor 0 = A A xor 1 = not A
如果模的除数为变量,即没有优化空间,指令为div
总结 加减乘看指令
变量乘法使用imul,多操作数
优化乘法使用lea和shl组合
除法
除数为常量的除法必优化
除数为变量的除法,idiv和div,可以据此推断出有无符号
除数为2的幂 无符号除以2的幂 使用 shr 指令
有符号除以2的幂 正除数 1 2 3 4 cdq and edx, 2^n - 1 add eax, edx sar eax, n
负除数 1 2 3 4 5 cdq and edx, 2^n - 1 add eax, edx sar eax, n neg eax
除数为非2的幂 原型:
1 2 设MagicNumber = 2^n / C A / C = A * M >> n
无符号除以非2的幂 M无进位
1 2 3 4 mov reg, A mov eax, M mul reg shr edx, n
M有进位
还原除数:C = 2^(n + 1) / (2^32 + M)
1 2 3 4 5 6 7 mov reg, A mov eax, M mul reg sub reg, edx shr edx, 1 add reg, edx shr edx, n
有符号除以非2的幂 M为正数
1 2 3 4 5 6 mov eax, M imul reg/men ;被除数 sar edx, n mov reg, edx shr reg, 31 add edx, reg
除数为负数还原除数 |C| = 2^n / neg(M)
1 2 3 4 5 6 7 mov eax, M imul reg/men ;被除数 sub edx, reg/men sar edx, n mov reg, edx shr reg, 31 add edx, reg
M为负数
除数为负还原除数 |C| = 2^n / neg(M)
计算器计算~M时先Dword计算16进制取反+1,得到的16进制值转Qword转十进制
1 2 3 4 5 6 mov eax, M imul reg/men ;被除数 sar edx, n mov reg, edx shr reg, 31 add edx, reg
1 2 3 4 5 6 7 mov eax, M imul reg/mem ;被除数 add edx, reg/men sar edx, n mov reg, edx shr reg, 31 add edx, reg
取模
无优化取模看div/idiv,其后使用edx,可以据此推断出有无符号
无符号取模 除数为2的幂
除数为非2的幂 r:余数 a:被除数 q:商 b:除数
低版本编译器无优化
高版本使用 r = a - qb 的数学原型求得余数
有符号取模 除数为2的幂 除数为正数 1 2 3 4 5 and reg, 80000xxxh jns xx dec reg and reg, fffffyyyh inc reg
xx:看xxx二进制有多少个连续的1,或者看yyy有多少个连续的0,其个数就是2^n中的指数n
除数为负数
1 2 3 4 5 6 cdq xor eax, edx sub eax, edx and eax, 2^n - 1 xor eax, edx sub eax, edx
除数为非2的幂
低版本无优化,根据除法指令还原
高版本使用 r = a - qb 的数学模型求得余数
条件表达式
表达式1 ? 表达式2 : 表达式3
当表达式2或表达式3不为常量,无优化,直接编译为分支结构,酌情还原
当表达式1为等值比较,表达式2和3为常量时,优化原型为:
1 2 3 eax = 0 ? 0 0 : -1 neg eax sbb eax, eax
当表达式1扩展为非0等值比较,表达式2和表达式3扩展为其他任意常量时:
1 2 3 4 5 6 7 A == B ? C : 0 mov reg, A sub reg, B neg reg sbb reg, reg and reg, D - C add reg, C
当表达式1扩展为区间比较,表达式2和3扩展为其他任意常量时:
低版本使用setxx
系列指令,dec以后构造出一个0或者-1的值,然后思路同上例
高版本使用cmovxx
系列指令,不存在位运算技巧,直接分析cmovxx
条件还原即可
三目运算 无优化版 产生分支和if else产生的汇编代码相同 如果返回的都是变量则无优化
1 2 无优化版高级代码: argc?100 argc*3:argc*-3
恒等优化版 取消分支
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 比较还原为 == 0 例: 高级代码: eax == 100 ? 0 : -1; 汇编代码: sub eax, 100 neg eax sbb eax, eax 高级代码: eax == 0 ? 0 : -1; 汇编代码: neg eax sbb eax, eax neg reg ; reg = 0 - reg sbb r1, r2 r1 = r1 - r2 - CF 立即数优化版 例: 高级代码: eax == 100 ?39 : 93; 汇编代码: sub eax, 100 neg eax sbb eax, eax ;if eax == 100, eax = 0, else eax = ffffffffh and eax, 93 - 39 ;接上面, eax = 0, else eax = 93-39 add eax, 39 ;接上面, eax = 39, else eax = 39 + 93 - 39 = 93
非恒等式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 使用set命令做比较 setg / setng 例: 高级代码: eax > 100 ? 39 : 93; 汇编代码: xor ecx, ecx cmp eax, 100 方式1: setng cl ;if eax > 100, cl = 0, else ecx = 0 dec ecx ;...接上面, ecx = ffffffffh, else ecx = 0 and ecx, 39 - 93 add ecx, 93 方式2: setg cl dec ecx and ecx, 93 - 39 add ecx, 39 高级编译器新增指令: cmovxx 解决方法: