本文由 loyee 翻译,E文很差,某些词句翻译不当,还请多多指正。译文辛苦,转载请注明出处。

  • 点击此处查看原文
  • 点击下载演示工程 - 8.52 Kb
  • 点击下载源代码 - 21.9 Kb

    Screenshot of the demp

    介绍

      .NET 的 System.Reflection 命名空间提供了完整的反射机制,用于查看程序集的结构。可以通过它获取程序集中的所有类型定义,字段,属性,基本上能满足你所有的要求[包括方法定义,译者注]。尽管如此,某些内容仍是看不见的,方法体中的代码就是如此。

      当你仔细查看代码的时候,可能想知道方法体内使用过的变量和其生命周期以及产生了什么结果。Microsoft 忽视了这个需求,但还是提供了一些东西:IL代码。但这是不够的,实际上是一个字节数组,对于未经专业训练的普通程序员来讲没有任何意义。

      我们需要的是一系列的,表现为IL编码形式的实际的操作指令,这正我下面要讲到的。

    背景

      任何程序员使用过反射,就一定听说过由Lutz Roeder写的让人生畏的 Reflector 。Reflector 能将任何.NET 程序集反编译成与源代码几乎一致的代码。

      你一定对我说的“几乎一致”有疑问,因为反射机制不能为你提供最初的源代码。编译器首先移除了所有代码注释,甚至包括从不使用的变量,并且只有有效的和必须的代码被编译到程序中。因而我们不能获得最精确的源代码。

      好了,Reflector 是非常不错。但是我们希望在自己的代码中也能获得如 Reflector 这样的效果,我们如何能做到?

      首先使用一个典型的例子“Hello world”,由此看看我们希望的结果和利用 .Net Framework 得到的实际的结果:

    C#代码:

    public void SayHello()
    {
        Console.Out.WriteLine(
    "Hello world");
    }


      当我们采用反射方法获取到 SayHello 方法体中的 IL 代码,得到的是一个字节数组,如下:

    0,40,52,0,0,10,114,85,1,0,112,111,53,0,0,10,0,42

      OK,这样很难读懂。我们所知道的是要将这些IL代码转换,以便我们能处理[阅读或理解,译者注]它。最容易的方法是将其转换为MSIL(Microsoft Intermediate Language)语言。下面是SayHello方法体转换而来的 MSIL 代码(我的程序集应该返回的结果):

    0000 : nop0001 : call System.IO.TextWriter System.Console::get_Out()0006 : ldstr "Hello world"0011 : callvirt instance System.Void System.IO.TextWriter::WriteLine()0016 : nop0017 : ret

    使用代码

    SDILReader 是一个仅仅包含了三个类的动态库文件。为了获得方法体的MSIL代码,需要创建一个MethodBodyReader对象的实例,由它构造你想分解的对象的MethodInfo实例。

    MethodInfo mi = null;
    // obtain somehow the method info of the method we want to dissasemble
    // ussually you open the assembly, get the module, get the type and then the 
    // method from that type 
    // 

    // instanciate a method body reader
    SDILReader.MethodBodyReader mr = new MethodBodyReader(mi);
    // get the text representation of the msil
    string msil = mr.GetBodyCode();
    // or parse the list of instructions of the MSIL
    for (int i=0; i<mr.instructions.Count;i++)
    {
    // do something with mr.instructions[i]
    }

     

    如何工作?

      很好,这是一个不错的问题。为了开始工作[不知道get started如何翻译,译者注],我们首先需要知道由 .Net 的反射机制获得的IL代码结构。

    IL 代码结构

      IL实际上是执行操作的指令的枚举。一个指令包含两部分<指令代码 [后文中的操作符也表示指令代码,译者注],操作数>。指令代码即System.Reflection.Emit.OpCode的二进制值,而操作数是对其作用的实体的元数据地址(方法、类型、值,等等)。这个地址在.NET Framework中称为元数据标志。

      因此,为了解释这串IL字节,我们必须像这样做:

    • 得到下一个字节,并且查看其操作符;
    • 根据操作符,获取后续1到4个字节中定义的元数据地址;
    • 采用MethodInfo.Module对象获取被定义在该元数据地址的对象;
    • 保存指令对<操作符,操作数>.
    • 重复上述工作,直到解释完这串IL代码。

    ILInstruction

      ILInstruction 类用于存储指令对<操作符,操作数>。同样,我们在类中提供了一个简单的方法,把内部的信息转换成一个易读的字符串。

    MethodBodyReader

      MethodBodyReader 类做所有实际的工作。在构造方法中,调用了私有方法ConstructInstructions,该方法解析IL代码:

    int position = 0;
    instructions 
    = new List<ILInstruction>();
    while (position < il.Length)
    {
      ILInstruction instruction 
    = new ILInstruction();
      
    // get the operation code of the current instruction
      OpCode code = OpCodes.Nop;
      
    ushort value = il[position++];
      
    if (value != 0xfe)
      
    {
        code 
    = Globals.singleByteOpCodes[(int)value];
      }

      
    else
      
    {
        value 
    = il[position++];
        code 
    = Globals.multiByteOpCodes[(int)value];
        value 
    = (ushort)(value | 0xfe00);
      }

      instruction.Code 
    = code;
      instruction.Offset 
    = position - 1;
      
    int metadataToken = 0;
      
    // get the operand of the current operation
      switch (code.OperandType)
      
    {
        
    case OperandType.InlineBrTarget:
          metadataToken 
    = ReadInt32(il, ref position);
          metadataToken 
    += position;
          instruction.Operand 
    = metadataToken;
          
    break;
        
    case OperandType.InlineField:
          metadataToken 
    = ReadInt32(il, ref position);
          instruction.Operand 
    = module.ResolveField(metadataToken);
          
    break;
        .
      }

      instructions.Add(instruction);
    }

      我们在这里看到的是解析IL的简单循环。其实,它并不简单,其中包括了18个case语句。我没有考虑所有的操作符,仅仅包含了最常用的一部分(实际上有多于240个的操作符)。 操作符在启动应用程序时被载入到两个静态列表中:

    public static OpCode[] multiByteOpCodes;

    public static OpCode[] singleByteOpCodes; public static void LoadOpCodes()
    {
      singleByteOpCodes 
    = new OpCode[0x100];
      multiByteOpCodes 
    = new OpCode[0x100];
      FieldInfo[] infoArray1 
    = typeof(OpCodes).GetFields();
      
    for (int num1 = 0; num1 < infoArray1.Length; num1++)
      
    {
        FieldInfo info1 
    = infoArray1[num1];
        
    if (info1.FieldType == typeof(OpCode))
        
    {
          OpCode code1 
    = (OpCode)info1.GetValue(null);
          
    ushort num2 = (ushort)code1.Value;
          
    if (num2 < 0x100)
          
    {
            singleByteOpCodes[(
    int)num2] = code1;
          }

          
    else
          
    {
            
    if ((num2 & 0xff00!= 0xfe00)
            
    {
              
    throw new Exception("Invalid OpCode.");
            }

            multiByteOpCodes[num2 
    & 0xff= code1;
          }

        }

      }

    }

      构造MethodBodyReader实例后,我们可以利用它任意解析指令清单,获得其字符串表现形式。就是它,有趣的反编译。