首页 > 其他分享 >函数调用堆栈

函数调用堆栈

时间:2023-06-11 13:04:31浏览次数:39  
标签:esp int mov 函数调用 eax ebp push 堆栈


这段代码反汇编后,代码是什么呢?

 
  #include <stdio.h>
  

  long test(int a,int
   b)
 {
      a = a + 3
  ;
      b = b + 5
  ;
      return a +
   b;
 }

int main(int argc, char*
   argv[])
 {
     printf("%d",test(10,90
  ));
     return 0
  ;
 } 
  


先来看一个概貌

 
 16:   int main(int
  argc, char* argv[])
17
 :   {
00401070   push
         ebp
00401071   mov
          ebp,esp
00401073   sub
          esp,40h
00401076   push
         ebx
00401077   push
         esi
00401078   push
         edi
00401079   lea
          edi,[ebp-40h]
0040107C   mov
          ecx,10h
00401081   mov
          eax,0CCCCCCCCh
00401086   rep
  stos    dword ptr [edi]
18:       printf("%d",test(10,90))
 ;
00401088   push
         5Ah
0040108A   push
         0Ah
0040108C   call        @ILT+0(test) (00401005
 )
00401091   add         esp,8
 
 00401094   push
         eax
00401095   push        offset string "%d" (0042201c
 )
0040109A   call        printf (004010d0
 )
0040109F   add         esp,8
 
 19:       return 0
 ;
004010A2   xor
          eax,eax
20
 :   }
 
 

下面来解释一下

函数调用堆栈_堆栈


开始进入Main函数  esp=0x12FF84   ebp=0x12FFC0

完成椭圆形框起来的部分

00401070   push        ebp     ebp的值入栈,保存现场(调用现场,从test函数看,如红线所示,即保存的0x12FF80用于从test函数堆栈返回到main函数)

00401071   mov         ebp,esp     此时ebp=0x12FF80 此时ebp就是“当前函数堆栈”的基址 以便访问堆栈中的信息;还有就是从当前函数栈顶返回到栈底


00401073   sub      esp,40h   

函数使用的堆栈,默认64个字节,堆栈上就是16个横条(密集线部分)此时esp=0x12FF40

在上图中,上面密集线是test函数堆栈空间,下面是Main的堆栈空间    (补充,其实这个就叫做 Stack Frame)


00401076   push        ebx

00401077   push        esi

00401078   push        edi    入栈 

00401079   lea         edi,[ebp-40h]
0040107C   mov         ecx,10h
00401081   mov         eax,0CCCCCCCCh
00401086   rep stos    dword ptr [edi]      
初始化用于该函数的栈空间为0XCCCCCCCC  即从0x12FF40~0x12FF80所有的值均为0xCCCCCCCC

18:       printf("%d",test(10,90));
00401088   push        5Ah    参数入栈 从右至左 先90  后10
0040108A   push        0Ah 

0040108C   call        @ILT+0(test) (00401005)   
函数调用,转向eip 00401005  
注意,此时仍入栈,入栈的是call test 指令下一条指令的地址00401091   下一条指令是add esp,8

@ILT+0(?test@@YAJHH@Z):
00401005   jmp         test (00401020)   
即转向被调函数test


  8:    long test(int a,int  b)
9 :    {
00401020   push         ebp
00401021   mov          ebp,esp           
00401023   sub          esp,40h
00401026   push         ebx
00401027   push         esi
00401028   push         edi
00401029   lea          edi,[ebp-40h]
0040102C   mov          ecx,10h
00401031   mov          eax,0CCCCCCCCh
00401036   rep  stos    dword ptr [edi]       //这些和上面一样
10:        a = a + 3 ;                                    
00401038   mov         eax,dword ptr [ebp+8 ]     //ebp=0x12FF24 加8 [0x12FF30]即取到了参数10
0040103B   add         eax,3 
 0040103E   mov         dword ptr [ebp+8
 ],eax
11:        b = b + 5 ;
00401041   mov          ecx,dword ptr [ebp+0Ch]
00401044   add         ecx,5 
 00401047   mov
          dword ptr [ebp+0Ch],ecx
12:        return a + b ;
0040104A   mov         eax,dword ptr [ebp+8 ]
0040104D   add          eax,dword ptr [ebp+0Ch]  //最后的结果保存在eax, 结果得以返回
13 :   }
00401050   pop          edi                 
00401051   pop          esi
00401052   pop          ebx
00401053   mov          esp,ebp     //esp指向0x12FF24, test函数的堆栈空间被放弃,从当前函数栈顶返回到栈底
00401055   pop          ebp           //此时ebp=0x12FF80, 恢复现场  esp=0x12FF28
00401056   ret                          ret负责栈顶0x12FF28之值00401091弹出到指令寄存器中,esp=0x12FF30  

