调用 python3 操作 Py_buffer 的扩展模块函数时获取 SIGSEGV

Getting a SIGSEGV when calling python3 extension module function operating a Py_buffer

我正在研究 Python C 扩展模块,我得到了这个简单的函数:

static PyObject *e_parse_metadata(PyObject *self, PyObject *args) {
    Py_buffer buf;

    if(!PyArg_ParseTuple(args, "y#", &buf)) {
        // interpreter raises exception, we return NULL to indicate failure
        return NULL;
    }

    fprintf(stdout, "extension: %c%c\n\n", *((char *) buf.buf) + 0, *((char*) buf.buf + 1)); // should print "BM"

    PyBuffer_Release(&buf);

    return PyLong_FromLong(33l);
}

它试图从 Python 中传递给它的参数中获取 Py_buffer。然后它将缓冲区的前 2 个字节显示为 stdout 的字符,释放缓冲区,并 returns 对表示整数 33 的新 PyObject 的引用。


接下来我得到这个 Python 使用上述函数的示例:

#!/usr/bin/env python3

import bbmp_utils # my module

with open('./mit.bmp', 'rb') as mit:
    if(mit.readable()):
        filedata = mit.read()
        res = bbmp_utils.parse_metadata(filedata) # call to my function in the extension module
        print(res, type(res))

这导致扩展模块成功地将字节流 (extension: BM) 中的前 2 个字节打印到标准输出,但随后终止:fish: “env PYTHONPATH=./build_dbg pyth…” terminated by signal SIGSEGV (Address boundary error)


奇怪的是,直接将 bytes 实例传递给我的扩展函数 根本不会 导致崩溃,例如

res = bbmp_utils.parse_metadata(mit.read())

为什么第一个示例会导致崩溃而第二个示例不会?

我在解析 Python 参数时使用了错误的格式说明符。

y# 要求将缓冲区的长度也传递给 PyArg_ParseTuple,我没有这样做。另请注意,# 变体采用只读缓冲区。


y* 按预期工作。


这很好,但它仍然不能解释为什么 python 版本之一崩溃而另一个没有。