使用 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