2012-06-17 8 views
8

Sto usando Mono Cecil per iniettare il codice in un altro metodo. Voglio aggiungere un blocco Try-Catch al mio codice.Aggiungi un try-catch con Mono Cecil

Quindi ho scritto un HelloWorld.exe con un blocco catch try e decompilato.

Ecco come si presenta in Reflector per Try-Catch:

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

come posso iniettare un tentativo di cattura come questo tramite cecil mono?

risposta

19

L'aggiunta di gestori di eccezioni con Mono.Cecil non è difficile, richiede semplicemente di sapere come vengono gestiti i gestori di eccezioni nei metadati.

Diciamo che avete il metodo C#:

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

Se decompilarlo, dovrebbe apparire in qualche modo simile a questo:

.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 
} 

Ora diciamo che si desidera iniettare codice in questo metodo come è simile al codice C#:

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

che è, si vuole semplicemente avvolgere il codice esistente in un gestore di catch try. Lo si può fare facilmente con Cecil questo modo:

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); 

Questo codice sta manipolando il metodo precedente per assomigliare a questo:

.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 
} 

Stiamo aggiungendo tre nuove istruzioni: una chiamata a Console.WriteLine , un permesso di uscire con grazia dal gestore delle catture e infine (gioco di parole), un ret. Quindi stiamo semplicemente creando un'istanza ExceptionHandler per rappresentare un gestore catch try il cui tentativo comprende il corpo esistente e il cui catch è l'istruzione WriteLine.

Una cosa importante da notare è che l'istruzione di fine di un intervallo non è contenuta all'interno dell'intervallo. È fondamentalmente un [TryStart: TryEnd [range.

+2

Il flusso di controllo non può cadere da un dispositivo di presa del genere. ECMA-335, §12.4.2.8.1 "L'uscita da blocchi, filtri o gestori protetti non può essere eseguita tramite caduta." (anche se il CLR di Microsoft non sembra applicare questa regola) – Daniel

+0

@Daniel, buona cattura, lasciami aggiungere il congedo mancante. Grazie per il testa a testa. –

+1

grazie per questa risposta veloce! funziona bene - e così facile! Grazie mille! – cyptus