© Данная статья была опубликована в № 01/2007 журнала "Информатика" издательского дома "Первое сентября". Все права принадлежат автору и издателю и охраняются.      Главная страница "Первого сентября"
     Главная страница журнала "Информатика"
     Содержание № 01/2007
Эксперименты. Еще раз о программе Debug

“В МИР ИНФОРМАТИКИ” № 84

Эксперименты. Еще раз о программе Debug

Е.А. Еремин,
г. Премь

Окончание. Начало см. газету-вкладку “В мир информатики” № 82 / “Информатика” № 23/2006

4. Эксперименты с отладчиком Debug

Для более детального знакомства с методами работы отладчика Debug проведем ряд экспериментов.

4.1. Как обеспечивается возврат в Debug

Понаблюдаем, как работает в программе механизм возврата из точки останова. Из пункта 3.4 в первой части статьи следует, что в момент выполнения программы в эту точку записывается особая команда INT 3. Казалось бы, ее невозможно увидеть, поскольку вывести на экран содержимое памяти можно только после остановки программы, а тогда Debug уже успеет восстановить первоначальное значение в контрольных точках. Но все же способ “проверить теорию” есть, и он несложен: надо в процессе работы программы скопировать содержимое контрольной точки в любую свободную ячейку памяти. После завершения программы изучить “копию” не составит труда.

Перейдем непосредственно к эксперименту. Стандартным способом запустим Debug (см. [1]) и проведем с отладчиком диалог, приведенный в следующем протоколе.

07-00.gif (6504 bytes)

Жирным шрифтом показаны символы, которые нам пришлось набирать; все остальные выведены на экран отладчиком. Причем текст, оформленный курсивом, у вас может отличаться от того, который получился на компьютере автора, а многоточие заменяет выводимые на экран строки, содержимое которых для нашей задачи абсолютно несущественно.

Итак, по команде a (адрес отсутствует; при старте отладчика по традициям, восходящим к CP/M, он устанавливается равным 100h) Debug переходит в режим приема программы в мнемонике ассемблера2.

Набираемая нами программа копирует содержимое точки останова из байта с номером 106 в свободный байт 109 через регистр микропроцессора al3. В “подопытном” байте памяти 106 поместим однобайтовую инструкцию nop (нет операции), которая отличается тем, что ничего не делает. Наконец, завершается программа командой INT 20, которая, как мы уже знаем, организует по завершении программы выход в операционную систему. Заметим, что для прекращения набора нажатием вводится пустая строка (см. строку с адресом 109).

Далее по команде u (аргумент снова пропускается) для контроля выводится результат набора. Обязательно обратите внимание на то, каково будет в вашем случае содержимое ячейки 109, поскольку именно туда позднее попадет копия точки останова.

Запустим программу командой g. Появившийся на экране текст свидетельствует о том, что программа проработала и завершилась нормально. Проверим результат, набрав u100: байт с номером 109 приобрел значение 90 — не что иное, как код команды nop. Мы не увидели ничего неожиданного — произошло обычное копирование байта.

Самое интересное начинается дальше. Введем команду g=100 106, которая расшифровывается так: запустить программу с адреса 100, установив в качестве контрольной точки адрес 106. Программа выполнится, и остановка произойдет в контрольной точке 106: это видно из последней выведенной строки, где всегда отображается команда, которая будет исполняться следующей. Но зато содержимое адреса 109 теперь уже не 90, а CC, что является именно 16-ричным кодом команды INT 3.

Предлагаем читателям самостоятельно найти в распечатке содержимого всех регистров значение al, через которое копировалось содержимое контрольного байта. Проверьте также, что ситуация не изменится при выборе другой точки останова (при условии, что она устанавливается на первый байт команды).

4.2. Как зафиксировать пошаговое исполнение

Из рассмотренной в пункте 3.4 теории следует, что пошаговое исполнение отлаживаемой программы реализуется процессором аппаратно после установки в единицу флага трассировки TF. Казалось бы, достаточно проанализировать значение флага, и наличие или отсутствие пошагового исполнения будет легко распознано. К сожалению, дело обстоит не так просто, поэтому рассмотрим проблему подробнее.

Флаг TF является восьмым битом (самый младший имеет номер 0) в регистре флагов [3, 4]. Это означает, что он попадает в старший байт, причем там он оказывается самым младшим битом. Единственной подходящей для получения флагов, которые находятся в старшем байте, является команда pushf, которая сохраняет весь 16-битный флаговый регистр в так называемом “стеке”.

