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

Assembler / P05

.pdf
Скачиваний:
64
Добавлен:
02.06.2015
Размер:
623 Кб
Скачать
☆

11

mov al,[203]

; Поместить z в AL (AL = 4)

cbw

; Расширить AL со знаком до слова (AX = 4)

dec ax

; Вычислить z – 1 (AX = 3)

imul word ptr

[201] ; Вычислить y(z – 1) (DX:AX = –9 = FFFF FFF7)

add ax,1

; Вычислить младшее слово числителя (AX = FFF8)

adc dx,0

; Вычислить старшее слово числителя (DX = FFFF)

; Вычисление знаменателя

mov bx,ax

; Сохранить младшее слово числителя в BX (BX = FFF8)

mov al,[200]

; Поместить x в AL (AL = 1)

cbw

; Расширить AL со знаком до слова (AX = 1)

add ax,3

; Вычислить знаменатель (AX = 4)

xchg ax,bx

; Поместить младшее слово числителя — в AX,

 

; знаменатель — в BX (AX = FFF8, BX = 4)

idiv bx

; Выполнить деление числителя на знаменатель

 

; (AX = –2 = FFFE —частное, DX = 0 — остаток)

inc ax

; Увеличить результат на единицу (AX = –1 = FFFF)

mov [204],ax

; Результат — в v ([204] = FFFF)

nop

 

Прокомментируем четвертую команду. Использован атрибутный оператор word ptr, т.к. иначе процессор "не будет знать", с какими операндами работать: перемножать байты AL и [201] или слова AX и [201]. Завершаем программу командой nop (нет операции). Она пригодится при работе в отладчике.

5.10.3. Выполнение программы в отладчике Turbo Debugger. 1) Вызов отладчика.

C:\prog\>td

На экране окно CPU. Расширяем его во весь экран нажатием клавиши F5.

2) Ассемблирование текста программы.

Вводим текст программы (разумеется, без комментариев).

cs:0100►A00302

mov

al,[0203]

cs:0103 98

cbw

 

cs:0104 48

dec

ax

cs:0105 F72E0102

imul

word ptr [0201]

cs:0109 050100

add

ax,0001

cs:010C 83D200

adc

dx,0000

cs:010F 8BD8

mov

bx,ax

cs:0111 A00002

mov

al,[0200]

cs:0114 98

cbw

 

cs:0115 050300

add

ax,0003

cs:0118 93

xchg

bx,ax

cs:0119 F7FB

idiv

bx

cs:011B 40

inc

ax

cs:011C A30402

mov

[0204],ax

cs:011F 90

nop

 

Отметим интересный момент, который может возникнуть при работе с TD. Набрав команду nop, мы перемещаем курсор вверх, чтобы установить его на команду с адресом cs:100. Но с удивлением видим примерно следующее:

 

 

 

12

cs:00FF 00A00302

add

[bx+si+0203],ah

cs:0103

98

cbw

 

cs:0104

48

dec

ax

Куда же исчезла первая команда нашей программы? Дело в том, что в диапазоне адресов CS:0000 — CS:00FF расположена специальная область данных, носящая название «префикс программного сегмента» — Program segment prefix (PSP). В панели кода отладчик рассматривает данные как коды некоторых команд. В результате дисассемблирования код последней из таких «команд» начался с адреса CS:00FF и занял четыре байта (первый байт здесь содержит нуль, а остальные три байта, как нетрудно видеть, код команды mov al,[0203]). Нужно не пролистывать панель кода «назад», а использовать команду локального меню Alt+F10 → Goto (Ctrl+G). В диалоговом окне вводим адрес 100. Теперь код программы отображается правильно.

3) Сохранение кода программы в файле.

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

Дадим файлу имя a2v0.com. Размер файла 20h. 4) Ввод исходные данных.

Введем данные для первого тестового набора. В него входит отрицательное число –3. Вызываем встроенный калькулятор (Ctrl+F4). Вводим –3. Получаем результат: word –3 (FFFDh).

