Dynamicznie zastąpić zawartość metody C#?

To co chcę zrobić to zmienić sposób wykonywania metody C#, gdy jest wywoływana, tak, że mogę napisać coś takiego:

[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;
}

W czasie wykonywania, muszę być w stanie analizować metody, które mają rozproszony atrybut (co już mogę zrobić), a następnie wstawić kod przed wykonaniem funkcji i po jej powrocie. Co ważniejsze, muszę być w stanie to zrobić bez modyfikowania kodu, gdzie jest wywoływany Solve lub na początku funkcji (w czasie kompilacji; robiąc to w run-time jest celem).

W tej chwili próbowałem tego bitu kodu (Załóżmy, że t jest typem, w którym Solve jest przechowywane, A m jest MethodInfo z 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;
}

Jednak Metoda.SwapMethodBody działa tylko na modułach dynamicznych; nie na tych, które zostały już skompilowane i zapisane w złożeniu.

Więc szukam sposobu, aby skutecznie zrobić SwapMethodBody na metodzie , która jest już przechowywana w załadowanym i wykonującym assembly .

Uwaga, nie jest to problem, jeśli muszę całkowicie skopiować metodę do modułu dynamicznego, ale w tym przypadku muszę znaleźć sposób na skopiowanie przez IL, a także zaktualizować wszystkie wywołania do Solve() tak, aby wskazywały na nową kopię.

Author: starblue, 2011-09-04

9 answers

Pomyśl tylko o konsekwencjach, jeśli to było możliwe. Możesz na przykład zastąpić zawartość klasy String i siać spustoszenie. Po załadowaniu metody przez CLR nie można jej modyfikować. Możesz spojrzeć na AOP i biblioteki, takie jak Castle DynamicProxy , które są używane przez wyśmiewanie frameworków, takich jak Rhino Mocks.

 -21
Author: Darin Dimitrov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-09-18 00:18:30

Dla. Net 4 i nowszych

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");
        }
    }

}
 111
Author: Logman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-06 21:25:08

Harmony jest biblioteką open source przeznaczoną do zastępowania, dekorowania lub modyfikowania istniejących metod C# w czasie wykonywania. Głównym celem są Gry i wtyczki napisane w Mono , ale technika może być używana z dowolną wersją. NET. Zajmuje się również wieloma zmianami w tej samej metodzie (gromadzą się one zamiast nadpisywać).

Tworzy metody typu DynamicMethod dla każdej oryginalnej metody i emituje do niej kod, który wywołuje metody niestandardowe na początku i na końcu. Pozwala również na pisanie filtrów do przetwarzania oryginalnego kodu IL, co pozwala na bardziej szczegółową manipulację oryginalną metodą.

Aby zakończyć proces, pisze prosty skok asemblera do trampoliny oryginalnej metody, która wskazuje na asembler wygenerowany z kompilacji dynamicznej metody. Działa to dla 32/64bit w systemach Windows, macOS i każdym Linuksie, który obsługuje Mono.

 71
Author: Andreas Pardeike,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-04 16:56:34

Można modyfikować zawartość metody w czasie wykonywania. Ale nie powinieneś, i zdecydowanie zaleca się, aby zachować to do celów testowych.

Wystarczy spojrzeć na:

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

