推荐文章:
使用原生 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 启动信息。
列出代码内容
在 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}
BashNOI Linux 自带此功能,如需要额外在老旧系统上安装此功能,可以参考:100-gdb-tips/print-STL-container.md
或者暴力背过脚本内容:
(gdb) p *(vec._M_impl._M_start)@vec.size()
$3 = {1}
BashTUI 界面
你知道 gdb 有图形化模式吗?
使用 -tui 参数启动 gdb,或者在调试中按下 Ctrl+X+A 即可进入 gdb 的图形模式。这时可以一边查询代码一边进行操作。