GDB摘要
GDB 摘要
像 GDB 这样的调试器的目的是让你在另一个程序执行时查看其"内部"的运行情况,或者在该程序崩溃时查看它当时正在做什么。
GDB 可以做四种主要的事情(以及支持这些事情的其他事情)来帮助你当场捕获错误:
- 启动你的程序,并指定任何可能影响其行为的因素。
- 让你的程序在指定条件下停止。
- 当你的程序停止时,检查发生了什么。
- 更改程序中的内容,这样你就可以尝试纠正一个错误的影响,并继续了解另一个错误。
你可以使用 GDB 来调试用 C 和 C++ 编写的程序。更多信息,请参阅"支持的语言"章节。更多信息,请参阅 C 和 C++ 部分。
对 D 语言的支持是部分的。有关 D 语言的信息,请参阅 D 部分。
对 Modula-2 语言的支持是部分的。有关 Modula-2 语言的信息,请参阅 Modula-2 部分。
对 OpenCL C 语言的支持是部分的。有关 OpenCL C 语言的信息,请参阅 OpenCL C 部分。
目前,调试使用集合、子范围、文件变量或嵌套函数的 Pascal 程序不起作用。GDB 不支持使用 Pascal 语法输入表达式、打印值或类似功能。
GDB 可用于调试用 Fortran 编写的程序,不过可能需要在引用某些变量时加上尾随下划线。
GDB 可用于调试用 Objective-C 编写的程序,使用 Apple/NeXT 或 GNU Objective-C 运行时均可。# 支持的语言
GDB 支持 C、C++、D、Go、Objective-C、Fortran、OpenCL C、Pascal、Rust、汇编、Modula-2 和 Ada。
GDB 调试基本流程
以下是 GDB 调试的基本流程图,展示了从启动程序到结束调试的完整过程:
这个流程图展示了 GDB 调试的核心循环:
- 启动程序:使用
run命令开始执行 - 设置断点:使用
break在关键位置设置暂停点 - 运行控制:程序运行并在断点处暂停
- 状态检查:暂停时检查变量值、调用栈等信息
- 执行控制:选择单步执行或继续运行
- 循环或结束:根据需要重复调试或让程序结束
实战示例
调试案例:完整 C 语言程序调试过程
本节将通过一个实际的 C 语言程序调试案例,详细展示从编译带调试信息、启动 GDB、设置断点、单步执行、查看变量到定位问题的完整调试流程。
1. 准备调试程序
首先,我们创建一个有问题的 C 程序 debug_demo.c:
#include <stdio.h>
#include <stdlib.h>
int factorial(int n) {
if (n <= 1) {
return 1;
}
return n * factorial(n - 1); // 递归计算阶乘
}
int main() {
int num = 5;
int result = 0;
printf("计算 %d 的阶乘...\n", num);
// 这里有一个逻辑错误:应该调用 factorial(num)
result = factorial(num - 1);
printf("结果: %d\n", result);
// 验证结果
if (result == 120) {
printf("✓ 结果正确!\n");
} else {
printf("✗ 结果错误,期望 120,实际得到 %d\n", result);
}
return 0;
}
2. 编译带调试信息
使用 -g 选项编译程序,生成包含调试信息的可执行文件:
gcc -g -o debug_demo debug_demo.c
3. 启动 GDB 并运行程序
gdb ./debug_demo
进入 GDB 后,运行程序:
(gdb) run
Starting program: /home/user/debug_demo
计算 5 的阶乘...
结果: 24
✗ 结果错误,期望 120,实际得到 24
[Inferior 1 (process 12345) exited normally]
程序运行完成,但结果错误(5的阶乘应该是120,实际得到24)。
4. 设置断点并重新运行
查看源码
(gdb) list
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 int factorial(int n) {
5 if (n <= 1) {
6 return 1;
7 }
8 return n * factorial(n - 1); // 递归计算阶乘
9 }
10
(gdb)
11 int main() {
12 int num = 5;
13 int result = 0;
14
15 printf("计算 %d 的阶乘...\n", num);
16
17 // 这里有一个逻辑错误:应该调用 factorial(num)
18 result = factorial(num - 1);
19
20 printf("结果: %d\n", result);
(gdb)
21
22 // 验证结果
23 if (result == 120) {
24 printf("✓ 结果正确!\n");
25 } else {
26 printf("✗ 结果错误,期望 120,实际得到 %d\n", result);
27 }
28
29 return 0;
30 }
我们在 main 函数和 factorial 函数设置断点:
(gdb) break main
Breakpoint 1 at 0x5555555551a9: file debug_demo.c, line 12.
(gdb) break factorial
Breakpoint 2 at 0x555555555169: file debug_demo.c, line 4.
(gdb) run
Starting program: /home/user/debug_demo
Breakpoint 1, main () at debug_demo.c:12
12 int main() {
5. 单步执行并查看变量
使用 next 命令逐行执行:
(gdb) next
13 int num = 5;
(gdb) next
14 int result = 0;
(gdb) next
16 printf("计算 %d 的阶乘...\n", num);
(gdb) next
计算 5 的阶乘...
19 result = factorial(num - 1);
在调用 factorial 之前,查看变量值:
(gdb) print num
$1 = 5
(gdb) print num - 1
$2 = 4
6. 进入函数调试
执行 step 命令进入 factorial 函数:
(gdb) step
factorial (n=4) at debug_demo.c:4
4 int factorial(int n) {
查看调用栈:
(gdb) backtrace
#0 factorial (n=4) at debug_demo.c:4
#1 0x00005555555551e0 in main () at debug_demo.c:19
7. 跟踪递归调用过程
继续单步执行,观察递归过程:
(gdb) next
5 if (n <= 1) {
(gdb) print n
$3 = 4
(gdb) next
8 return n * factorial(n - 1);
再次进入递归调用:
(gdb) step
factorial (n=3) at debug_demo.c:4
4 int factorial(int n) {
(gdb) continue
Continuing.
Breakpoint 2, factorial (n=3) at debug_demo.c:4
4 int factorial(int n) {
8. 发现问题
让我们快速执行到函数返回,查看最终结果:
(gdb) finish
Run till exit from #0 factorial (n=3) at debug_demo.c:4
0x00005555555551c5 in factorial (n=4) at debug_demo.c:8
Value returned is $4 = 6
继续执行直到 main 函数中的 factorial 调用完成:
(gdb) finish
Run till exit from #0 factorial (n=4) at debug_demo.c:8
0x00005555555551e0 in main () at debug_demo.c:19
Value returned is $5 = 24
9. 定位并修复问题
现在我们可以看到问题所在:factorial(4) 返回 24,而我们需要的是 factorial(5)。查看第19行代码:
(gdb) list 19
14 int result = 0;
15
16 printf("计算 %d 的阶乘...\n", num);
17
18 // 这里有一个逻辑错误:应该调用 factorial(num)
19 result = factorial(num - 1);
20
21 printf("结果: %d\n", result);
问题很明显:第19行错误地调用了 factorial(num - 1) 而不是 factorial(num)。
10. 验证修复
我们可以直接修改变量值来验证:
(gdb) set result = factorial(num)
(gdb) print result
$6 = 120
或者修改代码后重新编译:
// 修复第19行
result = factorial(num); // 正确:计算 num 的阶乘
11. 完整调试命令总结
本次调试使用的主要 GDB 命令序列:
# 启动和基本设置
gdb ./debug_demo
break main
break factorial
run
# 单步执行和查看
next
step
print variable
backtrace
# 控制执行流
continue
finish
# 修改变量和验证
set variable = value
12. 调试技巧
- 使用
layout src:在 GDB 中显示源代码窗口 - 使用
watch:设置观察点,当变量值改变时暂停(gdb) watch result - 使用
info breakpoints:查看所有断点状态 - 使用
commands:为断点设置自动执行的命令序列 - 使用
record和reverse:支持反向调试(需要编译时加-record)
通过这个完整的调试案例,你可以看到 GDB 如何帮助我们发现和定位代码中的逻辑错误。实际调试中,结合断点、单步执行、变量查看和调用栈分析,可以高效地解决复杂的程序问题。
以下是 GDB 中最常用的几个命令,每个命令都附有简短注释说明其作用:
# 1. 设置断点 - 在指定函数或行号处暂停程序执行
(gdb) break main # 在 main 函数入口处设置断点
(gdb) break 42 # 在当前文件的第 42 行设置断点
(gdb) break file.c:15 # 在 file.c 文件的第 15 行设置断点
# 2. 运行程序 - 启动被调试的程序
(gdb) run # 从头开始运行程序
(gdb) run arg1 arg2 # 带参数运行程序
# 3. 查看变量值 - 显示变量的当前值
(gdb) print variable # 打印变量的值
(gdb) print *pointer # 打印指针指向的值
(gdb) print array[0] # 打印数组元素
# 4. 单步执行 - 逐行执行代码
(gdb) next # 执行下一行代码(跳过函数调用)
(gdb) step # 执行下一行代码(进入函数调用)
# 5. 继续执行 - 从当前断点继续运行
(gdb) continue # 继续执行直到下一个断点或程序结束
(gdb) c # continue 的简写形式
# 6. 查看调用栈 - 显示函数调用链
(gdb) backtrace # 显示当前调用栈
(gdb) bt # backtrace 的简写形式
(gdb) frame 2 # 切换到调用栈的第 2 帧
# 7. 查看源代码 - 显示当前或指定位置的源代码
(gdb) list # 显示当前行附近的源代码
(gdb) list 20,30 # 显示第 20-30 行的源代码
(gdb) list function # 显示指定函数的源代码
这些命令涵盖了 GDB 调试的基本流程:设置断点 → 运行程序 → 查看变量 → 单步跟踪 → 继续执行。掌握这些命令后,你就可以开始有效地使用 GDB 进行程序调试了。
13. GDB 常见问题与排查
在实际使用 GDB 调试时,可能会遇到一些常见问题。下表列出了几个典型问题及其排查方法:
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 程序运行后立即退出,无断点暂停 | 1. 编译时未添加 -g 调试信息2. 断点设置位置不正确(如设置在了程序退出后) 3. 程序本身执行过快,在断点触发前已结束 | 1. 重新编译并确保添加 -g 选项:gcc -g -o program program.c2. 使用 info breakpoints 检查断点状态,确保断点有效3. 在 main 函数入口处设置断点:(gdb) break main |
print 命令显示变量值为 <optimized out> | 1. 编译时使用了优化选项(如 -O1, -O2, -O3)2. 变量被编译器优化掉,未在调试信息中保留 | 1. 重新编译时禁用优化:gcc -g -O0 -o program program.c2. 使用 -Og 选项(优化但不影响调试)3. 尝试在变量作用域内查看,或使用 info locals 查看局部变量 |
| 单步执行时跳过了预期代码行 | 1. 编译器优化导致代码重排 2. 行号信息不准确 3. 正在执行的是编译器生成的代码(如内联函数) | 1. 使用 stepi(单步指令)代替 step,逐条指令执行2. 使用 disassemble 查看当前函数的汇编代码3. 设置断点时使用函数名而非行号: (gdb) break function_name |
| 断点无法触发或显示为「待定」 | 1. 共享库未加载 2. 断点设置在尚未加载的代码段 3. 程序是多进程/多线程架构 | 1. 使用 start 命令启动程序并停在 main 函数2. 使用 set breakpoint pending on 允许设置待定断点3. 对于动态库,使用 break filename:function 格式 |
| 程序崩溃时无堆栈信息 | 1. 核心转储未启用或大小限制为 0 2. 程序被信号终止但未生成核心文件 | 1. 设置核心文件大小无限制:ulimit -c unlimited2. 运行程序时捕获信号: (gdb) catch signal3. 使用 generate-core-file 命令手动生成核心文件 |
遇到其他问题时,可以尝试以下通用排查步骤:
- 使用
info registers查看寄存器状态 - 使用
x/10i $pc查看当前指令 - 使用
thread apply all bt查看所有线程的调用栈 - 检查程序是否链接了调试版本库
14. GDB 命令速查表
下表整理了 GDB 常用命令,方便快速查阅:
程序运行控制
| 命令 | 简写 | 功能描述 | 示例 |
|---|---|---|---|
run | r | 启动程序执行 | (gdb) run |
start | - | 启动程序并停在 main 函数入口 | (gdb) start |
continue | c | 继续执行直到下一个断点 | (gdb) c |
step | s | 单步执行,进入函数调用 | (gdb) s |
next | n | 单步执行,跳过函数调用 | (gdb) n |
stepi | si | 单步执行一条机器指令 | (gdb) si |
nexti | ni | 单步执行一条机器指令,不进入函数 | (gdb) ni |
finish | fin | 执行完当前函数并返回 | (gdb) finish |
until | u | 运行到指定行号或函数结束 | (gdb) until 45 |
kill | k | 终止正在运行的程序 | (gdb) kill |
quit | q | 退出 GDB | (gdb) quit |
断点管理
| 命令 | 简写 | 功能描述 | 示例 |
|---|---|---|---|
break | b | 设置断点 | (gdb) b main(gdb) b 23(gdb) b file.c:45 |
break if | b if | 设置条件断点 | (gdb) b 30 if i==5 |
tbreak | tb | 设置临时断点(触发一次后删除) | (gdb) tb foo |
watch | - | 设置观察点(变量被修改时暂停) | (gdb) watch var |
rwatch | - | 设置读观察点(变量被读取时暂停) | (gdb) rwatch var |
awatch | - | 设置读写观察点 | (gdb) awatch var |
info breakpoints | i b | 显示所有断点信息 | (gdb) i b |
delete | d | 删除断点 | (gdb) d 1(gdb) d(删除所有) |
disable | dis | 禁用断点 | (gdb) dis 1-3 |
enable | en | 启用断点 | (gdb) en 2 |
clear | - | 清除指定位置的断点 | (gdb) clear main(gdb) clear 45 |
栈帧与变量查看
| 命令 | 简写 | 功能描述 | 示例 |
|---|---|---|---|
backtrace | bt | 显示调用栈 | (gdb) bt(gdb) bt full(显示局部变量) |
frame | f | 选择栈帧 | (gdb) f 2 |
up | - | 向上移动栈帧 | (gdb) up |
down | - | 向下移动栈帧 | (gdb) down |
info frame | i f | 显示当前栈帧信息 | (gdb) i f |
info locals | i locals | 显示当前帧的局部变量 | (gdb) i locals |
info args | i args | 显示当前帧的函数参数 | (gdb) i args |
print | p | 打印表达式值 | (gdb) p x(gdb) p *ptr(gdb) p array[5] |
display | disp | 每次程序暂停时自动显示表达式 | (gdb) display i |
info display | i disp | 显示所有自动显示表达式 | (gdb) i disp |
undisplay | undisp | 取消自动显示 | (gdb) undisp 1 |
whatis | what | 显示变量或表达式的类型 | (gdb) whatis var |
ptype | - | 显示类型的详细定义 | (gdb) ptype struct Node |
内存与寄存器
| 命令 | 简写 | 功能描述 | 示例 |
|---|---|---|---|
x | - | 检查内存内容 | (gdb) x/10x &array(16进制)(gdb) x/20c str(字符)(gdb) x/8i func(指令) |
info registers | i r | 显示所有寄存器值 | (gdb) i r |
info register | i reg | 显示指定寄存器值 | (gdb) i reg eax |
set | - | 设置变量或寄存器值 | (gdb) set var i=10(gdb) set $eax=0 |
examine | x | 同 x 命令,检查内存 | (gdb) examine/4wx 0x8048000 |
多线程调试
| 命令 | 简写 | 功能描述 | 示例 |
|---|---|---|---|
info threads | i threads | 显示所有线程信息 | (gdb) i threads |
thread | t | 切换到指定线程 | (gdb) t 2 |
thread apply | t apply | 对所有或指定线程执行命令 | (gdb) t apply all bt(gdb) t apply 1-3 p var |
set scheduler-locking | - | 设置线程调度锁定 | (gdb) set scheduler-locking on |
其他实用命令
| 命令 | 简写 | 功能描述 | 示例 |
|---|---|---|---|
list | l | 显示源代码 | (gdb) l(gdb) l 20,30(gdb) l main |
disassemble | disas | 反汇编当前函数或指定地址 | (gdb) disas(gdb) disas main |
shell | sh | 执行 shell 命令 | (gdb) shell ls -l |
set logging on | - | 开启日志记录 | (gdb) set logging on |
show | - | 显示 GDB 设置 | (gdb) show language(gdb) show args |
help | h | 显示命令帮助 | (gdb) help break |
支持的语言
C、C++
由于 C 和 C++ 密切相关,因此 GDB 的许多特性适用于这两种语言。只要是这种情况,我们就会一起讨论这些语言。
C++ 调试工具由 C++ 编译器和 GDB 共同实现。因此,要有效地调试你的 C++ 代码,你必须使用受支持的 C++ 编译器来编译你的 C++ 程序,例如 GNU g++,或者惠普 ANSI C++ 编译器(aCC)。
- C 和 C++ 运算符
- C 和 C++ 常量
- C++ 表达式
- C 和 C++ 默认值
- C 和 C++ 类型和范围检查
- GDB 和 C++
- GDB C++ 的特性
- 十进制浮点格式
D
GDB 可用于调试用 D 语言编写并使用 GDC、LDC 或 DMD 编译器编译的程序。目前 GDB 仅支持一项 D 语言特定特性 —— 动态数组。
Go
GDB 可用于调试用 Go 编写并使用以下编译器编译的程序:gccgo 或 6g 编译器(6g 是 Go 1.4 之前的编译器)。
以下是 Go 特定功能和限制的总结:
当前 Go 包
在指定全局变量和函数时,不需要指定当前包的名称。
例如,给定程序:
package main
var myglob = "Shall we?"
func main() {
// ...
}
当在 main 函数内部停止时,以下两种方式都可行:
(gdb) p myglob
(gdb) p main.myglob
内置 Go 类型
string 类型被 GDB 识别并作为字符串打印。
内置 Go 函数
GDB 表达式解析器识别 unsafe.Sizeof 函数并在内部处理它。
Go 表达式的限制
除了 &^ 之外,所有 Go 运算符都受支持。Go 的 _ "空白标识符"不受支持。不支持指针的自动解引用。

1015

被折叠的 条评论
为什么被折叠?



