#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 обнаружила недопустимую программу».