GDB查看栈信息

使用 GDB 调试程序时,当程序发生中断,我们首先应该知道程序在哪里产生中断以及产生中断的原因是什么?函数发生调用时,相关的调试信息就已经产生,并且被存储在一块被称为栈帧的数据里。

栈帧是在调用栈的内存区域里分配的,是调用栈划分的连续的区块,简称为栈。每个帧是一个函数调用另一个函数的相关数据,包含了传递给本地用函数的参数,这个函数的本地变量和这个函数的执行地址。
 
在函数开始的时候栈中只有一个帧,是 main 函数的,这个帧称为初始帧或者是最外层的帧。每当一个函数被调用,就产生一个新的栈帧。当函数返回时,这个调用所属的帧就被销毁了。如果调用的是递归函数,那么同一个函数就可能有多个帧。当前正在执行的函数调用的帧成为最内层的帧,这是最近创建的帧,同时还有别的帧存在。程序内部的栈帧用地址标识,一个栈帧有许多的字节组成,每个字节都有自己的地址。

GDB为所有现存的栈帧编号,从最内层帧 0 开始,1 是这个函数调用的帧,以此类推。这些编号并不真正存在于程序里:他们是由 GDB 分配,用于 GDB 的命令来区分栈帧。

显示栈帧信息

显示栈帧信息的命令主要有 frame 和 backtrace,下面是对这两个命令的介绍。

1.frame的命令格式展示如下:

frame

使用 frame 命令会打印出当前调用栈的信息,这些信息包含:栈帧的层编号,当前的函数名,函数参数值,函数所在文件及行号,函数执行到的语句。命令可以缩写为 f。

2.backtrace的命令格式展示如下:

backtrace <n>

不带参数:打印当前调用函数的栈帧信息,每个栈帧显示一行。
带参数:n 为正整数时,表示打印栈顶 n 层的栈信息;n 为负整数时,那么表示打印栈底 n 层的栈信息。
 
如果我们想要获取更详细的当前栈帧层的信息,可以使用命令:

info frame

打印出的大多数都是运行时的内地址。比如:函数地址,被调用的函数地址,当前函数是由什么样的语言写成的、函数参数地址及值、局部变量的地址。

切换到其他栈帧

1.切换到任意的栈帧使用 frame 相关的命令格式如下:

frame <n>

n 表示栈帧的标号,这个命令可以从一个堆栈帧转到另一个,并打印所选的堆栈帧。对于多个堆栈帧,从当前执行的栈帧开始,下面就是显示这个在调用的函数的栈帧。实例:

(gdb) frame 3

切换到标号为 3 的栈帧。

2.从当前的栈帧层向上移动,命令格式表示:

up <n>

n 表示栈帧的标号,在堆栈里上移 n 帧,对于正数向外层的帧移动,更高编号的帧,存在更长的时间的帧。实例:

(gdb) up 3

移动到编号为“当前栈帧的编号 + 3”的栈帧。

3.从当前的栈帧层向下移动,命令格式表示:

down <n>

n 表示栈帧的标号,在堆栈里下移 n 帧。对于正数n,向内层的帧移动,更低编号的帧,新创建的帧。实例:

(gdb) down 3

移动到编号为“当前栈帧的编号 - 3”的栈帧。

实例:代码中在main函数中调用的是一个递归函数,可以形成多个帧,使用上面的命令进行调试。

(gdb) l
1     #include <stdio.h>
2     int func(int a)
3     {
4            if(a == 1)
5                   return 1;
6            else
7                   return a + func(a - 1);
8     }
9    
10  
(gdb)
11   int main(void)
12   {
13          int sum = 0;
14          sum = func(100);
15          printf("%d\n",sum);
16  
17          return 0;
18   }

(gdb)
Line number 19 out of range; test.c has 18 lines.
(gdb) break func           //设置断点,func函数入口地址
Breakpoint 1 at 0x555555554655: file test.c, line 4.
(gdb) run
Starting program: /home/wjc/hsxy/lianxi/10/test/a.out
 
Breakpoint 1, func (a=100) at test.c:4
4            if(a == 1)

(gdb) continue
Continuing.
 
Breakpoint 1, func (a=99) at test.c:4
4            if(a == 1)

(gdb) continue
Continuing.
 
Breakpoint 1, func (a=98) at test.c:4
4            if(a == 1)
(gdb) bt
#0  func (a=98) at test.c:4
#1  0x000055555555466f in func (a=99) at test.c:7
#2  0x000055555555466f in func (a=100) at test.c:7
#3  0x0000555555554691 in main () at test.c:14

(gdb) info frame            //打印当前堆栈帧的信息
Stack level 0, frame at 0x7fffffffddb0:
 rip = 0x555555554655 in func (test.c:4); saved rip = 0x55555555466f
 called by frame at 0x7fffffffddd0
 source language c.
 Arglist at 0x7fffffffdda0, args: a=98
 Locals at 0x7fffffffdda0, Previous frame's sp is 0x7fffffffddb0
 Saved registers:
  rbp at 0x7fffffffdda0, rip at 0x7fffffffdda8

(gdb) bt 2                  //显示栈顶的两层的信息
#0  func (a=98) at test.c:4
#1  0x000055555555466f in func (a=99) at test.c:7
(More stack frames follow...)

(gdb) bt -2               //显示栈底的两层的信息
#2  0x000055555555466f in func (a=100) at test.c:7
#3  0x0000555555554691 in main () at test.c:14

(gdb) frame 0            //转到0层的堆栈帧
#0  func (a=98) at test.c:4
4            if(a == 1)

(gdb) up 1
#1  0x000055555555466f in func (a=99) at test.c:7
7                   return a + func(a - 1);

(gdb) down 1
#0  func (a=98) at test.c:4
4            if(a == 1)

(gdb) info args
a = 98