我首先看一下vs2013,简单写的C代码:
void cainiao2022(int a, int b)
{
int c = a + b;
}
void main()
{
cainiao2022(1, 2);
}
一个简单的有2个形参的cainiao2022()函数,接着在main()主函数调用cainiao2022()函数
然后我们debug看看,F10单步,然后右击鼠标,查看汇编。
void main()
7: {
009E1400 push ebp ;保存ebp, 执行这句前ESP = 0x003bfb78,EBP = 0x003bfbc4
;push的结果是esp总减少,执行后ESP = 0x003bfb74,EBP = 0x003bfbc4
009E1401 mov ebp,esp ;将esp放入ebp中此时ebp和esp相同,即执行后ESP = EBP = 0x003bfb74
;原EBP值已经被压栈(位于栈顶),而新的EBP又恰恰指向栈顶。
;此时EBP寄存器就已经处于一个非常重要的地位,该寄存器中存储着栈中的一个地址(原EBP入栈后的栈顶),
;从该地址为基准,向上(栈底方向)能获取返回地址、参数值,向下(栈顶方向)能获取函数局部变量值,
;而该地址处又存储着上一层函数调用时的EBP值!
------------------------------------------------------------------------------
009E1403 sub esp,0C0h ;把esp往上移动一个范围
;等于在栈中空出一片空间来存局部变量
;执行这句后ESP = 0x003bfab4
009E1409 push ebx ;下面3句都是保存3个寄存器
009E140A push esi
009E140B push edi
009E140C lea edi,[ebp-0C0h] ;把ebp-40h加载到edi中,目的是保存局部变量的区域
009E1412 mov ecx,10h
009E1417 mov eax,0CCCCCCCCh ;从ebp-0C0h开始的区域初始化成全部0CCCCCCCCh,就是int3断点
009E141C rep stos dword ptr [edi] ;拷贝字符串,初始化局部变量空间
;以上的语句就是在栈中开辟一块空间放局部变量
;然后把这块空间都初始化为0CCCCCCCCh,就是int3断点,一个中断指令。
;因为局部变量不可能被执行,执行了就会出错,这时候发生中断提示开发者。
;到时候我们可以用od很直观的看到
------------------------------------------------------------------------------
8: cainiao2022(1,2);
009E141E push 2 ;参数2入栈,执行前ESP = 0x003bfaa8,执行后ESP = 0x003bfb74
00401420 push 1 ;参数1入栈,执行后ESP = 0x003bfaa4
00401422 call cainiao2022 (0FC108Ch) ;调用cainiao2022()函数,可以按F11进
00401427 add esp,8 ;调用完函数后恢复/释放栈,执行后ESP = 0x003bfaa8
;其实call 指令调用一个过程, 但它有一个小动作
;在参数入栈以后, 被调用函数执行 之前, 它会将当前函数的下一条指令地址, 即EIP的值压入
;
;在 c/c++ 中, 函数的默认调用约定为 cdecl, 它约定参数从右到左入栈,
;由调用者清理堆栈, 所谓清理, 即调整ESP的值, 使得原来的局部数据不再属于栈
------------------------------------------------------------------------------
9: }
0040142A xor eax , eax;
0040142C pop edi ;下面3句都是恢复寄存器,上面怎样push,这里就要对应反过来pop
0040142D pop esi ;简单来说就是先进来最后才出去,最后进来的先出去
0040142E pop ebx
0040142F add esp,0C0h ;恢复esp,对应上面的sub esp,0C0h
00401435 cmp ebp,esp ;检查esp是否恢复正常,不正常就进入下面的call里面debug
00401437 call __chkesp (OFC113Bh) ;处理可能出现的堆栈错误(如果出错,将陷入debug)。
0040143C mov esp,ebp ;将栈顶指针放回esp
0040143E pop ebp ;恢复原来的ebp和esp,让上一个调用的函数正常使用
0040143F ret ;将返回地址存入EIP, 转移流程
下面我们看看cainiao2022()有那些不同就行了
------------------------------------------------------------------------------
1: void cainiao2022(int a,int b)
2: {
00FC13C0 push ebp
00FC13C1 mov ebp,esp
00FC13C3 sub esp,0CCh
00FC13C9 push ebx
00FC13CA push esi
00FC13CB push edi
00FC13CC lea edi,[ebp-0CCh]
00FC13D2 mov ecx,33h
00FC13D7 mov eax,0CCCCCCCCh
00FC13DC rep stos dword ptr es:[edi]
;上面的代码跟前面介绍过的几乎一样了
------------------------------------------------------------------------------
3: int c=a+b;
00FC13DE mov eax,dword ptr [a] ;取第一个参数a放在eax里面 ,这个a的值在ebp+8
00FC13E1 add eax,dword ptr [b] ;取第二个参数b,加上a的值放在eax中,这个b的值在ebp+0Ch
00FC13E4 mov dword ptr [c],eax ;将最终结果放在c中,应该在ebp-8
;一般而言,ss:[ebp+4]处为返回地址
;ss:[ebp+8]处为第一个参数值(这里是a),ss:[ebp+0Ch]处为第二个参数(这里是b,这里8+4=12=0Ch)
;ss:[ebp-8]处为第一个局部变量(这里是c),ss:[ebp]处为上一层EBP值
;ebp和函数返回值是32位,所以占4个字节
------------------------------------------------------------------------------
4: }
00FC13E7 pop edi
00FC13E8 pop esi
00FC13E9 pop ebx
00FC13EA mov esp,ebp
00FC13EC pop ebp
00FC13ED ret
;恢复性的操作,只是比前面少了检查esp的语句而已