gdb调试栈信息

当我们的程序停止时,我们首先需要知道的是它在哪里停止,以及它是如何到达那里的。

每次程序执行函数调用时,都会生成有关调用的信息。这些信息包括调用在程序中的位置、调用的参数以及被调用函数的局部变量。信息保存在称为堆栈帧的数据块中。堆栈帧在称为调用堆栈(call stack)的内存区域中分配。

当程序停止时,用于检查堆栈的GDB命令允许您查看所有这些信息。

One of the stack frames is selected by GDB and many GDB commands refer implicitly to the selected frame. In particular, whenever you ask GDB for the value of a variable in your program, the value is found in the selected frame. There are special GDB commands to select whichever frame you are interested in. See Selecting a Frame.

当程序停止时,GDB会自动选择当前正在执行的帧并简要描述它,类似于frame命令(请参阅有关帧的信息)。

https://sourceware.org/gdb/onlinedocs/gdb/Stack.html#Stack

一、栈帧

调用栈被分成连续的部分,称为栈帧,简称帧;每个帧是与一个函数的一个调用相关联的数据。帧包含给定给函数的参数、函数的局部变量以及函数执行的地址。

当程序启动时,栈只有一个帧,即main函数的帧。这称为初始帧或最外层帧。每次调用函数时,都会生成一个新的帧。每次函数返回时,该函数调用的帧都被抹除。如果一个函数是递归的,那么同一个函数可以有许多帧。实际执行的函数的帧称为最内层帧。这是所有仍然存在的堆栈帧中最近创建的。

在程序中,栈帧由它们的地址标识。堆栈帧由许多字节组成,每个字节都有自己的地址;每种计算机都有一个选择一个字节作为帧地址的约定。通常,当在该帧中执行时,该地址保存在称为帧指针寄存器的寄存器中(参见$fp)。

GDB用一个级别(level)标记每个现有的栈帧,最内层的栈帧级别为0,调用它栈帧级别为1,以此类推。这些级别号提供了一种在GDB命令中指定栈帧的方法。

一些编译器提供了一种不使用栈帧编译函数的方法(例如,GCC选项-fomit-frame-pointer就会在没有栈帧的情况下生成方程)。这通常发生在做大量使用的库函数,以节省帧设置时间。GDB处理这些函数调用的功能有限。如果最内层的函数调用没有栈帧,GDB仍然将其视为有一个单独的帧,该帧通常编号为零,从而允许正确跟踪函数调用链。但是,GDB没有为栈中的其他地方提供无栈帧函数。

https://sourceware.org/gdb/onlinedocs/gdb/Frames.html#Frames

二、回溯

回溯是我们的程序如何到达目的地的总结。对于许多帧,它每帧显示一行,从当前正在执行的帧(第0帧)开始,然后是它的调用者(第1帧),最后是栈。

要打印整个栈的回溯,我们可以使用backtrace命令或其别名bt。此命令将为栈中的每个帧打印一行。默认情况下,命令会打印所有栈帧。我们可以通过键入系统中断字符(通常是Ctrl-c)随时停止回溯。

使用方式:

backtrace [option][qualifier][count]
bt [option][qualifier][count]

回溯中的每一行都显示了帧号(也就是帧级别)和函数名。程序计数器值也会显示(除非我们使用了set print address off)回溯还会显示源文件名和行号,以及函数的参数。如果程序计数器值位于该行号的代码开头,则忽略该值。

下面就是回溯输出的一个例子,这个输出由命令bt 3给出,含义是输出最内层的三层栈帧。

#0  m4_traceon (obs=0x24eb0, argc=1, argv=0x2b8c8)
    at builtin.c:993
#1  0x6e38 in expand_macro (sym=0x2b600, data=...) at macro.c:242
#2  0x6840 in expand_token (obs=0x0, t=177664, td=0xf7fffb08)
    at macro.c:71
(More stack frames follow...)

可以看到,第0帧的显示不是以程序计数器值开始的,这表示我们的程序已在builtin.c的第993行代码的开头停止。

第1帧中的参数数据值已替换为…。默认情况下,GDB仅当参数是标量(整数、指针、枚举等)时才打印参数值。有关如何配置函数参数值的打印方式的详细信息,请参见“打印设置”中的“命令集打印帧参数”。命令set print frame info(请参阅打印设置)控制打印的帧信息。

如果我们的程序在编译时使用了优化选项,那么一些编译器会优化传递给函数的参数,如果这些参数在调用后从未使用过。这种优化生成的代码通过寄存器传递参数,但不会将这些参数存储在栈帧中。GDB无法在栈帧中显示这样的参数,除了最内层的参数。这时回溯看起来可能是这样的:

#0  m4_traceon (obs=0x24eb0, argc=1, argv=0x2b8c8)
    at builtin.c:993
#1  0x6e38 in expand_macro (sym=<optimized out>) at macro.c:242
#2  0x6840 in expand_token (obs=0x0, t=<optimized out>, td=0xf7fffb08)
    at macro.c:71
(More stack frames follow...)

未保存在堆栈帧中的参数值显示为<optimized out>

如果我们需要显示这样的优化输出参数的值,可以从其他变量中推断出来,这些变量的值取决于您感兴趣的变量,我们也可以不经过优化而重新编译。

大多数程序都有一个标准的用户入口点,即系统库和启动代码转换为用户代码的地方。对于C,这是main。当GDB在回溯中找到entry函数时,它将终止回溯,以避免跟踪到高度系统特定的(通常是无趣的)代码中。

https://sourceware.org/gdb/onlinedocs/gdb/Backtrace.html#Backtrace

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值