Dynamically replace the contents of a C# method?
Asked Answered
C

11

156

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<bool> Solve(int n, DEvent<bool> callback)
{
    for (int m = 2; m < n - 1; m += 1)
        if (m % n == 0)
            return false;
    return true;
}

At run-time, I need to be able to analyse methods that have the Distributed attribute (which I already can do) and then insert code before the body of the function executes and after the function returns. More importantly, I need to be able to do it without modifying code where Solve is called or at the start of the function (at compile time; doing so at run-time is the objective).

At the moment I have attempted this bit of code (assume t is the type that Solve is stored in, and m is a 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;
}

However, MethodRental.SwapMethodBody only works on dynamic modules; not those that have already been compiled and stored in the assembly.

So I'm looking for a way to effectively do SwapMethodBody on a method that is already stored in a loaded and executing assembly.

Note, it is not an issue if I have to completely copy the method into a dynamic module, but in this case I need to find a way to copy across the IL as well as update all of the calls to Solve() such that they would point to the new copy.

Condescending answered 4/9, 2011 at 12:7 Comment(9)
Not possible to swap methods already loaded. Otherwise Spring.Net wouldn't have to make strange things with proxies and interfaces :-) Read this question, it's tangent to your problem: #26303 (if you can intercept it, you can something-like-swap it... If you can't 1 then clearly you can't 2).Artful
In that case, is there a way to copy a method into a dynamic module, and update the rest of the assembly such that calls to that method point to the new copy?Condescending
Same old-same old. If it could be done easily, all the various IoC containers would probably do it. They don't do it->99% it can't be done :-) (without terrible and innominable hacks). There is a single hope: they promised metaprogramming and async in C# 5.0. Async we have seen... Metaprogramming nothing... BUT it could be it!Artful
Could you make Solve() virtual and create a class during runtime that inherits from the class Solve() is in?Behemoth
Why woud you like to swap the body? Do you know what your are swapping to? If so, why not make the method take in the body as a Func?Amaris
If you can't rewrite solve in any way, I don't think in C# there is anything you can do... Well in C++ you can inject dll and Proxy all functions, but I don't think is a good ideaContinual
You really haven't explained why you want to let yourself in for something so painful.Johnjohna
Possible duplicate of How do I replace a method implementation at runtime?Tegantegmen
Please see my answer below. This is totally possible. On code you don't own and during runtime. I don't understand why so many think this is not possible.Cocoon
C
445

Disclosure: Harmony is a library that was written and is maintained by me, the author of this post.

Harmony 2 is an open source library (MIT license) designed to replace, decorate or modify existing C# methods of any kind during runtime. Its main focus is games and plugins written in Mono or .NET. It takes care of multiple changes to the same method - they accumulate instead of overwrite each other.

It creates dynamic replacement methods for every original method and emits code to them that calls custom methods at the start and end. It also allows you to write filters to process the original IL code and custom exception handlers which allows for more detailed manipulation of the original method.

To complete the process, it writes a simple assembler jump into the trampoline of the original method that points to the assembler generated from compiling the dynamic method. This works for 32/64-bit on Windows, macOS and any Linux that Mono supports.

Documentation can be found here.

Example

(Source)

Original Code

public class SomeGameClass
{
    private bool isRunning;
    private int counter;

    private int DoSomething()
    {
        if (isRunning)
        {
            counter++;
            return counter * 10;
        }
    }
}

Patching with Harmony annotations

using SomeGame;
using HarmonyLib;

public class MyPatcher
{
    // make sure DoPatching() is called at start either by
    // the mod loader or by your injector

    public static void DoPatching()
    {
        var harmony = new Harmony("com.example.patch");
        harmony.PatchAll();
    }
}

[HarmonyPatch(typeof(SomeGameClass))]
[HarmonyPatch("DoSomething")]
class Patch01
{
    static FieldRef<SomeGameClass,bool> isRunningRef =
        AccessTools.FieldRefAccess<SomeGameClass, bool>("isRunning");

    static bool Prefix(SomeGameClass __instance, ref int ___counter)
    {
        isRunningRef(__instance) = true;
        if (___counter > 100)
            return false;
        ___counter = 0;
        return true;
    }

    static void Postfix(ref int __result)
    {
        __result *= 2;
    }
}

Alternatively, manual patching with reflection

