如何在调试 cpython 时遍历 Python 操作码?

How to walk through Python opcode while debugging cpython?

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

ceval.c 文件中的 for (;;) 循环

现在我要解释下面的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

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

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

请帮我解决这个问题。提前致谢。

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

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

我是怎么做到的(我可能会遗漏一些东西,因为我是凭记忆复制的):

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

    ~/learning_python/cpython-master/python my_breakpoint_setup.py build
    

    命令。它创建了一个 my_breakpoint.cpython-38dm-x86_64-linux-gnu.so 文件。

  5. 将上一步的共享对象文件复制到 CPython 的 Lib 目录:

    cp -iv my_breakpoint.cpython-38dm-x86_64-linux-gnu.so ~/learning_python/cpython-master/Lib/
    

    为了方便起见需要复制,否则我们应该在任何我们想使用(导入)这个模块的目录中有这个 .so 文件。

  6. 现在,我们可以做如下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
    
  7. 要调试此文件,请执行以下操作:

    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])

P.S.

我过去问过同样的问题,它对你有用: