Logo GenDocs.ru

Поиск по сайту:  

Загрузка...

Лабораторные работы по языку Ассемблера - файл 1.doc


Лабораторные работы по языку Ассемблера
скачать (100 kb.)

Доступные файлы (1):

1.doc100kb.15.12.2011 07:09скачать

содержание
Загрузка...

1.doc

Реклама MarketGid:
Загрузка...
Министерство образования,

и культуры Кыргызской Республики

I.Ошский Государственный Университет

II.



ЛАБОРАТОРНЫЕ РАБОТЫ

ПО ЯЗЫКУ ASSEMBLER


Составитель: Аркабаев А.Н

Ош 2004 г.

III.Лабораторная работа №1

Циклы


Цель работы:

  • научить организацию циклов на Ассемблере;

  • научить использовать команды организации циклов;


ТЕОРЕТИЧЕСКАЯ ЧАСТЬ

Методы адресации

Во всех 16-битовых ЭВМ принимаются какие-либо меры для расши­рения адресного пространства памяти, т.к. 16-битовый адрес позволя­ет адресоваться только к 64 Кб. В изучаемой ПЭВМ память разбивается на сегменты размером до 64 Кб. По умолчанию каждый сегмент начина­ется с границы параграфа (фрагмента памяти размером в 16 б). Физи­ческий адрес в памяти складывается из начального адреса сегмента и 16-битового смещения (исполнительного адреса, EA) в пределах сег­мента. Для получения физического начального адреса (ФА) содержимое сегментного регистра (начальный номер параграфа) умножается на 16, т.е. дополняется справа четырьмя нулями:

ФА = ННП * 16 + смещение.

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

При выполнении большинства команд операнд может находиться:

в регистре (1 или 2 байта); непосредственно в команде (1 или 2 бай­та); в памяти.

В последнем случае исполнительный адрес EA (смещение в сегмен­те) образуется как сумма содержимого базового регистра (или указа­теля), индексного регистра и сдвига, указанных в операторе. Возмож­ны и частные случаи - отсутствие того или иного компонента. Всего различных вариантов 8 (вариант задается 3-битовым полем r/m). С учетом того, что сдвиг может иметь длину 1 или 2 байта или отсутс­твовать (3 варианта), общее количество комбинаций (режимов адреса­ции) 8 * 3 = 24.

Следует заметить, что система адресации микpопpоцессоpа реали­зует далеко не все существующие в ВТ способы адресации и имеет ряд особенностей:

- существуют ограничения на выбор регистров: для каждого спо­соба адресации подразумевается использование не всех, а только определенной группы регистров;

- есть ограничения на использование способов адресации в двухоперандных командах (недопустимы команды "память-память", "сег­ментный регистр - сегментный регистр", "сегментный регистр - непосредс­твенный операнд"). Среди 24 режимов адресации есть такие, которые, по сути, мало отличаются друг от друга, поэтому в литературе режимы адресации обычно делят на 7 групп (способов адресации):

1) регистровая; 2) непосредственная; 3) прямая; 4) косвенная регистровая; 5) адресация по базе (базовая); 6) прямая с индексиро­ванием; 7) адресация по базе с индексированием (базовая индексная). Нетрудно заметить, что такое разделение есть не что иное, как по­пытка проклассифицировать режимы адресации по общепринятым способам адресации, причем не бесспорная, т.к., например, базовая отличается от индексной только используемыми регистрами.

ПРИМЕЧАНИЕ. Так как МП реализует режимы адресации, а в опера­торах языка указываются способы адресации, то различные трансляторы допускают различные вольности в форме написания оператора (далее вам предлагается убедиться в допустимости этих форм). Для переноси­мости программ на другой транслятор рекомендуется использовать ос­новную форму записи, если в ином нет острой необходимости. Способы адресации приведены в табл.1.

Таблица 1

N

п/п

Адресация

Основной формат

оператора

Сегмент по

умолчанию

Примечание

1

Регистровая

Регистр

1 б или 2 б




Наиболее быстрое выполнение

2

Непосредственная

Данное

1 б или 2 б




Применяется в опера­циях с константами

3

Прямая

Исполнительный адрес 2б

DS

Применяется для однократного обращения

к памяти

4

Косвенная регистровая

[BX]

[ВР]

[SI]

[DI]

DS

SS

DS

DS

Применяется при работе с одномерными массивами

5

Базовая

[BX] + сдвиг

[BP] + сдвиг

DS

SS

Применяется для об­ращения к элементу структуры, нач. адрес которой в ВР или ВХ

6

Прямая с индексированием

Сдвиг[SI]

Сдвиг [DI]

DS

DS

Применяется при ра­боте c одномерными массивами, сдвиг - нач. адрес массива

7

По базе с индексированием

Сдв.[BX][SI]

Сдв.[BX][DI]

Сдв.[BP][SI]

Сдв.[BP][DI]

DS

DS

SS

SS

Применяется при работе с двумерными массивами

Примечание. Термин "сдвиг", а не "смещение" (это может быть константа, метка, переменная) использован, чтобы избежать двусмыс­ленности: сдвиг указывается в операторе и используется для вычисле­ния смещения (исполнительного адреса) в пределах сегмента.

Одним из видов конструкций в программе, которые можно построить с помощью условных переходов, являются циклы. Цикл - это просто-напросто блок кода, завершающийся условным переходом, благодаря чему данных блок может выполняться повторно до достижения условия завершения. Возможно, вам уже знакомы такие конструкции циклов, как for и while в языке Си, while и repeat в Паскале и FOR в Бейсике.

Для чего используются циклы? Они служат для работы с массивами, проверки состояния портов ввода-вывода до получения определенного состояния, очистки блоков памяти, чтения строк с клавиатуры и вывода их на экран и т.д. Циклы - это основное средство, которое используется для выполнения повторяющихся действий. Поэтому используются они довольно часто, настолько часто, что в наборе инструкций процессора 8086 предусмотрено фактически несколько инструкций циклов: LOOP, LOOPNE, LOOPE и JCXZ.

Давайте рассмотрим сначала инструкцию LOOP. Предположим, мы хотим вывести 17 символов текстовой строки TestString. Это можно сделать следующим образом:

.

.

.DATA

TestString DB 'Это проверка! ...'

..

.CODE

..

mov cx,17

mov bx,OFFSET TestString

PrintStringLoop:

mov dl,[bx] ; получить следующий символ

inc bx ; ссылка на следующий символ

mov ah,2 ; функция DOS вывода на экран

int 21h ; вызвать DOS для вывода символа

dec cx ; уменьшить счетчик длины строки

jnz PrintStringLoop ; обработать следующий символ, если он имеется

.

Есть, однако, лучший способ. Возможно, вспомните, что ранее уже упоминалось о том, что регистр CX весьма полезно бывает использовать для организации циклов. Инструкция:

loop PrintStringLoop

делает то же, что и инструкции:

dec cx

jnz PrintStringLoop

однако выполняется она быстрее и занимает на один байт меньше. Всякий раз, когда вам нужно организовать цикл, пока значение счетчика не станет равным 0, запишите начальное значение счетчика в регистр CX и используйте инструкцию LOOP.

Как же строятся циклы с более сложным условием завершения, чем обратный отсчет значения счетчика? Для таких случаев предусмотрены инструкции LOOPE и LOOPNE.

