gdb是GNU开源组织发布的程序调试器,几乎所有的Linux发行版都自带或可以通过软件源安装。openKylin作为国内自主打造的桌面操作系统,基于Linux内核,完全兼容gdb的日常工作流程。写C或C++程序时,光靠printf打印日志排查问题效率很低,尤其是遇到段错误(Segmentation fault)这种直接崩溃的情况,连出错位置都摸不到头绪。gdb能让我们在程序运行过程中随时暂停、查看变量、回溯调用栈,是定位复杂问题的利器。本文以openKylin为例,完整走一遍从环境准备到实战调试的流程。

环境准备:安装gdb与编译调试版程序
openKylin默认的软件源里包含gdb,打开终端执行下面的命令即可安装:
sudo apt update sudo apt install gdb -y gdb --version
最后一条命令用来确认安装成功,正常会输出gdb的版本号。如果提示找不到软件包,先检查网络连接和软件源配置,openKylin的自带源在国内访问速度一般都不错。
第二个关键步骤是编译。很多初学者编译时没加调试选项,结果gdb里看不到源码、变量名全是问号。原因在于默认的release编译会把调试信息剥离,必须加上-g参数让gcc或g++把调试信息嵌入可执行文件:
gcc -g -o demo demo.c # C程序 g++ -g -O0 -o demo demo.cpp # C++程序,建议关闭优化
这里再强调一下-O0的作用。gcc默认优化级别是-O0,但有些项目会在Makefile里写-O2,优化后的代码执行顺序可能被编译器重排,断点位置和源码行号对不上,单步调试时会出现“跳来跳去”的现象。调试阶段建议始终用-O0编译,性能优化和调试体验很难兼得。
gdb基本操作:断点、单步与变量查看
准备一段有bug的示例代码,用一个经典的数组越界问题作为调试对象:
#include <stdio.h>
int sum(int arr[], int n)
{
int total = 0;
for (int i = 0; i <= n; i++) { /* bug:多访问了一个元素 */
total += arr[i];
}
return total;
}
int main(void)
{
int data[5] = {1, 2, 3, 4, 5};
int result = sum(data, 5);
printf("result = %d\n", result);
return 0;
}
循环条件写成i <= n,访问了arr[5],越界读取了一个垃圾值。启动调试:
gdb ./demo (gdb) break sum # 在sum函数入口打断点,可简写为 b sum (gdb) run # 运行程序,简写 r (gdb) next # 单步执行,不进入函数,简写 n (gdb) step # 单步执行,进入函数内部,简写 s (gdb) print total # 打印变量值,简写 p (gdb) print arr[3] # 查看数组某个元素 (gdb) info locals # 查看当前函数所有局部变量 (gdb) bt # 查看调用栈(backtrace) (gdb) continue # 继续运行到下一个断点,简写 c (gdb) quit # 退出gdb,简写 q
在循环里配合next单步走,每走一步print一次total,很容易发现total的值在某次累加后突然变得异常,此时查看i的值正好是5,越界问题一目了然。断点还支持按行号设置,比如break 8表示在第8行暂停;条件断点break 9 if i == 4表示i等于4时才中断,处理大循环时特别有用,省去了手动一次次continue的麻烦。
查看内存也是常用技能。x/5dw arr表示从arr地址开始,以十进制显示5个int值;p &total可以取变量地址。修改运行中的变量值用set var total=0,不需要重新编译就能测试不同的分支逻辑,这是gdb相比加日志打印最大的优势。
段错误与core文件分析
程序直接崩溃报Segmentation fault时,最快的定位方式是让程序在gdb里跑,崩溃后gdb会自动停在出错的那一行:
gdb ./demo (gdb) run Program received signal SIGSEGV, Segmentation fault. 0x0000555555555169 in main () at demo.c:15 15 int *p = NULL; *p = 10;
但如果程序是在gdb外部崩溃的,就需要借助core文件。openKylin默认core文件大小限制为0,需要手动放开:
ulimit -c unlimited # 临时放开,只对当前终端生效 sudo sh -c 'echo "kernel.core_pattern=/home/user/corefile/core.%e.%p" >> /etc/sysctl.conf' sudo sysctl -p
程序崩溃后会在指定目录生成core文件,之后执行gdb ./demo core.xxx加载,再输入bt查看崩溃现场的完整调用栈,就能知道崩在哪个函数、被谁调用。注意分析core文件必须使用与崩溃时完全相同的可执行文件和库版本,否则符号信息对不上,栈帧会显示一堆问号。
多线程调试与实用技巧
C++项目里多线程越来越常见,gdb对线程调试有完整支持。程序中断后,info threads列出所有线程,thread 3切换到3号线程,thread apply all bt一次性打印所有线程的调用栈,排查死锁时这个命令几乎必用——对比几个线程的栈,如果都停在pthread_lock相关的调用上,基本就能确认互相持锁等待了。
几个能明显提升效率的习惯:一是把常用命令写进用户目录的.gdbinit文件,比如set print pretty on让结构体输出带缩进更好读;二是安装gdb插件或者搭配vim使用clewn、ConqueGDB等工具,实现源码窗口跟随调试位置;三是调试大程序时用set logging on把gdb输出记录到文件,方便事后分析长会话内容。掌握这些操作后,在openKylin上调试C/C++程序就和任何主流Linux发行版一样顺手,遇到再诡异的bug也有章法可循。