精华内容
下载资源
问答
  • 函数调用堆栈

    2017-10-29 23:25:39
    1.函数调用堆栈   压栈  调用函数:  1.压入形参变量的地址和值  2.压入函数调用返回后要执行的指令的地址  被调用函数:  1.压入调用函数的底指针,把底指针寄存器指向被调用函数的底  2....
    1.函数调用堆栈

     

    压栈

           调用函数:
               1.压入形参变量的地址和值
               2.压入函数调用返回后要执行的指令的地址
          被调用函数:
              1.压入调用函数的栈底指针,把栈底指针寄存器指向被调用函数的栈底

              2.开辟被调用函数的栈帧大小,并初始化为CC

    清栈

         被调用函数
              1.清理被调用函数开辟的栈帧大小
              2.回退栈底指针到调用函数的栈底
              3.把回退到调用函数要执行的指令地址给pc寄存器
        调用函数:

             1.清理形参变量的内存




    2.函数的返回值
       1.    <=4 eax  值
       2.    <4<=8 eax edx
       3.    >8 开辟一个临时量(函数调用之前就产生,被调用函数return处通过ebp-8访问)
        eax  带出临时量的地址

        循环拷贝到接收返回值的变量的地址空间


    3.函数的调用约定
    _cdecl           c的调用约定
    _stdcall         Windows下的标准调用约定
    _fastcall        快速调用约定
    _thiscall        C++的成员函数调用约定

    1.符号的生成方式不同
    ld 合并符号表
      main: 符号表 _cdecl    ?sum@@YAHHH@Z  *UND*  global
       符号解析    
      test:        _stdcall  ?sum@@YGHHH@Z  .text  global
                   _fastcall ?sum@@YIHHH@Z
    2.参数的压栈方式不同(参数入栈顺序不同)
    3.函数的形参内存清理
        _cdecl  调用方开辟  调用方清理
        _stdcall 调用方开辟  被调用方清理
        _fastcall
         <= 8  
         寄存器带值   不需要形参变量的清理
         >  8
         前2个形参寄存器带值   不需要形参变量的清理
         后面的和stdcall调用方式一样

    展开全文
  • 函数调用 堆栈

    2014-12-29 16:07:37
    很多资料都说堆栈是c语言里函数调用的基础。具体,堆栈在c语言里和在系统里有哪些具体的作用。我在网上和书上查了一下资料,现在总结一下,以供大家参考。 我们一般说堆栈的时候都强调堆栈函数调用时把被调用的...

    最近在学习嵌入式系统移植时,一直有一个相关概念就是堆栈。很多资料都说堆栈是c语言里函数调用的基础。具体,堆栈在c语言里和在系统里有哪些具体的作用。我在网上和书上查了一下资料,现在总结一下,以供大家参考。

    我们一般说堆栈的时候都强调堆栈是函数调用时把被调用的函数所需参数压入栈中,以实现函数的调用。

    但是,函数调用具体是怎么实现的,我们一般不讲。现在总结一下堆栈的具体作用:

    1,传递参数(为被调用函数提供参数)

    2,保存局部变量

    3,保存中间变量

    4,在系统中用堆栈保存任务的状态(例如各个寄存器的值)

    堆栈有后进先出的特性,所以能帮我们做很多事情。一会我们通过实例分析时你就会有感触。

    下面先说一下看下面的具体事例分析时所需要的基础知识:

    1.什么是堆栈
          编译器一般使用堆栈实现函数调用。堆栈是存储器的一个区域,嵌入式环境有时需要程序员自己定义一个数组作为堆栈。Windows为每个线程自动维护一个堆栈,堆栈的大小可以设置。编译器使用堆栈来堆放每个函数的参数、局部变量等信息。
          函数调用经常是嵌套的,在同一时刻,堆栈中会有多个函数的信息,每个函数占用一个连续的区域。一个函数占用的区域被称作帧(frame)。【每个函数都占有一个帧区。就是为了区分开这个函数的框架,一会我们分析具体实例时,我们就会有体会了】


          编译器是从高地址开始使用堆栈。 
          在多线程(任务)环境,CPU的堆栈指针指向的存储器区域就是当前使用的堆栈。切换线程的一个重要工作,就是将堆栈指针设为当前线程的堆栈栈顶地址。 
          不同CPU,不同编译器的堆栈布局、函数调用方法都可能不同,但堆栈的基本概念是一样的。【我们说的是x86机器的,因为我们比较容易做实验】

    1.1堆栈相关寄存器:


    esp:堆栈指针(stack pointer),指向系统栈最上面一个栈帧的栈顶
    ebp: 基址指针(base pointer),指向系统栈最上面一个栈帧的底部
    cs:eip:指令寄存器(extended instruction pointer),指向下一条等待执行的指令地址
    注:ebp在C语言中用作记录当前函数调用基址。

    1.2堆栈操作


    push:以字节为单位将数据(对于32位系统可以是4个字节)压入栈,从高到低按字节依次将数据存入ESP-1、ESP-2、ESP-3、ESP-4的地址单元。
    pop: 过程与PUSH相反。
    call: 用来调用一个函数或过程,此时,下一条指令地址会被压入堆栈,以备返回时能恢复执行下条指令。

    leave:当调用函数调用时,一般都有这两条指令:pushl %ebp   movl %esp, %ebp  而,leave是这两条指令的反操作。
    ret: 从一个函数或过程返回,之前call保存的下条指令地址会从栈内弹出到EIP寄存器中,程序转到CALL之前下条指令处执行。
    注:
    call指令的两个作用
    ①将下一条指令的地址A保存在栈顶
    ②设置eip指向被调用程序代码开始处

    1.3函数堆栈框架的形成(C语言中)


    ①执行call XXX之前
    cs : eip原来的值指向call下一条指令,该值被保存到栈顶
    然后cs : eip的值指向xxx的入口地址
    ②进入 XXX
    第一条指令: pushl %ebp                  //意为保存调用者的栈帧地址
    第二条指令: movl %esp, %ebp        //初始化XXX的栈帧地址
    然后函数体中的常规操作,可能会压栈、出栈
    ③退出XXX
    movl %ebp,%esp
    popl %ebp
    ret

    2.函数调用约定
          函数调用约定包括传递参数的顺序,谁负责清理参数占用的堆栈等,如下面这个主要的函数约定表显示的 :
     
                         函数调用约定                参数传递顺序  谁负责清理参数占用的堆栈
                         __pascal                       从左到右         调用者
                         __stdcall                       从右到左         被调函数
                         __cdecl                         从右到左         调用者

          调用函数的代码和被调函数必须采用相同的函数的调用约定,程序才能正常运行。在Windows上,__cdecl是C/C++程序的缺省函数调用约定。在linux下gcc默认用的规则是__stdcall ( 一会我们分析的函数就是在linux下用c语言源码和反汇编语言对比分析一下函数调用的具体实现)
          在有的cpu上,编译器会用寄存器传递参数,函数使用的堆栈由被调函数分配和释放。这种调用约定在行为上和__cdecl有一个共同点:实参和形参数目不符不会导致堆栈错误。
          不过,即使用寄存器传递参数,编译器在进入函数时,还是会将寄存器里的参数存入堆栈指定位置。参数和局部变量一样应该在堆栈中有一席之地。参数可以被理解为由调用函数指定初值的局部变量。

    ############################################3

    好了,说了这么多,都是针对这个问题的基础知识。还有,你要完全看懂下面要举得具体例子还要有一定x86汇编语言的基础(当然是很简单的)。看这么长的文章一定要有耐心,希望看完之后对你有所帮助。

    好了,我们开始列举具体事例来说明问题:

    c语言源码如下:

     

     

    我们要把c语言源码编译成对应的汇编语言有以下两种方法,

    一:使用gcc -S 文件名【我这里是diaoyong.c】 这样可以生成一个和源文件同名的汇编文件。

    二:使用gdb工具调试。在gdb工具里用disassemble +文件名来反汇编c语言文件。

    下面是我用gcc -S 命令编译出的汇编代码:

    function:
            pushl   %ebp //保存原来的函数帧的栈底(当然这里是main函数的帧栈底部)
            movl    %esp, %ebp//此命令是把esp的值传送到ebp中去,还记得esp是什么吗,对时堆栈栈顶指针。
            movl    8(%ebp), %eax//把ebp的地址加上8里的值取出赋给eax
            sall    $1, %eax//左移一位实现乘2操作
            leave//相当于movl %esp, %ebp  pushl %ebp   
            ret//返回到调用函数
    .Lfe1:
            .size   function,.Lfe1-function
    .globl main
            .type   main,@function
    main:
            pushl   %ebp 保存原来的函数帧的栈底(这里当然是调用main函数的帧栈底部)
            movl    %esp, %ebp//此命令是把esp的值传送到ebp中去。和function一样,这也说明main()函数没有什么特殊的,只是层次稍微高了一点点而已。
            subl    $8, %esp//堆栈指针向下移动8个字节,为局部变量i j 分配空间
            andl    $-16, %esp//
            movl    $0, %eax
            subl    %eax, %esp
            subl    $12, %esp//红色部分是为了让堆栈指针是16 的倍数。至于为什么要是16的倍数,Linux下的编译器
    GCC默认的堆栈是16字节对齐的,可能有些人要问为什么要对齐,对齐其实为了加快CPU的访问效率,这里你记住这点就可以了。

            pushl   $10//把需要的参数压入栈中
            call    function//呼叫被调用函数
            addl    $16, %esp
            movl    %eax, -4(%ebp)//把function返回值送到eax中
            movl    $0, %eax//把eax清零
            leave
            ret

    下面做详细分析:

    看看上面的汇编代码,和前面一样的不分析。但是其中有句不一样:subl $8 %esp ; 因为主函数里有两个临时变量i, j;给临时变量腾出8个字节空间。在看看下面的代码:

    movl $10, (%esp) #====> %esp = 800, (800) = 10 ,其中800是我们假设的地址值,(800)表示地址800的内容这里的(%esp)指的是%esp地址里的内容,刚才我们假设这时候%esp的值是800, 那么地址为800的内容就是10了。执行函数调用了,注意在调用函数前其实是先把函数调用指令 call之后的地址压栈,也就是call之后那条指令的IP值压栈,所以这时候 %esp = 796;这里要弄明白为什么要把下条指令地址压栈,假设如果不把IP值压栈,那么当函数调用完毕后怎么能找到函数调用时的地址呢?也就是说如果没把IP压栈,那么函数调用完之后就回不到原来的执行地址了,就会造成程序执行顺序的错误!

    下面列出函数function的汇编代码:

    function:
    pushl %ebp
    movl %esp, %ebp
    movl 8(%ebp), %eax
    sall    $1, %eax
    popl %ebp

    leave
    ret

    pushl %ebp; 经过这条指令后 %esp值减4,所以这时候%esp值是792。下面这句:

    movl %esp, %ebp #==============> %ebp = 792, %esp = 792, (792) = %ebp ;其中(792)表示地址792的内容

    movl 8(%ebp), %eax #========> %eax = 10

    上面这句很多人可能不明白了,8(%ebp)指的是什么?8(%ebp)等于 : (%ebp + 8) ,这里注意,%ebp + 8 是表示一个地址值,加上括号表示存储在该地址上的内容。所以8(%ebp)其实就是地址为800的值,看前面地址800的值刚好是10!所以这句其实是把10复制给%eax寄存器.

     sall    $1, %eax#======> %eax = 20//左移相当于乘2

    相当于2 * %eax, %eax这时候等于20了,刚好是实现了C代码中的 (2 * i);

    popl %ebp #=========> 恢复%ebp寄存器的值, %esp这时候等于796

    ret #=========> 函数调用完毕返回,这句其实是把刚才压栈的IP值弹出栈,执行这条指令后 %esp = 800

    # 800!想想我们在调用函数的时候%esp也是800啊!这就是实现了“清栈”了,就是把调用函数所在的栈清除了!

    好了,函数 function的汇编代码分析完了,现在回头继续看看main函数里的下一条指令了。接下来是这句:

    movl %eax, -4(%ebp)

    %eax寄存器存放的是什么?看function函数的代码,可以知道其实就是(2 * i)的值,所以返回值其实是通过%eax来传递的!传递到-4(%ebp)里去了,-4(%ebp) = (%ebp - 4); -4(%ebp)到底是什么呢?看看C代码,返回值传给变量j,那么-4(%ebp)会不会就是j呢?答案是肯定的!我们先看看%ebp的值是什么。看看 main函数的汇编代码,可以得出,%ebp其实指向了main函数的栈底部,但记不记得前面说的subl $24, %esp是为临时变量而留出的空间?没错,-4(%ebp) 就是存储在临时变量区域!也就是变量 j 了。

    好了,说到这是不是有点晕了,没事,我画个图你就很明白了。 

     

     

     

    即 ,调用函数时c语言会利用堆栈来做一个函数调用框架。压入栈中的每一个函数都有一个帧栈,每个帧栈都以ebp为分界线。

    如最上层的函数,堆栈指针指向栈顶,而ebp指向帧栈底。而其他帧栈底部的指针ebp都压入到了栈中。当最外层的函数调用结束后,把ebp的值给dsp作为下一个函数的帧栈顶,而把压在栈中的ebp弹出做当前的帧栈底。如此往复。

    好了,先写到这。

    展开全文
  • 主要介绍了JavaScript实现显示函数调用堆栈的方法,实例分析了JavaScript显示函数调用堆栈的具体作用与使用方法,需要的朋友可以参考下
  • C语言函数调用栈(一)

    2021-03-03 18:28:54
    程序的执行过程可看作连续的函数调用。当一个函数执行完毕时,程序要回到调用指令的下一条指令(紧接...因此函数调用栈的实现与处理器寄存器组密切相关。Intel32位体系结构(简称IA32)处理器包含8个四字节寄存器,如下图
  • C++——函数调用堆栈

    千次阅读 2017-10-25 22:19:24
    函数调用堆栈

    汇编分为两种:

      1.inter x86:从右往左

      2.AT&T unix:从左往右

    函数调用堆栈

    源代码:

    反汇编代码:


    ebp:栈底指针  esp:栈顶指针,在栈上开辟内存,通过ebp指针的偏移量来访问数据

    10: int a = 10;

    002E143E mov        dword ptr [ebp-8],0Ah  ,将a入栈,

       11: int b = 20;

    002E1445 mov        dword ptr [ebp-14h],14h  ,将b入栈

       12: int ret = 0;

    002E144C mov        dword ptr [ebp-20h],0  ,将ret入栈

     压栈

     调用函数:

         1.压入形参变量的地址和值,

         2.压入函数调用返回后要执行的指令的地址

     被调用函数:

         1.压入调用函数的栈底指针,把栈底指针寄存器指向被调用函数的栈底

         2.开辟被调用函数的栈帧大小,并初始化为CC


     清栈

     被调用函数

         1.清理被调用函数开辟的栈帧大小

         2.回退栈底指针到调用函数的栈底

         3.把回退到调用函数要执行的指令地址给pc寄存器

     调用函数:

          1.清理形参变量的内存


    2.函数的返回值

      1.4 eax 

      2.8 eax edx

      3.>8开辟一个临时量(调用方函数栈帧)

       eax 带出临时量的地址

       循环拷贝到接收返回值的变量的地址空间


    3.函数的调用约定

    _cdecl c的调用约定

     

    _stdcall Windows下的标准调用约定

    _fastcall快速调用约定

     

    _thiscall C++的成员函数调用约定

     

    三种调用约定的区别:

    1.符号的生成方式

    ld合并符号表

     main:符号表 _cdecl   ?sum@@YAHHH@Z *UND* gobal

      符号解析   

     test:       _stdcall ?sum@@YGHHH@Z .text gobal

                  _fastcall ?sum@@YIHHH@Z

    2.参数的压栈方式

    3.函数的形参清理

           _cdecl 调用方开辟 调用方清理

           _stdcall调用方开辟 被调用方清理

           _fastcall

            <= 8 

            寄存器带值  不需要形参变量的清理

            > 8

            2个形参寄存器带值  不需要形参变量的清理

            后面的和stdcall调用方式一样






    展开全文
  • 【C++】函数调用堆栈

    2019-07-20 18:44:19
    函数调用堆栈过程: 1.实参从右向左入栈,为形参初始化; 2.将下一条指令地址入栈; 3.压入调用方函数的底指针寄存器的值,即底地址; 4.将edp移动到被调用方底; 5.跳转到被调用方函数栈帧,开辟被...

    函数调用堆栈过程:

        1.实参从右向左入栈,为形参初始化;

        2.将下一条指令地址入栈;

        3.压入调用方函数的栈底指针寄存器的值,即栈底地址;

        4.将edp移动到被调用方栈底;

        5.跳转到被调用方函数栈帧,开辟被调用方函数的运行空间,并初始化为0xcccc cccc。

    展开全文
  • Linux下追踪函数调用堆栈 文章目录Linux下追踪函数调用堆栈0x01 backtrace函数0x02 backtrace_symbols函数0x03 backtrace_symbols_fd函数0x04 使用案例 一般察看函数运行时堆栈的方法是使用GDB之类的外部调试器,...
  • C++ 获取函数调用堆栈的 高效实现代码
  • PHP查询函数调用堆栈

    2021-05-10 16:34:26
    PHP查询函数调用堆栈。 比如: 有函数A,函数B,函数C 函数A调用函数B,函数B再调用函数C,在函数C打印调用堆栈的话,就可以看到C<-B<-A这样的信息 获取堆栈信息用系统自带函数debug_backtrace() 代码: ...
  • 函数调用栈

    千次阅读 2012-08-06 19:22:38
    理解调用栈最重要的两点是:栈的结构,EBP寄存器的作用。 首先要认识到这样两个事实: 1、一个函数调用动作可分解为:零到多个PUSH指令(用于参数入栈),一个CALL指令。CALL指令内部其实还暗含了一个将返回地址...
  • 利用backtrace函数打印函数调用栈

    千次阅读 2016-10-24 13:05:17
    我们一般打印函数调用栈可以选择GDB的backtrace命令,简写bt命令都可以。这里有一个新的方法,那就是利用backtrace函数打印,不过还要加上backtrace_symbols()函数。 int backtrace(void **buffer, int size) 该...
  • 函数调用堆栈过程 调用约定 函数的调用约定很多,常见的包括__stdcall,__cdecl,__fastcall,__thiscall等等。主要的区别在于约束的三个事件,一个是参数传递是从左开始呢还是从右开始,还有就是堆栈清理的清理方是...
  • python查看函数调用栈

    千次阅读 2019-03-04 18:35:50
    我在看开源框架代码的时候,有时候好几天...这个时候就需要记录一下函数调用栈。 源码如下 def Caller(func): def f(*args,**kwargs): import sys from oslo_log import log as logging LOG = logging.getLog...
  • 在Linux应用程序中打印函数调用栈

    千次阅读 2019-03-08 11:10:58
    在Linux中打印函数调用栈 要求 在Linux系统的应用程序中写一个函数print_stackframe(),用于获取当前位置的函数调用栈信息 方法 execinfo.h库下的函数backtrace可以得到当前线程的函数调用栈指针和调用栈深度,...
  • 计算机工作的三个法宝:计算机存贮程序,函数调用堆栈和中断 堆栈是高级语言的开始 esp 堆栈指针 ebp 基址指针 push 栈顶指针较少4个字节 pop 栈顶指针增加4个字节 ebp 在c语言中用作记录当前函数调用的基址。...
  • 函数调用堆栈的过程

    2019-07-23 20:54:56
    函数在进行调用时会产生开和清的操作,那么就来介绍一下函数调用堆栈的过程吧 首先,利用一个小例子来研究这个过程: #include<iostream> int Fun(int a,int b) { int tmp = 0; tmp = a + b; ...
  • 函数调用堆栈

    千次阅读 2015-08-18 15:38:49
    熟悉函数调用时的堆栈操作是学好汇编语言的必备知识,在此只写出了最简单的函数调用过程 , 有错误的地方,欢迎批评指正. 注:该程序通过VS2012编译. 函数调用方式为C调用方式 : A. 用自右向左传参 B : 调用者平衡...
  • C语言函数调用栈

    2020-06-02 11:34:12
    函数调用过程通常使用堆栈实现,每个用户态进程对应一个调用栈结构(call stack)。编译器使用堆栈传递函数参数、保存返回地址、临时保存寄存器原有值(即函数调用的上下文)以备恢复以及存储本地局部变量。 不同...
  • 获取函数调用堆栈

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

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 570,622
精华内容 228,248
关键字:

函数调用栈