GDB 调试器使用指南
- C语言进阶
- 18天前
- 157热度
- 0评论
GDB 调试器使用指南
GDB(GNU Debugger)是 Linux 下最常用的 C/C++ 程序调试工具。GDB 主要能做以下四件事来帮助你定位 Bug:
- 启动程序 —— 可以指定任何可能影响程序行为的参数与环境。
- 条件中断 —— 让程序在满足指定条件时停下来。
- 检查现场 —— 程序停下后,检查当时究竟发生了什么。
- 动态修改 —— 在运行中修改程序状态,从而验证对某个 Bug 的修复思路,再继续排查其他问题。
1. 编译与启动
使用 GDB 调试前,编译时必须加 -g 选项以包含调试信息:
gcc -g -o a.out hello.c
启动 GDB:
gdb ./a.out
# -q参数 不打印介绍等信息
gdb ./a.out -q

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 命令详解
语法: print/[格式] 表达式(缩写 p),不加格式时按变量类型默认显示。
格式修饰符(官方完整列表)
| 格式 | 含义 | 示例 |
|---|---|---|
x |
十六进制(hexadecimal) | p/x a → 0x20 |
d |
有符号十进制 | p/d a → 32 |
u |
无符号十进制 | p/u a → 32 |
o |
八进制 | p/o a → 040 |
t |
二进制(two) | p/t a → 100000 |
a |
地址(十六进制 + 最近符号偏移) | p/a &a |
c |
字符 | p/c 65 → 65 '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]
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防止歧义 |
运行截图及解释

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)即可查看崩溃时的调用栈,快速定位问题所在。

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 # 继续运行到结束