为什么人们要拆解.NET(CLR)二进制文件?

4

我对.NET还比较新,但不是编程新手,我有些困惑于关于反汇编已编译的.NET代码的趋势和兴奋。这似乎毫无意义。

.NET的高级易用性是我使用它的原因。我曾在资源有限的环境中编写过C和真正的(硬件处理器)汇编语言。那是花费大量精力处理许多细节以提高效率的原因。在.NET领域,如果您浪费时间深入到实现的最神秘细节中,那么这有点违背了拥有高级面向对象语言的目的。在使用.NET的过程中,我已经通过阅读自己的源代码调试了通常的性能问题和奇怪的竞争条件,而且从未考虑过编译器正在生成哪种中间语言。例如,显然for(;;)循环将比在数组上使用foreach()更快,考虑到foreach()将使用一个枚举对象来调用每个下一个时间的方法,而不是简单地增加一个变量,这可以通过运行几百万次的紧密循环轻松证明(不需要反汇编)。

真正让IL的反汇编变得愚蠢的是它不是真正的机器代码。它是虚拟机代码。我听说有些人甚至喜欢移动指令以进行优化。你在开玩笑吧?即时编译的虚拟机代码甚至无法以本地编译代码的速度执行简单的紧密的for(;;)循环。如果您想从处理器中挤出每一个周期,那么请使用C/C++并花时间学习真正的汇编语言。这样,您花费在理解许多低级细节上的时间实际上将是值得的。

那么,除了手头太闲之外,为什么人们要反汇编.NET(CLR)二进制文件呢?


19
foreach语句在编译时已知数组的情况下并不使用枚举器。尝试反编译一些使用数组的代码以进行验证 :) - Jon Skeet
1
+1 @Jon,很好的例子,说明反编译可以帮助理解!-) - Alex Martelli
正如Jon所说,foreach不使用枚举器。请参见下面的答案以获取我的基准测试(和反编译代码),并告诉我它与您的有何不同。 - TheSoftwareJedi
1
我发现在将一个 int[] 强制转换为 Enumerable 后枚举它实际上比 for 循环更快。请看下面的示例。 - TheSoftwareJedi
如果你能少一点傲慢,我认为这篇文章会显得不那么无知。 - Jonathon Reinhart
显示剩余2条评论
13个回答

7
了解不同高级语言的编译器实际上是如何处理您的源代码的,这是您向掌握某个环境迈进的重要技能,就像了解数据库引擎将如何执行您提供的各种SQL查询一样。要在娴熟掌握某种抽象水平的使用方式,了解(至少)其下面一层级别的知识是相当有益的;例如,请参阅关于抽象主题的我的演讲笔记以及那场演讲的幻灯片,链接如下:some notes on my talk on the subject of abstractionthat talk的幻灯片,以及 Joel Spolsky 的“泄漏的抽象法则”(我在演讲中提到)。

我非常支持了解事物背后的工作原理,但是现在你提到了数据库引擎,我写的前几个SQL查询语句只是通过观察它们并思考数据库引擎必须使用的唯一逻辑算法,就使它们运行速度快了几个数量级。例如,不需要跟踪或查看数据库引擎源代码,就可以意识到从未建立索引的表中删除一行会比从已建立索引的表中删除一行要慢。良好的设计是常识。 - user114881
@unknown (yahoo) - 相信,但要验证。 - TheSoftwareJedi
我不认为这是一个有效的论点,因为最好使用提供的MSIL反汇编器(Ildasm.exe),而不是.NET Reflector。 - AMissico

3

当源代码丢失或特定标记发布的版本控制中的内容似乎与已发货的二进制文件不对应时,我会使用它。


我过去也曾反编译过,以找出某些加密算法的应用方式。 - tom

2

刚刚完成了为期四天的安全软件开发课程,我认为很多人会反编译源代码以查找其中的漏洞。了解客户端应用程序的源代码可以帮助计划对服务器的攻击。

当然,一些小工具之类的应用程序是没有这样的问题的。

如果我没记错的话,有一个应用程序可以混淆你的.NET二进制文件。我相信它叫做dotfuscator。


2
了解如何使用文档不完善的接口。
(遗憾的是,在基于.net的工具(如BizTalk或WCF)中,通常只有生成的通用文档,因此有时需要反汇编到C#才能查看方法正在做什么,以及在哪种上下文中使用它。)

哈哈!这很有趣。面向对象编程的目标之一不是“将接口与实现分离”吗? - user114881
我不会说这是面向对象编程特有的目标。面向对象编程提供了一些工具,可以帮助我们创建易于理解和方便的接口,但必须以此为目标使用(仅使用面向对象编程并不能自动保证自说明的接口)。因此,需要使用反射器作为文档的替代品绝对是一个有缺陷的接口的标志,但只要这些有缺陷的接口存在,它就是一个必要的恶。 - ckarras

1

实际上,对于int[]的foreach循环会被编译成for语句。如果我们将其强制转换为可枚举对象,你是正确的,它会使用一个枚举器。然而,这个奇怪的做法使它更快,因为没有增加临时整数。

为了证明这一点,我们使用基准测试和反编译来增加理解...

所以我认为通过提出这个问题,你已经自己回答了它。

如果这个基准测试与你的不同,请告诉我如何不同。我尝试过使用对象数组、null等等...

代码:

    static void Main(string[] args)
    {

        int[] ints = Enumerable.Repeat(1, 50000000).ToArray();

        while (true)
        {
            DateTime now = DateTime.Now;
            for (int i = 0; i < ints.Length; i++)
            {
                //nothing really
            }
            Console.WriteLine("for loop: " + (DateTime.Now - now));

            now = DateTime.Now;
            for (int i = 0; i < ints.Length; i++)
            {
                int nothing = ints[i];
            }
            Console.WriteLine("for loop with assignment: " + (DateTime.Now - now));

            now = DateTime.Now;
            foreach (int i in ints)
            {
                //nothing really
            }
            Console.WriteLine("foreach: " + (DateTime.Now - now));

            now = DateTime.Now;
            foreach (int i in (IEnumerable<int>)ints)
            {
                //nothing really
            }
            Console.WriteLine("foreach casted to IEnumerable<int>: " + (DateTime.Now - now));
        }

    }

结果:

for loop: 00:00:00.0273438
for loop with assignment: 00:00:00.0712890
foreach: 00:00:00.0693359
foreach casted to IEnumerable<int>: 00:00:00.6103516
for loop: 00:00:00.0273437
for loop with assignment: 00:00:00.0683594
foreach: 00:00:00.0703125
foreach casted to IEnumerable<int>: 00:00:00.6250000
for loop: 00:00:00.0273437
for loop with assignment: 00:00:00.0683594
foreach: 00:00:00.0683593
foreach casted to IEnumerable<int>: 00:00:00.6035157
for loop: 00:00:00.0283203
for loop with assignment: 00:00:00.0771484
foreach: 00:00:00.0771484
foreach casted to IEnumerable<int>: 00:00:00.6005859
for loop: 00:00:00.0273438
for loop with assignment: 00:00:00.0722656
foreach: 00:00:00.0712891
foreach casted to IEnumerable<int>: 00:00:00.6210938

反编译(请注意,空的 foreach 循环必须添加变量赋值...这是我们的空 for 循环没有但显然需要的):

private static void Main(string[] args)
{
    int[] ints = Enumerable.Repeat<int>(1, 0x2faf080).ToArray<int>();
    while (true)
    {
        DateTime now = DateTime.Now;
        for (int i = 0; i < ints.Length; i++)
        {
        }
        Console.WriteLine("for loop: " + ((TimeSpan) (DateTime.Now - now)));
        now = DateTime.Now;
        for (int i = 0; i < ints.Length; i++)
        {
            int num1 = ints[i];
        }
        Console.WriteLine("for loop with assignment: " + ((TimeSpan) (DateTime.Now - now)));
        now = DateTime.Now;
        int[] CS$6$0000 = ints;
        for (int CS$7$0001 = 0; CS$7$0001 < CS$6$0000.Length; CS$7$0001++)
        {
            int num2 = CS$6$0000[CS$7$0001];
        }
        Console.WriteLine("foreach: " + ((TimeSpan) (DateTime.Now - now)));
        now = DateTime.Now;
        using (IEnumerator<int> CS$5$0002 = ((IEnumerable<int>) ints).GetEnumerator())
        {
            while (CS$5$0002.MoveNext())
            {
                int current = CS$5$0002.Current;
            }
        }
        Console.WriteLine("foreach casted to IEnumerable<int>: " + ((TimeSpan) (DateTime.Now - now)));
    }
}

1
每种 .NET 语言都实现了自己的 CLR 功能子集。了解 CLR 能够做到当前使用的语言无法做到的事情,可以让您明智地决定是否更改语言、发出 IL 或找到其他方法。
你认为人们这样做的唯一原因是因为他们有太多时间,这种假设是侮辱性和无知的。

我相信作者的假设是正确的。只有在你真正需要进入其中或者你真的很好奇(这需要时间)时,你才会进行反汇编。如果某些东西不能直接使用,我宁愿尝试不同的方法,而不是花费不可预测的时间来拆解东西。大多数人拆解库、修补可执行文件、修改手机固件等,要么非常好奇并且有太多的时间,要么是期望获得一些利润的黑客。 - User
@Mastermind,我明白你的意思。我不仅指反汇编,还包括了对CLR的理解,它是一个独立于任何特定.NET语言的实体。也许这似乎离题了,但我认为这很重要。 - overslacked
我所质疑的是CLR的目的与.NET类及使用它们的语言的目的。拥有一个可以移植到不同操作系统的高级抽象是有用的,但是如果将更低级别的细节引入到该抽象中,则对于普通人来说它变得越来越不实用。如果在不同的.NET语言中确实存在CLR支持方面的差异,那么我认为.NET正在失败。它只是让世界变得像以前一样复杂,只不过在这个过程中消耗了更多的CPU周期和内存。 - user114881
@unknown - CLR就像你所说的,是一个虚拟机。然而,它的功能并不特定于任何.NET语言(就像传统的硬件指令集不特定于高级语言一样)-每种语言的编译器都要生成CLR兼容的MSIL。我认为这证明了一个强大的架构,考虑到你的硬件背景,我很惊讶你没有更加欣赏它。 - overslacked

1

为了找出库中的错误并解决它们。

例如:如果没有反射,您将无法远程抛出异常并重新抛出它而不会破坏其回溯。然而,框架可以做到这一点。


1
从你的问题来看,似乎你不知道反射器可以将CLR程序集反汇编为C#或VB代码,因此你基本上可以看到原始代码,而不是IL!

你在代码中看到了IL。你所看到的代码可能与原始代码有很大不同。 - AMissico

1

学习。

文章很好,但它们并没有呈现出生产代码。如果没有 .NET Reflector,我需要花费几周时间才能弄清楚 Microsoft 如何在 FileSystemWatcher 组件中实现事件。相反,只需要几个小时,我就能完成我的 FileSystemSearcher 组件。


0

我自己也常常想这个问题... :)

有时候需要了解特定库方法的工作原理或者为什么它会以这种方式工作。可能会出现文档对该函数的描述不够清晰,或者存在一些需要调查的奇怪行为。在这种情况下,有些人会去反汇编库,查看某些方法内部的调用。

至于优化方面,我从未听说过。我认为试图优化MIL是愚蠢的,因为它将被传递给一个翻译器,后者将以相当高的效率生成真正的机器代码,而你的“优化”可能会被忽略。


我曾经从一个网站上听说过优化,但我没有收藏它,也找不到了。这让我想知道如何实际应用。当源代码的新版本发布时,那个人会手动修改IL吗?这让我想知道人们是否编写内联IL汇编!实际上,请查看这个网站:http://www.partario.com/blog/2009/04/inline-il-assembly.html天啊,.NET是用于快速应用程序开发的,不是吗?阅读/编写IL或C#/VB.NET哪个更快? - user114881

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