我非常熟悉编写 C 代码,并且很擅长编写 Python 代码。 我正在尝试学习如何用 C 编写可以从 OSX 10.15.7 上的 Python-3.9.X 调用的模块。我已经得到了几个“hello world”类型的示例,但是对于复杂的示例,我正在努力弄清楚如何调试我编写的 C 扩展。
MWE:
src/add.c
// The C function that actually does the work
static PyObject * add_c_func(PyObject *self, PyObject *args)
{
int a=0;
int b=0;
int c=0;
// This determines the number arguments used by add_c_func
if (!PyArg_ParseTuple(args, "iii", &a, &b, &c))
{
return NULL;
}
printf("%i\n", a+b+c);
Py_RETURN_NONE;
}
// This defines the function used by
static PyMethodDef AddMethods[] = {
{"add_py_func", add_c_func, METH_VARARGS, "Add three numbers."},
{NULL, NULL, 0, NULL}
};
static struct PyModuleDef addpymod =
{
PyModuleDef_HEAD_INIT,
"addpymod", /* name of module */
"", /* module documentation, may be NULL */
-1, /* size of per-interpreter state of the module, or -1 if the module keeps state in global variables. */
AddMethods
};
PyMODINIT_FUNC PyInit_addpymod(void)
{
return PyModule_Create(&addpymod);
}
setup.py :
from setuptools import setup, Extension
setup(
name='addpymod',
version='1.0',
description='Python Package with Hello World C Extension',
ext_modules=[
Extension(
'addpymod',
sources=['src/add.c'],
py_limited_api=True)
],
)
编译/安装(默认情况下它使用
clang
):
python setup.py install
尝试调试:
(py-ext-test) local: understand-python-c-ext $ gdb
GNU gdb (GDB) 10.1
.
.
.
(gdb) b add.c : 20
No symbol table is loaded. Use the "file" command.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (add.c : 20) pending.
(gdb) python
>import addpymod
>addpymod.add_py_func(10,10,10) # Why didn't my breakpoint get hit?
>Quit
# This clearly failed, I'm not even sure where my stdout is
这里有多个级别的复杂性,我确信我被不止一个级别绊倒了。
问题 :
-
如何使用
add.c
(首选)或可能gdb
调试我的lldb
,因为它是默认使用clang
的-g
选项编译的?
以下是关于如何调试 C Python 扩展的逐步指南:
1. 使用调试符号进行编译
-
必须使用
-g
标志编译 C 代码和扩展模块,以便包含调试符号。这允许调试器将机器代码映射回你的源代码,从而实现有意义的断点和代码步进。修改你的
setup.py
文件,将extra_compile_args
和extra_link_args
选项添加到Extension
中:```python from setuptools import setup, Extension
setup( name='addpymod', version='1.0', description='Python Package with Hello World C Extension', ext_modules=[ Extension( 'addpymod', sources=['src/add.c'], py_limited_api=True, extra_compile_args=['-g'], # 添加此行 extra_link_args=['-g'] # 添加此行 ) ], ) ```
这将确保在编译和链接过程中传递
-g
标志。 -
使用以下命令重新安装扩展模块:
bash python setup.py install
2. 使用 GDB 进行调试
-
启动 GDB 并加载 Python: 打开终端并运行:
bash gdb python
-
设置断点: 在你的 C 代码中设置一个断点。例如,要在
add_c_func
函数的开头设置一个断点:gdb (gdb) b add.c:6
-
运行 Python 代码:
gdb (gdb) run -m addpymod
这将在
gdb
中启动 Python,加载你的模块并停止在你的断点处。 -
使用 GDB 命令: 现在你可以使用 GDB 命令,例如:
-
n
(next)单步执行下一行代码。 -
s
(step)步入函数调用。 -
c
(continue)继续执行,直到下一个断点或程序结束。 -
p
(print)打印变量的值。 -
bt
(backtrace)查看调用堆栈。
-
3. 使用 LLDB 进行调试(如果你坚持要这样做)
LLDB 的步骤类似于 GDB。以下是如何使用 LLDB 进行调试:
1. **使用调试符号进行编译:** 就像使用 GDB 一样,你需要使用 `-g` 标志进行编译。
2. **启动 LLDB:**
```bash
lldb python
```
3. **设置断点:**
```lldb
(lldb) b add.c:6
```
4. **运行 Python 代码:**
```lldb
(lldb) run -m addpymod
```
5. **使用 LLDB 命令:** LLDB 使用与 GDB 类似的命令,但有一些区别。例如:
* `n` 或 `next`:单步执行下一行代码。
* `s` 或 `step`:步入函数调用。
* `c` 或 `continue`:继续执行。
* `p` 或 `frame variable`:打印变量的值。
* `bt` 或 `thread backtrace`:查看调用堆栈。
其他提示
-
打印 Python 对象:
当使用
p
在 GDB 或 LLDB 中打印PyObject*
时,你只会看到原始指针值。要查看 Python 对象的实际内容,你需要使用 Python 调试器函数。将以下内容添加到你的.gdbinit
中,以获得更易读的 Python 对象表示形式:
``` python import gdb
class PyPrintCommand(gdb.Command): "Print a Python object." def init (self): gdb.Command. init (self, "ppy", gdb.COMMAND_DATA, gdb.COMPLETE_SYMBOL)
def invoke(self, arg, from_tty):
frame = gdb.selected_frame()
value = gdb.parse_and_eval(arg)
if hasattr(value, '__class__'):
print(value.__class__.__name__ + ':', value)
else:
print(value)
PyPrintCommand() end ```
现在你可以使用
ppy <variable_name>
来打印 Python 对象。
*
考虑使用 IDE:
如果你不习惯使用命令行调试器,许多 IDE(例如 Visual Studio Code、CLion 和 PyCharm)都支持调试 Python C 扩展。
调试 C Python 扩展可能具有挑战性,但通过遵循这些步骤并使用正确的工具,你可以有效地识别和修复代码中的问题。
标签:python,c,gdb,lldb,python-extensions From: 71866037