Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Лекции / GL03

.DOC
Скачиваний:
14
Добавлен:
20.05.2014
Размер:
104.45 Кб
Скачать

16

3. Отладчик Debug

3.1. Пример работы с отладчиком.

Мы уже посмотрели примеры работы с отладчиком в предыдущей главе. Теперь на очень простом примере продемонстрируем типичный сеанс работы с отладчиком.

Предположим, мы хотим изучить работу команды сложения двух целых чисел. Первое слагаемое хранится в слове со смещением 200h (в текущем сегменте данных) — оно будет загружено в регистр AX, второе слагаемое — число 2 — будет загружено в регистр BX, сумму требуется разместить в AX. Напишем с этой целью простую программу, содержащую всего три команды.

mov ax,[200h]

; Поместить в аккумулятор содержимое

 

; слова по адресу DS:200

mov bx,2

; Поместить в регистр BX число 2 ( BX = 2)

add ax,bx

; Сложить содержимое AX и BX

 

; и поместить результат в AX ( AX += BX)

Комментарии отделены от текста программы точкой с запятой. (Рекомендуется каждую фразу комментария начинать с прописной буквы. Если комментарий продолжается на следующей строке, то продолжение фразы начинается со строчной буквы.) В скобках последние две команды записаны с использованием нотации языка Си — так проще запомнить, что первый операнд является приемником, а второй — источником. Следует понимать, что на самом деле эти три команды являются только фрагментом программы на языке ассемблера. Если мы собираемся использовать транслятор (например MASM или TASM), то к этим командам нужно добавить еще ряд директив. Однако для отладчика этих команд достаточно. Еще заметим, что вместо последних двух команд можно было написать одну: add ax,2.

Вызовем отладчик. Для этого наберем в командной строке имя утилиты debug

D:\USER\>debug

нажмем Enter и получим приглашение к диалогу — дефис.

1) Ассемблирование текста программы. В отладчике имеется мини-ассемблер. Он переводит мнемонику команд в их машинные коды. Практически все команды отладчика вызываются набором одной буквы, далее, возможно, указываются параметры команды. Ассемблирование начинается по команде A (Assemble). Команды можно набирать и большими и малыми буквами. Команда A имеет параметр: адрес в памяти, начиная с которого будут размещены машинные коды. (Здесь необходимо уточнение: полный адрес представлен в виде segment:offsetсегмент:смещение. Как правило, мы будем указывать только смещение). Разместим коды команд, начиная со смещения 100h (этот выбор не случаен: ниже смещения 100h что-либо записывать не рекомендуется, пока вы не изучите структуру области от начала сегмента до смещения 100h — она носит название префикс программного сегмента (Program Segment Prefix — PSP)).

-a100

22B6:0100 mov ax,[200] (суффикс h не указываем!)

22B6:0103 mov bx,2

22B6:0106 add ax,bx

22B6:0108 nop

22B6:0109

В ответ на ввод команды a100 отладчик выдает адрес в формате сегмент:смещение (на вашей машине сегментная часть адреса может быть другой — в дальнейшем это не оговаривается) и ожидает ввод команды. Набираем команду и нажимаем клавишу Enter. Мнемоника команды немедленно переводится мини-ассемблером в машинный код (чуть ниже мы увидим, как его посмотреть) и отладчик выводит следующий свободный адрес — 103. Следовательно, код команды mov ax,[200] занимает три байта (с адресами 100, 101, 102). Мы вводим следующую команду и т.д. Завершаем программу командой nop (нет операции). Удобство ее включения в текст программы станет ясным из дальнейшего. В ответ на адрес 109 нажимаем Enter и получаем приглашение отладчика. Обратите внимание, что все числа в debug интерпретируются как 16-ричные, поэтому суффикс h не указывается.

Если какая-либо команда будет введена неправильно, отладчик сообщит об этом и выдаст тот же адрес (естественно — он же не смог транслировать команду):

-a100

22B6:0100 mov ax[200] (пропущена запятая)

^ Error

22B6:0100

Код нашей программы занимает байты с 100-го по 108-й, т.е. девять байтов.

2) Дисассемблирование кода (восстановление мнемоники команд по ма­шин­но­му коду).

-u100L9

22B6:0100 A10002 MOV AX,[0200]

22B6:0103 BB0200 MOV BX,0002

22B6:0106 01D8 ADD AX,BX

22B6:0108 90 NOP

Команда расшифровывается так: дисассемблировать (U — Unassemble), начиная с 100-го смещения код длиной (L — Length) 9 байтов. (Можно было набрать команду в иной форме: U100 108, т.е. указать начальный и конечный адреса кода).

