GDB 调试器使用指南

GDB 调试器使用指南

GDB(GNU Debugger)是 Linux 下最常用的 C/C++ 程序调试工具。GDB 主要能做以下四件事来帮助你定位 Bug:

  1. 启动程序 —— 可以指定任何可能影响程序行为的参数与环境。
  2. 条件中断 —— 让程序在满足指定条件时停下来。
  3. 检查现场 —— 程序停下后,检查当时究竟发生了什么。
  4. 动态修改 —— 在运行中修改程序状态,从而验证对某个 Bug 的修复思路,再继续排查其他问题。

1. 编译与启动

使用 GDB 调试前,编译时必须加 -g 选项以包含调试信息:

gcc -g -o a.out hello.c

启动 GDB:

gdb ./a.out
# -q参数 不打印介绍等信息
gdb ./a.out -q

image-20260310195749198


2. 常用命令速查表

2.1 查看代码

命令 缩写 说明
list l 显示当前位置附近的源代码(默认 10 行)
list 20 l 20 显示第 20 行附近的源代码
list main l main 显示 main 函数的源代码

2.2 断点管理(Breakpoint)

命令 说明
break main / b main main 函数入口设置断点
break 15 / b 15 在第 15 行设置断点
break if i==5 条件断点,当 i==5 时触发
info break / info br 查看所有断点信息
delete 1 / d 1 删除编号为 1 的断点
disable 1 禁用编号为 1 的断点
enable 1 启用编号为 1 的断点

2.3 运行与调试

命令 缩写 说明
run r 运行程序(遇到断点自动停下)
next n 单步执行(不进入函数内部)
step s 单步执行(进入函数内部)
continue c 继续运行直到下一个断点
finish 运行到当前函数返回
quit q 退出 GDB

2.4 查看变量与内存

命令 说明
print var / p var 打印变量 var 的值
print/x var 以十六进制打印变量
display var 每次暂停时自动显示变量值
info locals 查看当前函数的所有局部变量
backtrace / bt 查看函数调用栈

2.5 print 命令详解

参考:GDB 官方文档 - Examining Data / Output Formats

语法: print/[格式] 表达式(缩写 p),不加格式时按变量类型默认显示。

格式修饰符(官方完整列表)

格式 含义 示例
x 十六进制(hexadecimal) p/x a0x20
d 有符号十进制 p/d a32
u 无符号十进制 p/u a32
o 八进制 p/o a040
t 二进制(two) p/t a100000
a 地址(十六进制 + 最近符号偏移) p/a &a
c 字符 p/c 6565 'A'
f 浮点数 p/f a
s 字符串 p/s str

不同变量类型下 print 的输出对照

这是最容易混乱的部分。假设代码如下:

int a = 32;
char *str = "helloworld";    // str 是指针,存的是字符串的地址
char str2[] = "helloworld";  // str2 是数组,存的是字符串本身

print 变量 —— 打印变量的值:

(gdb) p a
$1 = 32                          # int → 直接显示数值

(gdb) p str
$2 = 0x555555556004 "helloworld" # 指针 → 显示地址 + 字符串内容

(gdb) p str2
$3 = "helloworld"                # 数组 → 直接显示字符串内容(不显示地址)

(gdb) p *str
$4 = 104 'h'                     # 解引用指针 → 第一个字符

print &变量 —— 打印变量自身的地址:

(gdb) p &a
$5 = (int *) 0x7fffffffe3e4          # int 的地址 → int *

(gdb) p &str
$6 = (char **) 0x7fffffffe3e8        # 指针的地址 → char **(二级指针!)

(gdb) p &str2
$7 = (char (*)[11]) 0x7fffffffe3f0   # 数组的地址 → char (*)[11](数组指针!)

[!important] 为什么 &str&str2 类型不同?

  • str 本身是一个指针变量(8 字节),存放在栈上某个位置 → &str 得到的是「指针的地址」,类型是 char **
  • str2 本身是一个数组(11 字节),就是数据本身 → &str2 得到的是「数组的地址」,类型是 char (*)[11]

虽然 str&str2都指向字符串 "helloworld",但它们的含义完全不同

  • str = 指向只读数据段中字符串的指针
  • &str2 = 栈上数组首地址

完整对照表

表达式 int a = 32 char *str = "hello" char str2[] = "hello"
p var 32 0x5...004 "hello" "hello"
p &var (int *) 0x...e4 (char **) 0x...e8 (char (*)[6]) 0x...f0
p *var ❌ 不能解引用 int 104 'h'(首字符) 104 'h'(首字符)
p/x var 0x20 0x5...004(只显示地址) ❌ 数组不适用 /x
p/s var "hello" "hello"

2.6 检查内存(examine)

x 命令(examine)直接按地址读取内存内容,比 print 更底层,特别适合查看字符串、字符数组的实际存储。

语法: x/[数量][格式][单位大小] 地址

格式修饰符:

格式 含义 单位大小 含义
s 字符串(遇 \0 停止) b 1 字节(byte)
c 单个字符 h 2 字节(halfword)
x 十六进制 w 4 字节(word)
d 十进制 g 8 字节(giant)
t 二进制

常用示例:

# 假设: char *str = "helloworld";  char str2[] = "helloworld";

