最近幫朋友做個(gè)東西,,由于歷史原因,,有一些文件沒(méi)有代碼,,但是又需要修改,。這就需要干吃力不討好的事情了——反編譯,。Google了一圈,,發(fā)現(xiàn).NET平臺(tái)比較流行的也就是mono項(xiàng)目里的神器——Cecil了。原沒(méi)Java平臺(tái)的Javassist用起來(lái)里面爽,,這里是一個(gè)應(yīng)用,。最不爽的是Cecil官方基本沒(méi)什么文檔,只有一個(gè)非常簡(jiǎn)單的介紹和示例,。網(wǎng)上相關(guān)的文章也都是基于0.6版本的,,不幸中大幸就是官方出了文檔說(shuō)明了0.6和0.9之間的差別了。Cecil非常讓人不爽的一點(diǎn)是不能直接插入或修改C#代碼,,只能插入和修改IL代碼,,悲摧的。
reflector之前有個(gè)非常強(qiáng)悍的插件叫Reflexil,,也是基于Cecil實(shí)現(xiàn)的,,可以非常方便地刪除,重命名,,注入方法的,,可惜和新版的reflector不兼容了。再說(shuō)reflector開(kāi)始收費(fèi)了,,之前的免費(fèi)版本也不能用了。這時(shí)SharpDevelop的ILSpy就應(yīng)運(yùn)而生了,可惜剛出來(lái)沒(méi)多久,,相關(guān)的插件很少,。好吧,其實(shí)只有兩個(gè),,一個(gè)還不能用,,另一個(gè)也沒(méi)試。廢話不多說(shuō)了,,下面進(jìn)入正題,。
由于注入方法需要通過(guò)IL代碼來(lái)實(shí)現(xiàn)。下面這個(gè)方法可以把執(zhí)行過(guò)程打印出來(lái),,幫助進(jìn)行下面的操作,。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public static void PrintTypes(string fileName) { var asm = AssemblyDefinition.ReadAssembly(fileName); foreach (TypeDefinition type in asm.MainModule.Types) { if (type.Name.Equals("<Module>")) continue; Console.WriteLine("ClassName is : {0}", type.Name); foreach (MethodDefinition method in type.Methods) { Console.WriteLine("{0} .maxstack {1}", method.FullName, method.Body.MaxStackSize); foreach (var ins in method.Body.Instructions) { Console.WriteLine("L_{0}: {1} {2}", ins.Offset.ToString("x4"), ins.OpCode.Name, ins.Operand is String ? String.Format("\"{0}\"", ins.Operand) : ins.Operand); } } Console.WriteLine("============================"); } } |
新建一個(gè)工程,生成TestCecil.dll
供測(cè)試
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
using System; namespace TestCecil { public class Class1 { public void Test() { string t = "test_xxxx"; t = t.Replace("_", "."); Console.WriteLine(t); } public void Test1(string content) { Console.WriteLine(content); } } } |
在已有方法中注入新的操作的過(guò)程
1 2 3 4 5 |
var processor = method.Body.GetILProcessor(); var newInstruction = processor.Create(OpCodes.Call, someMethodReference); var firstInstruction = method.Body.Instructions[0]; processor.InsertBefore(firstInstruction, newInstruction); |
具體示例,,在類TestCecil.Class1
的Test
方法中的第一行,,打印字符I am a test message !!!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
static void Main(string[] args) { var asm = AssemblyDefinition.ReadAssembly("TestCecil.dll"); var type = asm.MainModule.GetType("TestCecil.Class1"); var method = type.Methods[0]; var processor = method.Body.GetILProcessor(); var ldstr = processor.Create(OpCodes.Ldstr, "I am a test message !!!"); var call = processor.Create(OpCodes.Call, method.Module.Import( typeof(Console).GetMethod("WriteLine", new[] { typeof(string) }))); processor.InsertBefore(method.Body.Instructions[0], ldstr); processor.InsertAfter(method.Body.Instructions[0], call); asm.Write("TestCecil.patch.dll"); PrintTypes("TestCecil.patch.dll"); } |
在類TestCecil.Class1
中注入Test2(string)
方法
1 2 3 4 5 6 7 8 9 10 11 |
static void Main(string[] args) { var asm = AssemblyDefinition.ReadAssembly("TestCecil.dll"); var module = asm.Modules[0]; var type = module.Types.FirstOrDefault(m => m.Name == "Class1"); var method = new MethodDefinition("Test2", MethodAttributes.Public,module.Import(typeof(void))); type.Methods.Add(method); method.Parameters.Add(new ParameterDefinition(module.Import(typeof(string)))); asm.Write("TestCecil.patch.dll"); PrintTypes("TestCecil.patch.dll"); } |
移除一個(gè)現(xiàn)有方法
1 2 3 4 5 6 7 8 |
public static void RemoveMethod(TypeDefinition self, string methodName) { var method = self.Methods.FirstOrDefault(m => m.Name == methodName); if (method != null) { self.Methods.Remove(method); } } |
具體的使用方法,移除類TestCecil.Class1
中的Test
方法
1 2 3 4 5 6 7 8 |
static void Main(string[] args) { var asm = AssemblyDefinition.ReadAssembly("TestCecil.dll"); TypeDefinition type = asm.MainModule.GetType("TestCecil.Class1"); RemoveMethod(type, "Test"); asm.Write("TestCecil.patch.dll"); PrintTypes("TestCecil.patch.dll"); } |
這里只是一些簡(jiǎn)單的示例,,常見(jiàn)使用都包括了,。不過(guò)這種操作只能用來(lái)救急用的,偶爾干干還可以,,要是在生產(chǎn)環(huán)境中用此方法會(huì)死人的-_-||
--EOF--