GDB调试技巧与指令完全指南---个人学习篇

文章目录

  • [Linux 环境下GDB调试技巧与指令完全指南](#Linux 环境下GDB调试技巧与指令完全指南)
  • [1.GDB 概述与环境准备](#1.GDB 概述与环境准备)
    • [1.1 什么是GDB](#1.1 什么是GDB)
    • [1.2 安装GDB](#1.2 安装GDB)
    • [1.3 GCC、G++、GDB的区别与协作](#1.3 GCC、G++、GDB的区别与协作)
    • [1.4 编译调试版程序](#1.4 编译调试版程序)
    • [1.5 验证调试信息](#1.5 验证调试信息)
    • [1.6 GDB 的四种启动模式](#1.6 GDB 的四种启动模式)
  • [2.GDB 启动与基础操作](#2.GDB 启动与基础操作)
    • [2.1 启动与退出](#2.1 启动与退出)
    • [2.2 运行控制](#2.2 运行控制)
    • [2.3 运行参数与环境](#2.3 运行参数与环境)
    • [2.4 查看源码](#2.4 查看源码)
    • [2.5 搜索源码](#2.5 搜索源码)
    • [2.6 帮助系统](#2.6 帮助系统)
  • 3.断点体系------精准控制程序暂停
    • [3.1 普通断点(breakpoint)](#3.1 普通断点(breakpoint))
    • [3.2 条件断点](#3.2 条件断点)
    • [3.3 观察断点(watchpoint)](#3.3 观察断点(watchpoint))
    • [3.4 捕捉断点(catchpoint)](#3.4 捕捉断点(catchpoint))
    • [3.5 断点管理](#3.5 断点管理)
    • [3.6 断点命令列表(commands)](#3.6 断点命令列表(commands))
    • [3.7 保存与恢复断点](#3.7 保存与恢复断点)
  • 4.数据观察------变量、内存与表达式
    • [4.1 print 命令详解](#4.1 print 命令详解)
    • [4.2 打印复杂类型](#4.2 打印复杂类型)
    • [4.3 类型查询](#4.3 类型查询)
    • [4.4 display------持续显示变量](#4.4 display——持续显示变量)
    • [4.5 内存检查------x 命令](#4.5 内存检查——x 命令)
    • [4.6 修改变量与内存](#4.6 修改变量与内存)
    • [4.7 寄存器查看](#4.7 寄存器查看)
  • 5.栈帧与调用链分析
    • [5.1 栈帧基础](#5.1 栈帧基础)
    • [5.2 栈帧操作命令](#5.2 栈帧操作命令)
    • [5.3 backtrace 实战解读](#5.3 backtrace 实战解读)
    • [5.4 函数调用](#5.4 函数调用)
  • 6.多线程与多进程调试
    • [6.1 多线程调试基础](#6.1 多线程调试基础)
    • [6.2 线程断点](#6.2 线程断点)
    • [6.3 调度器锁定](#6.3 调度器锁定)
    • [6.4 线程创建与退出事件](#6.4 线程创建与退出事件)
    • [6.5 多进程调试](#6.5 多进程调试)
    • [6.6 多线程死锁排查实战](#6.6 多线程死锁排查实战)
  • [7.Core Dump 文件分析](#7.Core Dump 文件分析)
    • [7.1 什么是 Core Dump](#7.1 什么是 Core Dump)
    • [7.2 启用Core Dump](#7.2 启用Core Dump)
    • [7.3 分析Core Dump](#7.3 分析Core Dump)
    • [7.4 Core Dump分析实战](#7.4 Core Dump分析实战)
    • [7.5 Core Dump常见问题](#7.5 Core Dump常见问题)
    • [7.6 线上环境调试建议](#7.6 线上环境调试建议)
  • 8.内存检查与段错误排查
    • [8.1 段错误定位](#8.1 段错误定位)
    • [8.2 内存越界检查------AddressSanitizer](#8.2 内存越界检查——AddressSanitizer)
    • [8.3 内存泄漏检测](#8.3 内存泄漏检测)
    • [8.4 使用Valgrind检测内存问题](#8.4 使用Valgrind检测内存问题)
    • [8.5 GDB中的内存映射查看](#8.5 GDB中的内存映射查看)
  • 9.反向调试------时光倒流的魔法
    • [9.1 启用记录与回放](#9.1 启用记录与回放)
    • [9.2 设置执行方向](#9.2 设置执行方向)
    • [9.3 反向调试实战场景](#9.3 反向调试实战场景)
    • [9.4 反向调试的限制](#9.4 反向调试的限制)
    • [9.5 替代方案:rr(Record and Replay)](#9.5 替代方案:rr(Record and Replay))
  • 10.远程调试与gdbserver
    • [10.1 为什么需要远程调试](#10.1 为什么需要远程调试)
    • [10.2 gdbserver 基本用法](#10.2 gdbserver 基本用法)
    • [10.3 远程调试完整流程](#10.3 远程调试完整流程)
    • [10.4 gdbserver 高级选项](#10.4 gdbserver 高级选项)
  • [11.GDB TUI图形化界面](#11.GDB TUI图形化界面)
    • [11.1 启用 TUI模式](#11.1 启用 TUI模式)
    • [11.2 TUI窗口管理](#11.2 TUI窗口管理)
    • [11.3 TUI 快捷键](#11.3 TUI 快捷键)
    • [11.4 CGDB------增强版TUI](#11.4 CGDB——增强版TUI)
  • [12.VS Code 图形化调试](#12.VS Code 图形化调试)
    • [12.1 为什么用 VS Code 调试](#12.1 为什么用 VS Code 调试)
    • [12.2 环境准备](#12.2 环境准备)
    • [12.3 配置文件说明](#12.3 配置文件说明)
    • [12.4 tasks.json 配置](#12.4 tasks.json 配置)
    • [12.5 launch.json 配置](#12.5 launch.json 配置)
    • [12.6 调试工具栏](#12.6 调试工具栏)
    • [12.7 调试面板信息](#12.7 调试面板信息)
    • [12.8 调试控制台](#12.8 调试控制台)
    • [12.9 VS Code 调试常见问题](#12.9 VS Code 调试常见问题)
  • 13.信号处理与异常捕获
    • [13.1 信号基础](#13.1 信号基础)
    • [13.2 自定义信号处理](#13.2 自定义信号处理)
    • [13.3 主动发送信号](#13.3 主动发送信号)
    • [13.4 C++ 异常调试](#13.4 C++ 异常调试)
  • [14.自定义命令与 .gdbinit 配置](#14.自定义命令与 .gdbinit 配置)
    • [14.1 gdbinit 文件](#14.1 gdbinit 文件)
    • [14.2 自定义命令(define)](#14.2 自定义命令(define))
    • [14.3 Hook 命令](#14.3 Hook 命令)
    • [14.4 常用gdbinit实用配置集合](#14.4 常用gdbinit实用配置集合)
  • [15.GDB Python脚本编程](#15.GDB Python脚本编程)
    • [15.1 Python脚本基础](#15.1 Python脚本基础)
    • [15.2 使用GDB Python API](#15.2 使用GDB Python API)
    • [15.3 Pretty Printer------美化打印](#15.3 Pretty Printer——美化打印)
    • [15.4 自定义断点(Python)](#15.4 自定义断点(Python))
    • [15.5 实用Python脚本示例](#15.5 实用Python脚本示例)
  • 16.常用调试实战案例
    • [16.1 案例一:定位段错误](#16.1 案例一:定位段错误)
    • [16.2 案例二:调试多线程死锁](#16.2 案例二:调试多线程死锁)
    • [16.3 案例三:查找内存泄漏](#16.3 案例三:查找内存泄漏)
    • [16.4 案例四:条件断点优化循环调试](#16.4 案例四:条件断点优化循环调试)
    • [16.5 案例五:调试共享库问题](#16.5 案例五:调试共享库问题)
  • [17.附录 A GDB命令速查表](#17.附录 A GDB命令速查表)
    • [17.1 启动与退出](#17.1 启动与退出)
    • [17.2 运行控制](#17.2 运行控制)
    • [17.3 断点](#17.3 断点)
    • [17.4 数据查看](#17.4 数据查看)
    • [17.5 栈帧](#17.5 栈帧)
    • [17.6 多线程](#17.6 多线程)
    • [17.7 反向调试](#17.7 反向调试)
    • [17.8 源码与汇编](#17.8 源码与汇编)
  • 18.GCC编译选项速查
    • [18.1 常用编译选项](#18.1 常用编译选项)
    • [18.2 库文件编译](#18.2 库文件编译)
  • 19.总结

Linux 环境下GDB调试技巧与指令完全指南

   1.GDB(GNU Debugger)是 Linux下最强大的程序调试工具,支持C、C++、Go、等多种语言。

   2.本文从基础操作到高级技巧,系统性地讲解GDB的核心功能,涵盖断点管理、内存检查、多线程调试、反向调试、远程调试、脚本自动化等内容,并结合实战案例帮助你真正掌握这一调试利器。

1.GDB 概述与环境准备

1.1 什么是GDB

   GDB是GNU项目开发的调试器,诞生于 1986 年,由Richard Stallman亲自编写。它能够在程序运行时检查其内部状态,也可以在程序崩溃后分析其死亡现场。GDB的核心能力包括:

  • 启动程序,指定任意运行参数和环境
  • 设置断点,让程序在指定条件下暂停
  • 检查状态,查看变量、内存、寄存器、调用栈
  • 动态修改,在运行时改变程序的内部状态
  • 反向执行,让程序"时光倒流"回退到之前的执行点

1.2 安装GDB

   大多数Linux发行版通过包管理器即可安装:

bash 复制代码
# Debian/Ubuntu
sudo apt update
sudo apt install build-essential gdb

# Arch Linux
sudo pacman -S gdb

   安装完成后验证版本:

bash 复制代码
$ gdb --version
GNU gdb (Ubuntu 12.1-0ubuntu1~22.04) 12.1

   建议:使用GDB 9.0以上版本,以获得完整的多线程调试和Python脚本支持。

1.3 GCC、G++、GDB的区别与协作

工具 用途 特点
GCC 编译 C 程序 不自动链接 C++ 标准库
G++ 编译 C++ 程序 自动链接 C++ 标准库
GDB 程序调试 需加 -g 参数保留符号表

核心区别

  • .c 文件:gcc 当作 C 程序,g++ 当作 C++
  • .cpp 文件:都会当成 C++ 程序
  • g++ 会调用 gcc,但链接阶段由 g++ 完成(因为 gcc 不能自动和 C++ 程序使用的库连接)

1.4 编译调试版程序

   GDB依赖编译器生成的调试信息来映射机器码与源代码的对应关系。使用GCC/G++ 编译时,务必加上 -g 选项:

bash 复制代码
# 基础编译
g++ -g -O0 -o myapp myapp.cpp

# 多文件编译
g++ -g -O0 main.cpp utils.cpp -o app -lm

# 编译带更详细调试信息(包含宏定义)
g++ -g3 -O0 -o myapp myapp.cpp

# CMake 项目
cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_FLAGS="-g -O0" ..
选项 作用
-g 在可执行文件中嵌入调试信息(DWARF 格式)
-g3 包含宏定义等更详细的调试信息
-O0 关闭优化,保证变量和执行顺序与源码一致
-gdwarf-4 指定 DWARF 版本(默认通常为 DWARF 4)
-fno-omit-frame-pointer 保留帧指针,确保栈回溯正确

   重要提示 :调试阶段务必使用 -O0关闭优化。-O2-O3优化会内联函数、消除变量、重排指令,导致断点偏移、变量值显示为 <optimized out> 等问题。

1.5 验证调试信息

   编译后检查二进制文件是否包含调试信息:

bash 复制代码
# 检查调试段
readelf -S ./myapp | grep debug

# 快速查看文件类型
file ./myapp

# 输出示例(有调试信息)
# .debug_info, .debug_abbrev, .debug_line 等调试段存在

1.6 GDB 的四种启动模式

bash 复制代码
# 模式一:直接调试可执行文件
gdb ./myapp

# 模式二:调试正在运行的进程
gdb -p <PID>
# 或在 GDB 内部
(gdb) attach <PID>

# 模式三:调试 core dump 文件
gdb ./myapp core.<PID>

# 模式四:带参数启动
gdb --args ./myapp arg1 arg2 arg3

2.GDB 启动与基础操作

2.1 启动与退出

bash 复制代码
# 启动 GDB 并加载可执行文件
gdb ./myapp

# 启动时不显示版权信息(静默模式)
gdb -q ./myapp

# 带命令行参数启动
gdb --args ./myapp arg1 arg2 arg3

# 启动后设置参数
(gdb) set args arg1 arg2 arg3
(gdb) show args

   退出GDB:

bash 复制代码
(gdb) quit        # 或简写 q
(gdb) Ctrl+d      # 快捷键退出

2.2 运行控制

命令 缩写 说明
run r 从头开始运行程序
start --- 运行程序并在 main 函数入口停下
continue c 继续运行到下一个断点
step s 单步执行,进入函数内部
next n 单步执行,跳过函数调用
stepi si 机器指令级单步,进入函数
nexti ni 机器指令级单步,跳过函数
finish --- 运行到当前函数返回
until u 运行到当前循环结束(跳出循环)
until <行号> --- 运行至指定行
return --- 直接从当前函数返回(可指定返回值)
kill k 终止正在运行的程序
skip --- 跳过指定函数,不进入调试

   step与next的关键区别

c 复制代码
// 当执行到第 3 行时:
1  int result = 0;
2  result = compute(a, b);   ← step 会进入 compute() 函数
3  printf("%d\n", result);    ← next 会把 compute() 当一步执行完

2.3 运行参数与环境

bash 复制代码
# 设置运行参数
(gdb) set args --config /etc/app.conf --verbose

# 设置环境变量
(gdb) set env LD_LIBRARY_PATH=/usr/local/lib
(gdb) set env DEBUG=1

# 清除环境变量
(gdb) unset env DEBUG

# 改变工作目录
(gdb) cd /path/to/project

# 执行 shell 命令
(gdb) shell ls -la
(gdb) !ls -la          # 简写形式

2.4 查看源码

bash 复制代码
(gdb) list              # 显示当前行附近的源码(默认10行)
(gdb) list 30           # 以第 30 行为中心显示
(gdb) list main         # 显示 main 函数源码
(gdb) list file.c:50    # 显示 file.c 第 50 行
(gdb) list file.c:func  # 显示 file.c 的 func 函数
(gdb) set listsize 20   # 修改每次显示的行数
(gdb) show listsize     # 查看当前显示行数

2.5 搜索源码

bash 复制代码
(gdb) forward-search pattern    # 从当前位置向前搜索
(gdb) reverse-search pattern   # 从当前位置向后搜索

2.6 帮助系统

   GDB内置了完善的帮助文档:

bash 复制代码
(gdb) help                    # 顶层帮助
(gdb) help breakpoints        # 断点相关命令帮助
(gdb) help running            # 运行控制命令帮助
(gdb) help data               # 数据查看命令帮助
(gdb) help stack              # 栈相关命令帮助
(gdb) apropos thread          # 搜索与 thread 相关的所有命令

3.断点体系------精准控制程序暂停

   断点是GDB调试的核心机制。掌握不同类型的断点及其高级用法,是从入门走向精通的关键一步。

3.1 普通断点(breakpoint)

bash 复制代码
# 在函数入口设断点
(gdb) break main
(gdb) break MyClass::myMethod    # C++ 类方法

# 在指定行设断点
(gdb) break test.c:42

# 在指定文件指定函数设断点
(gdb) break file.c:process_data

# 在当前行设断点
(gdb) break

# 用正则表达式批量设断点
(gdb) rbreak ^handle_.*         # 所有以 handle_ 开头的函数
(gdb) rbreak file.c:.*          # file.c 中所有函数

# 临时断点(命中一次后自动删除)
(gdb) tbreak main
(gdb) tbreak file.c:100

3.2 条件断点

   当只需要在特定条件下才暂停程序时,条件断点极为有用:

bash 复制代码
# 直接设置条件断点
(gdb) break file.c:50 if i == 100
(gdb) break process_data if size > 1024
(gdb) break loop_func if strcmp(name, "error") == 0

# 为已有断点添加条件
(gdb) break file.c:50
(gdb) condition 1 i == 100      # 给 1 号断点加条件

# 移除条件(保留断点)
(gdb) condition 1               # 清除 1 号断点的条件

   条件断点实战技巧:在循环中调试时,如果只想在特定迭代时暂停,条件断点可以避免成千上万次的手动 continue:

c 复制代码
for (int i = 0; i < 10000; i++) {
    process(data[i]);  // 只在 i == 5000 时暂停
}
bash 复制代码
(gdb) break process if i == 5000

   实战案例:排查排序算法中第 9999 次交换操作出问题:

bash 复制代码
(gdb) break swap if swap_count == 9999

3.3 观察断点(watchpoint)

   观察断点不关注执行到了哪一行,而是关注某个变量或内存地址的值何时发生变化:

bash 复制代码
# 监视变量写入
(gdb) watch global_counter      # 变量值改变时暂停

# 监视变量读取
(gdb) rwatch global_counter     # 变量被读取时暂停

# 监视变量读写
(gdb) awatch global_counter     # 变量被读或写时暂停

# 监视内存地址
(gdb) watch *(int*)0x7ffff7a0   # 监视指定地址的 int 值

   观察断点触发时会显示旧值和新值:

复制代码
Old value = 42
New value = 100

   注意:watchpoint 依赖硬件调试寄存器,数量有限(通常x86架构仅有4个)。超出限制时 GDB 会使用软件模拟方式,性能会大幅下降。

3.4 捕捉断点(catchpoint)

   捕捉断点用于捕获特定事件而非代码位置:

bash 复制代码
(gdb) catch throw               # C++ 异常抛出时暂停
(gdb) catch catch               # C++ 异常捕获时暂停
(gdb) catch syscall open        # 系统调用 open 时暂停
(gdb) catch syscall             # 任意系统调用时暂停
(gdb) catch fork                # fork 调用时暂停
(gdb) catch vfork               # vfork 调用时暂停
(gdb) catch exec                # exec 调用时暂停
(gdb) catch load                # 动态库加载时暂停
(gdb) catch signal SIGSEGV      # 捕获特定信号

   catch throw 的实用场景 :当 C++ 程序因未捕获异常崩溃时,用 catch throw 可以在异常刚抛出时就暂停,而不是等到程序崩溃,这样可以获得完整的异常抛出上下文。

3.5 断点管理

bash 复制代码
# 查看所有断点
(gdb) info breakpoints          # 或 info break,简写 i b
(gdb) info watchpoints          # 只查看观察断点

# 禁用/启用断点
(gdb) disable 1                 # 禁用 1 号断点
(gdb) disable 2-5               # 禁用 2 到 5 号断点
(gdb) enable 1                  # 启用 1 号断点
(gdb) enable once 1             # 启用一次,命中后自动禁用
(gdb) enable delete 1           # 启用一次,命中后自动删除
(gdb) enable count 3 1          # 启用 1 号断点,3 次后自动禁用

# 删除断点
(gdb) delete 1                  # 删除 1 号断点
(gdb) delete                    # 删除所有断点
(gdb) clear                     # 删除当前位置的断点
(gdb) clear file.c:50           # 删除 file.c:50 处的断点

# 忽略断点
(gdb) ignore 1 100              # 忽略 1 号断点接下来 100 次命中

3.6 断点命令列表(commands)

   commands 可以为断点绑定一组自动执行的命令,非常适合自动化调试:

bash 复制代码
(gdb) break process_data if size > 1024
(gdb) commands
Type commands for breakpoint(s), 1, one per line.
End with a line saying just "end".
>printf "size = %d, data = %p\n", size, data
>backtrace 3
>continue
>end

   执行效果:每次断点命中时,自动打印变量值、显示调用栈,然后继续运行。无需人工干预。

   实用场景------用 commands 替代 printf 调试:

bash 复制代码
(gdb) break main_loop
(gdb) commands
>silent
>printf "iteration %d: value=%f\n", iter, value
>continue
>end

   silent会抑制断点命中的默认输出,只打印你指定的内容,相当于零侵入的日志打印。

3.7 保存与恢复断点

bash 复制代码
# 保存所有断点到文件
(gdb) save breakpoints breakpoints.gdb

# 加载断点
(gdb) source breakpoints.gdb

4.数据观察------变量、内存与表达式

4.1 print 命令详解

   print 是查看数据最基本的命令:

bash 复制代码
# 基本用法
(gdb) print var                # 打印变量值
(gdb) print/x var              # 十六进制输出
(gdb) print/d var              # 十进制输出
(gdb) print/o var              # 八进制输出
(gdb) print/t var              # 二进制输出
(gdb) print/c var              # 字符输出
(gdb) print/f var              # 浮点数输出
(gdb) print/a var              # 地址输出
(gdb) print/s (char*)ptr       # 字符串输出

   输出格式速查

格式符 含义 示例
/x 十六进制 0xff
/d 有符号十进制 255
/u 无符号十进制 255
/o 八进制 0377
/t 二进制 11111111
/c 字符 'A'
/f 浮点 3.14
/a 地址 0x7fffffffe000
/s 字符串 "hello"

4.2 打印复杂类型

bash 复制代码
# 打印结构体
(gdb) print my_struct
(gdb) print my_struct.field

# 打印指针指向的内容
(gdb) print ptr                  # 打印指针地址
(gdb) print *ptr                 # 解引用,打印指向的值
(gdb) print *ptr@10              # 打印连续 10 个元素(数组)

# 打印数组
(gdb) print array                # 打印整个数组
(gdb) print array[0]@5          # 从 array[0] 开始打印 5 个元素
(gdb) print *array@10            # 另一种打印 10 个元素的方式

# C++ STL 容器(需 pretty printer 支持)
(gdb) print my_vector
(gdb) print my_vector.size()
(gdb) print my_map

# 打印字符串
(gdb) print str                  # 打印 char* 指针
(gdb) print (char*)ptr           # 强制类型转换

4.3 类型查询

bash 复制代码
# 简要类型
(gdb) whatis var                 # 输出: type = int
(gdb) whatis my_struct           # 输出: type = struct MyStruct

# 详细类型定义
(gdb) ptype my_struct            # 输出完整的结构体定义
# struct MyStruct {
#     int id;
#     char name[64];
#     double value;
# }

# 查看类继承关系
(gdb) ptype MyClass
# class MyClass : public BaseClass {
#   public:
#     void process(void);
#   private:
#     int data_;
# }

4.4 display------持续显示变量

   display 在每次程序暂停时自动打印指定变量,非常适合监控状态变化:

bash 复制代码
(gdb) display i                  # 每次停下自动打印 i 的值
(gdb) display/x i               # 十六进制自动打印
(gdb) display *ptr@5             # 自动打印数组前 5 个元素

# 管理 display
(gdb) info display               # 查看所有 display 设置
(gdb) undisplay 1                # 取消 1 号 display
(gdb) delete display 1           # 同 undisplay
(gdb) disable display 1          # 禁用但保留
(gdb) enable display 1           # 重新启用

4.5 内存检查------x 命令

   x(examine)是直接检查原始内存的最强大工具:

bash 复制代码
# 语法: x/NFU address
# N: 显示单元数量
# F: 显示格式 (x/d/u/o/t/c/f/s/a/i)
# U: 单元大小 (b=1字节, h=2字节, w=4字节, g=8字节)
参数 含义
N 显示的单元数量
F 格式:x(hex) d(dec) u(unsigned) o(oct) t(bin) c(char) f(float) s(string) i(instruction)
U 单元大小:b(byte) h(halfword=2B) w(word=4B) g(giant=8B)

   典型用法

bash 复制代码
# 查看内存区域的十六进制
(gdb) x/16xb 0x7fffffffe000    # 16 个字节,十六进制
(gdb) x/8xw 0x7fffffffe000    # 8 个 word(4字节),十六进制
(gdb) x/4xg 0x7fffffffe000    # 4 个 giant(8字节),十六进制

# 查看字符串
(gdb) x/s str_ptr              # 从地址读取以 null 结尾的字符串

# 查看内存中的指令
(gdb) x/10i $pc                # 查看当前 PC 处的 10 条汇编指令
(gdb) x/10i main               # 查看 main 函数的汇编指令

# 查看栈上的数据
(gdb) x/32xb $rsp              # 查看栈顶 32 字节
(gdb) x/8xg $rbp               # 查看帧指针附近的 8 个 8 字节值

# 查看数组
(gdb) x/20dw array_ptr         # 20 个十进制整数

# 查看64字节十六进制内存
(gdb) x/64x &buffer

# 反汇编指定地址
(gdb) x/10i &function

4.6 修改变量与内存

bash 复制代码
# 修改变量值
(gdb) set variable i = 100
(gdb) set var ptr = (int*)malloc(sizeof(int) * 10)   # 动态分配内存

# 修改内存
(gdb) set *(int*)0x7fffffffe000 = 42

# 修改结构体字段
(gdb) set my_struct.field = 3.14

# 修改字符串
(gdb) set str = "new value"

   警告:运行时修改变量可能导致程序进入未预期的状态,请确保理解修改的影响。

4.7 寄存器查看

bash 复制代码
(gdb) info registers             # 查看所有通用寄存器
(gdb) info all-registers         # 查看所有寄存器(含浮点、向量)
(gdb) print $rax                 # 查看特定寄存器
(gdb) print $pc                  # 查看程序计数器
(gdb) print $rsp                 # 查看栈指针
(gdb) print $rbp                 # 查看帧指针
(gdb) set $rax = 0               # 修改寄存器值

5.栈帧与调用链分析

5.1 栈帧基础

   每当函数被调用,一个栈帧(stack frame)被压入调用栈,包含局部变量、参数、返回地址等信息。理解栈帧是排查复杂bug的关键。

bash 复制代码
+-------------------+
|   main() frame    |  ← frame 3 (最外层)
+-------------------+
|   parse() frame   |  ← frame 2
+-------------------+
|   process() frame |  ← frame 1
+-------------------+
|   compute() frame |  ← frame 0 (当前函数)
+-------------------+  ← 栈顶 (低地址方向)

5.2 栈帧操作命令

bash 复制代码
# 查看完整调用栈
(gdb) backtrace                  # 或 bt
(gdb) backtrace full             # 同时打印每个帧的局部变量
(gdb) backtrace 5                # 只显示最近 5 个帧
(gdb) bt                         # 简写

# 切换栈帧
(gdb) frame 2                    # 切换到第 2 号帧
(gdb) frame                      # 显示当前帧信息
(gdb) up                         # 切换到上一级调用帧
(gdb) up 2                       # 上移两级
(gdb) down                       # 切换到下一级调用帧
(gdb) down 1                     # 下移一级

# 查看帧信息
(gdb) info frame                 # 当前帧的详细信息(地址、参数等)
(gdb) info args                  # 当前帧的函数参数
(gdb) info locals                # 当前帧的局部变量
(gdb) info frame 2               # 指定帧的信息

5.3 backtrace 实战解读

复制代码
(gdb) bt
#0  compute (a=0x0, b=42) at math.c:25
#1  0x0000555555555189 in process (data=0x5555555592a0) at process.c:67
#2  0x00005555555551f4 in parse (input=0x7fffffffe2d0) at parse.c:103
#3  0x0000555555555268 in main (argc=2, argv=0x7fffffffe3d8) at main.c:34

阅读顺序(从下往上):

  1. #0 :确认"死因"------compute 函数中 a 为 NULL 指针,这很可能就是段错误的原因
  2. #1 :找到"谁把你送走的"------调用者 process,传入了 data 指针
  3. #2/#3:定位业务入口
bash 复制代码
(gdb) frame 1
(gdb) info locals
data_ptr = 0x5555555592a0
result = 0
index = 5

栈不是一条线,而是一棵树。很多人只看 #0,这是典型的新手误区。真正有价值的,往往在 #1 ~ #3。

5.4 函数调用

   GDB允许在调试过程中直接调用程序的函数:

bash 复制代码
(gdb) call printf("debug: i=%d\n", i)        # 调用 printf
(gdb) call malloc(1024)                        # 调用 malloc
(gdb) print compute(3, 4)                      # 调用并打印返回值
(gdb) call my_debug_function()                  # 调用自定义调试函数

   注意callprint 调用的函数中对全局变量和静态变量的修改,在函数返回后会被恢复到调用前的值。这是 GDB 的安全机制,确保调试操作不会意外改变程序状态。

6.多线程与多进程调试

6.1 多线程调试基础

   现代程序几乎都涉及多线程,GDB提供了完整的多线程调试支持。

bash 复制代码
# 查看所有线程
(gdb) info threads
#   Id   Target Id          Frame
# * 1    Thread 0x7f... "myapp"  compute (a=0x0, b=42) at math.c:25
#   2    Thread 0x7f... "myapp"  pthread_cond_wait (...) at thread.c:78
#   3    Thread 0x7f... "myapp"  __libc_accept (...) at server.c:142

# 切换线程
(gdb) thread 2                  # 切换到 2 号线程
(gdb) thread apply all bt       # 所有线程的调用栈(极其常用!)
(gdb) thread apply all info locals  # 所有线程的局部变量

# 对指定线程执行命令
(gdb) thread apply 1 3 bt      # 只查看 1 号和 3 号线程的调用栈
(gdb) thread apply 2 print var  # 查看 2 号线程的 var 变量

6.2 线程断点

bash 复制代码
# 只在特定线程暂停
(gdb) break file.c:50 thread 2

# 在所有线程的指定行设断点
(gdb) break file.c:50 thread all

6.3 调度器锁定

   多线程调试最大的痛点是:当你单步执行一个线程时,其他线程也在并行运行。GDB 提供了调度器锁定来解决这个问题:

bash 复制代码
# 查看当前调度器锁定模式
(gdb) show scheduler-locking

# 模式说明:
(gdb) set scheduler-locking off     # 默认:所有线程都可以执行
(gdb) set scheduler-locking on      # 只有当前线程可以执行
(gdb) set scheduler-locking step    # 单步时锁定,continue 时解锁
(gdb) set scheduler-locking replay  # 回放模式(用于反向调试)

   调试建议 :单步调试某个线程的逻辑时,开启 set scheduler-locking on,防止其他线程干扰;使用 continue 前切换回 off

6.4 线程创建与退出事件

bash 复制代码
# 在新线程创建时暂停
(gdb) set print thread-events on   # 打印线程创建/退出消息(默认开启)

# 捕获线程相关事件
(gdb) catch pthread_create          # 线程创建时暂停

6.5 多进程调试

   调试fork产生的子进程:

bash 复制代码
# 模式一:只调试父进程(默认)
(gdb) set detach-on-fork on

# 模式二:同时调试父子进程
(gdb) set detach-on-fork off
(gdb) set follow-fork-mode child   # fork 后跟踪子进程
(gdb) set follow-fork-mode parent  # fork 后跟踪父进程(默认)

# 查看所有被调试的进程
(gdb) info inferiors
#   Num  Description       Executable
# * 1    process 12345     ./myapp
#   2    process 12346     ./myapp

# 切换进程
(gdb) inferior 2

6.6 多线程死锁排查实战

   死锁产生的原因

  1. 加锁顺序不当:线程 A 持有资源 1 想获取资源 2,线程 B 持有资源 2 想获取资源 1
  2. 重复加锁:同一线程对非递归锁重复加锁
  3. 加锁后未解锁:异常导致锁未释放

   Shell初步判断

bash 复制代码
# 查看进程状态
ps aux | grep deadlock
# STAT 字段说明:S=中断睡眠,s=会话领导进程,l=多线程进程

# 深入查看线程资源占用
top -Hp <PID>
# 死锁特征:多个线程 CPU 使用率趋近于 0

   GDB深入排查

bash 复制代码
# Attach 到进程(需要 sudo 权限)
sudo gdb attach <PID>

# 查看所有线程堆栈
(gdb) thread apply all bt

# 输出示例分析
Thread 3:
#0  0x00007ffff7b59d8f in pthread_mutex_lock () from /lib64/libpthread.so.0
#1  0x0000000000400934 in threadFunction2 () at deadlock.cpp:20
...

Thread 2:
#0  0x00007ffff7b59d8f in pthread_mutex_lock () from /lib64/libpthread.so.0
#1  0x0000000000400899 in threadFunction1 () at deadlock.cpp:13
...

   分析要点

  • 两个线程都卡在 pthread_mutex_lock
  • 查看具体行号,分析加锁顺序
  • 对比各线程的 mutex 持有情况

7.Core Dump 文件分析

7.1 什么是 Core Dump

   当进程因为非法内存访问、除零、assert 失败等原因异常终止时,内核会把进程当时的内存、寄存器、线程栈等信息保存成一个core文件。

   本质 :core 文件是一个 ELF 格式的进程内存快照,包含了崩溃瞬间的完整状态。

   触发信号

信号 触发场景 默认行为
SIGSEGV 访问非法内存地址(空指针、越界) 终止 + core
SIGABRT 调用 abort(),断言失败 终止 + core
SIGFPE 除以零、浮点异常 终止 + core
SIGBUS 内存对齐错误 终止 + core
SIGILL 非法 CPU 指令 终止 + core

7.2 启用Core Dump

   默认情况下,很多 Linux 发行版禁用了 core dump 生成。需要手动开启:

bash 复制代码
# 查看当前限制
ulimit -c
# 0  (表示禁用)

# 临时开启(当前终端生效)
ulimit -c unlimited

# 永久开启
echo "ulimit -c unlimited" >> ~/.bashrc
source ~/.bashrc

   查看/修改 core文件格式

bash 复制代码
# 查看当前 core 文件格式
cat /proc/sys/kernel/core_pattern

# 自定义格式(需要 sudo)
echo "/tmp/core-%e-%p-%t" | sudo tee /proc/sys/kernel/core_pattern
# %e: 可执行文件名  %p: PID  %t: 时间戳  %s: 信号编号  %h: 主机名

   注意 :如果系统安装了 systemd-coredump,core 文件会被systemd接管,存到 /var/lib/systemd/coredump/ 里。可以用 coredumpctl list 查看。

7.3 分析Core Dump

bash 复制代码
# 加载 core 文件(必须同时指定可执行文件)
gdb ./myapp /tmp/core-myapp-12345-1700000000

# 常用分析步骤
(gdb) bt                         # 查看崩溃时的调用栈
(gdb) bt full                    # 调用栈 + 所有帧的局部变量
(gdb) info threads               # 查看所有线程状态
(gdb) thread apply all bt        # 所有线程的调用栈
(gdb) frame 0                    # 切换到崩溃帧
(gdb) info locals                # 查看局部变量
(gdb) print var                  # 查看关键变量
(gdb) x/10xb ptr                 # 检查指针指向的内存

7.4 Core Dump分析实战

bash 复制代码
$ gdb ./server /tmp/core-server-28431-1700000000
(gdb) bt
#0  0x00007f8a9b3e2a8c in pthread_mutex_lock () from /lib/x86_64-linux-gnu/libpthread.so.0
#1  0x0000555555555342 in handle_request (conn=0x5555555592a0) at server.c:89
#2  0x0000555555555489 in worker_thread (arg=0x0) at server.c:142
#3  0x00007f8a9b3e0609 in start_thread () from /lib/x86_64-linux-gnu/libpthread.so.0

(gdb) frame 1
(gdb) print conn->mutex
$1 = __data = {__lock = 2, __owner = 28430, ...}
# __owner = 28430 但当前线程不是持有者 → 死锁!

7.5 Core Dump常见问题

问题 原因 解决方案
生成不了 core 文件 ulimit -c 为 0 ulimit -c unlimited
core 文件不在预期位置 core_pattern 被修改 检查或修改 /proc/sys/kernel/core_pattern
看不到源代码 编译时没加 -g 重新编译加 -g
符号显示为 ?? 程序被 strip 保留 .debug 文件或用 addr2line

7.6 线上环境调试建议

   生产环境的二进制通常是剥离了调试信息的,但仍然可以分析:

   保留独立调试信息文件

bash 复制代码
# 编译带调试信息
g++ -g -O2 myapp.cpp -o myapp

# 剥离调试信息单独保存
objcopy --only-keep-debug myapp myapp.debug

# 部署时 strip(删除调试信息)
strip myapp

# 分析时加载 debug 文件
gdb myapp core
(gdb) symbol-file myapp.debug

   使用addr2line辅助定位

bash 复制代码
addr2line -e myapp 0x00401234
# 输出:myapp.cpp:42

8.内存检查与段错误排查

8.1 段错误定位

   段错误(Segmentation Fault)是最常见的运行时错误之一,通常由以下原因引起:

原因 典型场景
解引用 NULL 指针 *ptr,但 ptr == NULL
访问已释放的内存 use-after-free
栈溢出 递归调用过深
数组越界 array[100] 但数组只有 10 个元素
写入只读内存 修改字符串常量

基本定位步骤

bash 复制代码
# 方法一:直接运行,GDB 会自动在段错误处暂停
(gdb) run
# Program received signal SIGSEGV, Segmentation fault.
# 0x000055555555517a in compute (a=0x0, b=42) at math.c:25
# 25          return *a + b;

# 方法二:通过 core dump 分析(见第七章)

8.2 内存越界检查------AddressSanitizer

   GDB配合编译器的地址消毒器(AddressSanitizer)可以自动检测内存越界:

bash 复制代码
# 编译时启用 ASan
g++ -g -O0 -fsanitize=address -fno-omit-frame-pointer -o myapp myapp.cpp

# 运行时 ASan 会自动报告越界访问
$ ./myapp
# ==12345==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000010
# READ of size 4 at 0x602000000010 thread T0
#     #0 0x55555555517a in compute math.c:25

   ASan 直接在越界那行报错,不用等到崩溃。这是排查"随机崩溃"的神器。

8.3 内存泄漏检测

bash 复制代码
# 编译时启用 LeakSanitizer(ASan 自动包含)
g++ -g -O0 -fsanitize=address -o myapp myapp.cpp

# 运行程序后自动报告泄漏
$ ./myapp
# ==12345==ERROR: LeakSanitizer: detected memory leaks
# Direct leak of 1024 byte(s) in 1 object(s) allocated from:
#     #0 0x55555555517a in malloc
#     #1 0x5555555551a0 in process process.c:42

8.4 使用Valgrind检测内存问题

   Valgrind是与GDB互补的内存检查工具,无需重新编译即可使用:

bash 复制代码
# 安装
sudo apt install valgrind

# 基本内存检查
valgrind --leak-check=full --show-leak-kinds=all ./myapp

# 配合 GDB 使用
valgrind --vgdb=yes --vgdb-error=0 ./myapp
# 然后在另一个终端
gdb ./myapp
(gdb) target remote | vgdb

8.5 GDB中的内存映射查看

bash 复制代码
# 查看进程的内存映射
(gdb) info proc mappings

# 查看指定地址所属的内存区域
(gdb) info files

# 保存内存区域到文件
(gdb) dump memory heap.dump 0x555555559000 0x55555557a000

9.反向调试------时光倒流的魔法

   反向调试允许程序"倒着运行",回到之前的执行状态。这是GDB最强大也最容易被忽视的功能之一。

9.1 启用记录与回放

bash 复制代码
# 启动程序
(gdb) start

# 开始记录执行过程
(gdb) record                    # 开始记录(软件方式)
(gdb) record btrace             # 使用硬件分支追踪(性能更好)

# 现在可以正常运行程序
(gdb) continue
(gdb) step
(gdb) next

# 反向执行
(gdb) reverse-step              # 反向单步(进入函数)
(gdb) reverse-next              # 反向单步(跳过函数)
(gdb) reverse-stepi             # 反向单条指令(进入)
(gdb) reverse-nexti             # 反向单条指令(跳过)
(gdb) reverse-continue          # 反向继续执行
(gdb) reverse-finish            # 反向运行到函数被调用处

# 停止记录
(gdb) record stop

9.2 设置执行方向

   更优雅的方式是直接改变执行方向:

bash 复制代码
(gdb) set exec-direction reverse   # 所有执行命令变成反向
(gdb) step                         # 等同于 reverse-step
(gdb) next                         # 等同于 reverse-next
(gdb) continue                     # 等同于 reverse-continue

(gdb) set exec-direction forward   # 恢复正向执行(默认)

9.3 反向调试实战场景

   场景 :你在第50行发现变量 result 的值不对,想知道它是何时被改成这个值的。

bash 复制代码
# 1. 在发现问题的位置设断点
(gdb) break main.c:50
(gdb) continue

# 2. 确认 result 值不对
(gdb) print result
$1 = -1    # 不应该是 -1

# 3. 设置 watchpoint 监视 result
(gdb) watch result

# 4. 反向运行到 result 被修改的时刻
(gdb) reverse-continue
# Hardware watchpoint 2: result
# Old value = -1
# New value = 42
# 0x00005555555551a0 in compute_result () at result.c:30
# 30      result = compute(input);    # 这里是罪魁祸首!

9.4 反向调试的限制

  • 需要目标环境支持(大多数 Linux x86/x86-64 原生支持)
  • 系统调用和设备 I/O 无法完美回退
  • 优化过的代码可能导致行为异常
  • record 有性能开销(约 2-5x 减速)
  • 多线程程序的支持有限

9.5 替代方案:rr(Record and Replay)

   Mozilla开发的 rr 调试器是更强大的反向调试工具:

bash 复制代码
# 安装
sudo apt install rr

# 记录程序执行
rr record ./myapp

# 回放并调试
rr replay

# 在 rr 中使用 GDB 的所有反向调试命令
(rr) reverse-continue
(rr) reverse-step

   rr 的优势:性能开销更小、多线程支持更好、确定性回放。

10.远程调试与gdbserver

10.1 为什么需要远程调试

   在嵌入式开发、容器化部署、跨平台调试等场景中,程序运行在远程机器上,而开发者希望在本地使用GDB调试。这时就需要 dbserver。

10.2 gdbserver 基本用法

   远程端(目标机器)

bash 复制代码
# 启动 gdbserver,监听指定端口
gdbserver :1234 ./myapp

# 附加到已运行的进程
gdbserver :1234 --attach <PID>

# 使用串口连接
gdbserver /dev/ttyS0 ./myapp

   本地端(开发机器)

bash 复制代码
# 启动 GDB 并连接到远程
gdb ./myapp
(gdb) target remote 192.168.1.100:1234

# 或一行命令
gdb -ex "target remote 192.168.1.100:1234" ./myapp

10.3 远程调试完整流程

bash 复制代码
# ========== 远程端 ==========
# 1. 将编译好的带调试信息的程序复制到远程机器
scp ./myapp user@remote:/tmp/

# 2. 启动 gdbserver
user@remote:~$ gdbserver :1234 /tmp/myapp arg1 arg2
# Process /tmp/myapp created; pid = 5678
# Listening on port 1234

# ========== 本地端 ==========
# 3. 启动 GDB 连接
gdb ./myapp
(gdb) target remote 192.168.1.100:1234
# Remote debugging using 192.168.1.100:1234

# 4. 正常使用 GDB 命令调试
(gdb) break main
(gdb) continue
(gdb) step
(gdb) print var

10.4 gdbserver 高级选项

bash 复制代码
# 多进程模式(允许 gdbserver 在后台运行)
gdbserver --multi :1234

# 指定调试信息文件搜索路径
(gdb) set sysroot /path/to/target/rootfs
(gdb) set solib-search-path /path/to/libs

# 使用 SSH 隧道增强安全性
ssh -L 1234:localhost:1234 user@remote
# 然后连接 localhost:1234
(gdb) target remote localhost:1234

11.GDB TUI图形化界面

11.1 启用 TUI模式

   GDB内置了文本用户界面(TUI),可以在终端中显示源码、汇编、寄存器等窗口:

bash 复制代码
# 方式一:启动时启用
gdb -tui ./myapp

# 方式二:在 GDB 中切换
(gdb) tui enable                # GDB 10+ 启用 TUI
(gdb) Ctrl+x Ctrl+a            # 快捷键切换 TUI

# 方式三:使用 layout 命令
(gdb) layout src                # 源码窗口
(gdb) layout asm                # 汇编窗口
(gdb) layout split              # 源码 + 汇编窗口
(gdb) layout regs               # 寄存器窗口

11.2 TUI窗口管理

bash 复制代码
# 焦点切换
Ctrl+x o                        # 在窗口间切换焦点

# 窗口操作
(gdb) tui reg general           # 显示通用寄存器
(gdb) tui reg float             # 显示浮点寄存器
(gdb) tui reg system            # 显示系统寄存器
(gdb) tui reg next              # 下一组寄存器

# 刷新窗口(显示错乱时使用)
Ctrl+l                          # 刷新屏幕

11.3 TUI 快捷键

快捷键 功能
Ctrl+x Ctrl+a 切换 TUI 模式
Ctrl+x 1 只显示一个窗口
Ctrl+x 2 切换双窗口布局
Ctrl+x o 切换焦点窗口
Ctrl+l 刷新屏幕
PageUp/PageDown 滚动源码窗口
Ctrl+p / Ctrl+n 历史命令(TUI 模式下方向键用于滚动)

11.4 CGDB------增强版TUI

CGDB 是基于GDB的增强前端,提供类似vi的操作体验:

bash 复制代码
# 安装
sudo apt install cgdb

# 使用
cgdb ./myapp

# CGDB 快捷键(vi 风格)
# ESC         - 进入 vi 模式
# i           - 进入 GDB 命令模式
# /pattern    - 搜索源码
# :quit       - 退出
# Space       - 设置/取消断点

12.VS Code 图形化调试

12.1 为什么用 VS Code 调试

   GDB功能强大,但对于大多数开发者来说,纯命令行界面看复杂的嵌套结构、追踪多线程调用栈、频繁跳转源代码,还是有不小的难度。VS Code可以解决这些问题:

  • 把复杂的GDB指令转化为可视化的断点、变量监视窗口和调用堆栈树
  • VS Code启动非常快,内存占用相对较低
  • 配合Remote-SSH插件,可以在 Windows调试运行在 Linux服务器的 C++代码

   核心原理:VS Code 本身没有调试 C++代码的能力。它只是一个前端代理:C/C++ 插件把界面操作翻译成GDB指令,VS Code 通过 Debug Adapter Protocol 跟 GDB 通信。

12.2 环境准备

bash 复制代码
# Ubuntu/Debian
sudo apt update
sudo apt install build-essential gdb cmake -y

# Fedora/RedHat
sudo dnf groupinstall "Development Tools"
sudo dnf install gdb cmake -y

VS Code 插件

插件名称 说明
C/C++ (Microsoft) 必装。提供语法高亮、代码补全、GDB 调试适配器
C/C++ Extension Pack 推荐,插件包,包含基础插件、CMake 工具、Doxygen 支持
CMake Tools 自动识别 CMakeLists.txt,一键配置、编译和调试

12.3 配置文件说明

   VS Code调试的自动化通过 .vscode 文件夹的 JSON 配置文件实现:

配置文件 用途
tasks.json 调用编译器生成可执行文件
launch.json 调用调试器运行生成的文件
c_cpp_properties.json 配置 IntelliSense,消除代码红波浪线

12.4 tasks.json 配置

json 复制代码
{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "C/C++: g++ build",
            "type": "cppbuild",
            "command": "/usr/bin/g++",
            "args": [
                "-g",
                "${file}",
                "-o",
                "${fileDirname}/${fileBasenameNoExtension}"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            }
        }
    ]
}

12.5 launch.json 配置

json 复制代码
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Debug C++ Program",
            "type": "cppdbg",
            "request": "launch",
            "program": "${fileDirname}/${fileBasenameNoExtension}",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [],
            "externalConsole": false,
            "MIMode": "gdb",
            "setupCommands": [
                {
                    "description": "为 gdb 启用整齐打印",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                }
            ],
            "preLaunchTask": "C/C++: g++ build",
            "miDebuggerPath": "/usr/bin/gdb"
        }
    ]
}

12.6 调试工具栏

快捷键 按钮 功能
F5 Continue 继续运行,直到遇到下一个断点
F10 Step Over 逐过程。跳过当前行
F11 Step Into 逐语句。钻进函数内部
Shift+F11 Step Out 跳出。回到上一层函数

12.7 调试面板信息

变量(Variables)

  • Local 显示当前作用域下的所有变量
  • 可以直接双击变量值进行修改!

监视(Watch)

  • 点击 + 号手动添加要监控的变量
  • 跨作用域持续显示(不可见时显示 not available

调用堆栈(Call Stack)

  • 显示函数调用层级
  • 点击堆栈层级,编辑器自动跳转到对应源代码位置

12.8 调试控制台

   底部"调试控制台"可以:

  • 输入变量名直接查看值
  • 输入简单 C++ 表达式计算
  • 直接跟 GDB 对话 :输入 -exec <gdb命令>
bash 复制代码
# 示例 GDB 命令
-exec x/16xb &my_struct      # 查看内存原始数据
-exec set scheduler-locking on  # 多线程调试锁定
-exec info sharedlibrary     # 查看库依赖

12.9 VS Code 调试常见问题

问题 原因 解决方案
断点变成"空心圆" 缺少调试符号/代码被优化 检查 -g-O0
变量显示 "optimized out" 开启了优化 确保 -O0,或用 volatile 声明变量
无法进入标准库源码 缺少调试版标准库 sudo apt install libstdc++6-12-dbg
调试需要 sudo 的程序 权限不足 使用 gdbserver 而非直接调试
找不到可执行文件 路径问题 使用 ${workspaceFolder} 绝对路径
std::cin 无法输入 调试控制台不支持程序输入 externalConsole: true 或用终端面板

13.信号处理与异常捕获

13.1 信号基础

   Linux信号是进程间通信和异常处理的重要机制。GDB默认会拦截大多数信号:

bash 复制代码
# 查看所有信号的处理方式
(gdb) info signals
(gdb) info handle                # 同上

# Signal        Stop  Print  Pass to program  Description
# SIGSEGV       Yes   Yes    No               Segmentation fault
# SIGINT        Yes   Yes    No               Interrupt
# SIGPIPE       No    No     Yes              Broken pipe

13.2 自定义信号处理

bash 复制代码
# handle <signal> <actions>
# stop/nostop:   收到信号时是否暂停
# print/noprint: 是否打印信号信息
# pass/nopass:   是否传递给程序

# 让 SIGPIPE 不被 GDB 拦截(常见于网络编程)
(gdb) handle SIGPIPE nostop noprint pass

# 捕获 SIGUSR1 用于调试
(gdb) handle SIGUSR1 stop print pass

# 忽略 SIGALRM(定时器信号频繁触发时)
(gdb) handle SIGALRM nostop noprint pass

13.3 主动发送信号

bash 复制代码
# 向程序发送信号
(gdb) signal SIGUSR1             # 发送信号并继续运行
(gdb) signal 10                  # 用编号发送(SIGUSR1=10)

# 注意:signal 命令会继续运行程序
# 如果只想发送信号但保持暂停,使用:
(gdb) call kill(getpid(), SIGUSR1)

13.4 C++ 异常调试

bash 复制代码
# 在异常抛出时暂停
(gdb) catch throw

# 在异常被 catch 时暂停
(gdb) catch catch

# 在特定类型的异常抛出时暂停
(gdb) catch throw std::runtime_error

# 查看当前异常
(gdb) info exceptions            # 需要 GDB 10+ 版本

14.自定义命令与 .gdbinit 配置

14.1 gdbinit 文件

   .gdbinit 是GDB的配置文件,GDB启动时自动执行其中的命令。可以放在用户主目录(全局)或项目目录(项目级)。

   全局配置 ~/.gdbinit

gdb 复制代码
# 基础设置
set print pretty on              # 美化结构体输出
set print array on               # 美化数组输出
set print array-indexes on       # 显示数组索引
set print object on              # 显示 C++ 对象的实际类型
set print vtbl on                # 显示虚函数表
set print demangle on            # C++ 名称还原
set pagination off               # 关闭分页(长输出不暂停)
set confirm off                  # 关闭确认提示

# 历史记录
set history save on
set history size 10000
set history filename ~/.gdb_history

# 安全设置(允许加载项目级 .gdbinit)
set auto-load safe-path /

   项目级配置 项目目录/.gdbinit

gdb 复制代码
# 自动设置断点
break main
break fatal_error

# 设置源码搜索路径
directory src/
directory include/

# 自定义函数
define start_debug
    break main
    run
end

define show_status
    printf "=== Debug Status ===\n"
    info threads
    printf "\n=== Current Frame ===\n"
    info locals
    printf "\n=== Backtrace ===\n"
    backtrace 5
end

   安全提示 :GDB 默认不允许自动加载项目目录的 .gdbinit,需要在全局 ~/.gdbinit 中添加 set auto-load safe-path / 或指定信任路径。

14.2 自定义命令(define)

bash 复制代码
# 定义简单命令
(gdb) define ls
Type commands for ls, one per line.
End with a line saying just "end".
>shell ls -la
>end

# 定义带参数的命令
(gdb) define memcheck
>printf "Checking memory at %s\n", $arg0
>x/16xb $arg0
>end

# 使用
(gdb) memcheck 0x7fffffffe000

14.3 Hook 命令

   Hook允许在特定命令执行前后自动插入自定义操作:

bash 复制代码
# 在每次 continue 前打印当前行号
(gdb) define hook-continue
>printf "Continuing from line %d\n", $lineno
>end

# 在每次 step 后打印变量值
(gdb) define hookpost-step
>print i
>end

# 在 backtrace 前显示线程信息
(gdb) define hook-backtrace
>info threads
>end

14.4 常用gdbinit实用配置集合

gdb 复制代码
# ===== 输出美化 =====
set print pretty on
set print array on
set print array-indexes on
set print union on
set print demangle on
set print object on
set print static-members on
set print vtbl on
set print sevenbit-strings off

# ===== 调试体验 =====
set pagination off
set confirm off
set print elements 0             # 不限制字符串/数组显示长度
set max-value-size unlimited     # 不限制变量值大小

# ===== 历史记录 =====
set history save on
set history size 100000
set history filename ~/.gdb_history

# ===== 自定义命令 =====
define reload
    kill
    file $arg0
    run
end

define regs
    info registers
end

define ctx
    printf "====== Context ======\n"
    where
    printf "\n--- Locals ---\n"
    info locals
    printf "\n--- Args ---\n"
    info args
end

document ctx
Show current debugging context: backtrace, locals and args.
end

15.GDB Python脚本编程

15.1 Python脚本基础

   GDB从7.0版本开始内嵌 Python 解释器,允许使用Python编写强大的调试脚本:

bash 复制代码
# 在 GDB 中执行 Python 代码
(gdb) python print("Hello from Python")

# 执行多行 Python 代码
(gdb) python
>import gdb
>frame = gdb.selected_frame()
>print("Current function:", frame.name())
>print("Current line:", frame.find_sal().line)
>end

# 加载 Python 脚本文件
(gdb) source my_script.py

15.2 使用GDB Python API

python 复制代码
import gdb

class PrintLocalVars(gdb.Command):
    """Print all local variables with their types."""
    
    def __init__(self):
        super().__init__("plv", gdb.COMMAND_DATA)
    
    def invoke(self, arg, from_tty):
        frame = gdb.selected_frame()
        block = frame.block()
        
        print("=" * 60)
        print(f"Function: {frame.name()}")
        print(f"File: {frame.find_sal().symtab.filename}")
        print(f"Line: {frame.find_sal().line}")
        print("=" * 60)
        
        while block is not None:
            for symbol in block:
                if symbol.is_variable or symbol.is_argument:
                    try:
                        val = symbol.value(frame)
                        print(f"  {symbol.type} {symbol.name} = {val}")
                    except gdb.error:
                        print(f"  {symbol.type} {symbol.name} = <unavailable>")
            block = block.superblock

PrintLocalVars()

   在GDB中使用:

bash 复制代码
(gdb) source print_locals.py
(gdb) plv
# ============================================================
# Function: compute_result
# File: result.c
# Line: 42
# ============================================================
#   int count = 10
#   double total = 3.14159
#   char *name = 0x5555555592a0 "hello"

15.3 Pretty Printer------美化打印

   Pretty Printer可以为自定义类型提供友好的输出格式:

python 复制代码
import gdb

class MyStringPrinter:
    """Pretty printer for MyString type."""
    
    def __init__(self, val):
        self.val = val
    
    def to_string(self):
        try:
            length = int(self.val['length_'])
            data_ptr = self.val['data_']
            return data_ptr.string('utf-8', length=length)
        except gdb.error:
            return "<invalid MyString>"
    
    def display_hint(self):
        return "string"

def my_string_lookup(val):
    if str(val.type) == 'MyString':
        return MyStringPrinter(val)
    return None

gdb.pretty_printers.append(my_string_lookup)

   使用效果:

bash 复制代码
# 不使用 pretty printer
(gdb) print my_str
$1 = {length_ = 5, data_ = 0x5555555592a0 "hello", capacity_ = 16}

# 使用 pretty printer
(gdb) print my_str
$1 = "hello"

15.4 自定义断点(Python)

python 复制代码
import gdb

class MemoryLeakBreakpoint(gdb.Breakpoint):
    """Breakpoint that tracks malloc/free calls."""
    
    allocations = {}
    
    def __init__(self):
        # 在 malloc 和 free 处设断点
        super().__init__("malloc", type=gdb.BP_BREAKPOINT)
        self.silent = True
    
    def stop(self):
        # 获取 malloc 的参数和返回值
        size = int(gdb.parse_and_eval("$rdi"))
        # 将在 finish 时捕获返回值
        print(f"malloc({size}) called from {gdb.selected_frame().older().name()}")
        return False  # 不暂停程序

MemoryLeakBreakpoint()

15.5 实用Python脚本示例

   批量打印链表

python 复制代码
import gdb

def print_linked_list(head_name):
    """遍历并打印链表的所有节点"""
    head = gdb.parse_and_eval(head_name)
    node = head
    count = 0
    
    while node != 0:
        count += 1
        print(f"Node {count}:")
        print(f"  data = {node['data']}")
        print(f"  next = {node['next']}")
        node = node['next'].dereference() if node['next'] else 0
        
        if count > 1000:
            print("... (truncated, possible cycle)")
            break
    
    print(f"Total nodes: {count}")

# 注册为 GDB 命令
class PrintLinkedList(gdb.Command):
    def __init__(self):
        super().__init__("pll", gdb.COMMAND_DATA)
    
    def invoke(self, arg, from_tty):
        print_linked_list(arg)

PrintLinkedList()

16.常用调试实战案例

16.1 案例一:定位段错误

c 复制代码
// bug_demo.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    char* name;
    int age;
} Person;

void print_person(Person* p) {
    printf("Name: %s, Age: %d\n", p->name, p->age);  // 可能崩溃
}

int main() {
    Person* people = malloc(3 * sizeof(Person));
    people[0].name = strdup("Alice");
    people[0].age = 30;
    
    free(people[0].name);
    people[0].name = NULL;  // 悬空指针
    
    print_person(&people[0]);  // 访问 NULL 指针 → 段错误
    return 0;
}
bash 复制代码
$ g++ -g -O0 -o bug_demo bug_demo.cpp
$ gdb ./bug_demo
(gdb) run
# Program received signal SIGSEGV, Segmentation fault.
# 0x00007f... in strlen () from /lib/x86_64-linux-gnu/libc.so.6

(gdb) bt
#0  0x00007f... in strlen ()
#1  0x00007f... in printf ()
#2  0x0000555555555195 in print_person (p=0x5555555592a0) at bug_demo.cpp:10
#3  0x00005555555551f4 in main () at bug_demo.cpp:22

(gdb) frame 2
(gdb) print p->name
$1 = 0x0    # name 是 NULL 指针!
(gdb) print p->age
$2 = 30

   结论p->name 为 NULL,printf%s 格式化 NULL 指针导致段错误。

16.2 案例二:调试多线程死锁

c 复制代码
// deadlock_demo.cpp
#include <pthread.h>
#include <stdio.h>

pthread_mutex_t mutex_a = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex_b = PTHREAD_MUTEX_INITIALIZER;

void* thread1_func(void* arg) {
    pthread_mutex_lock(&mutex_a);       // 先锁 A
    printf("Thread 1: locked mutex_a\n");
    sleep(1);
    pthread_mutex_lock(&mutex_b);       // 再锁 B → 等待 Thread 2 释放 B
    printf("Thread 1: locked mutex_b\n");
    pthread_mutex_unlock(&mutex_b);
    pthread_mutex_unlock(&mutex_a);
    return NULL;
}

void* thread2_func(void* arg) {
    pthread_mutex_lock(&mutex_b);       // 先锁 B
    printf("Thread 2: locked mutex_b\n");
    sleep(1);
    pthread_mutex_lock(&mutex_a);       // 再锁 A → 等待 Thread 1 释放 A
    printf("Thread 2: locked mutex_a\n");
    pthread_mutex_unlock(&mutex_a);
    pthread_mutex_unlock(&mutex_b);
    return NULL;
}
bash 复制代码
$ g++ -g -O0 -pthread -o deadlock_demo deadlock_demo.cpp
$ gdb ./deadlock_demo
(gdb) run
# 程序挂起...

# Ctrl+C 中断
(gdb) thread apply all bt

# Thread 3 (Thread 0x7f... "deadlock_demo"):
# #0 pthread_mutex_lock ...
# #1 thread2_func at deadlock_demo.cpp:22
# Thread 2 (Thread 0x7f... "deadlock_demo"):
# #0 pthread_mutex_lock ...
# #1 thread1_func at deadlock_demo.cpp:11

# Thread 1 等待 mutex_b,Thread 2 等待 mutex_a → 典型死锁

16.3 案例三:查找内存泄漏

c 复制代码
// leak_demo.cpp
#include <stdlib.h>

void process_data(int n) {
    for (int i = 0; i < n; i++) {
        char* buffer = (char*)malloc(1024);    // 每次分配 1KB
        // 忘记 free(buffer)!
    }
}

int main() {
    process_data(1000);  // 泄漏 1MB
    return 0;
}
bash 复制代码
# 方法一:使用 ASan + LSan
$ g++ -g -O0 -fsanitize=address -o leak_demo leak_demo.cpp
$ ./leak_demo
# =================================================================
# ==12345==ERROR: LeakSanitizer: detected memory leaks
# Direct leak of 1024000 byte(s) in 1000 object(s) allocated from:
#     #0 0x55555555517a in malloc (.../asan_interceptors.cc:...)
#     #1 0x000055555555519c in process_data leak_demo.cpp:5

# 方法二:使用 Valgrind
$ valgrind --leak-check=full ./leak_demo
# ==12345== 1,024,000 bytes in 1,000 blocks are definitely lost in loss record 1 of 1
# ==12345==    at 0x4C29F73: malloc (vg_replace_malloc.c:309)
# ==12345==    by 0x10898C: process_data (leak_demo.cpp:5)

16.4 案例四:条件断点优化循环调试

c 复制代码
// 需要在第 1000 次循环时暂停
for (int i = 0; i < 10000; i++) {
    if (data[i] == -1) {
        // 某个元素被错误地设为 -1
        data[i] = 0;
    }
}
bash 复制代码
# 方法一:条件断点(i == 999 时暂停)
(gdb) break process.cpp:12 if i == 999

# 方法二:忽略前 N 次命中
(gdb) break process.cpp:12
(gdb) ignore 1 999               # 忽略前 999 次

# 方法三:使用 watchpoint 监视数据变化
(gdb) watch data[500]            # 监视特定元素

16.5 案例五:调试共享库问题

bash 复制代码
# 查看已加载的共享库
(gdb) info sharedlibrary

# 设置共享库搜索路径
(gdb) set solib-search-path /opt/mylib/lib:/usr/local/lib

# 在共享库函数设断点
(gdb) break malloc                # 系统库函数
(gdb) break mylib::process        # 自定义库函数

# 查看共享库的符号表
(gdb) info functions malloc
(gdb) info variables my_global    # 查找全局变量

17.附录 A GDB命令速查表

17.1 启动与退出

命令 说明
gdb program 调试可执行文件
gdb program core 调试 core dump
gdb -p PID 附加到运行中的进程
gdb --args prog arg1 arg2 带参数启动
gdb -tui prog TUI 模式启动
quit / q 退出 GDB

17.2 运行控制

命令 缩写 说明
run r 运行程序
start --- 运行到 main
continue c 继续运行
step s 单步进入
next n 单步跳过
stepi si 指令级进入
nexti ni 指令级跳过
finish --- 运行到函数返回
until u 跳出循环
kill k 终止程序

17.3 断点

命令 说明
break func 函数断点
break file:line 行断点
break if cond 条件断点
tbreak 临时断点
rbreak regex 正则断点
watch var 写观察断点
rwatch var 读观察断点
awatch var 读写观察断点
catch throw 捕获异常
catch syscall 捕获系统调用
info break 查看断点
delete N 删除断点
disable N 禁用断点
enable N 启用断点
ignore N count 忽略 N 次
condition N expr 设置条件
commands N 绑定命令

17.4 数据查看

命令 说明
print expr 打印表达式
print/x expr 十六进制打印
display expr 持续显示
whatis var 查看类型
ptype var 详细类型
x/NFU addr 检查内存
info locals 局部变量
info args 函数参数
set var = val 修改变量

17.5 栈帧

命令 说明
backtrace / bt 调用栈
bt full 调用栈+变量
frame N 切换帧
up / down 上下移帧
info frame 帧信息

17.6 多线程

命令 说明
info threads 查看线程
thread N 切换线程
thread apply all cmd 所有线程执行
set scheduler-locking on 锁定调度

17.7 反向调试

命令 说明
record 开始记录
reverse-step 反向单步进入
reverse-next 反向单步跳过
reverse-continue 反向继续
reverse-finish 反向到调用处
set exec-direction reverse 设置反向执行

17.8 源码与汇编

命令 说明
list / l 查看源码
disassemble 查看汇编
disassemble func 函数汇编
info source 源文件信息
directory dir 添加源码路径

18.GCC编译选项速查

18.1 常用编译选项

参数 说明
-v / --version 查看 gcc 版本号
-I 目录 指定头文件目录
-c 只编译,生成 .o 文件,不进行链接
-g 包含调试信息,用于支持 gdb 调试
-g3 包含宏定义等更详细的调试信息
-On (n=0~3) 编译优化,n 越大优化得越多
-Wall 提示更多警告信息
-D<DEF> 编译时定义宏
-E 生成预处理文件
-o 指定输出文件名

18.2 库文件编译

bash 复制代码
# 静态库制作
gcc -c add.c -o add.o
ar rcs lib库名.a add.o sub.o mul.o

# 静态库使用
gcc testlib.c libmymath.a -o test

# 动态库制作
gcc -c add.c -o add.o -fPIC
gcc -shared -o lib库名.so add.o sub.o mul.o

# 动态库使用
gcc testlib.c -o a.out -lmymath -L ./lib
ldd a.out  # 查看依赖的链接库

19.总结

   GDB是一个功能极其丰富的调试工具,本文涵盖了从基础到高级的主要功能:

  1. 基础操作:启动、运行控制、源码查看------这是日常使用最频繁的部分
  2. 断点体系:普通断点、条件断点、观察断点、捕捉断点------精准控制暂停时机
  3. 数据观察:print、x、display------深入理解程序状态
  4. 栈帧分析:backtrace、frame------追踪函数调用链
  5. 多线程/多进程:thread、scheduler-locking------应对并发调试
  6. 内存问题:ASan、Valgrind 配合------排查段错误和内存泄漏
  7. Core Dump:崩溃现场分析------事后定位问题
  8. 反向调试:record、reverse-*------时光倒流追溯问题
  9. 远程调试:gdbserver------嵌入式与容器化场景
  10. 脚本编程:Python API、.gdbinit------自动化提升效率
  11. 图形化调试:VS Code 集成------可视化调试体验

   调试的终极原则:用最短的时间定位问题。掌握 GDB 的高级功能不是为了炫技,而是为了在面对复杂 bug 时拥有足够的工具储备。当 printf 调试法力不从心时,GDB 的这些技巧就是你最可靠的武器。

参考资料

相关推荐
ttkwzyttk2 天前
GDB TUI窗口管理
gdb
ttkwzyttk2 天前
GDB观察点与捕获点使用
gdb
ttkwzyttk7 天前
GDB函数调用栈管理
gdb
ttkwzyttk8 天前
GDB调试变量、内存与寄存器查看与修改
gdb
ttkwzyttk9 天前
GDB调试简介与调试配置
gdb
zzzzzz31013 天前
NVIDIA 开源 SkillSpector:AI Agent 技能安全扫描器,你的 Agent 装了个定时炸弹?
机器学习·shell·cto
ScilogyHunter16 天前
Zephyr Shell完全指南
shell·zephyr
七夜zippoe16 天前
OpenClaw 节点命令执行:远程Shell与系统操作实战
github·shell·openclaw·nodes·系统操作
Mr -老鬼19 天前
EasyClick 入门指南:Shell 命令与 ADB 完全指南
android·adb·自动化·shell·easyclick·易点云测
modelmd22 天前
GDB 摘要
gdb