Добавление вызова метода перед STFLD вызывает исключение InvalidProgramException

#c# #jit #cil

#c# #jit #cil

Вопрос:

У меня есть доступ к промежуточному языку тела функции, подобному этому :

 byte[] ilCodes = NestedFooInfo.GetMethodBody().GetILAsByteArray();
  

Я хочу иметь возможность изменять его IL-код, чтобы всякий раз, когда есть stfld команда IL, я вызывал следующий метод с именем OnChangeField :

 public static void OnChangeField(object obj, object value)
{
    Console.WriteLine("VICTORY");
    return;
}
  

Пока я делаю это так :

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

 MethodInfo OnStfld = typeof(MethodBoundaryAspect).GetMethod("OnChangeField");
byte[] callIL = new byte[5];
callIL[0] = (byte)OpCodes.Call.Value;
callIL[1] = (byte)(OnStfld.MetadataToken amp; 0xFF);
callIL[2] = (byte)(OnStfld.MetadataToken >> 8 amp; 0xFF);
callIL[3] = (byte)(OnStfld.MetadataToken >> 16 amp; 0xFF);
callIL[4] = (byte)(OnStfld.MetadataToken >> 24 amp; 0xFF);
  

И я изменяю исходный NestedFoo(...) код тела метода () следующим образом :

 byte[] ilCodes = NestedFooInfo.GetMethodBody().GetILAsByteArray();
var stfldOpCode = (byte)OpCodes.Stfld.Value;
for (int i = 0; i < ilCodes.Length; i  )
{
    if (ilCodes[i] == stfldOpCode)
    {
         byte[] newIlCodes = ilCodes.Take(i).Concat(callIL).Concat(ilCodes.Skip(i)).ToArray(); // Insert the call instruction before the s
         InjectionHelper.UpdateILCodes(NestedFooInfo, newIlCodes); // Explanation below
         break; // Currently I just want to hook the first stfld as a PoC
     }
}
  

Тело метода, измененное в моем тестовом примере, выглядит следующим образом :

 public class ExceptionHandlingService : IExceptionHandlingService
{
        public static string var1 = "initialValue";
        public static string Var2 { get; set; } = "initialValue";
        public string var3 = "initialValue";
        public string Var4 { get; set; } = "initialValue";

        public string NestedFoo(SampleClass bar)
        {
            var1 = "value set in NestedFoo()";
            Var2 = "value set in NestedFoo()";
            var3 = "value set in NestedFoo()";
            Var4 = "value set in NestedFoo()";
            AddPerson("From", "NestedFoo", 2);
            return Foo();
        }
        [...]
}
  

Я вызываю метод следующим образом :

 var a = new ExceptionHandlingService();
var b = new SampleClass("bonjour", 2, 3L); // Not really relevant
a.NestedFoo(b);
  

И я получаю :

Система.Исключение InvalidProgramException: «Среда выполнения Common Language обнаружила недопустимую программу».

или

Система.Исключение BadImageFormatException: ‘Индекс не найден. (Исключение из HRESULT: 0x80131124)’

Если я удалю postsharp и интерфейс служб из ExceptionHandlingService

Я полагаю, что я отредактировал код Il таким образом, чтобы это привело к недопустимому потоку кода, но, просмотрев call stfld документацию и здесь (p368 и p453) Я не знаю, что я сделал не так.

Для тех, кто задается вопросом, какая магия происходит, InjectionHelper.UpdateILCodes(NestedFooInfo, newIlCodes); вы можете проверить эту ссылку, которая показывает, как вы можете редактировать Il-код во время выполнения.

Комментарии:

1. Можете ли вы показать пример примерного потока ввода IL вместе с тем, как вы хотите, чтобы конечный IL выглядел после вашей инъекции? Я спрашиваю, потому что не совсем ясно, как будет выглядеть конечный IL. В частности, мне интересно, откуда вы получаете значения параметров для вашего вызова OnChangeField .

Ответ №1:

IL немного хрупкий, на самом деле он не предназначен для сборки вручную 🙂 В любом случае, вы не можете найти stfld , просто ища значение в один байт — вам нужно убедиться, что вы действительно читаете код операции, а не аргумент и т.д.

Если у вас нет веской причины не делать этого, я бы рекомендовал использовать Expression деревья для компиляции «сырого» IL — это должно избавить вас от большой головной боли. В любом случае, попробуйте двигаться очень маленькими шагами — например, сначала попробуйте ввести вызов в начале метода, попробуйте метод без аргументов, затем попробуйте найти stfld , обработать более сложные случаи (бокс и т. Д.)…

Для продвижения вперед это помогает использовать компиляторы и декомпиляторы. Скомпилируйте обычный код C #, вызывающий ваш метод, и декомпилируйте его в IL. Попробуйте выполнить инъекцию и декомпилируйте полученный полный массив байтов IL — вы, вероятно, найдете ошибку (хотя я бы не сказал «достаточно легко», поскольку для выявления большинства ошибок требуется немного опыта IL :)).

Одна вещь, которая сразу бросается в глаза, заключается в том, что вы сохраняете оба stfld и call , но передаете аргументы только один раз. Опять же, как часть выявления проблемы, это помогает выполнять действия по очереди — заменять на stfld на call , чтобы убедиться, что эта часть работает; затем обработайте dup значения в стеке, чтобы позволить вам вызывать обе операции.

Ответ №2:

Следующее исключение было вызвано маркером метаданных метода OnChangeField(), который находился вне исполняемой сборки.

Система.Исключение BadImageFormatException: ‘Индекс не найден. (Исключение из HRESULT: 0x80131124)’

И следующее исключение было вызвано аспектом postsharp, применяемым к функции NestedFoo, а также потому, что я не включил ни один аргумент в стек для передачи аргументам OnchangeField:

Система.Исключение InvalidProgramException: «Среда выполнения Common Language обнаружила недопустимую программу».