在Linux环境下开发C/C++程序时,调试是每个开发者必须掌握的技能。与Windows平台不同,Linux下的调试工具链有着自己独特的工作方式和哲学。
软件开发通常分为两种构建模式:debug模式和release模式。这两种模式在编译过程中采用了完全不同的策略:
Debug模式:
Release模式:
在Windows的Visual Studio中,默认的调试配置就是debug模式,开发者可以直接开始调试。但在Linux环境下,gcc/g++编译器默认采用的是release模式,这意味着直接编译的程序无法进行调试。
要让Linux下的程序支持调试,必须在编译时添加-g选项:
bash复制gcc -g test.c -o test
这个-g选项的作用是:
注意:
-g选项应该放在编译命令的前面,因为gcc的选项顺序有时会影响编译行为。同时,建议将-g与-O0(禁用优化)一起使用,避免优化后的代码与源代码行号对应不上。
调试信息会显著增加可执行文件的大小,但不会影响程序的运行性能。在实际开发中,我们通常只在开发阶段使用-g选项,在最终发布时去掉这个选项。
GDB(GNU Debugger)是Linux下最常用的调试工具,它功能强大但学习曲线较陡。掌握GDB的基本使用是Linux开发者的必备技能。
启动GDB调试一个程序的基本命令是:
bash复制gdb 可执行文件
例如:
bash复制gdb test
进入GDB环境后,会看到(gdb)提示符。要退出GDB环境,可以使用:
bash复制quit
或者简写为:
bash复制q
在GDB中查看源代码是调试的基础操作。主要使用list命令(可简写为l):
bash复制list
这个命令会显示当前源文件的10行代码,默认从main函数开始。连续输入list会显示后续的代码。
更灵活的查看方式包括:
list 行号:从指定行开始显示list 函数名:显示指定函数的代码list 文件名:行号:显示指定文件的指定行提示:如果源代码没有显示,可能是因为编译时没有加
-g选项,或者GDB没有找到源文件。可以使用directory命令添加源代码搜索路径。
断点是调试的核心工具,GDB提供了丰富的断点管理功能。
基本断点设置命令是break(可简写为b):
bash复制break 行号
例如:
bash复制break 20
更复杂的断点设置方式:
break 函数名:在函数入口处设置断点break 文件名:行号:在指定文件的指定行设置断点break *地址:在内存地址处设置断点(高级用法)要查看当前设置的所有断点,使用:
bash复制info breakpoints
或者简写:
bash复制info b
这个命令会显示每个断点的编号、类型、状态、地址和位置等信息。
删除断点使用delete命令(可简写为d):
bash复制delete 断点编号
例如删除1号断点:
bash复制delete 1
禁用断点使用disable命令:
bash复制disable 断点编号
重新启用被禁用的断点使用enable命令:
bash复制enable 断点编号
经验分享:在实际调试中,建议不要轻易删除断点,而是禁用它们。这样当需要重新调试相同位置时,可以直接启用,而不需要重新设置。
GDB提供了多种控制程序执行的方式,可以满足不同的调试需求。
在GDB中启动程序使用run命令(可简写为r):
bash复制run
这个命令会从头开始执行程序,直到遇到断点、程序结束或发生异常。
可以给程序传递命令行参数:
bash复制run 参数1 参数2 ...
单步执行是调试中最常用的操作之一,GDB提供了两种单步执行方式:
next(简写n):逐过程执行
step(简写s):逐语句执行
当程序在断点处停止后,可以使用continue命令(简写c)继续执行:
bash复制continue
程序会继续运行,直到遇到下一个断点或程序结束。
当使用step进入函数内部后,如果想直接执行完当前函数并返回到调用处,可以使用finish命令:
bash复制finish
这个命令会执行完当前函数的剩余部分,并在函数返回后暂停。
如果想直接运行到某一行代码,可以使用until命令:
bash复制until 行号
例如:
bash复制until 50
这个命令会执行程序直到达到指定行,或者遇到断点、程序结束。
调试过程中查看和修改变量值是定位问题的重要手段。
使用print命令(简写p)可以查看变量值:
bash复制print 变量名
例如:
bash复制print x
print命令功能强大,可以:
如果需要在每次程序暂停时自动显示某些变量的值,可以使用display命令:
bash复制display 变量名
例如:
bash复制display x
要查看当前设置的自动显示变量,使用:
bash复制info display
取消自动显示使用undisplay命令:
bash复制undisplay 编号
在调试过程中可以直接修改变量的值,这对于测试不同条件下的程序行为非常有用:
bash复制set var 变量名=值
例如:
bash复制set var x=10
注意:修改后的值只在当前调试会话中有效,不会改变源代码。
掌握了GDB的基本使用后,下面介绍一些高级调试技巧。
条件断点只在特定条件满足时才会触发:
bash复制break 行号 if 条件
例如:
bash复制break 30 if x==5
也可以为已存在的断点添加条件:
bash复制condition 断点编号 条件
例如:
bash复制condition 2 i>10
观察点用于监视变量或表达式的值变化:
bash复制watch 变量名
例如:
bash复制watch x
当被监视的变量值发生变化时,程序会自动暂停。
当程序崩溃或暂停时,可以使用backtrace命令(简写bt)查看调用栈:
bash复制backtrace
这个命令会显示当前的函数调用链,对于分析程序崩溃原因非常有用。
对于多线程程序,GDB提供了专门的线程调试命令:
info threads:查看所有线程thread 线程号:切换到指定线程break 行号 thread 线程号:在指定线程设置断点当程序崩溃时,可以使用GDB分析核心转储文件:
bash复制gdb 可执行文件 core文件
这可以帮助定位程序崩溃的原因和位置。
虽然GDB功能强大,但其命令行界面对于查看源代码不太友好。CGDB是GDB的一个前端,提供了分屏显示源代码的功能。
在Ubuntu/Debian系统上安装:
bash复制sudo apt-get install cgdb
在CentOS/RHEL系统上安装:
bash复制sudo yum install cgdb
启动CGDB与GDB类似:
bash复制cgdb 可执行文件
CGDB界面分为两部分:
在CGDB中:
ESC键进入源代码窗口,可以使用方向键浏览代码i键返回命令窗口CGDB保留了GDB的所有功能,同时提供了更好的源代码查看体验,是GDB用户的理想选择。
高效的调试不仅需要工具使用技巧,还需要合理的策略和方法。
当面对复杂问题时,可以采用二分查找策略:
当遇到难以定位的问题时,尝试:
合理使用日志输出可以辅助调试:
以下是一些常见问题及其排查方法:
程序崩溃无提示:
gdb加载程序bt查看调用栈内存泄漏:
死锁:
info threads查看线程状态性能问题:
perf或gprof进行性能分析为了便于查阅,这里整理了一份常用的GDB命令速查表:
| 命令 | 说明 | 示例 |
|---|---|---|
run |
启动程序 | run arg1 arg2 |
break |
设置断点 | break 20 |
info breakpoints |
查看断点 | info b |
delete |
删除断点 | delete 1 |
disable |
禁用断点 | disable 2 |
enable |
启用断点 | enable 2 |
next |
逐过程执行 | next |
step |
逐语句执行 | step |
continue |
继续执行 | continue |
finish |
执行完当前函数 | finish |
until |
运行到指定行 | until 50 |
print |
打印表达式 | print x |
display |
自动显示变量 | display x |
undisplay |
取消自动显示 | undisplay 1 |
set var |
修改变量 | set var x=10 |
watch |
设置观察点 | watch x |
backtrace |
查看调用栈 | bt |
frame |
选择栈帧 | frame 2 |
info threads |
查看线程 | info threads |
thread |
切换线程 | thread 2 |
quit |
退出GDB | quit |
掌握这些命令,就能应对大多数调试场景。在实际使用中,建议从基本命令开始,逐步尝试更高级的功能。调试是一项实践性很强的技能,只有通过不断的实际操作,才能真正掌握GDB的强大功能。