能力值:
( LV2,RANK:10 )
|
-
-
2 楼
2: int add(int x,int y)
3: {
00401030 55 push ebp ;保护ebp
00401031 8B EC mov ebp,esp ;创建栈帧
00401033 83 EC 40 sub esp,40h ;分配局部变量空间,vc在debug模式会自动多分配0x40空间,防止意外栈破坏
00401036 53 push ebx ;保护寄存器
00401037 56 push esi ;保护寄存器
00401038 57 push edi ;保护寄存器
00401039 8D 7D C0 lea edi,[ebp-40h] ;局部变量起始地址
0040103C B9 10 00 00 00 mov ecx,10h ;局部变量字节数/4
00401041 B8 CC CC CC CC mov eax,0CCCCCCCCh
00401046 F3 AB rep stos dword ptr [edi] ;使用0xCC填充局部变量,用于判断栈破坏,0xCC对应int 3指令,若不小心跳到栈上执行将触发调试中断
4: return x+y;
00401048 8B 45 08 mov eax,dword ptr [ebp+8]
0040104B 03 45 0C add eax,dword ptr [ebp+0Ch]
5: }
0040104E 5F pop edi ;下面就是恢复寄存器,销毁栈帧了
0040104F 5E pop esi
00401050 5B pop ebx
00401051 8B E5 mov esp,ebp
00401053 5D pop ebp
00401054 C3 ret
|
能力值:
( LV2,RANK:10 )
|
-
-
3 楼
dword ptr [ebp+8] => x,
dword ptr [ebp+0Ch] => y
之外的语句是调试版填充CC指令(int 3)的,没什么大用。
|
能力值:
( LV2,RANK:10 )
|
-
-
4 楼
vc在debug模式会自动多分配0x40空间。
原来如此。
|
能力值:
( LV2,RANK:10 )
|
-
-
5 楼
调用一个函数时,会先保存当前代码所在函数ebp,esp,然后进入子函数,此时esp ebp指针指向函数栈,(int *)[esp+8]就是第一/第二个参数(不同的调用方式压参数入栈的顺序不同)
|
能力值:
( LV2,RANK:10 )
|
-
-
6 楼
威武,又学习了 填充cc的用处原来是干这个啊
|
能力值:
( LV6,RANK:90 )
|
-
-
7 楼
这个我感觉,不是说多分配40H的空间,这应该是编译器给函数开辟的栈,存放函数的局部变量,从ebp-4表示第一个局部变量就可以看出来。各位觉得呢?
|
能力值:
( LV3,RANK:20 )
|
-
-
8 楼
该函数又没有任何局部变量,为局部变量分配空间的说法说不过去吧。。。
2楼的说法有道理,就是不明白什么情况下会触发到这种保护?
|
能力值:
( LV2,RANK:10 )
|
-
-
9 楼
。。。。。。。。
|
能力值:
( LV4,RANK:50 )
|
-
-
10 楼
没碰到过0xcc被执行的,只是会碰到oxCCCCCCCC内存不能为read write的错误,溢出一般会直接把0xcc覆盖掉,一般不会把返回地址填成上面的0xcc地址吧,这个可能性非常小
|
能力值:
( LV6,RANK:90 )
|
-
-
11 楼
在DEBUG下,即使函数没有任何的局部变量,编译器也会为函数开辟栈和为局部变量分配内存。C语言的局部变量的作用域就是这么实现的么
|
能力值:
( LV4,RANK:50 )
|
-
-
12 楼
哎呦,这样的好处就是可以发现死循环了啦,如果像如下的递归调用
void test()
{
test();
}
debug下面就可以发现程序提前退出了,在调用test之后的代码一句都没有运行。
而release运行就只有jmp $ 构成了一个死循环,调试的时候还以为机器卡主了~
|
能力值:
( LV2,RANK:10 )
|
-
-
13 楼
push ebp
mov ebp,esp
sub esp,40h
这里的具体含义应该是先保存原来的堆栈栈底地址,然后再把当前的ESP做为新的栈底,再把ESP的指针向上移动64个字节做为新的栈顶,在这里需要注意看一下,这里的40H是十六进制的40
push ebx
push esi
push edi
在这里先保存一下原来的三个寄存器 EBX,ESI,EDI
在这里也顺便讲一下,因为在下面马上要执行rep stos dword ptr [edi] 指令
ESI,EDI是指针寄存器,代表是源指针地址和目标地址,而EBX是做为临时内存寻址方式需要临时存放地址用的,这三个寄存器在下面的指令中都需要用得上,所以才保存这三个寄存器,(由此可见,每个寄存器都有相应的具体功能,CPU是不会随便使用无关的寄存器)。
lea edi,[ebp-40h]
mov ecx,10h
mov eax,0CCCCCCCCh
rep stos dword ptr [edi]
这几句是把从栈顶开始,以CX做为计数器,计数器为16次,每次填充大小为4个字节,向下填充64个字节的CC断点,也就是64个INT3断点,也可以看做是把这个栈的内存地址初始化。
eax,dword ptr [ebp+8]
add eax,dword ptr [ebp+0Ch]
根据C调用约定 对二个参数进行相加
在这里要说明一下
根据函数尾部的 ret 可以判断出此函数为
_cdecl调用约定
如果是retn 8 则说明是_stdcall调用约定。
这里赋值的EAX也正是调用约定做为返回值的保存寄存器。
pop edi
pop esi
pop ebx
根据上面的,按顺序还原EDI,ESI,EBX三个寄存器,保持寄存器数据完整性。
mov esp,ebp
pop ebp
重新回来原来的栈底,在这个函数外面应该还有一个add esp,8 的代码来保持堆栈平衡。
根据上面的汇编根本上可以判断此程序为C代码,并非MFC代码。因为少了有异常处理相关的处理。
以上讲的为小弟个人的理解,有不对的地方,还请大家多指出一下。
在这里还有一点问题就是为什么要开辟64个字节的内存块,而不是32或是128 这个64有什么具体含义呢,还请大家也多讲讲看。
|
能力值:
( LV2,RANK:10 )
|
-
-
14 楼
00401039 8D 7D C0 lea edi,[ebp-40h]
0040103C B9 10 00 00 00 mov ecx,10h
00401041 B8 CC CC CC CC mov eax,0CCCCCCCCh
00401046 F3 AB rep stos dword ptr [edi]
说明是编译类型是Debug 版的, 没有做优化。 这些CCCCCC,是用来防止缓冲区溢出覆盖返回地址的, 这个也是某些情况下程序为什么编译成Debug 版可以工作, release版不能工作的原因。
|
能力值:
( LV2,RANK:10 )
|
-
-
15 楼
为什么是64个字节呢
|
|
|