您如何调试二进制格式?


11

我希望能够调试构建二进制生成器。现在,我基本上是将输入数据输出到二进制解析器,然后深入代码并打印输入到输出的映射,然后获取输出映射(整数)并使用它来定位对应的整数在二进制文件中。非常笨拙,并且需要我对源代码进行深度修改才能获得输入和输出之间的映射。

似乎您可以查看不同变体形式的二进制文件(在我的情况下,我想以十进制数字形式以8位块形式查看二进制文件,因为它非常接近输入内容)。实际上,有些数字是16位,有些是8位,有些是32位,等等。因此,也许会有一种查看二进制文件的方式,这些内存中的每个不同数字都以某种方式突出显示。

我看到的唯一可能的方法是,如果您实际上构建了特定于实际二进制格式/布局的可视化工具。因此,它知道序列中的32位数字应位于何处,以及8位数字应位于何处,等等。在某些情况下,这是很多工作并且有些棘手。所以想知道是否有通用的方法可以做到这一点。

我也想知道目前调试这种类型的东西的一般方法是什么,所以也许我可以从中得到一些尝试的想法。


75
您得到一个回答,说“直接使用hexdump,并另外执行此操作”,并且该回答得到了很多好评。第二个答案是5小时后(!),只说“使用hexdump”。然后,您接受了第二个而赞成第一个?认真吗
布朗

4
尽管您有充分的理由使用二进制格式,但请考虑是否可以仅使用现有的文本格式(如JSON)代替。人类的可读性非常重要,并且机器和网络通常足够快,以至于如今无需使用自定义格式来减小尺寸。
jpmc26

4
@ jpmc26二进制格式仍然有很多用途,而且将一直如此。人的可读性通常仅次于性能,存储要求和网络性能。而且在很多地方,特别是网络性能很差并且存储受到限制。另外,别忘了所有系统都必须与遗留系统(包括硬件和软件)进行接口并且必须支持其数据格式。
jwenting

4
@jwenting不,实际上,开发人员时间通常是应用程序中最昂贵的部分。当然,如果您在Google或Facebook工作,可能并非如此,但是大多数应用程序都无法达到这种规模。当您的开发人员花时间在东西上是最昂贵的资源时,对于程序进行解析,人类可读性所花费的时间远远超过了100毫秒。
jpmc26

3
@ jpmc26我没有在问题中看到任何暗示我OP是定义格式的东西。
JimmyJames

Answers:


76

对于临时检查,只需使用标准的十六进制转储并学习眼球。

如果您想进行适当的调查,我通常会使用Python之类的代码编写一个单独的解码器-理想情况下,该解码器将直接由消息规范文档或IDL驱动,并尽可能自动化(因此,没有机会手动引入两个解码器中都有相同的错误)。

最后,别忘了您应该使用已知正确的固定输入为解码器编写单元测试。


2
“只要使用标准的六边形倾卸料并学会眼球。” 对。根据我的经验,可以在白板上写下最多200位的多个部分,以进行分组比较,这有时可以帮助您入门。
桅杆

1
如果二进制数据在应用程序(或一般来说,系统)中起着重要的作用,我发现一个值得努力的单独解码器。如果数据格式可变,则更是如此:只需稍加练习即可将固定布局中的数据发现在十六进制转储中,但很快就会触及实用性。我们使用商用数据包解码器调试了USB和CAN流量,然后编写了PROFIBus解码器(变量分布在各个字节之间,在十六进制转储中完全不可读),发现这三个变量都非常有用。
彼得-恢复莫妮卡

10

这样做的第一步是,您需要一种方法来查找或定义描述数据结构(即模式)的语法。

这方面的一个示例是COBOL的语言功能,该功能被非正式地称为copybook。在COBOL程序中,您将定义内存中数据的结构。该结构直接映射到字节的存储方式。这是那个时代的语言所共有的,而不是当代的常见语言,在现代的当代语言中,内存的物理布局是一种实现关注点,是从开发人员那里抽象出来的。

谷歌搜索二进制数据模式语言会发现许多工具。一个示例是Apache DFDL。可能已经有UI了。


2
此功能并非保留给“古老”时代的语言。C和C ++结构和联合可以与内存对齐。C#具有StructLayoutAttribute,我已使用它来传输二进制数据。
卡斯珀·范·登·伯格