Теперь расположим исходные данные, начиная с адреса 200. Перейдем в панель данных (Shift+Tab). Перейдем к адресу 200 (Ctrl+G). Выберем команду локального меню Alt+F10 → Change (Ctrl+C). В диалоговом окне введем последовательность байтов: 1, 0fdh, 0ff, 4, 0, 0. При вводе числа FFFD нужно соблюдать осторожность. Числа хранятся в «перевернутом виде» (более значимое по более старшему адресу). Ввод fd и ff ошибочен, потому что число должно начинаться с десятичной цифры. Ввод 0fd ошибочен, потому что цифру d отладчик воспринимает как суффикс десятичного числа.

Проконтролировать правильность ввода исходных данных можно в панели кода. Перемещайте курсор на команды, содержащие прямую адресацию. Тогда в заголовке окна будет отображаться содержимое ячейки памяти, к которой обращается команда. Для команды mov al,[0203]отображается ds:0203 = 04. Для команды imul word ptr [0201] отображается ds:0201 = FFFD.

5) Трассировка программы.

Проведем трассировку программы, проверяя правильность промежуточных результатов. Для этого нажимаем функциональную клавишу F7 (Trace).

После выполнения команды умножения imul word ptr [0201] проанализируем результат. Он размещается в DX:AX, но т.к. CF=OF=0, то DX = FFFF — это расширение знака, и произведение фактически содержится только в AX = FFF7. Выясним, что это за число. Нажимаем Ctrl+F4 и вводим 0–0FFF7. Получаем результат: 9. Следовательно, FFF7 — это представление числа –9 в дополнительном коде. Итак, результат умножения — отрицательное число –9.

После add ax,0001 в панели флагов видим, что CF=0. Это означает, что при сложении младших слов не возник единичный перенос в старшее слово результата.

Когда мы доберемся до команды nop, в ячейке 204 появятся байты FF FF, т.е. результат правильный (равен –1).

13

6) Прогон программы.

Выполним прогон программы на втором тестовом наборе x = 7Dh, y = 6DB7h, z = –6h. На этот раз не будем проводить пошагового выполнения (трассировки), а выполним программу, как единое целое.

Сначала найдем дополнительный код отрицательного числа – 6. Он равен FFFA. Введем новые исходные данные: 7dh,0b7,6dh,0fa,0,0. (Из FFFA удаляем старший байт — расширение знака.)

Переместимся в панели кода на команду с адресом 100: Alt+F10/Goto (вводим 100). Будет выделена команда с адресом 100. А теперь сделаем так, чтобы IP содержал адрес этой команды: Alt+F10/New cs:ip. Поставим курсор на команду nop в конце программы. Нажмем клавишу F4 (Here — здесь). Программа будет выполнена.

В слове с адресом 204 записаны байты 01 FA, т.е. число FA01. Вызовем калькулятор (Ctrl+F4). Введем выражение 0-0fa01. Результат: word 1535 (5FFh). Итак, получен ответ –5FF.

Прогон программы на втором тестовом наборе также дал правильный результат.

7) Завершение работы с отладчиком.

Alt+X.

5.10.4. Таблица результатов промежуточных вычислений дли обоих тестов. Вызовем отладчик, указав в командной строке имя файла с программой.

C:\prog>td a2v0.com

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

Выражение

Размер

Результаты для

Результаты для

 

результата

1-го теста

2-го теста

z–1

слово

3

FFF9 = –7

y(z–1)

дв. слово

FFFF FFF7 = –9

FFFC FFFF = –30001

y(z–1)+1

дв. слово

FFFF FFF8 = –8

FFFD 0000 = –30000

x+3

слово

4

0080

(y(z–1)+1)/(x+3)

слово

FFFE = –2

FA00 = –600

(y(z–1)+1)/(x+3)+1

слово

FFFF = –1

FA01 = –5FF

Эта таблица понадобится при выполнении задания C1.

5.10.5. Обсуждение некоторых фрагментов программы Ответим на вопрос, почему команды вычисления знаменателя дроби не

приведены в такой последовательности: mov al,[200]

add al,3 cbw

