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

Assembler / P09

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

1

9. Косвенная адресация. Команда организации цикла.

9.1. Индексная адресация До сих пор мы писали программы, в которых обращались к отдельным

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

Массив на уровне реализации — это множество ячеек одинакового размера, занимающих непрерывную область памяти. Адресом массива называется адрес первого элемента массива. На рис. 9.1. схематически показан массив из четырех слов. Адрес массива ds:408

ds:0408

 

ds:040A

 

ds:040C

 

ds:040E

 

 

 

 

 

 

 

 

 

 

 

Рис. 9.1.

 

 

 

Посмотрим, как в процессоре реализовано обращение к элементам массива. Допустим, мы рассматриваем байтовый массив по адресу (смещению) 300.

Первый элемент (с номером нуль) расположен по адресу 300, второй элемент — по адресу 301 и т.д. С теми возможностями, которыми мы сейчас располагаем, мы можем записать в аккумулятор, допустим, четвертый элемент командой mov al,[303] Но, оказывается, то же самое можно сделать иначе:

mov si,3

mov al,[si+300]

В индексный регистр SI мы поместили номер элемента, а затем выбрали его по адресу 300+SI. Можно было записать эту команду иначе: mov al,300[si] с тем же эффектом. Вместо SI здесь можно было использовать DI и BX (и даже BP — для com-программ, которые мы создаем в отладчике — для них совпадают значения сегментных регистров, а вообще-то BP предназначено для стекового сегмента).

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

Пример. В массиве из 16 байт, расположенном с 200-го адреса, сосчитать количество элементов, величина которых больше значения –2.

mov cx,10h; Количество элементов — в CX

mov bx,0 ; Счетчик подходящих элементов — в BX mov si,0 ; SI — индекс массива

m:cmp byte ptr [si+200],-2 ; Очередной элемент больше –2 ? jng n ; Нет — на n

inc bx ; Да — увеличить счетчик

n:inc si ; Перейти к следующему элементу

dec cx ; Уменьшить счетчик повторений цикла

jnz m ; Если счетчик повторений отличен от нуля — на m

nop

; В BX — количество подходящих элементов

Использованный метод адресации носит название — индексный.

9.2. Команды организации циклов В только что разобранной программе легко выделить конструкцию для ор-

ганизации цикла с фиксированным числом повторений:

2

для k = 1 до N шаг 1 выполнить <тело цикла>

Ее эквивалент на языке Ассемблера mov cx,N

next:

<тело цикла> dec cx

jnz next

Последние две команды можно заменить одной командой loop next.

Управление циклом

loop opr

CX ← CX–1.

 

 

Если CX 0, то IP ←IP + Data8

LOOP — петля

 

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

Обратите внимание, что счетчик повторений — обязательно регистр CX. Команда loop проверяет именно его содержимое.

Если цикл включен в программу, и количество повторений тела цикла предварительно вычисляется, то имеет смысл проверить, что CX отличен от нуля.

Задача. Пусть в программе, приведенной выше команда mov cx,10h заменена на mov cx,0. Сколько раз будет выполнено тело цикла?

Ответ: 65536.

Проверку на равенство содержимого CX нулю легко осуществить последовательностью двух команд:

cmp cx,0 je error

но можно для этой же цели воспользоваться одной командой jcxz.

Перейти по CX = 0

jcxz opr

если CX = 0 , то IP IP + Data8

Jump if CX equal Zero

 

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

Команду ставят перед началом цикла. Метка opr, разумеется, находится вне цикла. Эту команду причисляют к командам условного перехода, хотя она заведомо выпадает из их ряда: jcxz проверяет содержимое регистра CX, а прочие команды — состояние флагов.

Имеются еще две команды организации циклов: LOOPZ/LOOPE opr и LOOPNZ/LOOPNE opr (через дробную черту указаны альтернативные мнемоники). В них непосредственно перед уменьшением CX проводится проверка флага ZF и в зависимости от его значения выполнение цикла продолжается до исчерпания CX или досрочно прекращается. Но эти команды редко используются. Поэтому их изложение опустим.

