Вот ты все кодишь, кодишь, отлаживаешь,
отлаживаешь … Стоп! А задумывался ли ты как
работает отладчик? Нет? Тогда слушай…
Начнем с того, что все семейство отладчиков
делится на отладчики приложений и
отладчики ядра. Различие в их реализации,
как правило, заключается в том, что
отладчики приложений используют Debugging API ОС
(функции, структуры), а отладчики ядра —
непосредственно отладочные возможности
процессора (регистры, команды). Очевидно,
что отладчики приложений проще в
реализации, посему с них и начнем.

Отладчики приложений

Описывать я буду Windows Debugging API. Итак, для
того, чтобы начать отладку, поток-отладчик
должен запустить отлаживаемый процесс так,
чтобы ОС знала о том, что запускаемый
процесс будет находиться под отладкой. Для
этого надо запустить отлаживаемую
программу функцией CreateProcess с параметром
dwCreationFlag равным DEBUG_ONLY_THIS_PROCESS (или DEBUG_PROCESS,
если отладчик может отлаживать несколько
процессов одновременно). Теперь отладчик
должен войти в т.н. цикл отладки. Для этого
надо войти в цикл, вызывая функцию WaitForDebugEvent
( while(WaitForDebugEvent(…)==1) ) и после завершения
обработки какого-либо события отладки
вызывать ContinueDebugEvent. Итак, обобщенная схема
работы отладчика:

CreateProcess(«путь_к_отлаживаемой_проге»,…,DEBUG_ONLY_THIS_PROCESS,…);
while(WaitForDebugEvent(…)==TRUE)
{
обрабатываем_события(см.далее)


ContinueDebugEvent(…);
}

Да, кстати, выше я сказал ПОТОК-отладчик —
это важно (Microsoft recommendation :), чтобы цикл
отладки, описанный выше находился в
отдельном потоке отладчика. Иначе отладчик
будет виснуть во время ожидания событий
отладки. Но это важно для серьезных
отладчиков, а на пока можно этим не
заморачиваться (это тебе так, на будущее).
Теперь нам надо научиться получать события
отладки. Для этого в Win DAPI есть структура
DEBUG_EVENT (посмотри SDK, описывать не буду, т.к.
структура не из маленьких). Указатель на эту
структуру передается первым параметром
функции WaitForDebugEvent, а она в свою очередь
заполняет ее интересной для нас
информацией. Само событие отладки, которое
нужно/можно обрабатывать будет в элементе
DEBUG_EVENT dwDebugEventCode. Итак какие существуют
события отладки:

CREATE_PROCESS_DEBUG_EVENT — в отлаживаемом процессе
создается новый процесс (или отладчик начал
отладку уже активного процесса — нам не надо)
CREATE_THREAD_DEBUG_EVENT — соответственно, в
отлаживаемом процессе создается новый
поток
EXCEPTION_DEBUG_EVENT — в отлаживаемом процессе
возникло исключение (точка останова,
деление на ноль и т.д)
EXIT_PROCESS_DEBUG_EVENT — возникает при выходе из
отлаживаемого процесса
EXIT_THREAD_DEBUG_EVENT — возникает при выходе из
потока отлаживаемого процесса
LOAD_DLL_DEBUG_EVENT — возникает при загрузке DLL в
адресное пространство 
OUTPUT_DEBUG_STRING_EVENT — возникает, когда
отлаживаемая программа вызывает OutputDebugString
UNLOAD_DLL_DEBUG_EVENT — отлаживаемый процесс
выгружает DLL
RIP_INFO — (только для Win98), сообщение об
ошибочной ситуации (например закрытие
недопустимых дескрипторов, т.е. ошибки, не
настолько смертельные, как при EXCEPTION_DEBUG_EVENT)

При возникновении событий отладки
существует множество нюансов, не описанных
мной. Например CREATE_PROCESS_DEBUG_EVENT и
CREATE_THREAD_DEBUG_EVENT возникают ПЕРЕД тем как
процесс или поток начнут выполняться.
Почитай более подробно в MSDN, т.к. сегодня мы
создаем ОЧЧЧЕНЬ простой отладчик.

Ну вот, кое-что уяснили, теперь напишем что-то
похожее на отладчик:

Исходник

Вот и все. Компиляй, запускай. Я думаю ты
догадался, что в качестве параметра этому
отладчику следует передать путь к
отлаживаемой программе. Теперь ты знаешь
примитивнейшие основы работы отладчика
приложений под Win. Написанный в качестве
примера отладчик даже не столько отладчик,
сколько прога, отображающая события
отладки. Для создания настоящего отладчика
требуется большое усердие и желание. Если
тебя заинтересовала эта тема, прочти, что
написано об отладке в MSDN и вперед!

Отладчики ядра

Плавно подходим к отладчикам ядра.. Оооооо!
Это очень интересно и настолько же сложно.
Как я уже говорил, отладчики ядра
используют ресурсы процессора. Сразу скажу,
что я ограничусь описанием этих ресурсов и
описанием их использования. Примера проги
не будет 🙁 В современных процах Intel (и
совместимых с ними), начиная с 80386 нам
доступны 8 отладочных регистров (DR0-DR7),
позволяющие ставить контрольные точки на
чтение/запись областей памяти и портов
ввода/вывода (начиная с Pentium). И это круто,
скажу я тебе! Всего контрольных точек
только четыре 🙁 Ну ладно, рассмотрим
отладочные регистры DR0-DR7. Наиболее важным
из них является DR7, т.к. именно он является
управляющим регистром отладки, позволяющим
координировать работу точек останова. Да,
кстати все восемь отладочных регистров 32-битные.
Рассмотрим подробнее регистр DR7:

