Mono.Cecil - 0.6
項目地址:
Mono.Cecil
項目描述: In simple English, with Cecil, you can load existing managed assemblies, browse all the contained types, modify them on the fly and save back to the disk the modified assembly.
類似項目:Microsoft CCI
Common Compiler Infrastructure: Metadata API
Common Compiler Infrastructure: Code Model and AST API
Common Compiler Infrastructure: Sample applications
Common Compiler Infrastructure - Contrib
對比評價:來自 StackOverflow
Mono.Cecil has better, more understandable and easy in use object model. However, I had an ugly bug when used it in my program (reference to the wrong method was saved in the assembly; I think there was some bug with metadata tokens handling)
Microsoft.CCI has an ugly, utterly over-designed object model in the same time lacking many simple features; however, it's more mature than Mono.Cecil. Finally, I abandoned Mono.Cecil and used Microsoft.CCI for my program.
基本示例
Cecil是對已編譯生成IL的程序集進行操作,所以先寫一個簡單的Console exe程序,這里項目名稱使用Cecil.Program:
這個程序集的運行結果如下:
方法SayHello的IL代碼如下:
接下來使用另外一個Console exe程序來修改Cecil.Program.exe,項目名稱使用Cecil:
編譯生成Cecil.exe,然后把Cecil.Program.exe拷貝到這個目錄下,運行Cecil.exe,便會在當前目錄生成Cecil.Program.modified.exe,運行Cecil.Program.modified.exe結果如下:
修改后的方法SayHello的IL代碼如下:
從上面的基本使用方法可以看出,Cecil的確是易於使用,對象模型結構非常實用,這里是官方網站的一個主要對象結構圖:
IL指令的復雜性
在assembly、type、method級別上對程序集做修改是非常簡單的,但是如果要修改方法體的IL代碼,則可能會遇到一些較麻煩的事情,需要細致的處理
例如上面的SayHello方法如果是這樣:
測試代碼這樣來調用:
其運行結果只會輸出一條Hello Cecil !消息,仍然使用Cecil.exe來修改這個程序集,其運行結果如下圖:
調用tt2.SayHello(false);時,應該也會有一個>>Intercepted SayHello消息,但是沒有輸出,對比一下IL代碼就清楚了:
修改后的IL代碼如下:
IL_000b那一句,為false時就直接跳轉到IL_0021這個返回指令上了,不會輸出Intercepted的消息
使用Mono.Cecil也可以修改這個跳轉地址,例如:
項目描述: In simple English, with Cecil, you can load existing managed assemblies, browse all the contained types, modify them on the fly and save back to the disk the modified assembly.
類似項目:Microsoft CCI
Common Compiler Infrastructure: Metadata API
Common Compiler Infrastructure: Code Model and AST API
Common Compiler Infrastructure: Sample applications
Common Compiler Infrastructure - Contrib
對比評價:來自 StackOverflow
Mono.Cecil has better, more understandable and easy in use object model. However, I had an ugly bug when used it in my program (reference to the wrong method was saved in the assembly; I think there was some bug with metadata tokens handling)
Microsoft.CCI has an ugly, utterly over-designed object model in the same time lacking many simple features; however, it's more mature than Mono.Cecil. Finally, I abandoned Mono.Cecil and used Microsoft.CCI for my program.
基本示例
Cecil是對已編譯生成IL的程序集進行操作,所以先寫一個簡單的Console exe程序,這里項目名稱使用Cecil.Program:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
using
System;
using
System.Reflection;
namespace
Cecil.Program
{
class
Program
{
static
void
Main(
string
[] args)
{
TestType tt =
new
TestType();
tt.SayHello();
tt.AboutMe();
Console.ReadKey();
}
}
public
class
TestType
{
[Obsolete]
public
void
SayHello()
{
Console.WriteLine(
"\tHello Cecil !"
);
}
public
void
AboutMe()
{
Type type =
typeof
(TestType);
MethodInfo method = type.GetMethod(
"SayHello"
);
if
(method.IsVirtual)
Console.WriteLine(
"\tI'm a virtual method"
);
else
Console.WriteLine(
"\tI'm a non-virtual method"
);
object
[] attributes = method.GetCustomAttributes(
false
);
if
(attributes !=
null
&& attributes.Length > 0)
{
Console.WriteLine(
"\tI have the following attributes:"
);
foreach
(
object
attr
in
attributes)
Console.WriteLine(
"\t\t"
+ attr.GetType().Name);
}
}
}
}
|

