内存调试技术:断点调试、内存断点、条件断点、watch point、core dump 分析

内存问题,是 C++ 开发者的老朋友了。段错误、内存泄漏、野指针…… 每个都够你喝一壶的。我做了十几年 C++,可以说一半的加班时间都花在了跟内存 bug 斗智斗勇上。

调试内存问题,光靠 printf 大法肯定不行。你需要一套趁手的兵器。今天我就把压箱底的内存调试技术拿出来,跟你好好聊聊。

断点调试:最基础的武器

断点(Breakpoint)是调试器的基本功。说白了,就是让程序在指定行停下来,让你看看现场。

我个人习惯在 GDB 里这么用:

(gdb) break main.cpp:42
(gdb) b MyClass::MyMethod
(gdb) b 88 if ptr == nullptr

嗯,这里要注意:断点打在函数入口时,参数还没初始化。如果你想看参数值,得用 breakcommands 组合拳。

小技巧: 在大型项目里,我习惯先给关键函数加断点,然后逐步缩小范围。别一上来就在循环体里打断点,那会点到手抽筋。

内存断点:抓野指针的利器

内存断点(Memory Breakpoint / Watchpoint)是专门对付内存问题的。它监视的是内存地址,不是代码行。

我在项目中遇到过最典型的一个场景:某个对象被意外释放了,但还有人在用它。用普通断点根本抓不到,因为崩溃的地方离 bug 源头十万八千里。

这时候就该内存断点出场了:

(gdb) watch *0x7fffffffe000
(gdb) rwatch *0x7fffffffe000
(gdb) awatch *0x7fffffffe000
  • watch:监视写入操作
  • rwatch:监视读取操作
  • awatch:读写都监视
注意: 硬件断点数量有限(通常 4 个)。软件实现的 watchpoint 会拖慢程序,慎用。

条件断点:精准打击

条件断点就是带条件的断点。程序只有在条件满足时才会停下来。

我曾经调试过一个内存池,它在第 10000 次分配时出问题。要是没条件断点,我得手动按 F5 一万次…… 想想都可怕。

(gdb) break allocate if count == 10000
(gdb) condition 1 ptr == 0xdeadbeef

条件断点还能跟内存断点结合:

(gdb) watch my_array[1024] if index > 1000

这样只有当 index 超过 1000 时,才监视 my_array[1024] 的变化。效率高多了。

Watch Point:盯住变量的变化

Watch Point 是内存断点的变种。它监视的是变量,不是地址。调试器会自动帮你处理地址变化。

在 Visual Studio 里,你可以右键变量 -> "添加监视"。在 GDB 里:

(gdb) watch my_var
Hardware watchpoint 1: my_var

你想想看,当你在调试一个复杂的链表操作时,某个节点的 next 指针莫名其妙变了。用 Watch Point 盯着它,谁改的立马现形。

类型 监视对象 适用场景
普通断点 代码行 控制流程
内存断点 内存地址 内存越界、野指针
条件断点 代码行 + 条件 循环、高频调用
Watch Point 变量 变量值意外变化

Core Dump 分析:事后诸葛亮

Core Dump 是程序崩溃时留下的"尸体"。它包含了程序崩溃那一刻的内存快照、寄存器状态、调用栈等信息。

我建议所有线上服务都开启 Core Dump:

# 设置 core 文件大小不限
ulimit -c unlimited

# 设置 core 文件命名格式
echo "/tmp/core.%e.%p" > /proc/sys/kernel/core_pattern

分析 Core Dump 的经典流程:

  1. 用 GDB 加载 core 文件:gdb ./my_program core.12345
  2. 看崩溃位置:bt(backtrace)
  3. 看局部变量:info locals
  4. 看寄存器:info registers
  5. 看内存:x/10gx $rsp
核心经验: 分析 Core Dump 时,别只看崩溃的那一行。往上翻几层调用栈,往往能找到真正的元凶。我曾经遇到一个崩溃在 std::string 析构函数里,查了半天才发现是前一个函数把栈给踩了。

知识体系总览

下面这张图,把今天讲的内存调试技术串起来了:

内存调试技术体系 断点调试 内存断点 条件断点 Watch Point 核心操作 break main.cpp:42 b MyClass::Method info locals / bt 监视内存地址 watch *addr rwatch / awatch 硬件断点有限 条件触发 break if count==N condition 1 expr 精准定位 监视变量变化 watch my_var 自动跟踪地址 谁改的现形 Core Dump 分析 ulimit -c unlimited gdb prog core bt / info registers

实战经验总结

调试内存问题,我总结了一套心法:

  • 先复现,再定位:不能稳定复现的 bug,先别急着上调试器。加日志、加断言,把现场固定下来。
  • 从外到内:先看 Core Dump,再看调用栈,最后才上断点。别一上来就单步执行。
  • 怀疑一切:内存问题往往不是你以为的地方。我曾经花了一整天查一个空指针,最后发现是另一个线程把内存给释放了。
  • 善用工具:Valgrind、AddressSanitizer、GDB 的 TUI 模式…… 这些工具能帮你省下大量时间。
避坑指南: 我曾经在线上环境直接挂 GDB 调试,结果把服务给 hang 住了。记住,线上环境优先用 Core Dump 分析,别直接 attach 调试器。

调试内存问题,说白了就是跟计算机玩侦探游戏。你掌握的工具越多,破案的速度就越快。今天讲的这些技术,每一个我都实打实地在项目里用过。希望它们也能帮你少加几天班。

公众号:蓝海资料掘金营,微信deep3321