首页
社区
课程
招聘
[原创]抠门的Release版本对代码动了哪些手脚?
发表于: 2012-11-14 21:57 6572

[原创]抠门的Release版本对代码动了哪些手脚?

2012-11-14 21:57
6572
     Debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。
     Release 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用。
     相信不少朋友用C++开发程序时,用debug版本可以运行,但用Release版本就不行了,或者倒过来。根本原因就是Release版本优化的问题,如果用Release版本调试会看到“面目全非”的反汇编代码,而debug版本没有做任何优化,代码与汇编能一一对上。
     本人菜鸟一枚,抛砖引玉举几个例子,在代码调试的角度看看Release对代码做了什么手脚,为了篇幅下面只贴上Release版本反汇编代码,读者可以自己运行看看Debug版本的。
     欢迎补充!
/*********************************
    运行和调试环境:VS2010 + win 7
    程序类型:控制台
    配置:默认
/*********************************
1.  变量优化------国家穷,能省就省
#include "stdio.h"
void main(){
  int intA;
  int intB;
  char ch[5]={0};
  char *pCh=ch;

  pCh+=11111;//数组越界
  *pCh='q';//内存非法写入数据
  
  intA=100;
  scanf("%d\n",&intB);
  printf("%d\n",intA);
  return;
}

      上面的代码在两个版本中编译通过,但在Release中可以运行,但在Debug中运行出错。先提一个问题,Release版本中有多少个变量会分配栈空间?

--------------Release版本反汇编----------------
#include "stdio.h"
void main(){
002A1000  push        ebp  
002A1001  mov         ebp,esp  
002A1003  sub         esp,8  //栈顶抬高8个字节,即分配8字节空间
002A1006  mov         eax,dword ptr [___security_cookie (2A3000h)]  
002A100B  xor         eax,ebp  
002A100D  mov         dword ptr [ebp-4],eax  //4个字节GS栈保护security_cookie
  int intA; //优化掉
  int intB;
  char ch[5]={0};//优化掉
  char *pCh=ch; //优化掉
  pCh+=11111; //优化掉
  *pCh='q'; //优化掉
  intA=100; //优化掉
  scanf("%d\n",&intB);
002A1010  lea         eax,[ebp-8] 
002A1013  push        eax  //可以看出为intB分分配4字节空间
002A1014  push        offset string "%d\n" (2A20F4h)  
002A1019  call        dword ptr [__imp__scanf (2A20A4h)]  
  printf("%d\n",intA);
002A101F  push        64h  //立即数,intA被优化掉了
002A1021  push        offset string "%d\n" (2A20F4h)  
002A1026  call        dword ptr [__imp__printf (2A209Ch)]  
  return;
}

      相信熟悉汇编的朋友一看就知道只有intB分配栈空间。intA即使有运算操作,也优化掉了,ch[5],*pCh因为优化掉,所以不存在数组越界访问的问题。
      Release认为国家穷能省就省,所以把一个函数看成一个黑盒,在对外面的输入输出不影响的情况下,能省就省,越简单越好。

2.  If语句的优化变换----变形金刚,变身只为更强大
#include "stdio.h"
void main(){
  int intA;
  int intB;
  scanf("%d",&intB);
  if (intB)
  {
    intA=13;
  }else intA=16;
  printf("%d",intA);
}

   if语句中判断真假依赖intB,所以intA的值不能确定,因而printf("%d",intA)的输出在编译时也不能确定。我想,这样编译器会给可怜的intA一个栈空间吧?结果我错了,编译器很抠门,把intA优化掉了。下面看看编译器怎么做的。假定这里输入为5,即让intB=5。

 --- ------- Release版本反汇编--------------------
#include "stdio.h"
void main(){
01361000  push        ebp  
01361001  mov         ebp,esp  
01361003  push        ecx  
  int intA;
  int intB;
  scanf("%d",&intB);
01361004  lea         eax,[intB]    //intB变量栈空间,这里相当lea eax,dword ptr[ebp-4]
01361007  push        eax  
01361008  push        offset string "%d" (13620F4h)  
0136100D  call        dword ptr [__imp__scanf (13620A4h)]  
  if (intB)         //if语句,有木有发现,怎么没有cmp,jmp这些明显判断语句?往下看
01361013  mov         eax,dword ptr [intB]     //取出intB的值,下面假设输入intB为5
01361016  neg         eax    //求反,影响标志位CF,如果intB非零,CF=1,否则为0。这里CF=1
01361018  sbb         eax,eax   //带位相减。由于CF=1,所以eax=0xFFFFFFFF  
0136101A  and         eax,FFFFFFFDh    //与运算后eax=0xFFFFFFFD
0136101D  add         eax,10h     //溢出,结果为eax=0xD(十进制13),所以说0xFFFFFFFD相当于-3
  {
    intA=13; //<-----------13
  }else intA=16;//
  printf("%d",intA);
01361020  push        eax  
01361021  push        offset string "%d" (13620F4h)  
01361026  call        dword ptr [__imp__printf (136209Ch)]  
0136102C  add         esp,10h  
}

     可以看出If语句优化后的执行效率很高,运算基本上都是在寄存器上进行的,没有cmp,jmp语句,没有分配额外的栈空间,寻址用立即数。
3.  函数合并----联合起来为了利益最大化
#include "stdio.h"
void fun(int a,char ch[]){
  printf("Integer:%d  String:%s",a,ch);
}
void main(){
  int intA;
  char ch[5]="abcd";
  intA=15;
  fun(intA,ch);
}

      当然在main中intA被优化掉了,参数传递用立即数,但数组ch[ ]没有被优化掉。这里有两个函数,所以在反汇编中应该能看到call fun这样的语句吧?答案是否定的。
 --- ------- Release版本反汇编--------------------
void main(){
01361000  push        ebp  
01361001  mov         ebp,esp  
01361003  sub         esp,0Ch    //栈顶抬高12个字节
01361006  mov         eax,dword ptr [___security_cookie (1363000h)]  
0136100B  xor         eax,ebp  
0136100D  mov         dword ptr [ebp-4],eax    //用了4个字节
  int intA;
  char ch[5]="abcd";
01361010  mov         eax,dword ptr [string "abcd" (136210Ch)] 
01361015  mov         cl,byte ptr ds:[1362110h]    //两句传送字符串”abcd\0”到寄存器
  intA=15;
  fun(intA,ch);
0136101B  lea         edx,[ebp-0Ch] //
0136101E  push        edx    //数组地址,ch[]
0136101F  push        0Fh    //立即数参数,15
01361021  push        offset string "Integer:%d String:%s" (13620F4h)  
01361026  mov         dword ptr [ebp-0Ch],eax  
01361029  mov         byte ptr [ebp-8],cl    //两句语句表示”abcd\0”写入栈中
0136102C  call        dword ptr [__imp__printf (13620A0h)]  //调用printf
}

      main与fun函数已经被编译器神不知鬼不觉地联合起来相当一个函数,这样做的好处是可以节省分配栈空间,也没有函数间的参数传递,提高了不少效率,一条裤子两个人穿。这样子是不是说可以任意地联合函数?不是的,当时fun()里面的语句复杂到一定的程度就不会发生联合,我认为如果合并可能会花费更大代价,也体现不了代码模块化。所以合不合并有一个平衡点。
     把fun函数改成这样:
void fun(int *a,char ch[]){
  printf("Integer:%d String:%s",*a,ch);
  printf("Integer:%d String:%s",*a,ch);
  printf("Integer:%d String:%s",*a,ch);
  printf("Integer:%d String:%s",*a,ch);
  printf("Integer:%d String:%s",*a,ch);
  printf("Integer:%d String:%s",*a,ch);
  printf("Integer:%d String:%s",*a,ch);
  printf("Integer:%d String:%s",*a,ch);
  printf("Integer:%d String:%s",*a,ch);
}

Call fun出来了:
                  .
                  .
                  .  
013C10A9  mov         dword ptr [ebp-10h],0Fh  
013C10B0  call        fun (13C1000h) 
                  .
                  .
                  . 

4.  断言assert()-----100%相信断言
     这个比较简单,就是Release版本编译时忽略所有断言语句,相信它们是对的。
#include "stdio.h"
#include<assert.h>
void main(){
  int intA=0;
  assert(intA==1);//Release版本会优化掉
  printf("I can run!");
}

在Release可以运行,但在Debug运行出错!读者可以自己试下!

后记: Debug与Release两个运行效果不同,原因基本上是自己写的代码有BUG,所以首先不应骂编译器,遇到问题应该多多调试才能找出原因,同时也会了解更多软件底层的知识。
有点长,总算写好了,欢迎大鸟多多提意见!

[课程]FART 脱壳王!加量不加价!FART作者讲授!

收藏
免费 6
支持
分享
最新回复 (9)
雪    币: 66
活跃值: (49)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
2
学习了 谢谢楼主分享
2012-11-14 22:02
0
雪    币: 27
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
不错不错,继续努力
2012-11-14 22:19
0
雪    币: 31
活跃值: (43)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
4
看来也是经常吃这种亏的人。
2012-11-15 00:22
0
雪    币: 326
活跃值: (41)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
5
我是来看楼上头像的。表示为什么 我看它 它不会动?
2012-11-15 09:19
0
雪    币: 1760
活跃值: (1616)
能力值: ( LV12,RANK:222 )
在线值:
发帖
回帖
粉丝
6
眨眼睛就动了
2012-11-15 09:32
0
雪    币: 297
活跃值: (235)
能力值: ( LV4,RANK:55 )
在线值:
发帖
回帖
粉丝
7
编译器编译选项不同 vc6就不会优化那些了
2012-11-15 09:55
0
雪    币: 64
活跃值: (40)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
8
不同編譯器的優化細節是不同的,
windows平臺下VC的優化是最詳細的
2012-11-15 21:36
0
雪    币: 239
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
mark~学习学习
2012-11-15 22:45
0
雪    币: 202
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
make~谢谢分享
2012-11-16 01:11
0
游客
登录 | 注册 方可回帖
返回
//