using SomeGame;
using System.Reflection;
using HarmonyLib;

public class MyPatcher
{
    // make sure DoPatching() is called at start either by
    // the mod loader or by your injector

    public static void DoPatching()
    {
        var harmony = new Harmony("com.example.patch");

        var mOriginal = typeof(SomeGameClass).GetMethod("DoSomething", BindingFlags.Instance | BindingFlags.NonPublic);
        var mPrefix = typeof(MyPatcher).GetMethod("MyPrefix", BindingFlags.Static | BindingFlags.Public);
        var mPostfix = typeof(MyPatcher).GetMethod("MyPostfix", BindingFlags.Static | BindingFlags.Public);
        // add null checks here

        harmony.Patch(mOriginal, new HarmonyMethod(mPrefix), new HarmonyMethod(mPostfix));
    }

    public static void MyPrefix()
    {
        // ...
    }

    public static void MyPostfix()
    {
        // ...
    }
}
Cocoon answered 4/2, 2017 at 16:56 Comment(13)
Had a look at the source code, very interesting! Can you explain (here and/or in documentation) how the specific instructions work which are used to perform the jump (in Memory.WriteJump)?Domenic
To partially answer my own comment: 48 B8 <QWord> moves a QWord immediate value to rax, then FF E0 is jmp rax - all clear there! My remaining question is about the E9 <DWord> case (a near jump): it seems in this case the near jump is preserved and the modification is on the target of the jump; when does Mono generate such code in the first place, and why does it get this special treatment?Domenic
Don’t just look at mono. A lot of people use Harmony on different versions of .Net and I observed this indirection on some trampoline code while testing. There are many more cases not covered but my time is limited and it works good enough for now.Cocoon
As far as I can tell it doesn't support .NET Core 2 yet, getting some exceptions with AppDomain.CurrentDomain.DefineDynamicAssemblyIow
Yes, Max, I hadn’t have the time to build and test Harmony with .NET Core 2 yet.Cocoon
A friend of mine, 0x0ade did mention to me that there's a less mature alternative which works on .NET Core, namely MonoMod.RuntimeDetour on NuGet.Cocoon
Update: By including a reference to System.Reflection.Emit, Harmony now compiles and tests OK with .NET Core 3Cocoon
Harmony 2 now uses MonoMod.Common to avoid Reflection.Emit and uses in-memory Cecil to generate the new method. It works with all .NET versions.Cocoon
Do you have and example of a patch, not prefix, postfix or transpiler, but just a method that replaces the original. Kind of like the oposite of reverse patch.Simile
@Simile To replace a method, you would create a Prefix that has all the arguments of the original plus __instance (If not static) and ref __result and let it return false to skip the original. In it, you use __instance and you assign the result to __result and then return false.Cocoon
This will not support .NET 7Fleuron
@AndreasPardeike I am trying to use this to create 'fakes' for my Unit tests (basically trying to replace calls to static functions while doing the xunit tests). The same simple code that works correctly in a console application doesn't work at all when running the tests (it is just ignored). Any ideas? Thanks!Ghazi
@Ghazi Your tests most likely run in release mode and your methods become inlined (which defeats patches). Not sure which version of Harmony you are using but newer versions try their best to prevent inlining. It also depends on the .NET version.Cocoon
W
229

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

}
Watchmaker answered 5/4, 2016 at 1:41 Comment(38)
This deserves so many more upvotes. I have a completely different scenario but this snippet is exactly what I needed to set me in the right direction. Thanks.Regen
Thanks for great answer. Is there a way to get this to work in full release mode with optimizations (i.e. run without debugging) when the methods are inlined? (When I add [MethodImpl(MethodImplOptions.NoInlining)] it works, but is there a way to replace it without this?)Wayne
@MrAnderson Yes but it's not so simple. It's more like asembler programming then c# as you need to get machine code of your inline function and then scan your application for that code. After that replace every place where you found your function with new code. It's not simple and it's prone to errors but you can do it.Watchmaker
@Watchmaker I downloaded and am studying the CLI source code, I figured out how to prevent inlining of method. Good results so far, still working though. Another question - when methodToInject is a DynamicMethod, your process fails. Any idea how to get this to work?Wayne
@Watchmaker great answer. But my question is: What's going on in debug mode? And is it possible to replace only one instruction? For example if I want to replace conditional jump on unconditional one? AFAIK you are replacing compiled method, so it's not easy to determine which condition we should replace...Gitlow
@AlexZhukovskiy in debug compiler adds some middle man code and to inject your method you need to recalculate address of your method. For the second question yes you can, you can modify assembly code of your application and swap one instruction to another. Trick is to find correct memory address to override.Watchmaker
@Watchmaker problem is that i wasn't able to recognize any asselmbly instruction. For example for a method return 15, it's just should be mov and ret, but I got an unrecognized sequence of bytes. I just casted a pointer to (byte*) and moved until 0xc3 (ret), but didn't encounter it. Am I doing it wrong? How can I get asselbmy representation from pointer?Gitlow
@AlexZhukovskiy try to map some of your code (convert it to string) and past it to this. There could be multiple reason why you not encounter 0xc3 opcode.Watchmaker
fyi There is a question pointing here...Pesade
@Watchmaker I know this site which is very useful. But I failed to map in on something. I'l post an example later if you wish.Gitlow
@AlexZhukovskiy if you like post it on stack and send me link. I will look into it and give you an answer after weekend. Machine I will look also into your question after weekend.Watchmaker
I think I've just found a solution for the above said question: I've posted my answer there, IMHO it works fine. Thanks anyway.Pesade
Two things I noticed when doing this for an integration test with MSTest: (1) When you use this inside injectionMethod*() it will reference an Injection instance during compile time, but a Target instance during runtime (this is true for all references to instance members you use inside an injected method). (2) For some reason the #DEBUG part was only working when debugging a test, but not when running a test that has been debug-compiled. I ended up always using the #else part. I don't understand why this works but it does.Clinic
this is tricky at runtime as method don't know for what object it was called and this need to be passed to method from outer method. But on compile time compiler know what is where and what call what.Watchmaker
very nice. time to break everything ! @GoodNightNerdPride use Debugger.IsAttached instead of #if preprocessorTamalatamale
@Watchmaker When injecting methods created with Reflection.Emit.MethodBuilder, throws AccessViolationException in debug mode (works in release). Any thoughts?Wayne
@MrAnderson Probably dynamic generated method has no debug code in it. Try use release version in debug for such methods. I will look closely into it in a free time.Watchmaker
Is there any way to get this to work with virtual methods? I am getting an AccessViolationException for methods which are implementations of an interface, I assume it's because they're virtual?Carrew
@RedTaz yes. Check User1892538 answer.Watchmaker
@nrofis can you explain what you have in mind saying "No" and referring to DynamicMethod?Watchmaker
@Watchmaker about the answer that you wrote to MrAnderson. If I am trying to use DynamicMethod as the injected method, DynamicMethod.MethodHandle throws an exception. It is not working even in release versionUnbridle
@Unbridle Please ask new question about injecting dynamic methods as problem is to long to resolve it in comment.Watchmaker
Any idea how I could make it work in .Net 3.5 running in Mono CLI?Guendolen
Nope. I try it on mono inside unity but I have only partial results and I don't have time right now to make it work. You may always analyze machine code and try to figure it out by yourself. You just need to have basic knowledge about how CPU works and spend some time analyzing memory as I do. Its time consuming but not too difficult task.Watchmaker
In my case, when I hit the line right before the #else, I am getting: Exception thrown: 'System.AccessViolationException' in CustomActionsTest.dll An exception of type 'System.AccessViolationException' occurred in CustomActionsTest.dll but was not handled in user code Additional information: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.Piscatory
I have the following error when run this code on .NET Core 2.0 and call the injected methods: An unhandled exception of type 'System.ExecutionEngineException' occurred in Unknown Module.Ocreate
@Piscatory I have the same result, did you find a solution? ThanksBlalock
What's the reasoning for the +2? (int*)methodToInject.MethodHandle.Value.ToPointer() + 2;Bechler
@jon - This happened when I changed the +2 value in the pointer area. Also, I only get a valid injection when I hit F5. For some reason CTRL + F5 doesn't work as expected. It doesn't make all the method calls in this case. I also used a console app. LINQPad didn't handle this code well.Bechler
Can someone explain the parts of this? What are the offsets for? I need to modify this method so that it can patch code that was compiled in release with code that was compiled in debug. Unmodified, the current implementation hard crashes the app. I'm not sure why, for example, in DEBUG with IntPrt.Size == 8 the code deals in int* instead of long*. Is that a bug or intentional?Ambiversion
@ZacharyBurns Probably you should create a new question as your problem is much more complex then this. To answer your second question, offset is necessary to access "real" pointer, but in some cases it may differ. Check this answer for ex.Watchmaker
Answered my own question. The +2 is pointer arithmetic. In x86 the code is working with int (System.Int32) on a 64 bit system (8 byte words). x64 builds uses long (System.Int64) and because expressions such as longVar + 1 produce long types we only add 1 in the latter case. productutorialspoint.com/cprogramming/c_pointer_arithmetic.htmBechler
This is awesome, thank you. One issue: the debug code doesn't seem to work with .NET core (getting NRE's when calling the patched method). Removing that makes this function for meIow
This triggers a System.ExecutionEngineException on instance methods with a return value and parameters. Doesn't work for parameterless functions either but does return uninitialized memory if you debug through it. (behaves differently with vs without debugger attached).Erosive
In .NETCore 3.X, it partially works but only for a tiny while. It looks like the pointers are being reverted by something. These are static methods that I'm trying to swap btw.Wryneck
Does anyone already know how to get the code running when the debugger is not attached during a unit test? If the debugger is attached the code works fine.Vibrato
I've reversed #IF DEBUG for that. But for me it does not work when I have debugger attached @VibratoOlivares
This appears to no longer work if the debugger is attached as of some very recent version of .NET (I suspect either 6.0.6 or 6.0.5)Frosted
D
27