Полученные в результате строки имеют формат:

сегмент:смещение машинный код мнемоника

В кодах команд можно увидеть коды операндов в "перевернутом виде": Расшифруем код A10002. A1 — код пересылки содержимого ячейки памяти в аккумулятор, 00 — младший байт и 02 — старший байт операнда-источника. Аналогично расшифровывается код второй команды. Можно убедиться в том, что машинные коды имеют разную длину: в нашем примере — 1, 2 и 3 байта.

3) Заполнение памяти. Перед выполнением программы нужно занести в слово памяти, расположенное по адресу DS:200, число 1. Можно было добавить это действие в нашу программу в качестве первой команды (чуть позже мы узнаем, как это сделать), но мы воспользуемся средствами отладчика. Команда E (Enter — ввод) позволяет занести в память новое содержимое (здесь то, что выводит debug, подчеркнуто):

-e200

22B6:0200 4D.01 E2.00

 (нажимаем пробельную клавишу)

Итак, мы вводим команду E и адрес. Отладчик сообщает, что в байте по адресу 200 записано число 4Dh, точка — приглашение к вводу. Набираем 01 (можно и без ведущего нуля) и нажимаем пробельную клавишу, чтобы получить содержимое следующего байта (если бы старое значение байта нас устраивало, мы сразу нажали бы пробел). По адресу 201 записано число 0E2h — заносим нуль. Завершаем команду E, нажимая Enter.

Команду E можно было ввести и в другой форме, указывая в одной строке адрес и список вводимых байтов (т.е. на этот раз мы не видим прежнее содержимое ячеек памяти):

-e200 1 0

Проверим результат занесения уже знакомой командой

-d200L2

22B6:0200 01 00

Записано слово 0001 (младший байт 01, старший байт 00).

Для ввода слова проще воспользоваться уже знакомой командой A:

-a200

22B6:0200 dw 1 (Define Word — задать слово)

Здесь есть одна тонкость. Команда E offset заносит данные по адресу DS:offset, а команда A offset — по адресу CS:offset. Но сейчас содержимое сегментных регистров CS и DS совпадает.

4) Содержимое регистров. Перед тем как выполнять программу посмотрим содержимое регистров центрального процессора. Введем команду R (Register):

-r

AX=0000 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000 DS=22B6 ES=22B6 SS=22B6 CS=22B6 IP=0100 NV UP EI PL NZ NA PO NC 22B6:0100 A10002 MOV AX,[0200] DS:0200=0001

Отладчик вывел три строки. В первой строке перечислены регистры общего назначения и показано их содержимое. Начальное содержимое регистров — нулевое. Только указатель стека SP содержит FFEEh, т.е. показывает на адрес, близкий к концу сегмента. Во второй строке присутствуют сегментные регистры DS, ES, SS, CS — в них хранится одинаковый сегментный адрес; счетчик команд IP содержит смещение 100h; далее перечислены текущие значения флажков в регистре флагов. Отдельные биты этого регистра устанавливаются (1) или сбрасываются (0) по результату выполнения команд. Но отладчик показывает не содержимое регистра флагов в шестнадцатеричном представлении, а значения битов в закодированной форме в виде двухбуквенных сокращений. Сведем их в таблицу 3.1.

Таблица 3.1. Кодирование значений флагов в debug.

Флаг

Наименование

Установлен (‘1’)

Сброшен (‘0’)

OF

переполнение

OV (OVerflow)

NV (Non oVerflow)

Есть знаковое переполнение

Нет знакового переполнения

DF

направление

DN (DowN)

UP

автоуменьшение указателя при выполнении строковых команд

автоувеличение указателя при выполнении строковых команд

IF

прерывание

EI (Enable Interrupt)

DI (Disable Interrupt)

внешние прерывания разрешены

внешние прерывания запрещены

SF

знак

NG (NeGativ)

PL (PLus)

результат отрицательный

результат неотрицательный

ZF

ноль

ZR (ZeRo)

NZ (Non Zero)

результат нулевой

результат ненулевой

AF

вспомогательный

AC (Adjust Carry)

NA (Non Adjust carry)

перенос

произошел перенос из младшей тетрады

переноса из младшей тетрады не было

PF

четность

PE (Parity Even)

PO (Parity Odd)

сумма битов младшего байта четная

сумма битов — нечетная

CF

перенос

CY (Carry Yes)

NC (Non Carry)

установлен флаг переноса

флаг переноса сброшен

В третьей строке представлены адрес, код и мнемоника команды, которая будет выполняться первой при запуске на выполнение. Ее сегментный адрес хранится в CS, а смещение в IP (именно этой информацией воспользовался отладчик, чтобы отобразить команду). Кроме того, в третьей строке показано содержимое ячейки памяти, на которую ссылается один из операндов команды.

