在开发 CPython 扩展或者处理 CPython 内部用 C 语言编写的代码时,我们经常需要进行低层级的调试。这时,GDB 作为一款强大的调试器就成为了我们的首选工具。由于 GDB 本身并不理解 CPython 解释器的高层级信息,例如 Python 对象的类型和值,但我们可以使用 python-gdb.py
扩展来增强 GDB 的功能。
使用方法
1. 加载 python-gdb.py
扩展
在启动 GDB 后,使用以下命令加载 python-gdb.py
扩展:
source /path/to/python-gdb.py
请将 /path/to/python-gdb.py
替换为 python-gdb.py
文件的实际路径。
2. 查看 Python 函数栈
加载扩展后,可以使用 py-bt
命令查看当前的 Python 函数栈:
(gdb) py-bt
该命令会打印出类似于 Python traceback 的信息,显示当前正在执行的 Python 函数调用栈。
3. 检查 Python 对象
使用 py-print
命令可以查看 PyObject*
指针所代表的 Python 对象的类型和值:
(gdb) py-print <PyObject* pointer>
将 <PyObject* pointer>
替换为实际的 PyObject*
指针地址。
示例
假设我们有一个简单的 CPython 扩展模块 example.c
:
#include <Python.h>
static PyObject* example_hello(PyObject* self, PyObject* args) {
PyObject *name = NULL;
if (!PyArg_ParseTuple(args, "O", &name)) {
return NULL;
}
return PyUnicode_FromFormat("Hello, %S!", name);
}
static PyMethodDef example_methods[] = {
{"hello", example_hello, METH_VARARGS, "Say hello."},
{NULL, NULL, 0, NULL}
};
static struct PyModuleDef example_module = {
PyModuleDef_HEAD_INIT,
"example",
"Example module",
-1,
example_methods
};
PyMODINIT_FUNC PyInit_example(void) {
return PyModule_Create(&example_module);
}
我们在 example_hello
函数中设置一个断点,然后使用 GDB 和 python-gdb.py
扩展来检查传递给函数的参数:
(gdb) break example_hello
(gdb) run
...
(gdb) py-bt
Traceback (most recent call last):
<module>()
example.hello('World')
(gdb) p args
$1 = (PyObject *) 0x7ffff7f86048
(gdb) py-print args
<tuple object at 0x7ffff7f86048>
(gdb) py-print args[0]
<str object at 0x7ffff7f86080>
(gdb) py-print args[0].ob_sval
"World"
在这个例子中,首先使用 py-bt
命令查看了 Python 函数调用栈。然后,使用 py-print
命令检查了传递给 example_hello
函数的参数 args
。通过 py-print
,我们可以看到 args
是一个元组,第一个元素是一个字符串 “World”。
总结
python-gdb.py
扩展极大地简化了 CPython 扩展和 CPython 内部代码的调试工作。它允许我们在 GDB 中直接查看 Python 函数调用栈和 Python 对象的信息,方便我们快速定位和解决问题。