gdb、core dump 调试指导

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 PIDthread apply all bt
变量被神秘改写watch 观察点
生产环境在线诊断gcore PID 不重启抓 core,事后分析
内存越界优先 AddressSanitizer,再用 watchpoint
逻辑错误条件断点 + display 观察变量变化

1.16.3 最重要的三个习惯

  1. 始终带 -g 编译:没有调试信息,GDB 能给的信息极为有限。
  2. 生产环境开 core dumpulimit -c unlimited 和合理的 core_pattern,崩溃时才能事后分析。
  3. bt,再 frame,再 print:这是分析任何崩溃的标准三步,形成肌肉记忆后调试效率大幅提升。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

「已注销」

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值