Издательский дом ООО "Гейм Лэнд"ЖУРНАЛ ХАКЕР #135, МАРТ 2010 г.

Дебаг и дизассемблирование приложений в .NET Framework

Александр Эккерт (stannic.man@gmail.com)


К дебагу приложений можно относиться по-разному - кто-то совершенно не любит копаться во внутренностях программы, а кто-то просто обожает. Одно известно наверняка - без дебага и отладки кода не обойтись вообще никаким образом.

Каждый кодер не одну сотню раз видел окошко с ошибкой типа "Программа выполнила недопустимую операцию и будет закрыта". Как этого избежать? Ну, разве что научиться писать программы сразу и без ошибок. Но, увы, такого в природе не встречается – придется смириться с необходимостью последующей отладки. Сегодня мы постараемся превратить процесс дебага .NET-приложений в приятное времяпрепровождение.

Немного философии

Если говорить о дебаге приложений в классическом понимании, то .NET-приложения тут стоят особняком, что, в сущности, понятно - архитектура .NET Framework коренным образом отличается от стандартных Win32-приложений.

Каждый .NET-исполняемый файл, по сути, является обыкновенным PE-файлом. Но при этом надо помнить, что компилятор при создании этого PE-файла вставляет в начало инструкцию, вызывающую runtime-систему CLR. Таким образом, в момент старта .NET-исполняемого файла управление передается CLR. Отличительной особенностью исполняемых .NET файлов является то, что эти файлы осуществляют импорт системных функций только из одной боблиотеки - mscoree.dll и в этой бибилиотеке они вызывают только одну функцию - _CoreExeMain. Другими словами, этот вызов является своеобразным пропуском в мир для файлов, написанных на .NET языках. В этом можно убедиться, просто просмотрев таблицу импорта любого .NET исполняемого файла - ничего кроме _CoreExeMain ты там не найдешь.

Если присмотреться к картинке, иллюстрирующей формат .NET-исполняемого файла, то можно увидеть, что половина всего логического содержимого .NET-приложения составляют метаданные. Если ты встречаешь это слово впервые, то самое простое и самое точное для него определение – «данные, которые описывают другие данные».

Что такое «метаданные» в контексте .NET Framework? Это набор программных элементов ЕХЕ-файла, таких как типы и реализации методов. И без знания метаданных, полноценный дебаг .NET приложений просто невозможен.

Благодаря технологии метаданных CLR узнает, какие во время выполнения потребуются типы и какие методы должны быть вызваны. Это дает среде возможность выполнить должную настройку для более эффективного выполнения приложения. Механизм запроса метаданных называется отражением (reflection). Библиотеки классов .NET Framework имеют целый набор методов отражения, позволяющих любому приложению (и не только CLR) запросить метаданные другого приложения.

Метаданные используются для различных целей - они устраняют необходимость в заголовочных и библиотечных файлах при компиляции, так как все сведения о типах-членах, на которые есть ссылки, содержатся в файле с MSIL-кодом, в котором они реализованы. Компиляторы могут читать метаданные прямо из управляемых модулей.

Visual Studio использует метаданные для того, чтобы помочь писать код разработчикам.

К примеру на основе анализа метаданных в Visual Studio.NET, построена реализация такой удобной штуки как IntelliSense. При наличии IntelliSense мы, набирая имя метода, видим на экране всплывающий список с аргументами этого метода. Visual Studio.NET дополняет это средство, показывая еще и все члены типа.

В процессе верификации кода CLR использует метаданные, чтобы убедиться, что код совершает только безопасные операции. Метаданные позволяют сериализовать поля объекта в блок памяти на удаленной машине и затем десериализовать их, восстановив объект и его состояние на удаленной машине. Метаданные позволяют сборщику мусора отслеживать жизненный цикл объектов. Сборщик мусора может определить тип любого объекта и, благодаря метаданным, знает, какие поля в объекте ссылаются на другие объекты.
Вся информация о том, как организованы метаданные, организована в таблицы с разным форматом.

Однако, все это лишь слова. Как же можно использовать метаданные в реальном дебаге (и не только управляемого кода)? Что мы можем там найти?

Существует три категории таблиц метаданных - это определения, ссылки и декларации. Вкратце рассмотрим самые важные и интересные из определений:

TypeDef - содержит по одной записи для каждого типа, определенного в модуле и указывает на записи таблиц MethodDef, PropertyDef и EventDef, содержащие соответственно сведения о методах, свойствах и событиях этого типа.

MethodDef - содержит по одной записи для каждого метода, определенного в модуле. Также имеются определения FieldDef, ParamDef, PropertyDef и EventDef, которые, как ты уже догадался, содержат записи, идентифицирующие поля, параметры, свойства и события. В стандартных таблицах ссылок нашего внимания заслуживают такие таблицы, как AssemblyRef, которая содержит по одной записи для каждой сборки, на которую ссылается модуль, ModuleRef - содержит по одной записи для каждого PE-модуля, в котором реализованы типы, на которые он ссылается.

Кроме того, в таблице ссылок имеются записи TypeRef и MemberRef. На самом деле, таблиц метаданных в гораздо больше, здесь коротко описаны лишь те, которые нас смогут заинтересовать.

Для того, чтобы просмотреть записи метаданных .NET-приложения, загрузи его в ILDASM (о нем - чуть ниже) и нажми Ctrl+M.