т.е. сначала увеличение операнда на 3, а затем его расширение со знаком. Проследим промежуточные результаты на втором тестовом наборе данных:

Правильно

Результат

mov al,[200]

AL = 7Dh

cbw

AX = 007Dh

Неправильно

Результат

mov al,[200]

AL = 7Dh

add al,3

AL = 80h

14

add ax,3

AX = 0080h

cbw

AX = FF80h

Итак, во втором варианте произошло знаковое переполнение из-за того, что размер операнда (байт) был недостаточно велик. В то же время на первом тестовом наборе сработает и "неправильный" вариант программы. В нашем исходном варианте шире диапазон исходных значений, к которым применима программа.

Атеперь рассмотрим фрагмент вычисления числителя дроби:

;Вычислить y(z – 1)

add ax,1

;

Вычислить младшее слово числителя

adc dx,0

;

Вычислить старшее слово числителя

Что произойдет,

если заменить add ax,1 более компактной командой

inc ax? (Нетрудно убедиться, что код второй команды занимает всего один байт, а первой — три байта). На первом тестовом наборе вычисления будут правильными, но на втором наборе результат окажется неверным. В самом деле, после выполнения команды imul word ptr [201] содержимое регистров следующее: DX = FFFCh, AX = FFFFh (проверьте!). При этом CF = 0. После выполнения команды inc ax содержимое AX нулевое, а CF по-прежнему сброшен! И в результате команда adc dx,0 оставит содержимое DX неизменным. В то же время команда add ax,1 не только обнулит AX, но и установит флаг переноса: CF = 1. Тогда результат adc dx,0 будет другим: DX = FFFDh.

Предпоследняя команда программы inc ax правомерна, т.к. действие производится над словом, а не над элементом двойного слова.

Подчеркнем, что несмотря на наши усилия увеличить диапазон допустимых входных значений, программа не сможет вычислить v на втором тестовом наборе, если x = –2. При делении возникнет переполнение.

Рассмотрим другой пример. Вычислить выражение

2 (x 2)( y 1)

, где x,

z

 

 

y, z — слова. Ограничимся вычислением числителя. Проблема в том, что после перемножения x – 2 и y – 1 результат окажется в DX:AX, и непонятно, как организовать вычитание этого длинного слова из непосредственного операнда. Рассмотрим два решения: неверное и верное. Пусть x размещено по адресу 200, y —

по адресу 202. Там записаны числа: [0200] = 8002h, [0202] = 8001h.

1 вариант (неправильный). Перепишем формулу для числителя в виде 2 ( (x 2))( y 1) . Теперь нужно сложить DX:AX с непосредственным операн-

дом 0000 0002.

mov ax,[200] ; AX = 8002h mov bx,[202] ; BX = 8001h

sub ax,2

; AX = 8000h

neg ax

; AX = 8000h, OF = 1

dec bx

; BX = 8000h

imul bx

; DX:AX = 4000 0000h

add ax,2

; AX = 0002h

adc dx,0

; DX = 4000h

Результат оказался положительным числом, хотя должен быть отрицательным! Нетрудно видеть, почему. При выполнении операции neg ax произошло знаковое переполнение!

15

2 вариант (правильный). Разместим непосредственный операнд в паре реги-

стров SI:DI.

mov ax,[200] ; AX = 8002h mov bx,[202] ; BX = 8001h

sub ax,2

; AX = 8000h

dec bx

; BX = 8000h

imul bx

; DX:AX = 4000 0000h

mov si,0

 

mov di,2

 

sub di,ax

; DI = 0002h

sbb si,dx

; SI = C000h

mov ax,di

; AX = 0002h

mov dx,si

; DX = C000h

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

1 3xy

Задача. Вычислить v y 1 , где x, v — слова, y — байт. x = 1, y = –2. (То-

гда v = –7.)

