Dynamically replace the contents of a C# method?

后端 未结 10 2285
面向向阳花
面向向阳花 2020-11-22 16:09

What I want to do is change how a C# method executes when it is called, so that I can write something like this:

[Distributed]
public DTask Solve         


        
相关标签:
10条回答
  • 2020-11-22 16:36

    Based on the answer to this question and another, ive came up with this tidied up version:

    // Note: This method replaces methodToReplace with methodToInject
    // Note: methodToInject will still remain pointing to the same location
    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
            }
        }
    
    0 讨论(0)
  • 2020-11-22 16:37

    you can replace it if the method is non virtual, non generic, not in generic type, not inlined and on x86 plateform:

    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.
    
    0 讨论(0)
  • 2020-11-22 16:39

    For .NET 4 and above

    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");
            }
        }
    
    }
    
    0 讨论(0)
  • 2020-11-22 16:41

    You can replace a method at runtime by using the ICLRPRofiling Interface.

    1. Call AttachProfiler to attach to the process.
    2. Call SetILFunctionBody to replace the method code.

    See this blog for more details.

    0 讨论(0)
提交回复
热议问题