9.3. Косвенная адресация Настало время перечислить все методы адресации. Нами уже изучены мето-

ды:

регистровый: inc bx

непосредственный: mov ax,6

прямой: dec word ptr [200].

При использовании косвенной адресации используется один или два регистра, в которых хранятся некоторые слагаемые адреса. Дадим сразу общую схему:

3

BX

 

 

 

 

[EA] BP

 

 

 

 

 

 

SI

 

Data8

 

 

 

 

DI

Data16

 

 

 

 

 

 

 

 

 

 

Здесь EA — Effective Address — эффективный (исполнительный) адрес. Фигурные скобки означают, что вычисляется один из перечисленных внутри них элементов (в том числе и никакой: на это указывает пробел). Возможны любые комбинации перечисленных элементов, за исключением двух: 1) [] , т.е. недопустимы квадратные скобки без содержимого, 2) [BP]. Если все-таки нужно использовать [BP], то для этого следует использовать адресацию [BP+D8], где D8 = 0 (в программе на языке Ассемблера можно записать обращение к операнду, как [BP], но Ассемблер автоматически переведет его в форму [BP+0]). Причину, почему использование [BP] невозможно, мы узнаем позже, когда изучим кодирование команд.

Итак, смысл приведенной выше формулы ясен: процессор складывает содержимое базового регистра, индексного регистра и смещение (displacement). Полученная величина является адресом, а точнее, смещением (offset).

В каком же сегменте вычисляется это смещение? Здесь действует правило: если в выражении встречается BP, то полный адрес вычисляется с использованием содержимого сегмента стека SS, во всех остальных случаях используется сегмент данных DS.

Отдельные комбинации регистров, входящих в EA, имеют свои названия. В литературе по поводу названий методов адресации нет единого мнения. Чаще употребляются следующие наименования.

1)[BX], [SI], [DI] (не [BP]!) — регистровая косвенная адресация.

2)[BX+20], 20[BX], [BP+8] — базовая адресация.

3)[SI–4], –4[SI], [DI+6] — индексная адресация.

4)[BX+SI+2], [BX][SI+2], 2[BX][SI] — базовая индексная адресация со смещением. Все три выражения эквивалентны. Предпочтительнее ис-

пользовать первое из них.

Остается неясным, чем базовый метод адресации отличается от индексного. В 16-разрядном режиме — ничем! Эти названия, по-видимому, были даны "на вырост", с учетом перспективы. Когда мы приступим к изучению 32-разрядного режима, то увидим, чем отличаются эти два режима.

9.4. Пример программы обработки массива (задание A4)

9.4.1. Формулировка задания

Дан массив A из 16 байтов. Скопировать его в массив B, заменяя элементы, равные 2, на нуль. Поместить в массив C адреса (смещения) измененных элементов из массива A. Сосчитать количество элементов, которые не подверглись изменению.

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

9.4.2. Программа

Разместим массив A, начиная с адреса 200, массив B — с адреса 210 (адреса 16-ричные!), массив C — с адреса 220.

Приведем текст программы.

mov si,200 ; Адрес массива A — в SI

4

mov di,210 ;

Адрес массива B — в DI

mov bx,220 ;

Адрес массива C — в BX

xor

dx,dx

;

Счетчик неизменяемых элементов — в DX

mov

cx,10

;

Количество элементов (10h = 16)

;в обрабатываемом массиве — в CX

n:mov al,[si]; Поместим очередной элемент массива A в AL

cmp al,2

;

Сравним его с 2

jne

m

;

Если элемент равен 2,

mov

byte ptr

[di],0 ; то записать в массив B нуль,

mov [bx],si ; поместить в C смещение измененного элемента,

inc bx

;

и переместить в массиве C указатель

inc

bx

;

на следующий элемент,

jmp

c

 

 

m:mov [di],al ; иначе — записать в B элемент из A,

inc dx