Инструкция LOOPE работает также, как инструкция LOOP, только цикл при ее выполнении будет завершаться (то есть перестанут выполняться переходы), если регистр CX примет значение 0 или флаг нуля будет установлен в значение 1 (нужно помнить о том, что флаг нуля устанавливается в значение 1, если результат последней арифметической операции был нулевым или два операнда в последней операции сравнения не совпадали). Аналогично, инструкция LOOPNE завершает выполнение цикла, если регистр CX принял значение 0 или флаг нуля сброшен (имеет нулевое значение).

Инструкция LOOPE известна так же, как инструкция LOOPZ, инструкция LOOPNE - как инструкция LOOPNZ, также как инструкции JE эквивалентна инструкция JZ (это инструкции-синонимы).

Имеется еще одна инструкция цикла. Это инструкция JCXZ. Инструкция JCXZ осуществляет переход только в том случае, если значение регистра CX равно 0. Это дает удобный способ проверять регистр CX перед началом цикла.

Относительно инструкций циклов необходимо помнить, во-первых, о том, что инструкции циклов, как и инструкции условных переходов, могут выполнять переход только на метку, отстоящую от инструкции цикла не более чем на 128 байт в ту или другую сторону. Циклы, превышающие 128 байт, требуют использования условных переходов с помощью безусловных переходов (этот метод описан в предыдущем разделе "Условные переходы"). Во-вторых, важно понимать, что ни одна из инструкций циклов не влияет на состояние флагов.

Задание 1 (2 часа)

1. Предварительная подготовка

В соответствии с выданным номером выбрать вариант задания, разработать схему и текст программы. Данные в сегменте DSEG можно изменить так, чтобы пошаговое выполнение программы при отладке не занимало слишком много времени. Каждый вариант задания предусматривает: поиск в массиве байтов, определенном в DSEG, первого обрабатываемого байта; обработку определенного количества байтов; вывод сообщения о результате.

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

Часть 1. Найти в DSEG: 1) 3-й по порядку нулевой байт; 2) 4-й по порядку код CR (0Dh); 3) 4-й байт из числа тех, которые ниже 20h; 4) 3-й по порядку код '$'(24h); 5) байт, следующий за 3-м кодом ';' (3Bh); 6) 4-й байт из числа больших, чем 29h; 7) байт, следующий за 3-м отрицательным байтом; 8) байт, являющийся 4-м нечетным; 9) байт, следующий за 3-м кодом пробела (20h); 10) 3-й байт из числа тех, которые выше 10h;

Задание 2 (2 часа)

Часть 2. Начиная с найденного байта, вычислить в формате слова: 1) сумму 4 байтов, исключая байты с четным количеством единиц; 2) сумму 5 байтов со знаком без самого большого; 3) сумму 4 байтов со знаком без самого маленького; 4) сумму 4 беззнаковых байтов без самого большого; 5) сумму 5 беззнаковых байтов без самого маленького; 6) сумму 5 байтов, исключая содержащие нечетное количество единиц; 7) сумму 5 байтов, причем из каждого положительного байта вычесть 2; 8) сумму 4 байтов, причем к каждому отрицательному байту добавить 5; 9) сумму 4 байтов; 10) сумму 3 знаковых байтов.

Часть 3. В зависимости от полученной суммы вывести на экран одно из следующих сообщений: 1) сумма < -256, -256 <= сумма < 64, сумма >= 64; 2) сумма <= -16, -16 < сумма <= 0, сумма > 0; 3) сумма < 0, 0 <= сумма < 64, сумма >= 64; 4) сумма выше 256, сумма ниже 256, сумма = 256; 5) сумма ниже 64, сумма выше или равна 64 и ниже 128, сумма выше или равна 128; 6) сумма < -128, -127 < сумма <= 64, сумма > 64; 7) сумма < 0, 0 < сумма < 32, сумма >=32; 8) сумма <= -256, -256 < сумма < 0, сумма >= 0; 9) сумма < 0, сумма = 0, сумма > 0; 10) сумма ниже 32, сумма выше или равна 64 и ниже 128, сумма выше или равна 128;

Приведем пример разработанной программы для варианта 9. В соответствии с заданием необходимо:

  1. найти байт, следующий за 3-м кодом пробела;

  2. сложить 4 байта, начиная с найденного;

  3. вывести сообщение о результате.


^ .MODEL SMALL

.STACK 100h

.DATA

DAN db 3Bh,20h,0Dh,32h,0A1h,24h,0A0h,0Dh,0,0A2h,20h

db 0B0h,40h,24h,0E1h,0Dh,0,24h,3Bh,30h,0C0h,0Dh

db 20h,97h,3Bh,83h,0,0A0h,20h,0D0h,27h,20h,0C6h

db 91h,0,20h,0FEh,3Bh,90h,0,3Bh,24h,17h,20h,24h

ZERO db 'СУММА РАВНА НУЛЮ',0ah,0dh,24h

GREAT db 'СУММА БОЛЬШЕ НУЛЯ',0ah,0dh,24h

LESS db 'СУММА МЕНЬШЕ НУЛЯ',0ah,0dh,24h

NET db 'КОД ПPОБЕЛА НЕ НАЙДЕН',0ah,0dh,24h

.code

start:

;инициализируем адрес сегмента данных

mov ax,@Data

mov ds,ax

;поиск начального элемента в DAN

mov cx,3 ;счетчик внешних циклов

mov dx,64 ;счетчик вложенных циклов

lea si,DAN ;нач. адрес данных

dec si

;начало внешнего цикла

EXT: push cx ;сохранение сч.внешних циклов

mov cx,dx ;загрузка сч.вложенных циклов

;вложенный цикл

LOC: inc si ;указатель - на следующий байт

cmp byte ptr [si],32;код пробела?

loopne LOC;повторять,пока нет

;продолжение внешнего цикла

jne NO ;код пробела не найден

mov dx,cx ;сч.вложенных циклов - в dx

pop cx ;восстановление сч.внешних циклов

loop EXT ;повторять,пока не найден 3-й пробел

;сложение 4 элементов

mov cx,4 ;количество слагаемых

sub bx,bx ;очистка аккумулятора

clc ;и переноса

A:inc si ;указатель - на следующий байт

mov al,byte ptr [si]

cbw ;pасшиpение знака

add bx,ax

loop A

;вывод сообщения о результате

jnz NZ ;переход, если сумма не равна нулю

lea dx,ZERO;вывод сообщения о нулевой

mov ah,9h;сумме

int 21h

jmp FIN

NZ: jg GR ;переход, если сумма > 0

lea dx,LESS;вывод сообщения об

mov ah,9h;отрицательной сумме

int 21h

jmp FIN

GR: lea dx,GREAT ;вывод сообщения о

mov ah,9h;положительной сумме

int 21h

jmp FIN

NO: lea dx,NET ;вывод сообщения

mov ah,9h;"пробел не найден"

int 21h

FIN:mov ax,4c00h ;выход из программы

int 21h

end start
2 Порядок выполнения работы на ЭВМ

1. С помощью текстового редактора ввести текст программы и записать в файл с уникальным именем.

2. Протранслировать программу:

tasm/zi имя_файла,имя_файла

3. Создать выполняемый exe-файл:

tlink/v имя_файла,имя_файла

4. Загрузить отладчик:

td

Отладить программу, выполняя ее по шагам (F8).

5. Показать преподавателю работу отлаженной программы.
ПРИМЕЧАНИЯ

1.Посмотреть выведенное программой сообщение можно на экране пользователя после нажатия клавиш Alt+F5; возврат в отладчик - Alt+F5.

2.При необходимости изменения содержимого сегмента данных следует нажимать клавишу Tab до тех пор, пока курсор не окажется в окне DUMP, подвести курсор к нужному элементу данных, ввести новое значение, нажать клавишу Enter, нажать клавишу Tab для возврата курсора в раздел сегмента кода программы.

3.Чтобы не повторять трансляцию при обнаружении каждой ошибки, можно исправлять отдельные операторы, не выходя из отладчика. Для этого надо установить курсор на ошибочный оператор, вызвать локальное меню, дать команду ASSEMBLE и ввести правильный оператор.

Контрольные вопросы:

  1. С какими операциями используется команда JUMP?

  2. Назовите сходство и различия команд перехода для знаковых и беззнаковых команд?

  3. Расскажите принцип работы операторов цикла.

  4. Можно ли организовать цикл без команд LOOP ?

  5. К какому типу переход (дальние, короткие) относятся условные переходы ?

  6. Как влияет команда CMP на регистр флагов ?

  7. Можно ли заменить в программе команду JL на команду JB ?

  8. Как можно реализовать переход на метку отстающую от инструкции перехода на расстояние больше чем 128 байт ?

  9. Найдите ошибку в следующем коде

..

Mov cx,10 ;повторить 10 раз

Repeat_Label:

Inc cx

Mov [si],cx

Inc si

Loop Repeat_Label

..
^

IV.Лабораторная работа №2

Массивы


Дадим формальное определение:
массив - структурированный тип данных, состоящий из некоторого числа элементов одного типа.

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

1. Перечислением элементов массива в поле операндов одной из директив описания данных. При перечислении элементы разделяются запятыми. К примеру:

;массив из 5 элементов.Размер каждого

элемента 4 байта:

mas dd 1,2,3,4,5

2. Используя оператор повторения dup. К примеру:

;массив из 5 нулевых элементов.

;Размер каждого элемента 2 байта:

mas dw 5 dup (0)


Такой способ определения используется для резервирования памяти с целью размещения и инициализации элементов массива.

3. Используя директивы label и rept. Пара этих директив может облегчить описание больших массивов в памяти и повысить наглядность такого описания. Директива rept относится к макросредствам языка ассемблера и вызывает повторение указанное число раз строк, заключенных между директивой и строкой endm. К примеру, определим массив байт в области памяти, обозначенной идентификатором mas_b. В данном случае директива label определяет символическое имя mas_b, аналогично тому, как это делают директивы резервирования и инициализации памяти. Достоинство директивы label в том, что она не резервирует память, а лишь определяет характеристики объекта. В данном случае объект — это ячейка памяти. Используя несколько директив label, записанных одна за другой, можно присвоить одной и той же области памяти разные имена и разный тип, что и сделано в следующем фрагменте:

...

n=0

...

mas_b label byte

mas_w label word

rept 4

dw 0f1f0h

endm

В результате в памяти будет создана последовательность из четырех слов f1f0. Эту последовательность можно трактовать как массив байт или слов в зависимости от того, какое имя области мы будем использовать в программе — mas_b или mas_w.

4. Использование цикла для инициализации значениями области памяти, которую можно будет впоследствии трактовать как массив.
Посмотрим на примере листинга 2, каким образом это делается.

Листинг 2 Инициализация массива в цикле

;prg_12_1.asm

MASM

MODEL small

STACK 256

.data

mes db 0ah,0dh,'Массив- ','$'

mas db 10 dup (?) ;исходный массив

i db 0

.code

main:

mov ax,@data

mov ds,ax

xor ax,ax ;обнуление ax

mov cx,10 ;значение счетчика цикла в cx

mov si,0 ;индекс начального элемента в cx

go: ;цикл инициализации

mov bh,i ;i в bh

mov mas[si],bh ;запись в массив i

inc i ;инкремент i

inc si ;продвижение к следующему элементу массива

loop go ;повторить цикл

;вывод на экран получившегося массива

mov cx,10

mov si,0

mov ah,09h

lea dx,mes

int 21h

show:

mov ah,02h ;функция вывода значения из al на экран

mov dl,mas[si]

add dl,30h ;преобразование числа в символ

int 21h

inc si

loop show

exit:

mov ax,4c00h ;стандартный выход

int 21h

end main ;конец программы

^ Доступ к элементам массива

При работе с массивами необходимо четко представлять себе, что все элементы массива располагаются в памяти компьютера последовательно.
Само по себе такое расположение ничего не говорит о назначении и порядке использования этих элементов. И только лишь программист с помощью составленного им алгоритма обработки определяет, как нужно трактовать эту последовательность байт, составляющих массив. Так, одну и ту же область памяти можно трактовать как одномерный массив, и одновременно те же самые данные могут трактоваться как двухмерный массив. Все зависит только от алгоритма обработки этих данных в конкретной программе. Сами по себе данные не несут никакой информации о своем «смысловом», или логическом, типе. Помните об этом принципиальном моменте.

Эти же соображения можно распространить и на индексы элементов массива. Ассемблер не подозревает об их существовании и ему абсолютно все равно, каковы их численные смысловые значения.
Для того чтобы локализовать определенный элемент массива, к его имени нужно добавить индекс. Так как мы моделируем массив, то должны позаботиться и о моделировании индекса. В языке ассемблера индексы массивов — это обычные адреса, но с ними работают особым образом. Другими словами, когда при программировании на ассемблере мы говорим об индексе, то скорее подразумеваем под этим не номер элемента в массиве, а некоторый адрес.

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

mas dw 0,1,2,3,4,5

Пусть эта последовательность чисел трактуется как одномерный массив. Размерность каждого элемента определяется директивой dw, то есть она равна 2 байта. Чтобы получить доступ к третьему элементу, нужно к адресу массива прибавить 6. Нумерация элементов массива в ассемблере начинается с нуля.
То есть в нашем случае речь, фактически, идет о 4-м элементе массива — 3, но об этом знает только программист; микропроцессору в данном случае все равно — ему нужен только адрес.

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

база + (индекс*размер элемента)

Архитектура микропроцессора предоставляет достаточно удобные программно-аппаратные средства для работы с массивами. К ним относятся базовые и индексные регистры, позволяющие реализовать несколько режимов адресации данных. Используя данные режимы адресации, можно организовать эффективную работу с массивами в памяти. Вспомним эти режимы:

  • индексная адресация со смещением — режим адресации, при котором эффективный адрес формируется из двух компонентов:

  • постоянного (базового) — указанием прямого адреса массива в виде имени идентификатора, обозначающего начало массива;

  • переменного (индексного) — указанием имени индексного регистра.
    К примеру:

mas dw 0,1,2,3,4,5

...

mov si,4

;поместить 3-й элемент массива mas в регистр ax:

mov ax,mas[si]

  • базовая индексная адресация со смещением — режим адресации, при котором эффективный адрес формируется максимум из трех компонентов:

  • постоянного (необязательный компонент), в качестве которой может выступать прямой адрес массива в виде имени идентификатора, обозначающего начало массива, или непосредственное значение;

  • переменного (базового) — указанием имени базового регистра;

  • переменного (индексного) — указанием имени индексного регистра.

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

Напомним, что в качестве базового регистра может использоваться любой из восьми регистров общего назначения. В качестве индексного регистра также можно использовать любой регистр общего назначения, за исключением esp/sp.

Микропроцессор позволяет масштабировать индекс. Это означает, что если указать после имени индексного регистра знак умножения «*» с последующей цифрой 2, 4 или 8, то содержимое индексного регистра будет умножаться на 2, 4 или 8, то есть масштабироваться.