Примечание. Стек — это память с особой формой организации. Она имеет в работе компьютера большое значение и заслуживает самостоятельного обсуждения, поэтому мы посвятим ей отдельный выпуск. Сейчас достаточно знать, что по команде push данные заносятся в стек, а по инструкции pop — извлекается оттуда. Важно подчеркнуть, что push и pop вполне могут использовать различные регистры, т.е. можно прочитать данные из одного регистра, а записать в другой.

Итак, регистр флагов находится в стеке. Прочитаем его содержимое с помощью команды pop ax. Удобно, хотя и не обязательно, “сбросить” все биты старшей “половинки” регистра командой and ah, 14. Остается просто сохранить ah в памяти аналогично тому, как мы делали это в предыдущем эксперименте.

Предложенная простая и понятная схема анализа для флага TF, увы, оказалась неработоспособной: она всегда давала нулевое значение данного флага. Процессор почему-то тщательно “скрывал” от программы истинное значение TF.

Честно говоря, автор уже хотел было отступиться от дальнейших экспериментов, мысленно поругивая внутренние аппаратные особенности отладки у процессоров Intel. Но не давало покоя одно обстоятельство: задача определения факта работы под отладчиком является любимой хакерской проблемой! Не может быть, что не найдено ее простого решения. И вот, после продолжительных и старательных поисков в Интернете, удалось узнать, что всем процессорам Intel, помимо стремления скрывать состояние бита TF, свойственна еще одна странная особенность: этот бит, оказывается, некорректно обрабатывается при изменении некоторых регистров, в частности, регистра ss. В результате этой странности отладчик “проскакивает” ту команду, которая следует сразу после инструкции, меняющей значение в ss, и этот фокус позволяет узнать столь интересующее нас состояние флага.

Дальше все уже просто. Ставим перед pushf две “компенсирующие” друг друга инструкции — push ss и pop ss, назначение которых имеет целью исключительно “сбить с толку” могучий интеллект процессора, и — о чудо! — все начинает великолепно работать!!!

Теперь, когда проблему удалось решить, можно пригласить к проведению эксперимента читателей. Протокол диалога с Debug приводится ниже; при его оформлении приняты те же соглашения, что и в пункте 4.1.

07-01.gif (9176 bytes)

07-02.gif (3172 bytes)

07-03.gif (1134 bytes)

Запускаем отладчик, набираем программу и проверяем ее так же, как в предыдущем эксперименте. Далее производим трассировку первых шести инструкций программы. Поскольку согласно “хакерским теориям” одну из команд отладчик не заметит, набираем t 5, т.е. на одну инструкцию меньше. В ответ Debug выдает данные о состоянии процессора после каждой трассируемой инструкции. Особый интерес для нас представляет последняя строка, отображающая команду, которая будет выполняться следующей. Внимательно присмотревшись к выдаче, можно заметить, что Debug действительно “не заметил” команду pushf — ее нет в распечатке; и хотя на экране только 5 блоков информации, судя по адресам, выполнено 6 инструкций.

А каков результат? Просмотр содержимого памяти по директиве u 100 показывает, что он наконец-то равен единице (в протоколе для удобства нахождения значение подчеркнуто), т.е. нашей программе удалось зафиксировать установку отладочного флага TF.

Остается проверить случай, когда пошаговый режим отсутствует. Командой g=100 выполним программу с начала целиком. Видно, что теперь в памяти был сохранен нулевой байт, т.е., как и следует из теоретических представлений, TF = 0.

Читателям предлагается самостоятельно проверить роль регистра ss в успешности программы. Для этого в первых двух инструкциях замените ss на другой регистр, например ax, и повторите эксперимент.

Таким образом, проведенные эксперименты показали, что механизмы взаимодействия Debug c отлаживаемой программой в среде Windows остаются такими же, как описано в классических книгах.

Литература

1. Заславская О.Ю. Работа с регистрами центрального процессора. / “Информатика” № 1/2005.

2. Уэйт М., Ангермейер Дж. Операционная система CP/M. М.: Радио и связь, 1986.

3. Гук М. Процессоры Intel: от 8086 до Pentium II. СПб.: Питер, 1997.

4. Абель П. Язык Ассемблера для IBM PC и программирования. М.: Высшая школа, 1992.


2 О термине “мнемоника” — см. первую часть статьи. — Ред.

3 Каким бы мощным не казался современный процессор Intel, он не в состоянии переписать данные из одного байта памяти в другой за одну операцию!

4 При выполнении логической операции AND (И) с константой те биты, которые в ней равны 0, стираются (“сбрасываются”) — вспомните таблицу истинности. (О логических операциях с числами см. газету-вкладку “В мир информатики” № 42 / “Информатика” № 43/2004 и № 65 / “Информатика” № 24/2005. — Прим. ред.)