【问题标题】:Memory Violation Dynamically Appending to Methods at runtime内存冲突在运行时动态附加到方法
【发布时间】:2020-05-03 18:57:08
【问题描述】:

免责声明:我这样做是为了学习。这不会在代码中使用。

我试图了解方法表是如何用于泛型的结构,我想在运行时动态附加到方法。我找到了一个非常有用的 stack overflow question 参考来帮助我入门。

我有一个简单的控制器,我用它来验证我的方法是否正在交换:

public class ValuesController : ControllerBase
{
    static ValuesController() {
        var methodToReplace = typeof(ValuesController).GetMethod(nameof(ValuesController.Seven),
            BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);

        var methodToAppend = typeof(ValuesController).GetMethod(nameof(ValuesController.Eight),
            BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);

        new Initializer(methodToReplace, methodToAppend);
    }

    [HttpGet("Seven")]
    public int Seven(string id)
    {
        return 7;
    }

    [HttpGet("Eight")]
    public int Eight(string id)
    {
        return 8;
    }
}

我有一个类 Initializer 负责处理附加到方法。

public class Initializer
{
    public Initializer(MethodInfo methodToReplace, MethodInfo methodToAppend)
    {
        var dummyMethod = typeof(Initializer).GetMethod(nameof(Dummy),
            BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);

        var proxyMethod = typeof(Initializer).GetMethod(nameof(Proxy),
            BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);

        var appendedMethod = typeof(Initializer).GetMethod(nameof(Appended),
            BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);

        dummyMethod.OneWayReplace(methodToReplace);
        methodToReplace.OneWayReplace(proxyMethod);
        appendedMethod.OneWayReplace(methodToAppend);
    }

    public int Proxy(string id)
    {
        Dummy(id);
        return Appended(id);
    }

    public int Dummy(string id)
    {
        return 0;
    }

    public int Appended(string id)
    {
        return 0;
    }
}

然后我得到了从原始 stackoverflow 问题中获得的扩展:

public static class InjectionExtensions
{
    // Note: This method replaces methodToReplace with methodToInject
    // Note: methodToInject will still remain pointing to the same location
    public static unsafe MethodReplacementState OneWayReplace(this MethodInfo methodToReplace, MethodInfo methodToInject)
    {
        //#if DEBUG
        RuntimeHelpers.PrepareMethod(methodToReplace.MethodHandle);
        RuntimeHelpers.PrepareMethod(methodToInject.MethodHandle);
        //#endif
        MethodReplacementState state;

        IntPtr tar = methodToReplace.MethodHandle.Value;
        var inj = methodToInject.MethodHandle.Value + 8;

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

注意:使用当前设置一切正常。但是,第二次我将 Initializer 类更改为泛型类 Initializer<T> 我得到内存冲突:

System.AccessViolationException: '试图读取或写入受保护的内存。这通常表明其他内存已损坏。'

我的猜测是methodToReplace.DeclaringType.TypeHandle.Value 计算对于泛型不同,或者因为编译器是生成它写入受保护内存的泛型类的那个?

编辑 我发现了更多信息,我需要在使用泛型参数时正确准备方法,例如:

RuntimeHelpers.PrepareMethod(methodToInject.MethodHandle, new[] { typeof(T).TypeHandle });

然而,要让这项工作发挥作用,还有一些难题要解决。

编辑

有一些开源项目,例如harmony 做类似的事情,但看起来他们发出自己的程序集。虽然我已经考虑过这个选项,但我仍然希望了解我的方法表如何与泛型一起使用

如何附加到泛型类中的方法?

【问题讨论】:

  • DeclaringType 在这种情况下将是泛型类型,对吧?您是否需要像 SO answer 那样重建方法签名?
  • @timur,DeclaringType 是泛型的,但是,该方法没有引用泛型类型。泛型显然对方法交换有一些影响。但是我没有足够的信息来理解,我的猜测是声明类型修改了类开始的地址计算。所以我正在写入受保护的内存,因为我正在覆盖内存屏障。我对重建方法签名的意思有点困惑,我不是通过反射调用方法,我只是交换指针

标签: c# methods dynamic reflection runtime


【解决方案1】:

我想你已经看过了:Dynamically replace the contents of a C# method?

我已经在自己的项目@https://github.com/juliusfriedman/net7mma_core/blob/master/Concepts/Classes/MethodHelper.cs中调整了其中的一些方法@

我认为问题在于,如果您使用附加的调试器运行,那么您还需要在编译时处理当前由IFDEF 定义的逻辑部分,并用System.Diagnostics.Debugger.IsAttached 替换它,尽管偏移量计算(跳过调试器注入的代码)可能必须根据所使用的框架版本等各种因素进行更改。

https://github.com/juliusfriedman/net7mma_core/blob/master/Concepts/Classes/MethodHelper.cs#L35

当未附加调试器并且我在发布模式下运行时,这对我在 .Net Core 3.1 中有效,在附加或不附加调试器的调试模式下运行或在附加调试器的发布模式下运行时,我收到不同的异常。 (在调试中我收到算术溢出,而在发布中我收到执行引擎异常)。

此外,这仅在 JIT 分层启动之前有效,如果我在没有附加调试器的情况下第二次运行该方法,我将收到内部 CLR 错误。

我相信这与调试器在附加时注入的代码有关,老实说,我不知道调试器在附加时注入的确切内容。

如果您需要它来使用附加的调试器,我会对该问题进行简化回购并提出问题@https://github.com/dotnet/runtime,我相信那里有人会指导您正确的方向。

【讨论】:

  • 我通常在调试模式下运行,并附加了调试器,我发布的代码工作正常,只有当我将 Initalizer 切换为通用时它会中断,或者我尝试交换通用方法,我认为这是因为泛型是如何在幕后完成的。你是说这将在发布模式下工作?
  • 可能至少是第一次。
猜你喜欢
  • 1970-01-01
  • 2013-01-03
  • 2010-10-17
  • 2020-06-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-06-08
相关资源
最近更新 更多