65.9K
CodeProject 正在变化。 阅读更多。
Home

BinDiff - 比较二进制文件的工具

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (5投票s)

2012 年 12 月 16 日

CPOL

5分钟阅读

viewsIcon

61003

downloadIcon

1978

比较二进制文件。

介绍 

我需要一个简单的工具来比较两个二进制文件,特别是关注少量添加或删除的字节序列。主要目的是检测音频传输中的变化,这些变化是由音频流经过的多个网关的时钟漂移引起的。

大多数二进制文件比较工具(例如“BinCoMerge”)在比较具有许多细微差别(字节添加或删除)的大文件时非常缓慢。所以我需要一个更快的工具。此外,我想将每次比较的结果保存在单独的文本文件中,以供以后分析。这就是我编写此程序的原因。

它的作用 

BinDiff以16字节为块读取文件1,并尝试在文件2中查找每个块。对于每个块,生成的文本文件将包含一行,显示这些16字节是否找到,如果找到,则显示在何处找到。此过程的思路是,文件1的字节将显示为普通十六进制编辑器中的样子,每行16个字节,而文件2的字节将被移动,以便用户可以轻松比较文件1文件2。此外,生成的文本文件将包含显示文件2中添加的字节的行(要么在第一个找到的块之前,要么在两个找到的块之间,要么在最后一个找到的块之后)。

示例

文件1:  

00000000  00 01 02 03  04 05 06 07   08 09 0A 0B  0C 0D 0E 0F
00000010  17 23 AC F7  59 DE FF DD   5C 74 DF A7  4B D9 58 26
00000020  10 11 12 13  14 15

文件2:   

00000000  FF 00 01 02  03 04 05 06   07 08 09 0A  0B 0C 0D 0E
00000010  0F 57 58 59  17 23 AC F7   59 DE FF DD  5C 74 DF A7
00000020  4B D9 58 26  AA BB CC DD   EE
 

差异

+                                                                     00000000  FF
   00000000  00 01 02 03  04 05 06 07   08 09 0A 0B  0C 0D 0E 0F      00000001  00 01 02 03  04 05 ..
+                                                                     00000011  57 58 59
   00000010  17 23 AC F7  59 DE FF DD   5C 74 DF A7  4B D9 58 26      00000014  17 23 AC F7  59 DE ..
-  00000020  10 11 12 13  14 15
+                                                                     00000024  AA BB CC DD  EE

比较算法

如果找不到块,则用“-”标记,并写在左侧。如果找到块,则不标记,并根据不同的文件位置写在两侧。如果在上一个找到的块和此找到的块之间存在一些字节,则用“+”标记,并写在右侧。同样的情况也会发生在第一个找到的块之前和最后一个找到的块之后。

作为这些要求的結果,函数DiffWriter.WriteDiffThread需要三个子例程,每个例程都可以将16字节(或更少)的块写入文本文件。

  • WriteBlockRemoved(如果找不到块,则用“-”标记)
  • WriteBlockEqual(如果找到块)
  • WriteBlockAdded(如果在两个找到的块之间存在一些字节,则用“+”标记)

这些子例程非常简单,我将不再详细解释。

重要的问题是:DiffWriter.WriteDiffThread函数如何决定是否在文件2中找到了块?这就是FindBlock函数发挥作用的地方。

private static Int32 FindBlock(Int32 nPos1,
                               Int32 nPos2,
                               Byte[] pFile1,
                               Byte[] pFile2,
                               Int32 nBytesToCompare,
                               Boolean bFoundFirstEqual)
{
    Int32 nStartOfEqualBlock2 = -1;

    Boolean bFound = false;
    Int32 i, j, nEqualBytes;

    for (i = nPos2; i < pFile2.Length; i++)
    {
        // Find block at position i:
        nEqualBytes = 0;
        for (j = i; j < (i + nBytesToCompare); j++)
        {
            if (j < pFile2.Length)
            {
                if (pFile2[j] == pFile1[nPos1 + nEqualBytes])
                {
                    nEqualBytes++;
                    if (nEqualBytes == nBytesToCompare)
                    {
                        bFound = true;
                        break;
                    }
                }
                else
                {
                     break;
                }
            }
            else
            {
                break;
            }
        }

        if (bFound) // Found block at position i.
        {
             nStartOfEqualBlock2 = i;
             break;
        }

        // If we have searched 1KB in advance,
        // and didn't find the block,
        // we declare it as "not found".
        // This way, we don't declare a very large block as "added"
        // if the block appears very much later in the file.
        // However, we search THE WHOLE FILE until we found the FIRST EQUAL BLOCK.
        if (bFoundFirstEqual && (i >= (nPos2 + 1024)))
        {
            break;
        }
    }

    return nStartOfEqualBlock2;
}

