This article is translated by loyee. e is poor, and some words are incorrectly translated. please correct me more. The translation is hard. Please indicate the source for reprinting.
Click to download source code-21.9 KBIntroduction. NetSystem.Reflection
The namespace provides a complete reflection mechanism for viewing the structure of an assembly. You can use it to obtain all types of definitions, fields, and attributes in the Assembly, which can basically meet all your requirements [including method definition, Translator's note]. Despite this, some content is still invisible, and the code in the method body is like this.
When you carefully check the code, you may want to know the variables used in the method body, their lifecycles, and what results are generated. Microsoft ignored this requirement, but provided some things: il code. But this is not enough. It is actually a byte array, which makes no sense for ordinary programmers without professional training.
What we need is a series of actual operation commands in the form of IL encoding. This is what I will talk about below.
BackgroundAny programmer who has used reflection must have heard of the daunting reflector written by Lutz roeder. Reflector can decompile any. Net assembly into code that is almost the same as the source code.
You must have doubts about the "almost consistent" I said, because the reflection mechanism cannot provide you with the initial source code. The compiler first removes all code comments, even including unused variables, and only valid and necessary code is compiled into the program. Therefore, we cannot obtain the most accurate source code.
Okay, reflector is very good. But how can we achieve this in our own code?
First, we use a typical example "Hello World" to see the expected results and the actual results obtained by using the. NET Framework:
C # code:
public void SayHello()
{
Console.Out.WriteLine("Hello world");
}
When we use the reflection method to obtain the Il code in the sayhello method body, we get a byte array, as shown below:
0,40,52,0,0,10,114,85,1,0,112,111,53,0,0,10,0,42
OK. This is hard to understand. All we know is to convert these il codes so that we can handle [read or understand, Translator's note] it. The easiest way is to convert it to the msil (Microsoft intermediate language) language. The following is the msil code converted from the sayhello method body (the result of my assembly should be returned ):
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
Use CodeSdilreader is a dynamic library file that only contains three classes. To obtain the msil code of the method body, you need to createAn instance of the methodbodyreader object that constructs
MethodInfo
Instance.
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
// Method from that type
//
// Instancate 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]
}
How to work?Good. This is a good question. To get started [do not know how to translate get started], we first need to know the Il code structure obtained by the. NET reflection mechanism.
Il code structureIl is actually an enumeration of commands that execute operations. A command contains two parts:<Command code[The following operators also represent the instruction code. Note:], Operand>. Command code isThe binary value of system. reflection. emit. opcode, And the operand is the metadata address (method, type, value, and so on) of the entity to which it acts)
. This address is called a metadata flag in. NET Framework.
Therefore, to interpret this string of IL bytes, we must do the following:
- Get the next byte and view its operator;
- Obtain the metadata address defined in 1 to 4 bytes based on the operator;
- Use
MethodInfo.Module
Get the object defined in the metadata address;
- Save the <operator, operand>.
- Repeat the above work until the string of IL Code is fully explained.
Ilinstruction ILInstruction
Class is used to store the <operator, operand> of an instruction pair. Similarly, we provide a simple method in the class to convert internal information into a readable string.
Methodbodyreader MethodBodyReader
Class to do all the actual work. The private method is called in the constructor.ConstructInstructions
This method parses the Il code:
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);
}
Here we can see a simple loop for parsing Il. In fact, it is not simple, including 18 case statements. I did not consider all operators, but included only the most common part (actually there are more than 240 operators ). The operator is loaded into two static lists when an application is started:
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;
}
}
}
}After constructing a methodbodyreader instance, we can use it to parse the command list to obtain its string representation. It is an interesting decompilation.