04 February, 2009

Секреты IL инструментации и профайлерного API для .NET Framework

По роду своей деятельности я занимаюсь в том числе и инструментацией IL. Задача состоит в инжекции в оригинальный IL callback'ов: в начало каждого стейтмента (вынимается из PDB) и в каждую точку выхода из функции. С точками возврата есть две проблемы: префикс tail. и throw/rethrow.

Первый в реальных программах практически не встречается, но при встрече гарантирует автору инструментатора головную боль. По спецификации после префикса tail. должен следовать call/calli/callvirt, а после него обязательно ret. Все бы хорошо, но по спецификации на ret разрешен переход (и какой урод это придумал). Объясню более подробно - вы должны проинструментировать все точки выхода из программы, с одной стороны вы не имеете права вставлять свои команды между tail., call/calli/callvirt и ret, а с другой стороны вы должны вставить перед командой ret код, в случае перехода на этот самый ret. Выход один - править структуру метода...

Второй вариант коварнее. При кидании exception'а невозможно на основании только кода метода понять куда нас этот exception занесет. Мы можем остаться в том же самом методе - в обработчике exception, а можем вылететь из метода как пробка из бутылки на несколько функций наверх по стеку. К чему я все это говорю? А к тому - слава великому Аллаху, что как минимум одному сотруднику компании Microsoft пришла в голову мысль добавить немного жизненно нужных event'ов в профайлерное API. Особый интерес представляют: ExceptionUnwindFunctionEnter, ExceptionUnwindFunctionLeave и ExceptionCatcherEnter.

И еще, в свете вышесказанного, флаги COR_PRF_MONITOR_EXCEPTIONS, COR_PRF_MONITOR_ENTERLEAVE и COR_PRF_MONITOR_CODE_TRANSITIONS должны всегда устанавливаться вместе. Последний флаг - это вообще магия без него нет метаданных в памяти для mscorlib и нет 90% Enter/Leave/Tailcall или Enter2/Leave2/Tailcall2 для managed кода.

А напоследок граф переходов обработчика exception так сказать для полноты картины:


No comments: