debugging - 通过 GDB 调试 CPython 时在 Python 源代码中设置断点的最佳方法
问题描述
我使用GDB来了解CPython如何执行test.py
源文件,并且我想在CPython开始执行我感兴趣的操作码时停止它。
操作系统: Ubuntu 18.04.2 LTS
调试器: GNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-git
第一个问题- 许多 CPython.py
自己的文件在test.py
轮到我之前执行,所以我不能只是中断_PyEval_EvalFrameDefault
- 它们有很多,所以我应该将我的文件与其他文件区分开来。
第二个问题——我不能设置像“当文件名等于test.py ”这样的条件,因为文件名不是一个简单的C
字符串,它是CPython的Unicode对象,所以标准的GDB字符串函数不能用于比较。
此刻,我做了下一个技巧来在所需的test.py
源代码行中断执行:
例如,我有源文件:
x = ['a', 'b', 'c']
# I want to set the breakpoint at this line.
for e in x:
print(e)
我将二进制左移运算符添加到代码中:
x = ['a', 'b', 'c']
# Added for breakpoint
a = 12
b = 2 << a
for e in x:
print(e)
然后,通过这个GDB命令跟踪文件BINARY_LSHIFT
中的操作码执行:Python/ceval.c
break ceval.c:1327
我选择了BINARY_LSHIFT
操作码,因为它在代码中很少使用。因此,我可以快速到达文件的所需部分——它在我之前执行的所有其他模块.py
中发生一次。.py
test.py
我看起来更直接的方式做同样的事情,所以 问题:
- 我能抓住
test.py
开始执行的那一刻吗?我应该提一下,test.py
文件名出现在不同阶段:解析、编译、执行。因此,可以在任何阶段中断CPython执行也将是一件好事。 - 我可以指定
test.py
要中断的行吗?文件很容易.c
,但文件不容易.py
。
解决方案
我的想法是使用 C 扩展,以便在 python 脚本(类似于pdb.set_trace()
或breakpoint()
自 Python3.7 之后)中设置 C 断点,我将其称为cbreakpoint
.
考虑以下 python 脚本:
#example.py
from cbreakpoint import cbreakpoint
cbreakpoint(breakpoint_id=1)
print("hello")
cbreakpoint(breakpoint_id=2)
它可以在 gdb 中按如下方式使用:
>>> gdb --args python example.py
[gdb] b cbreakpoint
[gdb] run
现在,调试器将停在cbreakpoint(breakpoint_id=1)
和cbreakpoint(breakpoint_id=2)
。
这是概念证明,用 Cython 编写,以避免其他需要的样板代码:
#cbreakpoint.pyx
cdef extern from *:
"""
long long last_breakpoint_id = -1;
void cbreakpoint(long long breakpoint_id){
last_breakpoint_id = breakpoint_id;
}
"""
void c_cbreakpoint "cbreakpoint"(long long breakpoint_id)
def cbreakpoint(breakpoint_id = 0):
c_cbreakpoint(breakpoint_id)
可以通过以下方式就地构建:
cythonize -i cbreakpoint.pyx
如果没有安装 Cython,我已经在github上上传了一个不依赖于 Cython 的版本(这篇文章的代码太多)。
给定 ,也可以有条件地中断breakpoint_id
,即:
>>> gdb --args python example.py
[gdb] break src/cbreakpoint.c:595 if breakpoint_id == 2
[gdb] run
hello
仅在打印后才会中断- at cbreakpoint
with id=2
(而cbreakpoint
withid=1
将被跳过)。根据 Cython 版本,该行可能会有所不同,但一旦 gdb 停止在cbreakpoint
.
它也可以在没有任何附加模块的情况下做类似的事情:
- 添加
breakpoint
或import pdb; pdb.set_trace()
代替cbreakpoint
gdb --args python example.py
+ 运行- 当
pdb
中断程序时,Ctrl+C
在 gdb 中点击以中断。 - 激活断点
gdb
。 - 继续,
gdb
然后在pdb
(即c+enter
两次)。
一个小问题是,在那之后断点可能会在 中被命中pdb
,所以第一种方法更健壮一些。
推荐阅读
- python - 如何在 Python 中将字符串转换为字节(已关闭)
- windows - Windows 调度程序如何加载下一个进程以在 CPU 上运行?
- typo3 - Typo3 选择填充来自 sys_category 的子类别
- puppet - 在模块/lib/facter/* 下的文件中找到的事实将以什么顺序加载到 puppet 客户端上?
- java - 关于使用模拟条带进行 e2e 测试
- visual-studio - 在 Visual Studio 中切换内置目标
- lisp - 递归地将元素添加到列表中的 Lisp 函数
- python - Python/Numpy - 这个 for 循环的矢量化实现?
- batch-file - 如何使用批处理文件更改配置文件 (config.yml) 中的连接字符串?
- javascript - 为什么 connectedCallback 不适用于本机 HTMLElement(不是自定义元素)?