; увеличить счетчик неизменяемых элементов,

c: inc si

; переместить указатели в массиве A

inc di

;

и в массиве B

loop n

;

Конец цикла

nop

 

 

Тестовые данные выберем следующие: до выполнения программы:

массив A: 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1

массив B: 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0

массив C: 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0

после выполнения программы:

массив A: 2, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 (без изменений) массив B: 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1

массив C: 200, 202, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 регистр DX = 14 = 0Eh.

Текст программы и данные составляют содержание письменного отчета.

9.4.3. Выполнение задания в отладчике Turbo Debugger.

1) Запуск Turbo Debugger. Ввод программы.

C:\prog\>td.exe

На экране появится окно CPU. Сейчас окно CPU занимает часть экрана. Нажмем F5 (Zoom), и окно будет "распахнуто" до размеров экрана. Перейдем к выполнению задания. Находясь в панели кода, начнем ввод ко-

манд программы. Нажмем букву m (первая буква команды mov si,200). Появляется окно "Enter instruction to assemble" (введите команду, которую нужно ассемблировать). Завершаем ввод команды и нажимаем Enter. Встроенный ми- ни-ассемблер немедленно преобразует ее в машинный код. Для ввода следующей команды вызываем команду ассемблирования из локального меню нажатием Ctrl+A (или Alt+F10→Assemble...). В появляющемся диалоговом окне мы видим ранее введенную команду mov si,0200. Нажимем клавишу ↓ (ранее введенная команда становится выделенной). Нажимаем клавишу End (выделение снимается). Редактируем команду, чтобы получить mov di,0210. Аналогично набираем остальные команды.

5

cs:0100►BE0002

mov

si,0200

cs:0103 BF1002

mov

di,0210

cs:0106 BB2002

mov

bx,0220

cs:0109 33D2

xor

dx,dx

cs:010B B91000

mov

cx,0010

cs:010E 8A04

mov

al,[si]

cs:0110 3C02

cmp

al,02

cs:0112 7509

jne

011D

cs:0114 C60500

mov

byte ptr [di],00

cs:0117 8937

mov

[bx],si

cs:0119 43

inc

bx

cs:011A 43

inc

bx

cs:011B EB03

jmp

0120

cs:011D 8805

mov

[di],al

cs:011F 42

inc

dx

cs:0120 46

inc

si

cs:0121 47

inc

di

cs:0122 E2EA

loop

010E

cs:0124 90

nop

 

Команды перехода (адреса 010D и 011B) приходится набирать дважды: сначала как jne 100 и jmp 100 (так как нам неизвестно числовое значение меток m и c), и только после ввода всего текста, когда мы выясним, что m = 011D, а с = 0120, мы можем исправить эти команды, поставив там нужные числовые значения меток, т.е. адреса перехода.

2) Сохраним код программы. Для этого, нажимая Shift+Tab, перейдем в панель данных. Вызовем локальное меню: Alt+F10. Выбираем команду Block. Появляется новое меню (на это указывает стрелка, замыкающая название команды Block). Меню показано на рис. 9.2.

Clear...

Move...

Set...

Read...

Write...

Рис. 9.2.

Выбираем команду Write.... Появляется окно с запросом имени файла: "Enter write file name". В поле ввода File Name введем имя файла a4v0.com. Имя,

разумеется, может быть любым, а вот расширение — только .com, не .exe. Нажимаем Enter.

Появляется новое окно с запросом адреса блока памяти и количества байтов в блоке: Enter memory address, count. Наша программа расположена, начиная с адреса CS:0100. В панели данных подразумевается, что сегментная часть адреса блока находится в регистре DS. Но в панели регистров мы видим, что содержимое сегментных регистров CS и DS совпадает. Поэтому адрес блока можно задать как 100 — только смещение (offset), без указания сегментной части адреса. Количество записываемых байтов: 0124h – 00FFh = 25h. Итак, в поле ввода указываем через запятую два числа: 100, 25, и нажимаем Enter.

6

