2017-03-12 6 views
6

Diese Frage bezieht sich auf statische Stack-Analyse von benutzerdefinierten C# IL-Code und wie die Opcodes den Compiler zu erfüllen.C# IL-Code-Änderung - Stapel intakt halten

Ich habe Code, der vorhandene C# -Methoden ändert, indem ich meinen eigenen Code an ihn anfüge. Um zu vermeiden, dass die ursprüngliche Methode zurückkehrt, bevor mein Code ausgeführt wird, ersetzt sie alle Opcodes RET durch eine BR Endlabel und fügt dieses Etikett am Ende des ursprünglichen Codes hinzu. Ich füge dann mehr Code hinzu und schließlich einen RET.

Das alles funktioniert im Allgemeinen gut, aber auf bestimmte Methoden fehlschlägt. Hier ist ein einfaches Beispiel:

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

, die durch diesen IL-Code dargestellt wird:

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

Nach meinem Programm es geändert, der Code sieht wie folgt aus:

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

und ich ein Kompilierfehler:

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

Gibt es eine einfache Möglichkeit, RETs auf generische Weise zu ersetzen und den statischen Analyzer glücklich zu machen?

+6

Das Problem wurde behoben. Ersetzen von RET mit BR erhöht die Codelänge und kurze Sprünge können möglicherweise illegal werden. Die Lösung besteht darin, sie durch lange Sprünge zu ersetzen. Getestet und funktioniert. –

+1

Sie könnten auch eine try-finally-Klausel verwenden. das wird all deine Probleme vermeiden. Natürlich macht es nur Sinn, wenn Sie * immer * diesen Code ausführen wollen - er wird auch bei einer Exception ausgeführt. Je nachdem, welchen Code du eingibst, kann das eine gute Sache oder eine schlechte Sache sein. – Luaan

+0

Guter Fang auf Ihrem eigenen Fehler :) Sie sollten auf sich selbst antworten –

Antwort

3

Das Problem stellte sich heraus, dass alle kurzen Sprungbefehle möglicherweise zu weit entfernt, weil das Einfügen werden könnte BR statt RET Opcode Größe erhöht.

Ich löste es, indem ich alle Opcodes, die auf "_S" enden, durch ihre entsprechenden Weitsprungversionen ersetzte. Weitere Informationen hierzu finden Sie in diesem Commit für mein Projekt: Fixed illegal short jumps

+2

@IlianPinzon, ich warte meine Abkühlphase von 24h, bevor stackoverflow erlaubt mir, meine _own_ Antwort zu akzeptieren. –

Verwandte Themen