Pull to refresh

Comments 19

Я, честно говоря, уже даже чуть-чуть отвык от таких чисто технических статей на хабре. Но такие мне определенно нравятся больше, чем новости об очередном мониторе/телефоне/ipad/etc..)
Можно хорошую шутку к 1 апреля устроить.
Я пока не могу этого сделать. Сделаю это как только завершу свой продукт
Для задач логирования и изменения поведения своего кода или кода фреймворка, логичнее использовать аспекты или перехват байткод модификации. В Java с аспектами все хорошо, имеется отдельная либа AspectJ, есть более низкоуровневое javassist. В .NET с этим хуже, т.к. либы типа Mono.Cecil выполняют модификацию не в рантайме, а создают отдельный файл сборки с модифицированным кодом, что не всегда удобно для собственных сборок, а для модификации фреймворка видимо нужны описанные в этой статье танцы с бубном. Тот же Reflection.Emit позволяет только динамически генерировать новые сборки, но не модифицировать существующие.

Поэтому для .NET лучше подходит специальное API от Microsoft — CLR Profiling API. Эти интерфейсы позволяют перехватывать внутренние механизмы работы CLR и в частности, JIT-компиляцию. Перед JIT-компиляцией есть возможность проанализировать байткод, и изменить его. Например при каждом входе-выходе в метод дергать callback из собственной сборки, и логировать выполняемые действия (или даже менять поведение). Весь код для модификации пишется на C++, далее DLL с реализацией интерфейсов CLR Profiling API регистрируется как COM-сборка, и прописывается в соответствующие места, чтобы вызывались callback-методы из этой DLL при работе CLR. Для отдельных приложений это переменная окружения, для перехвата работы ASP.NET/IIS — запись в реестре. Плюс такого подхода в том, что можно сделать универсальное решение, которое никак не привязано к конкретным методам и сборкам. Минус — API не очень удобное, для многих вещей приходится модифицировать непосредственно «сырые» массивы байт, находя там различные флаги и подменяя их. В случае ошибки приложение просто падает и показывается окошко типа «обнаружена невалидная сборка CLR». Со стороны кажется, что в Java с этим намного проще и удобнее, там код модификации аналогичен Reflection.Emit, но есть возможность модификации jar в рантайме.

Насколько мне известно, именно это API используется в профайлерах для .NET (JetBrains dotTrace, RedGate ANTS Performance Profiler).

Может быть, как-нибудь напишу статью на эту тему, если кому-нибудь интересно :)
Отвечу на ваш комментарий. Я определенно в курсе этих возможностей. Мало того, помимо Profiling API есть также точки входа в системные библиотеки, где можно подменить код компилятора JITter'а, таким образом чтобы MSIL, поступивший на вход компилятору был переделан в другой.
Насчет чистоты решения, тут можно немного поспорить, конечно, что будет работать быстрее. Но, например, такую операцию как замена обращений к полю класса к обращениям к Property, где вставлено логгирование, быстро runtime не решить. Это будет тормозить загрузку.
Специфика, «почему я так сделал», раскрыта не будет, потому что тут зарыта идея моего продукта. Однако сам факт подмены без внедрения в чьей-либо процесс возможен и он тут показан.
> Но, например, такую операцию как замена обращений к полю класса к обращениям к Property, где вставлено логгирование, быстро runtime не решить. Это будет тормозить загрузку.

JIT ведь выполняется один раз, значит тормоза будут только первый раз, не думаю, что это очень критично. Да и сомневаюсь, что подобные хаки могут использоваться для production. Такие инструменты обычно используются для отладочных целей — профайлинга, логирования. А для целей отладки как раз CLR Profiling API выглядит более стандартным решением.

Но т.к. я не знаю суть вашего инновационного продукта, то и не могу достаточно аргументированно спорить :)

P.S. Кстати, если кому-то интересен подход с использованием CLR Profiling API, есть отличная статья на эту тему: msdn.microsoft.com/en-us/magazine/cc188743.aspx
UFO just landed and posted this here
Хм. интересно. Спасибо за статью.
Отличное дополнение к утилитам .NET реверса в учебных целях.
Я видел толковую техническую статью на Хабре, и не ослеп!
Данный метод кажется немного более сложным и нелогичным, чем внедрение своей библиотеки в адресное пространство процесса и подменой адреса исходной ф-ции на адрес ф-ции в библиотеке.

Я не большой специалист по .NET, поэтому поправьте меня, если я не прав и представленный Вами метод лучше.
Представленный метод в некотором смысле хренов тем, что под x64 придется морочиться с подписью драйверов. Т.е. да, проверку можно отключить, можно подписать тестовым сертификатом, но муторно все это, да и распространять любительское приложение с таким довеском проблемно как раз из-за драйвера.
Это уже проблема не пользователя, а моя :)
Мое решение основано на методе видоизменения кода GAC библиотек. Т.е. оно пляшет от другого.
Но все же: если мы перехватываем вызовы, и каждый раз видоизменяем код тех библиотек, то получается что мы проседаем при каждом старте. Это не всегда хорошо. Например, представим что у нас есть .Net сервер, суть которого в том и состоит что он постоянно дергает запуск новых процессов. Не то чтобы сотнями в минуту, но все равно. Относительно часто. Тогда мы получим относительно более сильное проседание. Библиотека mscorlib большая для анализа. Если мы хотим вставить логгирование на, например, обращения к внутреннему филду, то нам надо будет по всему коду всех методов библиотеки проверить, нет ли к нему обращений. Это просядет загрузку процесса настолько что проще отказаться от такого изощренного способа отладки, в угоду более стардартным средствам. Если же все вычисления вынести за пределы загрузки, сделать их заранее, то скорость загрузки не пострадает вообще никак.
Мне всегда нравятся статьи в которых есть моменты типа:
Тут все просто…

и дальше стопицот страниц кода :)

А вообще — да, статья отличная.
Мне казалось, что все уже давно используют detours и аналоги, а выходит нет. Хотя данный способ мне нравится, поскольку использует документированные возможности системы.
Кстати никаких проблем с подписью драйверов нет — нужен лишь обычный сертификат подписи кода.
Только почему .NET? — подмена возможна любых файлов.
Естественно, любых :) Смысл в том чтобы подменить библиотеки в GAC
Sign up to leave a comment.

Articles