GDB摘要GDB 摘要GDB 调试基本流程实战示例调试案例完整 C 语言程序调试过程1. 准备调试程序2. 编译带调试信息3. 启动 GDB 并运行程序4. 设置断点并重新运行5. 单步执行并查看变量6. 进入函数调试7. 跟踪递归调用过程8. 发现问题9. 定位并修复问题10. 验证修复11. 完整调试命令总结12. 调试技巧13. GDB 常见问题与排查14. GDB 命令速查表程序运行控制断点管理栈帧与变量查看内存与寄存器多线程调试其他实用命令C、CDGoGDB 摘要像 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 调试的基本流程图展示了从启动程序到结束调试的完整过程是否单步执行继续执行修改变量启动程序 (run)设置断点 (break)运行/暂停 (run/continue)程序是否遇到断点?检查变量/调用栈 (print/backtrace)程序继续执行调试操作选择单步跟踪 (step/next)继续运行 (continue)修改变量值 (set)程序结束这个流程图展示了 GDB 调试的核心循环启动程序使用run命令开始执行设置断点使用break在关键位置设置暂停点运行控制程序运行并在断点处暂停状态检查暂停时检查变量值、调用栈等信息执行控制选择单步执行或继续运行循环或结束根据需要重复调试或让程序结束实战示例调试案例完整 C 语言程序调试过程本节将通过一个实际的 C 语言程序调试案例详细展示从编译带调试信息、启动 GDB、设置断点、单步执行、查看变量到定位问题的完整调试流程。1. 准备调试程序首先我们创建一个有问题的 C 程序debug_demo.c#includestdio.h#includestdlib.hintfactorial(intn){if(n1){return1;}returnn*factorial(n-1);// 递归计算阶乘}intmain(){intnum5;intresult0;printf(计算 %d 的阶乘...\n,num);// 这里有一个逻辑错误应该调用 factorial(num)resultfactorial(num-1);printf(结果: %d\n,result);// 验证结果if(result120){printf(✓ 结果正确\n);}else{printf(✗ 结果错误期望 120实际得到 %d\n,result);}return0;}2. 编译带调试信息使用-g选项编译程序生成包含调试信息的可执行文件gcc-g-odebug_demo debug_demo.c3. 启动 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. 设置断点并重新运行我们在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 46. 进入函数调试执行step命令进入factorial函数(gdb) step factorial (n4) at debug_demo.c:4 4 int factorial(int n) {查看调用栈(gdb) backtrace #0 factorial (n4) at debug_demo.c:4 #1 0x00005555555551e0 in main () at debug_demo.c:197. 跟踪递归调用过程继续单步执行观察递归过程(gdb) next 5 if (n 1) { (gdb) print n $3 4 (gdb) next 8 return n * factorial(n - 1);再次进入递归调用(gdb) step factorial (n3) at debug_demo.c:4 4 int factorial(int n) { (gdb) continue Continuing. Breakpoint 2, factorial (n3) at debug_demo.c:4 4 int factorial(int n) {8. 发现问题让我们快速执行到函数返回查看最终结果(gdb) finish Run till exit from #0 factorial (n3) at debug_demo.c:4 0x00005555555551c5 in factorial (n4) at debug_demo.c:8 Value returned is $4 6继续执行直到main函数中的factorial调用完成(gdb) finish Run till exit from #0 factorial (n4) at debug_demo.c:8 0x00005555555551e0 in main () at debug_demo.c:19 Value returned is $5 249. 定位并修复问题现在我们可以看到问题所在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行resultfactorial(num);// 正确计算 num 的阶乘11. 完整调试命令总结本次调试使用的主要 GDB 命令序列# 启动和基本设置 gdb ./debug_demo break main break factorial run # 单步执行和查看 next step print variable backtrace # 控制执行流 continue finish # 修改变量和验证 set variable value12. 调试技巧使用layout src在 GDB 中显示源代码窗口使用watch设置观察点当变量值改变时暂停(gdb) watch result使用info breakpoints查看所有断点状态使用commands为断点设置自动执行的命令序列使用record和reverse支持反向调试需要编译时加-record通过这个完整的调试案例你可以看到 GDB 如何帮助我们发现和定位代码中的逻辑错误。实际调试中结合断点、单步执行、变量查看和调用栈分析可以高效地解决复杂的程序问题。以下是 GDB 中最常用的几个命令每个命令都附有简短注释说明其作用# 1. 设置断点 - 在指定函数或行号处暂停程序执行(gdb)breakmain# 在 main 函数入口处设置断点(gdb)break42# 在当前文件的第 42 行设置断点(gdb)breakfile.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)frame2# 切换到调用栈的第 2 帧# 7. 查看源代码 - 显示当前或指定位置的源代码(gdb)list# 显示当前行附近的源代码(gdb)list20,30# 显示第 20-30 行的源代码(gdb)listfunction# 显示指定函数的源代码这些命令涵盖了 GDB 调试的基本流程设置断点 → 运行程序 → 查看变量 → 单步跟踪 → 继续执行。掌握这些命令后你就可以开始有效地使用 GDB 进行程序调试了。13. GDB 常见问题与排查在实际使用 GDB 调试时可能会遇到一些常见问题。下表列出了几个典型问题及其排查方法问题现象可能原因解决方案程序运行后立即退出无断点暂停1. 编译时未添加-g调试信息2. 断点设置位置不正确如设置在了程序退出后3. 程序本身执行过快在断点触发前已结束1. 重新编译并确保添加-g选项gcc -g -o program program.c2. 使用info breakpoints检查断点状态确保断点有效3. 在main函数入口处设置断点(gdb) break mainprint命令显示变量值为optimized out1. 编译时使用了优化选项如-O1,-O2,-O32. 变量被编译器优化掉未在调试信息中保留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. 核心转储未启用或大小限制为 02. 程序被信号终止但未生成核心文件1. 设置核心文件大小无限制ulimit -c unlimited2. 运行程序时捕获信号(gdb) catch signal3. 使用generate-core-file命令手动生成核心文件遇到其他问题时可以尝试以下通用排查步骤使用info registers查看寄存器状态使用x/10i $pc查看当前指令使用thread apply all bt查看所有线程的调用栈检查程序是否链接了调试版本库14. GDB 命令速查表下表整理了 GDB 常用命令方便快速查阅程序运行控制命令简写功能描述示例runr启动程序执行(gdb) runstart-启动程序并停在 main 函数入口(gdb) startcontinuec继续执行直到下一个断点(gdb) csteps单步执行进入函数调用(gdb) snextn单步执行跳过函数调用(gdb) nstepisi单步执行一条机器指令(gdb) sinextini单步执行一条机器指令不进入函数(gdb) nifinishfin执行完当前函数并返回(gdb) finishuntilu运行到指定行号或函数结束(gdb) until 45killk终止正在运行的程序(gdb) killquitq退出 GDB(gdb) quit断点管理命令简写功能描述示例breakb设置断点(gdb) b main(gdb) b 23(gdb) b file.c:45break ifb if设置条件断点(gdb) b 30 if i5tbreaktb设置临时断点触发一次后删除(gdb) tb foowatch-设置观察点变量被修改时暂停(gdb) watch varrwatch-设置读观察点变量被读取时暂停(gdb) rwatch varawatch-设置读写观察点(gdb) awatch varinfo breakpointsi b显示所有断点信息(gdb) i bdeleted删除断点(gdb) d 1(gdb) d删除所有disabledis禁用断点(gdb) dis 1-3enableen启用断点(gdb) en 2clear-清除指定位置的断点(gdb) clear main(gdb) clear 45栈帧与变量查看命令简写功能描述示例backtracebt显示调用栈(gdb) bt(gdb) bt full显示局部变量framef选择栈帧(gdb) f 2up-向上移动栈帧(gdb) updown-向下移动栈帧(gdb) downinfo framei f显示当前栈帧信息(gdb) i finfo localsi locals显示当前帧的局部变量(gdb) i localsinfo argsi args显示当前帧的函数参数(gdb) i argsprintp打印表达式值(gdb) p x(gdb) p *ptr(gdb) p array[5]displaydisp每次程序暂停时自动显示表达式(gdb) display iinfo displayi disp显示所有自动显示表达式(gdb) i dispundisplayundisp取消自动显示(gdb) undisp 1whatiswhat显示变量或表达式的类型(gdb) whatis varptype-显示类型的详细定义(gdb) ptype struct Node内存与寄存器命令简写功能描述示例x-检查内存内容(gdb) x/10x array16进制(gdb) x/20c str字符(gdb) x/8i func指令info registersi r显示所有寄存器值(gdb) i rinfo registeri reg显示指定寄存器值(gdb) i reg eaxset-设置变量或寄存器值(gdb) set var i10(gdb) set $eax0examinex同x命令检查内存(gdb) examine/4wx 0x8048000多线程调试命令简写功能描述示例info threadsi threads显示所有线程信息(gdb) i threadsthreadt切换到指定线程(gdb) t 2thread applyt apply对所有或指定线程执行命令(gdb) t apply all bt(gdb) t apply 1-3 p varset scheduler-locking-设置线程调度锁定(gdb) set scheduler-locking on其他实用命令命令简写功能描述示例listl显示源代码(gdb) l(gdb) l 20,30(gdb) l maindisassembledisas反汇编当前函数或指定地址(gdb) disas(gdb) disas mainshellsh执行 shell 命令(gdb) shell ls -lset logging on-开启日志记录(gdb) set logging onshow-显示 GDB 设置(gdb) show language(gdb) show argshelph显示命令帮助(gdb) help breakC、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 和 CGDB C 的特性十进制浮点格式DGDB 可用于调试用 D 语言编写并使用 GDC、LDC 或 DMD 编译器编译的程序。目前 GDB 仅支持一项 D 语言特定特性 —— 动态数组。GoGDB 可用于调试用 Go 编写并使用以下编译器编译的程序gccgo 或 6g 编译器6g 是 Go 1.4 之前的编译器。以下是 Go 特定功能和限制的总结当前 Go 包在指定全局变量和函数时不需要指定当前包的名称。例如给定程序packagemainvarmyglobShall we?funcmain(){// ...}当在main函数内部停止时以下两种方式都可行(gdb) p myglob (gdb) p main.myglob内置 Go 类型string类型被 GDB 识别并作为字符串打印。内置 Go 函数GDB 表达式解析器识别unsafe.Sizeof函数并在内部处理它。Go 表达式的限制除了^之外所有 Go 运算符都受支持。Go 的_空白标识符不受支持。不支持指针的自动解引用。