Применение масштабирования облегчает работу с массивами, которые имеют размер элементов, равный 2, 4 или 8 байт, так как микропроцессор сам производит коррекцию индекса для получения адреса очередного элемента массива. Нам нужно лишь загрузить в индексный регистр значение требуемого индекса (считая от 0). Кстати сказать, возможность масштабирования появилась в микропроцессорах Intel, начиная с модели i486. По этой причине в рассматриваемом здесь примере программы стоит директива .486. Ее назначение, как и ранее использовавшейся директивы .386, в том, чтобы указать ассемблеру при формировании машинных команд на необходимость учета и использования дополнительных возможностей системы команд новых моделей микропроцессоров.

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

Листинг 3. Просмотр массива слов с использованием

масштабирования

;prg_12_2.asm

MASM

MODEL small

STACK 256

.data ;начало сегмента данных

;тексты сообщений:

mes1 db 'не равен 0!$',0ah,0dh

mes2 db 'равен 0!$',0ah,0dh

mes3 db 0ah,0dh,'Элемент $'

mas dw 2,7,0,0,1,9,3,6,0,8 ;исходный массив

.code

.486 ;это обязательно

main:

mov ax,@data

mov ds,ax ;связка ds с сегментом данных

xor ax,ax ;обнуление ax

prepare:

mov cx,10 ;значение счетчика цикла в cx

mov esi,0 ;индекс в esi

compare:

mov dx,mas[esi*2] ;первый элемент массива в dx

cmp dx,0 ;сравнение dx c 0

je equal ;переход, если равно

not_equal: ;не равно

mov ah,09h ;вывод сообщения на экран

lea dx,mes3

int 21h

mov ah,02h ;вывод номера элемента массива на экран

mov dx,si

add dl,30h

int 21h

mov ah,09h

lea dx,mes1

int 21h

inc esi ;на следующий элемент

dec cx ;условие для выхода из цикла

jcxz exit ;cx=0? Если да — на выход

jmp compare ;нет — повторить цикл

equal: ;равно 0

mov ah,09h ;вывод сообщения mes3 на экран

lea dx,mes3

int 21h

mov ah,02h

mov dx,si

add dl,30h

int 21h

mov ah,09h ;вывод сообщения mes2 на экран

lea dx,mes2

int 21h

inc esi ;на следующий элемент

dec cx ;все элементы обработаны?

jcxz exit

jmp compare

exit:

mov ax,4c00h ;стандартный выход

int 21h

end main ;конец программы

Еще несколько слов о соглашениях:

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

;переслать байт из области данных, адрес

которой находится в регистре ebx:

mov al,[ebx]



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

add eax,mas[ebx*4]

;сложить содержимое eax с двойным словом в памяти

;по адресу mas + (ebx)*4



  • Если для описания адреса используются два регистра, то речь идет о базово-индексной адресации. Левый регистр рассматривается как базовый, а правый — как индексный. В общем случае это не принципиально, но если мы используем масштабирование с одним из регистров, то он всегда является индексным. Но лучше придерживаться определенных соглашений.
    Помните, что применение регистров ebp/bp и esp/sp по умолчанию подразумевает, что сегментная составляющая адреса находится в регистре ss.

Заметим, что базово-индексную адресацию не возбраняется сочетать с прямой адресацией или указанием непосредственного значения. Адрес тогда будет формироваться как сумма всех компонентов.

К примеру:

mov ax,mas[ebx][ecx*2]

;адрес операнда равен [mas+(ebx)+(ecx)*2]

...

sub dx,[ebx+8][ecx*4]

;адрес операнда равен [(ebx)+8+(ecx)*4]

Но имейте в виду, что масштабирование эффективно лишь тогда, когда размерность элементов массива равна 2, 4 или 8 байт. Если же размерность элементов другая, то организовывать обращение к элементам массива нужно обычным способом, как описано ранее.

Рассмотрим пример работы с массивом из пяти трехбайтовых элементов (листинг 4). Младший байт в каждом из этих элементов представляет собой некий счетчик, а старшие два байта — что-то еще, для нас не имеющее никакого значения. Необходимо последовательно обработать элементы данного массива, увеличив значения счетчиков на единицу.

Листинг 4. Обработка массива элементов с нечетной длиной

;prg_11_3.asm

MASM

MODEL small ;модель памяти

STACK 256 ;размер стека

.data ;начало сегмента данных

N=5 ;количество элементов массива

mas db 5 dup (3 dup (0))

.code ;сегмент кода

main: ;точка входа в программу

mov ax,@data

mov ds,ax

xor ax,ax ;обнуление ax

mov si,0 ;0 в si

mov cx,N ;N в cx

go:

mov dl,mas[si] ;первый байт поля в dl

inc dl ;увеличение dl на 1 (по условию)

mov mas[si],dl ;заслать обратно в массив

add si,3 ;сдвиг на следующий элемент массива

loop go ;повтор цикла

mov si,0 ;подготовка к выводу на экран

mov cx,N

show: ;вывод на экран содержимого

;первых байт полей

mov dl,mas[si]

add dl,30h

mov ah,02h

int 21h

loop show

exit:

mov ax,4c00h ;стандартный выход

int 21h

end main ;конец программы

^ Двухмерные массивы

С представлением одномерных массивов в программе на ассемблере и организацией их обработки все достаточно просто. А как быть если программа должна обрабатывать двухмерный массив? Все проблемы возникают по-прежнему из-за того, что специальных средств для описания такого типа данных в ассемблере нет. Двухмерный массив нужно моделировать. На описании самих данных это почти никак не отражается — память под массив выделяется с помощью директив резервирования и инициализации памяти.

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

Если последовательность однотипных элементов в памяти трактуется как двухмерный массив, расположенный по строкам, то адрес элемента (i, j) вычисляется по формуле

(база + количество_элементов_в_строке * размер_элемента * i+j)

Здесь i = 0...n–1 указывает номер строки, а j = 0...m–1 указывает номер столбца.

Например, пусть имеется массив чисел (размером в 1 байт) mas(i, j) с размерностью 4 на 4
(i= 0...3, j = 0...3):

23 04 05 67

05 06 07 99

67 08 09 23

87 09 00 08

В памяти элементы этого массива будут расположены в следующей последовательности:

23 04 05 67 05 06 07 99 67 08 09 23 87 09 00 08

Если мы хотим трактовать эту последовательность как двухмерный массив, приведенный выше, и извлечь, например, элемент
mas(2, 3) = 23, то проведя нехитрый подсчет, убедимся в правильности наших рассуждений:

^ Эффективный адрес mas(2, 3) = mas + 4 * 1 * 2 + 3 = mas + 11

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

Организовать адресацию двухмерного массива логично, используя рассмотренную нами ранее базово-индексную адресацию. При этом возможны два основных варианта выбора компонентов для формирования эффективного адреса:

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

mov ax,mas[ebx][esi]

  • сочетание двух индексных регистров, один из которых является и базовым и индексным одновременно, а другой — только индексным:

mov ax,[ebx][esi]

В программе это будет выглядеть примерно так:

;Фрагмент программы выборки элемента

;массива mas(2,3) и его обнуления

.data

mas db

23,4,5,67,5,6,7,99,67,8,9,23,87,9,0,8

i=2

j=3

.code

...

mov si,4*1*i

mov di,j

mov al,mas[si][di] ;в al элемент mas(2,3)

...

В качестве законченного примера рассмотрим программу поиска элемента в двухмерном массиве чисел (листинг 5). Элементы массива заданы статически.

Листинг 5. Поиск элемента в двухмерном массиве

;prg_11_4.asm

MASM

MODEL small

STACK 256

.data

;матрица размером 2x5 — если ее не инициализировать,

;то для наглядности она может быть описана так:

;array dw 2 DUP (5 DUP (?))

;но мы ее инициализируем:

array dw 1,2,3,4,5,6,7,3,9,0

;логически это будет выглядеть так:

;array= {1 2}

; {3 4}

; {5 6}

; {7 3}

; {9 0}

elem dw 3 ;элемент для поиска

failed db 0ah,0dh,'Нет такого элемента в массиве!','$'

success db 0ah,0dh,'Такой элемент в массиве присутствует ','$'

foundtime db ? ;количество найденных элементов

fnd db ' раз(а)',0ah,0dh,'$'

.code

main:

mov ax,@data

mov ds,ax

xor ax,ax

mov si,0 ;si=столбцы в матрице

mov bx,0 ;bx=строки в матрице

mov cx,5 ;число для внешнего цикла (по строкам)

external: ;внешний цикл по строкам

mov ax,array[bx][si] ;в ax первый элемент матрицы

push cx ;сохранение в стеке счётчика внешнего цикла

mov cx,2 ;число для внутреннего цикла (по столбцам)

mov si,0

iternal: ;внутренний цикл по строкам

inc si ;передвижение на следующий элемент в строке

;сравниваем содержимое текущего элемента в ax с искомым элементом:

cmp ax,elem

;если текущий совпал с искомым, то переход на here для обработки,

;иначе цикл продолжения поиска

je here

;иначе — цикл по строке cx=2 раз

loop iternal

here:

jcxz move_next ;просмотрели строку?

inc foundtime ;иначе увеличиваем счётчик совпавших

move_next: ;продвижение в матрице

pop cx ;восстанавливаем CX из стека (5)

add bx,1 ;передвигаемся на следующую строку

loop external ;цикл (внешний)

cmp foundtime,0h ;сравнение числа совпавших с 0

ja eql ;если больше 0, то переход

not_equal: ;нет элементов, совпавших с искомым

mov ah,09h ;вывод сообщения на экран

mov dx,offset failed

int 21h

jmp exit ;на выход

eql: ;есть элементы, совпавшие с искомым

mov ah,09h ;вывод сообщений на экран

mov dx,offset success

int 21h

mov ah,02h

mov dl,foundtime

add dl,30h

int 21h

mov ah,09h

mov dx,offset fnd

int 21h

exit: ;выход

mov ax,4c00h ;стандартное завершение программы

int 21h

end main ;конец программы

При анализе работы программы не забывайте, что в языке ассемблера принято элементы массива нумеровать с 0. При поиске определенного элемента массив просматривается от начала и до конца.
Приведенная программа сохраняет в поле foundtime количество вхождений искомого элемента в массив. В качестве индексных регистров используются si и bx.

Задание 1 (2 часа)

  1. Повторить теоретический материал о строковых операциях по конспекту лекций и литературе.

  2. Прочитайте задание, которое выполняет нижеприведенная программа, напишите эту программу с именем Lab6.asm.

  3. Сделайте исполняемый файл, и проследить за работой в Турбоотладчике.

  4. Перепишите программу в ваш отчет, и заполните комментариями о содержании регистров все строки в сегменте кода.

  5. На основе проделанной работы сделать отчет, внести в него краткие выводы.

;Даны два массива ArrayA, ArrayB,

;состоящие из 10 элементов каждый

;Сравнить эти массивы поэлементно и

; - если элементы равны, то записать в соответствующий элемент

; третьего массива (Difference) 'Y'

; - иначе 'N'

;Найти сумму и количество всех одинаковых и различных

;элементов двух массивов (ArrayA,ArrayB)

.model tiny

.stack 100h

.data

ArrayA db 05,10,06,44,20,32,05,11,46,0

ArrayB db 35,10,15,44,20,02,65,10,46,0

Difference db 10 dup(0)

NumOfDiff dw 0

NumOfEqual dw 0

.code

start:

mov ax,@data

mov ds,ax

push ds

pop es

mov di,offset Difference

mov cx,10

mov al,'Y'

cld

rep stosb

mov si,offset ArrayA

mov di,offset ArrayB

mov bx,offset Difference

mov cx,10

cld

findDE:

cmpsb

jne NotEqual

inc NumOfEqual

inc bx

dec di

dec si

mov al,byte ptr ds:[si]

cbw

add SumOfEqual, ax

mov al,byte ptr ds:[di]

cbw

add SumOfEqual, ax

inc si

inc di

jmp NextElement

NotEqual:

inc NumOfDiff

mov byte ptr ds:[bx],'N'

inc bx

dec di

dec si

mov al,byte ptr ds:[si]

cbw

add SumOfDiff, ax

mov al,byte ptr ds:[di]

cbw

add SumOfDiff, ax

inc si

inc di

NextElement:

loop findDE

mov ax,4c00h

int 21h

end start

Задание 2 (2 часа)

6. Написать свою программу, которая сравнивала бы поэлементно буквы вашего имени и фамилии. В случае разницы размеров дополнить начальными буквами. Имя и фамилию написать заглавными латинскими буквами. Например,

PAVLOVA ANNAANN (имя ANNA дополнена тремя буквами из маго себя ANN)

7. Подсчитать количество одинаковых и различных элементов, начиная с последнего, в обратном порядке.

8. Создать массив D, размером в количество букв, записывая ‘Y’, где есть совпадение и ‘N’, где нет. В приведенном примере D размером 7, массив после выполнения выглядит так YNNNNNN.

9. Показать работу программы преподавателю в Турбоотладчике.
Контрольные вопросы:

1. Можно ли заменить команду STOSB командой STOSW?

2. Какую операцию производят строковые инструкции автоматически?

3. При применении строковых операций, можно ли в качестве регистра смешения использовать другие регистры, отличные от SI и DI?

4. Что служит результатом сравнения двух строк при помощи команды CMPS?

5. Найдите ошибку в следующем коде

.

.

mov ax,SEG aArray

mov es,ax

mov di,OFFSET aArray

mov cx,10

rep stosb

.

.

6.Сколько раз выполнится следующая команда scasb ?

; в дополнительном сегменте данных

str db ‘Today is a friday ’

; в сегменте команд

. . .

mov di,offset str

mov al,’y’

mov cx,20

repne scasb

jmp exit

. . .

7. Используя какую строковую инструкцию можно присвоить значение элементам массива?

Лабораторная работа №3

Цель работы

  • дать предсталение стека

  • научить использовать при написании программ на Асемблере

СТЕК

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

Под стек можно отвести область в любом месте памяти. Размер ее может быть любым, но не должен превосходить 64Кб, а ее начальный адрес должен быть кратным 16. Другими словами, эта область должна быть сегментом памяти; он называется сегментом стека. Начало этого сегмента (первые 16 битов начального адреса) должно обязательно храниться в сегментном регистре SS.

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

В ПК принято заполнять стек снизу вверх, от больших адресов к меньшим: первый элемент записывается в конец области, отведенной под стек, второй элемент - в предыдущую ячейку области и т.д. Считывается всегда элемент, записанный в стек последним. В связи с этим нижняя граница стека всегда фиксирована, а верхняя - меняется. Слово памяти, в котором находится элемент стека, записанный последним, называется вершиной стека. Адрес вершины, отсчитанный от начала сегмента стека, обязан находиться в указателе стека - регистре SP. Таким образом, абсолютный адрес вершины стека определяется парой SS:SP.