1
@KaspervandenBerg除非您说C和C ++最近添加了这些功能,否则我认为这是同一时代。关键是这些格式不仅用于数据传输,还用于数据传输,它们直接映射到代码如何处理内存和磁盘中的数据。通常,尽管新语言可能具有这样的功能,但这并不是它们倾向于工作的方式。
JimmyJames

@KaspervandenBerg C ++并没有您想象的那么多。可以使用特定于实现的工具来对齐和消除填充(而且,公认的是,越来越多的标准为这种事情添加了功能)并且成员顺序是确定性的(但不一定与内存中的相同!)。
Lightness Races in Orbit,

6

ASN.1,抽象语法符号1,提供了一种指定二进制格式的方法。

  • DDT-使用样本数据和单元测试进行开发。
  • 文本转储可能会有所帮助。如果使用XML,则可以折叠/展开子层次结构。
  • ASN.1并不是真正需要的,但是基于语法,更具声明性的文件规范更加容易。

6
如果可以看到ASN.1解析器中永无止境的安全漏洞游行,那么采用它肯定会在调试二进制格式中提供很好的练习。
马克

1
@Mark许多小字节数组(以及在不同层次树中的数组)通常在C中无法正确(安全地)处理(例如,不使用异常)。永远不要低估C.ASN.1的低级别,固有的不安全性-例如,java不会暴露此问题。由于可以安全地完成ASN.1语法定向分析,即使C语言也可以使用较小而安全的代码库完成。漏洞的一部分是二进制格式本身固有的:人们可以利用格式语法的“合法”构造,这些构造具有灾难性的语义。
乔普·艾根

3

其他答案描述了查看十六进制转储,或用JSON写出对象结构。我认为将两者结合起来非常有帮助。

使用可以在十六进制转储顶部呈现JSON的工具非常有用;我编写了一个开源工具,该工具可解析名为dotNetBytes的 .NET二进制文件,这是示例DLL视图

dotNetBytes示例


1

我不确定我是否完全理解,但是听起来您有一个用于这种二进制格式的解析器,并且您可以控制它的代码。因此,此答案基于该假设。

解析器将以某种方式填充结构,类或语言所具有的任何数据结构。如果ToString对要解析的所有内容都实现,那么最终将以一种易于使用且易于维护的方法来以人类可读格式显示该二进制数据。

您基本上将具有:

byte[] arrayOfBytes; // initialized somehow
Object obj = Parser.parse(arrayOfBytes);
Logger.log(obj.ToString());

从使用角度来看,仅此而已。当然,这需要您ToStringObject类/结构/任何东西实现/重写该功能,并且对于任何嵌套的类/结构/任何东西也必须这样做。

您还可以使用条件语句来防止ToString在发行代码中调用该函数,以免浪费时间在调试模式之外无法记录的内容上。

ToString可能看起来像这样:

return String.Format("%d,%d,%d,%d", int32var, int16var, int8var, int32var2);

// OR

return String.Format("%s:%d,%s:%d,%s:%d,%s:%d", varName1, int32var, varName2, int16var, varName3, int8var, varName4, int32var2);

您最初的问题听起来似乎您已经尝试过执行此操作,并且您认为此方法很麻烦,但是您有时还实现了解析二进制格式并创建了变量来存储该数据。因此,您要做的就是以适当的抽象级别(变量所在的类/结构)打印这些现有变量。

这是您只需要做的一次,就可以在构建解析器时做到这一点。而且只有在二进制格式更改时它才会更改(无论如何它已经提示您对解析器进行更改)。

同样,某些语言具有将类转换为XML或JSON的强大功能。C#在这方面特别擅长。您不必放弃二进制格式,只需在调试日志记录语句中执行XML或JSON,而无需考虑发布代码。

我个人建议不要使用十六进制转储路径,因为它容易出错(您是否从正确的字节开始,确定从左到右阅读时是否在“看到”正确的字节序,等等) 。

示例:说出您ToStrings吐出的变量a,b,c,d,e,f,g,h。您运行程序并注意到带有的错误g,但问题实际上是从开始的c(但是您正在调试,因此您尚未弄清楚)。如果知道输入值(并且应该知道),您将立即看到c问题开始的地方。

与仅仅告诉你的十六进制转储相比338E 8455 0000 FF76 0000 E444 ....; 如果您的字段大小不同,从哪里c开始,值是什么-十六进制编辑器会告诉您,但我的意思是,这容易出错并且很耗时。不仅如此,而且您无法通过十六进制查看器轻松/快速地自动化测试。解析数据后打印出一个字符串将准确告诉您程序正在“思考”什么,这将是自动化测试过程中的第一步。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.