Решение. Расположим x по адресу 200, y по адресу 202 (202 = 200 + 2), v по адресу 203 (203 = 202 + 1). При вычислении числителя мы должны получить двойное слово. Организуем вычисления так: сначала перемножим байт y и число 3. Получим слово. Это слово умножим на x, Получим двойное слово. Константу 1 разместим как двойное слово в паре регистров SI:DI (в SI расположим старшие разряды, а в DI — младшие разряды). В знаменателе сначала преобразуем байт y в слово, а затем увеличим его на 1. Перед выполнением операции деления числитель оказывается в паре SI:DI, а знаменатель в AX. Чтобы выполнить деление, мы размещаем числитель в паре регистров DX:AX, а знаменатель

вDI.

;Вычисление числителя

mov

al,3

;

(AL = 3)

imul

byte ptr [202]

;

3*y (AX = -6 = FFFA)

imul

word ptr [200]

;

3*x*y (DX:AX = FFFF:FFFA)

mov

si,0

;

 

mov

di,1

;

(SI:DI = 0000 0001)

sub

di,ax

;

(DI = 0007, CF = 1)

sbb

si,dx

;

1 – 3*x*y (SI = 0000)

; Вычисление знаменателя

 

 

mov

al,[202]

;

(AL = FE = –2)

cbw

 

; (AX = FFFE = –2)

inc

ax

; (AX = FFFF = –1)

xchg

ax,di

;

(AX = 0007, DI = FFFF)

mov

dx,si

;

(DX = 0000)

idiv

di

;

(AX = FFF9 = -7 — частное,

 

 

; DX = 0000 — остаток)

mov

[203],ax

 

 

nop

 

 

 

16

5.11. Стек. Команды работы со стеком В рассмотренных фрагментах промежуточные результаты сохранялись в ре-

гистрах. Это — лучшее решение, т.к. обеспечивает наивысшее быстродействие программы. Но если регистров для хранения промежуточных данных не хватает, придется записывать данные в оперативную память. Здесь уместно начать знакомство со стеком.

Стек (stack) — важное понятие теоретического программирования, частный случай линейного списка. Стек — это одномерная динамическая структура данных, добавление элемента в которую (или исключение элемента из которой) производится с одного конца, называемого вершиной стека (TOS — Top of Stack). Другими словами, стек организован в виде списка типа LIFO (Last In – First Out — вошедший последним – выходит первым). Стек называют также магазинной памятью. При этом имеют в виду аналогию с магазином автоматического оружия. Патрон, который последним оказался в магазине при его снаряжении, первым попадает в ствольную коробку при стрельбе.

Мы пока не будем рассматривать теоретические аспекты этого понятия, а сразу обратимся к программно-аппаратному стеку в 8086. Стек размещается в ОЗУ, в стековом сегменте (т.е. сегментный адрес области памяти для стека хранится в SS). Элементы стека — слова. Регистр SP (Stack Pointer — указатель стека) содержит адрес (точнее, смещение) последнего включенного в стек слова, т.е. SP указывает на вершину стека. При включении в стек новых слов вершина перемещается в направлении уменьшения адресов (говорят: стек растет вниз). Включение в стек (push) обозначают стрелкой, направленной вниз↓, извлечение из стека (pop) — стрелкой, направленной вверх ↑. Перечислим команды для работы со стеком:

Включить в стек

 

push src

1) SP ← SP – 2

 

 

 

2) (SP) ← src

PUSH

 

флаги не изменяются

push r/m16; i16 (начиная с 80286)

 

 

 

 

 

Извлечь из стека

 

pop dst

1) dst ← (SP)

 

 

 

2) SP ← SP + 2

POP

 

флаги не изменяются

push r/m16

 

 

 

 

 

Включить в стек флаги

 

pushf

flags

PUSH Flags

 

флаги не изменяются

операнд подразумевается

 

 

 

 

 

Извлечь из стека флаги

 

popf

flags

POP Flags

 

флаги из стека

операнд подразумевается

 

 

Проиллюстрируем понятие стека следующей наглядной картинкой:

младшие адреса

слова

 

 

 

начало сегмента стека

SS

 

 

 

17

включение в стек

 

 

 

 

 

 

 

 

 

 

 

 

 

 

размер