x/s str          # 以字符串形式查看(遇 \0 自动停止)
x/10c str2       # 逐字符查看 str2 的前 10 个字符
x/11xb str2      # 以十六进制逐字节查看(能看到每个 ASCII 码和末尾的 \0)
x/4xw &a         # 以十六进制查看变量 a 所在地址的 4 字(16 字节)

[!tip] print vs x 的区别

  • print var → 高层视角,看变量的值
  • x/格式 地址 → 底层视角,看内存的原始内容

当你需要确认 \0 终止符位置、排查越界、观察内存布局时,用 x

2.7 其他实用功能

命令 说明
shell <command> 在 GDB 内执行 shell 命令,如 shell cat hello.c
watch var 设置观察点(Watchpoint),变量被修改时暂停
set var = value 在调试过程中修改变量的值,变量名单字母需要set var varname防止歧义

运行截图及解释
image-20260316154024925

3. Core Dump 分析

当程序发生段错误(Segmentation Fault)等严重错误时,操作系统可以生成一个 core dump 文件(核心转储文件),它记录了程序崩溃那一刻的内存映像、寄存器状态、调用栈等信息,是事后调试崩溃问题最直接、最有效的手段。

3.1 开启 core dump(必须先做这一步)

为什么要在调试前先开启 core dump?

  • 默认情况下,很多 Linux 发行版出于安全和磁盘空间考虑,会限制或完全禁用 core dump(ulimit -c 通常为 0)。
  • 如果没有开启,程序崩溃时只会看到 “Segmentation fault” 一行提示,什么额外信息都没有,基本无法定位问题。
  • 开启后才能在崩溃时自动生成 core 文件,之后用 gdb 分析调用栈、变量值、崩溃地址等。

在当前终端执行以下命令(建议放在 shell 启动脚本中长期生效):

# 1. 取消 core 文件大小限制(允许生成任意大小的 core 文件)
ulimit -c unlimited

# 2. (推荐)设置 core 文件的命名格式,便于区分多个崩溃文件,需要root权限执行
#    %e → 可执行文件名
#    %p → 进程 PID
#    %s → 导致崩溃的信号编号
#    %t → 崩溃时间戳(可选)
echo "core-%e-%p-%s" > /proc/sys/kernel/core_pattern

# 3. 验证是否生效:
ulimit -c          # 应该显示 unlimited
cat /proc/sys/kernel/core_pattern   # 应该显示你设置的格式

3.2 触发 core dump 的示例代码

#include <stdio.h>

int main(void)
{
    int *p = NULL;  // 指针初始化为 NULL
    *p = 0x10;      // 解引用空指针 → 段错误(Segmentation Fault)
    return 0;
}

3.3 使用 GDB 分析 core dump

gdb ./a.out core-a.out-12345-11

进入 GDB 后,使用 bt(backtrace)即可查看崩溃时的调用栈,快速定位问题所在。

image-20260310201650450


4. 调试正在运行的进程

如果程序已经在运行,可以通过 PID 附加调试:

# 后台运行程序
./a.out &
# 输出类似:[1] 135852

# 附加 GDB 到该进程
gdb -p 135852

附加后即可正常使用 GDB 的所有调试命令。


5. 综合练习

下面这段代码覆盖了本文所有 GDB 命令,编译后配合 GDB 逐步操练:

#include <stdio.h>
#include <string.h>

// 用于练习: step(进入函数)、finish(跳出函数)、bt(查看调用栈)
int sum(int n)
{
    int result = 0;
    for (int i = 1; i <= n; i++)
        result += i;
    return result;
}

int main(void)
{
    // 练习: print、print/x、display、watch、x/4xw
    int a = 32;

    // 练习: x/s(字符串查看)、x/xb(逐字节十六进制)
    char *str = "helloworld";
    char str2[] = "helloworld";

    printf("字符串指针 str  = %s  长度: %lu\n", str, strlen(str));
    printf("字符数组 str2[] = %s  长度: %lu\n", str2, strlen(str2));

    // 练习: break if i==5(条件断点)、next、continue
    for (int i = 0; i < 10; i++) {
        printf("i = %d\n", i);
    }

    // 练习: step 进入、finish 跳出、bt 查看调用栈
    int total = sum(10);
    printf("sum(10) = %d\n", total);

    return 0;
}

编译 & 启动调试:

gcc -g -o gdb_test gdb_test.c
gdb ./gdb_test -q

建议的练习步骤:

(gdb) l                    # 查看源代码
(gdb) b main               # 在 main 设断点
(gdb) r                    # 运行,停在 main
(gdb) n                    # 单步执行到 int a = 32
(gdb) p a                  # 打印 a → 32
(gdb) p/x a                # 十六进制打印 → 0x20
(gdb) x/s str              # 查看字符串指针 → "helloworld"
(gdb) x/s str2             # 查看字符数组   → "helloworld"
(gdb) x/11c str2           # 逐字符查看(含末尾 \0)
(gdb) x/11xb str2          # 十六进制逐字节查看
(gdb) b 30 if i==5         # 条件断点:循环中 i==5 时停下
(gdb) c                    # 继续运行,停在 i==5
(gdb) p i                  # 确认 i 的值
(gdb) c                    # 继续运行完循环
(gdb) s                    # step 进入 sum 函数
(gdb) bt                   # 查看调用栈
(gdb) finish               # 执行到 sum 返回
(gdb) p total              # 查看返回值 → 55
(gdb) c                    # 继续运行到结束

6. 参考资料