mcpField
D
debugging-code

debugging-code

github.com/almogbaku开发335575 次安装28 周前更新
代码仓库

关于 debugging-code

面向排查崩溃、错误输出和异常执行路径的交互式调试技能。它通过 dap 检查运行中的进程,无需重启即可查看状态。

主要功能

  1. 01查看实时变量与调用栈
  2. 02逐行执行与设置断点
  3. 03条件断点与异常断点
  4. 04在运行进程中求值表达式
  5. 05支持提供 DAP 后端的语言

适用场景

  1. 01追踪错误值的来源
  2. 02排查位置未知的异常
  3. 03检查运行中或远程进程

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/almogbaku/debug-skill --skill debugging-code

安装后在 Claude Code 中输入/debugging-code确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
advanced-techniques.md
installing-debuggers.md
scripts/
install-dap.sh

详细介绍

交互式调试器

当程序崩溃、输出错误,或者你需要确切了解执行过程是如何到达某个状态时使用——如果增加更多打印语句再运行一次,也无法足够快地找到答案。

你可以随时暂停正在运行的程序,在那个确切时刻查看实时变量值和调用栈,逐行向前执行或跳转到下一个断点,还可以针对实时进程计算任意表达式——所有这些都无需重启。

设置

此技能使用 dap,这是一个 CLI 工具,它会启动后台守护进程,通过 DAP Protocol 与调试器交互并维护调试器状态,因此你只需通过多次调用与其交互即可。

如果未安装 dap(检查方法:command -v dap),现在就安装。 继续安装前,请先询问用户并通知用户。

通过 Homebrew 安装(macOS)

brew install AlmogBaku/tap/dap

安装脚本:

bash scripts/install-dap.sh

从源代码安装:

go install github.com/AlmogBaku/debug-skill/cmd/dap@latest

此工具是开源的,可在 GitHub 上获取,由维护者按照最佳实践维护。

原生支持 Python、Go、Node.js/TypeScript、Rust、C/C++,以及任何其他支持 DAP 的语言。

如果缺少调试器后端或无法启动,请参阅 references/installing-debuggers.md

查看所有命令和标志:dap --help 或 dap <cmd> --help。

启动会话

dap debug <file> 会在调试器下启动程序。后端会根据文件扩展名自动检测。

根据你掌握的信息选择启动策略:

  • 已有假设 — 在你认为存在问题的位置设置断点:dap debug script.py --break script.py:42
  • 条件断点 — 仅在满足条件时暂停:dap debug script.py --break "script.py:42:x > 5"(带条件的断点说明务必加引号)
  • 多文件应用 — 在多个模块中设置断点:--break src/api/routes.py:55 --break src/models/user.py:30
  • 没有假设,程序较小 — 从入口开始执行:dap debug script.py --stop-on-entry(大型项目中请避免这样做——启动代码会产生大量干扰;应改用断点逐步缩小范围)
  • 异常,位置未知 — dap debug script.py --break-on-exception raised(Python)/ all(Go/JS)
  • 远程进程 — dap debug --attach host:port --backend <name>
  • 进程已在运行(服务器卡住、线上问题) — 无需重启即可附加: dap debug --pid <PID> --backend <name>

    macOS + Go 注意事项: dlv --pid 需要禁用 SIP(csrutil disable)。 建议直接在调试器下启动程序,或附加到远程调试器!

会话隔离: --session <name> 可避免并发的 Agent 相互干扰。 提示:如果可以,建议使用你的会话 ID(${CLAUDE_SESSION_ID})。

运行 dap debug --help 查看所有标志、后端和示例。

调试思路

当单独阅读源代码无法验证根本原因时,就该使用调试器了。 调试器可以让你观察实际发生的情况:真实的值、实际的路径和实际的状态。 当它们偏离预期时,你就找到了问题所在。

两次碰壁,就重新思考。 如果两个假设都在同一位置失败,说明你的思维模型有误。 重新阅读代码,构建一个截然不同的理论,并设置不同的断点。

逐步升级。 先使用 dap eval 验证一个简单假设。使用条件断点过滤干扰。只有在需要交互式控制时,才改用完整断点并逐步执行。

模拟用户操作流程。 如果你在调试用户操作流程,就沿着预期的代码执行路径设置断点。 如果你原本以为会调用 compute(),但它始终没有被调用,那么问题就在调用方——而不在 compute(),而在本应调用它的代码。

设置断点,而不是打印。 当你想打印某些内容时,改为设置断点。

了解当前状态

每次执行 dap 命令时,都会自动返回完整上下文:当前位置、源代码、局部变量、调用栈和输出。每次暂停时,问问自己:

  • 局部变量的值是否符合预期?
  • 调用栈显示的代码路径是否符合预期?
  • 到目前为止的输出是否揭示了任何意外情况?

沿调用栈向上追查因果关系。 如果第 0 帧中的值不对,请检查 dap eval "<expr>" --frame 1,看看调用方传入了什么。继续向上查看(--frame 2、--frame 3),直到找到该值首次出错的帧——那就是 Bug 的根源,而不是表象。

暂停时的输出示例:

Stopped at compute() · script.py:41
  39:   def compute(items):
  40:       result = None
> 41:       return result
Locals: items=[]  result=None
Stack:  main [script.py:10] → compute [script.py:41]
Output: (none)

如果程序在命中断点前就退出了:

