はじめてのCecil

Redirecting…

Cecil is a library written by Jb Evain (http://evain.net/blog/) to generate and inspect programs and libraries in the ECMA CIL format. It has full support for generics, and support some debugging symbol format.

ILバイナリをごにょごにょいじくり回せるライブラリです。Mono meetingでよく名前を聞いていましたが使ったことが無かったので試してみました。
サンプルコードをちょっとだけ改変したロギングコードを埋め込む例。(バイナリ書き換えAOPっぽいやつ)

using System;
using Mono.Cecil;
using Mono.Cecil.Cil;
using System.Reflection;

class Program
{
    // メソッド呼び出しのログ処理を埋め込む
    static void Logging (AssemblyDefinition assembly,TypeDefinition type)
    {
        MethodInfo miWriteLine = typeof (Console).GetMethod ("WriteLine",new { typeof (string),typeof (object) });
        MethodInfo miNow = typeof (DateTime).GetMethod ("get_Now",new Type[0]);

        // 型に属する全てのメソッドを書き換える
        foreach (MethodDefinition method in type.Methods)
        {
            CilWorker worker = method.Body.CilWorker;

            // Console.WriterLineとDateTime.Nowを使う
            MethodReference writeLine;
            writeLine = assembly.MainModule.Import (miWriteLine);
            MethodReference now;
            now = assembly.MainModule.Import (miNow);

            // 以下のコードを各メソッドの先頭に割り込ませる
            // Console.WriteLine ("in methodName at {0}.", DateTime.Now);
            Instruction ldstr = worker.Create (OpCodes.Ldstr,"in " + method.Name + " at {0}.");
            Instruction callNow = worker.Create (OpCodes.Call,now);
            Instruction box = worker.Create (OpCodes.Box,assembly.MainModule.Import (typeof (DateTime)));
            Instruction callWriteLine = worker.Create (OpCodes.Call,writeLine);

            // メソッドの先頭
            Instruction ins = method.Body.Instructions[0];
            // WriteLineに渡す文字列をスタックに積む 
            worker.InsertBefore (ins,ldstr);
            // DateTime.Nowを呼び出す
            worker.InsertAfter (ldstr,callNow);
            // 戻り値のDateTimeをbox化
            worker.InsertAfter (callNow,box);
            // Console.WriteLineを呼び出す
            worker.InsertAfter (box,callWriteLine);
        }
        // 変更した型をインポート
        assembly.MainModule.Import (type);
    }

    static void Main (string args)
    {
        if (args.Length == 0)
            return;

        // pathは書き換えるアセンブリのパス
        string path = args[0];

        AssemblyDefinition asm = AssemblyFactory.GetAssembly (path);

        foreach (TypeDefinition type in asm.MainModule.Types)
            Logging (asm,type);

        // アセンブリの保存
        // 本当にexe/dllを書き換えるのでテストする場合は、バックアップを取っておくこと
        AssemblyFactory.SaveAssembly (asm,path);
    }
} 

/* 結果
in Main at 2008/09/23 17:09:02.
in set_Greeting at 2008/09/23 17:09:02.
in Run at 2008/09/23 17:09:02.
in get_Greeting at 2008/09/23 17:09:02.
 */

Reflection.Emitと違って弄くりたいコードをAppDomainに読み込まなくてもいいみたいですね。他にもデバッグシンボルにアクセスしたり色々出来そうな感じですが、ドキュメントが少ないためソースコードを読む必要がありそ。(^^;