【问题标题】:How to convert a C binary buffer to it’s hex representation in Python string?如何将 C 二进制缓冲区转换为 Python 字符串中的十六进制表示?
【发布时间】:2019-10-28 00:47:32
【问题描述】:

众所周知pysha3与pypy不兼容,因为3年未维护,我只好自己修改。

当然,正确的方法是用纯 Python 代码执行完全重写(这也将导致比当前代码更快的实现),但我缺乏所需的密码学和背景数学知识来做到这一点, 并且使用它的程序非常密集(它需要一个没有 gil 的 python3 用于多线程或带有 jit 的 python3)。

必须由C代码调用的单点故障boils down to this function

static PyObject*
_Py_strhex(const char* argbuf, const Py_ssize_t arglen)
{
    static const char *hexdigits = "0123456789abcdef";

    PyObject *retval;
#if PY_MAJOR_VERSION >= 3
    Py_UCS1 *retbuf;
#else
    char *retbuf;
#endif
    Py_ssize_t i, j;

    assert(arglen >= 0);
    if (arglen > PY_SSIZE_T_MAX / 2)
        return PyErr_NoMemory();

#if PY_MAJOR_VERSION >= 3
    retval = PyUnicode_New(arglen * 2, 127);
    if (!retval)
            return NULL;
    retbuf = PyUnicode_1BYTE_DATA(retval);
#else
    retval = PyString_FromStringAndSize(NULL, arglen * 2);
    if (!retval)
            return NULL;
    retbuf = PyString_AsString(retval);
    if (!retbuf) {
            Py_DECREF(retval);
            return NULL;
    }
#endif
    /* make hex version of string, taken from shamodule.c */
    for (i=j=0; i < arglen; i++) {
        unsigned char c;
        c = (argbuf[i] >> 4) & 0xf;
        retbuf[j++] = hexdigits[c];
        c = argbuf[i] & 0xf;
        retbuf[j++] = hexdigits[c];
    }

    return retval;
}

pypy 的 cython 兼容级别为 3.2,PyUnicode_New 是在 python3.3 中引入的。

我尝试用锤子方法修复它,用以下 cython 代码替换整个文件:

cdef Py_strhex(const char* argbuf, const Py_ssize_t arglen):
    return (argbuf[:arglen]).hex()

但它似乎触发了分段错误,包括编译和使用官方 Python 实现。而且使用官方的 PyPy 二进制文件,我没有 gdb 的调试符号,所以我不知道为什么。

(gdb) bt
#0  0x00007ffff564cd00 in pypy_g_text_w__pypy_interpreter_baseobjspace_W_Root () from /usr/lib64/pypy3.6-v7.2.0-linux64/bin/libpypy3-c.so
#1  0x00007ffff5d721a8 in pypy_g_getattr () from /usr/lib64/pypy3.6-v7.2.0-linux64/bin/libpypy3-c.so
#2  0x00007ffff543a8bd in pypy_g_dispatcher_15 () from /usr/lib64/pypy3.6-v7.2.0-linux64/bin/libpypy3-c.so
#3  0x00007ffff5ab909b in pypy_g_wrapper_second_level.star_2_14 () from /usr/lib64/pypy3.6-v7.2.0-linux64/bin/libpypy3-c.so
#4  0x00007fffd7212372 in _Py_strhex.2738 () from /usr/lib64/pypy3.6-v7.2.0-linux64/site-packages/pysha3-1.0.3.dev1-py3.6-linux-x86_64.egg/_pysha3.pypy3-72-x86_64-linux-gnu.so
#5  0x00007fffd7217990 in _sha3_sha3_224_hexdigest_impl.2958 () from /usr/lib64/pypy3.6-v7.2.0-linux64/site-packages/pysha3-1.0.3.dev1-py3.6-linux-x86_64.egg/_pysha3.pypy3-72-x86_64-linux-gnu.so
#6  0x00007ffff5be2170 in pypy_g_generic_cpy_call__StdObjSpaceConst_funcPtr_SomeI_5 () from /usr/lib64/pypy3.6-v7.2.0-linux64/bin/libpypy3-c.so
#7  0x00007ffff54b25cd in pypy_g.call_1 () from /usr/lib64/pypy3.6-v7.2.0-linux64/bin/libpypy3-c.so
#8  0x00007ffff56715b9 in pypy_g_BuiltinCodePassThroughArguments1_funcrun_obj () from /usr/lib64/pypy3.6-v7.2.0-linux64/bin/libpypy3-c.so
#9  0x00007ffff56ffc06 in pypy_g_call_valuestack__AccessDirect_None () from /usr/lib64/pypy3.6-v7.2.0-linux64/bin/libpypy3-c.so
#10 0x00007ffff5edb29b in pypy_g_CALL_METHOD__AccessDirect_star_1 () from /usr/lib64/pypy3.6-v7.2.0-linux64/bin/libpypy3-c.so

将默认 Linux 堆栈深度增加到 65Mb 不会改变发生段错误的递归深度,因此即使堆栈深度大于 200,这似乎也与堆栈溢出无关。

