如何在调试 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.c
的 switch(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 扩展来实现。
我是怎么做到的(我可能会遗漏一些东西,因为我是凭记忆复制的):
- 将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])
P.S.
我过去问过同样的问题,它对你有用:。
我想了解 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.c
的 switch(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 扩展来实现。
我是怎么做到的(我可能会遗漏一些东西,因为我是凭记忆复制的):
- 将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])
P.S.
我过去问过同样的问题,它对你有用: