GCC 完全指南 / 07 - 调试支持
07 - 调试支持
学习如何使用 GCC 生成调试信息,理解 DWARF 格式,与 GDB 集成进行高效调试。
7.1 -g 选项与调试信息级别
| 选项 | 级别 | 信息量 | 文件大小影响 | 说明 |
|---|---|---|---|---|
-g0 | 无 | 无 | 无 | 不生成调试信息 |
-g1 | 最小 | 行号、函数名、外部符号 | 小 | 最小调试信息,用于 backtrace |
-g2 | 默认 | 局部变量、全局变量、行号 | 中 | 推荐,等价于 -g |
-g3 | 最大 | 宏定义、枚举值 | 大 | 额外包含宏信息,可用 GDB 展开宏 |
# 默认调试信息(-g 等价于 -g2)
gcc -g -o hello_debug main.c
# 最小调试信息(适合生产环境的栈回溯)
gcc -g1 -o hello_release main.c
# 最大调试信息(包含宏定义)
gcc -g3 -o hello_macro main.c
# 调试信息不会影响程序执行速度,但增加文件大小
ls -l hello_debug hello_release
分离调试信息
# 编译时生成调试信息
gcc -g -o hello main.c
# 将调试信息分离到独立文件
objcopy --only-keep-debug hello hello.debug
# 去除可执行文件中的调试信息
strip --strip-debug hello
# 运行时加载调试信息
gdb -ex "set debug-file-directory ." hello
# 或者使用 build-id
objcopy --only-keep-debug hello hello.debug
strip --strip-debug hello
objcopy --add-gnu-debuglink=hello.debug hello
7.2 DWARF 调试格式
DWARF 是 Linux/Unix 系统上最常用的调试信息格式。
DWARF 版本
| 版本 | GCC 默认 | 说明 |
|---|---|---|
| DWARF 2 | GCC 4.x 默认 | 基础调试信息 |
| DWARF 3 | GCC 5.x 默认 | 支持拆分式 DWARF |
| DWARF 4 | GCC 7.x 默认 | 性能改进 |
| DWARF 5 | GCC 11+ 默认 | 压缩、加速、拆分式改进 |
# 指定 DWARF 版本
gcc -gdwarf-2 -o hello main.c
gcc -gdwarf-3 -o hello main.c
gcc -gdwarf-4 -o hello main.c
gcc -gdwarf-5 -o hello main.c
# 查看 DWARF 版本
readelf --debug-dump=info hello | grep "DWARF"
# 或
file hello
DWARF 内容结构
ELF 文件中的 DWARF 段:
.debug_abbrev - 缩写表
.debug_aranges - 地址范围表
.debug_frame - 调用帧信息(CFI)
.debug_info - 核心调试信息(DIE 树)
.debug_line - 行号信息
.debug_loc - 变量位置表达式
.debug_macinfo - 宏信息(-g3)
.debug_pubnames - 公共名称索引
.debug_pubtypes - 公共类型索引
.debug_ranges - 地址范围
.debug_str - 字符串表
查看 DWARF 信息
# 查看行号信息
readelf --debug-dump=line hello
# 查看变量信息
readelf --debug-dump=info hello | head -60
# 查看函数信息
readelf --debug-dump=decodedline hello
# 使用 dwarfdump(需要安装)
dwarfdump hello
7.3 GDB 集成
基本 GDB 调试流程
# 编译带调试信息的程序
gcc -g -O0 -o hello main.c
# 启动 GDB
gdb hello
# 常用 GDB 命令
(gdb) break main # 在 main 函数设断点
(gdb) run # 运行程序
(gdb) next (n) # 单步执行(不进入函数)
(gdb) step (s) # 单步执行(进入函数)
(gdb) continue (c) # 继续运行
(gdb) print var # 打印变量值
(gdb) backtrace (bt) # 查看调用栈
(gdb) list (l) # 查看源代码
(gdb) info locals # 查看所有局部变量
(gdb) watch var # 监视变量变化
(gdb) finish # 运行到当前函数返回
(gdb) quit (q) # 退出 GDB
GDB 调试命令速查表
| 类别 | 命令 | 说明 |
|---|---|---|
| 运行 | run [args] | 运行程序 |
| 运行 | continue | 继续执行 |
| 运行 | next | 单步(不进入函数) |
| 运行 | step | 单步(进入函数) |
| 运行 | finish | 执行到函数返回 |
| 断点 | break func | 函数断点 |
| 断点 | break file:line | 行断点 |
| 断点 | break *0xaddr | 地址断点 |
| 断点 | delete N | 删除断点 N |
| 断点 | disable/enable N | 禁用/启用断点 |
| 数据 | print expr | 打印表达式 |
| 数据 | print /x var | 以十六进制打印 |
| 数据 | print /t var | 以二进制打印 |
| 数据 | display var | 每步自动打印 |
| 数据 | watch var | 监视变化 |
| 信息 | backtrace | 调用栈 |
| 信息 | info locals | 局部变量 |
| 信息 | info args | 函数参数 |
| 信息 | info registers | 寄存器 |
| 信息 | info breakpoints | 断点列表 |
| 内存 | x/Nfw addr | 检查内存 |
| 线程 | info threads | 线程列表 |
| 线程 | thread N | 切换到线程 N |
条件断点
# 条件断点
(gdb) break main.c:10 if i == 100
# 忽略断点前 N 次
(gdb) ignore 1 50 # 忽略断点 1 的前 50 次命中
# 断点命令列表
(gdb) break compute
(gdb) commands
> print x
> continue
> end
调试优化代码
# 最佳组合:-Og -g
gcc -g -Og -o hello main.c
# 如果必须用 -O2 调试
gcc -g -O2 -fno-omit-frame-pointer -o hello main.c
# 在 GDB 中查看优化后的变量
(gdb) info locals
# 某些变量可能被优化掉,显示 <optimized out>
# 可以尝试查看寄存器
(gdb) info registers
7.4 AddressSanitizer 与调试
# 使用 ASan 进行运行时错误检测
gcc -g -fsanitize=address -fno-omit-frame-pointer -o hello_asan main.c
# 运行
./hello_asan
# ASan 会报告内存错误的精确位置
7.5 核心转储(Core Dump)调试
# 启用核心转储
ulimit -c unlimited
# 运行程序(崩溃时生成 core 文件)
./hello
# Segmentation fault (core dumped)
# 调试核心转储
gdb hello core
# 在 GDB 中
(gdb) bt # 查看崩溃时的调用栈
(gdb) info locals # 查看崩溃时的局部变量
(gdb) list # 查看崩溃位置的源代码
配置核心转储
# 使用 systemd 管理的核心转储
# 核心转储存储在 /var/lib/systemd/coredump/
coredumpctl list
coredumpctl info <PID>
coredumpctl gdb <PID>
# 设置核心转储文件名模式
echo "/tmp/core.%e.%p.%t" | sudo tee /proc/sys/kernel/core_pattern
7.6 Valgrind 调试
# 安装 Valgrind
sudo apt install valgrind
# 使用 Valgrind 检测内存错误
valgrind --leak-check=full --show-leak-kinds=all ./hello
# 使用 Valgrind 的 GDB server
valgrind --vgdb=yes --vgdb-error=0 ./hello
# 在另一个终端
gdb ./hello
(gdb) target remote | vgdb
7.7 调试信息与代码优化的关系
| 组合 | 推荐度 | 说明 |
|---|---|---|
-g -O0 | ⭐⭐⭐⭐ | 完全不优化,调试最准确 |
-g -Og | ⭐⭐⭐⭐⭐ | 推荐日常开发使用 |
-g -O2 | ⭐⭐⭐ | 生产调试,部分变量被优化 |
-g3 -O0 | ⭐⭐⭐ | 需要调试宏定义时 |
-g -O3 | ⭐⭐ | 激进优化,调试信息可能不准 |
调试优化代码的技巧
# 保留帧指针(优化时默认省略)
gcc -g -O2 -fno-omit-frame-pointer -o hello main.c
# 使用 DWARF 5(更好的优化后调试支持)
gcc -g -gdwarf-5 -O2 -o hello main.c
# 在 GDB 中查看被优化掉的变量
(gdb) info args
(gdb) info frame
(gdb) disassemble /m main # 反汇编并显示源码
7.8 实用调试示例
示例:调试段错误
// buggy.c
#include <stdio.h>
#include <stdlib.h>
char *create_string(const char *src) {
char *dest = malloc(strlen(src)); // BUG: 少分配了 1 字节
strcpy(dest, src); // 堆缓冲区溢出
return dest;
}
int main(void) {
char *s = create_string("Hello, World!");
printf("%s\n", s);
free(s);
return 0;
}
# 编译并调试
gcc -g -fsanitize=address -o buggy buggy.c
./buggy
# ASan 输出:
# ==12345==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x...
# WRITE of size 14 at 0x... thread T0
# #0 0x... in strcpy
# #1 0x... in create_string buggy.c:6
# #2 0x... in main buggy.c:11
示例:GDB 调试会话
$ gdb ./hello
(gdb) break main
Breakpoint 1 at 0x1149: file main.c, line 5.
(gdb) run
Starting program: /home/user/hello
Breakpoint 1, main () at main.c:5
5 int x = 42;
(gdb) next
6 int y = x * 2;
(gdb) print x
$1 = 42
(gdb) step
7 printf("x=%d, y=%d\n", x, y);
(gdb) print y
$2 = 84
(gdb) continue
x=42, y=84
[Inferior 1 exited normally]
(gdb) quit
要点回顾
| 要点 | 核心内容 |
|---|---|
| -g 级别 | -g0(无) / -g1(最小) / -g2(默认) / -g3(含宏) |
| DWARF | Linux 标准调试格式,GCC 11+ 默认 DWARF 5 |
| GDB | 核心调试工具:断点、单步、查看变量、调用栈 |
| 核心转储 | ulimit -c unlimited + gdb ./prog core |
| ASan | 运行时内存错误检测,比 Valgrind 快 |
| 分离调试信息 | 生产环境使用 objcopy --only-keep-debug 分离 |
注意事项
-g 不影响性能:
-g选项只增加调试信息,不改变代码逻辑。生产构建也可以加-g1,便于崩溃时获取栈回溯。
strip 会移除调试信息: 部署到生产环境时,先用
objcopy --only-keep-debug分离调试信息,再strip。
-O2 调试困难: 优化后的代码执行顺序与源码不同,部分变量被优化掉。调试优化代码时使用
-fno-omit-frame-pointer。
Core dump 磁盘空间: 核心转储文件可能很大(与进程内存相当),注意磁盘空间。
扩展阅读
- GDB 手册 — 官方 GDB 文档
- DWARF 标准 — DWARF 调试格式规范
- GDB Dashboard — GDB 可视化界面
- rr — 录制回放调试工具
- DWARF wikipedia — DWARF 格式概述
下一步
→ 08 - 警告与静态分析:全面掌握 GCC 的警告系统和内置静态分析器。