Почему localloc ломает этот метод CIL?
У меня есть следующий фрагмент сокращенного кода CIL.
Когда этот метод CIL выполняется, CLR генерирует исключение InvalidProgramException:
.method assembly hidebysig specialname rtspecialname
instance void .ctor(class [mscorlib]System.Collections.Generic.IEnumerable`1<class System.Windows.Input.StylusDeviceBase> styluses) cil managed
{
.locals init (class [mscorlib]System.Collections.Generic.IEnumerator`1<class System.Windows.Input.StylusDeviceBase> V_0,
class System.Windows.Input.StylusDeviceBase V_1)
ldc.i4.8 // These instructions cause CIL to break
conv.u //
localloc //
pop //
ldarg.0
newobj instance void class [mscorlib]System.Collections.Generic.List`1<class System.Windows.Input.StylusDevice>::.ctor()
call instance void class [mscorlib]System.Collections.ObjectModel.ReadOnlyCollection`1<class System.Windows.Input.StylusDevice>::.ctor(class [mscorlib]System.Collections.Generic.IList`1<!0>)
ldarg.1
callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> class [mscorlib]System.Collections.Generic.IEnumerable`1<class System.Windows.Input.StylusDeviceBase>::GetEnumerator()
stloc.0
.try
{
leave.s IL_0040
}
finally
{
endfinally
}
IL_0040: ret
} // end of method StylusDeviceCollection::.ctor
У меня вопрос, почему этот код CIL недействителен?
Несколько аберраций:
- Если localloc
удаляется, код работает нормально. Насколько мне известно, localloc
заменяет размер параметра в стеке адресом, поэтому стек остается сбалансированным, AFAICT.
- Если блоки try и finally удаляются, код работает нормально.
- если первый блок инструкций, содержащий localloc
перемещается в после блока try-finally, код работает нормально.
Похоже, что-то в комбинации localloc и try-finally.
Немного предыстории:
Я пришел к этому моменту после того, как InvalidProgramException был брошен для оригинального метода, из-за некоторого инструментария, сделанного во время выполнения. Мой подход к отладке этого, чтобы выяснить, что не так с инструментарием:
- Разборка неисправной DLL с
ildasm
- Применение кода инструментария к методу сбоя
- Воссоздание DLL из модифицированного IL с
ilasm
- Повторный запуск программы и проверка ее сбоя
- Продолжайте постепенно сокращать код IL метода сбоя, вплоть до минимального сценария, вызывающего проблему (и стараясь не вносить ошибок по пути...)
К несчастью, peverify.exe /IL
не указал ни одной ошибки. Я пытался утешить спецификацию ECMA и книгу Сержа Лидина Expert .NET IL, но не смог понять, что же не так.
Есть что-то основное, что я пропускаю?
Редактировать:
Я немного обновил код IL, чтобы сделать его более полным (без изменения инструкций). Второй блок инструкций, в том числе ldarg
, newobj
и т. д., берется из рабочего кода как есть - исходный код метода.
Что странно для меня, удалив либо localloc
или же .try
- finally
код работает - но, насколько мне известно, ни один из них не должен изменять балансировку стека по сравнению с тем, присутствуют ли они в коде.
Вот код IL, декомпилированный в C# с помощью ILSpy:
internal unsafe StylusDeviceCollection(IEnumerable<StylusDeviceBase> styluses)
{
IntPtr arg_04_0 = stackalloc byte[(UIntPtr)8];
base..ctor(new List<StylusDevice>());
IEnumerator<StylusDeviceBase> enumerator = styluses.GetEnumerator();
try
{
}
finally
{
}
}
Изменить 2:
Больше наблюдений:
- Принимая localloc
Блок кода IL и перемещение его в конец функции, код работает нормально - так что кажется, что код сам по себе в порядке.
- Эта проблема не воспроизводится при вставке аналогичного кода IL в функцию тестирования Hello World.
Я очень озадачен...
Хотелось бы, чтобы был способ получить больше информации из InvalidProgramException. Кажется, что CLR не присоединяет точную причину сбоя к объекту исключения. Я также думал об отладке с помощью отладочной сборки CoreCLR, но, к сожалению, отлаживаемая программа не совместима с ним...
1 ответ
К сожалению, кажется, что я нажал на ошибку CLR...
Все работает при использовании устаревшего JIT-компилятора:
set COMPLUS_useLegacyJit=1
Я не смог выделить конкретную настройку RyuJit, которая может быть причиной этого. Я следовал рекомендации в этой статье:
https://github.com/Microsoft/dotnet/blob/master/Documentation/testing-with-ryujit.md
Спасибо всем, кто помог!
Последствие:
Через некоторое время после того, как я наткнулся на устаревший обходной путь JIT, я понял, что проблема проявляется только при localloc
(который является непроверяемым кодом операции) в критический метод безопасности, вызываемый из прозрачного метода безопасности. Только в этом случае RyuJit бросит InvalidProgramException
в то время как Legacy JIT не будет.
При воспроизведении я разобрал и заново собрал DLL, о которой идет речь, и напрямую изменил код функции, сохранив атрибуты безопасности без изменений, в частности AllowPartiallyTrustedCallers
атрибут сборки - объясняющий, почему проблема не была воспроизведена на единичном примере.
Вполне возможно, что в RyuJIT есть некоторое усиление безопасности по сравнению с Legacy JIT, которое решает эту проблему, но тем не менее тот факт, что localloc
заставит CLR бросить InvalidProgramException
в зависимости от наличия улова и его относительного расположения к localloc
Похоже, тонкая ошибка.
Запуск SecAnnotate.exe (средство .NET Security Annotator) в сбойной DLL помог выявить проблемы безопасности между вызовами функций.
Подробнее о прозрачном коде безопасности:
https://docs.microsoft.com/en-us/dotnet/framework/misc/security-transparent-code