【问题标题】:Converting endianness of struct-Data转换结构数据的字节顺序
【发布时间】:2020-09-09 15:17:41
【问题描述】:

我拥有的是:

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

我要做的是读取 hex 文件的字节并将其转换为小端系统上的结构类型。 在这个过程中,我将不得不反转每个结构成员的字节。

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

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

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

由于这个解决方案看起来工作量很大,我一直在徘徊,是否有更简单的方法,比如告诉编译器交换它或以某种方式使用调试信息。

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

感谢和问候!

备注: 更改导致这种情况的任何流程/更改输入条件或将责任委派给所涉及的其他开发人员是不可行的。

  • 无法将有关 hex 文件的某些内容更改为输入。此文件来自其他系统,不会更改以修复此问题。
  • 填充、数据类型大小等是相同的。其他措施也确保了这一点。所以字节顺序无疑是唯一的问题。这也是为什么我认为没有理由反对使用 dwarf2 信息来识别每个结构成员的字节。
    • 我同意结构的布局非常糟糕。但它有一些原因,而且简而言之,由于流程原因和向后兼容性,我可以/不允许不改变它。

提供更多范围:

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

【问题讨论】:

  • 使用适当的序列化库/系统。定义文件格式的语法。
  • 这听起来像是一条很滑的路。如果文件是在具有不同编译器的不同架构上创建的,那么您在这里做出了太多假设。除了字节序之外,您确定结构布局、填充、数据类型大小等相同吗?
  • “结构有多个层和大约 1200 个成员,其变化速度比我更新转换函数的速度要快” 这听起来像是一个非常严重的问题,值得自己解决正确的。抽象在哪里?!
  • 也许你需要温和地向你的同事介绍理智的概念,为了他们好。他们真的认为他们可以将一个巨大的结构二进制转储到一个文件中,而其他人将不得不编写一个软件来将该转储转换为另一个架构,同时跟踪他们所做的更改?在我的店里,任何有这种想法的人都非常欢迎收拾东西并立即离开。
  • "2. 迭代所有结构类型的成员并为它们生成交换" - magic_get 可能可以做到。

标签: c++ c struct dwarf


【解决方案1】:

最新版本的 GCC 允许使用编译指示 scalar_storage_order 或使用具有相同标识符的属性的特定类型来声明所需的字节顺序,而与源代码部分的目标平台无关。主要问题:g++ 不支持这一点。此外,这并非在所有情况下都有效。例如,将指针指向具有透明字节顺序转换的成员会导致错误。除非您可以坚持使用 C 进行结构访问(这完全取决于您当前的代码库),否则这不是一个选项。

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

您可以利用基于 C/C++ 的反射库或自己实现一个。对于 C,这肯定需要宏(例如 Metaresc)。如果是 C++,您实际上可能会摆脱原来的结构定义(例如 Boost.Precise and Flat Reflection)。

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

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

【讨论】:

  • 我不知道scalar_storage_order,所以感谢您指出。但是,我仍然建议不要使用它,除非在由适当的序列化系统创建的自动生成的结构定义中。
  • 你是对的。我也不建议这样做。我将这两个问题(序列化和内存表示)分开。
【解决方案2】:

据我所知,这个问题很棘手,但很容易解决。据我了解,数据提取不会在嵌入式设备上运行,因此不会受到资源限制。我说 - 接受桌面硬件允许的运行时低效率,并改为易于调试。

不要将源文件视为“几乎我需要模数几个小调整”,而是将其视为“具有开放式结尾的通用二进制文件,不断发展 em> 架构”。架构描述是 DWARF 数据。

我会做什么:开始一个 Python 项目。使用pyelftools PyPI 模块解析 DWARF。滚动查看编译单元 (CU)。在每个 CU 中,滚动浏览顶级条目 (DIE)。寻找一个特定值为DW_AT_nameDW_TAG_structure_type DIE(我希望提前知道结构名称)。然后通过 DW_TAG_member 子 DIE。 DW_AT_data_member_location 将为您提供偏移量,让您可以解决填充问题。查看DW_AT_type 以检测成员类型(您必须为此解析DIE 引用)。根据需要递归到结构和数组类型的成员。

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

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

【讨论】:

    【解决方案3】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-02-24
      • 2016-02-01
      • 1970-01-01
      • 2020-10-19
      • 2022-01-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多