关于 debugging-methodology
debugging-methodology 是一套系统化定位和修复软件缺陷的技能,适用于调查运行时错误、资源耗尽和性能回归。它强调复现问题、验证故障点证据,并以测量而非既有判断确定根因。
主要功能
- 01按步骤复现、定位并验证缺陷
- 02记录假设并逐步缩小范围
- 03在故障点测量实际资源状态
- 04识别默认标识符与哨兵值
- 05提供系统级调试工具参考
适用场景
- 01调查内存泄漏或竞态条件
- 02区分资源耗尽、碎片化与分配器问题
- 03核实错误信息中实体是否真实存在
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ laurigates/ claude-plugins --skill debugging-methodology安装后在 Claude Code 中输入/debugging-methodology确认已加载。
附带文件REFERENCE.md
详细介绍
调试方法
系统性地查找并修复 Bug。
何时使用此技能
| 在以下情况使用此技能…… | 在以下情况改用其他工具…… |
|---|---|
| 诊断正在发生的 Bug、内存泄漏、竞态或性能回退 | Bug 被吞掉的错误隐藏 → code-hidden-failures --track errors |
| 分析复现、隔离和根因 | Bug 被空结果时的静默成功隐藏 → code-hidden-failures --track degradation |
| 为系统级调查选择 strace/eBPF/perf | 根因已知后检查周边代码质量 → code-review |
| 记录假设并通过二分法定位故障 | 修复后重构有 Bug 的模块 → code-refactor |
核心原则
- 奥卡姆剃刀 - 从最简单的解释开始
- 二分查找 - 系统性地隔离问题区域
- 保留证据 - 修改前先了解状态
- 记录假设 - 跟踪尝试过但无效的方法
调试工作流
1. Understand → What is expected vs actual behavior?
2. Reproduce → Can you trigger the bug reliably?
3. Locate → Where in the code does it happen?
4. Diagnose → Why does it happen? (root cause)
5. Fix → Minimal change to resolve
6. Verify → Confirm fix works, no regressions
常见 Bug 模式
| 症状 | 可能原因 | 首先检查 |
|---|---|---|
| TypeError/null | 缺少空值检查 | 输入验证 |
| 差一错误 | 循环边界、数组索引 | 边界条件 |
| 竞态条件 | 异步时序 | Await/Promise 处理 |
| 导入错误 | 路径/模块解析 | 文件路径、导出 |
| 类型不匹配 | 传入了错误类型 | 函数签名 |
| 间歇性测试失败 | 时序、共享状态 | 测试隔离 |
在故障点诊断 — 哨兵值与资源读取
内容摘自 ~/.claude/rules/diagnose-at-the-failure-point.md,该文件的存根保留了两行门槛条件。工具或库报告故障时,两个表面特征经常会误导诊断:错误中点名的实体,以及你一开始就接受的问题表述。在故障发生的确切位置,将两者与实际情况核对,成本都很低。
1. 错误中点名的实体可能是哨兵值/默认值,而不是真实对象
错误中点名的对象具有较小 / 零 / 默认标识符时——
Memory page 0 doesn't exist、id 0、空字符串名称、index -1、
0.0.0.0、空 UUID——通常报告的是未初始化或哨兵默认值,
而非行为异常的真实实体。在推测该实体之前,先确认其标识符来自一次真正成功的操作(已完成的分配 / 查找 / 注册),而非回退路径。
典型案例(2026-07,cubecl CUDA):
couldn't find resource for that handle: Memory page 0 doesn't exist曾被一份交接说明、上游跟踪记录以及我 多次解读为内存池的回收竞态导致“page 0”损坏。实际情况是MemoryLocation::uninit() = {pool:0,page:0}: 一次分配失败使其句柄未绑定,保留为零默认值;之后每次 查找未绑定句柄时都会报告“page 0”。没有发生任何内存池回收。 证据很明确:不同“缺失”句柄的数量与分配失败次数完全相等。
关键线索通常是与上游某个数值相匹配的计数或比例 (缺失实体数量 == 失败次数)——在认定该实体真实存在之前,先查找这一线索。
2. 对于任何耗尽症状,请读取故障发生时的实际资源状态
OOM / out of memory / can't allocate / ENOSPC / too many open files /
pool exhausted 都是对应三种不同修复方式的症状——真正耗尽、碎片化或管理器/分配器错误。只需在故障发生时进行一次测量,就能区分它们:
| 症状 | 故障发生时读取 | 只有在以下情况下才算真正耗尽 |
|---|---|---|
| CUDA/GPU OOM | cuMemGetInfo(空闲、总量) | 空闲量 ≪ 请求量 |
ENOSPC | 目标文件系统上的 df | 空闲量 ≈ 0 |
EMFILE / 文件过多 | 文件描述符数量与 ulimit -n 对比 | 数量 ≈ 限制 |
| 分配器/内存池“已满” | 内存池的 in_use、reserved 与设备总量 | in_use ≈ 设备总量 |
在发生故障的调用位置加上检测并打印这些信息;不要根据几秒前采样的结果推断(峰值可能出现在两次采样之间)。在同一个案例中,失败的 malloc 调用处的 cuMemGetInfo 显示25.2 GB 中有 0.58 GB 可用——这确实是内存耗尽,并非碎片化,也不是内存池错误。这一条读数就结束了对分配器的排查:工作集确实超出了显卡容量。其他所有理论(回收竞态、游标保护、回收前同步、分辨率“调节手段”)都已被测量结果而非争论推翻。
**故障发生时的证据 > 你收到的描述 > 看似合理的机制。**措辞笃定的问题报告、维护者给出的根因,以及错误信息本身的表述,都只是需要验证的输入,而非结论。当理论与测量结果不一致时,以测量结果为准——重新诊断,而不是给理论打补丁。错误信息用可疑的整圆数/零值/默认值指名某个实体、“资源耗尽”故障的修复方案取决于耗尽原因,或继承的交接信息、上游跟踪记录已断言根因时,都很容易踩这个坑。跳过这项成本低廉的读取,相当于放弃花五秒执行 cuMemGetInfo/df/计数,却要花多轮排查一个从未发生过的机制。相关内容:agent-patterns-plugin:probe-input-integrity(由你编造的 ID)、git-plugin:git-issue-scoping 和 git-plugin:git-upstream-fix-check(不要轻信继承而来的描述)、agent-patterns-plugin:tool-result-traps(格式正确、却悄然与现实不符的输出)。
调试问题
遇到阻碍时,请问自己:
- 最近有哪些变化可能导致这个问题?
- 这个问题在所有环境中都会发生,还是只在某一个环境中发生?
- Bug 出在我的代码中,还是某个依赖中?
- 我有哪些假设可能是错的?
- 我能否编写一个最小复现?
有效的调试实践
- 有针对性地修改:提出假设,每次只改一处
- 使用合适的调试器:尽可能使用断点逐步执行代码
- 查找根因:追溯问题的源头,从源头修复
- 先复现:尝试修复之前,先创建一个最小复现
- 验证修复:确认修复解决了问题且测试通过
系统级工具(内存分析、性能分析、strace/eBPF 跟踪、网络调试)和特定语言的调试器命令,请参阅 REFERENCE.md。