it-swarm.com.de

Den Inhalt einer C # -Methode dynamisch ersetzen?

Was ich tun möchte, ist zu ändern, wie eine C # -Methode ausgeführt wird, wenn sie aufgerufen wird, so dass 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 Funktionsrumpf ausgeführt wird und nachdem die Funktion zurückgegeben wird. Noch wichtiger ist, ich muss in der Lage sein, dies zu tun, ohne den Code zu ändern, in dem Solve aufgerufen wird oder zu Beginn der Funktion (zur Kompilierungszeit; dies zur Laufzeit ist das Ziel).

Im Moment habe ich versucht, dieses Codebit (angenommen, t ist der Typ, in dem Solve gespeichert ist, und m ist eine MethodInfo of 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 bei dynamischen Modulen. nicht diejenigen, die bereits in der Versammlung zusammengestellt und gespeichert wurden.

Daher suche ich nach einer Möglichkeit, SwapMethodBody mit einer Methode, die bereits in einer geladenen und ausführenden Assembly gespeichert ist effektiv auszuführen.

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, die gesamte AWL zu kopieren und alle Aufrufe von Solve () so zu aktualisieren, dass sie aktualisiert werden würde auf die neue Kopie verweisen.

77
June Rhodes

Für .NET 4 und höher

using System;
using System.Reflection;
using System.Runtime.CompilerServices;


namespace InjectionTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Target targetInstance = new Target();

            targetInstance.test();

            Injection.install(1);
            Injection.install(2);
            Injection.install(3);
            Injection.install(4);

            targetInstance.test();

            Console.Read();
        }
    }

    public class Target
    {
        public void test()
        {
            targetMethod1();
            Console.WriteLine(targetMethod2());
            targetMethod3("Test");
            targetMethod4();
        }

        private void targetMethod1()
        {
            Console.WriteLine("Target.targetMethod1()");

        }

        private string targetMethod2()
        {
            Console.WriteLine("Target.targetMethod2()");
            return "Not injected 2";
        }

        public void targetMethod3(string text)
        {
            Console.WriteLine("Target.targetMethod3("+text+")");
        }

        private void targetMethod4()
        {
            Console.WriteLine("Target.targetMethod4()");
        }
    }

    public class Injection
    {        
        public static void install(int funcNum)
        {
            MethodInfo methodToReplace = typeof(Target).GetMethod("targetMethod"+ funcNum, BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
            MethodInfo methodToInject = typeof(Injection).GetMethod("injectionMethod"+ funcNum, BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
            RuntimeHelpers.PrepareMethod(methodToReplace.MethodHandle);
            RuntimeHelpers.PrepareMethod(methodToInject.MethodHandle);

            unsafe
            {
                if (IntPtr.Size == 4)
                {
                    int* inj = (int*)methodToInject.MethodHandle.Value.ToPointer() + 2;
                    int* tar = (int*)methodToReplace.MethodHandle.Value.ToPointer() + 2;
#if DEBUG
                    Console.WriteLine("\nVersion x86 Debug\n");

                    byte* injInst = (byte*)*inj;
                    byte* tarInst = (byte*)*tar;

                    int* injSrc = (int*)(injInst + 1);
                    int* tarSrc = (int*)(tarInst + 1);

                    *tarSrc = (((int)injInst + 5) + *injSrc) - ((int)tarInst + 5);
#else
                    Console.WriteLine("\nVersion x86 Release\n");
                    *tar = *inj;
#endif
                }
                else
                {

                    long* inj = (long*)methodToInject.MethodHandle.Value.ToPointer()+1;
                    long* tar = (long*)methodToReplace.MethodHandle.Value.ToPointer()+1;
#if DEBUG
                    Console.WriteLine("\nVersion x64 Debug\n");
                    byte* injInst = (byte*)*inj;
                    byte* tarInst = (byte*)*tar;


                    int* injSrc = (int*)(injInst + 1);
                    int* tarSrc = (int*)(tarInst + 1);

                    *tarSrc = (((int)injInst + 5) + *injSrc) - ((int)tarInst + 5);
#else
                    Console.WriteLine("\nVersion x64 Release\n");
                    *tar = *inj;
#endif
                }
            }
        }

        private void injectionMethod1()
        {
            Console.WriteLine("Injection.injectionMethod1");
        }

        private string injectionMethod2()
        {
            Console.WriteLine("Injection.injectionMethod2");
            return "Injected 2";
        }

        private void injectionMethod3(string text)
        {
            Console.WriteLine("Injection.injectionMethod3 " + text);
        }

        private void injectionMethod4()
        {
            System.Diagnostics.Process.Start("calc");
        }
    }

}
150
Logman

Harmony ist eine Open-Source-Bibliothek, mit der vorhandene C # -Methoden aller Art zur Laufzeit ersetzt, dekoriert oder geändert werden können. Das Hauptaugenmerk liegt auf Spielen und Plugins, die in Mono geschrieben wurden. Die Technik kann jedoch mit jeder .NET-Version verwendet werden. Es werden auch mehrere Änderungen an derselben Methode vorgenommen (sie werden akkumuliert, anstatt sie zu überschreiben).

Es erstellt Methoden vom Typ DynamicMethod für jede ursprüngliche Methode und gibt Code an diese Methode aus, der am Anfang und Ende benutzerdefinierte Methoden aufruft. Sie können auch Filter schreiben, um den ursprünglichen IL-Code zu verarbeiten, wodurch eine detailliertere Manipulation der ursprünglichen Methode möglich ist.

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.

119

Sie können den Inhalt einer Methode zur Laufzeit ändern. Das solltest du aber nicht, und es wird dringend empfohlen, dies zu Testzwecken aufzubewahren.

Schauen Sie sich nur an:

http://www.codeproject.com/Articles/463508/NET-CLR-Injection-Modify-IL-Code-during-Run-time

Grundsätzlich können Sie:

  1. Ruft den Inhalt der IL-Methode über MethodInfo.GetMethodBody () ab. GetILAsByteArray ()
  2. Verwirren Sie mit diesen Bytes.

    Wenn Sie nur einen Code voranstellen oder anhängen möchten, müssen Sie nur die gewünschten Opcodes voranstellen/anhängen (achten Sie jedoch darauf, den Stapel sauber zu lassen).

    Hier sind einige Tipps zum "Dekompilieren" vorhandener IL:

    • Die zurückgegebenen Bytes sind eine Folge von IL-Anweisungen, gefolgt von ihren Argumenten (wenn sie einige haben, hat beispielsweise '.call' ein Argument: das aufgerufene Methodentoken und '.pop' hat keine).
    • Die Korrespondenz zwischen IL-Codes und Bytes, die Sie im zurückgegebenen Array finden, kann mit OpCodes.YourOpCode.Value ermittelt werden (dies ist der tatsächliche Wert des Opcode-Bytes, der in Ihrer Assembly gespeichert ist).
    • Nach IL-Codes angehängte Argumente können abhängig vom aufgerufenen Opcode unterschiedliche Größen haben (von einem bis zu mehreren Bytes)
    • Möglicherweise finden Sie Token, auf die diese Argumente über geeignete Methoden verweisen. Wenn Ihre IL beispielsweise ".call 354354" enthält (codiert als 28 00 05 68 32 in hexa, 28h = 40 ist ".call" -Opcode und 56832h = 354354), kann die entsprechende aufgerufene Methode mit MethodBase.GetMethodFromHandle (354354) gefunden werden )
  3. Nach der Änderung kann das IL-Byte-Array über InjectionHelper.UpdateILCodes (MethodInfo-Methode, Byte [] ilCodes) erneut eingefügt werden - siehe oben genannten Link

    Dies ist der "unsichere" Teil ... Es funktioniert gut, aber dies besteht darin, interne CLR-Mechanismen zu hacken ...