Заметим, что если бы размер программы составил бы 2Dh, то ввод 100, 2d был бы ошибкой: в файл было бы записано всего два байта, т.к. d — суффикс десятичного числа. В этом случае обязательно надо указывать суффикс шестнадцатеричного числа: 100, 2dh.

3) Введем исходные данные. Сейчас активна панель данных.

Перейдем на адрес 200: Alt+F10→Goto. Появится окно с запросом адреса, на который надо перейти: Enter address to position to. Набираем 200 и нажимаем Enter. В панели данных отображается содержимое байтов, начиная с адреса

DS:0200.

Сначала обнулим массивы A,B,C: Alt+F10→Block→Clear. Появляется уже знакомый запрос: Enter memory address, count. Вводим 200,40 и нажимаем Enter.

Теперь введем единицы в массив A: Alt+F10→Block→Set. В ответ на запрос

Enter address, count, byte value вводим 200,10,1 и нажимаем Enter. Убеждаемся,

что 16 байтов, начиная с адреса 200, содержат единицы.

Подправим первые три элемента массива A: Alt+F10→Change. В ответ на запрос Enter new data bytes вводим 2,1,2 и нажимаем Enter.

Исходные данные подготовлены. Может быть, имеет смысл сохранить файл a4v0.com еще раз, разместив в нем исходные данные. Для этого надо повторить действия предыдущего пункта, только размер программы будет иной: 240h – 0FFh = 141h. При этом в файле сохранится и "мусор" из диапазона адресов 125h

–1FFh.

4) Разместим на экране окна для отображения содержимого массивов A и B. Массив C будем отображать в панели данных окна CPU.

Массив A. F10→View→Dump. На экране появляется окно с содержимым, аналогичным панели данных. Alt+F10→Goto. Вводим 200.

Переместим окно в левый нижний угол: Ctrl+F5 (окно приобретает тонкую зеленую рамку) и клавишами перемещения курсора перемещаем его. Затем нажимаем Shift и клавиши перемещения курсора — окно меняет размеры. Добиваемся, чтобы в окне отображалось ровно 16 байтов — это две строки. Нажатием Enter положение и размеры окна закрепляются, зеленая рамка заменяется обычной.

Операцию изменения положения и размеров окна проще осуществлять мышью. Для изменения положения окна ухватите его мышью за верхнюю рамку и перемещайте. Для изменения размера окна ухватите его мышью за правый нижний угол рамки.

Массив B. F10→View→Another→Dump. Появляется окно. Переходим к адресу ds:210: Alt+F10→Goto, вводим 210. Переместим окно в правый нижний угол и изменим его размеры, так чтобы отображалось 16 байтов.

Массив C. Перейдем в окно CPU. Это можно сделать двумя способами: либо последовательно нажимая F6 (переход из окна в окно), либо нажимая Alt+номер_окна, в нашем случае Alt+1. В панели данных перейдем на адрес 220. Массив C — это массив слов. Отобразим содержимое панели как слова: Alt+F10→Display as→Word. Изменим размер окна CPU так, чтобы на экране были размещены все три окна по следующей схеме (рис. 9.3):

код

регистры

флаги

 

 

 

данные — массив C

стек

 

 

 

данные — массив A

данные — массив B

 

 

 

7

Рис. 9.3.

5) Программу на языке Ассемблера никогда не следует сразу запускать на выполнение. Ее нужно обязательно прогонять по шагам, так как ошибки в таких программах сделать легче, чем в программах на языке высокого уровня, а последствия этих ошибок — куда тяжелее.

Мы находимся в окне CPU, в панели данных. Чтобы перейти в панель кода, нажмем Tab. В счетчике команд IP находится адрес 100, что соответствует указателю на первую команду нашей программы в панели кода. При последовательном нажатии на F7 указатель будет перемещаться по программе. Внимательно прослеживаем содержимое регистров и ячеек памяти. Если должна выполняться команда работы с памятью, то справа вверху в панели кода отображается текущее содержимое ячейки памяти. Это полезно иметь в виду, чтобы быть уверенным, что из памяти действительно выбирается нужная информация (т.е. корректно перемещаются указатели, правильно используются методы адресации и т.д.).

