精华内容
下载资源
问答
  • 函数调用堆栈的过程
    万次阅读 多人点赞
    2018-05-31 11:46:43

    本篇来分析函数调用的过程:

    通过下面一个简单的例子来进入话题:

    #include<stdio.h>
    int sum(int a,int b)
    {
    	int tmp=0;
    	tmp=a+b;
    	return tmp;
    }
    
    int main()
    {
    	int a=10;
    	int b=20;
    	int ret=0;
    
    	ret=sum(a,b);
    	printf("ret=%d\n",ret);
    	return 0;
    }

    首先,先从main函数开始,查看main()函数的反汇编代码,进去之后会发现在一开始就看到如下部分:


    那这一部分是干什么的呢?先留个疑问,等会解决!

    紧接着,继续看反汇编,如下图


    画出在栈中的整个过程:


    呃,画图的确有点麻烦,不过越画越清楚,^O^

    整个函数的调用,结合上面两张图就基本明白了。那现在说一下刚开始的问题,在第一张图中也做了一点标注。对于每个函数的刚开始都会出现基本类似的指令。这一大堆指令总结起来就干了四件事情:

    第一:将调用方的栈底地址入栈。====》push  ebp

    第二:让原本指向调用方栈底的ebp指向当前函数的栈底。====》mov   ebp,esp

    第三:给当前函数开辟栈帧。====>sub   esp,44h

    第四:对开辟的栈帧进行初始化。初始化的大小不一定。====>rep   stos  

    所以对于sum函数我们可以理解,但是在main函数刚开始也有这些指令,不由地,我们知道,main函数也是通过一个函数来进行调用的,所以也需要上面这四个步骤!!此时也就可以回答图二中我画??的地方咯,它一定存的是调用main函数的函数栈底地址。是不是很清楚呀^O^

    趁热打铁,自己写一下整个过程吧!

    #include<stdio.h>
    int sum(int a,int b)
    {
    	/*
    	push ebp
    	mov ebp,esp
    	sub esp,44h
    	push ebx
    	push esi
    	push edi
    	lea edi,[ebp-44h]
    	mov ecx,11h
    	mov eax,0xccccccch
    	rep stos ===>[esp,ebp]=0xcccccccc
    	*/
    	int tmp=0;//mov dword ptr[ebp-4],0
    	tmp=a+b;
    	/*
    	mov eax,dword ptr[ebp+8]
    	add eax,dword ptr[ebp+0ch]
    	mov dword ptr[ebp-4],eax
    	*/
    	return tmp;//mov dword ptr[ebp-4],eax
    }
    /*
    mov eax,dword ptr[ebp-4]
    mov esp,ebp
    pop ebp
    ret
    */
    
    int main()
    {
    	/*
    	push ebp
    	mov ebp,esp
    	sub esp,44h
    	push ebx
    	push esi
    	push edi
    	lea edi,[ebp-44h]
    	mov ecx,11h
    	mov eax,0xccccccch
    	rep stos ===>[esp,ebp]=0xcccccccc
    	*/
    	int a=10;//mov dword ptr[ebp-4],0Ah
    	int b=20;//mov dword ptr[ebp-8],14h
    	int ret=0;//mov dword ptr[ebp-0Ch],0
    	ret=sum(a,b);
    	/*
    	mov eax,ptr[ebp-8]
    	push eax
    	mov ebx,ptr[ebp-4]
    	push ebx
    	push ecx
    	call sum
    	add esp,8
    	mov dword ptr[ebp-0ch],eax
    	*/
    	printf("ret=%d\n",ret);
    	return 0;
    }

    总结一下吧~

    1、函数的运行都是在栈上开辟内存。

    2、栈是通过esp(栈顶指针)、ebp(栈底指针)两个指针来标识的。

    3、对于栈上的访问都是通过栈底指针的偏移来访问的。

    4、在call一个函数时,有两件事情要做:先将调用函数的下一行指令的地址压入栈中;再进行跳转。

    5、在函数调用时检查函数是否申明、函数名是否相同、函数的参数列表是否匹配、函数的返回值多大。

    ①如果  【函数的返回值<=4个字节】,则返回值通过寄存器eax带回。

    ②如果  【4<函数的返回值<=8个字节】,则返回值通过两个寄存器eax和edx带回。

    ③如果  【函数的返回值>8个字节】,则返回值通过产生的临时量带回。

    6、函数结束ret指令干了两件事:先出栈;再将出栈的值放到CPU的PC寄存器中。因为PC寄存器中永远放的是下一次执行指令的地址,所以就顺理成章的在函数调用完之后依旧接着原来的代码继续执行。

    END~


    更多相关内容
  • 主要介绍了JavaScript实现显示函数调用堆栈的方法,实例分析了JavaScript显示函数调用堆栈的具体作用与使用方法,需要的朋友可以参考下
  • C++ 获取函数调用堆栈的 高效实现代码
  • 文章来源:https://blog.seclibs.com/函数调用堆栈图-c语言/ 我们就使用一个简单的c语言程序来对描述一下在函数调用的时候都发生了什么。 中间的一小段没有意义的汇编语言是为了方便设置断点,为后面的调试做好铺垫...

    文章来源:https://blog.seclibs.com/函数调用堆栈图-c语言/

    我们就使用一个简单的c语言程序来对描述一下在函数调用的时候都发生了什么。

    中间的一小段没有意义的汇编语言是为了方便设置断点,为后面的调试做好铺垫,因为有时会碰到找不到断点位置的情况,使用这个方法,可以在找不到断点的时候向后执行一次,而不破坏我们想调试的程序当前的堆栈状态,这里对main函数和sum函数的效果是类似的,这里直接跟着断点来执行分析sum函数的堆栈操作。

    我们先假设初始状态下的堆栈图如下,esp与ebp的真实距离我们省略。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oSv52EA1-1581778359579)(https://blog.seclibs.com/wp-content/uploads/2020/02/image-12.png)]

    接下来我们来看一下后面的操作。

    在程序的执行当中,我们一般都是按照从右向左的方式去处理的,这里也不例外,我们可以发现当我们调用sum函数对数字1和数字2进行处理的时候,将数字2和1依次压入栈中,这个时候堆栈的情况是这个样子的,esp的值已经减8。

    接下来调用了call,这时进行了两步操作,先将call后面的地址push进堆栈,然后再jmp到call所调用的地址。

    因为jmp是不会影响堆栈的,所以现在的堆栈情况是这样的

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7MLKID9J-1581778359589)(https://blog.seclibs.com/wp-content/uploads/2020/02/image-16.png)]

    然后因为编译器的原因在call的时候还会有一个jmp来中转到后面的处理函数,因为jmp不影响堆栈,我们可以忽略掉它,这里是跳转到了sum函数的处理位置。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kZ1ZM22k-1581778359591)(https://blog.seclibs.com/wp-content/uploads/2020/02/image-17.png)]

    此时的堆栈是没有发生变化的,现在开始到了函数调用的关键阶段了。

    首先先将ebp的值push到堆栈中,因为用到了ebp寻址的方式,所以这里用这种方式来保存ebp中原本的值,然后将esp的值赋给ebp,用ebp寻址来代替esp寻址,因为esp的值一直在不断的发生变化,使用esp寻址会带来很大的计算负担,此时esp与ebp都指向了同一块地址,其中的内容是原来的ebp的值。

    然后让esp减去了0c0h位,开始提升堆栈了,为程序的运行开辟一个存储空间,这个区域也就是平时所说的缓冲区,因为一个单元是四个字节,c0也就是往上提了48个格,由于位置有限中间依旧省略,此时堆栈就变成了如下的样子。

    后面又进行了一系列的push操作,也是为了方便在后续使用这些寄存器的时候保证它们初始的值不丢失,与前面保存ebp的值是一样的方式。

    然后接下来的四步操作只有一个目的,那就是将中间的48格全部值为CC,CC在调试的时候相当于断点,也就是如果你程序跑过的话,就会触发断点不会再继续执行了。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MU3sJsCH-1581778359598)(https://blog.seclibs.com/wp-content/uploads/2020/02/image-22.png)]

    lea是交换地址中的值,给eax和ecx赋值是为rep的执行做准备的,stos是将eax中的值赋给edi,rep是执行后面的指令ecx次。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c0UrI6Dw-1581778359600)(https://blog.seclibs.com/wp-content/uploads/2020/02/image-23.png)]

    接下来的两步指令我们忽略,它们是vs编译器添加的调试指令

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pn7gUeWE-1581778359602)(https://blog.seclibs.com/wp-content/uploads/2020/02/image-24.png)]

    因为eax一般是来用作返回值的,所以这里的计算都是跟eax进行计算的,因为我们这里直接使用的是return返回,没有涉及到临时变量,所以不会用到缓冲区来存储。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bMClPYCJ-1581778359603)(https://blog.seclibs.com/wp-content/uploads/2020/02/image-25.png)]

    接下来的三步pop,是将之前存储在栈中的元素都恢复到它原来的位置。

    此时的堆栈情况就变成了,上面的值还是没有清除的,它们现在已经是垃圾数据的,下一次填充的时候会把它们覆盖掉,这也就造成了可以在其中获取到某些程序不想让人知道的临时变量值。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B2JJWXg4-1581778359607)(https://blog.seclibs.com/wp-content/uploads/2020/02/image-27.png)]

    接下来让esp增加0c0,也就恢复到了提升堆栈之前的位置,此时esp与ebp到了一个位置。

    接下来的三步操作依旧可以忽略,它们是vs编译器生成的,用来检测堆栈是否平衡,如果不平衡的话在这里就会产生报错。

    最后就是使用pop,将ebp恢复到之前的位置。

    最后使用ret回到堆栈中存储的地址,也就是call调用的下一个地址。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iRV9U8sh-1581778359616)(https://blog.seclibs.com/wp-content/uploads/2020/02/image-32.png)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lbQcHt9u-1581778359620)(https://blog.seclibs.com/wp-content/uploads/2020/02/image-33.png)]

    但是此时还有个问题,esp并没有回到调用前的位置,所以堆栈还是没有平衡的,如果堆栈不平衡,那在不断的执行的过程中,就会发生堆栈溢出,这里编译器是使用外平栈的方式来使堆栈恢复平衡的,它在esp的基础上增加了8。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3rzQe2IP-1581778359621)(https://blog.seclibs.com/wp-content/uploads/2020/02/image-34.png)]

    此时堆栈也就恢复到了平衡状态

    还有另一种方式是使用内平栈的方式,即在函数内部就将堆栈恢复平衡,使用ret 8的方式。

    再往后面的操作就是main函数的堆栈平衡的处理了,与上面的函数调用类似,就不提了。

    文章首发公众号和个人博客

    公众号:无心的梦呓(wuxinmengyi)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XYYAJJ30-1581778359625)(https://blog.seclibs.com/wp-content/uploads/2020/01/gongzhonghao.jpg)]

    博客:https://blog.seclibs.com/

    展开全文
  • 函数调用堆栈

    千次阅读 2018-10-14 17:33:19
    函数调用主要由三部分实现:CPU指令+寄存器+堆栈   CPU指令主要有: call类指令:跳转到被调用函数;需要说明的是call指令有两部分:先把下一条指令地址入栈,然后跳转到被调用函数。 ret类指令:释放栈空间,...

    函数调用主要由三部分实现:CPU指令+寄存器+堆栈

     

    CPU指令主要有:

    call类指令:跳转到被调用函数;需要说明的是call指令有两部分:先把下一条指令地址入栈,然后跳转到被调用函数。

    ret类指令:释放栈空间,并且把call入栈的下一条指令赋值给PC寄存器。

    (PC是16位程序计数器(Program Counter),它不属于特殊功能寄存器范畴,程序员不以像访问特殊功能寄存器那样来访问PC。PC是专门用于在CPU取指令期间寻址程序存储器。PC总是保存着下一条要执行的指令的16位地址。通常程序是顺序执行的,在一般情况下,当取出一个指令(更确切地说为一个指令字节)字节后,PC自动加1。如果在执行转移指令、子程序调用/返回指令或中断时,要把转向的地址赋给PC。 )

     

    寄存器(32位,64位名称不一样)主要用到的有:

    ebp,用作记录函数调用时当前函数的堆栈的最低位,也叫帧指针。需要说明的是,对于当前函数的ebp不会改变,所以对于参数以及调用函数存储的
            相关信息都是通过ebp的相对位置寻址或者表示的。

    esp,用作记录函数调用时当前函数的堆栈的最高位。

    eax,一般用做函数返回值的存储,在返回值大于4字节时协同edx一起返回,eax对应低4字节,edx对应高4字节;

             大于8字节的时候,调用函数会在自身栈上开辟空间,加一个隐藏参数传递给被调用函数,被调用函数把返回值拷贝到

             调用函数开辟的空间里面,最后把指针通过eax返回给调用者。

    ……

     

    堆栈

    堆栈是一种思想,粘合了寄存器以及CPU,让CPU在可控且有意义的运作,没有栈,函数调用就得重新做一套理论支持。

    调用者被调用者栈的组织结构:

    还是以经典的示例来写:

    int sum(int x,int y)

    {

        int temp = 0;

        temp = x+y;

        return temp;

    }

    int main()

    {

    int a=10;

    int b=10;

    int c=sum(a,b)

    printf("c is %d\n",a+b);

    }

    重要的点:

     

    call的时候调用函数负责把下一条指令地址入栈。

    在执行被调用函数的时候的第一条指令是push %rbp,会把caller的rbp保存在堆栈中,并且rsp+4;

    ………………(功能语言)

    %rsp+N(如果有需要)

    leave会pop %rbp,把之前保存的caller的rbp值再次赋值到%rbp。

    ret时pop %PC(?),把call的时候保存的下一个执行地址放到程序寄存器中,来执行。

     

     

    优秀博文推荐:

    https://www.jianshu.com/p/5a4f2d78cb53

    拷贝过来:

    这篇文章主要介绍x64平台下函数调用的过程。
    主要内容包括caller如何完成到callee的转换,两者之间参数传递方式,函数的栈分配模型,以及callee如何返回到caller。


    还是以一个例子来说明(为了简化说明过程,全部参数和局部变量均采用long类型,主要是因为其大小正好是8字节和寄存器大小一致,另外浮点的传参规范使用的是浮点寄存器,不在这篇文章里面讨论)。

    long callee(long i, long j) {
        long k;
        long l;
        i = 3333L;
        j = 4444L;
        k = 5555L;
        l = 6666L;
        return 9999L;
    }
    
    void caller() {
        long r = foo(...);
    }
    

    caller函数调用callee的汇编代码:

    long ret = foo(1111L, 2222L);   
    

    先介绍一点x64的通用寄存器集:
    x64的寄存器集有16个通用寄存器,即rax, rbx, rcx, rdx, rbp, rsp, rsi, rdi, r8, r9, r10, r11, r12, r13, r14, r15。
    乍一看这个寄存器排列毫无章法,命名也不整齐;了解RISC处理器的同学应该都比较喜欢RISC的寄存器命名,r0, r1, ..., r15或者r0, r1, ..., r31,简单直白。x64主要是借鉴的RISC处理器的一些特点,增加了通用寄存器的个数,然后又为了兼容历史版本,导致现在我们看到的通用寄存器命名不规范。早期x86处理器就没有这么多寄存器,也没有通用寄存器概念,基本都是专用寄存器即每个寄存器都有专门的用途,因为CISC不是采用load/store结构,大量指令都是直接操作内存运算的。

    x64的函数传参规范:

    • 对于整数和指针类型参数, x64使用6个寄存器传递前6个参数。
      第一个参数使用rdi,第二个参数使用rsi,第三、四,五,六个参数依次使用rdx, rcx, r8, r9;从第七个开始通过栈传递,因此如果函数参数不超过6个,那么所有参数都是通过寄存器传递的。比如函数:
      void callee(int a, int b, int c, int d, int e, int f);
    param #param nameregister
    1ardi
    2brsi
    3crdx
    4drcx
    5er8
    6fr9

    这个传参过程是从RISC处理器里面借鉴过来的,RISC处理器一般采用寄存器传参,比如ARM就使用四个寄存器R0-R4传参,而早期的x86系统都是使用栈传参的。
    至于为什么x64传参使用的寄存器命名这么没有规则,主要是为了和之前的x86处理器兼容,x86系统的ABI已经定义过一套寄存器使用规范。

    先看caller生成的汇编指令:

        movq    $2222, %rsi
        movq    $1111, %rdi
        call    callee
        movq    %rax, -4(%rbp)
    

    代表含义如下:

    ........instruction........description
    movq $2222, %rsi把第二个参数值2222放在寄存器rsi,前面说过第二个参数使用rsi传递
    movq $1111, %rdi把第一个参数值1111放在寄存器rdi,第一个参数使用rdi传递
    call calleecall指令调用函数callee;call指令完成两件事情:把当前指令的下一条指令(即将来callee函数的返回地址)压栈,然后把pc指向callee函数的入口,开始执行callee函数代码
    movq %rax, -4(%rbp)读取callee的返回值,函数返回值通过寄存器rax传递

    再看callee的汇编代码:

        pushq   %rbp
        movq    %rsp, %rbp
        movq    %rdi, -24(%rbp)
        movq    %rsi, -32(%rbp)
        movq    $3333, -24(%rbp)
        movq    $4444, -32(%rbp)
        movq    $5555, -16(%rbp)
        movq    $6666, -8(%rbp)
        movq    $9999, %rax
        leave
        ret
    

    这些指令大致分为三大块,第一块入口指令,第二块函数功能代码,第三块返回指令;指令含义如下:

    .........instruction.........description
    pushq %rbp保存caller的%rbp寄存器值,这个%rbp在函数返回给caller的时候需要恢复原来值,通过leave指令完成。
    moveq %rsp, %rbp把当前的%rsp作为callee的%rbp值
    moveq ..., offer(%rbp)这些moveq指令都是callee函数体的功能,不细说
    movq $9999, %rax设置函数的返回值到%rax,函数返回值是通过寄存器%rax传递的
    leaveleave完成两件事:把%rbp的值move到%rsp,然后从栈中弹出%rbp;这条指令的功能就是恢复到caller的frame结构,即把%rsp和%rbp恢复到caller函数的值
    ret指令负责从栈中弹出返回地址,并且跳转的返回地址。

    下面我们详细一步一步介绍函数调用过程中,寄存器和函数栈的变化过程:

    按照习惯下面步骤中的图示代码段地址从上往下以递增的方式排列,栈地址从上往下以递减的方式排列。

    1. call callee指令之前
      此时pc指向call指令,需要传递的参数已经放到传参寄存器,栈是caller的frame。

       

      1.jpg

    2. call callee指令之后
      call指令完成两件事情,1: 把返回地址压栈,可以看到在栈顶0x4005f6正是call指令的下一个指令地址;2: pc指向函数callee的第一条指令。

       

      2.jpg

    3. pushq %rbp指令之后
      把当前rbp的值压入栈,并且pc向前移动到下一条指令。

       

      3.jpg

    4. movq %rsp, %rbp指令之后
      移动rbp到当前rsp地址, 此时rbp和rsp指向同一个地址;rbp就是callee的frame地址,后面callee函数内都将通过rbp加上偏移的方式来访问局部变量。例如:
      movq $3333, -24(%rbp)
      movq $4444, -32(%rbp)

       

      4.jpg

    5 执行函数体功能指令,例如:
    movq %rdi, -24(%rbp)
    movq %rsi, -32(%rbp)
    这个时候我们可以清楚的看到,callee是如何分配栈空间的,rbp往下首先是局部变量,然后是参数预留空间。

     

    5.jpg

    6 movq $9999, %rax指令之后
    这条指令就是把函数返回值放到寄存器rax,在这个例子中9999=0x270f;前面我们说过函数返回值都是通过rax返回的。

     

    6.jpg

    7 leave指令之后
    leave指令完成两件事,1:把%rbp的值move到%rsp,在当前这个例子中,这个功能没有效果,因为 %rbp和%rsp的值始终相同。2:然后从栈中弹出%rbp。

     

    7.jpg

    8 ret指令之后
    ret指令也是完成两件事情,1:栈中弹出返回地址,2:并且跳转的返回地址。

     

    8.jpg

    我们可以看到此时栈结构和函数进来之前是一样的,从而保证callee返回以后caller能够继续执行。


    这个callee的代码其实有一点问题,不知道你有没有注意的,那就是callee只是调整了%rbp,但并没有调整%rsp,使得%rsp并没有真正指向栈顶,而是自始至终%rsp和%rbp指向同一个地址,按照前面的逻辑callee进来的时候保存了caller的%rbp和%rsp,并且在返回时需要恢复原来的值,而就是说%rbp和%rsp通常成对出现构成一个frame范围,那么这个callee为什么会这样呢?
    原因是callee是一个叶子函数,它不再调用其他函数,就是说从进入这个函数到离开这个函数之间不会发生栈的操作,设置%rsp的操作就可以省略。
    我们修改一下代码,添加一个子函数sub()让callee来使用:

    void sub() {
    }
    
    long callee(long i, long j) {
        long k;
        long l;
    
        i = 3333L;
        j = 4444L;
        k = 5555L;
        l = 6666L;
    
        sub();
        return 99L;
    }
    

    生成的callee代码如下:

        pushq   %rbp
        movq    %rsp, %rbp
        subq    $32, %rsp
        movq    %rdi, -24(%rbp)
        movq    %rsi, -32(%rbp)
        movq    $3333, -24(%rbp)
        movq    $4444, -32(%rbp)
        movq    $5555, -16(%rbp)
        movq    $6666, -8(%rbp)
        movl    $0, %eax
        call    sub
        movl    $9999, %eax
        leave
        ret
    

    相比较前面的callee代码,此时多了一条指令:
    subq $32, %rsp
    这条指令就是调整函数callee的新的%rsp值,使得%rbp和%rsp之间构成一个标准的callee函数frame范围。栈结构如下:

    9.jpg

    其实栈的内容和前面没有call sub的栈内容是一样的,只是调整了%rsp的指针,因为callee已经不是叶子函数了,它需要调用sub函数,这个过程中是有栈的操作的,所以必须把%rsp指向正确的位置。然后在函数返回的时候leave指令能够再把%rsp重新调整到%rbp的位置。



    作者:CodingCode
    链接:https://www.jianshu.com/p/5a4f2d78cb53
    來源:简书
    简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

     

     

    展开全文
  • C语言函数调用栈(一)

    2021-02-02 23:22:49
    函数调用过程通常使用堆栈实现,每个用户态进程对应一个调用栈结构(callstack)。编译器使用堆栈传递函数参数、保存返回地址、临时保存寄存器原有值(即函数调用的上下文)以备恢复以及存储本地局部变量。不同处理器和...
  • C语言函数调用堆栈过程

    千次阅读 2018-11-01 17:15:27
    函数调用堆栈过程 调用约定 函数的调用约定很多,常见的包括__stdcall,__cdecl,__fastcall,__thiscall等等。主要的区别在于约束的三个事件,一个是参数传递是从左开始呢还是从右开始,还有就是堆栈清理的清理方是...

    函数调用堆栈过程

    调用约定

    函数的调用约定很多,常见的包括__stdcall,__cdecl,__fastcall,__thiscall等等。
    主要的区别在于约束的三个事件,一个是参数传递是从左开始呢还是从右开始,还有就是堆栈清理的清理方是调用者还是被调用者。另外来说不同的函数调用约定函数产生的符号名称不同

    举个栗子,对于cdecl,参数是从右到左传递,堆栈平衡是由调用函数来执行的;而win32API一般使用的是stdcall,参数同样是采用了从右往左传递,而函数的堆栈平衡则是由被调用函数执行(不支持可变参数);fastcall参数直接放入寄存器而非栈中,规定前两个参数分别放入ecx和edx中,当寄存器用完时候参数才按照从右往左的顺序压入堆栈。

    调用约定使用场景
    _cdeclc调用约定
    _stdcallwindows标准调用约定
    _fastcall快速调用约定
    _thiscallC++成员函数调用约定

    压栈过程

    int add(int a, int b)
    {
        return a+b;
    }
    
    int main()
    {
        int a = 1;
        int b = 2;
        int res = add(a,b);
        return 0;
    }
    1. 首先从main函数初始,ebp和esp分别存放函数的栈底地址和栈顶地址,此时ebp-4即是a,ebp-8则是b的地址。
    2. 然后调用函数add,第一先将参数从右往左依次入栈,push在调用方的函数栈当中,也就是说此时esp往里开辟了两个参数
      图片描述
    3. 执行call指令,首先将下一条指令地址进行入栈,
      图片描述
    4. 随后开辟新栈,进行现场保护
      图片描述

      • 这里省略了现场保护的过程,主要做的就是push了多个寄存器(例如edi,ebx,edi)的值,在完成后还原现场进行pop,对程序没有什么其他影响这里省略。
      • 将edi置为栈顶地址,ecx置为11h,eax置为0CCCCCCCCh。
      • 从edi开始循环拷贝eax,也就是将整个栈内初始化为0CCCCCCCCh,也就是常见的“烫”。
    5. 执行add函数
      开辟一个临时变量,值是(a)(ebp+8) + (b)(ebp+0Ch),将这个值放入eax中。
    6. 执行完成后回退栈针
      图片描述

      • 首先mov esp,ebp 将add的函数栈回退。
      • 随后pop,将[ebp]的值弹出给ebp,也就是ebp弹回退到main函数的栈底。
      • 执行ret指令,将下一条指令的地址弹出给eip寄存器。
      • 随后在main函数的函数栈中回退形参变量所占用的内存 add esp+8。

    那么再来看看其他情况

    上面的返回值是一个int类型,也就是C的内置类型,通过eax寄存器带出。

    如果是一个double或者long long呢?那么可以通过eax、edx两个寄存器带出。

    如果是一个自定义类型呢?其实也是类似的:

    • 首先在参数传参过程中不能直接去push一个寄存器了,而现在是通过开辟内存后,将自定义类型的实参b的地址放入esi中,循环赋给实参。
      例如说自定义类型的b参数
      图片描述
    • 参数传递完成之后,再来看看返回值,返回值首先会在压入所有的形参之后,将main函数中返回值(临时量)的地址压入参数。
      图片描述
    • 对返回值的操作也是类似的,通过esi和edi、ecx,循环拷贝到main函数的函数栈之中。
    • 临时量返回值的地址是最后才会压栈的,那么它的地址一定是ebx+8
    展开全文
  • 在解bug或者排查问题时,经常需要知道一个方法在运行到里面的时候,是由谁来调用的,调用路径是什么(调用堆栈) Android 原生 SDK提供了方法: //获取调用堆栈的方法 String stackTraceString = Log....
  • 函数调用堆栈追踪(1)

    千次阅读 2018-02-21 19:39:38
    对于一个比较大的工程,函数的调用关系错综复杂,这时候仅仅靠IDE静态查看函数的调用关系已经远远不够,所以为了更好地理解代码,还是有必要去掌握一些方法和工具来跟踪函数调用堆栈。 一、 设断点 在eclipse里...
  • C++——函数调用堆栈

    千次阅读 2017-10-25 22:19:24
    函数调用堆栈
  • 经常需要进行动态调试栈回溯,查看Java函数的调用流程,Android的smali动态调试又不是很方便,因此使用Android的Java Hook的方法,打印Java函数调用堆栈信息辅助静态分析。package com.xposedd...
  • 获取函数调用堆栈

    千次阅读 2020-02-05 11:35:53
    函数用于获取当前线程的调用堆栈,获取的信息将会被存放在buffer中,它是一个指针列表。参数 size 用来指定buffer中可以保存多少个void* 元素。函数返回值是实际获取的指针个数,最大不超过size大小。 在buffer中的...
  • lua 打印函数调用堆栈

    千次阅读 2017-03-02 15:47:34
    --hook lua 函数 A if s.name == 'A' then print('called function A') end --只hook C的函数 if s.short_src == '[C]' and s.name == 'max' then print_stack('called max') end ...
  • 在Linux内核调试中,经常用到的打印函数调用堆栈的方法非常简单,只需在需要查看堆栈的函数中加入: dump_stack(); 或 __backtrace(); 即可 dump_stack()在~/kernel/ lib/Dump_stack.c中定义 void dump_stack(void)...
  • linux下追踪函数调用堆栈

    千次阅读 2019-09-26 00:47:38
    Linux下追踪函数调用堆栈 文章目录Linux下追踪函数调用堆栈0x01 backtrace函数0x02 backtrace_symbols函数0x03 backtrace_symbols_fd函数0x04 使用案例 一般察看函数运行时堆栈的方法是使用GDB之类的外部调试器,...
  • 调用堆栈是当前函数之前的所有已调用函数的列表,每个函数及其变量都被分配了一个 "栈帧",使用 GDB 查看函数调用堆栈可清晰地看到各个函数的调用顺序以及各函数的输入形参值,是分析程序的执行流程和输入依赖的重要...
  • 通过EBP EIP来找函数调用堆栈 通过EBP EIP来找函数调用堆栈 通过EBP EIP来找函数调用堆栈 通过EBP EIP来找函数调用堆栈
  • 详细解释了函数调用过程中堆栈的处理过程,主要对新手来说可以通过这个提升一下自己对函数调用过程的了解
  • 深入函数调用堆栈(学习篇)

    千次阅读 2018-04-06 10:42:35
    每一个程序的执行都使用了栈,没有栈就没有函数,没有局部变量,栈被定义为一个特殊的容器,用户可以将数据压入栈中(入栈,push),也可以将已经压入的数据弹出(pop,出栈),但栈这个...栈保存了一个函数调用所...
  • 函数调用堆栈

    千次阅读 2015-08-18 15:38:49
    熟悉函数调用时的堆栈操作是学好汇编语言的必备知识,在此只写出了最简单的函数调用过程 , 有错误的地方,欢迎批评指正. 注:该程序通过VS2012编译. 函数调用方式为C调用方式 : A. 用栈自右向左传参 B : 调用者平衡...
  • 函数递归调用堆栈分析.doc
  • 一般察看函数运行时堆栈的方法是...在头文件"execinfo.h"中声明了三个函数用于获取当前线程的函数调用堆栈Function: int backtrace(void **buffer,int size)该函数用与获取当前线程的调用堆栈,获取的信息将会被存放...
  • 在Linux中打印函数调用堆栈

    千次阅读 2018-08-21 14:27:16
    https://www.cnblogs.com/sky-heaven/p/5889612.html gcc myfun.c -o myfun -rdynamic #include &lt;stdio.h&gt; #include &lt;execinfo.h&gt; #include &lt;...#define...
  • windows环境下C++代码打印函数堆栈调用情况

    千次阅读 多人点赞 2019-09-04 15:38:39
    程序运行的过程中,函数之间的是会相互调用的,在某一时刻函数之间的调用关系,可以通过函数调用堆栈表现出来,这个调用堆栈所展现的就是函数A调用了函数B,而函数B又调用了函数C,这些调用关系在代码中都是静态的,...
  • 关于什么是函数调用堆栈在上篇文章《windows环境下C++代码打印函数堆栈调用情况》中已经介绍过了,简单的来说就是可以展现出函数之间的调用关系,上篇文章展示了如何在windows上打印出函数调用堆栈,其中用到了...
  • cpp层打印调用栈 方法一 一、在需要加堆栈的文件(*.cpp)里 #if defined(ENABLE_MBACKTRACE) &&...二、查找调用函数(此cpp的某个函数某个位置) #if defined(ENABLE_MBACKTRACE) &&...
  • 调用堆栈是当前函数之前的所有已调用函数的列表,每个函数及其变量都被分配了一个”栈帧”,使用 GDB 查看函数调用堆栈可清晰地看到各个函数的调用顺序以及各函数的输入形参值,是分析程序的执行流程和输入依赖的...
  • 关于函数调用堆栈

    千次阅读 2018-04-27 17:43:44
    函数调用堆栈过程 函数的创建都是创建在内存的栈区里面的,所以函数的调用方式也是一种后进先出: 我们先来看一段代码: #include&lt;stdio.h&gt; #include&lt;stdlib.h&gt; in...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 258,740
精华内容 103,496
关键字:

函数调用堆栈

友情链接: naomei.zip