25
Olivier

sie können es ersetzen, wenn die Methode nicht virtuell, nicht generisch, nicht generisch, nicht inline und auf x86-Plattenform ist:

MethodInfo methodToReplace = ...
RuntimeHelpers.PrepareMetod(methodToReplace.MethodHandle);

var getDynamicHandle = Delegate.CreateDelegate(Metadata<Func<DynamicMethod, RuntimeMethodHandle>>.Type, Metadata<DynamicMethod>.Type.GetMethod("GetMethodDescriptor", BindingFlags.Instance | BindingFlags.NonPublic)) as Func<DynamicMethod, RuntimeMethodHandle>;

var newMethod = new DynamicMethod(...);
var body = newMethod.GetILGenerator();
body.Emit(...) // do what you want.
body.Emit(OpCodes.jmp, methodToReplace);
body.Emit(OpCodes.ret);

var handle = getDynamicHandle(newMethod);
RuntimeHelpers.PrepareMethod(handle);

*((int*)new IntPtr(((int*)methodToReplace.MethodHandle.Value.ToPointer() + 2)).ToPointer()) = handle.GetFunctionPointer().ToInt32();

//all call on methodToReplace redirect to newMethod and methodToReplace is called in newMethod and you can continue to debug it, enjoy.
11
Teter28

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 ein paar Frameworks, die sich an die Interna von .NET anpassen. Diese sind wahrscheinlich anfälliger und können möglicherweise keinen Inline-Code ändern. Andererseits sind sie vollständig eigenständig und erfordern nicht die Verwendung von a Custom Launcher.

  • Harmony : MIT lizensiert. Scheint in einigen Spielemods erfolgreich eingesetzt worden zu sein, unterstützt sowohl .NET als auch Mono.
  • Deviare In Process Instrumentation Engine : GPLv3 und kommerziell. .NET-Support wird derzeit als experimentell eingestuft, hat jedoch den Vorteil, dass er kommerziell abgesichert ist.