方法SayHello的IL代碼如下:

接下來使用另外一個Console exe程序來修改Cecil.Program.exe,項目名稱使用Cecil:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
|
using
Mono.Cecil;
using
Mono.Cecil.Cil;
AssemblyDefinition assembly = AssemblyFactory.GetAssembly(
"Cecil.Program.exe"
);
TypeDefinition type = assembly.MainModule.Types[
"Cecil.Program.TestType"
];
MethodDefinition sayHello =
null
;
foreach
(MethodDefinition md
in
type.Methods)
if
(md.Name ==
"SayHello"
) sayHello = md;
//Console.WriteLine(string value)方法
MethodInfo writeLine =
typeof
(Console).GetMethod(
"WriteLine"
,
new
Type[] {
typeof
(
string
) });
//Console.WriteLine方法導入MainModule,並返回在AssemblyDefinition中的引用方式
MethodReference writeLineRef = assembly.MainModule.Import(writeLine);
//在SayHello方法開始位置插入一條trace語句
// Console.WriteLine(">>Intercepting ");
//如果插入的語句需要使用函數入參,則必須插入在OpCodes.Ldarg等指令之后
CilWorker worker = sayHello.Body.CilWorker;
Instruction ldstr = worker.Create(OpCodes.Ldstr,
">>Intercepting "
+ sayHello.Name);
Instruction call = worker.Create(OpCodes.Call, writeLineRef);
Instruction first = sayHello.Body.Instructions[0];
worker.InsertBefore(first, call);
worker.InsertBefore(call, ldstr);
//在SayHello方法結束位置插入一條trace語句
// Console.WriteLine(">>Intercepted ");
//語句必須插入在OpCodes.Ret指令的前面
int
offset = sayHello.Body.Instructions.Count - 1;
Instruction last = sayHello.Body.Instructions[offset--];
while
(last.OpCode == OpCodes.Nop || last.OpCode == OpCodes.Ret)
last = sayHello.Body.Instructions[offset--];
ldstr = worker.Create(OpCodes.Ldstr,
">>Intercepted "
+ sayHello.Name);
worker.InsertAfter(last, ldstr);
worker.InsertAfter(ldstr, call);
//把SayHello方法改為虛方法
sayHello.IsVirtual =
true
;
//給SayHello方法添加一個SerializableAttribute
CustomAttribute attribute =
new
CustomAttribute(
assembly.MainModule.Import(
typeof
(SerializableAttribute).GetConstructor(Type.EmptyTypes)
));
sayHello.CustomAttributes.Add(attribute);
AssemblyFactory.SaveAssembly(assembly,
"Cecil.Program.modified.exe"
);
Console.WriteLine(
"Assembly modified successfully!"
);
Console.ReadKey();
|

修改后的方法SayHello的IL代碼如下:

從上面的基本使用方法可以看出,Cecil的確是易於使用,對象模型結構非常實用,這里是官方網站的一個主要對象結構圖:

IL指令的復雜性
在assembly、type、method級別上對程序集做修改是非常簡單的,但是如果要修改方法體的IL代碼,則可能會遇到一些較麻煩的事情,需要細致的處理
例如上面的SayHello方法如果是這樣:
1
2
3
4
5
|
public
void
SayHello(
bool
print)
{
if
(print)
Console.WriteLine(
"\tHello Cecil !"
);
}
|
1
2
3
4
|
TestType2 tt2 =
new
TestType2();
tt2.SayHello(
true
);
tt2.SayHello(
false
);
Console.ReadKey();
|

調用tt2.SayHello(false);時,應該也會有一個>>Intercepted SayHello消息,但是沒有輸出,對比一下IL代碼就清楚了:

修改后的IL代碼如下:

IL_000b那一句,為false時就直接跳轉到IL_0021這個返回指令上了,不會輸出Intercepted的消息
使用Mono.Cecil也可以修改這個跳轉地址,例如:
1
2
3
4
5
|
//得到指令brfalse.s
Instruction jmp = sayHello.Body.Instructions[1];
....
//把跳轉的目標地址改成IL_0017 ldstr指令位置
jmp.Operand = ldstr;
|