我想了解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断点的可能性。
The idea:CPython是一个用C语言编写的大程序。我们可以像任何C程序一样轻松调试它-没问题。如果要在启动_PyType_Lookup
功能时停止执行,则只需运行break _PyType_Lookup
命令。因此,如果将自己的C函数添加到CPython程序中,例如cbreakpoint
,则可以在每次调用cbreakpoint
时停止执行。而且,如果我们找到将cbreakpoint
函数插入source.py
的方法,我们将获得所需的功能-每次解释器看到cbreakpoint
时,它都会停止运行(如果我们在之前将break cbreakpoint
设置为)。问题是:我们该怎么做?答案:“通过编写C扩展名”。
我如何做到的(我会错过一些事情,因为我是从记忆中复制出来的::
~/learning_python/cpython-master
目录中。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's 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])
P.S。
过去我问过同样的问题,对您可能有用:The optimal way to set a breakpoint in the Python source code while debugging CPython by GDB。