1 GDB / Core Dump 调试指南
1.1 前言
1.1.1 为什么要掌握 GDB
printf 调试法在代码简单时够用,但面对以下场景就力不从心:
- 程序崩溃(Segfault),复现概率低
- 多线程死锁,无法静态分析
- 生产环境 core dump,不能重跑
- 内存越界,崩溃位置和真正的错误位置不一致
- 第三方库出问题,没有源码
GDB 能做到:在程序崩溃的那一刻,完整保留所有线程的调用栈、变量值、内存内容,让你事后像"案发现场重建"一样还原问题。
1.1.2 编译选项说明
调试前必须用正确的编译选项,否则 GDB 能看到的信息极为有限:
# 推荐:保留调试信息 + 适度优化
gcc -O0 -g3 your_program.c -o your_program
# 参数含义
# -g3 :最完整的调试信息(包含宏定义)
# -g :标准调试信息(-g2,够用)
# -O0 :关闭优化(变量不会被优化掉,调用栈完整)
# -O2 -g:保留调试信息但开启优化(生产环境常见,部分变量会被优化)
# 线程调试需要链接 pthread
gcc -O0 -g -pthread your_program.c -o your_program
-O2 -g 的问题:编译器优化后,某些局部变量会被寄存器优化掉,GDB 里会显示 <optimized out>。开发调试阶段用 -O0,生产环境分析 core dump 时只能接受这个限制。
1.2 GDB 基础操作
1.2.1 启动方式
# 方式1:直接调试程序
gdb ./your_program
# 方式2:调试程序并传参
gdb --args ./your_program arg1 arg2
# 方式3:调试已在运行的进程(attach)
gdb -p 12345
gdb ./your_program 12345 # 同时指定二进制(便于加载符号)
# 方式4:分析 core dump(事后调试,最常用)
gdb ./your_program core
gdb ./your_program core.12345
# 方式5:批处理模式(非交互,适合脚本)
gdb -batch -ex "bt" -ex "quit" ./your_program core
1.2.2 退出 GDB
quit # 或简写 q
Ctrl + D # 同 quit
1.2.3 获取帮助
help # 列出所有命令类别
help breakpoints # 查看断点相关命令
help run # 查看 run 命令详情
apropos step # 搜索和 step 相关的命令
1.3 运行与控制
1.3.1 运行程序
run # 启动程序(简写 r)
run arg1 arg2 # 带命令行参数
run < input.txt # 重定向输入
run > output.txt 2>&1 # 重定向输出
# 设置参数后运行(等价于 run arg1 arg2)
set args arg1 arg2
run
# 查看当前参数设置
show args
1.3.2 继续执行
continue # 继续运行直到下一个断点(简写 c)
finish # 运行到当前函数返回(简写 fin)
until # 运行到当前循环结束(跳出循环体)
until 42 # 运行到第 42 行
1.3.3 单步执行
next # 执行下一行,不进入函数(简写 n)
step # 执行下一行,进入函数(简写 s)
nexti # 执行下一条汇编指令,不进入(简写 ni)
stepi # 执行下一条汇编指令,进入(简写 si)
next vs step 的区别:
int result = calculate(x, y); // 如果光标在这行
// next:直接到下一行,不进入 calculate
// step:进入 calculate 函数内部
1.4 断点管理
1.4.1 设置断点
# 按行号
break 42 # 在第 42 行打断点(简写 b)
break main.c:42 # 指定文件
break main.c:42 # 指定文件+行号
# 按函数名
break main # 在 main 函数入口
break process_data # 在 process_data 函数入口
break ClassName::method # C++ 成员函数
# 按地址
break *0x401234 # 在指定内存地址打断点
# 条件断点:只有满足条件时才停下
break 42 if x > 100 # x 大于 100 时停
break process if ptr == nullptr # ptr 为空时停
break loop.c:20 if i == 999 # 循环第 1000 次时停
# 临时断点:触发一次后自动删除
tbreak 42
tbreak process_data
1.4.2 查看和管理断点
info breakpoints # 查看所有断点(简写 i b)
info b # 同上
# 输出示例:
# Num Type Disp Enb Address What
# 1 breakpoint keep y 0x00401234 in main at main.c:10
# 2 breakpoint keep n 0x00401256 in foo at foo.c:42 ← 禁用的
disable 1 # 禁用断点1(不删除)
enable 1 # 启用断点1
delete 1 # 删除断点1
delete # 删除所有断点(会确认)
clear 42 # 清除第 42 行的断点
clear process_data # 清除函数断点
1.4.3 断点命令:触发断点时自动执行命令
break 42
commands 1 # 为断点1设置触发命令
print x # 触发时自动打印 x
print y
continue # 打印后自动继续(实现"无停顿日志")
end
这个技巧非常实用:在不修改代码的情况下,让程序在某个点自动打印变量值然后继续跑,相当于动态插入 printf。
1.4.4 观察点(Watchpoint)—— 监控变量变化
watch x # x 被写入时停下
watch *0x601234 # 监控内存地址被写入
rwatch x # x 被读取时停下
awatch x # x 被读或写时停下
# 实用场景:找出是谁修改了某个全局变量
watch global_var
run
# 程序会在 global_var 被修改的那一刻停下,并显示新旧值:
# Old value = 0
# New value = 42
# 0x00401289 in some_function () at some.c:78
1.4.5 捕获点(Catchpoint)
catch throw # 捕获 C++ 异常抛出
catch catch # 捕获 C++ 异常捕获
catch syscall read # 捕获 read 系统调用
catch fork # 捕获 fork 调用
catch exec # 捕获 exec 调用
1.5 查看信息
1.5.1 查看调用栈
backtrace # 打印调用栈(简写 bt)
backtrace full # 打印调用栈 + 每帧的局部变量
backtrace 5 # 只打印最近5帧
# 典型输出:
# #0 process_data (arr=0x0, n=100) at main.c:42 ← 当前帧
# #1 do_work (x=10) at main.c:78
# #2 main () at main.c:12
帧号(#0, #1, …):#0 是当前执行位置,数字越大越靠近 main。
1.5.2 切换栈帧
frame 2 # 切换到帧 #2(简写 f)
up # 向上移动一帧(往 main 方向)
down # 向下移动一帧(往崩溃位置方向)
# 切换帧后,print/info locals 等命令都在该帧的上下文中执行
1.5.3 查看变量和表达式
print x # 打印变量 x(简写 p)
print x + y # 打印表达式
print arr[5] # 打印数组元素
print ptr->member # 打印指针成员
print *ptr # 解引用指针
print (int)3.14 # 类型转换
print sizeof(MyStruct) # 打印大小
# 格式控制
print/x x # 十六进制
print/d x # 十进制(默认)
print/o x # 八进制
print/t x # 二进制
print/c x # 字符
print/f x # 浮点
print/s ptr # 当做字符串打印
# 打印数组
print arr # 打印数组(如果 GDB 知道大小)
print *arr@10 # 打印 arr 开始的 10 个元素(不知道大小时用这个)
print arr[0]@10 # 同上,更清晰的写法
# 自动显示(每次停下来时自动打印)
display x # 每次停下来都自动打印 x
display/x ptr # 十六进制自动显示
info display # 查看所有自动显示
undisplay 1 # 取消自动显示1号
1.5.4 查看局部变量和函数参数
info locals # 当前帧所有局部变量
info args # 当前帧所有函数参数
info variables # 全局/静态变量(可能很多)
info registers # 所有寄存器值
info registers rsp rip rax # 指定寄存器
1.5.5 查看内存
# x 命令:examine memory(检查内存)
# 格式:x/[数量][格式][大小] 地址
x/10d 0x601234 # 从地址 0x601234 打印 10 个十进制整数
x/10x 0x601234 # 十六进制
x/10c 0x601234 # 字符
x/s 0x601234 # 字符串(自动找 \0 结尾)
x/10i 0x401234 # 反汇编 10 条指令
# 大小修饰符:
# b = byte(1字节)
# h = halfword(2字节)
# w = word(4字节)
# g = giant(8字节)
x/4xw 0x601234 # 打印 4 个 4字节十六进制值(常用于查看结构体内存布局)
x/8xg $rsp # 查看栈顶 8 个 8字节值
1.5.6 查看源码
list # 显示当前位置附近的源码(简写 l)
list 42 # 显示第 42 行附近
list func # 显示 func 函数
list main.c:42 # 指定文件
list 1,50 # 显示第 1-50 行
set listsize 20 # 设置每次显示行数(默认10)
1.5.7 查看类型信息
ptype x # 打印变量 x 的类型
ptype MyStruct # 打印结构体定义
whatis x # 简短类型信息
info types MyStruct # 查找包含该名称的类型
1.6 多线程调试
1.6.1 查看线程信息
info threads # 列出所有线程
# 输出示例:
# Id Target Id Frame
# * 1 Thread 0x... "prog" main () at main.c:10 ← * 表示当前线程
# 2 Thread 0x... "prog" 0x7f... in futex_wait
# 3 Thread 0x... "prog" process_data () at main.c:42
1.6.2 切换线程
thread 2 # 切换到线程2
thread apply 2 bt # 对线程2执行 bt(查看调用栈)
# 对所有线程执行命令
thread apply all bt # 所有线程的调用栈
thread apply all bt full # 所有线程的完整调用栈(含局部变量)
thread apply all info locals
1.6.3 多线程断点控制
# 默认行为:任何线程命中断点,所有线程停下
# 修改为:只停当前线程,其他继续运行
set scheduler-locking on
# 恢复默认
set scheduler-locking off
# 步进时锁定(常用:单步时只让当前线程走)
set scheduler-locking step
1.6.4 死锁诊断
死锁时程序卡住,attach 后:
gdb -p PID
# attach 后立刻查看所有线程在干什么
thread apply all bt
# 寻找 futex_wait / pthread_mutex_lock 的线程,就是在等锁的线程
# 看它们在等谁持有的锁:
# #0 futex_wait (...)
# #1 pthread_mutex_lock (mutex=0x601234) at ... ← 这个地址就是锁的地址
# 找另一个线程是否持有这把锁
# 打印锁的内部状态
p *(pthread_mutex_t *)0x601234
# 看 __data.__owner 字段,就是持有者的 TID
1.7 Core Dump 调试
Core dump 是进程崩溃时内核自动保存的内存快照,包含崩溃瞬间所有线程的状态、内存内容、寄存器值。
1.7.1 配置系统允许生成 core dump
# 查看当前 core 文件大小限制(0 表示不生成)
ulimit -c
# 临时解除限制(当前 shell 有效)
ulimit -c unlimited
# 永久生效:编辑 /etc/security/limits.conf
echo '* soft core unlimited' | sudo tee -a /etc/security/limits.conf
echo '* hard core unlimited' | sudo tee -a /etc/security/limits.conf
# 确认生效
ulimit -c
# 输出:unlimited
1.7.2 配置 core dump 文件名和路径
# 查看当前 core 文件命名模式
cat /proc/sys/kernel/core_pattern
# 常见默认值:core(当前目录)或 /tmp/core(取决于发行版)
# 设置带进程信息的文件名(推荐)
sudo sh -c 'echo "/tmp/core.%e.%p.%t" > /proc/sys/kernel/core_pattern'
# 占位符说明:
# %e 可执行文件名
# %p 进程 PID
# %t 时间戳(Unix time)
# %s 导致崩溃的信号编号
# %u 用户 ID
# %h 主机名
# 永久生效(写入 sysctl)
echo 'kernel.core_pattern = /tmp/core.%e.%p.%t' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
1.7.3 手动触发 core dump(不退出进程)
# 方式1:用 gcore 命令(进程继续运行)
gcore -o /tmp/mycore PID
# 生成 /tmp/mycore.PID
# 方式2:在 GDB 内部生成
gdb -p PID
(gdb) generate-core-file /tmp/mycore
# 方式3:发送 SIGABRT(进程会退出)
kill -SIGABRT PID
gcore 是最安全的方式——进程会短暂暂停,dump 完后继续运行,适合在线诊断。
1.7.4 分析 core dump
# 基本格式
gdb 程序路径 core文件路径
gdb ./your_program /tmp/core.your_program.12345.1714000000
# GDB 启动后会显示崩溃信号和位置:
# Program terminated with signal 11, Segmentation fault.
# #0 0x00401289 in process_data (arr=0x0, n=100) at main.c:42
进入 GDB 后,所有正常调试命令都可以用,只是不能运行程序(因为是快照):
bt # 查看崩溃时的调用栈
bt full # 完整调用栈 + 局部变量
info threads # 所有线程状态
thread apply all bt # 所有线程调用栈
frame 1 # 切换栈帧
print x # 查看崩溃时的变量值
x/10x $rsp # 查看崩溃时的栈内存
1.7.5 Core dump 分析实例
场景:程序崩溃,留下了 core 文件。
gdb ./your_program core.your_program.12345
# GDB 输出:
# Core was generated by `./your_program arg1'.
# Program terminated with signal SIGSEGV, Segmentation fault.
# #0 0x00401289 in process_data (arr=0x0, n=100) at main.c:42
# 42 sum += arr[i] * arr[i];
(gdb) bt
# #0 0x00401289 in process_data (arr=0x0, n=100) at main.c:42
# #1 0x004012a3 in do_work (data=0x603010) at main.c:78
# #2 0x004012b1 in main () at main.c:12
(gdb) print arr # 打印 arr,看到它是 nullptr
# $1 = (int *) 0x0 ← NULL 指针!
(gdb) frame 1 # 切换到调用者帧
(gdb) print data # 看 data 是什么
# $2 = (WorkData *) 0x603010
(gdb) print *data # 展开结构体
# $3 = {arr = 0x0, n = 100, name = "test"} ← data->arr 就是 NULL
(gdb) frame 2 # 再往上看
(gdb) info locals # main 中的局部变量
通过逐层切换栈帧,找出 data->arr 在 main 中没有被初始化就传给了 do_work。
1.8 常见崩溃类型分析
1.8.1 段错误(Segmentation Fault,信号 11)
最常见的崩溃,通常是非法内存访问。
# 崩溃后 bt 看调用栈
bt
# 重点检查:
# 1. 崩溃帧的函数参数是否有 NULL 指针
print 参数名
# 2. 检查数组访问是否越界
print i # 循环下标
print sizeof(arr)/sizeof(arr[0]) # 数组大小
# 3. 检查指针是否已被 free
# 看内存内容是否是 0xdddddddd 或 0xfeeefeee(常见的 freed 内存填充值)
x/4xw ptr
常见原因:
- 对 NULL 指针解引用
- 数组越界写(破坏了其他数据)
- 使用已 free 的内存(use-after-free)
- 栈溢出(递归过深)
1.8.2 双重释放(Double Free,信号 6 SIGABRT)
# 崩溃在 free() 内部,bt 类似:
# #0 __GI_raise (sig=6) at raise.c:51
# #1 __GI_abort () at abort.c:79
# #2 __libc_message (...) at abort.c:...
# #3 malloc_printerr (...) at malloc.c:... ← heap 检测到错误
# #4 _int_free (...) at malloc.c:...
# #5 free (ptr=0x603010) at malloc.c:...
# #6 your_function () at your.c:42 ← 这里是你的代码
frame 6 # 切到你的代码帧
print ptr # 看这个指针
# 查看之前是否已经 free 过
1.8.3 栈溢出(Stack Overflow)
通常是无限递归,调用栈极深:
bt
# #0 recursive_func (n=12456) at main.c:10
# #1 recursive_func (n=12455) at main.c:10
# #2 recursive_func (n=12454) at main.c:10
# ... 几千帧 ...
# 只看最上和最下几帧
bt 10 # 最近10帧
bt -10 # 最早10帧(往 main 方向)
1.8.4 C++ 异常未捕获(信号 6 SIGABRT)
# 设置在异常抛出时停下
catch throw
run
# 停在抛出位置后查看异常类型
bt
print $_exception # GDB 7.x+ 可以打印当前异常
1.9 检查内存布局
1.9.1 查看进程内存映射
info proc mappings # 查看进程内存段(text/data/heap/stack/库)
# 输出示例:
# Mapped address spaces:
# Start Addr End Addr Size Offset Perms objfile
# 0x400000 0x401000 0x1000 0x0 r--p /path/your_program
# 0x401000 0x402000 0x1000 0x1000 r-xp /path/your_program ← 代码段
# 0x602000 0x603000 0x1000 0x2000 rw-p /path/your_program ← 数据段
# 0x603000 0x624000 0x21000 0x0 rw-p [heap]
# 0x7fff... 0x8000... 0x1000 0x0 rw-p [stack]
1.9.2 查看结构体内存布局
# 打印结构体各字段的偏移量
ptype /o MyStruct
# 输出示例:
# type = struct MyStruct {
# /* 0 | 4 */ int id;
# /* 4 | 4 */ int count;
# /* XXX 4-byte hole */ ← 填充字节(对齐)
# /* 8 | 8 */ double value;
# /* 16 | 8 */ char *name;
# /* total size (bytes): 24 */
# }
1.9.3 查看堆内存
# 打印 malloc 分配的块信息(需要 glibc)
info malloc-stats # gdb 8.1+
# 或者查看 heap 的内存内容
info proc mappings # 找到 heap 的起始地址
x/64xw 0x603000 # 查看 heap 开始的内存
1.10 GDB 脚本与自动化
1.10.1 .gdbinit 启动配置
在项目目录或 home 目录创建 .gdbinit,GDB 启动时自动执行:
# ~/.gdbinit 或 项目目录/.gdbinit
# 历史命令记录
set history save on
set history filename ~/.gdb_history
set history size 10000
# 打印设置
set print pretty on # 结构体多行展示
set print array on # 数组分行展示
set print array-indexes on # 显示数组下标
set print null-stop on # 字符串遇 NULL 停止
# 允许加载当前目录的 .gdbinit
# 在 ~/.gdbinit 中加这一行(安全设置)
set auto-load safe-path /
1.10.2 批处理脚本(非交互分析 core dump)
# 创建脚本文件 analyze.gdb
cat > analyze.gdb << 'EOF'
set print pretty on
bt full
echo \n=== All Threads ===\n
thread apply all bt
echo \n=== Registers ===\n
info registers
quit
EOF
# 执行
gdb -batch -x analyze.gdb ./your_program core.12345
# 一行版本
gdb -batch \
-ex "set print pretty on" \
-ex "bt full" \
-ex "thread apply all bt" \
./your_program core.12345
1.10.3 常用 GDB 自定义命令
# 在 .gdbinit 中定义自己的命令
# 打印所有线程的调用栈,比 "thread apply all bt" 更友好
define allbt
thread apply all bt
end
# 打印 std::string 内容
define pstr
print (char*)($arg0._M_dataplus._M_p)
end
# 使用:pstr my_string
# 打印 vector 的元素
define pvec
set $i = 0
while $i < $arg0._M_impl._M_finish - $arg0._M_impl._M_start
print $arg0._M_impl._M_start[$i]
set $i = $i + 1
end
end
1.11 调试 STL 容器
STL 容器的内部表示比较复杂,直接 print 看到的是底层指针,需要技巧。
1.11.1 安装 pretty-printers
# Ubuntu/Debian 通常自带,如果没有:
sudo apt install libstdc++6-dev
# 在 .gdbinit 中加载(通常是自动的)
python import subprocess
python subprocess.call(['gcc', '-print-file-name=libstdc++.so'])
1.11.2 打印常见容器
# std::vector
print v # 如果有 pretty-printer 会显示元素
print v.size() # 元素个数
print v[5] # 第6个元素
# 没有 pretty-printer 时手动访问:
print v._M_impl._M_start[0] # 第1个元素
print *v._M_impl._M_start@5 # 前5个元素
# std::string
print s # 直接打印
print s.c_str() # C 字符串指针
print s.size() # 长度
# std::map(红黑树,遍历复杂)
print m.size()
# 手动遍历比较麻烦,建议用 python 脚本或 pretty-printer
# std::unordered_map
print m.size()
print m.bucket_count()
# std::shared_ptr
print ptr # 显示指向的值
print ptr.use_count() # 引用计数
# std::unique_ptr
print *ptr # 解引用
print ptr.get() # 获取裸指针
1.11.3 Python 辅助脚本
GDB 内置 Python 支持,可以写更复杂的分析:
# 在 GDB 内执行 Python
(gdb) python
import gdb
val = gdb.parse_and_eval("my_vector")
start = val['_M_impl']['_M_start']
size = int(val['_M_impl']['_M_finish'] - start)
for i in range(size):
print(f"[{i}] = {start[i]}")
end
1.12 调试技巧进阶
1.12.1 反向调试(Record and Replay)
GDB 支持录制执行过程,然后反向运行,找出"变量是在哪里被改坏的":
# 开始录制
record
# 正常运行到问题出现的地方
continue
# 反向运行
reverse-continue # 反向运行到上一个断点
reverse-step # 反向单步(进入函数)
reverse-next # 反向单步(不进入函数)
reverse-finish # 回到函数调用处
# 注意:record 模式下程序很慢(10-100倍),适合小范围使用
实用场景:设好 watchpoint,正向运行到变量被改的地方,然后用 reverse-step 一步步看是谁改了它。
1.12.2 条件记录(只记录关键时刻)
# 在某个断点时才开始录制
break critical_function
commands 1
record
continue
end
1.12.3 fork 后调试子进程
# 默认:fork 后跟踪父进程
# 改为跟踪子进程
set follow-fork-mode child
# 同时调试父子进程(detach-on-fork off)
set detach-on-fork off
info inferiors # 查看所有进程
inferior 2 # 切换到进程2
1.12.4 调试动态库
# 查看已加载的共享库
info sharedlibrary
# 在动态库函数打断点(库加载后才能断)
break dlopen # 先在 dlopen 断
continue
break libfoo:my_func # 库加载后在库函数打断点
# 或者设置延迟断点(库加载时自动生效)
set breakpoint pending on
break libfoo.so:my_function
1.12.5 信号处理
# 查看 GDB 对各种信号的处理方式
info signals
# 修改信号处理(stop=是否停,print=是否打印,pass=是否传给程序)
handle SIGPIPE nostop noprint pass # 忽略 SIGPIPE(常见于网络程序)
handle SIGUSR1 stop print nopass # SIGUSR1 时停下但不传给程序
# 手动给程序发信号
signal SIGUSR1
1.12.6 调试优化代码(-O2 编译)
优化代码时变量可能 <optimized out>,几个技巧:
# 1. 查看寄存器找变量值(优化后变量可能在寄存器里)
info registers
# 对照汇编找对应的寄存器
# 2. 查看汇编
disassemble current_function
disassemble /s current_function # 混合源码和汇编
# 3. 用 x 直接查内存(不依赖符号名)
x/4xw $rsp # 查看栈内存
x/4xw $rbp-16 # 查看 rbp 偏移处
1.13 TUI 模式(文本图形界面)
GDB 内置了分屏文字界面,同时显示源码和命令行。
# 启动时进入 TUI
gdb -tui ./your_program
# 运行中切换
Ctrl + X, A # 切换 TUI 模式开关
1.13.1 TUI 布局
layout src # 显示源码窗口
layout asm # 显示汇编窗口
layout split # 同时显示源码和汇编
layout regs # 显示寄存器窗口
1.13.2 TUI 快捷键
| 快捷键 | 功能 |
|---|---|
Ctrl+X, A | 切换 TUI 开关 |
Ctrl+X, 1 | 单窗口布局 |
Ctrl+X, 2 | 双窗口布局 |
Ctrl+L | 刷新屏幕(显示错乱时用) |
PgUp/PgDn | 在源码窗口翻页 |
↑/↓ | 浏览命令历史(在命令窗口) |
1.14 常用命令速查表
1.14.1 启动与控制
gdb ./prog # 启动
gdb -p PID # attach 进程
gdb ./prog core # 分析 core dump
gdb -batch -ex "bt" ./p core # 批处理模式
run / r # 运行
run arg1 arg2 # 带参数运行
continue / c # 继续
next / n # 下一行(不进入函数)
step / s # 下一行(进入函数)
finish / fin # 运行到函数返回
until N # 运行到第N行
quit / q # 退出
1.14.2 断点
break N / b N # 第N行打断点
break func # 函数打断点
break N if cond # 条件断点
tbreak N # 临时断点(触发一次后删除)
watch var # 变量写入时停下
rwatch var # 变量读取时停下
catch throw # 捕获 C++ 异常
info breakpoints / i b # 查看断点
disable N / enable N # 禁用/启用
delete N # 删除断点
1.14.3 查看信息
backtrace / bt # 调用栈
bt full # 完整调用栈
frame N / f N # 切换栈帧
up / down # 上下移动栈帧
print expr / p expr # 打印表达式
print/x expr # 十六进制
print *ptr@N # 打印N个数组元素
display expr # 自动显示
x/Nfz addr # 检查内存
info locals # 局部变量
info args # 函数参数
info registers # 寄存器
info threads # 线程列表
info sharedlibrary # 共享库
list / l # 查看源码
disassemble # 反汇编
ptype var # 变量类型
1.14.4 多线程
info threads # 列出线程
thread N # 切换线程
thread apply all bt # 所有线程调用栈
set scheduler-locking on/off # 锁定/解锁线程调度
1.14.5 Core Dump 相关
ulimit -c unlimited # 允许 core dump
gcore -o core PID # 不重启生成 core
gdb prog core # 分析 core
generate-core-file path # GDB 内部生成 core
info proc mappings # 内存映射
1.15 实战:标准调试流程
1.15.1 程序崩溃调试流程
1. 确认编译有 -g 选项
↓
2. 确认 ulimit -c unlimited
↓
3. 运行程序,等待崩溃生成 core
↓
4. gdb ./prog core
↓
5. bt 看崩溃调用栈
↓
6. 找到自己代码的帧(frame N)
↓
7. print 各个变量,找出异常值
↓
8. 逐帧向上分析(up),追溯问题根源
↓
9. 确认根因后修复
1.15.2 逻辑错误调试流程
1. 确定大概问题范围(哪个函数)
↓
2. 在函数入口打断点
↓
3. run,程序停在断点
↓
4. 检查函数参数是否正常(info args)
↓
5. next/step 单步执行
↓
6. display 关键变量,观察其变化
↓
7. 找到变量"变坏"的那一行
↓
8. 分析该行的代码逻辑
1.15.3 内存破坏调试流程(最难)
内存破坏(写越界)的特点是:崩溃的位置往往不是真正出错的位置,因为越界写在稍后才被发现。
1. 先用 AddressSanitizer 定位(最有效)
gcc -fsanitize=address -g ./prog && ./prog
2. 如果无法用 ASan,用 GDB watchpoint:
找到被破坏的变量或内存地址
↓
watch *address 设置内存写监控
↓
run,程序在写入该地址时停下
↓
bt 看是谁在写这块内存
↓
这才是真正的越界写位置
1.16 总结
1.16.1 GDB 核心能力图
GDB 能做什么
├── 实时调试(程序运行中)
│ ├── 断点控制(break / watch / catch)
│ ├── 单步执行(next / step / finish)
│ ├── 变量查看和修改(print / set)
│ └── 多线程控制(thread / scheduler-locking)
│
├── 事后调试(core dump 分析)
│ ├── 还原崩溃现场(bt / frame / print)
│ ├── 所有线程状态(thread apply all bt)
│ └── 内存内容检查(x / info proc mappings)
│
└── 自动化分析
├── 批处理模式(-batch -ex)
├── .gdbinit 脚本
└── Python 扩展
1.16.2 调试方式选择
| 场景 | 推荐方式 |
|---|---|
| 程序崩溃,能复现 | 直接 gdb ./prog,设断点 |
| 程序崩溃,不能复现 | 开启 core dump,分析 gdb ./prog core |
| 程序卡死/死锁 | gdb -p PID,thread apply all bt |
| 变量被神秘改写 | watch 观察点 |
| 生产环境在线诊断 | gcore PID 不重启抓 core,事后分析 |
| 内存越界 | 优先 AddressSanitizer,再用 watchpoint |
| 逻辑错误 | 条件断点 + display 观察变量变化 |
1.16.3 最重要的三个习惯
- 始终带
-g编译:没有调试信息,GDB 能给的信息极为有限。 - 生产环境开 core dump:
ulimit -c unlimited和合理的core_pattern,崩溃时才能事后分析。 - 先
bt,再frame,再print:这是分析任何崩溃的标准三步,形成肌肉记忆后调试效率大幅提升。

1万+

被折叠的 条评论
为什么被折叠?



