Нарушение памяти, динамически добавляющееся к методам во время выполнения - PullRequest
3 голосов
/ 17 января 2020

Отказ от ответственности: Я делаю это в целях обучения. Это не будет использоваться в коде.

Я пытаюсь понять, как таблица методов является структурой для обобщений, я хочу динамически добавлять методы во время выполнения. Я нашел очень полезную справку по переполнению стека справочную информацию для начала работы.

У меня есть простой контроллер, который я использую в качестве теста для проверки подмены моих методов:

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 на общий c класс Initializer<T> возникает нарушение памяти:

System.AccessViolationException: 'Попытка чтения или записи в защищенную память. Это часто указывает на то, что другая память повреждена. '

Я предполагаю, что либо вычисление methodToReplace.DeclaringType.TypeHandle.Value отличается для обобщенных типов, либо поскольку компилятор генерирует класс generi c он записан в защищенную память?

Редактировать Я нашел больше информации, мне нужно правильно подготовить метод при использовании параметров generi c, например:

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

Тем не менее, есть еще несколько кусочков головоломки, чтобы это заработало.

Edit

Есть несколько проектов с открытым исходным кодом, таких как harmony которые делают подобные вещи, однако похоже, что они испускают свои собственные сборки. Несмотря на то, что я рассмотрел этот вариант, я все же предпочел бы понять, как я работаю с таблицами методов с обобщениями

Как я могу добавить методы, которые находятся в обобщенных c классах?

1 Ответ

1 голос
/ 15 февраля 2020

Полагаю, вы уже видели: Динамически заменить содержимое метода C#?

Я адаптировал некоторые из этих методов в своем собственном проекте @ https://github.com/juliusfriedman/net7mma_core/blob/master/Concepts/Classes/MethodHelper.cs

Я думаю, что проблема в том, что если вы работаете с приложенным отладчиком, вам нужно также обработать часть лога c, которая в настоящее время определяется IFDEF во время компиляции и замените это на System.Diagnostics.Debugger.IsAttached, хотя вычисления смещений (чтобы перепрыгнуть через введенный отладчиком код), вероятно, придется изменить в зависимости от различных вещей, например, от версии используемой платформы.

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

Это работает для меня в. Net Core 3.1, когда отладчик НЕ подключен, и я работаю в режиме выпуска, когда работает в режиме отладки с отладчиком или без него или в режиме выпуска с прикрепленным отладчиком я получаю разные исключения. (При отладке я получаю Arithmeti c Overflow, в то время как в выпуске я получаю Exception Engine Exception).

Кроме того, это работает только до запуска уровня JIT, если я запускаю метод 2-й раз без подключенного отладчика Я получаю внутреннюю ошибку CLR.

Я считаю, что это связано с кодом, введенным отладчиком при подключении, и, если честно, я не знаю точно, что именно вводит отладчик при подключении.

Я бы сделал упрощенное репо проблемы и задам вопрос @ https://github.com/dotnet/runtime, если вам нужно это для работы с подключенным отладчиком, и я уверен, что кто-то там поможет вам в правильное направление.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...