//
// 备注:本篇文章来自 vc驿站:http://www.cctry.com/thread-254205-1-1.html
// C、C++、VC++ 各种学习资源,免费教程,期待您的加入!
//
对于VC++的Debug模式与Release模式区别的简要介绍
QQ:446989572
看到很多像我一样的新手,对于Debug模式和Release模式区分不好
或者只是有着模糊的认识
所以,简单的写了这个分析,希望能帮助这些朋友加强区分。
另外声明本人也只是小菜一个,如果有什么说的不对的地方,还希望大家给我指出
共同交流,一起进步
一说到Debug模式,与Release模式的区别,可能大家最先想到:编程的时候使用Debug,
而完成了检验了没有错误后发行使用Release。总体上来说这是没错的,但是为什么会这样呢?
或者说两个模式有什么样的特性才让大家有了这样的认识呢?
首先从Debug模式的设计理念上来说明,Debug顾名思义就是调试的意思。
就是编译器为了方便使用者调试自己的程序而设计的模式
在工程的设置中可以看到,Debug和Release的很多设值、开关都不同
其实Debug最早在DOS编程的时候,作为唯一的调试方式广为大家熟知
Debug的基本原理,就是调用操作系统提供的调试接口,通过设下中断的方式
让程序代码执行后马上将控制权移交回Debug器手里并暂时中断
就处于这么运行、暂停、运行、暂停。。。的状态下
从而达到动态的查看数值,控制程序走向的目的
既然是调用系统的函数,那么我们就可以通过相关API检查自己程序的运行状态是否正在被调试
比如最简单的:函数原型为
BOOL WINAPI IsDebuggerPresent(void);
如果当前进程运行在一个用户调试器环境下,返回非零(也就是True)
否则返回0
这也是反调试技术的最初级形态和核心
(当然一个程序在被调试的时候,N多的标示值都会随之改变,所以获知自己是否被调试的方法也是多种多样,这里不再做过多说明)
那么,Debug模式究竟是怎么与Release区别以方便我们调试和维护呢?
主要区别在于检查和优化
我们先来说说检查:
可能我们在刚开始学习C语言的时候,听到过这么个理论:
当我们申请一个变量,但是并不给它初始化的时候,这个变量的值是随机的
这主要是因为CPU工作方式和内存读写方式决定的:
在变量销毁后,其值并没有消失,而是依然留在内存中,直到一个新的写入将它覆盖。
所以,我们得到的变量,在内存空间中的位置可能以前就被使用过,所以得到的值也是未知的。
上述理论并没有错误,然而,在VC++的Debug模式下,这个理论暂时不适用
为了增强程序的健壮性与容错率,Debug模式的编译器会在未初始化的变量申请后,自行的初始化它们,
值就是0xcc(一个字节)
所以我们使用如下代码:
int a;
得到的a值是固定的,a=0xCCCCCCCC;
下面给出一段简单的代码证实:
1
2
3
4
5
6
7
|
int
_tmain(
int
argc, _TCHAR* argv[])
{
int
a;
int
b;
b=0x1234abcf;
//只是便于在汇编代码中查找的常量赋值
return
0;
}
|
下面是Debug版的汇编代码:
_tmain(void)
{
00E01370 > 55 push ebp ; 常规的函数入口操作
00E01371 8BEC mov ebp,esp
00E01373 81EC D8000000 sub esp,0xD8 ; 这个值很大,为什么呢?
00E01379 53 push ebx
00E0137A 56 push esi
00E0137B 57 push edi
00E0137C 8DBD 28FFFFFF lea edi,dword ptr ss:[ebp-0xD8] ; 从edi开始,循环0x36次,填入0xCC
00E01382 B9 36000000 mov ecx,0x36
00E01387 B8 CCCCCCCC mov eax,0xCCCCCCCC
00E0138C F3:AB rep stos dword ptr es:[edi]
00E0138E C745 EC CFAB341>mov dword ptr ss:[ebp-0x14],0x1234ABCF ; b=0x1234ABCF
00E01395 33C0 xor eax,eax
00E01397 5F pop edi
00E01398 5E pop esi
00E01399 5B pop ebx
00E0139A 8BE5 mov esp,ebp
00E0139C 5D pop ebp
00E0139D C3 retn
好,从上面的汇编代码我们不难看出,不止变量a,b,0xD8这样大的空间都被赋值0xCC了
而编译器自作主张,将栈顶抬高一个明显我们程序用不到的空间高度(0xD8),目的就是加强程序的健壮性和可调试性
也是为了我们好。。。所以Debug版的程序无论大小还是运行速度都与Release版的有差别,原因就在这里了
那么,有的人就有了疑问,0xCC是什么?为什么要赋值为0xCC?为什么不是0xBB或者0xFF?
原因很简单,0xCC 对应的OpCode(汇编操作码)为int 3
当然这里的int不是那个C语言中的整数类型Integer,是种普通的中断
int 3就是我们调试器常用的中断
所以我们就能看出,这样做以后,如果程序因为某些错误(通常都是因为指针越界)导致程序代码执行
(也就是EIP)到了不该去的地方
这满屏幕的int 3就能马上将程序中断下来,而不至于导致严重的错误。
其实,上下翻动看看,我们这个Main函数就好像int 3海洋中的一个小孤岛——————早已被满屏的int 3包围了
这样大家就不难理解Debug模式的强大之处了吧~~
好吧。。。实际上,Debug编译器为我们做的,远比我们看到的多得多
比如,在我们的正常函数调用中,你会发现:
首先,是C++代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
int
func(
int
a,
int
b)
{
int
temp;
temp=a+b;
return
temp;
}
int
_tmain(
int
argc, _TCHAR* argv[])
{
int
a;
int
b;
a=0x123;
b=0x1234abcf;
int
AddNum=func(a,b);
printf
(
"%d"
,AddNum);
return
0;
}
|
只是简单的加法,奇异的数字只是为了便于寻找代码定位
好了,下面贴出部分汇编代码:
009217C0 > 55 push ebp
009217C1 8BEC mov ebp,esp
009217C3 81EC E4000000 sub esp,0xE4
009217C9 53 push ebx
009217CA 56 push esi
009217CB 57 push edi
009217CC 8DBD 1CFFFFFF lea edi,dword ptr ss:[ebp-0xE4] ; 这些地方没什么变化
009217D2 B9 39000000 mov ecx,0x39
009217D7 B8 CCCCCCCC mov eax,0xCCCCCCCC
009217DC F3:AB rep stos dword ptr es:[edi]
009217DE C745 F8 2301000>mov dword ptr ss:[ebp-0x8],0x123
009217E5 C745 EC CFAB341>mov dword ptr ss:[ebp-0x14],0x1234ABCF
009217EC 8B45 EC mov eax,dword ptr ss:[ebp-0x14]
009217EF 50 push eax
009217F0 8B4D F8 mov ecx,dword ptr ss:[ebp-0x8]
009217F3 51 push ecx
009217F4 E8 BFF9FFFF call Just_A_T.009211B8
执行加法 :
009217F9 83C4 08 add esp,0x8
009217FC 8945 E0 mov dword ptr ss:[ebp-0x20],eax
009217FF 8BF4 mov esi,esp
00921801 8B45 E0 mov eax,dword ptr ss:[ebp-0x20]
00921804 50 push eax
00921805 68 3C579200 push offset Just_A_T.??_C@_02DPKJAMEF@?$>; ASCII "%d"
0092180A FF15 B4829200 call dword ptr ds:[<&MSVCR90D.printf>] ; printf
00921810 83C4 08 add esp,0x8
00921813 3BF4 cmp esi,esp ; 这里有个奇怪的比较
00921815 E8 B2F9FFFF call Just_A_T.009211CC ; 重点在这里
0092181A 33C0 xor eax,eax
0092181C 5F pop edi
0092181D 5E pop esi
0092181E 5B pop ebx
0092181F 81C4 E4000000 add esp,0xE4
00921825 3BEC cmp ebp,esp ; 这里同样有个奇怪比较
00921827 E8 A0F9FFFF call Just_A_T.009211CC ; 重点也是这里
0092182C 8BE5 mov esp,ebp
0092182E 5D pop ebp
0092182F C3 retn
大家可能也注意到了上面两个奇怪的cmp和call,这是干什么呢?我们明明没有需要比较的步骤啊?
好吧,不吊胃口了,这其实也是Debug编译器为我们所做的事情,它们用于检查esi和ebp的值是否正确
call就是调用了检查的函数。。。
而上述的所有检查在Release模式中都是没有的。
那么,通过上面的说明,你是否已经被Debug模式的贴心和强大所征服了? O(∩_∩)O
好,接下来说说优化,这也是Debug与Release的一个重大区别
正常情况下,Debug模式是很少进行优化的
而Release却会使用很高级别的优化,以加强程序的运行速度与效率
那么我就简单的举个例子好了。
按照惯例,先是C++代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
int
_tmain(
int
argc, _TCHAR* argv[])
{
int
a;
int
b;
a=0x123;
b=0x1234abcf;
if
(b>a)
{
printf
(
"B是大于A的呦~"
);
}
else
{
printf
(
"B竟然比A小!"
);
}
return
0;
}
|
由于使用的Debug模式,所以汇编代码也没有变化:
00A32DB0 > 55 push ebp
00A32DB1 8BEC mov ebp,esp
00A32DB3 81EC D8000000 sub esp,0xD8
00A32DB9 53 push ebx
00A32DBA 56 push esi
00A32DBB 57 push edi
00A32DBC 8DBD 28FFFFFF lea edi,dword ptr ss:[ebp-0xD8]
00A32DC2 B9 36000000 mov ecx,0x36
00A32DC7 B8 CCCCCCCC mov eax,0xCCCCCCCC
00A32DCC F3:AB rep stos dword ptr es:[edi]
00A32DCE C745 F8 2301000>mov dword ptr ss:[ebp-0x8],0x123
00A32DD5 C745 EC CFAB341>mov dword ptr ss:[ebp-0x14],0x1234ABCF
00A32DDC 8B45 EC mov eax,dword ptr ss:[ebp-0x14]
00A32DDF 3B45 F8 cmp eax,dword ptr ss:[ebp-0x8] ; 对A与B进行比较
00A32DE2 7E 19 jle short Just_A_T.00A32DFD ; 如果b<a就跳转(当然是不可能的)
00A32DE4 8BF4 mov esi,esp
00A32DE6 68 0858A300 push offset Just_A_T.??_C@_0O@MCPPOFAI@B>; “B大于A呦~”
00A32DEB FF15 B482A300 call dword ptr ds:[<&MSVCR90D.printf>] ; 调用printf
00A32DF1 83C4 04 add esp,0x4
00A32DF4 3BF4 cmp esi,esp ; 这两条依然是对esi的检查
00A32DF6 E8 D1E3FFFF call Just_A_T.00A311CC ; 贴心的Debug
00A32DFB EB 17 jmp short Just_A_T.00A32E14 ; 执行完成后就跳出if语句
00A32DFD 8BF4 mov esi,esp ; 如果上面B<A,就跳到这里
00A32DFF 68 A057A300 push offset Just_A_T.??_C@_0N@ELAPGMA@B?>; “B竟然小于A!”
00A32E04 FF15 B482A300 call dword ptr ds:[<&MSVCR90D.printf>] ; 调用printf
00A32E0A 83C4 04 add esp,0x4
00A32E0D 3BF4 cmp esi,esp
00A32E0F E8 B8E3FFFF call Just_A_T.00A311CC
00A32E14 33C0 xor eax,eax
00A32E16 5F pop edi
00A32E17 5E pop esi
00A32E18 5B pop ebx
00A32E19 81C4 D8000000 add esp,0xD8
00A32E1F 3BEC cmp ebp,esp
00A32E21 E8 A6E3FFFF call Just_A_T.00A311CC
00A32E26 8BE5 mov esp,ebp
00A32E28 5D pop ebp
00A32E29 C3 retn
考虑到很多朋友看不懂汇编代码,我加入了比较详细的注释,应该没有问题了吧
可以看到,Debug版的汇编代码忠实的还原了我们程序的判断流程,虽然我们都能看出有一个判断分支是
永远不能到达的
所以说Debug就像那个爱着你,所以原谅你的小贪心小糊涂的好男人(女人),看透一切却什么都不说,
默默做事却从来都不邀功。。。
(*^__^*)
那么,我们来看看Release版的汇编代码。。。。
00891156 > \A1 1C308900 mov eax,dword ptr ds:[envpcurity_cookie_complement]
0089115B . 8B0D 80208900 mov ecx,dword ptr ds:[<&MSVCR90.__winitenv>] ; msvcr90.__winitenv
00891161 . 8901 mov dword ptr ds:[ecx],eax
00891163 . FF35 1C308900 push dword ptr ds:[envpcurity_cookie_complement]
00891169 . FF35 20308900 push dword ptr ds:[argvxceptionPointerssonement]
0089116F . FF35 18308900 push dword ptr ds:[argctxitbeginerredtableement]
00891175 . E8 86FEFFFF call Just_A_T.wmainerrinitgeBasexceptionFilter ;调用printf
00891000 >/$ 68 F4208900 push offset Just_A_T.??_C@_0O@MCPPOFAI@B?J?G?$LE?s?S>; /format = "B是大于A的呦~"
00891005 |. FF15 A0208900 call dword ptr ds:[<&MSVCR90.printf>] ; \printf
0089100B |. 83C4 04 add esp,0x4
0089100E |. 33C0 xor eax,eax
00891010 \. C3 retn
尽管做好了心理准备,但是我还是差点没认出我的代码来 (⊙﹏⊙)b)。。。。
我的0x1234abcf呢?我的“B竟然小于A”呢?
从00891000这里我们可以看出Release版本的优化能力来:
它在行进编译的时候,判断出来我们的那个if语句的第二个分支是使用常量来判断的
所以第二分支是不可能到达的
所以。。。它就残忍的剪掉了我们的代码,当然这样也达到了我们代码的运行速度极限。
这就是Release版本优化的魅力和威力的一个小小的缩影
QQ:446989572
//
// 备注:本篇文章来自 vc驿站:http://www.cctry.com/thread-254205-1-1.html
// C、C++、VC++ 各种学习资源,免费教程,期待您的加入!
//