首页
社区
课程
招聘
[原创]编译器优化分析-2
发表于: 2006-7-10 22:11 12683

[原创]编译器优化分析-2

2006-7-10 22:11
12683

DFCGVC编译器优化分析-2
今天讲讲常见的算术运算的优化:
1.        先来看看加减法运算
__asm int 3
                int a,b;
        scanf("%d %d",&a,&b);
        int c;
        int d;
        c=a+b;
        d=a-b;
        printf("\n%d %d",c,d);
        __asm int 3
汇编代码如下
00401020   /$  83EC 08            sub esp,8
00401023   |.  CC                 int3
00401024   |.  8D4424 04          lea eax,dword ptr ss:[esp+4]
00401028   |.  50                 push eax
00401029   |.  8D4C24 04          lea ecx,dword ptr ss:[esp+4]
0040102D   |.  51                 push ecx
0040102E   |.  68 50214000        push 编译器学.00402150                                       ; /format = "%d %d"
00401033   |.  FF15 C4204000      call dword ptr ds:[<&MSVCR80.scanf>]                     ; \scanf
00401039   |.  8B4424 0C          mov eax,dword ptr ss:[esp+C]
0040103D   |.  8B4C24 10          mov ecx,dword ptr ss:[esp+10]
00401041   |.  8BD0               mov edx,eax
00401043   |.  2BD1               sub edx,ecx
00401045   |.  52                 push edx                                                 ; /<%d>
00401046   |.  03C1               add eax,ecx                                              ; |
00401048   |.  50                 push eax                                                 ; |<%d>
00401049   |.  68 58214000        push 编译器学.00402158                                       ; |format = "
%d %d"
0040104E   |.  FF15 C8204000      call dword ptr ds:[<&MSVCR80.printf>]                    ; \printf
00401054   |.  83C4 18            add esp,18
00401057   |.  CC                 int3

虽然我们声明了4个变量。但编译器会认为这等价于
printf("\n%d %d",a+b,c+d);
因此也只给我们分配了两个变量。
其实这也告诉编程序的人,没有必要写成printf("\n%d %d",a+b,c+d);
形式,这种形式显然更难维护和阅读。
00401039   |.  8B4424 0C          mov eax,dword ptr ss:[esp+C]
0040103D   |.  8B4C24 10          mov ecx,dword ptr ss:[esp+10]
00401041   |.  8BD0               mov edx,eax
00401043   |.  2BD1               sub edx,ecx
00401045   |.  52                 push edx                                                 ; /<%d>
00401046   |.  03C1               add eax,ecx                                              ; |
这里比较巧妙由于涉及到两次运算,因此将两个变量统一读到两个寄存器中,而不象有些编译器会使用内存相加。
加减法没有什么好说的。主要说乘除法:
2.        乘除法
先来看乘法运算:
首先是有符号运算。
void study5()
        {
        __asm int 3
                int a,b;
        scanf("%d %d",&a,&b);
        int c,d,e;
        c=a*2;
        d=a*b;
        e=a*7;
        printf("\n%d %d",c,d,e);
        __asm int 3       
}
00401023   |.  CC                 int3
00401024   |.  8D4424 04          lea eax,dword ptr ss:[esp+4]
00401028   |.  50                 push eax
00401029   |.  8D4C24 04          lea ecx,dword ptr ss:[esp+4]
0040102D   |.  51                 push ecx
0040102E   |.  68 50214000        push 编译器学.00402150                                 ; /format = "%d %d"
00401033   |.  FF15 C4204000      call dword ptr ds:[<&MSVCR80.scanf>]               ; \scanf
00401039   |.  8B4424 0C          mov eax,dword ptr ss:[esp+C]
0040103D   |.  8BC8               mov ecx,eax
0040103F   |.  0FAF4C24 10        imul ecx,dword ptr ss:[esp+10]
00401044   |.  8D14C5 00000000    lea edx,dword ptr ds:[eax*8]
0040104B   |.  2BD0               sub edx,eax
0040104D   |.  52                 push edx
0040104E   |.  51                 push ecx                                           ; /<%d>
0040104F   |.  8D1400             lea edx,dword ptr ds:[eax+eax]                     ; |
00401052   |.  52                 push edx                                           ; |<%d>
00401053   |.  68 58214000        push 编译器学.00402158                                 ; |format = "
%d %d"
00401058   |.  FF15 C8204000      call dword ptr ds:[<&MSVCR80.printf>]              ; \printf
0040105E   |.  83C4 1C            add esp,1C
00401061   |.  CC                 int3