Значение 0 в регистре SP свидетельствует о том, что стек полностью заполнен (его вершина "дошла" до начала области стека). Поэтому, для контроля за переполнением стека надо перед новой записью в стек проверять условие SP=0 (сам ПК этого не делает). Для пустого стека значение SP должно равняться размеру стека, т.е. пара SS:SP должна указывать на байт, следующий за последним байтом области стека. Контроль за чтением из пустого стека, если надо, обязана делать сама программа.

Начальная установка регистров SS и SP может быть произведена в самой программе, однако в MASM предусмотрена возможность автоматической загрузки этих регистров. Если в директиве SEGMENT, начинающей описание сегмента стека, указать параметр STACK, тогда ассемблер (точнее, загрузчик) перед тем, как передать управление на первую команду машинной программы, загрузит в регистры SS и SP нужные значения. Например, если в программе сегмент стека описан следующим образом:

st segment stack

db 256 dup(?) ;размер стека - 256 байтов

st ends

и если под этот сегмент была выделена область памяти начиная с абсолютного адреса 12340h, тогда к началу выполнения программы в регистре SS окажется величина 1234h, а в регистре SP - величина 100h (=256).

Отметим, что эти значения соответствуют пустому стеку.

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

Запись слова в стек: PUSH opr

Здесь opr обозначает любой 16-битовый регистр (в том числе и сегментный) или адрес слова памяти. По этой команде значение регистра SP уменьшается на 2 (вычитание происходит по модулю 2^16), после чего указанное операндом слово записывается в стек по адресу SS:SP.

Чтение слова из стека: POP op

Слово, считанное из вершины стека, присваивается операнду op (регистру, в том числе сегментному, но не CS, или слову памяти), после чего значение SP увеличивается на 2.

Задание 1 (2 часа)

1. Повторить теоретический материал о способах адресации по конспекту лекций и литературе.

2. Подготовить измененное содержимое ds, es, ss.

3. Написать нижеприведенную программу с именем Lab3.asm, сделать исполняемый файл, и проследить за работой в Турбоотладчике.

Задание 2 (2 часа)

4. На основе работы программы в таблице 2, в графы 2 и 3 занести ожидаемые значения операндов.

Таблица 2

Оператор

Операнд-приемник




до выполнения

после выполнения

1

2

3

  1. На основе проделанной работы сделать отчет, внести в него краткие выводы.

SSEG segment para stack 'stack'

Db 1,2,3,4,5,6,7,8,9,128 dup(0Ah)

SSEG ends

DSEG segment para public 'data'

B_TAB db 1Ah,2Bh,3Ch,4Dh,5Eh,6Fh,7Ah,8Bh

W_TAB dw 1A2Bh,3C4Dh,5E6Fh,7A8Bh

B_TAB1 db 0Ah,8 dup(1)

W_TAB1 dw 8 dup(1)

DSEG ends

ESEG segment

W_TAB2 dw 11h,12h,13h,14h,15h,16h,17h,18h

ESEG ends

CSEG segment para public 'code'

PROG proc far

assume ds:DSEG,cs:CSEG,ss:SSEG,es:ESEG

push ds

mov ax,0

push ax

;инициализация сегментных регистров

mov ax,dseg

mov ds,ax

mov ax,eseg

mov es,ax

;непосредственная (операнд-источник)

mov al,-3 ;расширение знака

mov ax,3

mov B_TAB,-3

mov W_TAB,-3

mov ax,2A1Bh

;регистровая

mov bl,al

mov bh,al

sub ax,bx

sub ax,ax

;прямая

mov ax,W_TAB

mov ax,W_TAB+3

mov ax,W_TAB+5

mov al,byte ptr W_TAB+6

mov al,B_TAB

mov al,B_TAB+2

mov ax,word ptr B_TAB

mov es:W_TAB2+4,ax

;косвенная

mov bx,offset B_TAB

mov si,offset B_TAB+1

mov di,offset B_TAB+2

mov dl,[bx]

mov dl,[si]

mov dl,[di]

mov ax,[di]

mov bp,bx

mov al,[bp] ;какой сегмент?

mov al,ds:[bp]

mov al,es:[bx]

mov ax,cs:[bx]

; базовая

mov ax,[bx]+2 ;основная форма

mov ax,[bx]+4 ;проверьте допустимость других

mov ax,[bx+2]

mov ax,[4+bx]

mov ax,2+[bx]

mov ax,4+[bx]

mov al,[bx]+2

mov bp,bx ;другой базовый регистр

mov ax,[bp+2] ;откуда содержимое ax?

mov ax,ds:[bp]+2 ;попробуем переназначить

;сегментный регистр

mov ax,ss:[bx+2]

;индексная

mov si,2 ;загрузка индекса

mov ah,B_TAB[si] ;основная форма

mov al,[B_TAB+si] ;проверьте другие

mov bh,[si+B_TAB]

mov bl,[si]+B_TAB

mov bx,es:W_TAB2[si]

mov di,4

mov bl,byte ptr es:W_TAB2[di]

mov bl,B_TAB[si]

;базовая индексная

mov bx,offset B_TAB ;загрузка базы

mov al,3[bx][si] ;основная форма

mov ah,[bx+3][si]

mov al,[bx][si+2]

mov ah,[bx+si+2]

mov bp,bx

mov ah,3[bp][si] ;из какого сегмента?

mov ax,ds:3[bp][si]

mov ax,word ptr ds:2[bp][si]

ret

PROG endp

CSEG ends

end PROG

Контрольные вопросы:

  1. Объясните формирование 20 битного физического адреса по схеме сегмент:смешение?

  2. Сколько режимов адресации существует, расскажите разницу?

  3. Приведите общий формат команды определения данных программ на Ассемблере.

  4. Найдите ошибку в следующих командах:

MOV DS,@DATA

MOV AX,ES

MOV DS,AX

MOV CS,AX

MOV (ячейка памяти), (ячейка памяти)

  1. Назначение команды POP и PUSH, приведите общий формат команды, приведите пример использования этой команды.

  2. Расскажите об операторе PTR.

  3. Назначение команды XCHG, приведите пример использования этой команды.

  4. Найдите ошибку в следующих командах:

XCNG AX,BX

XCHG CX,BL

XCHG ES,DS

XCHG DATA1,AX

  1. Для чего нужен стек в программе?

  2. Расскажите о способах определения сегментов памяти?
^

Лабораторная работа №4

Процедуры.


Цель работы:

  • ознакомить с основами работы с процедурами;

  • объяснить виды адресаций процедур (NEAR, FAR);

  • показать использование локальных переменных;


^ ТЕОРЕТИЧЕСКАЯ ЧАСТЬ
В виде процедур обычно оформляютсямногократноповторяющиеся фрагментыпрограммдляускоренияразработки и сокращения объема программ.Процедуры могут быть внутренними (находятся в том же исходном модуле, что и вызывающая программа, транслируются совместно) и внешними (находятся в отдельных исходныхмодулях,транслируются отдельно).Последние чаще применяют в программах,предназначенных для преобразования в EXE-файлы,а также в случаях, когда процедуры достаточно велики по объему,сложны для отладки или могут быть использованы в других программах.

Ассемблер позволяет вам описывать процедуры несколькими способами. В данной работе описываются процедуры NEAR и FAR, объявление языка процедур, использование в процедурах аргументов и переменных, сохранение регистров, вложенные процедуры и описание процедур методов для объектов.

^ Синтаксис определения процедур

Для описания процедур вы можете использовать директиву PROC. Директива имеет следующий синтаксис:

PROC имя [расстояние]

[ARG список_аргументов] [RETURN список_элементов];

[LOCAL список_аргументов]

[USES список_элементов]