5) Выполнение программы "по шагам". В этом режиме после каждой команды выполнение будет приостанавливаться. Для этого вводим команду T (Trace — трассировка). Выполняется команда, адрес которой содержится в IP, и показывается информация, как при подаче команды R.

-t

AX=0001 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000 DS=22B6 ES=22B6 SS=22B6 CS=22B6 IP=0103 NV UP EI PL NZ NA PO NC 22B6:0103 BB0200 MOV BX,0002

Обратите внимание, что изменилось содержимое только двух регистров: AX — в него загружено значение 1, и IP — в нем адрес следующей выполняемой команды. Сама команда показана в третьей строке. Флаги не изменились.

-t

AX=0001 BX=0002 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000 DS=22B6 ES=22B6 SS=22B6 CS=22B6 IP=0106 NV UP EI PL NZ NA PO NC 22B6:0106 01D8 ADD AX,BX

-t

AX=0003 BX=0002 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000 DS=22B6 ES=22B6 SS=22B6 CS=22B6 IP=0108 NV UP EI PL NZ NA PE NC 22B6:0108 90 NOP

Только теперь (после команды add ax,bx) изменилось содержимое регистра флагов. Правда это коснулось только одного флага PF (в младшем байте результата (03h=00000011b) установлено два бита, то есть четное число битов).

6) Внесение изменений в программу: в регистр BX запишем –1. Заменим вторую команду нашей программы.

-a103

22B6:0103 mov bx,-1

22B6:0106

Проверим результат изменений (на всякий случай, хотя и так видно, что длина команды не изменилась):

-u100L9

22B6:0100 A10002 MOV AX,[0200]

22B6:0103 BBFFFF MOV BX,FFFF (дополнительный код числа ­–1)

22B6:0106 01D8 ADD AX,BX

22B6:0108 90 NOP

Вновь выполним трассировку. Но сейчас в IP содержится 108h, поэтому первая команда трассировки выглядит иначе, чем раньше: после знака равенства указываем стартовый адрес.

-t=100

AX=0001 BX=0002 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000 DS=22B6 ES=22B6 SS=22B6 CS=22B6 IP=0103 NV UP EI PL NZ NA PE NC 22B6:0103 BBFEFF MOV BX,FFFE

-t

.............

7) Изменение содержимого регистров. Если наша цель — изучение команды add, то проще изменять значения регистров средствами отладчика непосредственно перед выполнением команды add. Вновь воспользуемся командой R, но теперь укажем имя регистра:

-rax

AX 0000

:-2

^ Error

Отладчик показывает содержимое AX и запрашивает новое содержимое. Вводим число –2. Отладчик воспримет это как ошибку. Нужно сначала получить дополнительный код –2. Его можно узнать с помощью отладчика. Команда H <1-е число> <2-е число> вычисляет сумму и разность аргументов‑слов:

-h0 2

0002 FFFE (т.е. 0 + 2 = 0002 и 0 – 2 = 0FFFEh)

Теперь введем в AX дополнительный код числа –2.

-rax

AX 0000

:fffe

Изменим BX

-rbx

BX FFFE

:3

А теперь выполним трассировку только команды сложения.

-t=106

AX=0001 BX=0003 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000 DS=22B6 ES=22B6 SS=22B6 CS=22B6 IP=0108 NV UP EI PL NZ AC PO CY 22B6:0108 90 NOP

8) Выполнение всей программы:

-g=100 108

AX=0000 BX=FFFF CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000 DS=22B6 ES=22B6 SS=22B6 CS=22B6 IP=0108 NV UP EI PL ZR AC PE CY 22B6:0108 90 NOP

Итак, использована команда G (Go — прогон), далее после знака равенства указываем стартовый адрес, затем адрес точки останова (теперь мы видим пользу команды nop — именно ее адрес служит адресом точки останова). Заметим, что команда g=100 приведет скорее всего к зависанию программы (лучше не проверять это!). Действительно, после наших четырех команд в оперативной памяти хранится "мусор", который будет интерпретироваться процессором как коды команд.

9) Сохранение файла с программой на диске. Для этого выполним последовательность действий:

а) дадим файлу имя pr1.com. Введем команду N (Name — имя)

-npr1.com

(отладчик создает исполняемые файлы только типа .com — не .exe!)

б) в регистры BX и CX занесем длину программы в байтах. BX:CX — длинное беззнаковое целое: в BX — старшие разряды, в CX — младшие разряды. Типичная ошибка: забывают занести в BX нуль. В результате на диске образуется очень большой файл.