【问题讨论】:

  • 是否有机会将您的代码库更新到 Python 3.6 或更高版本?
  • @Selcuk PyPy 支持 python3.6,但仅限于 python 级别。在 C 级别,它仍然处于 3.2 级别的兼容性。即使是最新版本的pysha3也没有运行的功能。
  • 对不起,我说的是使用 Python 3.6 自带的内置 hashlib.sha3
  • @Selcuk 是为了做 sha3 的 keccak 变体,以便与以太坊兼容,所以很遗憾它不兼容。项目本身不使用 pysha3:它被许多 pip 依赖项使用,因此修复这个问题会更简单。
  • 我不知道你为什么说C级还是3.2级。也许它错过了一个特定的 API 函数,但如果你报告它,我们会修复这个错误。

标签: python c python-3.x cython pypy


【解决方案1】:

就 Cython 而言,它比您想象的要简单:

cdef Py_strhex(const char* argbuf, const Py_ssize_t arglen):
    return (argbuf[:arglen]).hex()

基本上你不需要malloc(它无论如何都会引入内存泄漏,因为它缺少free)并且你不需要memcpyargbuf[:arglen] 创建一个具有适当长度的 bytes 对象(制作数据的副本)。

这绝对适用于 CPython。在 PyPy2 上,它产生 AttributeError: 'str' object has no attribute 'hex',这对于 Python 2 是正确的。我想如果它产生分段错误,它会在 AttributeError 之前发生,所以这是有希望的。我没有现成的 PyPy3...


编辑

我现在已经成功地在 PyPy3 上测试了我的代码,如下所示:

# extra Cython code just to call the function
def test():
    cdef const char* a = "0123456789"
    return Py_strhex(a,10)

然后来自 Python:

import modulename
modulename.test()

这工作正常,没有分段错误;因此我非常确信这段代码是好的。

我不知道你是如何调用 Cython 代码的,因为你没有说;但是,Cython 不会生成 C 代码,目的是您只是复制单个函数。它生成一个模块并且该模块期望被导入(在模块导入期间设置了一些东西)。具体来说,Cython 在模块初始化期间设置了一个字符串表,包括用于查找属性的字符串 "hex"。要正确使用此代码you'd need to ensure the module it's contained in is imported first,而不仅仅是将生成的 Cython 代码的副本转储到 C 文件中。在 Python 3 中这样做有点复杂,可能不适合您的目的。

我将保留此答案的当前状态,因为我相信它是正确的,并且问题出现在您未指定的部分中。它很可能对您没有用,您可以随意忽略它。

【讨论】:

  • 如果将 argbuf 传递给堆栈,这不会失败(因为它稍后会被垃圾收集器尝试释放)?当然,我使用的是最新版本的 PyPy3。但正如您在回溯中看到的那样,Segfault 的发生是因为尝试调用interpter 来查找要执行的函数。
  • 字符串拥有自己的内存。这将创建一个带有argbuf 副本的临时字符串(可以安全地比argbuf 活得更久,但在这种情况下不需要)。从它派生的十六进制字符串也是独立的,拥有自己的内存。从内存管理的角度来看,只要argbuf 在调用函数时有效就可以了。恐怕这只是试图回答“如何编写 Cython 函数将const char* 转换为十六进制”部分问题。我真的无法诊断 PyPy 内部问题!
  • 如回溯所示,如果在不调用 python getattr 的情况下使用完整的 C 语言(这也意味着不使用 cython),这听起来像是真正的问题。我的意思是使用与 Cpython 3.2 完全兼容的 C 语言(我不知道如何实现)。
  • 如果 Cython 代码仍然产生 seg-fault 那么我想我不能帮助你;我很快就会删除我的答案
  • 不,它仍然可以编辑它。我能做的是使用 C 字符串转换为十六进制,然后转换为 python 字符串。归结为这个问题:如何将 C 字符串转换为 PyObject 字符串?另外,您确定 (argbuf[:arglen]) 返回的是 bytearray 而不是 str 对象吗?
【解决方案2】:

好的,使用此变体找到了我正在寻找的东西。 这不适用于所有编译器,并且仅与 Python3 兼容,但它带来了部分 PyPy 兼容性(一些应该失败的测试,因为返回了不正确的哈希值而成功)与 pysha3 以及它所依赖的程序:

static PyObject * _Py_strhex(const char* argbuf, const Py_ssize_t arglen) {
    static const char *hexdigits = "0123456789abcdef";

    assert(arglen >= 0);

    if (arglen > PY_SSIZE_T_MAX / 2)
        return PyErr_NoMemory();

    const Py_ssize_t len=arglen*2;
    char retbuf[len+1];
    retbuf[len+1]=0;

    /* make hex version of string, taken from shamodule.c */
    for (Py_ssize_t i=0,j=0; i < arglen; i++) {
        retbuf[j++] = hexdigits[(argbuf[i] >> 4) & 0xf];
        retbuf[j++] = hexdigits[argbuf[i] & 0xf];
    }

    return PyUnicode_FromStringAndSize(retbuf,len);
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-07-26
    • 1970-01-01
    • 2017-05-26
    • 2017-02-23
    • 2014-08-03
    • 1970-01-01
    • 2016-09-16
    • 2018-01-31
    相关资源
    最近更新 更多