8
poizan42

Logmans Lösung , aber mit einer Schnittstelle zum Austauschen von Methodenkörpern. Auch ein einfacheres Beispiel.

using System;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace DynamicMojo
{
    class Program
    {
        static void Main(string[] args)
        {
            Animal kitty = new HouseCat();
            Animal lion = new Lion();
            var meow = typeof(HouseCat).GetMethod("Meow", BindingFlags.Instance | BindingFlags.NonPublic);
            var roar = typeof(Lion).GetMethod("Roar", BindingFlags.Instance | BindingFlags.NonPublic);

            Console.WriteLine("<==(Normal Run)==>");
            kitty.MakeNoise(); //HouseCat: Meow.
            lion.MakeNoise(); //Lion: Roar!

            Console.WriteLine("<==(Dynamic Mojo!)==>");
            DynamicMojo.SwapMethodBodies(meow, roar);
            kitty.MakeNoise(); //HouseCat: Roar!
            lion.MakeNoise(); //Lion: Meow.

            Console.WriteLine("<==(Normality Restored)==>");
            DynamicMojo.SwapMethodBodies(meow, roar);
            kitty.MakeNoise(); //HouseCat: Meow.
            lion.MakeNoise(); //Lion: Roar!

            Console.Read();
        }
    }

    public abstract class Animal
    {
        public void MakeNoise() => Console.WriteLine($"{this.GetType().Name}: {GetSound()}");

        protected abstract string GetSound();
    }

    public sealed class HouseCat : Animal
    {
        protected override string GetSound() => Meow();

        private string Meow() => "Meow.";
    }

    public sealed class Lion : Animal
    {
        protected override string GetSound() => Roar();

        private string Roar() => "Roar!";
    }

    public static class DynamicMojo
    {
        /// <summary>
        /// Swaps the function pointers for a and b, effectively swapping the method bodies.
        /// </summary>
        /// <exception cref="ArgumentException">
        /// a and b must have same signature
        /// </exception>
        /// <param name="a">Method to swap</param>
        /// <param name="b">Method to swap</param>
        public static void SwapMethodBodies(MethodInfo a, MethodInfo b)
        {
            if (!HasSameSignature(a, b))
            {
                throw new ArgumentException("a and b must have have same signature");
            }

            RuntimeHelpers.PrepareMethod(a.MethodHandle);
            RuntimeHelpers.PrepareMethod(b.MethodHandle);

            unsafe
            {
                if (IntPtr.Size == 4)
                {
                    int* inj = (int*)b.MethodHandle.Value.ToPointer() + 2;
                    int* tar = (int*)a.MethodHandle.Value.ToPointer() + 2;

                    byte* injInst = (byte*)*inj;
                    byte* tarInst = (byte*)*tar;

                    int* injSrc = (int*)(injInst + 1);
                    int* tarSrc = (int*)(tarInst + 1);

                    int tmp = *tarSrc;
                    *tarSrc = (((int)injInst + 5) + *injSrc) - ((int)tarInst + 5);
                    *injSrc = (((int)tarInst + 5) + tmp) - ((int)injInst + 5);
                }
                else
                {
                    throw new NotImplementedException($"{nameof(SwapMethodBodies)} doesn't yet handle IntPtr size of {IntPtr.Size}");
                }
            }
        }

        private static bool HasSameSignature(MethodInfo a, MethodInfo b)
        {
            bool sameParams = !a.GetParameters().Any(x => !b.GetParameters().Any(y => x == y));
            bool sameReturnType = a.ReturnType == b.ReturnType;
            return sameParams && sameReturnType;
        }
    }
}
7
C. McCoy IV

