1 backtrace和frame

一般来说,查看函数调用栈,主要是为了研究函数的调用过程。

一般使用下面的命令进行查看:

  • backtrace

    • 查看函数的调用顺序(函数调用栈的信息)
  • frame N

    • 切换到栈编号为N的上下文中(具体栈编号是什么在下面的实际案例中会有)
  • info frame

    • 查看当前函数调用栈帧的信息

至于什么是栈帧信息,大概就是下图的样子,这里不再多介绍,后面还会有文章学习函数栈帧的概念,或者推荐大家去阅读程序员的自我修养。

  • 上面有一个info frame命令,我们在前几篇文章已经学习过info的几个命令。下面再介绍几个下图中的info命令:

2 使用GDB进行函数调用栈的查看的实际代码案例

我们还是给出以下代码,作为这次调试的代码:

frame.c

#include <stdio.h>


int sum(int n)
{
    int ret = 0;
    
    if( n > 0 )
    {
        ret = n + sum(n-1);
    }
    
    return ret;
}


int main()
{
    int s = 0;
    
    s = sum(10);
    
    printf("sum = %d\n", s);
    
    return 0;
}

上述代码很简单,sum函数是一个递归的求解过程,最终求得1+2+3+…+n

  • 开始进行调试:
  • 首先将程序编译,并打开gdb调试,这在前几篇文章已经做过很多次,大概如下图所示的步骤:

  • 然后我们再sum函数处打一个断点,并给出条件,当n==0的时候断点成立

  • break sum if n==0

  • 查看断点是否打上:info breakpoints

  • 运行程序:continue

  • 运行上述几个步骤后,程序运行到sum函数,并在sum函数递归调用到n==0的时候停止:

  • 此时,函数调用被中断,我们现在来使用backtrace命令来查看之前sum函数的调用栈的顺序(左侧的#0 ,#1…就是栈的编号):

  • 此时程序运行到n==0,本应该继续运行sum函数,但是却被我们的断点中断了。所以此时停在最后一层的sum函数递归调用上。且是停在sum函数中的第6行:

  • 我们连续输入两次next,并且查看当前程序的栈信息:

  • 程序运行到13行停下来了,这一行是本该return的。此时的函数栈中 n==0,ret==0,这个ret就差返回给上一层函数调用了。

  • 现在我们来使用info registers查看当前的函数调用过程的各个寄存器的值,并使用info frame查看当前函数调用过程的函数栈帧的详细信息:

  • 如上图,寄存器比较多,这里我们只关心一个寄存器,ebp,ebp寄存器保存的是调用这个函数的函数(也就是上一个函数,在这里是#1号栈对应的函数)栈帧基地址(old_ebp)。可以看到,此时的函数栈帧中的ebp地址为0xbffff088。注意你自己运行的话地址可能与我的不一样。这个地址中保存的是上一个函数,其实就是1号栈的基地址。我们使用以下命令来查看该地址处的内容:

  • x /1bx 0xbffff088 //显示结果为:

  • 如上图,红框内的内容,就是#1号栈的基地址。当然我们可以验证:连续输入两个next命令,程序就会把返回值返回给#1号栈的函数调用。那么此时再输入info args,n就等于1,因为此时位于#1号栈中。然后在输入info registers命令查看#1号栈的寄存器值信息,如下:

  • 如上图,#1号栈中的ebp值为0xbffff0b8,与我们上面在#0号栈中查询的值是一样的。这与函数栈帧的理论也是完全相符的。

上面的调试内容,非常简单,我们并没有调试什么bug,而是通过上述内容,学习一些调试的技巧。

3 总结

  • 本节内容学习如何使用GDB查看函数的调用栈信息。
  • 本文章参考狄泰软件学院相关课程 想学习的可以加狄泰软件学院群, 群聊号码:199546072

  • 学习探讨加个人(可以免费帮忙下载CSDN资源):

  • qq:1126137994

  • 微信:liu1126137994

  • 学习交流资源分享qq群:962535112