python-debugpy
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChinesePython Debugger (pdb + debugpy)
Python调试器(pdb + debugpy)
Overview
概述
Three tools, picked by situation:
| Tool | When |
|---|---|
| Local, interactive, simplest. Add |
| Launch an existing script under pdb with no source edits. Useful for quick poking. |
| Remote / headless / "attach to already-running process." Talks DAP, scriptable from terminal, works for long-lived processes (gateway, daemon, PTY children). |
Start with . It's the cheapest thing that works.
breakpoint()根据场景选择以下三种工具:
| 工具 | 使用场景 |
|---|---|
| 本地调试、交互式、最简单。在源代码中添加 |
| 无需修改源代码,直接在pdb环境下启动现有脚本。适合快速排查问题。 |
| 远程/无头模式/“附加到已运行进程”。支持DAP协议,可通过终端脚本控制,适用于长期运行的进程(网关、守护进程、PTY子进程)。 |
优先使用,这是最便捷有效的方式。
breakpoint()When to Use
适用场景
- A test fails and the traceback doesn't reveal why a value is wrong
- You need to step through a function and watch a collection mutate
- A long-running process (hermes gateway, tui_gateway) misbehaves and you can't restart it
- Post-mortem: an exception fired in prod-ish code and you want to inspect locals at the crash site
- A subprocess / child (Python , PTY bridge worker) is the actual bug site
_SlashWorker
Don't use for: things / solve in under a minute, or things already reveals.
print()logging.debugpytest -vv --tb=long --showlocals- 测试失败,但回溯信息无法揭示值错误的原因
- 需要单步执行函数,观察集合的变化过程
- 长期运行的进程(Hermes网关、tui_gateway)出现异常,且无法重启
- 事后调试:生产环境代码抛出异常,需要检查崩溃现场的局部变量
- 子进程/子任务(Python 、PTY桥接工作进程)是实际的问题源头
_SlashWorker
不适用场景:使用 / 可在一分钟内解决的问题,或已能揭示的问题。
print()logging.debugpytest -vv --tb=long --showlocalspdb Quick Reference
pdb快速参考
Inside any pdb prompt ():
(Pdb)| Command | Action |
|---|---|
| help |
| next line (step over) |
| step into |
| return from current function |
| continue |
| continue until line N |
| jump to line N (same function only) |
| list source around current line / full function |
| where (stack trace) |
| move up / down in the stack |
| print args of the current function |
| print / pretty-print expression |
| auto-print expr on every stop |
| set breakpoint |
| break on function entry |
| conditional breakpoint |
| clear breakpoint N |
| one-shot breakpoint |
| execute arbitrary Python (assignments included) |
| drop into full Python REPL in current scope (Ctrl+D to exit) |
| quit |
The command is the most powerful — you can import anything, inspect complex objects, even call methods that mutate state. Locals are read-only by default; use from the prompt to mutate.
interact!x = 42(Pdb)在任意pdb提示符()下:
(Pdb)| 命令 | 操作 |
|---|---|
| 查看帮助 |
| 下一行(单步跳过) |
| 单步进入 |
| 从当前函数返回 |
| 继续执行 |
| 继续执行至第N行 |
| 跳至第N行(仅限当前函数) |
| 查看当前行附近的源代码 / 查看当前函数的完整源代码 |
| 查看调用栈(Where) |
| 在调用栈中向上/向下移动 |
| 打印当前函数的参数 |
| 打印/格式化打印表达式结果 |
| 每次暂停时自动打印表达式结果 |
| 设置断点 |
| 在函数入口处设置断点 |
| 设置条件断点 |
| 清除编号为N的断点 |
| 设置一次性断点 |
| 执行任意Python语句(包括赋值操作) |
| 进入当前作用域的完整Python REPL环境(按Ctrl+D退出) |
| 退出pdb |
interact(Pdb)!x = 42Recipe 1: Local breakpoint
方案1:本地断点
Easiest. Edit the file:
python
def compute(x, y):
result = some_helper(x)
breakpoint() # <-- drops into pdb here
return result + yRun the code normally. You land at the line with full access to locals.
breakpoint()Don't forget to remove before committing. Use or a pre-commit grep:
breakpoint()git diffbash
rg -n 'breakpoint\(\)' --type py最简单的方式。修改代码文件:
python
def compute(x, y):
result = some_helper(x)
breakpoint() # <-- 在此处进入pdb环境
return result + y正常运行代码。你会在所在行进入pdb环境,并可完全访问局部变量。
breakpoint()提交代码前别忘了移除。可以使用或预提交的grep检查:
breakpoint()git diffbash
rg -n 'breakpoint\(\)' --type pyRecipe 2: Launch a script under pdb (no source edits)
方案2:在pdb环境下启动脚本(无需修改源代码)
bash
python -m pdb path/to/script.py arg1 arg2bash
python -m pdb path/to/script.py arg1 arg2Lands at first line of script
进入脚本的第一行
(Pdb) b path/to/script.py:42
(Pdb) c
undefined(Pdb) b path/to/script.py:42
(Pdb) c
undefinedRecipe 3: Debug a pytest test
方案3:调试pytest测试用例
The hermes test runner and pytest both support this:
bash
undefinedHermes测试运行器和pytest均支持以下方式:
bash
undefinedDrop to pdb on failure (or on any raised exception):
测试失败时(或抛出任意异常时)进入pdb:
scripts/run_tests.sh tests/path/to/test_file.py::test_name --pdb
scripts/run_tests.sh tests/path/to/test_file.py::test_name --pdb
Drop to pdb at the START of the test:
在测试开始时进入pdb:
scripts/run_tests.sh tests/path/to/test_file.py::test_name --trace
scripts/run_tests.sh tests/path/to/test_file.py::test_name --trace
Show locals in tracebacks without pdb:
在回溯信息中显示局部变量,无需进入pdb:
scripts/run_tests.sh tests/path/to/test_file.py --showlocals --tb=long
Note: `scripts/run_tests.sh` uses xdist (`-n 4`) by default, and pdb does NOT work under xdist. Add `-p no:xdist` or run a single test with `-n 0`:
```bash
scripts/run_tests.sh tests/foo_test.py::test_bar --pdb -p no:xdistscripts/run_tests.sh tests/path/to/test_file.py --showlocals --tb=long
注意:`scripts/run_tests.sh`默认使用xdist(`-n 4`),而pdb在xdist环境下无法正常工作。需添加`-p no:xdist`或使用`-n 0`运行单个测试:
```bash
scripts/run_tests.sh tests/foo_test.py::test_bar --pdb -p no:xdistor
或者
source .venv/bin/activate
python -m pytest tests/foo_test.py::test_bar --pdb
This bypasses the hermetic-env guarantees — fine for debugging, but re-run under the wrapper to confirm before pushing.source .venv/bin/activate
python -m pytest tests/foo_test.py::test_bar --pdb
这种方式会绕过封闭环境的保证——调试时没问题,但推送代码前需通过脚本重新运行测试确认。Recipe 4: Post-mortem on any exception
方案4:针对任意异常的事后调试
python
import pdb, sys
try:
run_the_thing()
except Exception:
pdb.post_mortem(sys.exc_info()[2])Or wrap a whole script:
bash
python -m pdb -c continue script.pypython
import pdb, sys
try:
run_the_thing()
except Exception:
pdb.post_mortem(sys.exc_info()[2])或者为整个脚本添加包装:
bash
python -m pdb -c continue script.pyWhen it crashes, pdb catches it and you're in the frame of the exception
代码崩溃时,pdb会捕获异常并进入异常所在的栈帧
Or set a global hook in a repl/jupyter:
```python
import sys
def excepthook(etype, value, tb):
import pdb; pdb.post_mortem(tb)
sys.excepthook = excepthook
或者在REPL/Jupyter中设置全局钩子:
```python
import sys
def excepthook(etype, value, tb):
import pdb; pdb.post_mortem(tb)
sys.excepthook = excepthookRecipe 5: Remote debug with debugpy (attach to running process)
方案5:使用debugpy进行远程调试(附加到已运行进程)
For long-lived processes: Hermes gateway, tui_gateway, a daemon, a process that's already misbehaving and can't be restarted clean.
适用于长期运行的进程:Hermes网关、tui_gateway、守护进程,或已出现异常且无法正常重启的进程。
Setup
安装配置
bash
source /home/bb/hermes-agent/.venv/bin/activate
pip install debugpybash
source /home/bb/hermes-agent/.venv/bin/activate
pip install debugpyPattern A: Source-edit — process waits for debugger at launch
模式A:修改源代码——进程启动时等待调试器连接
Add near the top of the entry point (or inside the function you want to debug):
python
import debugpy
debugpy.listen(("127.0.0.1", 5678))
print("debugpy listening on 5678, waiting for client...", flush=True)
debugpy.wait_for_client()
debugpy.breakpoint() # optional: pause immediately once attachedStart the process; it blocks on .
wait_for_client()在入口文件顶部(或需要调试的函数内部)添加以下代码:
python
import debugpy
debugpy.listen(("127.0.0.1", 5678))
print("debugpy正在监听5678端口,等待客户端连接...", flush=True)
debugpy.wait_for_client()
debugpy.breakpoint() # 可选:连接成功后立即暂停启动进程;进程会在处阻塞,等待调试器连接。
wait_for_client()Pattern B: No source edit — launch with -m debugpy
-m debugpy模式B:无需修改源代码——使用-m debugpy
启动
-m debugpybash
python -m debugpy --listen 127.0.0.1:5678 --wait-for-client your_script.py arg1Equivalent for module entry:
bash
python -m debugpy --listen 127.0.0.1:5678 --wait-for-client -m your.modulebash
python -m debugpy --listen 127.0.0.1:5678 --wait-for-client your_script.py arg1针对模块入口的等效命令:
bash
python -m debugpy --listen 127.0.0.1:5678 --wait-for-client -m your.modulePattern C: Attach to an already-running process
模式C:附加到已运行的进程
Needs the PID and debugpy preinstalled in the target's environment:
bash
python -m debugpy --listen 127.0.0.1:5678 --pid <pid>需要目标进程的PID,且目标环境已预安装debugpy:
bash
python -m debugpy --listen 127.0.0.1:5678 --pid <pid>debugpy injects itself into the process. Then attach a client as below.
debugpy会注入到目标进程中。之后按以下方式连接客户端。
Some kernels/security configs block the ptrace-based injection (`/proc/sys/kernel/yama/ptrace_scope`). Fix with:
```bash
echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
部分内核/安全配置会阻止基于ptrace的注入(`/proc/sys/kernel/yama/ptrace_scope`)。可通过以下命令修复:
```bash
echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scopeConnecting a client from the terminal
从终端连接客户端
The easiest terminal-side DAP client is VS Code CLI or a small script. From inside Hermes you have two practical options:
Option 1: 's own CLI REPL — not an official feature, but a tiny DAP client script:
debugpypython
undefined最简单的终端端DAP客户端是VS Code CLI或小型脚本。在Hermes环境中,你有两种实用选择:
选项1:自带的CLI REPL——这并非官方功能,但可通过一个小型DAP客户端脚本实现:
debugpypython
undefined/tmp/dap_client.py
/tmp/dap_client.py
import socket, json, itertools, time, sys
HOST, PORT = "127.0.0.1", 5678
s = socket.create_connection((HOST, PORT))
seq = itertools.count(1)
def send(msg):
msg["seq"] = next(seq)
body = json.dumps(msg).encode()
s.sendall(f"Content-Length: {len(body)}\r\n\r\n".encode() + body)
def recv():
header = b""
while b"\r\n\r\n" not in header:
header += s.recv(1)
length = int(header.decode().split("Content-Length:")[1].split("\r\n")[0].strip())
body = b""
while len(body) < length:
body += s.recv(length - len(body))
return json.loads(body)
send({"type": "request", "command": "initialize", "arguments": {"adapterID": "python"}})
print(recv())
send({"type": "request", "command": "attach", "arguments": {}})
print(recv())
send({"type": "request", "command": "setBreakpoints",
"arguments": {"source": {"path": sys.argv[1]},
"breakpoints": [{"line": int(sys.argv[2])}]}})
print(recv())
send({"type": "request", "command": "configurationDone"})
import socket, json, itertools, time, sys
HOST, PORT = "127.0.0.1", 5678
s = socket.create_connection((HOST, PORT))
seq = itertools.count(1)
def send(msg):
msg["seq"] = next(seq)
body = json.dumps(msg).encode()
s.sendall(f"Content-Length: {len(body)}\r\n\r\n".encode() + body)
def recv():
header = b""
while b"\r\n\r\n" not in header:
header += s.recv(1)
length = int(header.decode().split("Content-Length:")[1].split("\r\n")[0].strip())
body = b""
while len(body) < length:
body += s.recv(length - len(body))
return json.loads(body)
send({"type": "request", "command": "initialize", "arguments": {"adapterID": "python"}})
print(recv())
send({"type": "request", "command": "attach", "arguments": {}})
print(recv())
send({"type": "request", "command": "setBreakpoints",
"arguments": {"source": {"path": sys.argv[1]},
"breakpoints": [{"line": int(sys.argv[2])}]}})
print(recv())
send({"type": "request", "command": "configurationDone"})
... loop reading events and sending continue/stepIn/etc.
... 循环读取事件并发送continue/stepIn等命令
This is fine for one-off automation but painful as an interactive UX.
**Option 2: Attach from VS Code / Cursor / Zed** — if the user has one open, they can add a `launch.json`:
```json
{
"name": "Attach to Hermes",
"type": "debugpy",
"request": "attach",
"connect": { "host": "127.0.0.1", "port": 5678 },
"justMyCode": false,
"pathMappings": [
{ "localRoot": "${workspaceFolder}", "remoteRoot": "/home/bb/hermes-agent" }
]
}Option 3: Ditch DAP, use — usually what you actually want from a terminal agent:
remote-pdbbash
pip install remote-pdbIn your code:
python
from remote_pdb import set_trace
set_trace(host="127.0.0.1", port=4444) # blocks until connectionThen from the terminal:
bash
nc 127.0.0.1 4444
这种方式适合一次性自动化操作,但作为交互式体验并不友好。
**选项2:从VS Code / Cursor / Zed连接**——如果已打开这些编辑器,可添加`launch.json`配置:
```json
{
"name": "Attach to Hermes",
"type": "debugpy",
"request": "attach",
"connect": { "host": "127.0.0.1", "port": 5678 },
"justMyCode": false,
"pathMappings": [
{ "localRoot": "${workspaceFolder}", "remoteRoot": "/home/bb/hermes-agent" }
]
}选项3:放弃DAP,使用——通常这是终端环境下更实用的选择:
remote-pdbbash
pip install remote-pdb在代码中添加:
python
from remote_pdb import set_trace
set_trace(host="127.0.0.1", port=4444) # 阻塞直到连接建立然后在终端中执行:
bash
nc 127.0.0.1 4444You get a (Pdb) prompt exactly as if debugging locally.
你会获得一个与本地调试完全相同的(Pdb)提示符。
`remote-pdb` is the cleanest agent-friendly choice when `debugpy`'s DAP protocol is overkill. Use `debugpy` only when you actually need IDE integration.
当`debugpy`的DAP协议过于复杂时,`remote-pdb`是终端环境下最简洁的选择。仅当需要IDE集成时才使用`debugpy`。Debugging Hermes-specific Processes
调试Hermes特定进程
Tests
测试用例
See Recipe 3. Always add or run single tests without xdist.
-p no:xdist参考方案3。务必添加或在无xdist的情况下运行单个测试。
-p no:xdistrun_agent.py
/ CLI — one-shot
run_agent.pyrun_agent.py
/ CLI —— 一次性进程
run_agent.pyEasiest: add near the suspect line, then run normally. Control returns to your terminal at the pause point.
breakpoint()hermes最简单的方式:在可疑代码行附近添加,然后正常运行。暂停时控制权会返回至终端。
breakpoint()hermestui_gateway
subprocess (spawned by hermes --tui
)
tui_gatewayhermes --tuitui_gateway
子进程(由hermes --tui
启动)
tui_gatewayhermes --tuiThe gateway runs as a child of the Node TUI. Options:
A. Source-edit the gateway:
python
undefined网关作为Node TUI的子进程运行。可选方案:
A. 修改网关源代码:
python
undefinedtui_gateway/server.py near the top of serve()
tui_gateway/server.py中serve()函数的顶部
import debugpy
debugpy.listen(("127.0.0.1", 5678))
debugpy.wait_for_client()
Start `hermes --tui`. The TUI will appear frozen (its backend is waiting). Attach a client; execution resumes when you `continue`.
**B. Use `remote-pdb` at a specific handler:**
```python
from remote_pdb import set_trace
set_trace(host="127.0.0.1", port=4444) # in the RPC handler you want to trapTrigger the matching slash command from the TUI, then in another terminal.
nc 127.0.0.1 4444import debugpy
debugpy.listen(("127.0.0.1", 5678))
debugpy.wait_for_client()
启动`hermes --tui`。TUI会显示为冻结状态(其后端正在等待连接)。连接客户端后,执行`continue`即可恢复运行。
**B. 在特定处理器中使用`remote-pdb`:**
```python
from remote_pdb import set_trace
set_trace(host="127.0.0.1", port=4444) # 在需要捕获的RPC处理器中添加从TUI触发对应的slash命令,然后在另一个终端中执行。
nc 127.0.0.1 4444_SlashWorker
subprocess
_SlashWorker_SlashWorker
子进程
_SlashWorkerSame pattern — with inside the worker's path. The worker is persistent across slash commands, so the first trigger blocks until you connect; subsequent slash commands pass through normally unless you re-arm.
remote-pdbset_trace()exec采用相同模式——在工作进程的路径中添加的。工作进程会在slash命令之间保持持久化,因此第一次触发时会阻塞直到连接建立;后续slash命令会正常执行,除非重新设置断点。
execremote-pdbset_trace()Gateway (gateway/run.py
)
gateway/run.py网关(gateway/run.py
)
gateway/run.pyLong-lived. Use at a handler, or with if you're restarting the gateway anyway.
remote-pdbdebugpy--wait-for-client长期运行的进程。可在处理器中使用,或如果需要重启网关,可使用带参数的。
remote-pdb--wait-for-clientdebugpyCommon Pitfalls
常见陷阱
-
pdb under pytest-xdist silently does nothing. You won't see the prompt, the test just hangs. Always useor
-p no:xdist.-n 0 -
in CI / non-TTY contexts hangs the process. Safe locally; never commit it. Add a pre-commit grep as a safety net.
breakpoint() -
disables all
PYTHONBREAKPOINT=0calls. Check the env if your breakpoint isn't hitting:breakpoint()bashecho $PYTHONBREAKPOINT -
blocks only if you also call
debugpy.listen. Without it, execution continues and your first breakpoint may fire before the client is attached.wait_for_client() -
Attach to PID fails on hardened kernels.(Ubuntu default) allows only same-user ptrace of child processes. Workaround:
ptrace_scope=1(needs root) or launch underecho 0 > /proc/sys/kernel/yama/ptrace_scopefrom the start.debugpy -
Threads.only debugs the current thread. For multithreaded code, use
pdb(thread-aware DAP) or setdebugpyper thread.threading.settrace() -
asyncio.works in coroutines but
pdbinside pdb requires Python 3.13+ orawaitfromawaitmode on older versions. For 3.11/3.12, useinteracttricks orasyncio.run_coroutine_threadsafe-based awaits via!stmt.asyncio.ensure_future -
strips credentials and sets
scripts/run_tests.sh. If your bug depends on user config or real API keys, it won't reproduce under the wrapper. Debug with rawHOME=<tmpdir>first to repro, then re-confirm under the wrapper.pytest -
Forking / multiprocessing. pdb does not follow forks. Each child needs its ownor
breakpoint(). For Hermes subagents, debug one process at a time.set_trace()
-
pdb在pytest-xdist环境下会静默失效。你看不到提示符,测试只会挂起。务必使用或
-p no:xdist。-n 0 -
在CI/非TTY环境中使用会导致进程挂起。本地使用安全;绝对不要提交包含
breakpoint()的代码。可添加预提交的grep作为安全措施。breakpoint() -
****会禁用所有
PYTHONBREAKPOINT=0调用。如果断点未触发,请检查环境变量:breakpoint()bashecho $PYTHONBREAKPOINT -
仅在调用
debugpy.listen时才会阻塞。如果不调用该函数,代码会继续执行,你的第一个断点可能在客户端连接前就已触发。wait_for_client() -
在加固的内核上附加到PID会失败。(Ubuntu默认设置)仅允许同一用户跟踪其子进程。解决方法:
ptrace_scope=1(需要root权限),或从一开始就使用echo 0 > /proc/sys/kernel/yama/ptrace_scope启动进程。debugpy -
线程。仅调试当前线程。对于多线程代码,使用
pdb(支持线程的DAP)或为每个线程设置debugpy。threading.settrace() -
asyncio。可在协程中工作,但在pdb中使用
pdb需要Python 3.13+或在旧版本中从await模式执行interact。对于3.11/3.12版本,可使用await技巧或通过asyncio.run_coroutine_threadsafe语法结合!stmt实现asyncio.ensure_future。await -
会清除凭证并设置
scripts/run_tests.sh。如果你的问题依赖于用户配置或真实API密钥,在脚本环境下无法复现。先使用原生HOME=<tmpdir>调试复现问题,再通过脚本重新运行确认。pytest -
fork/多进程。pdb不会跟随fork进程。每个子进程需要单独设置或
breakpoint()。对于Hermes子代理,一次仅调试一个进程。set_trace()
Verification Checklist
验证检查清单
- After , confirm:
pip install debugpypython -c "import debugpy; print(debugpy.__version__)" - For remote debug, confirm the port is actually listening:
ss -tlnp | grep 5678 - First breakpoint actually hits (if it doesn't, you likely have , you're under xdist, or execution finished before attach)
PYTHONBREAKPOINT=0 - /
whereshows the expected call stackw - Post-debug cleanup: no stray /
breakpoint()in committed codeset_trace()bashrg -n 'breakpoint\(\)|set_trace\(|debugpy\.listen' --type py
- 安装debugpy后,确认版本:
python -c "import debugpy; print(debugpy.__version__)" - 远程调试时,确认端口正在监听:
ss -tlnp | grep 5678 - 第一个断点确实触发(如果未触发,可能是、处于xdist环境,或在连接前代码已执行完毕)
PYTHONBREAKPOINT=0 - /
where显示预期的调用栈w - 调试完成后清理:提交的代码中无残留的/
breakpoint()set_trace()bashrg -n 'breakpoint\(\)|set_trace\(|debugpy\.listen' --type py
One-Shot Recipes
快速解决方案
"Why is this dict missing a key?"
python
undefined“为什么这个字典缺少某个键?”
python
undefinedadd above the KeyError site
在KeyError出现位置上方添加
breakpoint()
breakpoint()
then in pdb:
然后在pdb中执行:
(Pdb) pp d
(Pdb) pp list(d.keys())
(Pdb) w # how did we get here
**"This test passes in isolation but fails in the suite."**
```bash
scripts/run_tests.sh tests/the_test.py --pdb -p no:xdist(Pdb) pp d
(Pdb) pp list(d.keys())
(Pdb) w # 查看调用路径
**“这个测试单独运行通过,但在测试套件中失败。”**
```bash
scripts/run_tests.sh tests/the_test.py --pdb -p no:xdistBut if it only fails WITH other tests:
如果仅在与其他测试一起运行时失败:
source .venv/bin/activate
python -m pytest tests/ -x --pdb -p no:xdist
source .venv/bin/activate
python -m pytest tests/ -x --pdb -p no:xdist
Now it pdb-traps at the exact failing test after state accumulated.
现在pdb会在状态累积后,在第一个失败的测试处触发。
**"My async handler deadlocks."**
```python
**“我的异步处理器死锁了。”**
```pythonAdd at handler entry
在处理器入口添加
import remote_pdb; remote_pdb.set_trace(host="127.0.0.1", port=4444)
Trigger the handler. `nc 127.0.0.1 4444`, then `w` to see the suspended frame, `!import asyncio; asyncio.all_tasks()` to see what else is pending.
**"Post-mortem on a crash in an Ink child process / subprocess."**
```bash
PYTHONFAULTHANDLER=1 python -m pdb -c continue path/to/entrypoint.pyimport remote_pdb; remote_pdb.set_trace(host="127.0.0.1", port=4444)
触发处理器。执行`nc 127.0.0.1 4444`,然后使用`w`查看挂起的栈帧,使用`!import asyncio; asyncio.all_tasks()`查看其他待处理任务。
**“对Ink子进程/子进程的崩溃进行事后调试。”**
```bash
PYTHONFAULTHANDLER=1 python -m pdb -c continue path/to/entrypoint.pyOn crash, pdb lands at the frame of the exception with full locals
崩溃时,pdb会进入异常所在的栈帧,并显示完整的局部变量
undefinedundefined