.

.

ENDP [имя]

Ассемблер также воспринимает для определения процедур синтаксис MASM.

^ Описание процедур NEAR или FAR

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

Процедура FAR вызывается с помощью вызова дальнего типа и содержит возврат дальнего типа. Процедуры FAR вы можете вызывать вне сегмента, в котором они определяются. Вызов FAR заносит в стек адрес в виде сегмента и смещения, а затем устанавливает CS:IP в адрес процедуры. Когда процессор обнаруживает возврат дальнего типа, он извлекает из стека сегмент и смещение адреса возврата и устанавливает в него CS:IP.

Расстояние (NEAR или FAR), используемое в процедуре по умолчанию, определяется текущей выбранной моделью. Для моделей TINY, SMALL и COMPACT по умолчанию процедура будет ближней (NEAR). Для всех других моделей по умолчанию выбирается расстояние FAR. Если вы не используете упрощенные директивы определения сегментов, то по умолчанию процедура всегда будет ближней (NEAR).

Примечание: FAR или NEAR можно задать в качестве аргумента оператора MODEL.

Вы можете переопределить используемое по умолчанию расстояние, задав нужное расстояние в определении процедуры. Для этого вы можете использовать ключевые слова NEAR или FAR. Эти ключевые слова переопределяют расстояние, используемое в процедуре по умолчанию, но только для текущей процедуры. Например:
MODEL TINY; по умолчанию расстояния NEAR

.

.; test1 - это дальняя процедура

test1 PROC FAR

; тело процедуры

RET; это будет дальним возвратом:

ENDP

; test2 по умолчанию является

; ближней процедурой

test2 PROC

; тело процедуры

RET; это будет ближним возвратом

ENDP

.

.

В процедурах NEAR и FAR используется одна и та же инструкция RET.Ассемблер использует расстояние процедуры для определения того, требуется возврат ближнего или дальнего типа. Аналогично,Ассемблер использует расстояние процедуры для определения того, требуется для ссылки на процедуру возврат ближнего или дальнего типа.

..

CALL test1 ; это дальний вызов

CALL test2 ; это ближний вызов

..

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

..

test1 PROC NEAR

MOV ax,10

CALL test2

RET

test1 ENDP

test2 PROC FAR

ADD ax,ax

RET

test2 ENDP

..
КогдаАссемблер при первом проходе достигает инструкции call test2, он еще не обнаруживает test2, и, следовательно, не знает расстояния. Он предполагает, что это расстояние NEAR, и что можно сделать ближний вызов.

КогдаАссемблер обнаруживает, что test2 является на самом деле дальней процедурой, он определяет, что для корректной генерации вызова требуется второй проход. Если вы разрешаете несколько проходов (с помощью параметра-переключателя командной строки /m), то можно сделать второй проход. Если вы не разрешаете несколько проходов, то Ассемблер будет выводить ошибку 'forward reference needs override' ('опережающая ссылка требует переопределения').

Чтобы избежать такой ситуации (и уменьшить число проходов), вы можете задать в вызове расстояние процедур с опережающей ссылкой, как NEAR PTR и FAR PTR.

..

test1 PROC NEAR

mov AX,10

CALL FAR PTR test2

RET

test1 ENDP

..

В предыдущем примере Ассемблеру сообщается, что нужно использовать дальний вызов, поэтому не возникает необходимость в нескольких проходах.

^ Работа команд RET и RETF

Алгоритм работы:
Работа команд зависит от типа процедуры:

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

  • для процедур дальнего типа — последовательно восстановить из стека содержимое ip и сегментного регистра cs.

  • если команда ret имеет операнд, то увеличить содержимое sp на величину операнда число;


^ Состояние флагов после выполнения команды не меняется.

Применение:Команду ret необходимо применять для возврата управления вызывающей программе из процедуры, управление которой было передано по команде call. На самом деле микропроцессор имеет три варианта команды возврата ret - это ret, ее синоним retn, а также команда retf. Они отличаются типами процедур, в которых используются. Команды ret и retn служат для возврата из процедур ближнего типа. Команда retf — команда возврата для процедур дальнего типа. Какая конкретно команда будет использоваться, определяется компилятором; программисту лучше использовать команду ret, и доверить транслятору самому сгенерировать ее ближний или дальний вариант. Количество команд ret в процедуре должно соответствовать количеству точек выхода из нее. Некоторые языки высокого уровня, к примеру Pascal, требуют, чтобы вызываемая процедура очищала стек от переданных ей параметров. Для этого команда ret содержит необязательный параметр число, который, в зависимости от установленного атрибута размера адреса, означает количество байт или слов, удаляемых из стека по окончании работы процедуры

^ Синтаксис директив ARG и LOCAL

Приведем синтаксис определения передаваемых процедуре аргументов:

ARG аргумент [,аргумент]... [=идентификатор]

[RETURNS аргумент] [,аргумент]]

При определении локальных переменных процедуры используется следующий синтаксис:
LOCAL аргумент [,аргумент]... [=идентификатор]

Отдельные аргументы имеют следующий синтаксис:

имя_аргумента [[выражение_счетчик_1]]

[: сложный_тип [:выражение_счетчик_2]]

где "сложный_тип" - это тип данных аргумента. Он может быть либо простым типом, либо сложным выражением-указателем.

"Выражение_счетчик_2" задает, сколько элементов данного типа определяет аргумент. Например, в определении аргумента:

ARG tmp:DWORD:4

определяется аргумент с именем "tmp", состоящий из 4 двойных слов.

По умолчанию "выражение_счетчик_2" имеет значение 1 (кроме аргументов типа BYTE). Так как вы не можете занести в стек байтовое значение, для аргументов типа BYTE значение счетчика по умолчанию равно 2, что обеспечивает для них в стеке размер в слово.

Это согласуется с языками высокого уровня, которые интерпретируют передаваемые в качестве параметров символьные переменные. Если вы действительно хотите задать аргумент, как один байт в стеке, нужно явным образом определить значение поля "выражение_счетчик_2", равное 1. Например:

ARG realbyte:BYTE:1

"Выражение_счетчик_1" представляет собой число элементов массива. Общее пространство, резервируемое для аргумента в стеке, равно произведению "выражения_счетчик_2" на длину, заданную полем "тип_аргумента" и на "выражение_счетчик_1". Если поле "выражение_счетчик_1" не задано, то по умолчанию оно равно 1. Общее число аргументов задает произведение "выражения"_счетчик_1" на "выражение_счетчик_2".

Если вы завершаете список аргументов символом равенства (=) и идентификатором, тоАссемблер будет приравнивать этот идентификатор к общему размеру блока аргументов (в байтах). Если вы не используете автоматическое использование соглашений языков высокого уровня вАссемблере, то можете использовать данное значение в конце процедуры в качестве аргумента инструкции RET. Заметим, что это вызывает очистку стека от всех занесенных туда перед возвратом аргументов (это соглашения по вызову, принятые в Паскале).

Аргументы и переменные определяются в процедуре как операнды в памяти относительно BP. Передаваемые аргументы, определенные с помощью директивы ARG, имеют положительное смещение относительно BP. Локальные переменные, определенные с помощью директивы LOCAL, имеют отрицательное смещение от BP. Приведем пример:

.

func1 PROC NEAR

ARG a:WORD,b:WORD:4,c:BYTE=d

LOCAL x:DWORD,y=WORD:2=z

.

Здесь a определяется, как [bp+4], b определяется, как [bp+6], c определяется, как [bp+14], а d - как 20. x - это [bp-2], y - [bp-6], а z - 8.