如您所见,此函数有六个参数。

  • nPos1 ... 文件1中要比较的块开始的位置。
  • nPos2 ... 文件2中开始搜索相等块的位置。
  • pFile1 ... 包含文件1所有字节的字节数组。
  • pFile2 ... 包含文件2所有字节的字节数组。
  • nBytesToCompare ... 要比较的块包含的字节数(必须大于0)。
  • bFoundFirstEqual ... 一个标志,指定在FindBlock的先前调用中是否已找到至少1个相等块。

该函数以1个字节为步长遍历文件2,并比较从循环计数器i开始的nBytesToCompare个字节。如果所有nBytesToCompare个字节都相等,则在该位置i找到了块。在这种情况下,循环结束,并返回该位置。

如果设置了bFoundFirstEqual标志,一旦搜索了超过1KB的数据且未找到相等块,循环就会结束。这意味着:在找到第一个块之后,程序将在接下来的1KB范围内搜索下一个块,如果该块出现在之前找到的块之后超过1KB处,则将其声明为“未找到”(“-”)。这样,如果正在搜索的块在文件2中很晚才找到,程序就不会将大量字节声明为“添加”(“+”)。

DiffWriter.WriteDiffThread函数以16个字节为步长遍历文件1,并始终读取最多16个字节的块。使用对FindBlock的调用来搜索每个块。如果找到了块,则将位置nPos2移动到找到的块之后1个字节,这样就不会找到重复的块。

根据FindBlock的结果,将调用WriteBlockRemovedWriteBlockEqualWriteBlockAdded函数。如果添加的字节数超过16个,WriteBlockAdded将被调用多次。

当文件1的所有块都搜索完毕后,必须写入文件2中剩余的字节。这同样通过调用WriteBlockAdded来完成,从nPos2开始,该位置是最后一个找到的块之后1个字节。

现在两个文件都已完全处理完毕。

BinDiff的局限性 

BinDiff无法比较大于2GB的文件。请注意,在开始比较之前,两个文件都会被加载到内存中。BinDiff仅在文件2不包含大于1KB的添加字节序列时才有效。如果一个连续的添加字节序列大于1KB,那么文件2的其余部分在文本文件中也将被标记为“添加”(“+”)。

线程同步 

MainWindow类负责所有GUI操作,而DiffWriter类负责文件1文件2的比较。

如果用户单击开始按钮,将执行DiffWriter.StartWriteDiff函数。它会打开文件(如果可能),并启动一个名为WriteDiffThread的线程。该线程比较文件1文件2,将结果写入文本文件,并在完成后引发DiffWriter.ThreadEnded事件。

如果用户单击停止按钮或关闭应用程序,将执行DiffWriter.Stop函数。它通过设置变量m_bStop = true来停止线程。线程会意识到m_bStop的变化,并立即停止所有循环。

MainWindow中的事件处理:事件处理程序DiffWriter_ThreadEnded应该使GUI能够启用/禁用按钮和文本框,但它不能直接访问GUI元素,因为它是在GUI线程之外的其他线程中执行的。因此,使用了一个简单的解决方法:

private delegate void ThreadEndedSyncHandler();
private ThreadEndedSyncHandler ThreadEnded;

private void MainWindow_Load(object sender, EventArgs e)
{
    // Subscribe for my own synchronous delegate
    // that will be invoked when the asynchronous event "DiffWriter.ThreadEnded" is raised:
    this.ThreadEnded += this_ThreadEnded;

    // Subscribe for an asynchronous event
    // that will be raised when the DiffWriter's thread has ended:
    DiffWriter.ThreadEnded += DiffWriter_ThreadEnded;
}

private void DiffWriter_ThreadEnded()
{
    if (!m_closing)
    {
        this.Invoke(ThreadEnded);
    }
}

private void this_ThreadEnded()
{
    // Enable all buttons except stop button:
    EnableButtons();
}

事件处理程序DiffWriter_ThreadEnded调用一个名为ThreadEnded的委托,并且this_ThreadEnded处理程序已订阅此委托。Form.Invoke方法会自动处理线程同步,因此可以在this_ThreadEnded内部访问GUI元素。

关注点

您可以使用此程序快速比较具有许多细微差异的大型二进制文件。

历史

  • 2012年12月16日
    • 发布于CodeProject。
© . All rights reserved.