当前位置:   article > 正文

【C/C++】汇编解析:函数调用的压栈、弹栈过程;_调用函数的栈底指针

调用函数的栈底指针

前言

  • 首先知道release下就算关闭优化也和dubug下的汇编差了很多,主要是debug 要有很多调试信息存在;
  • 如果只需要分析函数调用过程,那么直接看关闭了优化的release 的代码即可;
  • 栈空间的开辟与释放,是没有默认初始化的过程的,直接减sp指针或者恢复sp指针即可;
  • 所以会存在报错:局部变量不初始化就直接使用;
    在这里插入图片描述
常见指令
  • rep 指令主要是循环(repeat)
  • 汇编指令STOS(Store String Data) 用法:stos dst ;将寄存器(AX,EAX)里的内容存储(store)到内存单元地址(ES:DI),同时CPU自动修改DI,以指向下一元素;
待分析代码
int func(int a, int b)
{
	int temp = a + b;
	return temp;
}
int main()
{
	func(1, 2);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

release 模式下分析 (单纯的压栈弹栈过程)

func函数调用压栈过程分析;

  1. 首先将参数从右向左压栈;再调用函数;(压栈过程在下面main 的部分)
  2. 将ebp就上一轮调用的函数栈基地址压栈;(用于后面恢复现场)(这里主要是恢复上一个函数的函数栈: ebp、esp
  3. 这里有个疑问点push ecx ecx 并没有被用到,我自己得理解是push后这里得栈帧占用,否则后面得 [ebp-4] // 疑问点

func函数返回弹栈过程分析;

  1. 将当前ebp替换esp;(即直接退出栈空间,这一步就可以直接声明之前的栈空间已经被释放了
  2. 弹栈给ebp;(当前栈顶指针已经是当前函数的栈底指针了,当前位置弹栈,就是之前压入的上一个调用函数的栈底指针,所以这里其实恢复了现场)
  3. add esp,8 为了恢复esp,因为之前压了两个参数,所以恢复回去;
  • 能看到,栈空间的使用和释放没有默认初始化和清空的过程,这也是栈空间速度快的原因之一;同时也是栈空间不初始化就是用编译器报错的原因;
  • 同时可以看到,栈空间中的变量,和传进来的实参的访问,都是根据栈底指针+偏移来用的;
//int func(int a, int b)
//{
004C1570  push        ebp 
004C1571  mov         ebp,esp  
004C1573  push        ecx  
//	int temp = a + b;
004C1574  mov         eax,dword ptr [ebp+8]  
004C1577  add         eax,dword ptr [ebp+0Ch]  
004C157A  mov         dword ptr [ebp-4],eax  
//	return temp;
004C157D  mov         eax,dword ptr [ebp-4]  
//}
004C1580  mov         esp,ebp  
004C1582  pop         ebp  
004C1583  ret  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
//	func(1, 2);
00F3A328  push        2  
00F3A32A  push        1  
00F3A32C  call        func (0F32063h)  
00F3A331  add         esp,8  
  • 1
  • 2
  • 3
  • 4
  • 5

debug 模式下分析 (主要是看rep 指令初始化的问题)

func函数调用压栈过程分析;

  1. 首先将参数从右向左压栈;再调用函数;(压栈过程在下面main 的部分)
  2. 将ebp就上一轮调用的函数栈基地址压栈;(用于后面恢复现场
  3. 用新的栈顶指针来更新当前ebp;(即从这里开始是当前函数的函数栈帧基地址
  4. 更新栈顶指针;(sub esp,0CCh ;0CCh 表示的就是当前函数的栈大小)
  5. 之前的寄存器压栈保存;ebx 、esi 、edi ;
  6. 循环的将整个函数栈空间初始化 0x CCh;(这里只有debug情况下才会有,0xCC是int 3指令的机器码,好像叫调试中断指令,如果CPU意外执行这样的指令,证明程序哪里出错了,产生中断
  • 只需要分析到这里即可;因为剩下的更多的都是debug调试信息;
//int func(int a, int b)
//{
00F35260  push        ebp  
00F35261  mov         ebp,esp  
00F35263  sub         esp,0CCh  
00F35269  push        ebx  
00F3526A  push        esi  
00F3526B  push        edi  
00F3526C  lea         edi,[ebp+0CCh]  
00F35272  mov         ecx,33h  
00F35277  mov         eax,0CCCCCCCCh  
00F3527C  rep stos    dword ptr es:[edi]  
00F3527E  mov         ecx,offset _CB9A9D65_test@cpp (0F5A04Ah)  
00F35283  call        @__CheckForDebuggerJustMyCode@4 (0F31726h)
//	int temp = a + b;
00F35288  mov         eax,dword ptr [ebp+8]  
00F3528B  add         eax,dword ptr [ebp+0Ch]  
00F3528E  mov         dword ptr [ebp-8],eax  
//	return temp;
00F35291  mov         eax,dword ptr [ebp-8]  
//}
00F35294  pop         edi  
00F35295  pop         esi  
00F35296  pop         ebx  
00F35297  add         esp,0CCh  
00F3529D  cmp         ebp,esp  
00F3529F  call        00F31767  
00F352A4  mov         esp,ebp  
00F352A6  pop         ebp 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
//	func(1, 2);
00F3A328  push        2  
00F3A32A  push        1  
00F3A32C  call        func (0F32063h)  
00F3A331  add         esp,8  
  • 1
  • 2
  • 3
  • 4
  • 5

总结

  • 函数调用的压栈弹栈过程是简单的, 平时使用看到的很复杂的,很大一部分原因是存在大量debug信息;
  • 栈空间的使用十分遍历、快捷;
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/355187
推荐阅读
相关标签
  

闽ICP备14008679号