用给定的C头解压缩深度嵌套的struct到字典中?

Jas*_*per 6 python struct unpack

我正在寻找一种解包二进制数据的方法.数据由C头文件中的整个结构树(最多四层深,总结构大小几乎为64k)描述.

对于这个问题:在Python中解压缩嵌套的C结构,唯一的答案是将整个数据描述"压扁"成一个字符串,但我不愿意这样做,因为它不可维护.

我喜欢的是读取头文件然后神奇地创建一个获取二进制数据并返回包含所有数据的(正确嵌套的)字典的函数.

是否有任何Python模块可以完成此任务,或者它的某些部分?

或者还有其他方法可以在Python中处理这些数据吗?

Ser*_*sta 5

我不知道如何可靠地完成解析C头文件的困难部分,因为您甚至可能依赖于来自其他包含文件的声明,例如,您可以使用struct stat依赖sys/stat.h于其他标准包含文件的声明.但是我稍后会回到那一点......

因此,我建议您手动执行此操作,并使用分层描述,以允许从struct模块返回的平面字段列表中重建层次结构.

假设我们有一个用C语言描述的结构:

struct S4 {
    char c;
    char d;
};
struct my_struct {
    int a[4];
    struct {
        int b;
        struct {
            struct S4 z[2];
            int e;
        } y;
    } x;
    int f;
    struct {
        int g;
        struct {
            struct S4 v;
            int j;
        } u;
    } t;
};
Run Code Online (Sandbox Code Playgroud)

我首先手工编写我的初始结构描述:

'''\
S4: { c: b, d: b, c: 2x}
{ a: 4h, x: { b: h, y: { z: 2 S4, e: h}}, f: h, t: { g: h, u: { v: S4, j: h}}}\
'''
Run Code Online (Sandbox Code Playgroud)

它给出了字段的名称及其类型(以struct模块格式)

然后解析器(使用PLY)解析它并返回一个对象,该对象可以将二进制结构转换为namedtuple,其中包含正确名称的整个结构,所有内部结构也都是名称元组(struct格式字符串作为子产品) - 所有这些正确管理显式填充 - 可以使用命名结构(S4在示例中),它们必须在使用前声明 - 数组(简单元素或命名结构)通过在类型之前添加数字来实现(a : 4 h<=> short a[4];x : 2 S4< => struct S4 x[2];在示例中)

但它不是一个单一的文件轻量级解决方案,它需要大量的评论和文档.另一种方法是直接使用pycparser和原始的C头文件,但它会更复杂,我不太喜欢导入标准C库头的想法.无法解释为什么,但我觉得它不那么健壮.

这是一个使用示例:

>>> s = '''\
S4: { c: b, d: b, c: 2x}
{ a: 4h, x: { b: h, y: { z: 2 S4, e: h}}, f: h, t: { g: h, u: { v: S4, j: h}}}\
'''
>>> ns = Named_struct(s)
>>> import struct
>>> b = struct.pack(ns.fmt, * range(15))
>>> b
b'\x00\x00\x01\x00\x02\x00\x03\x00\x04\x00\x05\x06\x00\x00\x07\x08\x00\x00\t\x00\n\x00\x0b\x00\x0c\r\x00\x00\x0e\x00'
>>> t = ns.unpack(b)
>>> t
_X_(a=[0, 1, 2, 3], x=x(b=4, y=y(z=[x(c=5, d=6), x(c=7, d=8)], e=9)), f=10, t=t(g=11, u=u(v=v(c=12, d=13), j=14)))
>>> t.x.y.z[1]
x(c=7, d=8)
>>> t.x.y.z[1].d
8
Run Code Online (Sandbox Code Playgroud)

如果你有兴趣,我可以把它放在github或...

让我们回到困难的部分.我做了一些研究,但现在我只能给你一些可能的指示,而且还会有繁重的工作.您可以尝试使用pycparser但它只知道C(而不是C++)并且文档不是那么广泛.或者,如果您需要C++,您可以尝试使用Eli Bendersky(pycparser的作者)网站中提出的CLang Python绑定,使用Clang在Python中解析C++,但这里的文档几乎不存在.

现在对于代码,即使我仍然认为它是一项正在进行的工作,因为缺乏评论和正式测试 - 但它确实有效

h_lexer.py

import ply.lex as lex

states = ( ('value', 'exclusive'),)
tokens = ( 'BEG', 'NAME', 'COLON', 'END', 'COMMA', 'TYPE', 'PAD', 'NUMBER')
linepos = 0

def t_COLON(t):
    r':'
    t.lexer.begin('value')
    return t

def t_ANY_BEG(t):
    r'{'
    t.lexer.begin('INITIAL')
    return t

def t_value_TYPE(t):
    r'[cbB?hHiIlLqQnNfdp]|\d*[sP]'
    t.lexer.begin('INITIAL')
    return t

def t_value_PAD(t):
    r'x'
    t.lexer.begin('INITIAL')
    return t

def t_value_NUMBER(t):
    r'\d+'
    return t

def t_ANY_NAME(t):
    r'\w+'
    t.lexer.begin('INITIAL')
    return t

t_COMMA = ','
t_ANY_ignore= ' \t\r'
t_END=r'}'

def t_newline(t):
    r'\n'
    t.lexer.lineno += 1
    global linepos
    linepos = t.lexpos

# Error handling rule
def t_ANY_error(t):
    print("Illegal character '%s'" % t.value[0])
    t.lexer.skip(1)

def getpos(lexer):
    global linepos
    return lexer.lineno, lexer.lexpos - linepos
Run Code Online (Sandbox Code Playgroud)

h_yacc.py:

import ply.yacc as yacc
from h_lexer import tokens

