Ce que je veux faire, c'est changer la façon dont une méthode C # s'exécute lorsqu'elle est appelée, afin que je puisse écrire quelque chose comme ceci:
[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;
}
Au moment de l'exécution, je dois pouvoir analyser les méthodes qui ont l'attribut Distributed (ce que je peux déjà faire), puis insérer du code avant que le corps de la fonction ne s'exécute et après le retour de la fonction. Plus important encore, je dois être capable de le faire sans modifier le code où Solve est appelé ou au début de la fonction (au moment de la compilation; l'objectif est de le faire au moment de l'exécution).
Pour le moment, j'ai essayé ce bit de code (supposons que t est le type dans lequel Solve est stocké et m est un MethodInfo de 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;
}
Cependant, MethodRental.SwapMethodBody ne fonctionne que sur les modules dynamiques; pas ceux qui ont déjà été compilés et stockés dans l'assembly.
Je cherche donc un moyen de faire efficacement SwapMethodBody sur une méthode qui est déjà stockée dans un assembly chargé et en cours d'exécution .
Notez que ce n'est pas un problème si je dois copier complètement la méthode dans un module dynamique, mais dans ce cas, je dois trouver un moyen de copier à travers l'IL ainsi que de mettre à jour tous les appels à Solve () de sorte qu'ils pointerait vers la nouvelle copie.
Réponses:
Harmony 2 est une bibliothèque open source (licence MIT) conçue pour remplacer, décorer ou modifier les méthodes C # existantes de tout type pendant l'exécution. Il se concentre principalement sur les jeux et les plugins écrits en Mono ou .NET. Il prend en charge plusieurs modifications apportées à la même méthode - elles s'accumulent au lieu de se remplacer.
Il crée des méthodes de remplacement dynamiques pour chaque méthode d'origine et leur émet du code qui appelle des méthodes personnalisées au début et à la fin. Il vous permet également d'écrire des filtres pour traiter le code IL d'origine et des gestionnaires d'exceptions personnalisés, ce qui permet une manipulation plus détaillée de la méthode d'origine.
Pour terminer le processus, il écrit un simple saut d'assembleur dans le trampoline de la méthode d'origine qui pointe vers l'assembleur généré à partir de la compilation de la méthode dynamique. Cela fonctionne pour 32 / 64Bit sur Windows, macOS et tout Linux pris en charge par Mono.
La documentation peut être trouvée ici .
Exemple
( Source )
Code d'origine
Patcher avec les annotations Harmony
Alternativement, patching manuel avec réflexion
la source
Memory.WriteJump
)?48 B8 <QWord>
déplace une valeur immédiate QWord versrax
, puisFF E0
estjmp rax
- tout est clair là-bas! Ma question restante concerne leE9 <DWord>
cas (un saut de près): il semble que dans ce cas le saut de près soit conservé et la modification soit sur la cible du saut; Quand Mono génère-t-il un tel code en premier lieu et pourquoi bénéficie-t-il de ce traitement spécial?Pour .NET 4 et supérieur
la source
this
intérieur,injectionMethod*()
il référencera uneInjection
instance pendant la compilation , mais uneTarget
instance pendant l' exécution (cela est vrai pour toutes les références aux membres d'instance que vous utilisez dans un injecté méthode). (2) Pour une raison quelconque, la#DEBUG
partie ne fonctionnait que lors du débogage d' un test, mais pas lors de l' exécution d' un test compilé par débogage. J'ai fini par toujours utiliser la#else
pièce. Je ne comprends pas pourquoi cela fonctionne mais c'est le cas.Debugger.IsAttached
au lieu du#if
préprocesseurVous POUVEZ modifier le contenu d'une méthode lors de l'exécution. Mais vous n'êtes pas censé le faire, et il est fortement recommandé de le conserver à des fins de test.
Jetez un œil à:
http://www.codeproject.com/Articles/463508/NET-CLR-Injection-Modify-IL-Code-during-Run-time
En gros, vous pouvez:
Mess avec ces octets.
Si vous souhaitez simplement ajouter ou ajouter du code, il vous suffit de préprendre / ajouter les opcodes que vous voulez (faites attention à ne pas laisser la pile propre, cependant)
Voici quelques conseils pour "décompiler" l'IL existant:
Une fois modifié, votre tableau d'octets IL peut être réinjecté via InjectionHelper.UpdateILCodes (méthode MethodInfo, byte [] ilCodes) - voir lien mentionné ci-dessus
C'est la partie "non sûre" ... Cela fonctionne bien, mais cela consiste à pirater les mécanismes CLR internes ...
la source
vous pouvez le remplacer si la méthode est non virtuelle, non générique, pas de type générique, non intégrée et sur plateforme x86:
la source
Il existe quelques frameworks qui vous permettent de changer dynamiquement n'importe quelle méthode au moment de l'exécution (ils utilisent l'interface ICLRProfiling mentionnée par user152949):
Il existe également quelques frameworks qui se moquent des internes de .NET, ceux-ci sont probablement plus fragiles et ne peuvent probablement pas modifier le code intégré, mais d'un autre côté, ils sont entièrement autonomes et ne vous obligent pas à utiliser un lanceur personnalisé.
la source
Solution de Logman , mais avec une interface pour permuter les corps de méthode. Aussi, un exemple plus simple.
la source
Sur la base de la réponse à cette question et à une autre, ive a proposé cette version rangée:
la source
Vous pouvez remplacer une méthode au moment de l'exécution à l'aide de l' interface ICLRPRofiling .
Consultez ce blog pour plus de détails.
la source
Je sais que ce n'est pas la réponse exacte à votre question, mais la façon habituelle de le faire consiste à utiliser l'approche usines / proxy.
Nous déclarons d'abord un type de base.
Ensuite, nous pouvons déclarer un type dérivé (appelez-le proxy).
Le type dérivé peut également être généré lors de l'exécution.
La seule perte de performances est lors de la construction de l'objet dérivé, la première fois est assez lente car elle utilisera beaucoup de réflexion et de réflexion émise. Toutes les autres fois, il s'agit du coût d'une recherche de table simultanée et d'un constructeur. Comme dit, vous pouvez optimiser la construction en utilisant
la source