因为win32汇编一般用eax返回结果 所以如果最终结果不是在eax里面的话 还要把它放到eax

注意,从被调函数返回时,是弹出EBP,恢复堆栈到函数调用前的地址,弹出返回地址到EIP以继续执行程序。

从test函数返回,执行
00401091   add         esp,8       
清栈,清除两个压栈的参数10 90 调用者main负责
(所谓__cdecl调用由调用者负责恢复栈,调用者负责清理的只是入栈的参数,test函数自己的堆栈空间自己返回时自己已经清除,靠!一直理解错)

00401094   push       eax          入栈,计算结果108入栈,即printf函数的参数之一入栈
00401095   push        offset string "%d" (0042201c)     入栈,参数 "%d"  当然其实是%d的地址
0040109A   call        printf (004010d0)      函数调用 printf("%d",108) 因为printf函数时
0040109F   add         esp,8       清栈,清除参数 ("%d", 108)
19:       return 0;           
004010A2   xor         eax,eax     eax清零
20:   }

main函数执行完毕 此时esp=0x12FF34   ebp=0x12FF80
004010A4   pop         edi
004010A5   pop         esi
004010A6   pop         ebx
004010A7   add         esp,40h    //为啥不用mov esp, ebp? 是为了下面的比较
004010AA   cmp         ebp,esp   //比较,若不同则调用chkesp抛出异常
004010AC   call        __chkesp (00401150)   
004010B1   mov         esp,ebp   
004010B3   pop         ebp          //ESP=0X12FF84  EBP=0x12FFC0 尘归尘 土归土 一切都恢复最初的平静了  :)
004010B4   ret


1. 如果函数调用方式是__stdcall 不同之处在于
main函数call 后面没有了 add esp, 8
test函数最后一句 是 ret 8   (由test函数清栈, ret 8意思是执行ret后,esp+8)

2. 运行过程中0x12FF28 保存了指令地址 00401091是怎么保存的?
栈每个空间保存4个字节(粒度4字节) 例如下一个栈空间0x12FF2C保存参数10  
因此
0x12FF28 0x12FF29 0x12FF2A 0x12FF2B   
  91              10            40           00       
little-endian  认为其读的第一个字节为最小的那位上的数

3. char a[] = "abcde"  
对局部字符数组变量(栈变量)赋值,是利用寄存器从全局数据内存区把字符串“abcde”拷贝到栈内存中的

4. int szNum[5] = { 1, 2, 3, 4, 5 }; 栈中是如何分布的?
     00401798   mov         dword ptr [ebp-14h],1
     0040179F   mov         dword ptr [ebp-10h],2
     004017A6   mov         dword ptr [ebp-0Ch],3
     004017AD   mov         dword ptr [ebp-8],4
     004017B4   mov         dword ptr [ebp-4],5
可以看出来 是从右边开始入栈,所以是 5 4 3 2 1 入栈

 int *ptrA = (int*)(&szNum+1);
 int *ptrB = (int*)((int)szNum + 1);
 std::cout<< ptrA[-1] << *ptrB << std::endl;
结果如何?
28:       int *ptrA = (int*)(&szNum+1);
004017BB   lea         eax,[ebp]
004017BE   mov         dword ptr [ebp-18h],eax
&szNum是指向数组指针;加1是加一个数组宽度;&szNum+1指向移动5个int单位之后的那个地方, 就是把EBP的地址赋给指针
ptrA[-1]是回退一个int*宽度,即ebp-4
29:       int *ptrB = (int*)((int)szNum + 1);
004017C1   lea         ecx,[ebp-13h]
004017C4   mov         dword ptr [ebp-1Ch],ecx
如果上面是指针算术,那这里就是地址算术,只是首地址+1个字节的offset,即ebp-13h给指针

