Ich möchte ändern, wie eine C # -Methode beim Aufruf ausgeführt wird, damit ich so etwas schreiben kann:
[Distributed]
public DTask<bool> Solve(int n, DEvent<bool> callback)
{
for (int m = 2; m < n - 1; m += 1)
if (m % n == 0)
return false;
return true;
}
Zur Laufzeit muss ich in der Lage sein, Methoden mit dem Attribut Distributed zu analysieren (was ich bereits kann) und dann Code einzufügen, bevor der Hauptteil der Funktion ausgeführt wird und nachdem die Funktion zurückgegeben wird. Noch wichtiger ist, dass ich in der Lage sein muss, dies zu tun, ohne den Code zu ändern, in dem Solve aufgerufen wird, oder zu Beginn der Funktion (zur Kompilierungszeit; dies ist zur Laufzeit das Ziel).
Im Moment habe ich dieses Codebit versucht (angenommen, t ist der Typ, in dem Solve gespeichert ist, und m ist eine MethodInfo von Solve) :
private void WrapMethod(Type t, MethodInfo m)
{
// Generate ILasm for delegate.
byte[] il = typeof(Dpm).GetMethod("ReplacedSolve").GetMethodBody().GetILAsByteArray();
// Pin the bytes in the garbage collection.
GCHandle h = GCHandle.Alloc((object)il, GCHandleType.Pinned);
IntPtr addr = h.AddrOfPinnedObject();
int size = il.Length;
// Swap the method.
MethodRental.SwapMethodBody(t, m.MetadataToken, addr, size, MethodRental.JitImmediate);
}
public DTask<bool> ReplacedSolve(int n, DEvent<bool> callback)
{
Console.WriteLine("This was executed instead!");
return true;
}
MethodRental.SwapMethodBody funktioniert jedoch nur mit dynamischen Modulen. nicht diejenigen, die bereits kompiliert und in der Assembly gespeichert wurden.
Daher suche ich nach einer Möglichkeit, SwapMethodBody effektiv für eine Methode auszuführen, die bereits in einer geladenen und ausgeführten Assembly gespeichert ist .
Beachten Sie, dass es kein Problem ist, wenn ich die Methode vollständig in ein dynamisches Modul kopieren muss. In diesem Fall muss ich jedoch eine Möglichkeit finden, über die IL zu kopieren und alle Aufrufe von Solve () so zu aktualisieren, dass sie würde auf die neue Kopie verweisen.
Antworten:
Harmony 2 ist eine Open Source-Bibliothek (MIT-Lizenz), mit der vorhandene C # -Methoden zur Laufzeit ersetzt, dekoriert oder geändert werden können. Das Hauptaugenmerk liegt auf Spielen und Plugins, die in Mono oder .NET geschrieben wurden. Es werden mehrere Änderungen an derselben Methode vorgenommen - sie sammeln sich an, anstatt sich gegenseitig zu überschreiben.
Es erstellt dynamische Ersetzungsmethoden für jede ursprüngliche Methode und gibt Code an diese aus, der am Anfang und am Ende benutzerdefinierte Methoden aufruft. Außerdem können Sie Filter schreiben, um den ursprünglichen IL-Code und benutzerdefinierte Ausnahmebehandlungsroutinen zu verarbeiten, wodurch die ursprüngliche Methode detaillierter bearbeitet werden kann.
Um den Vorgang abzuschließen, wird ein einfacher Assembler-Sprung in das Trampolin der ursprünglichen Methode geschrieben, der auf den Assembler verweist, der beim Kompilieren der dynamischen Methode generiert wurde. Dies funktioniert für 32 / 64Bit unter Windows, MacOS und jedem Linux, das Mono unterstützt.
Dokumentation finden Sie hier .
Beispiel
( Quelle )
Originalcode
Patchen mit Harmony-Anmerkungen
Alternativ manuelles Patchen mit Reflexion
quelle
Memory.WriteJump
) ausgeführt wird?48 B8 <QWord>
Verschiebt einen QWord-Sofortwert aufrax
, dannFF E0
istjmp rax
- alles klar da! Meine verbleibende Frage bezieht sich auf denE9 <DWord>
Fall (ein Nahsprung): In diesem Fall scheint der Nahsprung erhalten zu sein und die Modifikation befindet sich auf dem Ziel des Sprunges; Wann generiert Mono überhaupt einen solchen Code und warum wird er speziell behandelt?Für .NET 4 und höher
quelle
this
innerhalbinjectionMethod*()
es wird eine Referenz -Injection
Instanz während der Kompilierung , sondern eineTarget
Instanz während der Laufzeit (dies gilt für alle Verweise auf Instanz Mitglieder , die Sie innerhalb eines injizierten verwenden Methode). (2) Aus irgendeinem Grund#DEBUG
funktionierte das Teil nur beim Debuggen eines Tests, nicht jedoch beim Ausführen eines Tests, der vom Debug kompiliert wurde. Am Ende habe ich immer das#else
Teil benutzt. Ich verstehe nicht, warum das funktioniert, aber es funktioniert.Debugger.IsAttached
anstelle von#if
PräprozessorSie können den Inhalt einer Methode zur Laufzeit ändern. Dies ist jedoch nicht vorgesehen, und es wird dringend empfohlen, dies zu Testzwecken aufzubewahren.
Schauen Sie sich einfach an:
http://www.codeproject.com/Articles/463508/NET-CLR-Injection-Modify-IL-Code-during-Run-time
Grundsätzlich können Sie:
Leg dich mit diesen Bytes an.
Wenn Sie nur Code voranstellen oder anhängen möchten, stellen Sie einfach die gewünschten Opcodes vor / fügen Sie sie hinzu (achten Sie jedoch darauf, dass der Stapel sauber bleibt).
Hier sind einige Tipps zum "Dekompilieren" vorhandener IL:
Nach der Änderung kann Ihr IL-Byte-Array über InjectionHelper.UpdateILCodes (MethodInfo-Methode, Byte [] ilCodes) erneut injiziert werden - siehe oben genannten Link
Dies ist der "unsichere" Teil ... Es funktioniert gut, aber dies besteht darin, interne CLR-Mechanismen zu hacken ...
quelle
Sie können es ersetzen, wenn die Methode nicht virtuell, nicht generisch, nicht generisch, nicht inline und auf x86-Plattenform ist:
quelle
Es gibt einige Frameworks, mit denen Sie jede Methode zur Laufzeit dynamisch ändern können (sie verwenden die von user152949 erwähnte ICLRProfiling-Schnittstelle):
Es gibt auch einige Frameworks, die sich über die Interna von .NET lustig machen. Diese sind wahrscheinlich anfälliger und können Inline-Code wahrscheinlich nicht ändern. Andererseits sind sie vollständig in sich geschlossen und erfordern keine Verwendung von a Benutzerdefinierter Launcher.
quelle
Logmans Lösung , jedoch mit einer Schnittstelle zum Austauschen von Methodenkörpern. Auch ein einfacheres Beispiel.
quelle
Basierend auf der Antwort auf diese und eine andere Frage habe ich mir diese aufgeräumte Version ausgedacht:
quelle
Sie können eine Methode zur Laufzeit mithilfe der ICLRPRofiling-Schnittstelle ersetzen .
Weitere Informationen finden Sie in diesem Blog .
quelle
Ich weiß, dass dies nicht die genaue Antwort auf Ihre Frage ist, aber der übliche Weg, dies zu tun, ist die Verwendung eines Fabrik- / Proxy-Ansatzes.
Zuerst deklarieren wir einen Basistyp.
Dann können wir einen abgeleiteten Typ deklarieren (Proxy nennen).
Der abgeleitete Typ kann auch zur Laufzeit generiert werden.
Der einzige Leistungsverlust besteht während der Erstellung des abgeleiteten Objekts. Das erste Mal ist ziemlich langsam, da viel Reflexion und Reflexionsemission verwendet werden. In allen anderen Fällen sind dies die Kosten für eine gleichzeitige Tabellensuche und einen Konstruktor. Wie gesagt, Sie können die Konstruktion mit optimieren
quelle