Модификация кода C # IL - не трогайте стек

Этот вопрос касается статического анализа стека пользовательского кода C # IL и того, как разработать коды операций для удовлетворения требований компилятора.

У меня есть код, который изменяет существующие методы C #, добавляя к нему мой собственный код. Чтобы избежать возврата исходного метода до выполнения моего кода, он заменяет все коды операций RET на конечную метку BR и добавляет эту метку в конец исходного кода. Затем я добавляю туда еще код и, наконец, RET.

В целом все это работает нормально, но не работает с некоторыми методами. Вот простой пример:

public static string SomeMethod(int val)
{
    switch (val)
    {
        case 0:
            return "string1".convert();
        case 1:
            return "string2".convert();
        case 2:
            return "string3".convert();
        // ...
    }
    return "";
}

который представлен этим кодом IL:

.method public hidebysig static string SomeMethod(int32 val) cil managed
{
    .maxstack 1
    .locals val ([0] int32 num)
    L_0000: ldarg.0 
    L_0001: stloc.0 
    L_0002: ldloc.0 
    L_0003: switch (L_002e, L_004f, L_0044, ...)
    L_002c: br.s L_0091
    L_002e: ldstr "string1"
    L_0033: call string Foo::convert(string)
    L_0038: ret 
    L_0039: ldstr "string2"
    L_003e: call string Foo::convert(string)
    L_0043: ret 
    L_0044: ldstr "string3"
    L_0049: call string Foo::convert(string)
    L_004e: ret 
    ... 
    L_0091: ldstr ""
    L_0096: ret 
}

После того, как моя программа изменила его, код выглядит так:

.method public hidebysig static string SomeMethod(int32 val) cil managed
{
    .maxstack 1
    .locals val ([0] int32 num)
    L_0000: ldarg.0 
    L_0001: stloc.0 
    L_0002: ldloc.0 
    L_0003: switch (L_002e, L_004f, L_0044, ...)
    L_002c: br.s L_0091
    L_002e: ldstr "string1"
    L_0033: call string Foo::convert(string)
    L_0038: br L_009b // was ret 
    L_0039: ldstr "string2"
    L_003e: call string Foo::convert(string)
    L_0043: br L_009b // was ret 
    L_0044: ldstr "string3"
    L_0049: call string Foo::convert(string)
    L_004e: br L_009b // was ret 
    ... 
    L_0091: ldstr ""
    L_0096: br L_009b // was ret
    L_009b: my code here
    ...
    L_0200: ret
}

и я получаю ошибку компиляции:

Не удалось выполнить действие после длительного события. Исключение: System.TypeInitializationException: исключение было создано инициализатором типа для FooBar ---> System.InvalidProgramException: недопустимый код IL в (динамический метод оболочки) Foo: SomeMethod (int): IL_0000: ldnull

Есть ли какой-нибудь простой способ заменить RET обычным способом и сделать так, чтобы статический анализатор остался доволен?


person Andreas Pardeike    schedule 12.03.2017    source источник
comment
Решил проблему. Замена RET на BR увеличивает длину кода, и короткие переходы могут стать незаконными. Решение - заменить их прыжками в длину. Проверено и работает.   -  person Andreas Pardeike    schedule 13.03.2017
comment
Вы также можете использовать предложение try-finally; это позволит избежать всех ваших неприятностей. Конечно, это имеет смысл только в том случае, если вы хотите всегда выполнять этот код - он также будет выполняться при исключении. В зависимости от того, какой код вы вводите, это может быть хорошо или плохо.   -  person Luaan    schedule 13.03.2017
comment
хороший улов на собственной ошибке :) ты должен сам себе ответить   -  person Regis Portalez    schedule 13.03.2017


Ответы (1)


Проблема оказалась в том, что все инструкции короткого перехода могли оказаться слишком далеко, потому что вставка BR вместо RET увеличивает размер кода операции.

Я решил это, заменив все коды операций, заканчивающиеся на "_S", на их соответствующие версии с длинным прыжком. Подробнее об этом можно узнать из этой фиксации моего проекта: Исправлены недопустимые короткие переходы

person Andreas Pardeike    schedule 13.03.2017
comment
@IlianPinzon, я жду 24-часовой период охлаждения, прежде чем stackoverflow позволит мне принять мой собственный ответ. - person Andreas Pardeike; 14.03.2017