2017-03-12 3 views
6

Cette question concerne l'analyse de pile statique du code C# IL personnalisé et la façon de concevoir les opcodes pour satisfaire le compilateur.Modification du code C# IL - conserver la pile intacte

J'ai un code qui modifie les méthodes C# existantes en y ajoutant mon propre code. Pour éviter que la méthode d'origine ne retourne avant que mon code ne soit exécuté, il remplace tous les opcodes RET par un BR endlabel et ajoute cette étiquette à la fin du code d'origine. Je rajoute ensuite plus de code là et enfin un RET.

Tout cela fonctionne très bien en général, mais échoue sur certaines méthodes. Voici un exemple simple:

public static string SomeMethod(int val) 
{ 
    switch (val) 
    { 
     case 0: 
      return "string1".convert(); 
     case 1: 
      return "string2".convert(); 
     case 2: 
      return "string3".convert(); 
     // ... 
    } 
    return ""; 
} 

qui est représenté par ce code IL:

.method public hidebysig static string SomeMethod(int32 val) cil managed 
{ 
    .maxstack 1 
    .locals val ([0] int32 num) 
    L_0000: ldarg.0 
    L_0001: stloc.0 
    L_0002: ldloc.0 
    L_0003: switch (L_002e, L_004f, L_0044, ...) 
    L_002c: br.s L_0091 
    L_002e: ldstr "string1" 
    L_0033: call string Foo::convert(string) 
    L_0038: ret 
    L_0039: ldstr "string2" 
    L_003e: call string Foo::convert(string) 
    L_0043: ret 
    L_0044: ldstr "string3" 
    L_0049: call string Foo::convert(string) 
    L_004e: ret 
    ... 
    L_0091: ldstr "" 
    L_0096: ret 
} 

Après mon programme a modifié, le code ressemble à ceci:

.method public hidebysig static string SomeMethod(int32 val) cil managed 
{ 
    .maxstack 1 
    .locals val ([0] int32 num) 
    L_0000: ldarg.0 
    L_0001: stloc.0 
    L_0002: ldloc.0 
    L_0003: switch (L_002e, L_004f, L_0044, ...) 
    L_002c: br.s L_0091 
    L_002e: ldstr "string1" 
    L_0033: call string Foo::convert(string) 
    L_0038: br L_009b // was ret 
    L_0039: ldstr "string2" 
    L_003e: call string Foo::convert(string) 
    L_0043: br L_009b // was ret 
    L_0044: ldstr "string3" 
    L_0049: call string Foo::convert(string) 
    L_004e: br L_009b // was ret 
    ... 
    L_0091: ldstr "" 
    L_0096: br L_009b // was ret 
    L_009b: my code here 
    ... 
    L_0200: ret 
} 

et je reçois une erreur de compilation:

Could not execute post-long-event action. Exception: System.TypeInitializationException: An exception was thrown by the type initializer for FooBar ---> System.InvalidProgramException: Invalid IL code in (wrapper dynamic-method) Foo:SomeMethod (int): IL_0000: ldnull

Existe-t-il un moyen simple de remplacer les TER de manière générique et de garder l'analyseur statique satisfait?

+6

Résolu le problème. Le remplacement de RET par BR augmente la longueur du code et les sauts courts peuvent devenir illégaux. La solution est de les remplacer par de longs sauts. Testé et fonctionne. –

+1

Vous pouvez également utiliser une clause try-finally; cela évitera tous vos problèmes. Bien sûr, cela n'a de sens que si vous voulez * toujours * exécuter ce code - il s'exécutera également sur une exception. Selon le code que vous injectez, cela peut être une bonne chose ou une mauvaise chose. – Luaan

+0

bonne prise sur vous propre erreur :) vous devriez répondre à vous-même –

Répondre

3

Le problème avéré être que toutes les instructions de saut courtes pourraient devenir trop loin parce que l'insertion BR au lieu de RET augmente la taille de opcode. Je l'ai résolu en remplaçant tous les opcodes se terminant par "_S" avec leurs versions de saut en longueur correspondantes. Pour plus de détails à ce sujet, jetez un oeil à cette validation de mon projet: Fixed illegal short jumps

+2

@IlianPinzon, j'attends ma période de refroidissement de 24h avant que stackoverflow me permet d'accepter ma réponse _own_. –