首页 > 解决方案 > 通过 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中发生一次。.pytest.py

我看起来更直接的方式做同样的事情,所以 问题:

  1. 我能抓住test.py开始执行的那一刻吗?我应该提一下,test.py文件名出现在不同阶段:解析、编译、执行。因此,可以在任何阶段中断CPython执行也将是一件好事。
  2. 我可以指定test.py要中断的行吗?文件很容易.c,但文件不容易.py

标签: debugginggdbcpython

解决方案


我的想法是使用 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 cbreakpointwith id=2(而cbreakpointwithid=1将被跳过)。根据 Cython 版本,该行可能会有所不同,但一旦 gdb 停止在cbreakpoint.


它也可以在没有任何附加模块的情况下做类似的事情:

  1. 添加breakpointimport pdb; pdb.set_trace()代替cbreakpoint
  2. gdb --args python example.py+ 运行
  3. pdb中断程序时,Ctrl+C在 gdb 中点击以中断。
  4. 激活断点gdb
  5. 继续,gdb然后在pdb(即c+enter 两次)。

一个小问题是,在那之后断点可能会在 中被命中pdb,所以第一种方法更健壮一些。


推荐阅读