我想了解 Python 解释器的功能。我了解操作码的生成过程,并希望更好地了解解释器部分。为此,我在互联网上阅读了很多内容,并了解了 python 解释器(Cpython)中 for (;;) 文件中的 ceval.c 循环。

现在我想解释以下 python 代码 a.py :

a = 4
b = 5
c = a + b

当我做 python -m dis a.py
  1           0 LOAD_CONST               0 (4)
              2 STORE_NAME               0 (a)

  2           4 LOAD_CONST               1 (5)
              6 STORE_NAME               1 (b)

  3           8 LOAD_NAME                0 (a)
             10 LOAD_NAME                1 (b)
             12 BINARY_ADD
             14 STORE_NAME               2 (c)
             16 LOAD_CONST               2 (None)
             18 RETURN_VALUE

现在我已将调试点放在 switch(opcode)ceval.c 行中。现在,当我启动调试器时,它出现了 2000 多次这个位置。我认为这是因为在开始之前,python 还必须做一些其他的解释。所以,我的问题是如何只调试相关的操作码指令?

基本上,我怎么知道我正在调试的指令实际上来自我创建的程序?

请帮我解决同样的问题。提前致谢。

最佳答案

我做了很多 CPython 调试,以更好地理解它的工作方式。我通过编写 C 扩展模块解决了在 Python 源文件中设置 gdb 断点的可能性。

想法 : CPython 是一个用 C 语言编写的大程序。我们可以像任何 C 程序一样轻松调试它——这里没有问题。如果我们想在 _PyType_Lookup 函数启动时停止执行,我们只需运行 break _PyType_Lookup 命令。因此,如果我们将我们自己的 C 函数添加到 CPython 程序中,例如 cbreakpoint ,我们可以在每次调用 cbreakpoint 时停止执行。如果我们找到将这个 cbreakpoint 函数插入 source.py 的方法,我们将获得所需的功能 - 每次解释器看到 cbreakpoint 时,它​​都会被停止(如果我们之前设置了 break cbreakpoint )。我们可以通过编写 C 扩展来做到这一点”。

我是怎么做到的(我可能会错过一些东西,因为我是从内存中复制的):

  • 下载了一个 CPython 源代码到~/learning_python/cpython-master 目录并编译。有一些错综复杂的 - Can't get rid of “value has been optimized out” in GDB
  • 自己创建了一个模块 - my_breakpoint.c
  • 创建了一个安装文件 - my_breakpoint_setup.py
  • 运行一个
    ~/learning_python/cpython-master/python my_breakpoint_setup.py build
    

    命令。它创建了一个 my_breakpoint.cpython-38dm-x86_64-linux-gnu.so 文件。
  • 将上一步中的共享对象文件复制到 CPython 的 Lib 目录中:
    cp -iv my_breakpoint.cpython-38dm-x86_64-linux-gnu.so ~/learning_python/cpython-master/Lib/
    

    为了方便起见,需要复制,否则我们应该在我们想要使用(导入)这个模块的任何目录中都有这个 .so 文件。
  • 现在,我们可以制作以下 source.py :
    #!/usr/bin/python3
    
    from my_breakpoint import cbreakpoint
    
    cbreakpoint(1)
    a = 4
    
    cbreakpoint(2)
    b = 5
    
    cbreakpoint(3)
    c = a + b
    

    要执行这个文件,我们必须使用我们的 ~/learning_python/cpython-master 解释器,而不是系统的 python3 ,因为系统的 python 没有 my_breakpoint 模块:
    ~/learning_python/cpython-master/python source.py
    
  • 要调试此文件,请执行以下操作:
    gdb --args ~/learning_python/cpython-master/python -B source.py
    

    然后,在 gdb 里面:
    (gdb) start
    
    (gdb) break cbreakpoint
    Function "cbreakpoint" not defined.
    Make breakpoint pending on future shared library load? (y or [n]) y
    Breakpoint 2 (cbreakpoint) pending.
    
    (gdb) cont
    

    有一个问题。当您按下 cont 时,gdb 停止在 cbreakpoint 函数的开头,您需要执行许多 next 命令来跳过此函数和 CPython 函数调用代码以实现所需 Python 代码执行的开始。或者你可以在 cbreakpoint 被命中后设置一个新的断点,比如:
    (gdb) break ceval.c:1080 ### The LOAD_CONST case beginning
    (gdb) cont
    

    但是,在多次执行此操作后,我将这些操作自动化,因此您只需将这些行添加到您的 ~/.gdbinit 中:
    set breakpoint pending on
    break cbreakpoint
        command $bpnum
        tbreak ceval.c:1098
            command $bpnum
            n
            end
        cont
        end
    set breakpoint pending off
    

    现在,您只需像第 7 步一样启动 gdb 并执行以下操作:
    (gdb) start
    (gdb) cont
    

    您将跳转到 source.py 代码执行的开头。

  • my_breakpoint.c
    #include <Python.h>
    
    static PyObject* cbreakpoint(PyObject *self, PyObject *args){
        int breakpoint_id;
    
        if(!PyArg_ParseTuple(args, "i", &breakpoint_id))
            return NULL;
    
        return Py_BuildValue("i", breakpoint_id);
    }
    
    static PyMethodDef my_methods[] = {
        {"cbreakpoint", cbreakpoint, METH_VARARGS, "breakpoint function"},
        {NULL, NULL, 0, NULL}
    };
    
    static struct PyModuleDef my_breakpoint = {
        PyModuleDef_HEAD_INIT,
        "my_breakpoint",
        "the module for setting C breakpoint in the Python source",
        -1,
        my_methods
    };
    
    PyMODINIT_FUNC PyInit_my_breakpoint(void){
        return PyModule_Create(&my_breakpoint);
    }
    

    my_breakpoint_setup.py
    from distutils.core import setup, Extension
    
    module = Extension('my_breakpoint', sources = ['my_breakpoint.c'])
    
    setup (name = 'PackageName',
           version = '1.0',
           description = 'This is a package for my_breakpoint module',
           ext_modules = [module])
    

    附言

    我过去问过同样的问题,它对你有用: The optimal way to set a breakpoint in the Python source code while debugging CPython by GDB

    关于python - 如何在调试 cpython 时遍历 Python 操作码?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/58343685/

    10-13 04:12