^ Область действия аргументов и имен локальных переменных

Если вы не задаете для них имена с предшествующий префиксом локального идентификатора, все аргументы, заданные в заголовке процедуры, определены ли они с помощью директивы ARG (передаваемые аргументы), RETURN (возвращаемые аргументы) или LOCAL (локальные переменные) имеют глобальную область действия.

Идентификаторы с локальной областью действия разрешает директива LOCALS. Например:

..

LOCALS

test1 PROC PASCAL FAR

ARG @a:WORD,@d:WORD,@c:BYTE

LOCAL @x:WORD,@y:DWORD

MOV ax,@a

MOV @x,ax

LES di,@b

MOV WORD ptr @y,di

MOV WORD ptr @y+2,es

MOV @c,'a'

RET

ENDP
test2 PROC PASCAL FAR

ARG @a:DWORD,@b:BYTE

LOCAL @x:WORD

LES di,@a

MOV ax,es:[di]

MOV @x,ax

CMP a1,@b

jz @dn

MVO @x,0

@dn: MOV ax,@x

RET

ENDP

..

Поскольку в данном примере используются переменные локальной области действия, их имена существуют только в теле процедуры. Таким образёом, в test2 можно снова использовать имена @a, @b и @x.

^ Сохранение регистров

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

Ассемблер может автоматически генерировать код для сохранения и восстановления этих регистров, который включается в код начала и завершения. Эти регистры можно указать в операторе USES, который имеет следующий синтаксис:

USES элемент [,элемент]...

где "элемент" может быть регистром или состоящим из одной лексемы элементом данных, который можно заносить и извлекать из стека. В одной процедуре допускается использовать не более 8 элементов.

Например:

.

myproc PROC PASCAL NEAR

ARG @source:DWORD,@dest:DWORD,@count:WORD

USES cx,si,di,foo

MOV cx,@count

MOV foo,@count

LES di,@dest

LDS si,@source

^ REP MOVSB

ENDP

..

Вложенные процедуры и правила области действия

Хотя вы можете вкладывать одну процедуру в другую, все процедуры имеют глобальную область действия. Например:

.

test1 PROC FAR

; код процедуры

CALL test2

; код процедуры

RET

test2 PROC NEAR

; код процедуры

RET ; ближний возврат

test2 ENDP

test1 ENDP

.

В данном примере вне охватывающей процедуры можно вызывать test1 и test2.

Если вы хотите получить локальные подпроцедуры, используйте имя с локальной областью действия, например:

.

LOCALS

test1 PROC FAR ; код процедуры

RET

@test2 PROC NEAR ; код процедуры

RET

@test2 ENDP

test1 ENDP

.

.

Примечание: Директива LOCALS разрешает идентификаторы с локальной областью действия.

В данном коде в процедуре test1 вы можете обратиться только к процедуре @test2. Фактически, если они не находятся в одной и той же процедуре может существовать несколько процедур с именем @test2. Например, допустимо следующее:

.

.

LOCALS

test1 PROC FAR

MOV si, OFFSET Buffer

CALL @test2

RET

@test2 PROC NEAR ; некоторый код

RET

@test2 ENDP

test2 PROC FAR

MOV si,OFFSET Buffer2

CALL @test2

RET

@test2 PROC NEAR ; некоторый код

RET

@test2 ENDP

test2 ENDP

.

Следующий код недопустим:

..

LOCALS

test1 PROC FAR

MOV si,OFFSET Buffer

CALL @test2

RET

test1 ENDP
@test2 PROC NEAR

; код процедуры

RET

@test2 ENDP

..

так как вызов @test2 задает локальный идентификатор для процедуры test1, а таких идентификаторов не существует.

Ниже приведены форматы соответствующих машинных команд. Прямой вызов NEAR-процедуры (для получения адреса перехода disp добавляется к IP):

Е8

disp_l

disp_h

Прямой вызов FAR-процедуры (offset и seg замещают IP и CS):

Е8

offset_l

offset_h

seg_l

seg_h


Задание 1 (2 часа)

  1. Повторить теоретический материал о процедурах по конспекту лекций и литературе.

  2. Прочитайте задание, которое выполняет нижеприведеннаяпрограмма, напишите эту программу с именем Lab7-1.asm.

  3. Сделайте исполняемый файл, и проследить за работой в Турбоотладчике с различными данными.

Задание 2 (2 часа)

  1. Напишите комментарий к каждой строке программы №7.1 с содержанием регистров.

  2. На основе проделанной работы сделать отчет, внести в него краткие выводы.

Следующая программа переводит содержимое регистра AX в шестнадцатеричное представление и записывает результат в строку смещение которой хранится в регистре BX

^

Программа №7.1


.model tiny

.stack 100h

.data

outStrdb '0000$' ;Выходная строка

.code

; Входные данные для процедуры translByte AL-байт, который нужно перевести

; Выходные данные

; BX-смещение строки в первые два байта которой будет записан результат

translByte proc

push ax

push ax

shr al,4

cmp al,9

ja greater10

mov byte ptr [bx],'0'

add [bx],al

jmp next4Bit

greater10:

mov byte ptr [bx],'A'

sub al,10

add [bx],al

next4Bit:

pop ax

and al,0Fh

cmp al,9

ja _greater10

mov byte ptr [bx+1],'0'

add [bx+1],al

jmp exitByteProc

_greater10:

mov byte ptr [bx+1],'A'

sub al,10

add [bx+1],al

exitByteProc:

pop ax

ret

translByte endp
translWord proc

push ax

push ax

shr ax,8

call translByte

pop ax

and ax,00FFh

add bx,2

call translByte

sub bx,2

pop ax

ret

translWord endp
start:

mov ax,@data

mov ds,ax

mov bx,OFFSET outStr

mov ax,60000

call translWord

mov ah,9

mov dx,OFFSET outStr

int 21h

mov ax,4c00h

int 21h

endstart


6. Написать программу № 7.2, с использованием процедур, которая запрашивает строку (ввод с клавиатуры), и затем переводит все символы по следующему алгоритму:

Вариант 1. Если символ в нижнем регистре, перевести его в верхний регистр; если в верхнем – в нижний;

Вариант 2. Вывести строку в обратном порядке;

Вариант 3. Вывести строку, в закодированном виде, от каждого кода символа строки отнимается число 10;

Вариант 4. Удалить все символы в верхнем регистре;

Вариант 5. Найти позицию символа (вводится с клавиатуры) в строке и вывести позицию (и) в шестнадцатеричном виде.

^

Контрольные вопросы


  1. Расскажите основные отличия между процедурами на языке Ассемблера от языков высокого уровня?

  2. Расскажите основные отличия между адресацией процедур типа FAR и NEAR?

  3. Какое смешение имеют локальные переменные, объявленные директивой LOCALS и почему?

  4. Разумно ли использование в программе с типом памяти Tiny процедуры с типомвызова FAR?

  5. Где применяется косвенный вызов процедуры?

  6. Чем отличается косвенный вызов процедуры от прямого вызова?

  7. Что такое опережающий вызов процедуры?

  8. Какой ключ нужно использовать для компиляции программы, в которой есть опережающий вызов процедуры?

  9. Возможен ли вызов процедуры расположенной внутри другой процедуры, в основной программе?

  10. Найдите ошибку в следующем коде

. Model tiny

.

.code

proc test1

.

.

retf

endp

start:

.

.

call test1

.

.

end start







Скачать файл (100 kb.)

Поиск по сайту:  

© gendocs.ru
При копировании укажите ссылку.
обратиться к администрации