第一章 GDB库链接调试:跨平台编译疑难解答
1.1 编译带调试信息的程序
调试这事儿,说白了就是让程序在运行时把「底牌」亮给你看。我刚开始用GDB时,最常犯的错误就是——编译时忘了加 -g 选项。结果呢?GDB里看到的全是汇编指令,变量名一个都认不出来,那叫一个抓瞎。
要生成调试信息,编译命令很简单:
gcc -g -o myprogram myprogram.c
这里的关键是 -g 选项。它告诉编译器,把源代码的行号、变量名、函数名这些信息,一股脑塞进可执行文件里。嗯,这里要注意:-g 不会影响程序的执行速度,只是让文件体积变大一些。我个人习惯,开发阶段永远带着 -g 编译。
-g 加到 CFLAGS 里。这样每次编译都自动带调试信息,省得忘了。
你可能会问:「调试信息会不会泄露源码?」其实不会。调试信息只是映射关系,不是源码本身。但发布生产版本时,我建议用 strip 命令去掉调试信息:
strip myprogram
这样文件体积能缩小一半以上。
1.2 使用GDB调试段错误
段错误(Segmentation Fault)是C程序员的「老朋友」了。说白了就是程序访问了不该访问的内存地址。我在项目中遇到过最典型的场景:一个嵌入式设备跑着跑着就崩了,连日志都没来得及打印。
用GDB定位段错误,流程其实很固定:
- 编译时加
-g选项 - 用GDB加载程序:
gdb ./myprogram - 在GDB里输入
run运行程序 - 程序崩溃后,输入
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。
为什么会这样?因为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。这是所有调试的基础,没得商量。
layout src 命令可以在GDB里直接显示源码,省得切窗口。
好了,这一章的内容就这些。记住:-g 是调试的入场券,bt 是段错误的照妖镜,print 是逻辑错误的显微镜。这三样东西用熟了,大部分调试问题都能搞定。