-rbx

BX FFFF

:0

-rcx

CX 0000

:9

Еще раз напомним, что диапазон адресов 100 – 108 имеет длину 9 байтов.

в) введем команду записи на диск W (Write — писать).

-w

Writing 00009 bytes

Отладчик сообщил количество записанных байтов.

10) Выход из отладчика. Вводим команду Q (Quit — мы с отладчиком "квиты")

-q

D:\USER\>_

Убедитесь, что в текущем каталоге действительно имеется файл pr1.com размера 9 байтов. Запускать его на выполнение из командной строки бессмысленно по двум причинам:

  • программа не отображает результаты работы на экране, проследить ее работу можно только средствами отладчика;

  • в программе нет команд, обеспечивающих возврат в DOS, поэтому после наших четырех команд начнет выполняться "мусор" с непредсказуемыми последствиями.

11) Отладка программы, хранящейся в файле. Мы хотим еще поработать с нашей программой, записанной в файле pr1.com. Начать работу можно двумя способами.

а) Указать имя файла в командной строке при запуске debug:

D:\USER\> debug pr1.com

-r

AX=0000 BX=0000 CX=0009 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000 DS=22B6 ES=22B6 SS=22B6 CS=22B6 IP=0100 NV UP EI PL NZ NA PO NC 22B6:0100 B80100 MOV AX,0001

В BX:CX записан размер считанной программы.

б) Загрузить нужный файл в оперативную память с помощью команд debug:

D:\USER\> debug

-n pr1.com (назовем имя)

-L (Load — загрузка содержимого файла в оперативную память)

Программа считана с диска. С ней можно работать. После внесения изменений в программу может возникнуть желание сохранить новый вариант программы на диске. Для записи на диск достаточно установить нужные BX:CX и ввести команду W, т.к. имя файла уже задано.

Теперь, после того как мы получили представление о возможностях отладчика и типовых "связках" команд, дадим систематическое изложение. Отладчики, которые мы будем изучать позже, не будут сопровождаться столь подробным изложением, т.к. для них можно получить информацию в Справке (Help) программного продукта.

3.2. Параметры команд отладчика.

Запуск отладчика возможен в двух вариантах:

1) D:\USER\> debug

2) D:\USER\> debug <имя файла>

При втором варианте запуска отладчик загружает содержимое файла в нижний доступный сегмент (при этом файл с расширением .com загружается в этом сегменте со смещением 100h).

После запуска debug на выполнение пользователь получает приглашение (-) и может вводить команды. Формат команды:

буква [параметр(ы)] Здесь буква — первая буква команды, например команда F — (Fill —заполнять). параметр(ы) — перечень параметров, которые могут быть разделены пробелами или запятыми. Разделитель обязателен между последовательностью шестнадцатеричных величин. Буквы могут быть прописными и строчными.

Перечислим типы параметров: величина — от одной до четырех шестнадцатеричных цифр (суффикс h не указывается); байт — две шестнадцатеричные цифры; адрес — можно задать тремя способами:

1) сегмент:смещение, например, 057D:0020, или 57D:20 (ведущие нули можно не указывать);

2) сегментный регистр:смещение, например, cs:109

3) смещение. Если команда работает с машинными инструкциями (команды A,G,L,T,U,W), подразумевается сегментный регистр CS, если работа идет с данными — DS.

область — диапазон адресов; его можно задать двумя способами

1) адрес  смещение, например, cs:100 110 (Неверно: cs:100 cs:110 — второй параметр не является смещением).

2) адресLвеличина, где величина — размер области в байтах, например cs:100L11 задает точно такую же область длиной 17 байтов, как и в предыдущем примере.

строка — цепочка символов, заключенная в одинарные или двойные кавычки (если кавычки встречаются внутри строки, их следует удваивать).

Примеры: 'Эта ''строка'' правильная'

"this ""string"" is okay"

список — последовательность байтов и/или строк.

пример: "Hello!",d,a,'$'

3.3. Команды отладчика

Сгруппируем команды по их функциональному назначению.

1) Работа с данными.

1.1)

D [область]

Dump — дамп

Эта команда бала разобрана ранее.

1.2)

E адрес [список]

Enter — ввод

Ввод данных по указанному адресу.

а) E адрес (список опущен)

-e200

1C36:0200 01.02 12. 1B.0

вводим пробел для перехода к следующему байту

Результат:

-d200L3

1C36:0200 02 12 00

Итак, при нажатии пробельной клавиши отладчик выводит очередной байт и запрашивает его изменение, выводя точку. Для перехода к предыдущему байту нужно ввести дефис (попробуйте!).

