GDB调试CPython时在Python源代码中设置断点的最佳方式
The optimal way to set a breakpoint in the Python source code while debugging CPython by GDB
我使用 GDB 来了解 CPython 如何执行 test.py
源文件,我想停止 CPython 当它开始执行我感兴趣的操作码时。
OS: Ubuntu 18.04.2 LTS
调试器: GNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-git
第一个问题 - 许多CPython的.py
自己的文件在轮到我的test.py
之前执行,所以我不能只在_PyEval_EvalFrameDefault
- 有很多,所以我应该把我的文件与其他文件区分开来。
第二个问题 - 我不能像"when the filename is equal to the test.py"那样设置条件,因为文件名不是简单的C
字符串,它是CPython 的 Unicode 对象,所以标准的 GDB 字符串函数不能用于比较。
此刻我在 test.py
source:
的所需行执行下一个技巧来中断执行
比如我有源文件:
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 命令跟踪 Python/ceval.c
文件中的 BINARY_LSHIFT
操作码执行:
break ceval.c:1327
我选择了 BINARY_LSHIFT
操作码,因为它在代码中很少使用。因此,我可以快速到达 .py
文件的所需部分 - 它在我的 test.py
.
之前执行的所有其他 .py
模块中发生一次
我看起来更直接的方法是做同样的事情,所以
问题:
- 我可以捕捉到
test.py
开始执行的时刻吗?我应该提一下,test.py
文件名出现在不同的阶段:解析、编译、执行。因此,能够在任何阶段中断 CPython 执行也是很好的。
- 我可以指定
test.py
的那一行吗? .c
个文件很容易,但 .py
个文件不方便。
我的想法是使用 C 扩展,以便在 python 脚本中设置 C 断点(类似于 pdb.set_trace()
or breakpoint()
,因为 Python3.7),我称之为 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 的版本(此 post 的代码太多)。
也可以有条件地中断,给定 breakpoint_id
,即:
>>> gdb --args python example.py
[gdb] break src/cbreakpoint.c:595 if breakpoint_id == 2
[gdb] run
将仅在打印 hello
后中断 - 在 cbreakpoint
和 id=2
处(而 cbreakpoint
和 id=1
将被跳过)。根据 Cython 版本的不同,该行可能会有所不同,但是一旦 gdb 停止在 cbreakpoint
.
就可以找到
它也可以在没有任何额外模块的情况下做类似的事情:
- 添加
breakpoint
或 import pdb; pdb.set_trace()
而不是 cbreakpoint
gdb --args python example.py
+ 运行
- 当
pdb
中断程序时,在gdb中按Ctrl+C
中断。
- 激活
gdb
中的断点。
- 在
gdb
中继续,然后在 pdb
中继续(即 c+enter
两次 )。
一个小问题是,之后在pdb
中可能会遇到断点,所以第一种方法更稳健一些。
我使用 GDB 来了解 CPython 如何执行 test.py
源文件,我想停止 CPython 当它开始执行我感兴趣的操作码时。
OS: Ubuntu 18.04.2 LTS
调试器: GNU gdb (Ubuntu 8.1-0ubuntu3) 8.1.0.20180409-git
第一个问题 - 许多CPython的.py
自己的文件在轮到我的test.py
之前执行,所以我不能只在_PyEval_EvalFrameDefault
- 有很多,所以我应该把我的文件与其他文件区分开来。
第二个问题 - 我不能像"when the filename is equal to the test.py"那样设置条件,因为文件名不是简单的C
字符串,它是CPython 的 Unicode 对象,所以标准的 GDB 字符串函数不能用于比较。
此刻我在 test.py
source:
比如我有源文件:
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 命令跟踪 Python/ceval.c
文件中的 BINARY_LSHIFT
操作码执行:
break ceval.c:1327
我选择了 BINARY_LSHIFT
操作码,因为它在代码中很少使用。因此,我可以快速到达 .py
文件的所需部分 - 它在我的 test.py
.
.py
模块中发生一次
我看起来更直接的方法是做同样的事情,所以 问题:
- 我可以捕捉到
test.py
开始执行的时刻吗?我应该提一下,test.py
文件名出现在不同的阶段:解析、编译、执行。因此,能够在任何阶段中断 CPython 执行也是很好的。 - 我可以指定
test.py
的那一行吗?.c
个文件很容易,但.py
个文件不方便。
我的想法是使用 C 扩展,以便在 python 脚本中设置 C 断点(类似于 pdb.set_trace()
or breakpoint()
,因为 Python3.7),我称之为 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 的版本(此 post 的代码太多)。
也可以有条件地中断,给定 breakpoint_id
,即:
>>> gdb --args python example.py
[gdb] break src/cbreakpoint.c:595 if breakpoint_id == 2
[gdb] run
将仅在打印 hello
后中断 - 在 cbreakpoint
和 id=2
处(而 cbreakpoint
和 id=1
将被跳过)。根据 Cython 版本的不同,该行可能会有所不同,但是一旦 gdb 停止在 cbreakpoint
.
它也可以在没有任何额外模块的情况下做类似的事情:
- 添加
breakpoint
或import pdb; pdb.set_trace()
而不是cbreakpoint
gdb --args python example.py
+ 运行- 当
pdb
中断程序时,在gdb中按Ctrl+C
中断。 - 激活
gdb
中的断点。 - 在
gdb
中继续,然后在pdb
中继续(即c+enter
两次 )。
一个小问题是,之后在pdb
中可能会遇到断点,所以第一种方法更稳健一些。