1

I'm trying to swap out the contents of a method at runtime for the purposes of unit testing legacy code. I've been working with these SO answers;

  1. Dynamically replace the contents of a C# method?
  2. How to replace the pointer to the overridden (virtual) method in the pointer of my method? (Release x64 and x86)

Here's a full code sample of what I have so far.

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

namespace Foo.Bar {

    public interface IFoo {
        string Apple();
    }

    public class Bar {

        protected virtual object One() {
            return null;
        }

        protected virtual object Two() {
            return null;
        }

        protected virtual object Three() {
            return null;
        }

        /* Uncomment this to generate a null reference */
        //protected virtual object Four() {
        //    return null;
        //}

    }

    public class Foo : Bar, IFoo {

        public string Apple() {
            return "Apple";
        }

        public string Orange() {
            return "Orange";
        }

        /* Uncommenting this fixes the null reference */
        //public override int GetHashCode() {
        //    throw new NotImplementedException();
        //}

        public void ReplaceMethod(Delegate targetMethod, Delegate replacementMethod) {

            MethodInfo methodToReplace = targetMethod.Method;
            MethodInfo methodToInject = replacementMethod.Method;

            RuntimeHelpers.PrepareMethod(methodToReplace.MethodHandle);
            RuntimeHelpers.PrepareMethod(methodToInject.MethodHandle);

            if (methodToReplace.IsVirtual)
                ReplaceVirtualInner(methodToReplace, methodToInject);
            else
                ReplaceStandard(methodToReplace, methodToInject);

        }

        private void ReplaceStandard(MethodInfo methodToReplace, MethodInfo methodToInject) {

            IntPtr targetPtr = methodToInject.MethodHandle.Value;
            IntPtr replacePtr = methodToReplace.MethodHandle.Value;

            unsafe
            {
                if (IntPtr.Size == 4) {

                    int* inj = (int*)replacePtr.ToPointer() + 2;
                    int* tar = (int*)targetPtr.ToPointer() + 2;

                    if (Debugger.IsAttached) {

                        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 {
                        *tar = *inj;
                    }
                }
                else {

                    long* inj = (long*)replacePtr.ToPointer() + 1;
                    long* tar = (long*)targetPtr.ToPointer() + 1;

                    if (Debugger.IsAttached) {

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

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

                        *tarSrc = (((long)injInst + 5) + *injSrc) - ((long)tarInst + 5);
                    }
                    else {
                        *tar = *inj;
                    }
                }
            }


        }

        private void ReplaceVirtualInner(MethodInfo methodToReplace, MethodInfo methodToInject) {

            unsafe
            {
                UInt64* methodDesc = (UInt64*)(methodToReplace.MethodHandle.Value.ToPointer());
                int index = (int)(((*methodDesc) >> 32) & 0xFF);

                if (IntPtr.Size == 4) {
                    uint* classStart = (uint*)methodToReplace.DeclaringType.TypeHandle.Value.ToPointer();
                    classStart += 10;
                    classStart = (uint*)*classStart;

                    uint* tar = classStart + index;
                    uint* inj = (uint*)methodToInject.MethodHandle.Value.ToPointer() + 2;

                    if (Debugger.IsAttached) {

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

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

                        *tarSrc = (((uint)injInst + 5) + *injSrc) - ((uint)tarInst + 5);
                    }
                    else {
                        *tar = *inj;
                    }

                }
                else {

                    ulong* classStart = (ulong*)methodToReplace.DeclaringType.TypeHandle.Value.ToPointer();
                    classStart += 8;
                    classStart = (ulong*)*classStart;

                    ulong* tar = classStart + index;
                    ulong* inj = (ulong*)methodToInject.MethodHandle.Value.ToPointer() + 1;

                    if (Debugger.IsAttached) {

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

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

                        *tarSrc = (((ulong)injInst + 5) + *injSrc) - ((ulong)tarInst + 5);
                    }
                    else {
                        *tar = *inj;
                    }

                }

            }
        }

    }

}

Usage;

    Foo.Bar.Foo foo = new Foo.Bar.Foo();

    foo.ReplaceMethod(
        ((Func<string>)foo.Apple),
        ((Func<string>)foo.Orange)
    );

    var result = foo.Apple(); // this is "Orange" :)

I have a broad understanding of the code above, essentially it's locating the address of the target method and changing the value so that it points to a different memory location. There's also some extra voodoo to accommodate for memory buffers added by the debugger.

Virtual methods are handled differently which I don't fully understand, especially;

uint* classStart = (uint*)methodToReplace.DeclaringType.TypeHandle.Value.ToPointer();
classStart += 10; /* why 10?? */
classStart = (uint*)*classStart;

The code works, however, here's where things get weird; if the base class (of the declaring type of the target method) has more than 3 virtual methods, implements an interface and does not override any methods then a NullReferenceException is thrown.

Please, can someone explain what's going on and help me gain a deeper understanding of the code?

Community
  • 1
  • 1
Red Taz
  • 4,159
  • 4
  • 38
  • 60
  • Since you're doing this for unit testing, i'd look into Microsoft Fakes framework. Does the same for you without the hassles of manipulating pointers. Additionally, AFAIC the size of the method you're replacing has to match the memory size of the method you're replacing it with. – zaitsman May 23 '17 at 12:44
  • @zaitsman That looks like it would be very useful, unfortunately it seems to require Visual Studio Enterprise, time to get the begging bowl out – Red Taz May 23 '17 at 14:16

1 Answers1

1

What you are asking is actually a feature in typemock a unit testing framework that allows you to change the implementation of a mocked method to another one in a simple line of code, for example:

[TestMethod]
public void TestMethod1()
{
    var real = new Foo();

    Isolate.WhenCalled(() => real.Apple()).DoInstead(x => { return real.Orange(); });

    Assert.AreEqual("Orange", real.Apple());
}

you can learn more about this feature here.

JamesR
  • 745
  • 4
  • 15