You CAN modify a method's content at runtime. But you're not supposed to, and it's strongly recommended to keep that for test purposes.

Just have a look at:

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

Basically, you can:

  1. Get IL method content via MethodInfo.GetMethodBody().GetILAsByteArray()
  2. Mess with these bytes.

    If you just wish to prepend or append some code, then just preprend/append opcodes you want (be careful about leaving stack clean, though)

    Here are some tips to "uncompile" existing IL:

    • Bytes returned are a sequence of IL instructions, followed by their arguments (if they have some - for instance, '.call' has one argument: the called method token, and '.pop' has none)
    • Correspondence between IL codes and bytes you find in the returned array may be found using OpCodes.YourOpCode.Value (which is the real opcode byte value as saved in your assembly)
    • Arguments appended after IL codes may have different sizes (from one to several bytes), depending on opcode called
    • You may find tokens that theses arguments are referring to via appropriate methods. For instance, if your IL contains ".call 354354" (coded as 28 00 05 68 32 in hexa, 28h=40 being '.call' opcode and 56832h=354354), corresponding called method can be found using MethodBase.GetMethodFromHandle(354354)
  3. Once modified, you IL byte array can be reinjected via InjectionHelper.UpdateILCodes(MethodInfo method, byte[] ilCodes) - see link mentioned above

    This is the "unsafe" part... It works well, but this consists in hacking internal CLR mechanisms...