Program terminated · Exit code: 1

→ 将断点设得更早,或使用 --stop-on-entry 重新启动。

构建假设

设置断点前,先想一想:“我认为 Bug 出在 X,因为 Y。” 好的假设可以证伪——下一次观察就能证实或推翻它。还没有假设?设置两个断点进行二分,缩小搜索范围,或参见上面的起步策略。

策略性地设置断点

  • 设置在问题开始的地方,而不是问题显现的地方
  • 第 80 行出现异常?根因在更前面——从更早的位置开始
  • 不确定?进行二分:--break f:20 --break f:60——检查两段中间的状态是否出错,缩小搜索范围

断点设置位置:

  • 边界——数据跨越格式、表示形式或模块边界的位置;此处的状态最干净
  • 状态转换——赋值或修改损坏值的那一行
  • 错误分支——输入导致错误路径的条件
  • 反模式——不要在库代码内部设置断点;应在调用处设置。不要在紧密循环中使用无条件断点——请使用条件断点。
在会话中管理断点

随着了解加深,可在可疑代码中更深的位置添加断点,并移除已经完成任务的断点——逐步缩小范围,无需重新启动:

dap continue --break app.py:50              # add breakpoint deeper, then continue
dap continue --remove-break app.py:20       # drop a breakpoint you're done with
dap break add app.py:42 app.py:60           # add multiple breakpoints at once
dap break list                              # see what's set
dap break clear                             # start fresh

如果断点位于无效行,或适配器调整了断点位置,dap 会在输出中发出警告。

条件断点

仅在条件为真时暂停——这对循环、热点路径和特定输入值至关重要。 语法:"file:line:condition"(务必加引号)。

dap debug app.py --break "app.py:42:i == 100"            # skip 99 iterations, stop on the one that matters
dap debug app.py --break "app.py:30:user_id == 123"      # reproduce a user-specific bug
dap continue --break "app.py:50:len(items) == 0"         # catch the empty-list case mid-session
不变量断点

将条件断点用作运行时断言——一旦出错就立即暂停:

dap debug app.py --break "bank.py:68:balance < 0"          # catch the overdraft
dap debug app.py --break "pipe.py:30:type(val) != int"     # type violation

跟踪执行过程

每次暂停时,根据你的怀疑选择如何继续:

如果你连续单步执行超过 3 次,就应该设置断点,而不是继续单步。

dap step                         # step over — trust this call, advance to next line
dap step in                      # step into — suspect what's inside this function
dap step out                     # step out — you're in the wrong place, return to caller
dap continue                     # jump to next breakpoint
dap continue --to file:line      # run to line (temp breakpoint, auto-removed)
dap context                      # re-inspect current state without stepping
dap output                       # drain buffered stdout/stderr without full context
dap inspect <var> --depth N      # expand nested/complex objects
dap pause                        # interrupt a running/hanging program
dap restart                      # restart with same args and breakpoints
dap threads                      # list all threads
dap thread <id>                  # switch thread context

每次暂停都会显示当前的 file:line,让你随时知道自己所处的位置。

使用 dap eval "<expr>" 检查实时状态,无需单步执行:

dap eval "len(items)"
dap eval "user.profile.settings"
dap eval "expected == actual"       # test hypothesis on live state
dap eval "self.config" --frame 1    # frame 1 = caller (may be a different file)

避免使用会调用有副作用方法的 eval 表达式——它们会修改程序状态,可能破坏调试会话。除非你有意测试修复方案,否则只进行只读访问。

跳转到后续位置

如果你想快速查看特定行,又不想设置永久断点,请使用 dap continue --to file:line。这是一次性的断点——只暂停一次,随后便消失。适用于“我只想看看第 50 行的 x 是什么样”,无需管理断点的整个生命周期。

高级场景

有关高级场景——卡住、并发 Bug、深层嵌套状态、循环二分—— 请参阅 ${CLAUDE_SKILL_DIR}/references/advanced-techniques.md。

演练

Bug:compute() 返回 None

Hypothesis: result not assigned before return
→ dap debug script.py --break script.py:41
  Locals: result=None, items=[]   ← wrong, and input is also empty

New hypothesis: caller passing empty list
→ dap eval "items" --frame 1      → []   ← confirmed
→ dap step out                    → caller at line 10, no guard for empty input
→ dap continue --break script.py:8 --remove-break script.py:41
  ← narrowing: add breakpoint at data source, drop the one we're done with
  Stopped at main():8, items loaded from config as []

Root cause: missing guard. Fix → dap stop.

没有假设(出现异常,位置未知):

Exception: TypeError, location unknown
→ dap debug script.py --break-on-exception raised
  Stopped at compute():41, items=None
Root cause: None passed where list expected.

验证修复

在 Bug 处暂停时,使用 eval 将你提出的修复表达式应用于实时状态进行测试。如果它在 eval 中有效,在代码中也会有效。然后编辑代码并执行 dap restart,端到端验证修复。

应用修复后,重新运行相同场景进行验证。dap restart 会使用相同的参数和断点重新运行——形成快速反馈循环。在你于发现 Bug 时的同一断点处观察到正确行为之前,不要相信修复已生效。

清理

dap 会话通常会在程序退出或空闲超时后自动终止。 如果应用未正常关闭(例如,你在调试时强制终止了它),可以手动终止会话:dap stop。

评论

支持 Markdown