我们在linux下调试程序时,一般都会用到gdb,本文主要介绍一下gdb常用的操作命令以及TUI模式的使用方式。


一 常用操作命令

假设我们有段程序叫main.c,代码如下,

#include <stdio.h>

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 xx,设置断点(b是breakpoint的缩写),可以是b 函数名,即在指定函数的起始处设置断点,也可以是b 行号,即在指定代码行设置断点。
  • r,运行的意思(r是run的缩写),一般用来代码开始运行,或者重新运行(如果调试到一半又想从头开始运行)
  • c,继续执行(c是continue的缩写),当执行r运行到某个断点后,后面想继续执行到下一个断点或者把剩下代码执行完毕,就可以使用c
  • n,next的意思,执行当前行代码
  • s,step的意思,当一行代码里有函数调用,那么执行s会跳入函数里执行,如果没有函数调用,那么效果和n相同
  • info xx,查看一些信息,如断点或者局部变量,分别是info binfo locals
  • p xx,p是print的缩写,打印某个变量的值
  • set xx,设置某个变量的值
  • x 指定大小 起始内存地址,即查看起始内存地址上指定大小的内存里的值。如x /3b 0x11223344,就是查看以0x11223344开始的3个字节的值,也可以是x /3w 0x11223344,就是查看以0x11223344开始的3个word的值
  • bt,backtrace的缩写,回溯,当使用s进入某个函数后,输入bt可以打印该函数的栈帧
  • list,在命令行下显示源码,可以是list或者list 行号,后者是以指定行号为基准,显示该行号前后的代码
  • q,quit的意思,即退出gdb调试

有了以上知识后,我们来看下如何调试,

起始

输入b main并回车,意思是在main函数起始处打断点,然后输入r并回车,开始执行程序,程序会在main开始处停下来,因为这边设置了断点。
在这里插入图片描述
这套操作是调试的基础,因为程序运行都从main函数开始执行,所以我们都会从main开始调试。

执行代码

此时输入n并回车,程序就会执行一行代码。每输入一个n并回车,都会执行一行代码。如果一行代码有函数调用,那么输入s就会进入函数体内,如果输入n则直接把被调函数执行完。
在这里插入图片描述

查看变量

代码里有个结构体变量obj,我们来查看下它的信息,输入p obj
在这里插入图片描述
查看结构体里的成员则是p obj.namep 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 命令来查看帮助信息。


二 TUI模式

经过第一节,我们知道了gdb的一些常用操作方式,但是这种使用方法都是在命令行下进行的,使用起来非常不方便,假如我们想在某行打断点,需要使用list来显示源码,很麻烦。
基于此,gdb提供了一种可视化的调试模式,即TUI(Text User Interface的缩写),这个模式使用了curses库来进行图形界面显示。

首先执行gdb ./a.out,然后Ctrl+x+a,即可进入到TUI模式,
在这里插入图片描述
此时啥都没有,我们可以按照第一节的方式输入b mainr
在这里插入图片描述
可以看到源码显示在上面窗口,命令输入在下面窗口。中间是状态行,表示当前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的使用方法。

如果有写的不对的地方,希望能留言指正,谢谢阅读。

GitHub 加速计划 / li / linux-dash
10.39 K
1.2 K
下载
A beautiful web dashboard for Linux
最近提交(Master分支:2 个月前 )
186a802e added ecosystem file for PM2 4 年前
5def40a3 Add host customization support for the NodeJS version 4 年前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