GDB 5分钟快速入门

一个简单快速的GDB使用入门,直接上手使用进行调试。

GCC/G++ 编译文件

在使用gdb进行调试之前,需要使用GCC的编译器编译可执行文件,我们以C++代码为例,使用g++进行编译,此时应该带上编译选项-g确保生成调试信息,点击此处查看具体信息 GNU/GCC Debugging-Options

$ g++ main.cc -g -o main

// main.cc
#include <iostream>

int main() {
    std::cout << "Hello\n";
    return 0;
}

GDB Text User Interface (TUI)

其实gdb也是有UI的,一种终端接口,直接在终端中显示源文件、程序输出、程序寄存器等。点击此处查阅文档 GDB Text User Interface 。在使用时gdb,添加上选项-tui即可。如果直接使用gdb execution_file,那么也可以输入tui enable切换进入tui模式。

$ gdb main -tui

开始调试

如果你正确使用了g++-g选项,那么你应该可以在gdb打开后看见源代码在上方,命令窗口在下方。如下图,颜色不同仅仅是终端配置区别。这里我没有指定输出的可执行文件名,所以是a.out


打上断点

我们可以看见各代码的行数,在gdb中,使用break,或简单地使用b打断点,例如我们在main函数处打断点则会在该函数的下面一行添加一个断点。我们也可以用delete或者clear清除断点,前者用于指定某个断点,后者则全部清空。

$ b main
# or you can also specific lines
$ b 4

运行

使用run或者r,将直接运行至当前的第一个断点处,此时可以继续使用step或者s步过,也可以使用stepi或者si步入。可以看见直接输入s后代码框左边的箭头>指向了return 0,并且在命令行看见了运行结果。 此时当我们输入si的话,则需要layout asm查看汇编,因为这时候步入到主函数返回。我们看见当前的汇编恰好对应ret


监视变量

现在我们修改一下源代码,如下并重新编译调试。

// main.cc
int foo(int& x){
     x = 10;
     return x * x; 
}

int main(){
	int val = 90;
    foo(val);
    return 0;
}

我们使用b mainb foo在两个函数的位置打上断点,通过watch监视变量。此时则需要先run程序,让程序走到需要监视的变量被初始化的作用域,才能成功监视。我们先r。然后再watch val。可以看见我们成功停在了main函数,同时也成功watchval这个变量。


打印变量值

我们再不断s,直到程序断在foo函数——由于我们在foo打了断点,所以s不会直接步过foo,当我们经过了x = 10的赋值语句,此时命令行会提醒我们val的值变化。

我们也可以使用print或者p打印x,即p x,此时的作用域下只能打印x。也可以通过p/x指定打印的格式,例如p/x就是按照十六进制打印。p/x xp/t x。如果要打印变量地址的话,则p &x即可,当然,由于我们的程序中x是左值引用,所以p x的时候,就会显示出其指向的地址,我们的程序是0x7fffffffd4b4。也可以用p *0x7fffffffd4b4打印出该地址的值。

  • x 按十六进制格式显示变量。
  • d 按十进制格式显示变量。
  • u 按十六进制格式显示无符号整型。
  • o 按八进制格式显示变量。
  • t 按二进制格式显示变量。
  • a 按十六进制格式显示变量。
  • c 按字符格式显示变量。
  • f 按浮点数格式显示变量。

大家应该察觉到了,当我们目前在foo函数内时,p val是无法打印出main函数内的值的,此时应该使用p 'main'::val进行打印。如果你有多个文件,也可以通过p 'file2.cc::value'打印file2.cc当中的某个值。这个也适用于各种打印方法,比如watch

对于数组类型,我们需要通过p *array@len进行打印,其中len是指定的个数。例如我们将arr的前5个元素打印出来p *arr@5

自动显示变量

我们每次都使用p来显示变量太麻烦了,使用watch的话也只能在变量改变的时候显示,在各种ide里调试的时候可以实时显示当前的每一个变量,太酷了😎,其实我们也可以用display来监视,在该变量存在的作用域内,每次操作都会打印一次被display的变量。

退出调试

使用q然后y退出

Python自动化的GDB脚本

我们可以直接使用python编写gdb脚本,让我们每次直接运行该脚本,免去调试时重复输入的痛苦😻。例如我们使用gdb -x debug.py -tui进行调试,会自动帮我们执行这些指令。点击这里查看 GDB Python API

# debug.py
import gdb

gdb.execute("file ./a.out")
gdb.execute("b main" )
gdb.execute("r")
gdb.execute("display 'main'::val")

一点启示

如果我们可以自动根据当前作用域display所有变量并绘制一份UI······