第一部分:认识GDB------开发者的必备工具
在Linux软件开发中,有四大核心工具构成工作基石:
-
gcc编译器:将源代码转换为可执行文件。
-
vim文本编辑器:编写和修改源代码。
-
gdb调试器 :用于动态检查和修正程序的逻辑错误,是解决程序崩溃(如"段错误")和异常行为的关键。
-
makefile:自动化工程管理,控制编译流程。
本笔记聚焦于 GDB,掌握它是从"能写代码"迈向"能高效解决问题"的关键一步。
第二部分:GDB调试完整流程
一次完整的GDB调试通常遵循下图所示的清晰路径,从准备代码到一步步定位问题:
flowchart TD
A[第一步:准备调试<br>编译时加入 -g 选项] --> B[第二步:启动调试器<br>gdb ./可执行文件]
B --> C[第三步:设置断点<br>b 行号/函数名]
C --> D[第四步:运行程序<br>r (可加参数)]
D --> E{程序暂停于断点}
E --> F[第五步:检查程序状态<br>p/display 查看变量<br>where/bt 查看调用栈]
F --> G{第六步:控制执行}
G -- 不进入函数 --> H[n: 单步执行(跳过函数)]
H --> E
G -- 进入函数内部 --> I[s: 单步执行(进入函数)]
I --> E
G -- 继续运行 --> J[c: 继续到下一断点]
J --> E
接下来,我们对流程中的每个环节和常用命令进行详细说明。
第三部分:核心命令详解与实战演示
1. 准备与启动
-
编译(关键步骤!) :必须使用
-g选项编译,将调试信息嵌入可执行文件。gcc -g main.c linklist.c -o myprogram -
启动:加载编译好的程序。
gdb ./myprogram
2. 运行与断点控制
-
运行 (r/run) :开始执行程序。如果需要命令行参数,直接在
r后面添加。(gdb) r (gdb) r arg1 arg2 # 带参数运行 -
设置断点 (b/break):让程序在特定位置暂停,以便观察。
(gdb) b 20 # 在当前文件第20行设断点 (gdb) b main # 在main函数入口设断点 (gdb) b linklist.c:71 # 在指定文件的指定行设断点 (gdb) b DestroyLinkList # 在函数入口设断点
3. 查看状态(调试的核心)
-
查看变量 (p/print):
(gdb) p list # 查看变量list的值(通常是一个地址) (gdb) p *list # 解引用,查看指针list指向的内容 (gdb) p **list # 对二级指针双重解引用(常用于查看链表头节点结构) -
持续监视 (display):设置后,每次程序暂停都会自动打印该变量的值。
-
查看调用栈 (where/bt) :这是定位问题的"王牌命令"。当程序崩溃或停在断点时,它能显示函数调用层次,明确告诉你"程序是如何一步步执行到这里的"。
(gdb) where -
查看代码 (l/list):显示当前位置附近的源代码。
4. 控制执行流程
-
单步执行-不进入 (n/next) :执行下一行代码,将整个函数调用当作一步。
-
单步执行-进入 (s/step) :执行下一行代码,如果下一行是函数调用,则进入该函数内部。
-
继续执行 (c/continue):从当前断点继续运行,直到遇到下一个断点或程序结束。
5. 其他
- 退出 (q/quit):退出GDB调试器。
第四部分:实战应用------调试"段错误"(Segmentation Fault)
"段错误"是C/C++程序中最常见的崩溃原因,通常由非法内存访问(如空指针解引用)引起。GDB可以精确定位。
专用调试步骤(与常规调试有区别):
-
编译 :
gcc -g -o test test.c(必须加-g) -
启动GDB :
gdb ./test -
直接运行 :
(gdb) r(无需设断点,让程序自由运行直到崩溃) -
重现错误:如果程序需要输入,此时提供输入数据。
-
定位根源 :程序崩溃后,GDB会捕获错误并暂停。立即输入
where或bt。 -
分析结果 :
where命令会直接输出导致崩溃的源代码文件和行号(例如:linklist.c:71),以及完整的函数调用链。结合代码即可快速分析(例如,在71行对某个可能为NULL的指针进行了->操作)。
示例输出分析:
Program received signal SIGSEGV, Segmentation fault.
0x0000555555554d29 in IsEmptyLinkList (list=0x0) at linklist.c:71
71 return NULL == list->head;
(gdb) where
#0 0x0000555555554d29 in IsEmptyLinkList (list=0x0) at linklist.c:71
#1 0x0000555555554e7b in InsertTailLinkList (list=0x0, data=...) at linklist.c:105
#2 0x0000555555554a9e in main (argc=1, argv=0x7fffffffddc8) at main.c:42
解读 :错误发生在 linklist.c文件的第71行,函数 IsEmptyLinkList内部。原因是传入的参数 list的值是 0x0(NULL),在第71行试图访问 list->head导致了段错误。调用它的是 InsertTailLinkList函数,而最初调用者是 main函数。这样,问题根源一目了然。
视图切换 :在GDB中输入 layout src可以打开源码/命令分屏视图,方便对照调试。
总结
GDB调试的核心思想是:控制程序执行 -> 暂停观察状态 -> 分析逻辑。请牢记以下关键点:
-
调试的前提 :编译时必须加
-g选项。 -
调试的起点 :通过
b设置断点或直接r运行至崩溃。 -
定位问题的利器 :程序异常时,第一时间使用
where命令查看调用栈。 -
查看内存的利器 :对指针灵活使用
p、p *、p **来探查数据结构。 -
区分执行 :
n(跳过函数)和s(进入函数)用于不同粒度的单步跟踪。
将这份流程和命令作为手册,在实战中反复运用,您将能独立解决大部分程序运行时的疑难杂症。