Упражнение. С помощью команды E можно просматривать память. Посмотрите, что будет происходить, если вы подадите команду efff0 и несколько раз нажмете пробельную клавишу. Перейдете ли Вы границу сегмента? А что произойдет, если вы наберете команду dfff0?

б) E адрес список

e200 02 12 00

Результат тот же, что и в предыдущем примере, но на этот раз замена данных ведется "вслепую".

Слова приходится заносить в память побайтно. Байты заносятся в обратном порядке. Например, для ввода по адресу (смещению) 120 слова 1234 подаем команду:

-e120 34 12

Пример ввода строки символов:

-e130 'string for example',D,A,'$'

(0Dh — код возврата каретки, 0Ah — код перевода строки. $ — терминатор (ограничитель) строки, который нужен при использовании системной функции вывода строки на экран. Эту функцию мы изучим позднее.)

1.3)

R [имя регистра]

Register — регистр

а) R (без параметра) — выводятся имена всех регистров и их содержимое. Последняя строка вывода имеет формат:

адрес код команды мнемоника команды [содержимое памяти]

1С36:0100 A10002 MOV AX,[0200] DS:0200=01E4

б) R имя регистра — debug выводит содержимое регистра, а на следующей строке помещает двоеточие — приглашение к вводу нового значения. К сожалению, можно указывать только 16-разрядные регистры.

-rbx

1107

: 1 (вводим новое содержимое регистра)

<Enter>

-rah

br ERROR (недопустимое имя регистра)

в) RF выводит содержимое регистра флагов

NV UP DI NG NZ AC PE NC -

После дефиса можно ввести новые значения флагов: - PLCY (если задано более чем один код значения для флага, появляется сообщение об ошибке df error если задан код значения, отсутствующий в списке — сообщение bf error).

1.4)

F область список

Fill—заполнение

Если размер области превосходит длину списка, то список используется повторно, пока область не будет заполнена. Остаток списка усекается.

Пример. Перед созданием программы в отладчике полезно заполнить участок памяти кодом команды nop (90h) (если это не сделать, "мусор" в памяти сбивает с толку начинающего пользователя).

-f100L100 90

Упражнение. Выполнить f400L10 1 2 3 и изучить результат.

2) Работа с кодами команд.

2.1)

A [адрес]

Assemble — ассемблировать

Пример работы этой команды мы уже видели. Если в начале сеанса работы с debug не указывать адрес, то ассемблирование начнется с адреса XXXX:0100.

Отметим, что с использованием мини-ассемблера заполнение памяти данными осуществить даже проще, чем с помощью команды E:

-a200

1C36:0200 dw ffd0,12,a1c7

1C36:0206

-

(В результате [200]=FFD0h, [202]=0012h, [204]=A1C7h.)

-a200

1C36:0200 db "String",D,A,'$'

1C36:0209

Здесь использованы директивы dw (Define Word — задать слово) и db (Define Byte — задать байт).

2.2)

U [область]

Unassemble — дисассемблировать

Команда переводит коды из области памяти в мнемонику команд.

Если перевод невозможен, то выводится ???? или DB число. Если параметр область опущен, то команда дисассемблирует 20 байт, начиная с текущего значения IP. Вновь введем U — следующие 20 байт и т.д. (аналогично команде D без параметров).

3) Выполнение программы.

3.1)

T[=стартовый адрес] [количество повторений]

Trace — трассировка

После выполнения каждой команды выводится информация, как при подаче команды R. Приведем примеры:

а) t — выполнить одну команду, адрес которой в CS:IP;

б) t5 — выполнить 5 команд, начиная с текущего адреса;

в) t=106 8 — выполнить 8 команд, начиная с адреса CS:106.

3.2)

G[=стартовый адрес] [адрес останова]

Go — прогон

а) g — выполнить программу, начиная с адреса, хранящегося в IP. Программа должна заканчиваться инструкциями завершения (эти инструкции мы изучим позднее — сейчас упоминаем о них только в справочных целях), например, INT 20h или MOV AH,4Ch /INT 21h. После выполнения INT 20h debug выводит сообщение:

Program terminated normally (Выполнение программы завершено) Теперь повторный запуск программы возможен только после выполнения команды L (см. ниже).

б) g=100 адрес команды nop

Мы рекомендуем на начальном этапе обучения завершать учебные программы, набираемые в debug, командой nop и использовать ее адрес в качестве адреса останова.

4) Работа с файлами и дисками.

4.1)

N имя файла

Name — имя

Задание имени файла. Расширение указывать обязательно. Используется перед следующими двумя командами.

Соседние файлы в папке Лекции