【问题标题】:The optimal way to set a breakpoint in the Python source code while debugging CPython by GDB通过 GDB 调试 CPython 时在 Python 源代码中设置断点的最佳方式
【发布时间】:2019-07-30 19:15:51
【问题描述】:

我使用 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”这样的条件,因为文件名不是一个简单的@ 987654325@字符串,它是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 命令在Python/ceval.c 文件中跟踪BINARY_LSHIFT 操作码的执行:

break ceval.c:1327

我选择了BINARY_LSHIFT 操作码,因为它很少在代码中使用。因此,我可以快速到达.py 文件的所需部分——它在我的test.py 之前执行的所有其他.py 模块中发生一次。

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

  1. 我可以捕捉到test.py 开始执行的那一刻吗?我应该提一下,test.py 文件名出现在不同阶段:解析、编译、执行。因此,在任何阶段都可以中断 CPython 执行也是一件好事。
  2. 我可以指定test.py 的行,我想在哪里中断? .c 文件很容易,但对于 .py 文件则不然。

【问题讨论】:

    标签: debugging gdb cpython


    【解决方案1】:

    我的想法是使用 C 扩展,以便在 python 脚本中设置 C 断点(类似于 pdb.set_trace()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 的版本(这篇文章的代码太多)。

    也可以有条件地中断,给定breakpoint_id,即:

    >>> gdb --args python example.py
    [gdb] break src/cbreakpoint.c:595 if breakpoint_id == 2
    [gdb] run
    

    仅在打印hello 后才会中断 - 在cbreakpointid=2 (而cbreakpointid=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 中遇到断点,所以第一种方法更健壮一些。

    【讨论】:

    • 第二种方法不行。我执行了第 1,2 步并得到了pdb 的命令行,点击Ctrl+C 并得到程序收到信号 SIGINT,中断。 消息。 Python的程序,一直在调试中,被Ctrl+C彻底打断了,之后就没什么可调试的了。
    • 我正在尝试测试第一种方法,但使用的是普通的 C 扩展,因为我根本不懂 Cython。感谢您的想法。
    • @MiniMax 否“程序收到信号 SIGINT,中断”正是您所需要的 - 在该程序被中断之后。您需要输入 c+enter (for continue) 让 gdb 运行,然后另外 c+enter 让 pdb 运行。
    • @MiniMax 您可以从提供的 github-link 安装 cbreakpoint 或自己将其重写为 C 扩展 - 但是对于带有 cythonize 的解决方案,除了安装 cython 之外,没有什么比它节省的了给定的代码为 cbreakpoint.pyx 并调用 cythonize - 结果可以在需要时使用。
    猜你喜欢
    • 2012-04-15
    • 1970-01-01
    • 2011-05-18
    • 2011-02-12
    • 2011-03-08
    • 2012-02-06
    • 2022-01-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多