Sie können eine Methode zur Laufzeit ersetzen, indem Sie ICLRPRofiling Interface verwenden.

  1. Rufen Sie AttachProfiler auf, um eine Verbindung zum Prozess herzustellen.
  2. Rufen Sie SetILFunctionBody auf, um den Methodencode zu ersetzen.

Siehe diesen Blog für weitere Details.

5
user152949

Ich weiß, dass dies nicht die genaue Antwort auf Ihre Frage ist, aber die übliche Vorgehensweise ist die Verwendung des Factory/Proxy-Ansatzes.

Zuerst deklarieren wir einen Basistyp.

public class SimpleClass
{
    public virtual 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;
    }
}

Dann können wir einen abgeleiteten Typ deklarieren (Proxy nennen).

public class DistributedClass
{
    public override DTask<bool> Solve(int n, DEvent<bool> callback)
    {
        CodeToExecuteBefore();
        return base.Slove(n, callback);
    }
}

// At runtime

MyClass myInstance;

if (distributed)
    myInstance = new DistributedClass();
else
    myInstance = new SimpleClass();

Der abgeleitete Typ kann auch zur Laufzeit generiert werden.

public static class Distributeds
{
    private static readonly ConcurrentDictionary<Type, Type> pDistributedTypes = new ConcurrentDictionary<Type, Type>();

    public Type MakeDistributedType(Type type)
    {
        Type result;
        if (!pDistributedTypes.TryGetValue(type, out result))
        {
            if (there is at least one method that have [Distributed] attribute)
            {
                result = create a new dynamic type that inherits the specified type;
            }
            else
            {
                result = type;
            }

            pDistributedTypes[type] = result;
        }
        return result;
    }

    public T MakeDistributedInstance<T>()
        where T : class
    {
        Type type = MakeDistributedType(typeof(T));
        if (type != null)
        {
            // Instead of activator you can also register a constructor delegate generated at runtime if performances are important.
            return Activator.CreateInstance(type);
        }
        return null;
    }
}

// In your code...

MyClass myclass = Distributeds.MakeDistributedInstance<MyClass>();
myclass.Solve(...);

Der einzige Leistungsverlust ist während der Erstellung des abgeleiteten Objekts, das erste Mal ist ziemlich langsam, da es viel Reflexion und Reflexionsemission verwendet. In allen anderen Fällen sind dies die Kosten für eine gleichzeitige Tabellensuche und einen Konstruktor. Wie gesagt, mit können Sie die Konstruktion optimieren

ConcurrentDictionary<Type, Func<object>>.
4

Basierend auf der Antwort auf diese und eine andere Frage hat sich ive diese aufgeräumte Version ausgedacht:

public static unsafe MethodReplacementState Replace(this MethodInfo methodToReplace, MethodInfo methodToInject)
        {
//#if DEBUG
            RuntimeHelpers.PrepareMethod(methodToReplace.MethodHandle);
            RuntimeHelpers.PrepareMethod(methodToInject.MethodHandle);
//#endif
            MethodReplacementState state;

            IntPtr tar = methodToReplace.MethodHandle.Value;
            if (!methodToReplace.IsVirtual)
                tar += 8;
            else
            {
                var index = (int)(((*(long*)tar) >> 32) & 0xFF);
                var classStart = *(IntPtr*)(methodToReplace.DeclaringType.TypeHandle.Value + (IntPtr.Size == 4 ? 40 : 64));
                tar = classStart + IntPtr.Size * index;
            }
            var inj = methodToInject.MethodHandle.Value + 8;
#if DEBUG
            tar = *(IntPtr*)tar + 1;
            inj = *(IntPtr*)inj + 1;
            state.Location = tar;
            state.OriginalValue = new IntPtr(*(int*)tar);

            *(int*)tar = *(int*)inj + (int)(long)inj - (int)(long)tar;
            return state;

#else
            state.Location = tar;
            state.OriginalValue = *(IntPtr*)tar;
            * (IntPtr*)tar = *(IntPtr*)inj;
            return state;
#endif
        }
    }

    public struct MethodReplacementState : IDisposable
    {
        internal IntPtr Location;
        internal IntPtr OriginalValue;
        public void Dispose()
        {
            this.Restore();
        }

        public unsafe void Restore()
        {
#if DEBUG
            *(int*)Location = (int)OriginalValue;
#else
            *(IntPtr*)Location = OriginalValue;
#endif
        }
    }
1
TakeMeAsAGuest