Comments 19
Я, честно говоря, уже даже чуть-чуть отвык от таких чисто технических статей на хабре. Но такие мне определенно нравятся больше, чем новости об очередном мониторе/телефоне/ipad/etc..)
+12
Можно хорошую шутку к 1 апреля устроить.
+5
Где же полный сорс для желающий потестить?
0
Для задач логирования и изменения поведения своего кода или кода фреймворка, логичнее использовать аспекты или перехват байткод модификации. В 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).
Может быть, как-нибудь напишу статью на эту тему, если кому-нибудь интересно :)
Поэтому для .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).
Может быть, как-нибудь напишу статью на эту тему, если кому-нибудь интересно :)
+9
Отвечу на ваш комментарий. Я определенно в курсе этих возможностей. Мало того, помимо Profiling API есть также точки входа в системные библиотеки, где можно подменить код компилятора JITter'а, таким образом чтобы MSIL, поступивший на вход компилятору был переделан в другой.
Насчет чистоты решения, тут можно немного поспорить, конечно, что будет работать быстрее. Но, например, такую операцию как замена обращений к полю класса к обращениям к Property, где вставлено логгирование, быстро runtime не решить. Это будет тормозить загрузку.
Специфика, «почему я так сделал», раскрыта не будет, потому что тут зарыта идея моего продукта. Однако сам факт подмены без внедрения в чьей-либо процесс возможен и он тут показан.
Насчет чистоты решения, тут можно немного поспорить, конечно, что будет работать быстрее. Но, например, такую операцию как замена обращений к полю класса к обращениям к Property, где вставлено логгирование, быстро runtime не решить. Это будет тормозить загрузку.
Специфика, «почему я так сделал», раскрыта не будет, потому что тут зарыта идея моего продукта. Однако сам факт подмены без внедрения в чьей-либо процесс возможен и он тут показан.
0
> Но, например, такую операцию как замена обращений к полю класса к обращениям к Property, где вставлено логгирование, быстро runtime не решить. Это будет тормозить загрузку.
JIT ведь выполняется один раз, значит тормоза будут только первый раз, не думаю, что это очень критично. Да и сомневаюсь, что подобные хаки могут использоваться для production. Такие инструменты обычно используются для отладочных целей — профайлинга, логирования. А для целей отладки как раз CLR Profiling API выглядит более стандартным решением.
Но т.к. я не знаю суть вашего инновационного продукта, то и не могу достаточно аргументированно спорить :)
P.S. Кстати, если кому-то интересен подход с использованием CLR Profiling API, есть отличная статья на эту тему: msdn.microsoft.com/en-us/magazine/cc188743.aspx
JIT ведь выполняется один раз, значит тормоза будут только первый раз, не думаю, что это очень критично. Да и сомневаюсь, что подобные хаки могут использоваться для production. Такие инструменты обычно используются для отладочных целей — профайлинга, логирования. А для целей отладки как раз CLR Profiling API выглядит более стандартным решением.
Но т.к. я не знаю суть вашего инновационного продукта, то и не могу достаточно аргументированно спорить :)
P.S. Кстати, если кому-то интересен подход с использованием CLR Profiling API, есть отличная статья на эту тему: msdn.microsoft.com/en-us/magazine/cc188743.aspx
+1
UFO just landed and posted this here
Хм. интересно. Спасибо за статью.
Отличное дополнение к утилитам .NET реверса в учебных целях.
Отличное дополнение к утилитам .NET реверса в учебных целях.
0
Я видел толковую техническую статью на Хабре, и не ослеп!
+5
Данный метод кажется немного более сложным и нелогичным, чем внедрение своей библиотеки в адресное пространство процесса и подменой адреса исходной ф-ции на адрес ф-ции в библиотеке.
Я не большой специалист по .NET, поэтому поправьте меня, если я не прав и представленный Вами метод лучше.
Я не большой специалист по .NET, поэтому поправьте меня, если я не прав и представленный Вами метод лучше.
0
Представленный метод в некотором смысле хренов тем, что под x64 придется морочиться с подписью драйверов. Т.е. да, проверку можно отключить, можно подписать тестовым сертификатом, но муторно все это, да и распространять любительское приложение с таким довеском проблемно как раз из-за драйвера.
0
Мое решение основано на методе видоизменения кода GAC библиотек. Т.е. оно пляшет от другого.
Но все же: если мы перехватываем вызовы, и каждый раз видоизменяем код тех библиотек, то получается что мы проседаем при каждом старте. Это не всегда хорошо. Например, представим что у нас есть .Net сервер, суть которого в том и состоит что он постоянно дергает запуск новых процессов. Не то чтобы сотнями в минуту, но все равно. Относительно часто. Тогда мы получим относительно более сильное проседание. Библиотека mscorlib большая для анализа. Если мы хотим вставить логгирование на, например, обращения к внутреннему филду, то нам надо будет по всему коду всех методов библиотеки проверить, нет ли к нему обращений. Это просядет загрузку процесса настолько что проще отказаться от такого изощренного способа отладки, в угоду более стардартным средствам. Если же все вычисления вынести за пределы загрузки, сделать их заранее, то скорость загрузки не пострадает вообще никак.
Но все же: если мы перехватываем вызовы, и каждый раз видоизменяем код тех библиотек, то получается что мы проседаем при каждом старте. Это не всегда хорошо. Например, представим что у нас есть .Net сервер, суть которого в том и состоит что он постоянно дергает запуск новых процессов. Не то чтобы сотнями в минуту, но все равно. Относительно часто. Тогда мы получим относительно более сильное проседание. Библиотека mscorlib большая для анализа. Если мы хотим вставить логгирование на, например, обращения к внутреннему филду, то нам надо будет по всему коду всех методов библиотеки проверить, нет ли к нему обращений. Это просядет загрузку процесса настолько что проще отказаться от такого изощренного способа отладки, в угоду более стардартным средствам. Если же все вычисления вынести за пределы загрузки, сделать их заранее, то скорость загрузки не пострадает вообще никак.
0
Мне всегда нравятся статьи в которых есть моменты типа:
Тут все просто…
и дальше стопицот страниц кода :)
А вообще — да, статья отличная.
Тут все просто…
и дальше стопицот страниц кода :)
А вообще — да, статья отличная.
+4
Мне казалось, что все уже давно используют detours и аналоги, а выходит нет. Хотя данный способ мне нравится, поскольку использует документированные возможности системы.
Кстати никаких проблем с подписью драйверов нет — нужен лишь обычный сертификат подписи кода.
Только почему .NET? — подмена возможна любых файлов.
Кстати никаких проблем с подписью драйверов нет — нужен лишь обычный сертификат подписи кода.
Только почему .NET? — подмена возможна любых файлов.
0
Sign up to leave a comment.
Первая итерация драйвера для GAC