Если в программе замечена ошибка, исправляем ее. Для этого возможно придется вставить новые команды или удалить существующие. Чтобы заново не набирать команды, расположенные вслед за исправленными командами (если они занимают больше места в памяти), воспользуйтесь в панели данных командой перемещения данных (в том числе и кода!) в памяти: Alt+F10→Block→Move. Разберитесь самостоятельно, как работать с этой командой.

Если нужно вновь прогнать программу по шагам, сначала следует в счетчик команд занести 0100 — адрес первой команды. Для этого переместитесь в панели кода на команду с адресом 100: Alt+F10→Goto и введите 100. Будет выделена команда с адресом 100. А теперь сделаем так, чтобы IP содержал адрес этой команды: Alt+F10→New cs:ip. В строке подсказки при выборе этого пункта ме-

ню вы увидите: Set the cs:ip to the current location — установить CS:IP в текущее положение курсора. Эквивалентная команда — Ctrl+N. Теперь вновь можно нажимать F7.

Выполнить программу как единое целое можно так: выделим команду nop в конце программы. При этом в IP, разумеется, должен быть стартовый адрес программы. Нажмем клавишу F4 (Here — здесь). Программа будет выполнена.

9.5. Еще примеры.

Для усвоения приемов работы с отладчиком разберите еще два примера. Обязательно выполните их на компьютере.

Пример. В массивах A и B (каждый из 8 слов) расположены «сверхдлинные» целые числа. Выполнить над ними сложение: A += B. Адреса (смещения) тех элементов массива A, при вычислении которых произошел перенос единицы в следующее слово, записать в массив C. Вычислить их количество.

Для эффективного решения этой задачи познакомимся с командами изменения флага CF.

Перечислим команды для изменения флага CF.

Сбросить флаг переноса

clc

CF 0

CLear CF

 

CF = 0

 

 

8

 

 

 

 

Инвертировать флаг переноса

cmc

CF CF

 

CoMplement CF

 

CF = 0

 

 

 

 

Установить флаг переноса

stc

CF 1

 

SeT CF

 

CF = 1

 

Расположим массив A по адресу 200h, B по адресу 210h = 200h + 10h, C по адресу 220h = 210 +10h

Тест

До

AFFFF, 2, FFFE, FFFD, FFFС, FFFС, FFFС, FFFС

B1, 3, 2, 2, 2, 2, 2, 2 (число: 00020002000200020002000200030001)

C0, 0, 0, 0, 0, 0, 0, 0

После

A0, 6, 0, 0, FFFF, FFFE, FFFE, FFFE

B1, 3, 2, 2, 2, 2, 2, 2

C200, 204, 206, 0, 0, 0, 0, 0

mov si,200 ; Адрес массива A — в SI mov di,210 ; Адрес массива B — в DI mov bx,220 ; Адрес массива C — в BX xor dx,dx ; Счетчик элементов — в DX

mov cx,8 ; Количество элементов в массиве A — в CX clc ; Обнулить флаг CF,

;чтобы отдельно не обрабатывать младший элемент

n:mov ax,[si] ; Выполнить сложение очередных элементов adc ax,[di]

mov [si],ax

jnc m ; Если возник единичный бит переноса, inc dx ; то увеличить счетчик

mov [bx],si ; и записать адрес в массив C

inc bx ; Переместить указатель в массиве C inc bx

m:inc si ; Переместить указатель в массиве A inc si

inc di ; Переместить указатель в массиве B inc di

loop n nop

Код в отладчике

 

 

cs:0100 BE0002

mov

si,0200

cs:0103 BF1002

mov

di,0210

cs:0106 BB2002

mov

bx,0220

cs:0109 33D2

xor

dx,dx

9

cs:010B B90800

mov

cx,0008

cs:010E F8

clc

 

cs:010F 8B04

