在运行时动态地将内存冲突附加到方法上 [英] Memory Violation Dynamically Appending to Methods at runtime

查看:83
本文介绍了在运行时动态地将内存冲突附加到方法上的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

免责声明:我这样做是出于学习目的.这将不会在代码中使用.

Disclaimer: I'm doing this for learning purposes. This is not going to be used in code.

我试图了解方法表是泛型的结构,我想在运行时动态地附加到方法上.我发现了一个非常有用的堆栈溢出问题参考,可以帮助我开始.

I'm trying to understand how method table are structure for generics, I want to dynamically appending to methods at runtime. I found a very useful stack overflow question reference for getting me started.

我有一个简单的控制器,可以用来测试我的方法是否在交换:

I have a simple controller which I'm using as a test to verify my methods are swapping:

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,负责处理方法的附加内容.

I have a class Initializer which is in charge of handling appending to the method.

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问题获得的扩展:

And then I have the Extensions which I've obtained from the original stackoverflow question:

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>我遇到了内存冲突:

Note: Using the current setup everything works fine. However, the second I change the Initializer class to be a generic class Initializer<T> I get a memory violation:

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

System.AccessViolationException: 'Attempted to read or write protected memory. This is often an indication that other memory is corrupt.'

我的猜测是methodToReplace.DeclaringType.TypeHandle.Value计算对于泛型而言是不同的,还是因为编译器是生成将其写入受保护内存的泛型类的编译器?

My guess is that either the methodToReplace.DeclaringType.TypeHandle.Value calculation differs for generics, Or since the compiler is the one who generates the generic class it written to protected memory?

修改 我发现了更多信息,在使用通用参数时,我需要正确地准备方法:

Edit I've found more information I need to prepare the method properly when using generic parameters e.g:

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

但是,还有更多的难题可以解决这个问题.

However there are still a few more pieces to the puzzle to get this working.

修改

有一些类似的开源项目,例如 harmony ,但是看起来它们发出自己的程序集.在考虑了该选项之后,我还是更愿意了解我的方法表如何与泛型一起使用

There are a few open source project's such as harmony that do similar things, However it looks like their emitting their own assemblies. While I've considered the option, I would still prefer to understand how I method tables work with generics

如何附加到驻留在通用类中的方法?

How can I append to methods that reside in generic classes?

推荐答案

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

I have adapted some of those methods in my own project @ https://github.com/juliusfriedman/net7mma_core/blob/master/Concepts/Classes/MethodHelper.cs

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

I think the problem is that if your are running with the Debugger Attached then you need to also handle the portion of the logic which is currently defined by IFDEF at compilation time and replace that with an System.Diagnostics.Debugger.IsAttached although the offsets calculations (to jump over the debugger injected code) will probably have to change depending on various things like the version of the framework in use.

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

当未连接调试器且我在发布模式下运行时,在.Net Core 3.1中对我有用,当在调试模式下运行(无论是否连接调试器)或在发布模式下运行(已连接调试器)时,我会收到不同的异常. (在调试中,我收到算术溢出,而在发行版中,我收到执行引擎异常).

This works for me in .Net Core 3.1 when the debugger IS NOT attached and I am running in Release mode, when running in Debug mode with or without the debugger attached or in Release mode with the debugger attached I receive different exceptions. (In debug I receive Arithmetic Overflow, while in release I receive Execution Engine Exception).

此外,这仅在JIT Tiering启动之前有效,如果我第二次运行该方法而未连接调试器,则会收到内部CLR错误.

Furthermore this only works until the JIT Tiering kicks in, if I run the method a 2nd time without the debugger attached I am getting a Internal CLR Error.

我认为这与调试器在附加时注入的代码有关,老实说,我还不完全了解调试器在附加时注入的内容.

I believe this has to do with the code injected by the debugger when attached and to be honest I am not up to do date on exactly what the debugger is injecting when attached.

我将简化该问题的回购并询问一个问题@ https://github.com/dotnet /runtime ,如果您需要它与附加的调试器一起使用,我相信那里的人会引导您朝着正确的方向发展.

I would make a simplified repo of the problem and ask a question @ https://github.com/dotnet/runtime if you need this to work with the debugger attached and I am sure someone there will guide you in the right direction.

这篇关于在运行时动态地将内存冲突附加到方法上的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