2012-06-17 2 views
8

J'utilise Mono Cecil pour injecter du code dans une autre méthode. Je veux ajouter un bloc Try-Catch autour de mon code.Ajouter un try-catch avec Mono Cecil

J'ai donc écrit un HelloWorld.exe avec un bloc try catch et l'ai décompilé.

Il ressemble à ceci dans le réflecteur pour le try-catch:

.try L_0001 to L_0036 catch [mscorlib]System.Exception handler L_0036 to L_003b 

Comment puis-je injecter une prise d'essai comme celui-ci par l'intermédiaire mono cecil?

Répondre

19

L'ajout de gestionnaires d'exceptions avec Mono.Cecil n'est pas difficile, il vous suffit de savoir comment les gestionnaires d'exceptions sont disposés dans les métadonnées.

Disons que vous avez la méthode C#:

static void Throw() 
{ 
    throw new Exception ("oups"); 
} 

Si vous décompiler, il devrait ressembler un peu semblable à ceci:

.method private static hidebysig default void Throw() cil managed 
{ 
    IL_0000: ldstr "oups" 
    IL_0005: newobj instance void class [mscorlib]System.Exception::.ctor(string) 
    IL_000a: throw 
} 

Maintenant, nous allons dire que vous voulez injecter du code dans ce méthode telle que c'est similaire au code C#:

static void Throw() 
{ 
    try { 
     throw new Exception ("oups"); 
    } catch (Exception e) { 
     Console.WriteLine (e); 
    } 
} 

Autrement dit, vous voulez simplement envelopper le code existant dans un essayeur attrape le gestionnaire. Vous pouvez le faire facilement avec Cecil cette façon:

var method = ...; 
    var il = method.Body.GetILProcessor(); 

    var write = il.Create (
     OpCodes.Call, 
     module.Import (typeof (Console).GetMethod ("WriteLine", new [] { typeof (object)}))); 
    var ret = il.Create (OpCodes.Ret); 
    var leave = il.Create (OpCodes.Leave, ret); 

    il.InsertAfter (
     method.Body.Instructions.Last(), 
     write); 

    il.InsertAfter (write, leave); 
    il.InsertAfter (leave, ret); 

    var handler = new ExceptionHandler (ExceptionHandlerType.Catch) { 
     TryStart = method.Body.Instructions.First(), 
     TryEnd = write, 
     HandlerStart = write, 
     HandlerEnd = ret, 
     CatchType = module.Import (typeof (Exception)), 
    }; 

    method.Body.ExceptionHandlers.Add (handler); 

Ce code est manipulaient la méthode précédente pour ressembler à ceci:

.method private static hidebysig default void Throw() cil managed 
{ 
    .maxstack 1 
    .try { // 0 
     IL_0000: ldstr "oups" 
     IL_0005: newobj instance void class [mscorlib]System.Exception::'.ctor'(string) 
     IL_000a: throw 
    } // end .try 0 
    catch class [mscorlib]System.Exception { // 0 
     IL_000b: call void class [mscorlib]System.Console::WriteLine(object) 
     IL_0010: leave IL_0015 
    } // end handler 0 
    IL_0015: ret 
} 

Nous avons ajouté trois nouvelles instructions: un appel à Console.WriteLine , un congé pour quitter gracieusement le gestionnaire de prise, et enfin (jeu de mots), un ret. Ensuite, nous créons simplement une instance ExceptionHandler pour représenter un gestionnaire try catch dont l'essai englobe le corps existant et dont la capture est l'instruction WriteLine.

Une chose importante à noter est que l'instruction de fin d'une plage n'est pas contenue dans la plage. C'est fondamentalement un [TryStart: TryEnd [gamme.

+2

Le flux de contrôle ne doit pas tomber d'un manipulateur de prise comme celui-là. ECMA-335, §12.4.2.8.1 «La sortie de blocs, de filtres ou de gestionnaires protégés ne peut pas être accomplie via une interruption. (bien que Microsoft CLR ne semble pas appliquer cette règle) – Daniel

+0

@Daniel, bonne prise, laissez-moi ajouter le congé manquant. Merci pour l'information. –

+1

merci pour cette réponse rapide! fonctionne bien - et si facile! Merci beaucoup! – cyptus