我想了解 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 扩展来做到这一点”。
我是怎么做到的(我可能会错过一些东西,因为我是从内存中复制的):
~/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
文件。 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/