0040103F   |.  0FAF4C24 10        imul ecx,dword ptr ss:[esp+10]
没有什么说的,整数乘法
00401044   |.  8D14C5 00000000    lea edx,dword ptr ds:[eax*8]
0040104B   |.  2BD0               sub edx,eax
呵呵。看到这里是否想到我们小学的时候得简便运算。我们是乘以7
但是编译器给我们编程了乘以8在减掉被乘数。神奇吧。
同样的道理
0040104F   |.  8D1400             lea edx,dword ptr ds:[eax+eax]                     ; |
乘以二就是两个这个数相加。。想象力丰富吧。
总结一下载VC中乘法如果在已知被除数的情况下用
lea edx,dword ptr ds:[eax*x]的形式,
否则采用IMUL 指令。
现在我们把代码变成这样
c=a*-2;
        d=a*-4;
        e=a*-7;
测试一下有符号运算。
00401033   |.  FF15 C4204000      call dword ptr ds:[<&MSVCR80.scanf>]               ; \scanf
00401039   |.  8B4424 0C          mov eax,dword ptr ss:[esp+C]
0040103D   |.  8D14C5 00000000    lea edx,dword ptr ds:[eax*8]
00401044   |.  8BC8               mov ecx,eax
00401046   |.  2BD0               sub edx,eax
00401048   |.  F7D9               neg ecx
0040104A   |.  F7DA               neg edx
0040104C   |.  52                 push edx
0040104D   |.  03C9               add ecx,ecx
0040104F   |.  03C9               add ecx,ecx
00401051   |.  F7D8               neg eax
00401053   |.  51                 push ecx                                           ; /<%d>
00401054   |.  03C0               add eax,eax                                        ; |
00401056   |.  50                 push eax                                           ; |<%d>
00401057   |.  68 58214000        push 编译器学.00402158                                 ; |format = "
%d %d"
0040105C   |.  FF15 C8204000      call dword ptr ds:[<&MSVCR80.printf>]              ; \printf

代码有些变化 除了用了NEG指令以外。还有
原来的
lea edx,dword ptr ds:[eax+eax]                     ; |
变成了
0040104D   |.  03C9               add ecx,ecx
0040104F   |.  03C9               add ecx,ecx

不过大体上没有什么好说的。
乘法总的来说都比较好识别
代码在改一改 改为
        d=short(a*b);
        c=c*c*c;
        e=a*100;
我们来看看类型转换的问题
其他的都差不多主要说一下
0040104E   |.  0FBFD1             movsx edx,cx
00401051   |.  8BC8               mov ecx,eax
00401053   |.  0FAFC8             imul ecx,eax
00401056   |.  0FAFC8             imul ecx,eax
如果是short 那么就会用movsx符号取得低位
c=c*c*c---〉imul ecx,eax;imul ecx,eax
乘法总的来说都比较容易识别也比较简单。
除法就不一样了,无符号有符号完全不同,另外以及不同编译器的处理也很不一样
且涉及到求模运算和除法运算两种。
由于CPU中只有加法器和乘法器 所以一般而言除法会转变为乘法运算。
我们先来看两组最简单的。
先讲无符号的明天再说有符号的
代码:
        __asm int 3
        unsigned        int a,b;
        scanf("%d %d",&a,&b);
        unsigned int c,d,e;
        d=a/2;
        c=b/4;
        printf("\n%d %d",c,d);
        __asm int 3
生成代码(部分)
00401039   |.  8B5424 0C            mov edx,dword ptr ss:[esp+C]
0040103D   |.  8B4424 10            mov eax,dword ptr ss:[esp+10]
00401041   |.  D1EA                 shr edx,1
00401043   |.  52                   push edx                                     ; /<%d>
00401044   |.  C1E8 02              shr eax,2                                    ; |
当乘以2的n次方的时候可以采用位移的方式。
Shr 向右移
其实就是一个乘方关系不是很难理解,但是要大家注意,不要看着shr就想着用 〉〉指令可读性肯定没有用/??好
但是如果是求模式不同的
看代码:
d=a%2;
        c=b%4;
会变为
00401039   |.  8B5424 0C            mov edx,dword ptr ss:[esp+C]
0040103D   |.  8B4424 10            mov eax,dword ptr ss:[esp+10]
00401041   |.  83E2 01              and edx,1
00401044   |.  52                   push edx                                     ; /<%d>
00401045   |.  83E0 03              and eax,3                                    ; |
00401048   |.  50                   push eax                                     ; |<%d>
00401049   |.  68 58214000          push 编译器学.00402158                           ; |format = "
%d %d"
即为AND运算。其实也比较好理解
我们随便举个例子:
比如17的二进制代码为        10001
如果是除以2那么就是AND     00001
留下最后一位,
如果是除以4那么就是AND     00011
余下最后两位。其实我们想象成10进制就好理解了
比如12345 mod 10 肯定就是5 也就是保留个位
12345 mod 100 肯定就是45 保留十位
二进制是同样一个道理当是2的N次方的时候就和10的N 次方的时候是一样的
也就是去掉 N位的前面所有位就是余数
这一点希望大家注意 。很多时候为什么我们在写注册机的时候往往会出现很多位操作原因就是我们对这些理解不是很到位。而且位操作也不便于反退注册过程。
今天就讲到这里

除法部分比较复杂。

谢 谢
   By Fox


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 7
支持
分享
最新回复 (12)
雪    币: 146
活跃值: (33)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
2
这东西要顶,虽然还不是很复杂,但却属于上乘武功之挥刀自宫式
2006-7-11 00:07
0
雪    币: 223
活跃值: (25)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
3
2006-7-11 11:28
0
雪    币: 152
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
谢谢了。收下学习之!
2006-7-11 21:16
0
雪    币: 461
活跃值: (93)
能力值: ( LV9,RANK:1170 )
在线值:
发帖
回帖
粉丝
5
好文章,楼主辛苦了,收藏,学习.
2006-7-11 21:43
0
雪    币: 47147
活跃值: (20455)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
6
foxabu文章不错,感觉可以再深化一些。
2006-7-11 21:51
0
雪    币: 339
活跃值: (1510)
能力值: ( LV13,RANK:970 )
在线值:
发帖
回帖
粉丝
7
期待你那个除法,一年来,除法部分我一直理解不了,期待你给我讲明白。
2006-7-11 22:48
0
雪    币: 179
活跃值: (131)
能力值: ( LV12,RANK:290 )
在线值:
发帖
回帖
粉丝
8
楼主研究的透彻
不过好像对做编译器的有用
2006-7-11 23:31
0
雪    币: 338
活跃值: (10)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
9
学习一下.
2006-7-11 23:44
0
雪    币: 47147
活跃值: (20455)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
10
最初由 WAKU 发布
楼主研究的透彻
不过好像对做编译器的有用


呵~对逆向破解也很有用的。
2006-7-12 09:11
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
好功夫,,,,,,,学习学习
2006-7-12 19:47
0
雪    币: 221
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
nop
12
对于最后说的求余优化
楼主举的是无符号数的例子,

如果是有符号数,
例如eax%256 (eax是个有符号数)
编译器优化后代码是这样:
and        eax, 800000ffh
jns        L_A
dec        eax
or        eax, 0ffffff00h
inc        eax
L_A:
; do something ...

2007-1-27 01:10
0
雪    币: 709
活跃值: (2420)
能力值: ( LV12,RANK:1010 )
在线值:
发帖
回帖
粉丝
13
思路蛮好的,
借鉴一下!
2007-1-27 08:06
0
游客
登录 | 注册 方可回帖
返回
//