强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

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 2GCC 4.x 默认基础调试信息
DWARF 3GCC 5.x 默认支持拆分式 DWARF
DWARF 4GCC 7.x 默认性能改进
DWARF 5GCC 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(含宏)
DWARFLinux 标准调试格式,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 磁盘空间: 核心转储文件可能很大(与进程内存相当),注意磁盘空间。


扩展阅读


下一步

08 - 警告与静态分析:全面掌握 GCC 的警告系统和内置静态分析器。