`
wanzhanzhuce
  • 浏览: 27881 次
社区版块
存档分类
最新评论

转】函数调用 堆栈

c 
阅读更多
转】函数调用 堆栈

#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

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

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

原文地址:http://www.cnblogs.com/dylanwind/archive/2008/12/08/1349822.html
分享到:
评论

相关推荐

    C++高效获取函数调用堆栈

    C++高效获取函数调用堆栈 在程序设计和开发过程中,出现问题是很正常的。这时候,快速找到问题所在,并确定程序的上下文环境就变得非常重要。函数调用堆栈的信息对于解决问题具有很大的帮助。传统的方法是使用 ...

    函数递归调用堆栈分析.doc

    函数递归调用堆栈分析是指在计算机科学中,函数递归调用时,函数调用自身的过程中,如何使用堆栈来存储变量和参数的过程。堆栈是一种 lasts-in-first-out(LIFO)的数据结构,用于存储函数调用的参数和变量。 在...

    通过EBP EIP来找函数调用堆栈

    在计算机科学中,特别是在软件调试和逆向工程领域,理解函数调用堆栈是至关重要的。函数调用堆栈(也称为调用栈或执行栈)记录了程序中函数调用的顺序,这对于追踪代码执行流程,特别是错误定位非常有用。在x86架构...

    JavaScript函数调用堆栈loader

    JavaScript函数调用堆栈是编程过程中非常重要的一个概念,它记录了程序运行时函数的调用顺序。在JavaScript中,每当一个函数被调用,一个新的调用帧(call frame)就会被添加到调用堆栈上,包含了这个函数执行的所有...

    linux C用户态调试追踪函数调用堆栈以及定位段错误.docx

    "Linux C用户态调试追踪函数调用堆栈以及定位段错误" Linux C用户态调试追踪函数调用堆栈以及定位段错误是指在 Linux 平台上使用 C 语言编写的程序中,如何追踪函数调用堆栈并定位段错误的方法。 在 Linux 平台上...

    Win32环境下函数调用的堆栈之研究

    在Win32环境下,函数调用涉及到堆栈的操作,这是理解程序执行和内存管理的关键部分。本文将深入探讨这个主题,特别是通过一个简单的C语言示例程序来解析其背后的汇编代码。 首先,我们要明白堆栈是计算机内存中的一...

    函数调用堆栈变化分析[参考].pdf

    在计算机科学中,函数调用堆栈(也称为调用栈或执行栈)是程序运行时内存中的一个重要组成部分,主要用于管理函数的调用与返回。本文将深入探讨函数调用堆栈的变化分析,以帮助理解程序执行过程。 首先,我们需要...

    main函数调用子函数堆栈解析

    Main 函数调用子函数堆栈解析 在计算机程序设计中,函数调用是最基本的编程单元。函数调用会在内存中创建一个新的堆栈帧,该堆栈帧用于存放函数的实参、局部变量和返回地址等信息。在这个过程中,main 函数如何调用...

    函数调用,参数堆栈等学习资料收集

    函数调用,参数堆栈等学习资料收集 关于函数调用时堆栈的变化分析(转自Jim's blog) - H_S_的学习总结与心得 - 博客园.mht ...函数调用堆栈分析.doc 转贴:关于函数调用的深入分析 百度空间_应用平台.mht

    堆栈、栈帧与函数调用过程分析

    【应聘笔记系列】堆栈、栈帧与函数调用过程分析,C-C++堆栈指引

    函数调用与堆栈

    ### 函数调用与堆栈 #### 变量的生存期概述 在计算机编程中,尤其是在C/C++这类语言中,变量的生存期是指变量在程序执行过程中占据内存的时间段。根据变量生存期的不同,我们可以将变量大致分为三类:静态生存期...

    vc调试技巧之调用堆栈

    3. **性能优化**:有时候,通过对调用堆栈的分析可以发现程序中不必要的函数调用路径,从而进行优化。 #### 如何使用VC中的调用堆栈 1. **启动调试器**:首先确保已经设置了断点,并通过F5或其他方式启动调试器。 ...

    调用堆栈的类

    在编程领域,调用堆栈(Call Stack)是程序执行过程中不可或缺的一部分,它记录了函数调用的顺序。当一个函数被调用时,它的相关信息(如返回地址、局部变量等)会被推入堆栈,形成一个堆栈帧。本文将深入探讨如何...

    VC中打印当前调用堆栈信息实例

    在Microsoft Visual C++ (VC) 开发环境中,打印当前调用堆栈信息是一个非常有用的调试技巧,它可以帮助开发者追踪程序执行流程,理解函数调用顺序,尤其是在遇到异常或错误时。下面将详细介绍如何在VC中实现这个功能...

    浅谈在linux kernel中打印函数调用的堆栈的方法

    在Linux内核开发和调试过程中,理解函数调用堆栈是非常关键的,它能帮助开发者追踪问题的根源,尤其是在处理内核崩溃或者异常时。本文将深入探讨如何在Linux内核中打印函数调用的堆栈。 首先,Linux内核提供了一个...

    JavaScript实现显示函数调用堆栈的方法

    JavaScript函数调用堆栈是程序执行过程中一种重要的信息,它记录了函数调用的顺序和层次关系,有助于开发者理解程序执行流程,特别是在进行错误调试和性能分析时。在现代浏览器中,通常可以通过console对象的trace...

    利用 RtlWalkFrameChain 回溯调用堆栈

    调用堆栈是程序执行过程中函数调用关系的记录,每一个函数调用都会在堆栈上留下一个帧(frame),包含了返回地址、局部变量等信息。RtlWalkFrameChain函数能够遍历这些帧,从而让我们知道函数是如何被调用的,以及...

    函数调用中堆栈的个人理解共4页.pdf.zip

    2. **调用堆栈(Call Stack)**:在程序运行时,每个线程都有一个调用堆栈,记录了当前执行路径上的所有函数调用。每当一个函数被调用,一个新的栈帧(Stack Frame)会被创建,包含该函数的局部变量、参数和返回地址...

    个人总结--函数堆栈调用

    在函数调用时,操作系统或运行时环境会使用一个称为调用堆栈(Call Stack)的数据结构来管理函数的执行。每当一个函数被调用时,函数的返回地址、局部变量的值以及其他相关信息会被压入堆栈。当函数执行完毕后,这些...

Global site tag (gtag.js) - Google Analytics