W zasadzie można:

  1. Pobierz zawartość metody IL poprzez MethodInfo.GetMethodBody ().GetILAsByteArray ()
  2. Bałagan z tymi bajtami.

    Jeśli chcesz tylko przedłożyć lub Dodaj kod, a następnie po prostu preprend/append opcodes, które chcesz (uważaj, aby nie pozostawić stosu czystym)

    Oto kilka wskazówek, jak "odkompilować" istniejące IL:

    • bajty zwracane są sekwencją instrukcji IL, po których następują ich argumenty (jeśli mają jakieś-na przykład '.call 'ma jeden argument: wywołany token metody oraz'.pop ' has none)
    • korespondencja pomiędzy kodami IL i bajtami znajdowanymi w zwracanej tablicy może być znaleziona za pomocą kodów OpCodes.YourOpCode.Wartość (która jest rzeczywistą wartością bajtu kodu opcode zapisaną w Twoim złożeniu)
    • argumenty dołączane po kodach IL mogą mieć różne rozmiary( od jednego do kilku bajtów), w zależności od kodu opcode o nazwie
    • można znaleźć tokeny, do których te argumenty odnoszą się za pomocą odpowiednich metod. Na przykład, jeśli twój IL zawiera".zadzwoń 354354 "(kod 28 00 05 68 32 w hexa, 28h=40 to '.call ' opcode i 56832h=354354), odpowiednią wywołaną metodę można znaleźć za pomocą MethodBase.GetMethodFromHandle (354354)
  3. Po zmodyfikowaniu macierz IL bajtów może zostać ponownie wysłana przez InjectionHelper.UpdateILCodes (metoda MethodInfo, bajt[] ilCodes) - patrz link wspomniany powyżej

    To jest część "niebezpieczna"... Działa dobrze, ale polega na hakowaniu wewnętrznych mechanizmów CLR...

 23
Author: Olivier,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-16 12:54:15

Możesz ją zastąpić, jeśli metoda nie jest wirtualna, nie generyczna, Nie w typie generycznym, nie inlined i na płycie x86:

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.
 10
Author: Teter28,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-12-30 14:25:00

Rozwiązanie Logmana , ale z interfejsem do zamiany ciał metod. Również prostszy przykład.

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;
        }
    }
}
 6
Author: C. McCoy IV,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-30 03:48:20

Wiem, że nie jest to dokładna odpowiedź na twoje pytanie, ale zwykle można to zrobić za pomocą podejścia factories / proxy.

Najpierw deklarujemy typ bazowy.

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

Wtedy możemy zadeklarować typ Pochodny (nazwijmy go proxy).

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();

Typ Pochodny może być również generowany w czasie wykonywania.

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(...);

Jedyna utrata wydajności jest podczas budowy obiektu pochodnego, pierwszy raz jest dość powolny, ponieważ będzie zużywał dużo odbicia i odbicia emitowanego. Wszystkie inne czasy, jest to koszt równoczesnego przeszukiwania tabeli i konstruktora. Jak już wspomniano, możesz zoptymalizować budowę za pomocą

ConcurrentDictionary<Type, Func<object>>.
 4
Author: Salvatore Previti,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-09-04 13:21:48

Możesz zastąpić metodę w czasie wykonywania za pomocą interfejsu ICLRPRofiling .

  1. wywołanie AttachProfiler aby dołączyć do procesu.
  2. wywołanie SetILFunctionBody w celu zastąpienia kodu metody.

Zobacz ten blog Po Więcej Szczegółów.

 4
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-27 10:50:14

Istnieje kilka frameworków, które pozwalają dynamicznie zmieniać dowolną metodę w czasie wykonywania (używają interfejsu ICLRProfiling wymienionego przez user152949):

  • Prig : wolne i otwarte źródło!
  • Microsoft podróbki : komercyjne, zawarte w Visual Studio Premium i Ultimate, ale nie Community i Professional
  • Telerik JustMock : komercyjna, dostępna jest wersja "lite"
  • Typemock Isolator : Komercyjne

Istnieje również kilka frameworków, które wyśmiewają się z wewnętrznych. NET, są one prawdopodobnie bardziej kruche i prawdopodobnie nie mogą zmienić wlined kodu, ale z drugiej strony są w pełni autonomiczne i nie wymagają używania niestandardowego launchera.

  • Harmony : licencja MIT. Wydaje się, że faktycznie zostały wykorzystane z powodzeniem w kilku modach do gry, obsługuje zarówno. NET, jak i Mono.
  • Deviare In Process Instrumentation Engine : GPLv3 i komercyjne. Wsparcie. NET obecnie oznaczone jako eksperymentalne, ale z drugiej strony ma tę zaletę, że jest wspierane komercyjnie.
 2
Author: poizan42,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-19 12:00:28