вершина стека

 

последний элемент

 

текущее SP

 

 

стека

извлечение из стека

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Исходное SP

 

 

 

старшие адреса

 

 

 

 

 

 

 

Стек является удобным средством для сохранения и восстановления регистров.

Пример. Сохраним в стеке содержимое AX, BX, CX. Пусть AX = 3, BX = 5, CX = 4, SP = FFFE.

Состояние стека после выполнения фрагмента push ax

push bx push cx

FFF6

 

 

 

FFF8

4

текущее SP

 

FFFA

5

 

 

FFFC

3

 

 

FFFE

 

 

исходное SP

Проследим, как происходило занесение в стек содержимого регистра AX. Сначала указатель стека SP перемещается на следующее слово. Это сводится к вычитанию из содержимого SP двойки: FFFE – 2 = FFFC. После этого по адресу SS:SP заносится содержимое регистра AX. Далее эта операция повторяется еще дважды, только уже с регистрами BX и CX. В результате SP = FFF8.

Восстановим содержимое регистров из стека (предположим, что содержимое регистров AX, BX, CX со времени их сохранения претерпело изменения, а содержимое SP не изменилось).

pop cx pop bx pop ax

Обратите внимание, что извлечение регистров происходит в порядке, обратном их занесению. В результате SP = FFFE. Значения 3, 5, 4 пока сохраняются в памяти, но при следующих операциях со стеком они будут скорее всего уничтожены.

Упражнение. Выполнить этот пример в Turbo Debugger. В панели регистров занести в регистры требуемые значения; в панели кода набрать последовательность из указанных шести команд; выполнить эти команды, наблюдая изменения в панели стека. Обратите внимание, что вершина стека в панели стека отображается черным треугольником.

Заметим, что действия по перемещению указателя стека (т.е. увеличение и уменьшение на два) выполняются в командах push и pop в разном порядке:

push: сначала указатель перемещается вниз на свободное слово, после чего в это слово записывается содержимое источника.

pop: содержимое слова в вершине стека помещается в приемник, после чего указатель перемещается вверх на следующее слово.

di si bp sp bx dx cx ax
флаги не изменяются

18

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

В286-м процессоре можно использовать в команде push непосредственный операнд. Итак, если в 8086 для размещения в стеке константы 5 нужно было использовать две команды: mov ax,5 и push ax, то в 286-м процессоре достаточно одной команды push 5. (Атрибутный оператор не нужен, т.к. в стеке сохраняются только слова.)

В286-м процессоре появились две новые команды для работы со стеком.

Включить в стек все HL- и

pusha

ax cx dx bx sp bp si di

PI-регистры

 

 

PUSH All

флаги не изменяются

операнд подразумевается

В стек помещается то содержимое SP, которое было в нем до выполнения команды pusha.

Извлечь из стека все HL- и popa PI-регистры

POP All

операнд подразумевается

Эти две команды полезны, когда нужно сохранить и восстановить практически все регистры общего назначения. Если же нужно сохранить и восстановить только два-три регистра, то лучше для каждого из них использовать отдельную команду push и pop.

5.12. Операции с сегментными регистрами Только в трех командах возможен операнд – сегментный регистр. Это ко-

манды mov, push, pop.

Команда mov может иметь только такие форматы: mov sr,r/m16; r/m16,sr (sr

— сегментный регистр). Поэтому недопустимы, например, команды mov es, 0B800h и mov es, ds. Чтобы реализовать эти действия, приходится использовать промежуточный регистр: mov ax,0B800h / mov es,ax и mov ax,ds / mov es, ax.

Регистр cs не может быть приемником в команде mov. (Для изменения содержимого cs надо использовать, например, команду дальнего безусловного перехода, которую мы изучим позже.)

Сегментные регистры могут быть операндами в командах push и pop. Вместо пары команд mov ax, ds / mov es, ax эффектнее использовать push ds / pop es. Но первая пара команд выполняется быстрее: ведь при выполнении второй пары команд происходит два обращения к памяти, сопровождающиеся изменени-

ем sp.

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