биты 31-30 : поле LEN для т/о (точки останова) 3
биты 29-28 : поле R/W для т/о 3
биты 27-36 : поле LEN для т/о 2
биты 25-24 : поле R/W для т/о 2
биты 23-22 : поле LEN для т/о 1
биты 21-20 : поле R/W для т/о 1
биты 19-18 : поле LEN для т/о 0
биты 17-16 : поле R/W для т/о 0

поле LEN задает длину т/о:
00 — 1 байт
01 — 2 байта
10 — 3 байта
11 — 4 байта

поле R/W задает условие срабатывания т/о:
00 — исполнение команды
01 — запись данных
10 — обращение к порту ввода/вывода (если 3-й
бит регистра CR4 равен 1), иначе не определено
11 — чтение/запись

биты 15-14 : 0-0 (равны нулю)
бит 13 : при его установке любое обращение к
отладочным регистрам (даже из 0 кольца)
вызывает исключение
биты 12-10 : 0-0-1
биты 9-8 : 1-1 (а здесь уже Intel recommendation 🙂
бит 7 : 1 — включить т/о 3
бит 5 : 1 — включить т/о 2
бит 3 : 1 — включить т/о 1
бит 1 : 1 — включить т/о 0
биты 8,6,4,2,0 : то же, что и 7,5,3,1, но сбрасывается
при каждом переключении задачи (актуально
для защищенного режима, в реальном режиме
эти пары регистров равноценны)

Регистры DR0-DR3 задают линейные адреса
четырех точек останова (0-3 соответственно).
Тут есть некоторые пометки: если размер т/о —
слово, то адрес толжен быть четным,
если двойное слово, то адрес должен быть
кратный четырем.
И наконец регистр DR6 отображает состояние
отладки:

биты 31-16 : все выставить в единицу
бит 15 : если 1, то исключение вызвано
переключением задач (опять же, актуально
для защищенного режима)
бит 14 : устанавливается при трассировке
бит 13 : устанавливается, если следующая
команда обращается к любому отладочному
регистру (бит 13 регистра DR7 должен быть
установлен)
бит 12 : 0
биты 11-4 : единицы
бит 3 : сработала т/о 3 
бит 2 : сработала т/о 2
бит 1 : сработала т/о 1
бит 0 : сработала т/о 0

Ox-x-x, это тебе не Windows Debugging API 🙂 

Пример:

;Этот пример в Win не работает (и в DOS окне тоже)
mov eax, 0000000011000000110000010 ; длина т/о равна 1 байт,
ставим на чтение/запись памяти
mov ebx, 90000h ;адрес точки останова (кто прочтет/начнет
писАть — сразу пискнет)
mov dr7, eax
mov dr0, ebx

Отладчики же после установки т/о ждут
генерации int 0x1 при обращении к т/о и получая
управление творят беспредел 🙂 С портами все
точно так же, но надо сначала установить в
единицу 3-й бит регистра CR4, и задать адрес
порта в один из регистров DR0-DR3.

Из древних возможностей отладки можно
сказать о контрольных точках останова и
трассировке. Контрольная точка останова
реализуется с помощью опкода 0xCC. Встретив
его, процессор вызывает int 0x3 и запоминает в
стеке регистр флагов, регистр CS, регистр IP,
запрещает преревания (флаг FI в регистре
флагов устанавливается в 0), сбрасывает флаг
трассировки. После этого отладчик опять же
может делать с программой все что угодно. И
о трассировке. Она организуется установкой
флага TF (флаг трассировки) в единицу.
Установил TF в единицу, и процессор после
каждой команды будет вазывать int 0x1,
сохраняя в стеке регистр флагов и регистр IP
и очищая флаг TF. Хитрый отладчик
перехватывал исключение и — опять беспредел
🙂 Для отладки в реальном режиме процессора
отладчик должен был перехватывать
прерывания(исключения) 0x1 и 0x3 и
обрабатывать их по своему усмотрению. В
защищенном режиме в принципе все так же (в
смысле так же надо перехватывать 0x1 и 0x3), но
здесь при возникновении исключения
предоставляется больше информации, но
программировать в защищенном режиме
достаточно сложно. А уж в нулевом кольце
защиты Windows и подавно. Чуть не забыл сказать
об т.н. эмулирующих отладчиках. Эти
отладчики эмулируют выполнение команд
процессора отлаживаемой программы (ну и
регистры проца конечно эмулируют). С точки
зрения защиты от антиотладочного кода это
большое подспорье. Но и на эмулирующие
отладчики управа всегда найдется.

К чему пришли?..

Ну вот, теперь ты знаешь, пусть примерно,
как работают отладчики. Да, кстати, SoftIce —
отладчик ядра, а так же единственный из них,
способный отлаживать используя один комп.
«Что значит один комп?» — спросишь ты. А
это значит что остальные известные
отладчики ядра ведут отладку «по модему»,
т.е. отлаживаемая система вместе с ядром
отладчика на одном компе, а управление
ядром происходит по сети, другим компом. Вот
так-то. А как правило встроенные в средства
разработки отладчики — не что иное как
отладчики приложений, причем не самые
лучшие. Что же использовать, спосишь ты меня.
Да ты и сам знаешь — SoftIce 🙂 А еще неплохой
отладчик — WinDBG. Он идет вместе с PlatformSDK. Удачи
в ловле жуков!

Оставить мнение

Check Also

Вычислительная мощь. Какие чипы AMD и Nvidia лучше для расчетов на видеокарте

Этим летом мы запустили серию статей о вычислениях на видеокартах. Подробно разобрали, как…