手把手教你使用 GNU 调试器
GDB
![](/data/attachment/album/202207/22/122211c2fgo53m9tw3xe2p.jpg)



> 
> GNU 调试器是一个发现程序缺陷的强大工具。
> 
> 
> 


如果你是一个程序员，想在你的软件增加某些功能，你首先考虑实现它的方法：例如写一个方法、定义一个类，或者创建新的数据类型。然后你用编译器或解释器可以理解的编程语言来实现这个功能。但是，如果你觉得你所有代码都正确，但是编译器或解释器依然无法理解你的指令怎么办？如果软件大多数情况下都运行良好，但是在某些环境下出现缺陷怎么办？这种情况下，你得知道如何正确使用调试器找到问题的根源。


<ruby> GNU 调试器 <rt>  GNU Project Debugger </rt></ruby>（[GDB](https://www.gnu.org/software/gdb/)）是一个发现项目缺陷的强大工具。它通过追踪程序运行过程中发生了什么来帮助你发现程序错误或崩溃的原因。（LCTT 校注：GDB 全程是“GNU Project Debugger”，即 “GNU 项目调试器”，但是通常我们简称为“GNU 调试器”）


本文是 GDB 基本用法的实践教程。请跟随示例，打开命令行并克隆此仓库：



```
git clone https://github.com/hANSIc99/core_dump_example.git

```

### 快捷方式


GDB 的每条命令都可以缩短。例如：显示设定的断点的 `info break` 命令可以被缩短为 `i break`。你可能在其他地方看到过这种缩写，但在本文中，为了清晰展现使用的函数，我将所写出整个命令。


### 命令行参数


你可以将 GDB 附加到每个可执行文件。进入你克隆的仓库（`core_dump_example`），运行 `make` 进行编译。你现在能看到一个名为 `coredump` 的可执行文件。（更多信息，请参考我的文章《[创建和调试 Linux 的转储文件](https://opensource.com/article/20/8/linux-dump)》。）


要将 GDB 附加到这个可执行文件，请输入: `gdb coredump`。


你的输出应如下所示：


![gdb coredump output](/data/attachment/album/202207/22/122227oj3pl6n6atioslct.png)


返回结果显示没有找到调试符号。


调试信息是<ruby> 目标文件 <rt>  object file </rt></ruby>（可执行文件）的组成部分，调试信息包括数据类型、函数签名、源代码和操作码之间的关系。此时，你有两种选择：


* 继续调试汇编代码（参见下文“无符号调试”）
* 使用调试信息进行编译，参见下一节内容


### 使用调试信息进行编译


为了在二进制文件中包含调试信息，你必须重新编译。打开 `Makefile`，删除第 9 行的注释标签（`#`）后重新编译：



```
CFLAGS =-Wall -Werror -std=c++11 -g

```

`-g` 告诉编译器包含调试信息。运行 `make clean`，接着运行 `make`，然后再次调用 GDB。你得到如下输出后就可以调试代码了：


![GDB output with symbols](/data/attachment/album/202207/22/122228ss9usrv88awa4a8a.png)


新增的调试信息会增加可执行文件的大小。在这种情况下，执行文件增加了 2.5 倍（从 26,088 字节 增加到 65,480 字节）。


输入 `run -c1`，使用 `-c1` 开关启动程序。当程序运行到达 `State_4` 时将崩溃：


![gdb output crash on c1 switch](/data/attachment/album/202207/22/122228fn8iihjpn0sjjfvf.png)


你可以检索有关程序的其他信息，`info source` 命令提供了当前文件的信息：


![gdb info source output](/data/attachment/album/202207/22/122228k2u20vnu2pfuftp6.png)


* 101 行代码
* 语言: C++
* 编译器（版本、调优、架构、调试标志、语言标准）
* 调试格式：[DWARF 2](http://dwarfstd.org/)
* 没有预处理器宏指令（使用 GCC 编译时，宏仅在 [使用 -g3 标志编译](https://sourceware.org/gdb/current/onlinedocs/gdb/Compilation.html#Compilation) 时可用）。


`info shared` 命令打印了动态库列表机器在虚拟地址空间的地址，它们在启动时被加载到该地址，以便程序运行：


![gdb info shared output](/data/attachment/album/202207/22/122228jueq55u9ljv6e9au.png)


如果你想了解 Linux 中的库处理方式，请参见我的文章 [在 Linux 中如何处理动态库和静态库](https://opensource.com/article/20/6/linux-libraries)。


### 调试程序


你可能已经注意到，你可以在 GDB 中使用 `run` 命令启动程序。`run` 命令接受命令行参数，就像从控制台启动程序一样。`-c1` 开关会导致程序在第 4 阶段崩溃。要从头开始运行程序，你不用退出 GDB，只需再次运行 `run` 命令。如果没有 `-c1` 开关，程序将陷入死循环，你必须使用 `Ctrl+C` 来结束死循环。


![gdb output stopped by sigint](/data/attachment/album/202207/22/122229mp93xq1uz9qucx0p.png)


你也可以一步一步运行程序。在 C/C++ 中，入口是 `main` 函数。使用 `list main` 命令打开显示 `main` 函数的部分源代码：


![gdb output list main](/data/attachment/album/202207/22/122229iq3znn6f47k3n6x3.png)


`main` 函数在第 33 行，因此可以输入 `break 33` 在 33 行添加断点:


![gdb output breakpoint added](/data/attachment/album/202207/22/122229mbokzxhazvxifi2g.png)


输入 `run` 运行程序。正如预期的那样，程序在 `main` 函数处停止。输入 `layout src` 并排查看源代码：


![gdb output break at main](/data/attachment/album/202207/22/122230jj16igub6aqu8ih6.png)


你现在处于 GDB 的文本用户界面（TUI）模式。可以使用键盘向上和向下箭头键滚动查看源代码。


GDB 高亮显示当前执行行。你可以输入 `next`（`n`）命令逐行执行命令。如果你没有指定新的命令，GBD 会执行上一条命令。要逐行运行代码，只需按回车键。


有时，你会发现文本的输出有点显示不正常：


![gdb output corrupted](/data/attachment/album/202207/22/122230pf6kv7nghf21ekrk.png)


如果发生这种情况，请按 `Ctrl+L` 重置屏幕。


使用 `Ctrl+X+A` 可以随时进入和退出 TUI 模式。你可以在手册中找到 [其他的键绑定](https://sourceware.org/gdb/onlinedocs/gdb/TUI-Keys.html#TUI-Keys) 。


要退出 GDB，只需输入 `quit`。


### 设置监察点


这个示例程序的核心是一个在无限循环中运行的状态机。`n_state` 变量枚举了当前所有状态：



```
while(true){
        switch(n_state){
        case State_1:
                std::cout << "State_1 reached" << std::flush;
                n_state = State_2;
                break;
        case State_2:
                std::cout << "State_2 reached" << std::flush;
                n_state = State_3;
                break;
        
        (.....)
        
        }
}

```

如果你希望当 `n_state` 的值为 `State_5` 时停止程序。为此，请在 `main` 函数处停止程序并为 `n_state` 设置监察点：



```
watch n_state == State_5

```

只有当所需的变量在当前上下文中可用时，使用变量名设置监察点才有效。


当你输入 `continue` 继续运行程序时，你会得到如下输出：


![gdb output stop on watchpoint_1](/data/attachment/album/202207/22/122230qm44qvg4rk14vv1q.png)


如果你继续运行程序，当监察点表达式评估为 `false` 时 GDB 将停止：


![gdb output stop on watchpoint_2](/data/attachment/album/202207/22/122231dgk4vllg47vvg8y6.png)


你可以为一般的值变化、特定的值、读取或写入时来设置监察点。


### 更改断点和监察点


输入 `info watchpoints` 打印先前设置的监察点列表：


![gdb output info watchpoints](/data/attachment/album/202207/22/122231vczlbpkpchtkpglk.png)


#### 删除断点和监察点


如你所见，监察点就是数字。要删除特定的监察点，请先输入 `delete` 后输入监察点的编号。例如，我的监察点编号为 2；要删除此监察点，输入 `delete 2`。


*注意：* 如果你使用 `delete` 而没有指定数字，*所有* 监察点和断点将被删除。


这同样适用于断点。在下面的截屏中，我添加了几个断点，输入 `info breakpoint` 打印断点列表：


![gdb output info breakpoints](/data/attachment/album/202207/22/122231ldjxf5xvkjmqx22e.png)


要删除单个断点，请先输入 `delete` 后输入断点的编号。另外一种方式：你可以通过指定断点的行号来删除断点。例如，`clear 78` 命令将删除第 78 行设置的断点号 7。


#### 禁用或启用断点和监察点


除了删除断点或监察点之外，你可以通过输入 `disable`，后输入编号禁用断点或监察点。在下文中，断点 3 和 4 被禁用，并在代码窗口中用减号标记：


![disabled breakpoints](/data/attachment/album/202207/22/122232zxmu0u6z8mswssef.png)


也可以通过输入类似 `disable 2 - 4` 修改某个范围内的断点或监察点。如果要重新激活这些点，请输入 `enable`，然后输入它们的编号。


### 条件断点


首先，输入 `delete` 删除所有断点和监察点。你仍然想使程序停在 `main` 函数处，如果你不想指定行号，可以通过直接指明该函数来添加断点。输入 `break main` 从而在 `main` 函数处添加断点。


输入 `run` 从头开始运行程序，程序将在 `main` 函数处停止。


`main` 函数包括变量 `n_state_3_count`，当状态机达到状态 3 时，该变量会递增。


基于 `n_state_3_count` 的值添加一个条件断点，请输入：



```
break 54 if n_state_3_count == 3

```

![Set conditional breakpoint](/data/attachment/album/202207/22/122232cqz0zswoqbppfyzx.png)


继续运行程序。程序将在第 54 行停止之前运行状态机 3 次。要查看 `n_state_3_count` 的值，请输入：



```
print n_state_3_count

```

![print variable](/data/attachment/album/202207/22/122232dyq8ybotqys0mhtq.png)


#### 使断点成为条件断点


你也可以使现有断点成为条件断点。用 `clear 54` 命令删除最近添加的断点，并通过输入 `break 54` 命令添加一个简单的断点。你可以输入以下内容使此断点成为条件断点：



```
condition 3 n_state_3_count == 9

```

`3` 指的是断点编号。


![modify breakpoint](/data/attachment/album/202207/22/122233i1oo1ytovbtdmyot.png)


#### 在其他源文件中设置断点


如果你的程序由多个源文件组成，你可以在行号前指定文件名来设置断点，例如，`break main. cpp:54`。


#### 捕捉点


除了断点和监察点之外，你还可以设置捕获点。捕获点适用于执行系统调用、加载共享库或引发异常等事件。


要捕获用于写入 STDOUT 的 `write` 系统调用，请输入：



```
catch syscall write

```

![catch syscall write output](/data/attachment/album/202207/22/122233g24zz7442i33z72z.png)


每当程序写入控制台输出时，GDB 将中断执行。


在手册中，你可以找到一整章关于 [断点、监察点和捕捉点](https://sourceware.org/gdb/current/onlinedocs/gdb/Breakpoints.html#Breakpoints) 的内容。


### 评估和操作符号


用 `print` 命令可以打印变量的值。一般语法是 `print <表达式> <值>`。修改变量的值，请输入：



```
set variable <variable-name> <new-value>.

```

在下面的截屏中，我将变量 `n_state_3_count` 的值设为 `123`。


![catch syscall write output](/data/attachment/album/202207/22/122233m22g95vo4bmkv25m.png)


`/x` 表达式以十六进制打印值；使用 `&` 运算符，你可以打印虚拟地址空间内的地址。


如果你不确定某个符号的数据类型，可以使用 `whatis` 来查明。


![whatis output](/data/attachment/album/202207/22/122234oxgtn3c6kbclh12n.png)


如果你要列出 `main` 函数范围内可用的所有变量，请输入 `info scope main` :


![info scope main output](/data/attachment/album/202207/22/122234ch9yjhch7h9bcycw.png)


`DW_OP_fbreg` 值是指基于当前子程序的堆栈偏移量。


或者，如果你已经在一个函数中并且想要列出当前堆栈帧上的所有变量，你可以使用 `info locals` :


![info locals output](/data/attachment/album/202207/22/122234cnnyc910iiggfdig.png)


查看手册以了解更多 [检查符号](https://sourceware.org/gdb/current/onlinedocs/gdb/Symbols.html) 的内容。


### 附加调试到一个正在运行的进程


`gdb attach <进程 ID>` 命令允许你通过指定进程 ID（PID）附加到一个已经在运行的进程进行调试。幸运的是，`coredump` 程序将其当前 PID 打印到屏幕上，因此你不必使用 [ps](https://man7.org/linux/man-pages/man1/ps.1.html) 或 [top](https://man7.org/linux/man-pages/man1/top.1.html) 手动查找 PID。


启动 `coredump` 应用程序的一个实例：



```
./coredump

```

![coredump application](/data/attachment/album/202207/22/122234q9cde196c8cdm6ic.png)


操作系统显示 PID 为 `2849`。打开一个单独的控制台窗口，移动到 `coredump` 应用程序的根目录，然后用 GDB 附加到该进程进行调试：



```
gdb attach 2849

```

![attach GDB to coredump](/data/attachment/album/202207/22/122234l2k1mlzkfozwklwo.png)


当你用 GDB 附加到进程时，GDB 会立即停止进程运行。输入 `layout src` 和 `backtrace` 来检查调用堆栈：


![layout src and backtrace output](/data/attachment/album/202207/22/122235h6zssoqeioi6lt17.png)


输出显示在 `main.cpp` 第 92 行调用 `std::this_thread::sleep_for<...>(. ..)` 函数时进程中断。


只要你退出 GDB，该进程将继续运行。


你可以在 GDB 手册中找到有关 [附加调试正在运行的进程](https://sourceware.org/gdb/current/onlinedocs/gdb/Attach.html#Attach) 的更多信息。


#### 在堆栈中移动


在命令窗口，输入 `up` 两次可以在堆栈中向上移动到 `main.cpp` :


![moving up the stack to main.cpp](/data/attachment/album/202207/22/122235il2vavtstxxl12a1.png)


通常，编译器将为每个函数或方法创建一个子程序。每个子程序都有自己的栈帧，所以在栈帧中向上移动意味着在调用栈中向上移动。


你可以在手册中找到有关 [堆栈计算](https://sourceware.org/gdb/current/onlinedocs/gdb/Stack.html#Stack) 的更多信息。


#### 指定源文件


当调试一个已经在运行的进程时，GDB 将在当前工作目录中寻找源文件。你也可以使用 [目录命令](https://ftp.gnu.org/old-gnu/Manuals/gdb/html_node/gdb_48.html#SEC49) 手动指定源目录。


### 评估转储文件


阅读 [创建和调试 Linux 的转储文件](https://opensource.com/article/20/8/linux-dump) 了解有关此主题的信息。


参考文章太长，简单来说就是：


1. 假设你使用的是最新版本的 Fedora
2. 使用 `-c1` 开关调用 coredump：`coredump -c1`


![Crash meme](/data/attachment/album/202207/22/122235iscnb2eozvuolrv2.png)


3. 使用 GDB 加载最新的转储文件：`coredumpctl debug`
4. 打开 TUI 模式并输入 `layout src`


![coredump output](/data/attachment/album/202207/22/122236nex7tbote4p7tx84.png)


`backtrace` 的输出显示崩溃发生在距离 `main.cpp` 五个栈帧之外。回车直接跳转到 `main.cpp` 中的错误代码行：


![up 5 output](/data/attachment/album/202207/22/122236jgmgvzj0w0f129og.png)


看源码发现程序试图释放一个内存管理函数没有返回的指针。这会导致未定义的行为并引起 `SIGABRT`。


### 无符号调试


如果没有源代码，调试就会变得非常困难。当我在尝试解决逆向工程的挑战时，我第一次体验到了这一点。了解一些 [汇编语言](https://en.wikipedia.org/wiki/Assembly_language) 的知识会很有用。


我们用例子看看它是如何运行的。


找到根目录，打开 `Makefile`，然后像下面一样编辑第 9 行：



```
CFLAGS =-Wall -Werror -std=c++11 #-g

```

要重新编译程序，先运行 `make clean`，再运行 `make`，最后启动 GDB。该程序不再有任何调试符号来引导源代码的走向。


![no debugging symbols](/data/attachment/album/202207/22/122236k5eh6f6x9ee7zz99.png)


`info file` 命令显示二进制文件的内存区域和入口点：


![info file output](/data/attachment/album/202207/22/122236a9uwtpyhu9z7jzpz.png)


`.text` 区段始终从入口点开始，其中包含实际的操作码。要在入口点添加断点，输入 `break *0x401110` 然后输入 `run` 开始运行程序：


![breakpoint at the entry point](/data/attachment/album/202207/22/122237gko9cihecc922taz.png)


要在某个地址设置断点，使用取消引用运算符 `*` 来指定地址。


#### 选择反汇编程序风格


在深入研究汇编之前，你可以选择要使用的 [汇编风格](https://en.wikipedia.org/wiki/X86_assembly_language#Syntax)。 GDB 默认是 AT&T，但我更喜欢 Intel 语法。变更风格如下：



```
set disassembly-flavor intel

```

![changing assembly flavor](/data/attachment/album/202207/22/122237xdsvvvs24yojys49.png)


现在输入 `layout asm` 调出汇编代码窗口，输入 `layout reg` 调出寄存器窗口。你现在应该看到如下输出：


![layout asm and layout reg output](/data/attachment/album/202207/22/122237catajn93nn2pypvv.png)


#### 保存配置文件


尽管你已经输入了许多命令，但实际上还没有开始调试。如果你正在大量调试应用程序或尝试解决逆向工程的难题，则将 GDB 特定设置保存在文件中会很有用。


该项目的 GitHub 存储库中的 [gdbinit](https://github.com/hANSIc99/core_dump_example/blob/master/gdbinit) 配置文件包含最近使用的命令：



```
set disassembly-flavor intel
set write on
break *0x401110
run -c2
layout asm
layout reg

```

`set write on` 命令使你能够在程序运行期间修改二进制文件。


退出 GDB 并使用配置文件重新启动 GDB ： `gdb -x gdbinit coredump`。


#### 阅读指令


应用 `c2` 开关后，程序将崩溃。程序在入口函数处停止，因此你必须写入 `continue` 才能继续运行：


![continuing execution after crash](/data/attachment/album/202207/22/122238dz4abef555z54rsz.png)


`idiv` 指令进行整数除法运算：`RAX` 寄存器中为被除数，指定参数为除数。商被加载到 `RAX` 寄存器中，余数被加载到 `RDX` 中。


从寄存器角度，你可以看到 `RAX` 包含 `5`，因此你必须找出存储堆栈中位置为 `rbp-0x4` 的值。


#### 读取内存


要读取原始内存内容，你必须指定比读取符号更多的参数。在汇编输出中向上滚动一点，可以看到堆栈的划分：


![stack division output](/data/attachment/album/202207/22/122238iebmftyewwb322ow.png)


你最感兴趣的应该是 `rbp-0x4` 的值，因为它是 `idiv` 的存储参数。你可以从截图中看到`rbp-0x8` 位置的下一个变量，所以 `rbp-0x4` 位置的变量是 4 字节宽。


在 GDB 中，你可以使用 `x` 命令*查看*任何内存内容：



> 
> `x/` < 可选参数 `n`、`f`、`u` > < 内存地址 `addr` >
> 
> 
> 


可选参数：


* `n`：单元大小的重复计数（默认值：1）
* `f`：格式说明符，如 [printf](https://en.wikipedia.org/wiki/Printf_format_string#Type_field)
* `u`：单元大小
	+ `b`：字节
	+ `h`：半字（2 个字节）
	+ w: 字（4 个字节）（默认）
	+ g: 双字（8 个字节）


要打印 `rbp-0x4` 的值，请输入 `x/u $rbp-4` :


![print value](/data/attachment/album/202207/22/122238obnnz33a1q31n5yb.png)


如果你能记住这种模式，则可以直接查看内存。参见手册中的 [查看内存](https://sourceware.org/gdb/current/onlinedocs/gdb/Memory.html) 部分。


#### 操作汇编


子程序 `zeroDivide()` 发生运算异常。当你用向上箭头键向上滚动一点时，你会找到下面信息：



```
0x401211 <_Z10zeroDividev>              push   rbp
0x401212 <_Z10zeroDividev+1>            mov    rbp,rsp

```

这被称为 [函数前言](https://en.wikipedia.org/wiki/Function_prologue)：


1. 调用函数的基指针（`rbp`）存放在栈上
2. 栈指针（`rsp`）的值被加载到基指针（`rbp`）


完全跳过这个子程序。你可以使用 `backtrace` 查看调用堆栈。在 `main` 函数之前只有一个堆栈帧，所以你可以用一次 `up` 回到 `main` :


![Callstack assembly](/data/attachment/album/202207/22/122238uj6y2jo4w4632xrr.png)


在你的 `main` 函数中，你会找到下面信息:



```
0x401431 <main+497>     cmp    BYTE PTR [rbp-0x12],0x0
0x401435 <main+501>     je     0x40145f <main+543>
0x401437 <main+503>     call   0x401211<_Z10zeroDividev>

```

子程序 `zeroDivide()` 仅在 `jump equal (je)` 为 `true` 时进入。你可以轻松地将其替换为 `jump-not-equal (jne)` 指令，该指令的操作码为 `0x75`（假设你使用的是 x86/64 架构；其他架构上的操作码不同）。输入 `run` 重新启动程序。当程序在入口函数处停止时，设置操作码：



```
set *(unsigned char*)0x401435 = 0x75

```

最后，输入 `continue` 。该程序将跳过子程序 `zeroDivide()` 并且不会再崩溃。


### 总结


你会在许多集成开发环境（IDE）中发现 GDB 运行在后台，包括 Qt Creator 和 VSCodium 的 [本地调试](https://github.com/WebFreak001/code-debug) 扩展。


![GDB in VSCodium](/data/attachment/album/202207/22/122239xp2g8gh28hiug9ik.png)


了解如何充分利用 GDB 的功能很有用。一般情况下，并非所有 GDB 的功能都可以在 IDE 中使用，因此你可以从命令行使用 GDB 的经验中受益。




---


via: <https://opensource.com/article/21/1/gnu-project-debugger>


作者：[Stephan Avenwedde](https://opensource.com/users/hansic99) 选题：[lkxed](https://github.com/lkxed) 译者：[Maisie-x](https://github.com/Maisie-x) 校对：[wxy](https://github.com/wxy)


本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


OK
If you're a programmer and you want to put a certain functionality in your software, you start by thinking of ways to implement it—such as writing a method, defining a class, or creating new data types. Then you write the implementation in a language that the compiler or interpreter can understand. But what if the compiler or interpreter does not understand the instructions as you had them in mind, even though you're sure you did everything right? What if the software works fine most of the time but causes bugs in certain circumstances? In these cases, you have to know how to use a debugger correctly to find the source of your troubles.

The GNU Project Debugger ([GDB](https://www.gnu.org/software/gdb/)) is a powerful tool for finding bugs in programs. It helps you uncover the reason for an error or crash by tracking what is going on inside the program during execution.

This article is a hands-on tutorial on basic GDB usage. To follow along with the examples, open the command line and clone this repository:

`git clone https://github.com/hANSIc99/core_dump_example.git`


## Shortcuts


Every command in GDB can be shortened. For example, `info break`

, which shows the set breakpoints, can be shortened to `i break`

. You might see those abbreviations elsewhere, but in this article, I will write out the entire command so that it is clear which function is used.

## Command-line parameters

You can attach GDB to every executable. Navigate to the repository you cloned, and compile it by running `make`

. You should now have an executable called **coredump**. (See my article on [ Creating and debugging Linux dump files](https://opensource.com/article/20/8/linux-dump) for more information..

To attach GDB to the executable, type: `gdb coredump`

.

Your output should look like this:

![gdb coredump output gdb coredump output](https://opensource.com/sites/default/files/uploads/gdb_output_no_dbg_symbols.png)

(Stephan Avenwedde, CC BY-SA 4.0)

It says no debugging symbols were found.

Debugging information is part of the object file (the executable) and includes data types, function signatures, and the relationship between the source code and the opcode. At this point, you have two options:

- Continue debugging the assembly (see "
[Debug without symbols](#without_symbols)" below) - Compile with debug information using the information in the next section

## Compile with debug information

To include debug information in the binary file, you have to recompile it. Open the **Makefile** and remove the hashtag (`#`

) from line 9:

`CFLAGS =-Wall -Werror -std=c++11 -g`


The `g`

option tells the compiler to include the debug information. Run `make clean`

followed by `make`

and invoke GDB again. You should get this output and can start debugging the code:

![GDB output with symbols GDB output with symbols](https://opensource.com/sites/default/files/uploads/gdb_output_with_symbols.png)

(Stephan Avenwedde, CC BY-SA 4.0)

The additional debugging information will increase the size of the executable. In this case, it increases the executable by 2.5 times (from 26,088 byte to 65,480 byte).

Start the program with the `-c1`

switch by typing `run -c1`

. The program will start and crash when it reaches `State_4`

:

![gdb output crash on c1 switch](https://opensource.com/sites/default/files/uploads/gdb_output_crash_on_c1_switch.png)

(Stephan Avenwedde, CC BY-SA 4.0)

You can retrieve additional information about the program. The command `info source`

provides information about the current file:

![gdb info source output gdb info source output](https://opensource.com/sites/default/files/uploads/gdb_output_info_source.png)

(Stephan Avenwedde, CC BY-SA 4.0)

- 101 lines
- Language: C++
- Compiler (version, tuning, architecture, debug flag, language standard)
- Debugging format:
[DWARF 2](http://dwarfstd.org/) - No preprocessor macro information available (when compiled with GCC, macros are available only when
[compiled with the](https://sourceware.org/gdb/current/onlinedocs/gdb/Compilation.html#Compilation)).`-g3`

flag

The command `info shared`

prints a list of dynamic libraries with their addresses in the virtual address space that was loaded on startup so that the program will execute:

![gdb info shared output gdb info shared output](https://opensource.com/sites/default/files/uploads/gdb_output_info_shared.png)

(Stephan Avenwedde, CC BY-SA 4.0)

If you want to learn about library handling in Linux, see my article [ How to handle dynamic and static libraries in Linux](https://opensource.com/article/20/6/linux-libraries).

## Debug the program

You may have noticed that you can start the program inside GDB with the `run`

command. The `run`

command accepts command-line arguments like you would use to start the program from the console. The `-c1`

switch will cause the program to crash on stage 4. To run the program from the beginning, you don't have to quit GDB; simply use the `run`

command again. Without the `-c1`

switch, the program executes an infinite loop. You would have to stop it with **Ctrl+C**.

![gdb output stopped by sigint gdb output stopped by sigint](https://opensource.com/sites/default/files/uploads/gdb_output_stopped_by_sigint.png)

(Stephan Avenwedde, CC BY-SA 4.0)

You can also execute a program step by step. In C/C++, the entry point is the `main`

function. Use the command `list main`

to open the part of the source code that shows the `main`

function:

![gdb output list main gdb output list main](https://opensource.com/sites/default/files/uploads/gdb_output_list_main.png)

(Stephan Avenwedde, CC BY-SA 4.0)

The `main`

function is on line 33, so add a breakpoint there by typing `break 33`

:

![gdb output breakpoint added gdb output breakpoint added](https://opensource.com/sites/default/files/uploads/gdb_output_breakpoint_added.png)

(Stephan Avenwedde, CC BY-SA 4.0)

Run the program by typing `run`

. As expected, the program stops at the `main`

function. Type `layout src`

to show the source code in parallel:

![gdb output break at main gdb output break at main](https://opensource.com/sites/default/files/uploads/gdb_output_break_at_main.png)

(Stephan Avenwedde, CC BY-SA 4.0)

You are now in GDB's text user interface (TUI) mode. Use the Up and Down arrow keys to scroll through the source code.

GDB highlights the line to be executed. By typing `next`

(n), you can execute the commands line by line. GBD executes the last command if you don't specify a new one. To step through the code, just hit the **Enter** key.

From time to time, you will notice that TUI's output gets a bit corrupted:

![gdb output corrupted gdb output corrupted](https://opensource.com/sites/default/files/images/gdb_output_screen_corrupted.png)

(Stephan Avenwedde, CC BY-SA 4.0)

If this happens, press **Ctrl+L** to reset the screen.

Use **Ctrl+X+A** to enter and leave TUI mode at will. You can find [other key bindings](https://sourceware.org/gdb/onlinedocs/gdb/TUI-Keys.html#TUI-Keys) in the manual.

To quit GDB, simply type `quit`

.

## Watchpoints

The heart of this example program consists of a state machine running in an infinite loop. The variable `n_state`

is a simple enum that determines the current state:

```
while(true){
switch(n_state){
case State_1:
std::cout << "State_1 reached" << std::flush;
n_state = State_2;
break;
case State_2:
std::cout << "State_2 reached" << std::flush;
n_state = State_3;
break;
(.....)
}
}
```


You want to stop the program when `n_state`

is set to the value `State_5`

. To do so, stop the program at the `main`

function and set a watchpoint for `n_state`

:

`watch n_state == State_5`


Setting watchpoints with the variable name works only if the desired variable is available in the current context.

When you continue the program's execution by typing `continue`

, you should get output like:

![gdb output stop on watchpoint_1 gdb output stop on watchpoint_1](https://opensource.com/sites/default/files/uploads/gdb_output_stop_on_watchpoint_1.png)

(Stephan Avenwedde, CC BY-SA 4.0)

If you continue the execution, GDB will stop when the watchpoint expression evaluates to `false`

:

![gdb output stop on watchpoint_2 gdb output stop on watchpoint_2](https://opensource.com/sites/default/files/uploads/gdb_output_stop_on_watchpoint_2.png)

(Stephan Avenwedde, CC BY-SA 4.0)

You can specify watchpoints for general value changes, specific values, and read or write access.

## Altering breakpoints and watchpoints

Type `info watchpoints`

to print a list of previously set watchpoints:

![gdb output info watchpoints gdb output info watchpoints](https://opensource.com/sites/default/files/uploads/gdb_output_info_watchpoints.png)

(Stephan Avenwedde, CC BY-SA 4.0)

### Delete breakpoints and watchpoints

As you can see, watchpoints are numbers. To delete a specific watchpoint, type `delete`

followed by the number of the watchpoint. For example, my watchpoint has the number 2; to remove this watchpoint, enter `delete 2`

.

*Caution:* If you use `delete`

without specifying a number, *all* watchpoints and breakpoints will be deleted.

The same applies to breakpoints. In the screenshot below, I added several breakpoints and printed a list of them by typing `info breakpoint`

:

![gdb output info breakpoints gdb output info breakpoints](https://opensource.com/sites/default/files/uploads/gdb_output_info_breakpoints.png)

(Stephan Avenwedde, CC BY-SA 4.0)

To remove a single breakpoint, type `delete`

followed by its number. Alternatively, you can remove a breakpoint by specifying its line number. For example, the command `clear 78`

will remove breakpoint number 7, which is set on line 78.

### Disable or enable breakpoints and watchpoints

Instead of removing a breakpoint or watchpoint, you can disable it by typing `disable`

followed by its number. In the following, breakpoints 3 and 4 are disabled and are marked with a minus sign in the code window:

![disabled breakpoints disabled breakpoints](https://opensource.com/sites/default/files/uploads/gdb_output_disabled_breakpoints.png)

(Stephan Avenwedde, CC BY-SA 4.0)

It is also possible to modify a range of breakpoints or watchpoints by typing something like `disable 2 - 4`

. If you want to reactivate the points, type `enable`

followed by their numbers.

## Conditional breakpoints

First, remove all breakpoints and watchpoints by typing `delete`

. You still want the program to stop at the `main`

function, but instead of specifying a line number, add a breakpoint by naming the function directly. Type `break main`

to add a breakpoint at the `main`

function.

Type `run`

to start the execution from the beginning, and the program will stop at the `main`

function.

The `main`

function includes the variable `n_state_3_count`

, which is incremented when the state machine hits state 3.

To add a conditional breakpoint based on the value of `n_state_3_count`

type:

`break 54 if n_state_3_count == 3`



![Set conditional breakpoint Set conditional breakpoint](https://opensource.com/sites/default/files/uploads/gdb_output_set_conditional_breakpoint.png)

(Stephan Avenwedde, CC BY-SA 4.0)

Continue the execution. The program will execute the state machine three times before it stops at line 54. To check the value of `n_state_3_count`

, type:

`print n_state_3_count`



![print variable print variable](https://opensource.com/sites/default/files/uploads/gdb_output_print_variable.png)

(Stephan Avenwedde, CC BY-SA 4.0)

### Make breakpoints conditional

It is also possible to make an existing breakpoint conditional. Remove the recently added breakpoint with `clear 54`

, and add a simple breakpoint by typing `break 54`

. You can make this breakpoint conditional by typing:

`condition 3 n_state_3_count == 9`


The `3`

refers to the breakpoint number.

![modify breakpoint modify breakpoint](https://opensource.com/sites/default/files/uploads/gdb_output_modify_breakpoint.png)

(Stephan Avenwedde, CC BY-SA 4.0)

### Set breakpoints in other source files

If you have a program that consists of several source files, you can set breakpoints by specifying the file name before the line number, e.g., `break main.cpp:54`

.

### Catchpoints

In addition to breakpoints and watchpoints, you can also set catchpoints. Catchpoints apply to program events like performing syscalls, loading shared libraries, or raising exceptions.

To catch the `write`

syscall, which is used to write to STDOUT, enter:

`catch syscall write`



![catch syscall write output catch syscall write output](https://opensource.com/sites/default/files/uploads/gdb_output_syscall_catch.png)

(Stephan Avenwedde, CC BY-SA 4.0)

Each time the program writes to the console output, GDB will interrupt execution.

In the manual, you can find a whole chapter [covering break-, watch-, and catchpoints](https://sourceware.org/gdb/current/onlinedocs/gdb/Breakpoints.html#Breakpoints).

## Evaluate and manipulate symbols

Printing the values of variables is done with the `print`

command. The general syntax is `print <expression> <value>`

. The value of a variable can be modified by typing:

`set variable <variable-name> <new-value>.`


In the screenshot below, I gave the variable `n_state_3_count`

the value *123*.

![catch syscall write output print variable](https://opensource.com/sites/default/files/uploads/gdb_output_print_and_modify.png)

(Stephan Avenwedde, CC BY-SA 4.0)

The `/x`

expression prints the value in hexadecimal; with the `&`

operator, you can print the address within the virtual address space.

If you are not sure of a certain symbol's data type, you can find it with `whatis`

:

![whatis output whatis output](https://opensource.com/sites/default/files/uploads/gdb_output_whatis.png)

(Stephan Avenwedde, CC BY-SA 4.0)

If you want to list all variables that are available in the scope of the `main`

function, type `info scope main`

:

![info scope main output info scope main output](https://opensource.com/sites/default/files/uploads/gdb_output_info_scope_main.png)

(Stephan Avenwedde, CC BY-SA 4.0)

The `DW_OP_fbreg`

values refer to the stack offset based on the current subroutine.

Alternatively, if you are already inside a function and want to list all variables on the current stack frame, you can use `info locals`

:

![info locals output info locals output](https://opensource.com/sites/default/files/uploads/gdb_output_info_locals_main.png)

(Stephan Avenwedde, CC BY-SA 4.0)

Check the manual to learn more about [examining symbols](https://sourceware.org/gdb/current/onlinedocs/gdb/Symbols.html).

## Attach to a running process

The command `gdb attach <process-id>`

allows you to attach to an already running process by specifying the process ID (PID). Luckily, the `coredump`

program prints its current PID to the screen, so you don't have to manually find it with [ps](https://man7.org/linux/man-pages/man1/ps.1.html) or [top](https://man7.org/linux/man-pages/man1/top.1.html).

Start an instance of the coredump application:

`./coredump`



![coredump application coredump application](https://opensource.com/sites/default/files/uploads/coredump_running.png)

(Stephan Avenwedde, CC BY-SA 4.0)

The operating system gives the PID `2849`

. Open a separate console window, move to the coredump application's source directory, and attach GDB:

`gdb attach 2849`



![attach GDB to coredump attach GDB to coredump](https://opensource.com/sites/default/files/uploads/gdb_output_attaching_to_process.png)

(Stephan Avenwedde, CC BY-SA 4.0)

GDB immediately stops the execution when you attach it. Type `layout src`

and `backtrace`

to examine the call stack:

![layout src and backtrace output layout src and backtrace output](https://opensource.com/sites/default/files/uploads/gdb_output_backtrace.png)

(Stephan Avenwedde, CC BY-SA 4.0)

The output shows the process interrupted while executing the `std::this_thread::sleep_for<...>(...)`

function that was called in line 92 of `main.cpp`

.

As soon as you quit GDB, the process will continue running.

You can find more information about [attaching to a running process](https://sourceware.org/gdb/current/onlinedocs/gdb/Attach.html#Attach) in the GDB manual.

### Move through the stack

Return to the program by using `up`

two times to move up in the stack to `main.cpp`

:

![moving up the stack to main.cpp moving up the stack to main.cpp](https://opensource.com/sites/default/files/uploads/gdb_output_stackframe_up.png)

(Stephan Avenwedde, CC BY-SA 4.0)

Usually, the compiler will create a subroutine for each function or method. Each subroutine has its own stack frame, so moving upwards in the stackframe means moving upwards in the callstack.

You can find out more about [stack evaluation](https://sourceware.org/gdb/current/onlinedocs/gdb/Stack.html#Stack) in the manual.

### Specify the source files

When attaching to an already running process, GDB will look for the source files in the current working directory. Alternatively, you can specify the source directories manually with the [ directory command](https://ftp.gnu.org/old-gnu/Manuals/gdb/html_node/gdb_48.html#SEC49).

## Evaluate dump files

Read [ Creating and debugging Linux dump files](https://opensource.com/article/20/8/linux-dump) for information about this topic.

TL;DR:

- I assume you're working with a recent version of Fedora
- Invoke coredump with the c1 switch:
`coredump -c1`


(Stephan Avenwedde,[CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)) - Load the latest dumpfile with GDB:
`coredumpctl debug`

- Open TUI mode and enter
`layout src`


![coredump output coredump output](https://opensource.com/sites/default/files/uploads/gdb_output_coredump.png)

(Stephan Avenwedde, CC BY-SA 4.0)

The output of `backtrace`

shows that the crash happened five stack frames away from `main.cpp`

. Enter to jump directly to the faulty line of code in `main.cpp`

:

![up 5 output up 5 output](https://opensource.com/sites/default/files/uploads/gdb_output_up_five.png)

(Stephan Avenwedde, CC BY-SA 4.0)

A look at the source code shows that the program tried to free a pointer that was not returned by a memory management function. This results in undefined behavior and caused the `SIGABRT`

.

Debug without symbols

If there are no sources available, things get very hard. I had my first experience with this when trying to solve reverse-engineering challenges. It is also useful to have some knowledge of [assembly language](https://en.wikipedia.org/wiki/Assembly_language).

Check out how it works with this example.

Go to the source directory, open the **Makefile**, and edit line 9 like this:

`CFLAGS =-Wall -Werror -std=c++11 #-g`


To recompile the program, run `make clean`

followed by `make`

and start GDB. The program no longer has any debugging symbols to lead the way through the source code.

![no debugging symbols no debugging symbols](https://opensource.com/sites/default/files/uploads/gdb_output_no_debugging_symbols.png)

(Stephan Avenwedde, CC BY-SA 4.0)

The command `info file`

reveals the memory areas and entry point of the binary:

![info file output info file output](https://opensource.com/sites/default/files/uploads/gdb_output_info_file.png)

(Stephan Avenwedde, CC BY-SA 4.0)

The entry point corresponds with the beginning of the `.text`

area, which contains the actual opcode. To add a breakpoint at the entry point, type `break *0x401110`

then start execution by typing `run`

:

![breakpoint at the entry point breakpoint at the entry point](https://opensource.com/sites/default/files/uploads/gdb_output_break_at_start.png)

(Stephan Avenwedde, CC BY-SA 4.0)

To set up a breakpoint at a certain address, specify it with the dereferencing operator `*`

.

### Choose the disassembler flavor

Before digging deeper into assembly, you can choose which [assembly flavor](https://en.wikipedia.org/wiki/X86_assembly_language#Syntax) to use. GDB's default is AT&T, but I prefer the Intel syntax. Change it with:

`set disassembly-flavor intel`



![changing assembly flavor changing assembly flavor](https://opensource.com/sites/default/files/uploads/gdb_output_disassembly_flavor.png)

(Stephan Avenwedde, CC BY-SA 4.0)

Now open the assembly and register the window by typing `layout asm`

and `layout reg`

. You should now see output like this:

![layout asm and layout reg output layout asm and layout reg output](https://opensource.com/sites/default/files/uploads/gdb_output_layout_reg_asm.png)

(Stephan Avenwedde, CC BY-SA 4.0)

### Save configuration files

Although you have already entered many commands, you haven't actually started debugging. If you are heavily debugging an application or trying to solve a reverse-engineering challenge, it can be useful to save your GDB-specific settings in a file.

The [config file gdbinit](https://github.com/hANSIc99/core_dump_example/blob/master/gdbinit) in this project's GitHub repository contains the recently used commands:

```
set disassembly-flavor intel
set write on
break *0x401110
run -c2
layout asm
layout reg
```


The `set write on`

command enables you to modify the binary during execution.

Quit GDB and reopen it with the configuration file: `gdb -x gdbinit coredump`

.

### Read instructions

With the `c2`

switch applied, the program will crash. The program stops at the entry function, so you have to write `continue`

to proceed with execution:

![continuing execution after crash continuing execution after crash](https://opensource.com/sites/default/files/uploads/gdb_output_asm_div_zero.png)

(Stephan Avenwedde, CC BY-SA 4.0)

The `idiv`

instruction performs an integer division with the dividend in the `RAX`

register and the divisor specified as an argument. The quotient is loaded into the `RAX`

register, and the remainder is loaded into `RDX`

.

From the register overview, you can see the `RAX`

contains *5*, so you have to find out which value is stored on the stack at position `RBP-0x4`

.

### Read memory

To read raw memory content, you must specify a few more parameters than for reading symbols. When you scroll up a bit in the assembly output, you can see the division of the stack:

![stack division output stack division output](https://opensource.com/sites/default/files/uploads/gdb_output_stack_division.png)

(Stephan Avenwedde, CC BY-SA 4.0)

You're most interested in the value of `rbp-0x4`

because this is the position where the argument for `idiv`

is stored. From the screenshot, you can see that the next variable is located at `rbp-0x8`

, so the variable at `rbp-0x4`

is 4 bytes wide.

In GDB, you can use the `x`

command to *examine* any memory content:


`x/`

< optional parameter`n`

`f`

`u`

> < memory address`addr`

>

Optional parameters:

`n`

: Repeat count (default: 1) refers to the unit size`f`

: Format specifier, like in[printf](https://en.wikipedia.org/wiki/Printf_format_string#Type_field)`u`

: Unit size`b`

: bytes`h`

: half words (2 bytes)`w`

: word (4 bytes)(default)`g`

: giant word (8 bytes)


To print out the value at `rbp-0x4`

, type `x/u $rbp-4`

:

![print value print value](https://opensource.com/sites/default/files/uploads/gdb_output_examine_1.png)

(Stephan Avenwedde, CC BY-SA 4.0)

If you keep this pattern in mind, it's straightforward to examine the memory. Check the [examining memory](https://sourceware.org/gdb/current/onlinedocs/gdb/Memory.html) section in the manual.

### Manipulate the assembly

The arithmetic exception happened in the subroutine `zeroDivide()`

. When you scroll a bit upward with the Up arrow key, you can find this pattern:

```
0x401211 <_Z10zeroDividev> push rbp
0x401212 <_Z10zeroDividev+1> mov rbp,rsp
```


This is called the [function prologue](https://en.wikipedia.org/wiki/Function_prologue):

- The base pointer (
`rbp`

) of the calling function is stored on the stack - The value of the stack pointer (
`rsp`

) is loaded to the base pointer (`rbp`

)

Skip this subroutine completely. You can check the call stack with `backtrace`

. You are only one stack frame ahead of your `main`

function, so you can go back to `main`

with a single `up`

:

![Callstack assembly Callstack assembly](https://opensource.com/sites/default/files/uploads/gdb_output_callstack_assembly_0.png)

(Stephan Avenwedde, CC BY-SA 4.0)

In your `main`

function, you can find this pattern:

```
0x401431 <main+497> cmp BYTE PTR [rbp-0x12],0x0
0x401435 <main+501> je 0x40145f <main+543>
0x401437 <main+503> call 0x401211<_Z10zeroDividev>
```


The subroutine `zeroDivide()`

is entered only when `jump equal (je)`

evaluates to `true`

. You can easily replace this with a `jump-not-equal (jne)`

instruction, which has the opcode `0x75`

(provided you are on an x86/64 architecture; the opcodes are different on other architectures). Restart the program by typing `run`

. When the program stops at the entry function, manipulate the opcode by typing:

`set *(unsigned char*)0x401435 = 0x75`


Finally, type `continue`

. The program will skip the subroutine `zeroDivide()`

and won't crash anymore.

## Conclusion

You can find GDB working in the background in many integrated development environments (IDEs), including Qt Creator and the [Native Debug](https://github.com/WebFreak001/code-debug) extension for VSCodium.

![GDB in VSCodium GDB in VSCodium](https://opensource.com/sites/default/files/uploads/vs_codium_native_debug.png)

(Stephan Avenwedde, CC BY-SA 4.0)

It's useful to know how to leverage GDB's functionality. Usually, not all of GDB's functions can be used from the IDE, so you benefit from having experience using GDB from the command line.

## Comments are closed.