调试工具 gdb 的多种用法
本文内容针对于 Linux(特别是 NOI Linux)编写,注意部分内容不支持 WIndows

推荐文章:

使用原生 gdb 命令行会比使用 Dev-C++ 甚至 VSCode 解锁更多高级功能。

文章内容可能很长,建议使用左侧目录快速导航。

什么是 gdb

gdb (GNU Debugger) 是一个用于Unix/Linux系统的命令行调试工具,主要用于C/C++程序的调试。

它允许程序员在程序运行时暂停执行,逐步执行代码,检查变量的值,修改程序的执行流程,并诊断崩溃原因。gdb 支持多种编程语言(虽然主要用于 C++ 程序),可以调试本地和远程程序。作为 GNU 工具链的重要组成部分,gdb 因其强大的功能和广泛的可用性而成为程序员必不可少的调试工具之一。

编译一个支持 gdb 调试的 C++ 源文件

以下面的例子为例($n^2$ 最长公共子序列问题):

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <vector>
#include <map>
#define INF 0x3f3f3f3f
#define N 10004
#define MOD 998'244'353
#define ll long long
inline int read();
using namespace std;
int listA[N], listB[N];
int dp[N][N];

int main() {
    int n;
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) {
        scanf("%d", &listA[i]);
    }
    for(int i = 1; i <= n; i++) {
        scanf("%d", &listB[i]);
    }
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= n; j++) {
            if(listA[i] == listB[j]) {
                dp[i][j] = dp[i - 1][j - 1] + 1;
            } else {
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
            }
        }
    }
    printf("%d", dp[n][n]);
}

inline int read() {
    int x = 0, f = 1;
    char ch;
    ch = getchar();
    while (ch > '9' || ch < '0') {
        if (ch == '-')
            f = -f;
        ch = getchar();
    }
    while (ch <= '9' && ch >= '0') {
        x = (x << 1) + (x << 3) + (ch & 15);
        ch = getchar();
    }
    return x * f;
}
C++

将其保存为 LCS.cpp,使用指令编译:

$ g++ -g [ -O2 -Wall -Wextra -std=c++14 ] LCS.cpp -o LCS
Bash

“[ ... ]” 内的参数是可选的。

这会获得一个可执行文件 LCS不会还有人不知道 Linux 下可执行文件没有后缀名吧)。使用以下指令启动 gdb:

$ gdb -q ./LCS
Bash

注:-q 可以跳过 gdb 启动信息。

列出代码内容

编辑器里查看的 cpp 文件可能会与调试信息里的代码信息有冲突。设置断点以调试信息里的为准。

在 gdb 中可以使用 list(简写为 l)命令来显示源代码以及行号。默认输出 10 行,你可以通过按下回车再输出 10 行。