Donahoe answered 16/10, 2013 at 12:54 Comment(1)
Just to be pedantic, 354354 (0x00056832) is not a valid metadata token, the high-order byte should be 0x06 (MethodDef), 0x0A (MemberRef) or 0x2B (MethodSpec). Also, the metadata token should be written in little-endian byte order. Finally, the metadata token is module specific and MethodInfo.MetadataToken will return the token from the declaring module, making it unusable if you want to call a method not defined in the same module as the method you are modifying.Stupa
E
16

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
        }
    }
Exurbia answered 6/3, 2019 at 15:22 Comment(6)
For the moment this one is best answerOlivares
would be helpful to add a usage exampleUnsuspected
Amazing! I just tried and it works ~0~ But I wonder how it works. Could you tell me something about it? a link or a topic to let find the answer?Bevus
Does not work for dynamically generated methodToInject. Throws everything, like AccessViolationException, Internal CLR error, SEXException etcOlivares
Oh, sorry. To be clear, it does not work with dynamically generated methodToInject when debugger is attachedOlivares
Someone wrote a project based on this answer where he explains things and shows usage: github.com/spinico/MethodRedirectAnhinga
A
14

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.
Actomyosin answered 30/12, 2014 at 14:25 Comment(6)
That looks crazy dangerous. I really hope nobody uses it in production code.Stupa
Such this is used by application performance monitoring (APM) tools and is used in production as well.Repeal
Thank you for reply, i am working on a project to offer this kind of capability as Aspect Oriented Programming API. I resolved my limitation to manage virtual method and generic method on both x86 & x64. Let me know if you need more details.Actomyosin
What is the class Metadata?Minaminabe
This answer is pseudo code and outdated. Many of the methods no longer exist.Villus
@Teter28, I'm trying to figure out how I can replace a method inside a generic. I know this doesn't work with generics but do you know why?Gravelblind
T
10

