撤消 IL CLR 方法注入?

Undo IL CLR Method Injection?

我有一些代码采用两种方法并在 IL 中注入一种 (injectionMethod) 代替另一种 (replacedMethod)。这用于操纵 replacedMethod 的结果,以在单元测试中给出 injectionMethod 提供的结果。

public static void Inject<T>( Func<T> replacedMethod, Func<T> injectionMethod )
  {
     string replacedMethodName = replacedMethod.Method.Name;
     Type replacedMethodType = replacedMethod.Method.ReflectedType;
     string injectionMethodName = injectionMethod.Method.Name;
     Type injectionMethodType = injectionMethod.Method.ReflectedType;
     MethodInfo methodToReplace = replacedMethodType.GetMethod( replacedMethodName );
     MethodInfo methodToInject = injectionMethodType.GetMethod( injectionMethodName );
     RuntimeHelpers.PrepareMethod( methodToReplace.MethodHandle );
     RuntimeHelpers.PrepareMethod( methodToInject.MethodHandle );
     ReplacedMethod = methodToReplace;
     unsafe {
        if( IntPtr.Size == 4 ) {

           int* inj = (int*) methodToInject.MethodHandle.Value.ToPointer() + 2;
           int* tar = (int*) methodToReplace.MethodHandle.Value.ToPointer() + 2;

           if( System.Diagnostics.Debugger.IsAttached ) {
              //Version x86 Debug
              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 {
              //Version x86 Release
              *tar = *inj;
           }
        }
        else {
           long* inj = (long*) methodToInject.MethodHandle.Value.ToPointer() + 1;
           long* tar = (long*) methodToReplace.MethodHandle.Value.ToPointer() + 1;

           if( System.Diagnostics.Debugger.IsAttached ) {
              //Version x64 Debug
              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 {
              //Version x64 Release
              *tar = *inj;
           }
        }
     }
  }

该代码有效,但我不完全理解它是如何工作的。一旦 IL 被 jitted,注入的方法似乎被永久缓存,任何其他引用原始方法的代码都会失败,因为它正在引用注入的方法。

我不打算重构被测的原始代码,也不想 运行 一次使用 Inject 方法进行一个测试。有没有办法 "undo" 在这里完成注射?

我曾尝试保存目标源,但是一旦目标方法被替换,我就不能简单地重新分配以前的源,而代码不会进入中断模式。 结果证明这是一个相当简单的答案。我所要做的就是 运行 在完成 运行 交换(注入)方法后第二次执行相同的命令。 运行 使用 ToLongString() 方法对对象进行的 NUnit 测试将通过:

  public string InterceptorToString()
  {
     return "Injected Text";
  }

  [Test]
  [Category( "Interceptor" )]
  public void InjectionTest()
  {
     MyObject obj = new MyObject();

     string objString1 = obj.ToLongString();
     string intString1 = InterceptorToString(); //returns "Injected Text"

     Interceptor.Inject( obj.ToLongString, InterceptorToString );

     string objString2 = obj.ToLongString(); //returns "Injected Text"
     string intString2 = InterceptorToString();

     Interceptor.Inject( obj.ToLongString, InterceptorToString );

     string objString3 = obj.ToLongString();
     string intString3 = InterceptorToString(); //returns "Injected Text"

     Assert.That( objString2, Is.Not.EqualTo( intString2 ) );
     Assert.That( objString3, Is.Not.EqualTo( intString3 ) );
     Assert.That( objString2, Is.EqualTo( "Injected Text" ) );
  }

必须对拦截器进行的更改class:

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

           int* inj = (int*) methodToInject.MethodHandle.Value.ToPointer() + 2;
           int* tar = (int*) methodToReplace.MethodHandle.Value.ToPointer() + 2;

           if( System.Diagnostics.Debugger.IsAttached ) {
              //Version x86 Debug
              byte* injInst = (byte*) *inj;
              byte* tarInst = (byte*) *tar;

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

              Replaced = (((int) tarInst + 5) + *tarSrc) - ((int) injInst + 5);

              *tarSrc = (((int) injInst + 5) + *injSrc) - ((int) tarInst + 5);
              *injSrc = Replaced;
           }
           else {
              //Version x86 Release
              Replaced = *tar;
              *tar = *inj;
              *inj = Replaced;
           }

        }