第一章 GDB库链接调试:跨平台编译疑难解答

1.1 编译带调试信息的程序

调试这事儿,说白了就是让程序在运行时把「底牌」亮给你看。我刚开始用GDB时,最常犯的错误就是——编译时忘了加 -g 选项。结果呢?GDB里看到的全是汇编指令,变量名一个都认不出来,那叫一个抓瞎。

要生成调试信息,编译命令很简单:

gcc -g -o myprogram myprogram.c

这里的关键是 -g 选项。它告诉编译器,把源代码的行号、变量名、函数名这些信息,一股脑塞进可执行文件里。嗯,这里要注意:-g 不会影响程序的执行速度,只是让文件体积变大一些。我个人习惯,开发阶段永远带着 -g 编译。

小技巧: 如果你用 Makefile 管理项目,建议把 -g 加到 CFLAGS 里。这样每次编译都自动带调试信息,省得忘了。

你可能会问:「调试信息会不会泄露源码?」其实不会。调试信息只是映射关系,不是源码本身。但发布生产版本时,我建议用 strip 命令去掉调试信息:

strip myprogram

这样文件体积能缩小一半以上。

1.2 使用GDB调试段错误

段错误(Segmentation Fault)是C程序员的「老朋友」了。说白了就是程序访问了不该访问的内存地址。我在项目中遇到过最典型的场景:一个嵌入式设备跑着跑着就崩了,连日志都没来得及打印。

用GDB定位段错误,流程其实很固定:

  1. 编译时加 -g 选项
  2. 用GDB加载程序:gdb ./myprogram
  3. 在GDB里输入 run 运行程序
  4. 程序崩溃后,输入 bt 查看调用栈

举个例子,假设你有这么一段代码:

#include <stdio.h>

void crash() {
    int *p = NULL;
    *p = 42;  // 这里会段错误
}

int main() {
    crash();
    return 0;
}

编译运行后,GDB会停在 *p = 42 这一行。输入 bt,你会看到:

#0  0x0000555555555149 in crash () at crash.c:5
#1  0x000055555555515a in main () at crash.c:9

你看,GDB直接告诉你:第5行出事了,是 crash 函数里干的。调用栈也清清楚楚——main 调了 crash。

核心思路: 段错误定位,就是「先看bt,再看具体行号,最后检查指针或数组越界」。我这些年遇到的段错误,90%都是这三种原因。

为什么会这样?因为C语言不检查内存边界。你写了个野指针,它不会当场报错,而是等到访问非法地址时才崩溃。GDB就是帮你抓住这个「犯罪现场」的。

1.3 使用GDB调试逻辑错误

逻辑错误比段错误更隐蔽。程序不崩溃,但结果不对。我记得有一次,一个排序算法跑出来的结果总是少几个元素,我盯着代码看了两小时没看出问题。后来用GDB一步步跟,才发现是循环边界写错了。

调试逻辑错误,我常用的命令就这几个:

命令 作用
break 或 b 设置断点,比如 b main 或 b 10
next 或 n 单步执行,不进入函数内部
step 或 s 单步执行,进入函数内部
print 或 p 打印变量值,比如 p i
display 每次停下时自动打印某个变量
continue 或 c 继续运行到下一个断点

来看一个实际例子。假设你写了个求阶乘的函数:

#include <stdio.h>

int factorial(int n) {
    int result = 1;
    for (int i = 1; i < n; i++) {  // 这里应该是 i <= n
        result *= i;
    }
    return result;
}

int main() {
    printf("%d\n", factorial(5));
    return 0;
}

运行结果是24,但正确答案应该是120。你想想看,问题出在哪?

用GDB调试:

gdb ./factorial
(gdb) break factorial
(gdb) run
(gdb) display i
(gdb) display result
(gdb) next

每次执行 next,GDB都会自动打印 i 和 result 的值。你会发现 i 只跑到4就停了,根本没乘5。嗯,这里就暴露了——循环条件应该是 i <= n,而不是 i < n。

避坑指南: 我曾经在调试一个多线程程序时,用了 next 但发现变量值莫名其妙变了。后来才意识到,是另一个线程在改它。多线程调试建议用 thread apply all bt 先看所有线程的调用栈。

逻辑调试的核心,说白了就是「观察变量变化」。你盯着关键变量,看它每一步的值是否符合预期。一旦发现某个变量值不对,就往前追溯,找到第一次出错的地方。

知识体系总览

下面这张图,是我自己总结的GDB调试核心逻辑。你一看就明白:

GDB调试核心流程 编译加 -g 选项 gdb 加载程序 run 运行 崩溃? 段错误处理 bt 查看调用栈 检查指针/数组越界 逻辑错误处理 break 设置断点 print/display 观察变量

这张图把GDB调试分成了两条路:程序崩溃走左边,结果不对走右边。但不管哪条路,第一步都是编译加 -g。这是所有调试的基础,没得商量。

我的习惯: 调试时我一般开两个终端。一个跑GDB,一个看源码。这样对照着看,效率高很多。另外,layout src 命令可以在GDB里直接显示源码,省得切窗口。

好了,这一章的内容就这些。记住:-g 是调试的入场券,bt 是段错误的照妖镜,print 是逻辑错误的显微镜。这三样东西用熟了,大部分调试问题都能搞定。

可可精品资料库