There exists a couple of frameworks that allows you to dynamically change any method at runtime (they use the ICLRProfiling interface mentioned by user152949):

There are also a few frameworks that mocks around with the internals of .NET, these are likely more fragile, and probably can't change inlined code, but on the other hand they are fully self-contained and does not require you to use a custom launcher.

  • Harmony: MIT licensed. Seems to actually have been used sucessfully in a few game mods, supports both .NET and Mono.
  • [Pose][7]: MIT licensed but not updated since 2021.
  • Deviare In Process Instrumentation Engine: GPLv3 and Commercial. .NET support currently marked as experimental, but on the other hand has the benefit of being commercially backed. Unfortunately doesn't seems to have been updated since 2020.
Tegantegmen answered 4/9, 2011 at 12:8 Comment(0)
C
8

Logman's solution, but with an interface for swapping method bodies. Also, a simpler example.

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;
        }
    }
}
Cyrille answered 30/8, 2017 at 3:48 Comment(2)
This gave me: An exception of type 'System.AccessViolationException' occurred in MA.ELCalc.FunctionalTests.dll but was not handled in user code Additional information: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.,,,When replacing a getter.Villus
I got exception "wapMethodBodies doesn't yet handle IntPtr size of 8"Mellman
B
8

Based on TakeMeAsAGuest's answer, here's a similar extension which does not require to use unsafe blocks.

Here's the Extensions class:

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

namespace MethodRedirect
{
    static class Extensions
    { 
        public static void RedirectTo(this MethodInfo origin, MethodInfo target)
        {
            IntPtr ori = GetMethodAddress(origin);
            IntPtr tar = GetMethodAddress(target);
         
            Marshal.Copy(new IntPtr[] { Marshal.ReadIntPtr(tar) }, 0, ori, 1);
        }

        private static IntPtr GetMethodAddress(MethodInfo mi)
        {
            const ushort SLOT_NUMBER_MASK = 0xffff; // 2 bytes mask
            const int MT_OFFSET_32BIT = 0x28;       // 40 bytes offset
            const int MT_OFFSET_64BIT = 0x40;       // 64 bytes offset

            IntPtr address;

            // JIT compilation of the method
            RuntimeHelpers.PrepareMethod(mi.MethodHandle);

            IntPtr md = mi.MethodHandle.Value;             // MethodDescriptor address
            IntPtr mt = mi.DeclaringType.TypeHandle.Value; // MethodTable address

            if (mi.IsVirtual)
            {
                // The fixed-size portion of the MethodTable structure depends on the process type
                int offset = IntPtr.Size == 4 ? MT_OFFSET_32BIT : MT_OFFSET_64BIT;

                // First method slot = MethodTable address + fixed-size offset
                // This is the address of the first method of any type (i.e. ToString)
                IntPtr ms = Marshal.ReadIntPtr(mt + offset);

                // Get the slot number of the virtual method entry from the MethodDesc data structure
                long shift = Marshal.ReadInt64(md) >> 32;
                int slot = (int)(shift & SLOT_NUMBER_MASK);
                
                // Get the virtual method address relative to the first method slot
                address = ms + (slot * IntPtr.Size);                                
            }
            else
            {
                // Bypass default MethodDescriptor padding (8 bytes) 
                // Reach the CodeOrIL field which contains the address of the JIT-compiled code
                address = md + 8;
            }

            return address;
        }
    }
}

And here's a simple usage example:

using System;
using System.Reflection;