实际保存是这样的
01               00           00      00           02           00      00      00
ebp-14h     ebp-13h                      ebp-10h
注意是int*类型的,最后获得的是 00 00 00 02 
由于Little-endian, 实际上逻辑数是02000000   转换为十进制数就为33554432
最后输出533554432

标签:esp,int,mov,函数调用,eax,ebp,push,堆栈
From: https://blog.51cto.com/u_130277/6457454

相关文章

  • 调试技巧之调用堆栈
    在计算机科学中,Callstack是指存放某个程序的正在运行的函数的信息的栈。Callstack由stackframes组成,每个stackframe对应于一个未完成运行的函数。在当今流行的计算机体系架构中,大部分计算机的参数传递,局部变量的分配和释放都是通过操纵程序栈来实现的。栈用来传递函数参数,存......
  • 使用dbghelp获取调用堆栈--release下的调试方法学
    当软件作为release模式被发布给用户时,当程序崩溃时我们很难去查找原因。常见的手法是输出LOG文件,根据LOG文件分析程序崩溃时的运行情况。我们可以通过SEH来捕获程序错误,然后输出一些有用的信息作为我们分析错误的资料。一般我们需要输出的信息包括:系统信息、CPU寄存器信息、堆栈......
  • java不打印异常堆栈
    背景:生产环境抛异常,但却没有将堆栈信息输出到日志,只有简单的java.lang.NullPointerException错误信息。原因分析JVM在默认启动的时候会加上OmitStackTraceInFastThrow参数,含义是当大量抛出同样的异常的后,后面的异常输出将不打印堆栈。原因是打印堆栈的时候底层会调用到Throw......
  • c++函数调用压栈过程
    c++函数调用,栈内情况如下图所示:首先主函数将被调函数所需参数从右至左压入栈中然后再将主函数地址即返回地址EIP压入栈中再将主函数栈基址EBP压入栈中,此时构造被调函数栈,将当前ESP值mov给EBP,即被调函数栈从此处开始上图ida反汇编代码,可以看到对变量的使用,参数(argc,argv,env......
  • EBP、ESP作用——esp始终指向栈顶,ebp是在堆栈中寻址用的(就是临时变量嘛)
    基本概念:(1)ESP:栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶。(2)EBP:基址指针寄存器(extended base pointer),其内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部。—————————————————————以......
  • 堆栈算法模板
     动态维护中位数一般都是用双堆解决–同理:动态维护第K大数295.数据流的中位数难度困难800收藏分享切换为英文接收动态反馈中位数是有序整数列表中的中间值。如果列表的大小是偶数,则没有中间值,中位数是两个中间值的平均值。例如 arr=[2,3,4] 的中位数是 3 。例如 arr=......
  • try catch捕获异常解析堆栈信息
    1packagekd.moya.zhgs.helper;2importjava.io.PrintWriter;3importjava.io.StringWriter;4/**5@ClassName:ErroHelper6@Description:TODO(异常解析)7@author:liul8@Date:2021/11/100010/15:08/星期三9/10publicclassErroHelper{11/*12......
  • 算法总结——堆栈、字符串、数组类题目
    先说stack的题目stack的实现:链表,数组题目:(1)简单的:minstack,一个数组实现三个stack(2)经典的stack问题:经典汉诺塔问题,逆波兰式计算或者产生逆波兰式,简化文件路径,验证括号对是否合法,找出最长有效括号(贪心+stack求解)(3)涉及tree的遍历问题:tree中序遍历的迭代解法,二叉搜索树的两节点和(twosu......
  • 堆栈模拟队列 (25分)
    堆栈模拟队列(25分)设已知有两个堆栈S1和S2,请用这两个堆栈模拟出一个队列Q。所谓用堆栈模拟队列,实际上就是通过调用堆栈的下列操作函数:intIsFull(StackS):判断堆栈S是否已满,返回1或0;intIsEmpty(StackS):判断堆栈S是否为空,返回1或0;voidPush(StackS,ElementTypeitem):将......
  • java 如何实现短函数调用?
    在Java中,可以使用lambda表达式来实现短函数调用。Lambda表达式是一个匿名函数,它可以传递给方法或存储在变量中,以便在需要时使用。不多说废话!!下面直接给大家示范一下,如何使用Lambda表达式实现短函数调用:在上面这段代码中:首先我们自定义一个函数接口TestInterface;在这个接口中......