(gdb) l
3       #include <cstring>
4       #include <string>
5       #include <algorithm>
6       #include <vector>
7       #include <map>
8       #define INF 0x3f3f3f3f
9       #define N 10004
10      #define MOD 998'244'353
11      #define ll long long
12      inline int read();
(gdb) ↩
13      using namespace std;
14      int listA[N], listB[N];
15      int dp[N][N];
16
17      int main() {
18          int n;
19          scanf("%d", &n);
20          for(int i = 1; i <= n; i++) {
21              scanf("%d", &listA[i]);
22          }
Bash

↩ 是回车。

list 命令可以指定行号,函数(但不支持 inline 函数):

(gdb) l 19
14      int listA[N], listB[N];
15      int dp[N][N];
16
17      int main() {
18          int n;
19          scanf("%d", &n);
20          for(int i = 1; i <= n; i++) {
21              scanf("%d", &listA[i]);
22          }
23          for(int i = 1; i <= n; i++) {
(gdb) l main
12      inline int read();
13      using namespace std;
14      int listA[N], listB[N];
15      int dp[N][N];
16
17      int main() {
18          int n;
19          scanf("%d", &n);
20          for(int i = 1; i <= n; i++) {
21              scanf("%d", &listA[i]);
Bash

还可以指定范围:

(gdb) l 38,52
38      inline int read() {
39          int x = 0, f = 1;
40          char ch;
41          ch = getchar();
42          while (ch > '9' || ch < '0') {
43              if (ch == '-')
44                  f = -f;
45              ch = getchar();
46          }
47          while (ch <= '9' && ch >= '0') {
48              x = (x << 1) + (x << 3) + (ch & 15);
49              ch = getchar();
50          }
51          return x * f;
52      }
Bash

设置断点

在 gdb 中可以使用 breakpoint(简写为 b)命令来设置断点。

(gdb) b 18
Breakpoint 1 at 0x110a: file LCS.cpp, line 18.
(gdb) b 28
Breakpoint 2 at 0x1260: file LCS.cpp, line 28.
Bash

使用 i b 列出所有断点,d <断点ID> 删除断点。你也可以使用 d 删除所有断点。

(gdb) i b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x000000000000110a in main() at LCS.cpp:18
2       breakpoint     keep y   0x00005555555551ec in main() at LCS.cpp:28
(gdb) d 2
(gdb) i b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x000000000000110a in main() at LCS.cpp:18
(gdb) d
Delete all breakpoints? (y or n) y
(gdb) i b
No breakpoints or watchpoints.
Bash

这样设置的断点在每次执行到此行时都会打断程序,如果想使断点只生效一次,可以使用 tbreak(简写为 tb)设置临时断点。

(gdb) b 18
Breakpoint 3 at 0x110a: file LCS.cpp, line 18.
(gdb) tb 28
Temporary breakpoint 4 at 0x1260: file LCS.cpp, line 28.
(gdb) i b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x000000000000110a in main() at LCS.cpp:18
2       breakpoint     del  y   0x00005555555551ec in main() at LCS.cpp:28
Bash

如果想让断点在前 $n$ 次命中时不打断,可以用 ignore <断点ID> <次数> 设置次数忽略:

(gdb) ignore 2 5
Will ignore next 5 crossings of breakpoint 2.
Bash

现在,运行吧!

使用 run 指令运行程序。程序会在第一个断点处停止:

(gdb) run
Starting program: /home/workspace/LCS 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, main () at LCS.cpp:19
19          scanf("%d", &n);
Bash

使用 continue 使程序运行到下一个断点(你可能需要在这时输入输入数据):

(gdb) continue
Continuing.
5 
3 2 1 4 5
1 2 3 4 5

Breakpoint 2, main () at LCS.cpp:28
28                  if(listA[i] == listB[j]) {
Bash

使用 next 跳转至下一行,使用 step 进入函数。进入函数后,输入 finish 立即执行函数剩余内容并跳出。

(gdb) next
31                      dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
(gdb) step
std::max<int> (__b=@0x555555561c90: 0, __a=@0x555555558044: 0) at /usr/include/c++/11/bits/stl_algobase.h:254
254         max(const _Tp& __a, const _Tp& __b)
(gdb) finish
Run till exit from #0  std::max<int> (__b=@0x555555561c90: 0, __a=@0x555555558044: 0)
    at /usr/include/c++/11/bits/stl_algobase.h:254
main () at LCS.cpp:27
27              for(int j = 1; j <= n; j++) {
Bash

使用 until <行号> 立即执行到指定行号(如果中间有断点还是会暂停):

(gdb) until 33

Breakpoint 2, main () at LCS.cpp:28
28                  if(listA[i] == listB[j]) {
(gdb) d 2
(gdb) until 33
0x00007ffff7829d90 in __libc_start_call_main (main=main@entry=0x5555555550e0 <main()>, argc=argc@entry=1, argv=argv@entry=0x7fffffffe588) at ../sysdeps/nptl/libc_start_call_main.h:58
(gdb) next
3
[Inferior 1 (process 44134) exited normally]
Bash

查看变量

使用 print <变量>(简写为 p) 查看表达式的值:

(gdb) print i
$1 = 1
(gdb) p j
$2 = 1
(gdb) p listA[i]
$3 = 3
(gdb) p listB[j]
$4 = 1
Bash

你也可以设置观察点(Watchpoint),当表达式值发生改变时会提示。使用 watch <变量名> 即可:

(gdb) watch dp[n][n]
Hardware watchpoint 3: dp[n][n]
(gdb) continue
Continuing.

Hardware watchpoint 3: dp[n][n]

Old value = 0
New value = 3
main () at LCS.cpp:27
27              for(int j = 1; j <= n; j++) {
Bash

注:如果提示 value has been optimized out,则证明变量已被 O2 优化。

修改变量的值

使用 set var 来修改大部分变量的值:

(gdb) set var n=100
Bash

查看 STL 容器

自 gdb 7.0 之后,默认使用 gcc 提供的 Python 脚本,来改善显示结果:

(gdb) l 20
15      int dp[N][N];
16      vector<int> vec;
17
18      int main() {
19          int n;
20          vec.push_back(1);
21          scanf("%d", &n);
22          for(int i = 1; i <= n; i++) {
23              scanf("%d", &listA[i]);
24          }
(gdb) b 20
Breakpoint 1 at 0x1324: file LCS.cpp, line 20.
(gdb) run
Starting program: /home/zhangborui/OI-Workspace/LCS 
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, main () at LCS.cpp:20
20          vec.push_back(1);
(gdb) next
21          scanf("%d", &n);
(gdb) p vec
$1 = std::vector of length 1, capacity 1 = {1}
Bash

NOI Linux 自带此功能,如需要额外在老旧系统上安装此功能,可以参考:100-gdb-tips/print-STL-container.md

或者暴力背过脚本内容:

(gdb) p *(vec._M_impl._M_start)@vec.size()
$3 = {1}
Bash

TUI 界面

你知道 gdb 有图形化模式吗?

使用 -tui 参数启动 gdb,或者在调试中按下 Ctrl+X+A 即可进入 gdb 的图形模式。这时可以一边查询代码一边进行操作。

暂无评论

发送评论 编辑评论


				
上一篇
下一篇