我们在linux下调试程序时,一般都会用到gdb,本文主要介绍一下gdb常用的操作命令以及TUI模式的使用方式。
假设我们有段程序叫main.c,代码如下,
#include
typedef struct {
char name[8];
int age;
int birthYear;
} student;
int func(int data1, int data2)
{
int temp = 100;
return temp + data1 + data2;
}
int main(void)
{
student obj = {
.name = "Lucy",
.age = 2018,
.birthYear = 1990
};
printf("name: %s\n", obj.name);
printf("age: %d\n", obj.age);
printf("birthYear: %d\n", obj.birthYear);
int ret = func(200, 300);
printf("ret: %d\n", ret);
return 0;
}
对main.c进行编译,并开启-g选项生成调试信息,这样gdb才可以调试程序
gcc -g main.c
编译ok后生成a.out。我们开始调试,在terminal下输入gdb ./a.out
,打印如下,
此时处于命令模式,我们可以输入相关命令来进行调试。下面列出常用的调试命令,
b 函数名
,即在指定函数的起始处设置断点,也可以是b 行号
,即在指定代码行设置断点。info b
和info locals
list
或者list 行号
,后者是以指定行号为基准,显示该行号前后的代码有了以上知识后,我们来看下如何调试,
输入b main
并回车,意思是在main函数起始处打断点,然后输入r
并回车,开始执行程序,程序会在main开始处停下来,因为这边设置了断点。
这套操作是调试的基础,因为程序运行都从main函数开始执行,所以我们都会从main开始调试。
此时输入n
并回车,程序就会执行一行代码。每输入一个n并回车,都会执行一行代码。如果一行代码有函数调用,那么输入s
就会进入函数体内,如果输入n
则直接把被调函数执行完。
代码里有个结构体变量obj,我们来查看下它的信息,输入p obj
,
查看结构体里的成员则是p obj.name
,p obj.age
查看obj或obj内的成员变量的地址,
可以看到age的地址减去obj的地址是8,正好是name占据的内存空间。如果我们想用比较raw的方式查看内存空间里的值,可以使用x
,
查看到的值是0x0400,即1024。
或者使用&
符号来直接取地址
修改obj里成员变量的值,使用set obj.age=1024
这个命令就是直接修改内存里的值,这样如果后续代码没有对这个变量重新赋值,那么后面这个变量的值就是我们手动设置的值了。
如果想使用比较raw的方式来修改内存地址上的值,可以按如下操作,
使用了*
这个解引用符号
如果想删除某个断点,就是用delete Num
,这个Num就是使用info查看到的索引(最左侧的Num一列)
查看局部变量是查看当前函数内的局部变量,使用info locals
就可以了。
代码里调用了func,我们先给func打个断点,然后使用c
让代码运行到该断点,然后输入bt
来查看
这里可以看到2个栈帧,一个是func的,一个是main的,因为func是被main调用的。
以上介绍了gdb的一些常用操作,对于命令的使用方法也可以在gdb命令行下使用help 命令
来查看帮助信息。
经过第一节,我们知道了gdb的一些常用操作方式,但是这种使用方法都是在命令行下进行的,使用起来非常不方便,假如我们想在某行打断点,需要使用list来显示源码,很麻烦。
基于此,gdb提供了一种可视化的调试模式,即TUI(Text User Interface的缩写),这个模式使用了curses库来进行图形界面显示。
首先执行gdb ./a.out
,然后Ctrl+x+a,即可进入到TUI模式,
此时啥都没有,我们可以按照第一节的方式输入b main
和r
,
可以看到源码显示在上面窗口,命令输入在下面窗口。中间是状态行,表示当前gdb调试的目标属于native的(即本地调试,还有一种是远程调试),进程号是2694,当前调用函数是main,当前位于19行,PC值是0x5555555546d2
如果此时按下Ctrl+x+2,那么就会出现汇编窗口
如果我们连续按下Ctrl+x+2就会发现还会显示寄存器窗口,但是源码显示没有了。
这是咋回事呢?因为在TUI模式下,总共有4种窗口
其中命令窗口肯定要显示出来的,这是我们debug的输入,对于另外3个窗口,如果我们输入Ctrl+x+2就可以显示其中2个窗口,这个数字2意思就是除了命令窗口外还可以同时再显示2个窗口。连续按下Ctrl+x+2就会出现这三个窗口的两两组合。
如果我们想除了命令窗口外只显示一个窗口,那么可以按下Ctrl+x+1,这个数字1就是只显示一个窗口(除了命令窗口外)。
对于源码窗口,我们可以使用PageUp,PageDown和4个方向键来查看源码。
TUI模式下有时显示会出现混叠现象,此时按下Ctrl+l(是小写的L)可以进行刷新。
断点设置是TUI模式带来的很方便的特性,如我们在main函数开始处设置了断点,
在19行出现B+>
,假如我们在27行也设置个断点,在命令行下输入b 27
,
此时在27行出现b+
,跟19行显示的不一样。他们的具体意义如下,
B
表示断点处代码已经运行至少一次b
表示断点处代码还没有运行到+
表示该断点处于enable状态-
表示该断点处于disable状态那么如何disable一个断点呢,
使用info b
来查看断点信息,然后对于想要disable的断点,直接输入disable Num
就可以了,也可以看到27行的b+
变成了b-
,如果想enable断点,就输入enable Num
。
本文主要讲述了gdb的常用命令和gdb的TUI模式,文中介绍的常用命令在TUI下都可以使用,这样2者结合起来会更加方便。
另外,读者也可以参考Richard Stallman编写的《Debugging with gdb》,里面详尽描述了gdb的使用方法。
如果有写的不对的地方,希望能留言指正,谢谢阅读。