named_structs = {}

def p_prog(p):
    'prog : named_strucs struc'
    p[0] = p[2]

def p_named_structs_empty(p):
    'named_strucs : '

def p_named_structs(p):
    'named_strucs : named_strucs named_struc'

def p_named_struct(p):
    'named_struc : NAME COLON struc'
    global named_structs
    named_structs[p[1]] = p[3]

def p_struc(p):
    'struc : BEG affects END'
    # print("STRUCT", p[2])
    p[0] = p[2]
def p_typ(p):
    'typ : TYPE'
    p[0] = p[1]

def p_pad(p):
    'pad : PAD'
    p[0] = p[1]

def p_pad_n(p):
    'pad : NUMBER PAD'
    p[0] = p[1] + p[2]

def p_affect_pad(p):
    'affect : NAME COLON pad'
    p[0] = (None, p[3])

def p_val(p):
    '''val : typ
           | struc'''
    p[0] = p[1]

def p_val_struct(p):
    'val : NAME'
    test_named_struct(p[1])
    p[0] = named_structs[p[1]]

def p_nval_single(p):
    'nval : val'
    p[0] = p[1]

def p_nval_multi(p):
    'nval : NUMBER val'
    n = int(p[1])
    if n == 0:
        raise SyntaxError
    if n == 1:
        print('WARN: array of one single element')
    p[0] = tuple([ p[2] for i in range(n) ])

def p_affect(p):
    'affect : NAME COLON nval'
    p[0] = (p[1], p[3])

def p_affects_init(p):
    'affects : affect'
    p[0] = [ p[1] ]

def p_affects_multi(p):
    'affects : affects COMMA affect'
    p[0] = p[1] + [ p[3] ]

def p_error(p):
    print ("Syntax error", p)

def test_named_struct(name):
    global named_structs
    if name not in named_structs:
        raise SyntaxError
Run Code Online (Sandbox Code Playgroud)

h_named_struct.py

import h_yacc
import h_lexer
import collections
import struct

class Named_struct(object):
    yacc = h_yacc.yacc.yacc(module = h_yacc)
    lexer = h_lexer.lex.lex(module = h_lexer)

    def __init__(self, desc_str, name='_X_', struc_typ = '='):
        l = self.__class__.yacc.parse(desc_str, self.__class__.lexer)
        if l is None:
            col, line = h_lexer.getpos(self.__class__.lexer)
            raise Exception(
                "Syntax error in description string near line %d col %d" %
                (line, col - 1))
        fmts = []
        self.t = self.__make_named(l, fmts, name)
        self.fmt = struc_typ + ''.join(fmts)
        self.struct = struct.Struct(self.fmt)
        self.name = name
        self.l = l

    def __make_named(self, l, fmts, name):
        names = []
        values = []
        for elt in l:
            if elt[0] is None:
                fmts.append(elt[1])
            else:
                val = None
                names.append(elt[0])
                if isinstance(elt[1], str):
                    fmts.append(elt[1])
                elif isinstance(elt[1], tuple):
                    val = self.__make_array(elt[1], fmts, elt[0])
                else:
                    val = self.__make_named(elt[1], fmts, elt[0])
                values.append(val)
        t = collections.namedtuple(name, names)
        return t, t(*values)

    def __make_array(self, l, fmts, name):
        values = []
        for elt in l:
            if isinstance(elt, str):
                fmts.append(elt)
                values.append(None)
            else:
                val = self.__make_named(elt, fmts, "x")
                values.append(val)
        t = self.__class__.__lister
        return t, t(*values)

    staticmethod
    def __lister(*l):
        return list(l)

    def __gen(self, t, it):
        l = []
        for g in t[1]:
            if g is None:
                l.append(next(it))
            else:
                l.append(self.__gen(g, it))
        return t[0](*l)

    def unpack(self, buffer):
        l = struct.unpack(self.fmt, buffer)
        t = self.__gen(self.t, iter(l))
        return t
Run Code Online (Sandbox Code Playgroud)


小智 5

您可以使用cffipython模块解释C struct定义并最终解析原始数据.以下是要解析的一组嵌套结构的示例:

from cffi import FFI
ffi = FFI()

## Define a set of (possibly nested) structures
ffi.cdef("""
  typedef struct { int32_t inner; } Ax;
  typedef struct { Ax a1; Ax a2; } Bx;
  typedef struct { Bx b1; Bx b2; } Cx;
  typedef struct { Cx c1; Cx c2; } Dx;
""")

def parse_Dx(raw_data):
    D_struct = ffi.new("Dx*")
    D_buffer = ffi.buffer(D_struct)
    D_buffer[:] = raw_data # Assign raw bytes
    return D_struct

## Make up some data (of the right length)
data = "\x01\x00\x00\x00" * (ffi.sizeof("Dx") / ffi.sizeof("int32_t")) 

structure = parse_Dx(data)
print structure.c1.b1.a1.inner ## Print "1"
Run Code Online (Sandbox Code Playgroud)

这种方法有几个缺陷:

  1. cffi模块依赖于存在的编译器,您需要使用您的代码为所有平台分发二进制文件
  2. 机器"endianness"将影响内存中数字的表示以及它们的解析方式
  3. 你没有得到结构字段的名称.

尽管如此,它可能涵盖您的用例.

  • 自从撰写本文以来,CFFI 1.0 已被引入,这使得分发变得更加容易,并且不再需要针对此用例的编译器。 (2认同)

小智 1

这是一个旧线程,但我遇到了同样的问题,没有找到任何支持嵌套结构的 python 库。

因此我编写了pycstruct库。

它支持结构、联合、位域和枚举。它还支持任何级别的所有字节顺序、对齐和嵌套。