判断.NET程序集是否来自同一源代码

30

有没有办法比较两个.NET程序集以确定它们是否是从“相同”的源文件构建的?

我知道有一些差异工具可用,例如Reflector的插件,但我不想在GUI中查看差异,我只想自动比较一组二进制文件,以查看它们是否是从相同(或等效)的源文件构建的。我了解到多个不同的源文件可能会产生相同的IL,并意识到该过程仅对IL中的差异敏感,而不是原始源。

仅仅比较两个程序集的字节流的主要障碍在于.NET在程序集中包含一个名为“MVID”(模块版本标识符)的字段。这似乎对于每次编译都具有不同的值,因此如果您两次构建相同的代码,则程序集将不同。

相关问题是,有人知道如何强制MVID在每次编译时保持相同?这将避免我们需要具有对MVID值差异不敏感的比较过程。一致的MVID更好,因为这意味着可以使用标准校验和。

背景是,一个第三方公司负责在我们被允许发布到生产环境之前独立审查和签署我们的发行版,这包括审查源代码。他们希望独立确认我们提供的源代码与早期构建、测试和当前计划部署的二进制文件相匹配。我们正在寻找一个过程,允许他们独立从我们提供给他们的源代码中构建系统,然后将校验和与我们已经测试并计划放入生产环境中的二进制文件的校验和进行比较。

顺便说一句,请注意,我们正在使用持续集成、自动化构建、源代码控制等。问题与对给定构建使用了哪些源文件的内部控制不相关。问题在于第三方负责验证我们提供的源代码是否产生与我们测试并计划放入生产环境中的相同的二进制文件。他们不应该信任我们的任何内部系统或控件,包括构建服务器或源代码控制系统。他们只关心获取与构建相关联的源码,自己执行构建,然后验证输出是否与我们要部署的内容相匹配。

比较解决方案的运行速度并不特别重要。

谢谢。


4
如果唯一的区别就是MVID,那么它肯定会出现在字节流的相同位置,您可以让差异算法忽略这些字节位置吗? - Eric J.
是的,没错,但我需要知道文件的结构才能忽略这个字段。你知道有关格式的参考资料吗? - Clayton
这真的可能吗?不同的源代码(C#,VB.NET等)是否会导致相同的二进制文件(或IL代码)?那么它可能不会产生功能上的差异,但仍然会有所不同。 编辑:糟糕,抱歉。刚才看到他们重新构建,然后比较二进制文件。 - Christian.K
他们已经有了源代码,他们将构建它,文件将几乎完全相同...所以我不明白为什么他们必须比较这些可能相同的文件,以使用您提供的版本而不是他们构建的版本。 - Diadistis
7个回答

10

使用命令行工具从IL的文本表示中过滤掉MVID和日期时间戳并不困难。假设file1.exe和file2.exe是由相同的源代码构建而成:

c:\temp> ildasm /all /text file1.exe | find /v "Time-date stamp:" | find /v "MVID" > file1.txt

c:\temp> ildasm /all /text file2.exe | find /v "Time-date stamp:" | find /v "MVID" > file2.txt

c:\temp> fc file1.txt file2.txt

比较文件 file1.txt 和 FILE2.TXT

FC:未发现任何差异


1
我认为这并不是完全最健壮的方法,原因我还无法确定根本原因。为了发现这一点,我基本上构建了我的源代码,并复制了部署文件夹,其中包含所有内容。然后,我删除了部署文件夹的内容并重新构建了源代码。我使用您的技术生成了反汇编文本,但发现两者之间存在差异,超出了您和其他人提供的所有过滤选项。 - jxramos
看起来某些GUID正在更新。_“.field /04000027/ static assembly valuetype '<PrivateImplementationDetails>{A310135E-980F-48EA-A97F-FB0E9C30EA63}'/0200000F//'_StaticArrayInitTypeSize=6'/02000010/ '$$method0x600001d-1' at I_00002CE0”我们的构建有点复杂,将CLI C++交互与.NET和C#合并在一起,涵盖了60多个项目。很遗憾,在生成中无法修复使用的ID。 - jxramos

9

当使用 ILDasm v4.0.319.1 比较类库时,似乎图像基址未初始化。为避免不匹配,请使用经过修订的解决方案:

ildasm /all /text assembly.dll
| find /v "// Time-date stamp:"
| find /v "// MVID:"
| find /v "// Checksum:"
| find /v "// Image base:"
> assembly.dasm

入口点(镜像基址)实际上是可执行程序集的有趣信息,需要仔细验证。注入新的镜像基址是使程序执行完全不同的常见方法。在我的情况下,我正在尝试验证多线程构建的一致性,因此跳过入口点是安全的。

关于性能的说明:我拿了一个大小为8MB的AnyCPU DLL,并运行了ILDasm。生成的文件大小为251MB,制作时间长达数分钟。大约产生了32倍的大小。


8
我使用了Jerry Curry在.Net 4程序集上的解决方案,并发现现在有第三个每次构建都会变化的项目:校验和。在程序集中发现校验和是不是很惊讶?我认为将文件的校验和添加到该文件中会改变校验和...
无论如何,修改后的命令为:
ildasm /all /text "assembly.dll"
| find /v "// Time-date stamp:"
| find /v "// MVID:"
| find /v "// Checksum:"
> assembly.dasm

请注意,我还稍微修改了搜索字符串,添加了斜杠以避免意外匹配。这个命令的行应该在同一行上运行,为了可读性可以拆分。如果文件名包含空格,则需要在它们周围加上双引号。

3
根据你愿意付出的工作量和性能/准确性的重要程度,有几种方法可以实现此目标。Eric J.提出的一种方法是通过比较二进制程序集,排除每次编译都会更改的部分。这种解决方案简单快速,但可能会给出许多错误的负面结果。更好的方法是通过反射来深入了解。如果性能至关重要,您可以从比较类型开始,然后转到成员定义。在检查类型和成员定义以及如果到那个点上一切都相等之后,您可以通过使用GetILAsByteArray方法获取每个方法的实际IL来进一步检查。即使一切都相同但使用了稍微不同的标志或不同版本的编译器进行编译,您仍然会发现差异。我认为最好的解决方案是使用连续集成工具,将构建与源代码控制的变更集编号进行标记(您正在使用一个,对吗?)。
相关文章: 一篇相关文章

你和Eric J关于忽略文件的变量部分是正确的。如果格式已经被记录,那么这很简单,但我还没有找到参考资料。你知道有没有相关的资料吗?关于使用反射,我们倾向于选择最简单的解决方案,因为外部团队需要理解和测试这个工具。如果它由开发团队提供,那么对它会有更大的怀疑,而不是如果软件由第四方提供。忽略文件中的几个字节比使用反射更简单。 - Clayton

3

1
你可以使用反射器差异 AddIn 这里

0

另一个需要考虑的解决方案:

源代码信息在二进制文件以调试模式编译时被存储。然后,您可以检查 pdb 是否与 exe 匹配,以及 pdb 行是否与源代码匹配。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接