我正在寻找一种解包二进制数据的方法。数据由C头文件中的整个结构树(最多四层深,总结构大小几乎为64k)描述。
对于这个问题:Unpacking nested C structs in Python唯一的答案是将整个数据描述“压扁”成一个字符串,但我不愿意这样做,因为它不可维护。
我喜欢的东西是读取头文件,然后神奇地创建一个函数,该函数获取二进制数据并返回包含所有数据的(正确嵌套的)字典。
是否有任何Python模块可以完成此任务,或者它的某些部分?
还有其他方法可以在Python中处理这些数据吗?
答案 0 :(得分:5)
我不知道如何可靠地完成解析C头文件的困难部分,因为您甚至可能依赖于来自其他包含文件的声明,例如,您可以使用依赖于struct stat
的{{1}} {1}}以及其他标准包含文件。但是我稍后会回到这一点......
因此,我建议您手动执行部分,并使用分层描述,以便从struct module返回的平面字段列表中重建层次结构。
假设我们有一个用C语言描述的结构:
sys/stat.h
我先用手写 我的初始结构描述:
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;
};
提供字段名称及其类型(以'''\
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}}}\
'''
模块格式)
然后解析器(使用PLY)解析它并返回一个对象,该对象可以将二进制结构转换为namedtuple,其中包含正确名称的整个结构,所有内部结构也都是名称元组(struct
格式字符串来作为子产品)
- 所有正确管理显式填充
- 可以使用命名结构(示例中为struct
),必须在使用前声明它们
- 数组(简单元素或命名结构)通过在类型之前添加数字来实现(S4
< => a : 4 h
或short a[4];
< => {{1}在示例中)
但它不是一个单一的文件轻量级解决方案,它需要大量的评论和文档。另一种方法是直接使用x : 2 S4
和原始的C头文件,但它会更复杂,我不太喜欢导入标准C库头的想法。无法解释原因,但我觉得不那么健壮。
以下是使用示例:
struct S4 x[2];
如果您有兴趣,我可以把它放在github或......
让我们回到困难的部分。我做了一些研究,但现在我只能给你一些可能的指示,而且还会有繁重的工作。您可以尝试使用pycparser但它只知道C(而不是C ++)并且文档不是那么广泛。或者如果您需要C ++,您可以尝试在Eli Bendersky(pycparser的作者)网站Parsing C++ in Python with Clang 中提出的CLang Python绑定,但这里的文档几乎不存在。
现在对于代码,即使我仍然认为它是一项正在进行的工作,因为缺乏评论和正式测试 - 但它确实有效
h_lexer.py
pycparser
h_yacc.py:
>>> 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
h_named_struct.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
答案 1 :(得分:5)
您可以使用cffi
python模块来解释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"
这种方法存在一些缺陷:
cffi
模块依赖于存在的编译器和您
需要使用您的代码为所有平台分发二进制文件尽管如此,它可能会涵盖您的用例。
答案 2 :(得分:1)
这是一个旧线程,但是我遇到了同样的问题,没有找到任何支持嵌套结构的python库。
因此,我编写了pycstruct库。
它支持结构,联合,位域和枚举。它还支持任何级别的所有字节顺序,对齐方式和嵌套。