В качестве лирического отступления от темы, хочу сказать, что техники инфекта .NET-файлов, существующие на данный момент, неразрывно связаны с использованием метаданных. К примеру, вирус Donut использует для этого технику замещения CLR-заголовка и метаданных .NET-исполняемого файла. Так как на самом деле CLR представляет собой набор COM-интерфейсов, то для открытия метаданных .NET используются интерфейсы IMetaDataDispenser и IMetaDataDispenserEx, а для непосредственного изменения метаданных - IMetaDataEmit и IMetaDataAssemblyEmit.

Теперь о главном оружии диззасемблера .NET-программ - программулине ILDASM, диззассмблере языка Microsoft IL. Ее ты сможешь найти в утилитах, прилагаемых к Visual Studio. Эта утилита позволяет просмотреть содержимое любой сборки .NET, как exe-файла, так и dll - ее манифест, метаданные типов и инструкции IL. При этом надо сказать, что сам по себе дизассемблер ILDASM в корне не похож на дизассемблеры Win32-программ, настолько отличается его выдача - программа лежит как на ладони! Эх, все бы дизассемблеры были такими интуитивно понятными и удобными…

Кстати, если тебя заинтересует спецификация MSIL, ее ты сможешь найти здесь - http://www.ecma-international.org/, или на диске к журналу. Зачем она нужна? Знаю нескольких "немного не в себе" программистов, которые пишут именно на нем :).

За работу!

Microsoft предоставляет программисту два приличных дебаггера, которые отлично справляются со своей работай. Встречай: CorDbg - дебаггер, запускающийся из командной строки и GUI-дебаггер DbgCLR. Второй, визуальный DbgCLR ты можешь видеть в стандартном окне Visual Studio.

Дебаггер среды выполнения CorDbg - отличное средство для поиска и исправления ошибок в программах, работающих в .NET Framework.

CorDbg может легко дизассемблировать в нативный или IL-код по команде dis[assemble] , осуществить вывод переменных по их именам или устанавливать их значение вручную командой set. Просмотреть значения регистров машины при выполнении управляемого кода также можно, для этого тебе придется использовать команду reg.

Подробнее информацию о командах СorDbg можно найти в MSDN. Нельзя обойти внимание еще один отладчик командной строки (уф, сколько отладчиков развелось!) - Mdbg, также входящий в стандартный набор Visual Studio, который является весомым дополнением к CorDbg.

О визуальном отладчике DBGCLR я расскажу лишь вскользь - программисту, использующему в работе Visual Studio, этот отладчик знаком не понаслышке, потому что является неотъемлемой ее частью.

Дебаг на практике - инструкция по применению

А сейчас коротко остановимся на тех методах, которые помогут тебе сделать отладку кода более практичной и удобной.

Средства, предоставленные программисту для осуществления полноценной отладки кода, представлены в пространстве имен System.Diagnostics.Debug.

Для удобства в отладке программ, написанных на .NET-овских языках, предусмотрен метод Assert(), который сгенерирует исключение, если не выполнится определенное условие:

System.Diagnostics.Debug.Assert(условие, ...)

В своем коде при отладке ты можешь легко обеспечить трассировку стека вызовов - теперь это делается проще простого: в пространстве имен System.Diagnostics есть класс StackTrace, который можно задействовать для получения всего стека вызовов. Метод GetFrame(0) вернет первый фрейм объекта StackTrace - того, что выполняется в данный момент, а метод GetMethod() вернет ссылку на объект MethodBase, соответствующий методу заданного фрейма. Получится примерно вот так:

StackTrace stack = new StackTrace(0);
for(int i = 0; i < stack.FrameCount; i++)
{
Console.WriteLine(stack.GetFrame(i).GetMethod().Name);
}

Полный вариант кода, который отражает текущее состояние стека вызовов, ищи на диске.

Только не стоит путать класс StackTrace, пример которого вы видели, со свойством StackTrace в классе Exception. Кстати, поговорим о нем. Свойство StackTrace типа Exception поистине волшебно. Обращаясь к нему, ты на самом деле вызываешь код CLR. При генерации исключения механизм CLR регистрирует место, где была выполнена команда throw. Когда фильтр перехвата захватывает исключение, CLR регистрирует место перехвата. Теперь, если в блоке catch обратиться к свойству StackTrace сгенерированного объекта исключения, код этого свойства вызовет CLR. При этом CLR построит строку, идентифицирующие те методы, вызванные между исключением и срабатыванием фильтра, перехватившего исключение.

По умолчанию в Visual Studio для отладки включен только безопасный (managed) код. Если хочешь подключить режим отладки всего кода, лезь в меню и подключай опцию Unmanaged code debugging.

Заключение

В данной статье я попытался собрать и кратко описать те инструменты, которые помогут тебе в дизассемблировании, дебаге и отладке управляемого кода. Если содержимого статьи тебе покажется мало или стандартные средства отладки управляемого кода тебе покажутся не очень впечатляющими, начиная с .NET Framework 2.0 реализована поддержка отладки управляемого кода в стандартном отладчике WinDBG.

Удачного компилирования и да пребудет с тобой Сила!

DVD

На диске можно будет отыскать приведенный в статье код, а также дизассемблеры и отладчики под .NET Framework.

WWW

INFO

Если тебе лень каждый раз грузить программы в ILDASM - при отладке программ в окне вызовов (call stack) достаточно дважды кликнуть на метод microsoft.visual.studio.dll!DesignerHost.Add и дебаггер сам отразит декомплированный IL-код твоей программы

От редакции

Пользуясь случаем, обращаем твое внимание на старые материалы из братского журнала IT Спец:

Содержание