mov

ax,[si]

cs:0111 1305

adc

ax,[di]

cs:0113 8904

mov

[si],ax

cs:0115 7305

jnb

011C

cs:0117 42

inc

dx

cs:0118 8937

mov

[bx],si

cs:011A 43

inc

bx

cs:011B 43

inc

bx

cs:011C 46

inc

si

cs:011D 46

inc

si

cs:011E 47

inc

di

cs:011F 47

inc

di

cs:0120 E2ED

loop

010F

cs:0122 90

nop

 

Дамп памяти после выполнения программы ds:0200 0000 0006 0000 0000 ds:0208 FFFF FFFE FFFE FFFE ds:0210 0001 0003 0002 0002 ds:0218 0002 0002 0002 0002 ds:0220 0200 0204 0206 0000

Пример. Дан массив A из 16 байтов. Из байтов, равноотстоящих от середины массива, образовать слово. При этом байт с меньшим адресом становится младшим. Начинать с крайних элементов, продвигаясь к середине. Если слово не делится на 3 (числа знаковые), то записать его в массив слов B, а адрес (смещение) младшего байта в массив С. Сосчитать количество таких элементов.

Расположим массив A по адресу 200h, B по адресу 210h = 200h + 10h, C по адресу 220h = 210 +10h

Тест Сначала составим пары чисел.

0 и 4 → 04, 04 mod 3 = 1 → не делится; ff и fd → fffd = –3, –3 mod 3 = 0 → делится;

Вкалькуляторе TD вычисляем, что 2567h mod 3 дает результат 2 → не делится.

Вкалькуляторе TD набираем десятичное число 1452d (его сумма цифр делится на 3). А его 16-ричный эквивалент: 5AC;

Для оставшихся элементов выбираем 0 и 1 → 01 → не делится.

Итак, данные для тестирования До

A4, 0FDh, 67h, 0Ach, 1, 1, 1, 1, 0, 0, 0, 0, 5, 25, 0FFh, 0

B0, 0, 0, 0, 0, 0, 0, 0

C0, 0, 0, 0, 0, 0, 0, 0

После

A 4, 0FDh, 67h, 0Ach, 1, 1, 1, 1, 0, 0, 0, 0, 5, 25, 0FFh, 0

10

B4, 2567, 1, 1, 1, 1

C200, 202, 204, 205, 206, 207, 0, 0

количество: 6

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

mov si,200 ; Адрес первого элемента массива A - в SI mov di,20F ; Адрес первого элемента массива A - в DI mov bx,210 ; Адрес массива B в BX

xor bp,bp ; Счетчик подходящих элементов – в BP mov cx,8 ; Количество пар элементов – в CX

n:mov al,[si]; Младший элемент — в AL mov ah,[di]; Старший элемент — в AH cwd ; Знаковое делимое в DX:AX push bp

push ax

mov bp,3 ; Делитель в BP idiv bp

or dx,dx ; Если число не делится на 3, je m

pop ax ; то восстановить AX pop bp ; и счетчик элементов

mov [bx],ax; записать AX в массив B,

mov [bx+10],si ; а адрес младшего элемента в массив C inc bp ; Увеличить счетчик

inc bx ; Переместить указатель в B

inc bx

m:inc si ; Переместить указатели в массиве A dec di

loop n nop

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

mov dh,3 idiv dh or ah,ah je m

Напомним ответ: здесь возможно переполнение при делении (более подробно это обсуждалось в гл. 8).

В этой программе нам не хватило регистров для размещения промежуточных результатов, поэтому пришлось воспользоваться стеком для их временного хранения. Вы заметили недостаток программы? Мы увеличиваем стек при обработке каждого элемента, а уменьшаем его только при обработке элемента, который не делится на 3. Исправить программу можно, например, так: после команды pop bp вставить команду sub sp,4, а после метки m команду add sp,4. После первой из этих команд указатель стека будет принудительно перемещен вниз на два слова, а после второй — вернется на прежнее место.

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