點擊下載原始碼 - 21.9 Kb介紹 .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執行個體後,我們可以利用它任意解析指令清單,獲得其字串表現形式。就是它,有趣的反編譯。