正在转换结构数据的字节序

Converting endianness of struct-Data

本文关键字:字节 数据 结构 转换      更新时间:2023-10-16

我拥有的是:

  • 一个十六进制文件,其中包含c指令的字节,以big-endian排序
  • 结构定义为*.h文件
  • 结构信息为侏儒2调试信息
  • 我的应用程序必须用C/C++编写。使用例如python的中间脚本就可以了

我要做的是读取十六进制文件的字节,并将其转换为系统上的结构类型,该系统是小端序。在这个过程中,我必须反转每个结构成员的字节。

显而易见的解决方案是编写一个转换函数,为每个结构成员进行字节交换,但由于结构有多个层和大约1200个成员,它们的变化速度比我更新转换函数的速度快,因此手动编写转换函数不是解决方案。

因此,我可以通过以下方式自动生成转换函数:

  1. 查找和解析多个*.h文件中的类型
  2. 迭代所有结构类型的成员并为它们生成交换->而不需要某种反射api——这并不容易)
  3. 通过转换函数加载结构

由于这个解决方案似乎需要做很多工作,我想知道是否有更简单的方法,比如告诉编译器交换它或以某种方式使用调试信息。

有人知道在这种情况下可能有用的技巧吗?

谢谢和问候!

备注:无法更改导致此情况的任何流程/更改输入条件或将责任委托给其他相关开发人员。

  • 将十六进制文件的某些内容更改为输入是不可能的。此文件来自其他系统,这些系统不会在此处更改以解决此问题
  • 填充、数据类型大小等是相同的。其他措施也确保了这一点。因此,endianes是唯一的防御问题。这也是为什么我认为没有理由反对使用侏儒2信息来识别每个结构成员的字节。
    • 我同意结构的布局非常糟糕。但它之所以会这样,有一些原因,简而言之,由于流程原因和向后兼容性,我无论如何都可以/不允许不更改

要提供更多范围:

所有这些所使用的软件都部署到多个不同的嵌入式设备(多种类型)。十六进制文件包含软件的校准信息,因此存储在只能输出该十六进制文件的特定系统中。我现在正在将软件移植到一个小的endian设备上,我必须使用软件的"主"分支(big-endian)提供的十六进制文件作为输入。

没有办法告诉C或C++编译器自动将字节从LE交换到BE,反之亦然。你真的必须自己做。如果您的数据结构非常庞大,那么最好的方法可能是实现自动转换代码生成。

据我所知,这个问题很棘手,但很容易处理。据我所知,数据提取不会在嵌入式设备上运行,因此不会受到资源限制。我说——接受桌面硬件允许的运行时低效率,转而选择易于调试的方式。

与其将源文件视为">几乎我需要模化几个小调整的东西",不如将其视为"具有开放端、不断发展的模式的通用二进制文件"。模式描述是DWARF数据。

我要做的是:启动一个Python项目。使用pyelftoolsPyPI模块解析DWARF。滚动查找编译单位(CU)。在每个CU中,滚动浏览顶级条目(DIE)。查找具有特定值DW_AT_nameDW_TAG_structure_typeDIE(我希望结构名称提前知道)。然后通过DW_TAG_member子DIE。DW_AT_data_member_location将为您提供偏移量,让您围绕填充进行操作。查看DW_AT_type以检测成员类型(为此,您必须解析DIE引用)。根据需要递归到结构类型和数组类型的成员中。

由此,为struct.unpack方法生成一个格式字符串——它可以无缝地读取big-endian-int。然后使用struct.pack将其格式化为C++使用者期望的任何格式。

这取决于您是否能够将数据文件跟踪到生成可执行文件的DWARF信息,完全相同的构建。我希望该组织的程序允许这样做。

GCC的最新版本允许为使用杂注scalar_storage_order的源代码段或使用具有相同标识符的属性的特定类型声明所需的endianness,而不考虑目标平台。主要捕获:g++不支持此操作。此外,这并不是在所有情况下都有效。例如,将指针指向具有透明endianness转换的成员会导致错误。除非您同意使用C进行结构访问(这完全取决于您当前的代码库),否则这不是一个选项。

持久性布局是基于原始结构布局的,那就这样吧。然而,应该首选更明确的序列化结构的方法,这正是你提到这一点的原因。除了endianness问题外,结构打包还影响兼容性,应该显式指定。对于持久性,1的封装将是最佳的。对于内存中的数据结构,就性能和并发特性而言,这种对齐远非最佳。此外,不同的平台可能具有不兼容的数据类型(例如,64位Linux/Windows上的sizeof(long)-LP64与LLP64)。因此,保持持久性布局与内存中的数据结构分离往往有一长串优点,因此通常会超过必须单独维护序列化代码的缺点。特别是,如果可移植性是一个主要问题。

您可以利用基于C/C++的反射库,也可以自己实现一个。在C的情况下,这肯定需要宏(例如Metaresc)。在C++的情况下,您实际上可能会放弃原来的结构定义(例如Boost.Precisive和Flat Reflection)。

如果反射不是一个选项,则可以通过解析标头或调试符号来生成序列化代码。一般来说,解析C/C++比较复杂。通过将所涉及的结构移动到专用的头中,您可以使用一个简单的C/C++解析器。为了让事情变得更容易,您可以通过基于调试符号处理ptype的gdb输出来简化解析。或者,您可以直接解析调试符号。对于像Python这样的脚本语言,这两种方法都应该是可行的(想到pygccxmlpyelftools)。

与其坚持将生成序列化代码作为构建过程的一部分,您可以一次性生成该代码,并在未来结构发生变化时需要更新。这就是我在多平台场景中要做的。这样做也可以省去实现一个可以处理各种C/C++输入的完美解析器的痛苦,它只需要足够好就可以一次性生成。