namespace MethodRedirect
{
    class Scenario
    {    
      static void Main(string[] args)
      {
          Assembly assembly = Assembly.GetAssembly(typeof(Scenario));
          Type Scenario_Type = assembly.GetType("MethodRedirect.Scenario");

          MethodInfo Scenario_InternalInstanceMethod = Scenario_Type.GetMethod("InternalInstanceMethod", BindingFlags.Instance | BindingFlags.NonPublic);
          MethodInfo Scenario_PrivateInstanceMethod = Scenario_Type.GetMethod("PrivateInstanceMethod", BindingFlags.Instance | BindingFlags.NonPublic);

          Scenario_InternalInstanceMethod.RedirectTo(Scenario_PrivateInstanceMethod);

          // Using dynamic type to prevent method string caching
          dynamic scenario = (Scenario)Activator.CreateInstance(Scenario_Type);

          bool result = scenario.InternalInstanceMethod() == "PrivateInstanceMethod";

          Console.WriteLine("\nRedirection {0}", result ? "SUCCESS" : "FAILED");

          Console.ReadKey();
      }

      internal string InternalInstanceMethod()
      {
          return "InternalInstanceMethod";
      }

      private string PrivateInstanceMethod()
      {
          return "PrivateInstanceMethod";
      }
    }
}

This is distilled from a more detailed project I made available on Github (MethodRedirect).

Remark : The code was implemented using .NET Framework 4 and it has not been tested on newer version of .NET.

Beret answered 28/11, 2020 at 15:21 Comment(3)
Will this allow the replacement of a property setter method with an Action or a normal method. Additionally, are we able to replace a constructor with an Action? With my own code (.NET 6.0) I've been able to swap constructors, but can't seem to repoint a setter. Thanks!Villus
The restore token operation doesn't seem to work in Net Core from this exampleRatfink
For property accessors redirection (Get or Set), you may use reflection to get the corresponding PropertyInfo from which you can obtain the respective MethodInfo using GetGetMethod and/or GetSetMethod as needed.Beret
L
5

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.

Lunnete answered 27/6, 2016 at 10:50 Comment(0)
T
3

I know it is not the exact answer to your question, but the usual way to do it is using factories/proxy approach.

First we declare a base type.

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

Then we can declare a derived type (call it 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();

The derived type can be also generated at runtime.

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

The only performance loss is during construction of the derived object, the first time is quite slow because it will use a lot of reflection and reflection emit. All other times, it is the cost of a concurrent table lookup and a constructor. As said, you can optimize construction using

ConcurrentDictionary<Type, Func<object>>.
Tarkington answered 4/9, 2011 at 13:6 Comment(2)
Hmm.. that still requires work on the programmer's behalf to actively be aware of the distributed processing; I was looking for a solution that relies only on them setting the [Distributed] attribute on the method (and not subclassing or inheriting from ContextBoundObject). Looks like I might need to do some post-compiling modifications on the assemblies using Mono.Cecil or something like that.Condescending
I would not say, that this is usual way. This way is simple in terms of skills required (no need to understand CLR), but it requires to repeat same steps for each of replaced method/class. If later you want to change something (for example, execute some code after, not only before) then you will have to do it N times (in contrast with unsafe code which requires to do it once). So it's N hours job vs 1 hour job)Olivares
Y
1

have a look into Mono.Cecil:

using Mono.Cecil;
using Mono.Cecil.Inject;

public class Patcher
{    
   public void Patch()
   {
    // Load the assembly that contains the hook method
    AssemblyDefinition hookAssembly = AssemblyLoader.LoadAssembly("MyHookAssembly.dll");
    // Load the assembly
    AssemblyDefinition targetAssembly = AssemblyLoader.LoadAssembly("TargetAssembly.dll");

    // Get the method definition for the injection definition
    MethodDefinition myHook = hookAssembly.MainModule.GetType("HookNamespace.MyHookClass").GetMethod("MyHook");
    // Get the method definition for the injection target. 
    // Note that in this example class Bar is in the global namespace (no namespace), which is why we don't specify the namespace.
    MethodDefinition foo = targetAssembly.MainModule.GetType("Bar").GetMethod("Foo");

    // Create the injector
    InjectionDefinition injector = new InjectionDefinition(foo, myHook, InjectFlags.PassInvokingInstance | InjectFlags.passParametersVal);

    // Perform the injection with default settings (inject into the beginning before the first instruction)
    injector.Inject();

    // More injections or saving the target assembly...
   }
}
Yurikoyursa answered 14/10, 2020 at 20:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.