Debugging GDB调试CPython时在Python源代码中设置断点的最佳方法

Debugging GDB调试CPython时在Python源代码中设置断点的最佳方法,debugging,gdb,cpython,Debugging,Gdb,Cpython,我使用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轮到它之前执行的,所以我不能在\u PyEval\u evalframefault-有很多文件,所以我应该将我的文件与其他文件区分开来 第二个问题-我无法设置“当文件名

我使用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
轮到它之前执行的,所以我不能在
\u PyEval\u evalframefault
-有很多文件,所以我应该将我的文件与其他文件区分开来

第二个问题-我无法设置“当文件名等于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)
我选择了
二进制移位
操作码,因为它很少在代码中使用。因此,我可以快速访问
.py
文件中所需的部分-它在我的
test.py
之前执行的所有其他
.py
模块中发生一次

我想用更直接的方法来做同样的事情,所以 问题:

  • 我能捕捉到
    test.py
    开始执行的时刻吗?我应该提到,
    test.py
    文件名出现在不同的阶段:解析、编译和执行。因此,在任何阶段都可以打破CPython执行
  • 我可以指定要中断的
    test.py
    行吗?这对于
    .c
    文件很容易,但对于
    .py
    文件则不容易

  • 我的想法是使用C扩展,使在python脚本中设置C断点成为可能(类似于Python3.7或自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(断点\u id=1)
    cbreakpoint(断点\u 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没有安装,我已经上传了一个不依赖Cython的版本(这篇文章的代码太多)

    考虑到断点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
    ,就可以找到线路


    它还可以在没有任何附加模块的情况下执行类似的操作:

  • 添加
    断点
    导入pdb;pdb.set_trace()
    而不是
    cbreakpoint
  • gdb--args python example.py
    +run
  • pdb
    中断程序时,点击
    Ctrl+C
    以在gdb中中断
  • gdb
    中激活断点
  • gdb
    中继续,然后在
    pdb
    中继续(即
    c+输入两次

  • 一个小问题是,在这之后,断点可能会在
    pdb
    中被命中,因此第一种方法更健壮。

    第二种方法不起作用。我做了几步№ 1,2,并获得
    pdb
    的命令行,点击
    Ctrl+C
    并获得程序接收信号SIGINT,中断。消息正在调试的Python程序被
    Ctrl+C
    完全中断,因此之后就没有什么可调试的了。我正在尝试测试第一种方法,但使用的是纯C扩展,因为我根本不懂Cython。谢谢您的建议。@MiniMax No“程序收到信号SIGINT,Interrupt”正是您所需要的-在程序被中断之后。您需要输入c+enter(对于
    继续
    )用于让gdb运行,然后另外
    c+输入
    用于让pdb运行。@MiniMax您可以从提供的github链接安装
    cbreakpoint
    ,或者自己将其重写为c扩展-但是对于使用
    cythonize
    的解决方案,除了安装cython之外,没有其他功能,将给定的代码保存为cbreakpoint.pyx并调用cythonize-生成的so可以在需要时使用。
    >>> gdb --args python example.py
    [gdb] break src/cbreakpoint.c:595 if breakpoint_id == 2
    [gdb] run