Создание графических примитивов на ассемблере. Программирование на ассемблере для начинающих и не только Команда сканирования строки SCAS

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

Чтобы использовать средства ассемблера, необходимо ясно представлять себе детали архитектуры микропроцессоров Intel 80x86. К этому семейству относятся микропроцессоры:

8086 - 16-разрядный микропроцессор, используемый в ПК IBM PC/XT;

8088 - аналог 8086, отличается от него только взаимодействием с памятью: 8086 может обмениваться с памятью как байтами, так и 16-разрядными словами, в то время как 8088 - только байтами;

80286 - улучшенный вариант 8086, используемый в ПК IBM AT; может работать в двух режимах: в реальном режиме, полностью эмулирующем работу МП 8086, и в защищенном режиме, в котором способен адресовать память до 16 Мбайт (в реальном - до 1 Мбайт);

80386 - 32-разрядный вариант 80286; способен адресовать до 4 Гбайт;

80486 - комбинация 80386/80387, т.е. имеет внутреннюю подсистему реализации операций с плавающей точкой;

80586 (Pentium) - имеет ряд усовершенствований, обеспечивающих ему увеличение производительности в 2...3 раза по сравнению с 80486, в том числе возможность обрабатывать 64-разрядные числа.

Микропроцессоры этого семейства наращивают свои возможности в перечисленном порядке, но строго совместимы от младших моделей к старшим: все, что может 8086/8088, реализует и Pentium, но не наоборот. Ниже обсуждается архитектура (внутреннее устройство, способы адресации и система команд) МП 8086/8088.



12.1.1. Регистры

В МП 8086/8088 имеется 14 регистров. В функциональном отношении они делятся на группы:

· регистры общего назначения (АХ, ВХ, СХ, DX); предназначены для хранения операндов и выполнения основных команд; любой из них может использоваться как совокупность двух независящих друг от друга 8-разрядных регистров: старшего байта регистра (АН, ВН, СН, DH) и младшего байта (AL, BL, CL, DL); например, АХ состоит из АН и AL;

· сегментные регистры (CS, DS, SS, ES); используются для указания сегмента при адресации памяти;

· регистры-указатели (SP, BP, IP); используются для указания смещения при адресации памяти;

· индексные регистры (SI, DI); применяются для индексной адресации;

· регистр флагов; используется для хранения признаков состояния процессора.

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

Регистр АХ . Является основным сумматором. Используется во всех арифметических операциях (сложить, умножить и т.п.). Только с помощью АХ и его полурегистров AHIAL возможен обмен данными с портами ввода/вывода.

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

Регистр СХ . В основном используется как счетчик при выполнении операций повторения и сдвига. Может также участвовать в арифметических операциях.

Регистр DX. Используется как регистр данных в операциях ввода/вывода, а также как сумматор при обработке длинных целых чисел (32-разрядных).

Регистр CS. Содержит номер сегмента памяти (сегмента кода), в котором располагается текущая машинная инструкция. Для получения полного адреса следующей команды его содержимое сдвигается влево на 4 разряда и складывается с регистром-указателем IP. Содержимое CS автоматически изменяется в командах дальнего (межсегментного) перехода и вызова процедур.

Регистр IP. Определяет смещение относительно начала сегмента кода CS очередной исполняемой машинной инструкции. Содержимое IP автоматически изменяется в ходе исполнения инструкции, обеспечивая правильный порядок выборки команд из памяти.

Регистр DS. Содержит номер сегмента памяти (сегмента данных), в котором располагаются данные (константы и переменные). Все глобальные переменные и типизированные константы программы Турбо Паскаля всегда располагаются в единственном сегменте, адресуемом этим регистром.

Регистр SS. Содержит номер сегмента стека. Стек - это участок автоадресуемой памяти, предназначенный для временного хранения операндов. С помощью стека ТурбоПаскаль организует обмен данными между программой и процедурами, кроме того, в нем он размещает все локальные переменные (т.е. переменные, объявленные внутри процедуры). Память стека используется по правилу «последним пришел - первым ушел»: самый последний помещенный в стек операнд будет первым извлекаться из него.

Регистр SP. Указывает на вершину стека, т.е. совместно с регистром 55 адресует ячейку памяти, куда будет помещаться операнд или откуда он будет извлекаться. Содержимое этого регистра автоматически уменьшается после размещения в стеке очередного операнда и увеличивается после извлечения операнда из стека.

Регистр ВР. Так называемый указатель базы. Облегчает создание и использование локального стека (т.е. стека для использования внутри процедуры).

Регистр ES. Дополнительный сегментный регистр ES используется для межсегментного обмена данными и в некоторых строковых операциях.

Регистр SI. Определяет адрес источника информации при индексной адресации данных (например, при обработке массивов). Обычно используется в паре с регистром DS.

Регистр DI. В паре с регистром £5 определяет приемник информации при межсегментном обмене данными.

Регистр флагов . Отдельные разряды (биты) этого регистра имеют следующее назначение.

Флаг переноса CF. Содержит 1, если произошел перенос единицы при сложении или заем единицы при вычитании. Используется также в циклических операциях и операциях сравнения.

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

Флаг внешнего переноса AF. Контролирует перенос из 3-го бита данных. Полезен при операциях над упакованными десятичными числами.

Флаг нуля ZF. Равен 1, если в результате операции получен ноль, и равен 0 в противном случае.

Флаг знака SF. Равен 1, если в результате операции получено отрицательное число (с единицей в старшем разряде).

Флаг трассировки TF. Равен 1, если программа исполняется по шагам, с передачей управления после каждой выполненной команды по прерыванию с вектором 1.

Флаг прерываний IF. Содержит 1, если микропроцессору разрешена обработка прерываний.

Флаг направления DF. Управляет направлением передачи данных: если он содержит 0, то после каждой индексной операции содержимое индексных регистров увеличивается на 1, в противном случае - уменьшается на 1.

Флаг переполнения OF. Устанавливается в единицу, если в результате операции получено число, выходящее за разрядную сетку микропроцессора.

12.1.2. Адресация

В архитектуре МП 8086/8088 адрес любого байта задается двумя 16-битовыми словами - сегментом и смещением. При формировании 20-разрядного полного адреса, необходимого для адресации в пределах 1 Мбайт, сегмент сдвигается влево на 4 разряда (умножается на 16) и складывается со смещением. Поскольку емкость 16-разрядного смещения составляет 65536 значений, в пределах одного сегмента можно адресовать до 64 Кбайт.

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

Регистровая

Извлекает операнд из регистра или помещает его в регистр. Примеры:

mov ах,bх {Извлекаем из ВХ и помещаем в АХ}

add cx,ax {Содержимое АХ прибавляем к СХ}

push ex {Заталкиваем в стек содержимое СХ}

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

Операнд (8- или 16-разрядная константа) содержится непосредственно в теле команды. Примеры:

mov ax,100 {Загружаем в АХ значение 100}

add ax,5 {К содержимому АХ прибавляем 5}

mov cx,$FFFF {Помещаем в СХ значение 65535}

Прямая

Смещение операнда задается в теле программы и складывается с регистром DS; например:

X: Word; В: Byte;

mov ах,Х {Пересылаем значение переменной X регистр АХ}

add ah,В {К содержимому регистра АН прибавляем значение переменной В}

mov X,ax {Пересылаем содержимое регистра АХ, в область памяти переменной X}

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

Исполнительный адрес операнда (точнее, его смещение) содержится в одном из регистров ВХ, ВР, SI или DI. Для указания косвенной адресации этот регистр должен заключаться в квадратные скобки, например:

mov ax, {Содержимое 16-разрядного слова, хранящегося в памяти по адресу DS:BX,пересылаем в регистр АХ};

Каждый из регистров BX...DI по умолчанию работает со своим сегментным регистром:

DS:BX, SS:BP, DS:SI, ES:DI

Допускается явное указание сегментного регистра, если он отличается от умалчиваемого, например:

Адресация по базе

Базовый регистр ВХ (или ВР) содержит базу (адрес начала некоторого фрагмента памяти), относительно которой ассемблер вычисляет смещение, например:

mov ах,[Ьх]+10 {Загружаем в АХ 10-й по счету байт от начала базы памяти по адресу DS-.BX};

Индексная адресация

Один из индексных регистров SI или DI указывает положение элемента относительно начала некоторой области памяти. Пусть, например, АОВ - имя массива значений типа Byte. Тогда можно использовать такие фрагменты:

mov si,15 {Помещаем в SI константу 15}

mov ah,АОВ {Пересылаем в АН 16-й по порядку байт от начала массива}

mov AOB,ah {Пересылаем полученное в самый первый элемент массива}

Адресация по базе с индексированием

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

Этот тип адресации удобен при обработке двумерных массивов. Если, например, АОВ есть массив из 10x10 байт вида

АОВ: array of Byte;

то для доступа к элементу АОВ можно использовать такой фрагмент

mov bx,20 {База строки 2}

mov si,2 {Номер 3-го элемента}

mov ax,AOB {Доступ к элементу}

12.1.3. Система команд

В приводимых ниже таблицах указывается мнемоника всех допустимых инструкций для МП 8086/8088. Для удобства пользования все команды разбиты на 6 функциональных групп - пересылки данных, арифметические, битовые, строковые, передачи управления, прерываний. Внутри каждой группы команды объединяются в подгруппы по общим дополнительным признакам.

Детальный анализ всех команд МП 8086/8088 занял бы слишком много места, поэтому в идущих за таблицами пояснениях рассматриваются лишь наиболее популярные команды. Исчерпывающее описание всех команд Вы найдете в , .

Команды пересылки данных

Мнемоника Формат Пояснение
Команды общего назначения
MOV MOV приемник, источник Переслать значение
PUSH PUSH источник Поместить в стек
POP POP приемник Извлечь из стека
XCHG XCHG приемник, источник Обменять значения
XLAT XLAT таблица Загрузить в AL байт из таблицы
Команды ввода-вывода
IN IN аккумулятор, порт Читать из порта
OUT OUT порт, аккумулятор Записать в порт
Команды пересылки адреса
LEA LEA регистр 16, память 16 Загрузить исполнительный адрес
LDS LDS регистр 16, память32 Загрузить в DS:регистр16 полный адрес
LES LES регистр 16, память32 Загрузить в ES:регистр16 полный адрес
Команды пересылки флагов
LAHF LAHF Загрузить флаги в АН
SAHF SAHF Установить флаги из АН
PUSHF PUSHF Поместить флаги в стек
POPF POPF Извлечь флаги из стека

Одна из наиболее часто используемых команд - МОV позволяет в защищенном режиме переслать байт или слово из регистра в регистр, из памяти в регистр или из регистра в память. Тип пересылаемых данных (байт или слово) определяется регистром, участвующим в пересылке. Ниже приводятся примеры использования команды:

mov ах,Table {Пересылка слова из памяти в АХ}

mov Table,ah {Пересылка байта из АН в память}

mov ds,ax {Пересылка в сегмент данных}

mov es:,ax {Пересылка слова в память: базовая адресация с заменой сегмента}

mov ch,-17 {Переслать константу в регистр}

mov Table,$FF {Переслать константу в память}

С помощью MOV нельзя пересылать:

· из памяти в память, например, вместо

следует использовать

· константу или переменную в DS, например, нельзя

· один сегментный регистр в другой, например, нельзя

· в регистр CS; значение этого регистра (сегмента кода) автоматически меняется при выполнении дальних команд CALL и JMP; кроме того, он загружается из стека при выполнении команды RETF (выход из дальней процедуры).

Для временного сохранения регистров и данных, а также для обмена значениями между регистрами широко используются стековые команды PUSH и POP. Каждая из них работает со словом, т.е. в стек нельзя поместить или извлечь из него одиночный байт. При выполнении PUSH вначале уменьшается на 2 содержимое указателя SP, а затем операнд помещается по адресу SS: SP. При извлечении из стека сначала читается память по адресу SS: SP, а затем SP увеличивается на 2. Таким образом, при заполнении указатель вершины стека SP смещается к младшим адресам, а при освобождении -к старшим. При работе со стеком следует помнить о специфике использования стековой памяти («последним пришел - первым ушел»), а также о том, что эта память интенсивно используется при вызове процедур, т.е. состояние стека к моменту выхода из процедуры должно быть строго согласовано с дальнейшей работой программы. Первое условие определяет порядок извлечения данных из стека - он должен быть обратным порядку, в котором эти данные помещались в стек. Второе условие фактически означает, что после выхода из процедуры указатель SP должен содержать то же смещение, что и к моменту входа в нее. Иными словами, процедура не должна «забыть» в стеке лишнее слово или взять из него больше нужного.

Команда загрузки адреса LEA загружает в регистр адрес (смещение) нужного участка памяти. Этого же можно достичь с помощью зарезервированного слова OFFSET, стоящего перед именем переменной. Например:

mov ax, OFFSET X {Загружаем смещение X в АХ}

lea ax,X {To же действие}

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

Две другие команды адресной загрузки - LDS и LES загружают первое 16-разрядное слово из источника в регистр-приемник, а затем следующее слово - в регистр DS или ES, т.е. они рассчитаны на загрузку полного адреса операнда (сегмента и смещения).

Арифметические команды

Мнемоника Формат Комментарий
Команды сложения
ADD ADD приемник, источник Сложить
ADC ADC приемник, источник Сложить, добавить перенос
ААА ААА Скорректировать сложение для таблицы ASCII
DAA DAA Скорректировать сложение для двоично-десятичных чисел
INC INC приемник Увеличить на единицу
Команды вычитания
SUB SUB приемник, источник Вычесть
SBB SBB приемник, источник Вычесть с заемом
AAS AAS Скорректировать вычитание для таблицы ASCII
DAS DAS Скорректировать вычитание для двоично-десятичных чисел
DEC DEC приемник Уменьшить на единицу
NEG NEG приемник Обратить знак
СМР СМР приемник, источник Сравнить
Команды умножения
MUL MUL источник Умножить без знака
IMUL IMUL источник Умножить со знаком
AАМ ААМ Скорректировать умножение для таблицы ASCII
Команды деления
DIV DIV источник Делить без знака
IDIV IDIV источник Делить со знаком
AAD AAD Скорректировать деление для таблицы ASCII
Команды расширения знака
CBW CBW Преобразовать байт в слово
CWD CWD Преобразовать слово в двойное слово

При использовании арифметических команд следует помнить о том, что МП может обрабатывать знаковые числа, числа без знака, а также двоично-десятичные числа. В беззнаковых числах для представления значения используются все биты. т.е. они эквивалентны типам Byte и Word, в то время как знаковые числа в старшем разряде хранят знак числа и эквивалентны типам Shortlnt и Integer. Двоично-десятичные числа используют по 4 бита для каждого десятичного разряда и могут быть упакованными или неупакованными. В первом случае один байт хранит 2 десятичные цифры (старшая - в старшем полубайте), во втором - только одну (старший полубайт не используется). Основные арифметические команды МП (ADD, SUB, MUL, DIV) не учитывают двоично-десятичную форму представления чисел, поэтому в архитектуру МП включены команды коррекции результата.

Битовые команды

Мнемоника Формат Комментарий
Логические команды
AND AND приемник, источник Выполнить AND
OR OR приемник, источник Выполнить OR
XOR XOR приемник, источник Выполнить XOR
NOT NOT приемник Выполнить NOT
TEST TEST приемник, источник Проверить
Сдвиговые команды
SAL/SHL SAL приемник, счетчик Сдвинуть влево
SAR/SHR SAR приемник, счетчик Сдвинуть вправо
ROL ROL приемник, счетчик Сдвинуть влево циклически.
ROR ROR приемник, счетчик Сдвинуть вправо циклически
RCL RCL приемник, счетчик Сдвинуть влево с переносом
RCR RCR приемник, счетчик Сдвинуть вправо с переносом

Битовые команды используются при исчислении логических выражений, а также в тех случаях, когда необходимо изменить отдельные разряды операнда. Логические команды AND, OR, XOR и NOT эквивалентны соответствующим операциям Турбо Паскаля в случае, когда операндами являются целочисленные выражения. Команда TEST выполняет целочисленную операцию поразрядного суммирования AND, но не изменяет значения операндов, а лишь устанавливает флаги в соответствии со значением результата сравнения: обнуляет CF и OF, изменяет PF, ZF, SF и не меняетAF (флаг ZF установится в 1 в том случае, когда оба операнда содержат по единице хотя бы в одном соответствующем разряде). Команды сдвига SHL/SHR эквивалентны одноименным операциям Турбо Паскаля и отличаются от команд циклического сдвига ROLIROR тем, что вытесненные в ходе их выполнения значащие разряды теряются, в то время как при циклическом сдвиге эти разряды появляются «с другой стороны». Например, если выполнить фрагмент

mov al,1 {Загружаем в AL единицу}

shr al,1 {Сдвигаем вправо,на 1 разряд}

регистр AL будет содержать 0 (вытесненная вправо единица будет помещена в CF), в то время как после замены команды SHR на ROR в нем будет значение $80=128 (вытесненная единица будет помещена в старший бит регистра).

Заметим, что счетчиком в командах сдвига может быть цифра 1 или количество сдвигов, указываемое в регистре CL.

Команды передачи управления

Мнемоника Формат Комментарий
Безусловные переходы
CALL CALL имя Войти в процедуру
RET RET [количество параметров] Вернуться из процедуры
JUMP JUMP имя Перейти
Условные переходы
JA/JNBE JA близкая_метка Перейти, если выше (после сравнения беззнаковых операндов)
JAE/JNB JAE близкая_метка Перейти, если выше или равно
JB/JBAE/JC JB близкая_метка Перейти, если ниже
JBE/JNA JBE близкая_метка Перейти, если ниже или равно
JCXZ JCXZ близкая_метка Перейти, если СХ=0
JE/JZ JE близкая_метка Перейти, если равно
JG/JNLE JG близкая_метка Перейти, если больше (после сравнения знаковых операндов)
JGE/JNL LGE близкая_метка Перейти, если больше или равно
JL/JNGE JL близкая_метка Перейти, если меньше
JLE/JNG JLE близкая_метка Перейти, если меньше или равно
JNC JNC близкая_метка Перейти, если нет переноса
JNE/JNZ JNE близкая_метка Перейти, если не равно
JNO JNO близкая_метка Перейти, если нет переполнения
JNP/ JPO JNP близкая_метка Перейти, если нечетный
JO JO близкая_метка Перейти, если перенос
JP/JPE JP близкая_метка Перейти, если четный
JS JS близкая_метка Перейти, если отрицательный
Команды управления циклами
LOOP LOOP близкая_метка Повторить цикл
LOOPE/LOOPZ LOOPE близкая_метка Повторять, пока равны
LOOPNE/LOOPNZ LOOPNE близкая_метка Повторять, пока не равны

Команды безусловных переходов CALL, RET, JMP могут использовать дальнюю или ближнюю модель памяти, в то время как команды условных переходов - только малую (в пределах -128...+127 байтов). При дальней модели памяти (устанавливается опцией Options/Compiler/Force far calls среды Турбо Паскаля или директивой компилятора {F+}) осуществляется как внутрисегментная, так и межсегментная передача управления, при ближней - только внутрисегментная.

Инструкция CALL работает следующим образом. Вначале адрес следующей за CALL инструкции (адрес возврата) помещается в стек, затем в регистр IP (или в пару CS:IP) помещается адрес точки входа в процедуру, таким образом сразу за командой CALL будет исполняться уже первая команда процедуры. Оба адреса (точки входа и возврата) будут 16-битовыми смещениями для внутрисегментного вызова или 32-битовыми полными адресами - для межсегментного. Все процедуры (функции) Паскаля, оттранслированные в режиме {F+} или содержащие зарезервированное слово FAR в заголовке, должны вызываться как дальние. Для этого за инструкцией CALL следует указать модель памяти:

Procedure MyProc; Far;

call FAR MyProc {Вызов дальней процедуры}

Таким же способом должны вызываться все библиотечные подпрограммы (т.е. объявленные в интерфейсных частях модулей). При дальнем вызове в стек сначала заталкивается содержимое сегмента кода CS, а уже затем - смещение возврата.

При выходе из дальней процедуры команда RET извлекает из стека оба 16-разрядных слова и помещает первое в IP, а второе в CS, а при выходе из ближней извлекает из стека только смещение и помещает его в IP.

Команды условных переходов способны передавать управление на метку, расположенную в пределах ближайших плюс-минус 128 байт от самой команды. Если нужно передать управление на метку, расположенную дальше в том же сегменте, или на метку в другом сегменте, сразу за командой условной передачи располагают безусловную команду JMP или CAL, например:

стр ах,0 {Проверяем АХ}

jne@NotZero {AX=0 ?}

jmp IsZero {Да - переходим на дальнюю метку}

....... {Нет - продолжаем работу}

В таблице термин «выше/ниже» используется применительно к сравнению беззнаковых операндов, а «больше/меньше» - знаковых.

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

Например:

сmр ах,5 {АХ>5 ?}

ja @AboveS {Да, больше - переходим}

стр bх,- 3 {ВХ<=-3 ?}

jle @LessM3 {Да, меньше или равен}

Команды LOOP/LOOPE/LOOPNE служат для организации циклов. Все они используют содержимое регистра СХ как счетчик числа повторений. Команда LOOP уменьшает СХ на единицу и передает управление на метку начала цикла, если содержимое этого регистра отлично от нуля. Команды LOOPE/LOOPNE также уменьшают счетчик СХ, но передают управление в начало цикла при совместном условии установки (или сброса) флага ZF и неравенства нулю счетчика СХ.

Вот как, например, можно отыскать нулевой байт в массиве АОВ:

АОВ: array of Byte;

mov ex, It)00 {Инициируем счетчик СХ}

lea bx,AOB {Помещаем адрес АОВ в ВХ}

dec bx {Готовим цикл}

{Здесь начало цикла проверки}

@@Test: inc bx {Адрес очередного байта}

cmp BYTE PTR ,0 {Проверяем байт}

loopne ©Test {Замыкаем цикл}

jnz ©NotZero {Если не найден нулевой байт}

....... {Нашли нулевой байт}

Строковые команды

Мнемоника Формат

ЯЗЫК АССЕМБЛЕРА МИКРОПРОЦЕССОРА 8088

Язык ассемблера - машинный язык, записанный в символической форме. В то время как адреса и команды в машинном языке суть числа, язык ассемблера разрешает присваивать адресам памяти неповторяющиеся имена, состоящие из букв. Так как исполнительные устройства микропроцессоров 8086 и 8088 идентичны, то и язык ассемблера 8088 также оказывается иден-тичным языку ассемблера 8086 и является частью языка ассемблера микропроцессоров 80186 и 80286, представляющих собой улучшенные модификации ЦП 8086.

Программа, написанная на языке ассемблера 8088, может быть переведена (транслирована) на машинный язык с помощью программы, называемой ассемблером. Ассемблер, описанный в данной книге, является макроассемблером фирмы Microsoft, используемым в персональном компьютере IBM с дисковой операционной системой PC-DOS.

Существуют и другие версии ассемблеров, приемлемые для семейства микропроцессоров 8086/8088, например ассемблер IASM-86, разработанный фирмой Intel Corporation, который (включает стандартные мнемонические команды, в том числе команды арифметических операций для чисел с плавающей запятой в процессоре 8087. Несмотря на имеющиеся различия между этими ассемблерами, их структуры и форматы команд языков в значительной степени совместимы.

Почему необходим язык ассемблера?

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


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

Язык ассемблера - способ символической записи программ на машинном языке. Например, код машинной команды ЗС02 (шестнадцатеричный) может быть записан в символической форме на языке ассемблера как CMP AL,02 (эта команда сравнивает содержимое регистра AL с шестнадцатеричным числом 02). Таким образом, рутинные преобразования машинных кодов и данных в программе, перечисленные выше, можно. максимально переложить на программу - ассемблер. Следовательно, основная цель ассемблера - трансляция программ, написанных на языке ассемблера, в машинный язык (машинные коды).

Ассемблер следит за использованными ячейками памяти, с тем чтобы команды в программе имели ссылки на эти адреса в символическом виде, используя имена и метки, заменяющие действительные значения. Например, машинный код в шестнадцатерич-ной записи С606001201 может быть записан на языке ассемблера как MOV BYTE-COUNT,01, где BYTE-COUNT есть символическое имя адреса памяти 1200Н.

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

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

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




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

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

Формат программы

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

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

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

1. Ассемблер микропроцессора Intel 8086/8088

1.1. Формат операторов ассемблера

Операторы языка ассемблера ПЭВМ имеют следующий формат:

[<метка> :]<префикс> <код операции > [<спиcок операндов >1 [<комментарии>].

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

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

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

1.2. Определение полей памяти для размещения данных

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

DB - определить однобайтовое поле, DW - определить слово (двухбайтовое поле), DD - определить двойное слово (четырехбайтовое поле).

Формат команды:
DB
[<имя поля>] DW [< количество > DUP (]{ <список чисел >}[)]
DD ?

где <количество >- количество полей памяти указанной длины, которое определяется данной командой (указывается, если определяется не одно поле памяти); ? - используется при резервировании памяти.

Приведем примеры
  1. Записать в байт памяти десятичное число 23 и присвоить этому байту имя а:
    a db 23.
  2. Зарезервировать 1 байт памяти: db ?
  3. Записать в слово памяти шестнадцатеричное число 1234: dw 1234H.
  4. Определить 31 байт памяти, повторяя последовательность 1, 2, 3, 4, 5, 1, 2, 3, 4,... :
    db 31 dup (1,2,3,4,5)

Примечание. При записи слов в память младший байт записывается в поле с младшим адресом. Например, в примере 3, если запись выполнялась по адресу 100, то по адресу 100 будет записано 34H, а по адресу 101 - 12H.

1.3. Операнды команд ассемблера

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

Данные, непосредственно записанные в команде, называются литералами. Так, в команде
mov ah, 3 3 - литерал.

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

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

inc OPND

Здесь OPND - символическое имя поля памяти, определенного оператором ассемблера

OPND dw ?

При трансляции программы ассемблер заменит символическое имя на исполнительный адрес указанного поля памяти (смещение относительно начала сегмента) и занесет этот адрес на место индексного смещения. Адресация а этом случае выполняется по схеме: BР + <индексное смещение>, но содержимое регистра ВР при вычислении исполнительного адреса не используется (частный случай).

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


OPND
OPND +
+

Приведенные выше формы записи косвенного адреса интерпретируются одинаково.

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

Примечание. При использовании косвенной адресации по схеме ВР + <индексное смещение> индексное смещение не может быть опущено, так как частный случай адресации по данной схеме с нулевой длиной индексного смещения используется для организации прямой адресации. Следовательно, при отсутствии индексного смещения в команде следует указывать нулевое индексное смещение, т.е. [ВР + 0] .

Приведем два примера: и + +6 .

В первом случае исполнительный адрес операнда определяется суммой содержимого регистра bх и индексного смещения, заданного символическим именем "а", а во втором - суммой содержимого регистров bp, si и индексного смещения, равного 6.

Длина операнда может определяться:

  • а) кодом команды - в том случае, если используемая команда обрабатывает данные определенной длины, что специально оговаривается;
  • б) объемом регистров, используемых для хранения операндов (1 или 2 байта);
  • в) специальными указателями byte ptr (1 байт) и word ptr (2 байта), которые используются в тех случаях, когда длину операнда нельзя установить другим способом. Например,
    mov byte ptr x, 255
    т. е. операнд пересылается в поле с именем "х" и имеет длину I байт.

1.4. Команды пересылки / преобразования данных

1. Команда пересылки данных

MOV <адрес приемника> ,< адрес источника>

используется для пересылки данных длиной I или 2 байта из регистра в регистр, из регистра в основную память, из основной памяти в регистр, а также для записи в регистр или основную память данных, непосредственно записанных в команде. Все возможные пересылки представлены на рис. 6.

Приведем примеры
  • а) mov ax, bx- пересылка содержимого регистра bx в регистр ax;
  • б) mov cx, exword - пересылка 2 байт, расположенных в поле exword, из основной памяти в регистр cx;
  • в) mov si, 1000 - запись числа 1000 в регистр si;
  • г) mov word ptr , 4 - запись числа 4 длиной 2 байта в основную память по адресу .

Для загрузки "прямого" адреса в сегментный регистр используются две команды пересылки:

mov ax, code
mov ds, ax

2. Команда обмена данных

ХCHG <операнд 1> , <операнд 2>

организует обмен содержимого двух регистров (кроме сегментных) или регистра и поля основной памяти. Например:

xchg bx, cx - обмен содержимого регистров bx и сх.

3. Команда загрузки исполнительного адреса

LEA < операнд l > , < операнд 2 >

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

  • lea bx, exword - в регистр bx загружается исполнительный адрес exword;
  • lea bx, - в регистр bx загружается адрес l0-го байта относительно точки, на которую указывает адрес в регистре di.

4. Команды загрузки указателя

LDS < регистр > ,<операнд 2>
LЕS < регистр > ,<операнд 2>

Команда LDS загружает в регистры DS:< регистр> указатель (< адрес сегмента > : < исполнительный адрес >), расположенный по адресу, указанному во втором операнде.

Команда LЕS загружает указатель по адресу, расположенному во втором операнде, в регистры ЕS:< регистр> .

Например:

lds si, exword

т.e. слово (2 байта) по адресу exword загружается в si, а по адресу exword+ 2 - в ds.

5. Команда записи в стек

PUSH < операнд>

организует запись в стек слова, адрес которого указан в операнде. Например;
push dx - запомнить содержимое регистра dx в стеке.

6. Команда восстановления из стека

POP < операнд>

организует чтение из стека последнего слова и помещает его по адресу, указанному во втором операнде. Например:
pop dx - восстановить содержимое регистра dx из стека.

7. Команды сложения

ADD <операнд 1> , <операнд 2>
ADC <операнд 1> , <операнд 2>

устанавливают флаги четности, знака результата, наличия переноса, наличия переполнения.

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

На рис. 7 показаны возможные способы размещения слагаемых, где а -операнды - слова, б - операнды - байты.

Приведем пример сложения двух 32-разрядных чисел:

mov ax, value1
add value2, ax
mov ax, value1+2
adc value2+2, ax

Исходные числа находится в основной памяти по адресам value1 и value2, а результат записывается по адресу value1.

8. Команды вычитания

SUB <уменьшаемое-результат> , <вычитаемое>
SBB <уменьшаемое-результат>, <вычитаемое>

устанавливают флаги четности, знака результата, наличия заема, наличия переполнения.

При выполнении операции по команде SUB заем не учитывается, а по команде SBB - учитывается. Ограничения на местоположение операндов такие же, как и у команды сложения.

9. Команда изменения знака

NEG <операнд>
знак операнда изменяется на противоположный.

10. Команда добавления единицы

INC <операнд>
значение операнда увеличивается на единицу.

11. Команда вычитания единицы

DEC <операнд>
значение операнда уменьшается на единицу.

12. Команда сравнения

СМP <операнд 1> , < операнд 2>
выполняется операция вычитания без записи результата и устанавливаются признаки во флажковом регистре.

13. Команды умножения

MUL <операнд>
IМUL <операнд>

устанавливают флаги наличия переноса или переполнения.

По команде MUL числа перемножаются без учета, и по команде - IМUL с учетом знака (в дополнительном коде).

На рис. 8 (где а - операнды - слова, б - операнды - байты) приведены возможные способы размещения сомножителей и результата (один из сомножителей всегда расположен в регистре-аккумуляторе.

Рассмотрим пример:
imul word ptr c

Здесь содержимое основной памяти по адресу "с" длиной слово умножается на содержимое регистра ax. Младшая часть результата операции записывается в регистр aх, а старшая часть - и регистр dx.

14. Команда деления

DIV <операнд-делитель>
IDIV <операнд-делитель>

По команде DIV операция деления выполняется без учета, а по команде IDIV - с учетом знака (в дополнительном коде).

На рис. 9 приведены возможные способы размещения делимого, делителя и результата (а - операнды - слова, б - операнды - байты).

15. Команда преобразования байта в слово, а слова - в двойное слово

CBW
CWD

По команде CBW число из al переписывается в ax (дополнение выполняется знаковыми разрядами). Аналогично по команде CWD число из ax переписывается в два регистра dx и ax (dx:ax).

1.5. Команды передачи управления

1. Команда безусловного перехода

JMP <адрес перехода>

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

  • short - при переходе по адресу, который находится на расстоянии -128...127 байт относительно адреса данной команды (длина адресной части 1 байт);
  • near ptr - при переходе по адресу, который находится на расстоянии З2 Кбайта (-32768...32767 байт) относительно адреса данной команды (длина адресной части 2байта);
  • far ptr - при переходе по адресу, который находится на расстоянии превышающем 32 Кбайта (длина адресной части 4 байта).

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

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

  • а) < имя > : nор (nор - команда "нет операции");
  • б)< имя> label near (для внутрисегментных переходов);
  • в) <имя> label far (для внесегментных переходов).
  • а) jmp short b - переход по адресу b;
  • б) jmp - переход по адресу в регистре bx (адрес определяется косвенно);
  • в) a: пор - описание метки перехода "a";
  • г) b label near - описание метки перехода "b".

2. Команды условного перехода

<мнемоническая команда> <адрес перехода>
Мнемоника команд условного перехода:

  • JZ - переход по "ноль";
  • JE - переход по "равно";
  • JNZ - переход по "не нуль";
  • JNE - переход по "не равно";
  • JL - переход по "меньше";
  • JNG , JLE - переход по "меньше или равно ";
  • JG - переход по "больше";
  • JNL , JGE - переход по "больше или равно ";
  • JA - переход по "выше" (беззнаковое больше);
  • JNA , JBE - переход по "не выше"(беззнаковое не больше);
  • JB - переход по "ниже" (беззнаковое меньше);
  • JNB , JAE - переход по"не ниже" (беззнаковое не меньше).

Все команды имеют однобайтовое поле адреса, следовательно, смешение не должно превышать -128...127 байт. Если смещение выходит за указанные пределы, то используется специальный прием: (читайте в методичке)

3. Команды организации циклической обработки

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

1) Команда организации цикла

LOOP < адрес перехода >
при каждом выполнении уменьшает содержимое регистра cx на единицу и передает управление по указанному адресу, если cx не равно 0:

begin_loop:
; ... тело цикла...
loop begin_loop

Примечание. Если перед началом цикла в регистр cx загружен 0, то цикл выполняется 35536 раз.

2) Команда перехода по обнуленному счетчику

JCXZ <адрес перехода>

передает управление по указанному адресу, если содержимое регистра cx равно 0. Например:
mov cx, loop_count; загрузка счетчика
jcxz end_of_loop; проверка счетчика
begin_loop:
; ... тело цикла...
loop begin_loop
end_of_loop:
...

3) Команды организации цикла с условием

LООРE <адрес перехода>
LOOPNE <адрес перехода>

уменьшают содержимое на единицу и передают управление по указанному адресу при условии, что содержимое cx отлично от нуля, но LООРE дополнительно требует наличия признака "равно", а LOOPNE - ""не равно", формируемых командами сравнения. Например:

mov cx, loop_count ; загрузка счетчика
jcxz end_of_loop ; проверка счетчика
begin_loop:
; ... тело цикла...
cmp al, 100 ; проверка содержимого al
loopne begin_loop ; возврат в цикл, если cx0 и al100
end_of_loop: ...

4. Команды вызова подпрограмм

1) Команда вызова процедуры

CALL <адрес процедуры>

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

  • а) если процедура удалена не более чем на -128...127 байт, то специальных указаний не требуется;
  • б) если процедура удалена в пределах 32 килобайт, то перед адресом процедуры необходимо указать near ptr,
  • в) если процедура подпрограмма удалена более, чем на 32 килобайта, то перед адресом процедуры необходимо записать far ptr.

Например:

call near ptr p - вызов подпрограммы "р".
Текст процедуры должен быть оформлен в виде:
< имя процедуры> ргос < указатель удаленности>
... тело процедуры...
<имя процедуры> end

Здесь указатель удаленности также служит дли определения длины адресов, используемых при обращении к процедуре: near - при использовании двухбайтовых адресов, far - при использовании четырехбайтовых адресов.

2) Команда возврата управления

RET [<число>]

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

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

1.6. Команды обработки строк

Команды обработки строк используются для организации циклической обработки последовательностей элементов длиной I или 2 байта. Адресация операндов при этом выполняется с помощью пар регистров: DS:SI - источник, ES:DI - приемник. Команды имеют встроенную корректировку адреса операндов согласно флагу направления D: 1 - уменьшение адреса на длину элемента, 0 - увеличение адреса на длину элемента. Корректировка выполняется после выполнения операции.

Установка требуемого значения флага направления выполняется специальными командами:

STD - установка флага направления в единицу,
CLD - сброс флага направления в ноль.

1) Команда загрузки строки LODS

Команда загружает байт в АL или слово в AX. Для адресации операнда используются регистры DS:SI

2) Команда записи строки STOS

STOSB (запись байта),
STOSW (запись слова)

записывает в основную память содержимое AL или АX соответственно. Для адресации операнда используются регистры ES:DI.

3) Команда пересылки MOVS

MOVSB (пересылка байта),
МОVSW (пересылки слова)
пересылает элемент строки из области, адресуемой регистрами DS:SI, в область, адресуемую регистрами ЕS:DI.

4) Команда сканирования строки SCAS

SCASB (поиск байта),
SCASW (поиск слова).

По команде содержимое регистра AL или АХ сравниваются с элементом строки, адресуемым регистрами DS:SI и устанавливается значение флажков в соответствии с результатом - AL или -AX.

5) Команда сравнения строк CMPS

СMPSB (сравнение байт),
СMPSW (сравнение слов)

элементы строк, адресуемых парами регистров DS:SI и ES:DI, сравниваются и устанавливаются значения флажков в соответствии с результатом -.

6) Префиксная команда повторения

REP <команда>

позволяет организовать повторение указанной команды CX раз. Например:

rep stosb

Здесь поле, адресуемое парой регистров ES:DI длиной CX заполняется содержимым AL .

7) Префиксные команды "повторять, пока равно" и "повторять, пока не равно"

REPE < команда >
REPNE < команда >

Префиксные команды используются совместно с командами СMPS и SCAS. Префикс REPE означает повторять, пока содержимое регистра СХ не равно нулю и значение флажка нуля равно единице, a REPNE - повторять, пока содержимое регистра CX не равно нулю и значение флажка нуля равно нулю.

1.7. Команды манипулирования битами

1. Логические команды

  • NOT <операнд> - логическое НЕ;
  • AND <операнд 1>, <операнд 2> - лигическое И;
  • OR <операнд 1>, <операнд 2> - логическое ИЛИ;
  • XOR <операнд 1>, <операнд 2> - исключающее ИЛИ;
  • TEST <операнд 1>, <операнд 2> - И без записи результата.

Операнды байты или слова.

Пример. Выделить из числа в AL первый бит:
and al, 10000000B

2. Команды сдвига

<код операции> <операнд>, <счетчик>
Счетчик записывается в регистр СL. Если счетчик равен 1, то его можно записать в команду.

Коды команд сдвига

  • SAL - сдвиг влево арифметический;
  • SHL - сдвиг влево логический;
  • SAR - сдвиг вправо арифметический;
  • SHR - сдвиг вправо логический;
  • ROL - сдвиг влево циклический;
  • ROR - сдвиг вправо циклический;
  • RCL - сдвиг циклический влево с флагом переноса;
  • RCR - сдвиг циклический вправо с флагом переноса.

Пример. Умножить число в AX на 10:

mov bx, ax
shl ax, 1
shl ax, 1
add ax, bx
shl ax, 1

1.8. Команды ввода - вывода

Обмен данными с внешней средой осуществляемся с помощью следующих команд:

  • IN <регистр>, <порт> (ввод из порта в регистр),
  • IN <регистр >, DX (ввод из порта, номер которого указан в регистре DX в регистр);
  • OUT <порт>, <регистр> (вывод содержимого регистра в порт),
  • OUT DX, <регистр> (вывод содержимого регистра в порт, номер которого указан в регистре DX).

В качестве регистра можно указать AL или AX (соответственно будет обрабатываться байт или два байта). Порт отождествляется с некоторым внешним устройством (0...255).

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

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

Примеры:

  • a) mov ah, 1 ; номер функции
    int 21h ; ввод символа: символ в AL
  • б) mov ah, 2 ; номер функции
    mov dl, "A"
    int 21h ; вывод символа из DL
  • в) lea dx, STRING ; адрес буфера ввода
    mov ah, 0Ah ; номер функции
    int 21h ; ввод строки: во втором байте буфера - количество
    ... ; введенных символов, далее в буфере символы
    STRING db 50, 50 dup (?)
  • г) lea dx, MSG ; адрес выводимой строки
    mov ah, 9 ; номер функции
    int 21h ; вывод строки
    ...
    MSG db "Пример вывода", 13, 10, "$"

Задание к лабораторной работе №3

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

SGSTACK SEGMENT PARA STACK "STACK"
DB 32 DUP(?)
SGSTACK ENDS

DATA SEGMENT PARA PUBLIC "DATA"
MAX DW ?
ARRAY DW 10H, 20H, 30H, 0D0H,0A0H
DATA ENDS

CODE SEGMENT PARA PUBLIC "CODE"
ASSUME CS:CODE, DS:DATA, SS:SGSTACK

START: MOV AX, DATA ; загрузить в DS
MOV DS, AX ;селектор сегмента данных
LEA BX, ARRAY ; загрузить в BX начальный адрес массива
MOV CX, 4 ; инициализировать счетчик
MOV AX, ; инициализировать начальное значение max

CYCLE: ADD BX, 2 ; перейти к следующему элементу массива
CMP , AX ; сравнить два значения
JBE BE ; переход если равен или ниже
MOV AX, ; сохранить большее значение
BE: LOOP CYCLE ; проверка на выход из цикла (--CX при CX=0)
MOV MAX, AX ; сохранение максимального значения

EXIT: XOR AL, AL ; выход в OS
MOV AH, 4CH
INT 21H
CODE ENDS
END START

Порядок выполнения работ

Для выполнения лабораторной работы необходимо:

  1. Получить вариант задание у преподавателя из таблицы
  2. Составить программу согласно заданному варианту
  3. Получить файл с исходным текстом программы в EXE и COM формате
  4. Оттранслировать, отладить программу. Изучить листинг программы.
  5. Скомпоновать выполняемый файл, изучить карту загрузки (порядок следования сегментов, их размеры и относительные адреса)
  6. Запустить программу под отладчиком.
  7. Оформить отчет

Программа Emu8086 платная. Однако в течение 30 дней вы можете использовать её для ознакомления бесплатно.

Итак, вы скачали и установили программу Emu8086 на свой компьютер. Запускаем её и создаём новый файл через меню FILE – NEW – COM TEMPLATE (Файл – Новый – Шаблон файла COM). В редакторе исходного кода после этого мы увидим следующее:

Рис. 1.1. Создание нового файла в Emu8086.

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

После создания файла в Emu8086 описанным выше способом в редакторе исходного кода вы увидите строку «add your code hear» - «добавьте ваш код здесь» (рис. 1.1). Эту строку мы удаляем и вставляем вместо неё следующий текст:

MOV AH, 02h MOV DL, 41h INT 21h INT 20h Таким образом, полный текст программы будет выглядеть так: ORG 100h MOV AH, 02h MOV DL, 41h INT 21h INT 20h RET Кроме этого в верхней части ещё имеются комментарии (на рис. 1.1 – это текст зелёного цвета). Комментарий в языке Ассемблера начинается с символа; (точка с запятой) и продолжается до конца строки. Если вы не знаете, что такое комментарии и зачем они нужны, см. книгу Как стать программистом . Как я уже говорил, здесь мы не будем растолковать азы программирования, так как книга, которую вы сейчас читаете, рассчитана на людей, знакомых с основами программирования.

Также отметим, что регистр символов в языке ассемблера роли не играет. Вы можете написать RET, ret или Ret – это будет одна и та же команда.

Вы можете сохранить этот файл куда-нибудь на диск. Но можете и не сохранять. Чтобы выполнить программу, нажмите кнопку EMULATE (с зелёным треугольником) или клавишу F5. Откроется два окна: окно эмулятора и окно исходного кода (рис. 1.2).

Рис. 1.2. Окно эмулятора Emu8086.

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

В окне эмулятора вы можете запустить вашу программу на выполнение целиком (кнопка RUN) либо в пошаговом режиме (кнопка SINGLE STEP). Пошаговый режим удобен для отладки. Ну а мы сейчас запустим программу на выполнение кнопкой RUN. После этого (если вы не сделали ошибок в тексте программы) вы увидите сообщение о завершении программы (рис. 1.3). Здесь вам сообщают о том, что программа передала управление операционной системе, то есть программа была успешно завершена. Нажмите кнопку ОК в этом окне и вы увидите, наконец, результат работы вашей первой программы на языке ассемблера (рис. 1.4).

Рис. 1.3. Сообщение о завершении программы.

Рис. 1.4. Ваша первая программа выполнена.

Как мы уже говорили, наша первая программа выводит на экран английскую букву «А». Результат оправдал наши ожидания – буква «А» выведена на экран.

Здесь стоит отметить, что Emu8086 – это ЭМУЛЯТОР, то есть он эмулирует работу компьютера с процессором 8086. Поэтому в описанном выше примере программа выполняется не операционной системой, а эмулятором. Emu8086 может создавать и реальные программы, которые могут самостоятельно выполняться на компьютере. Но описание работы с Emu8086 не входит в наши планы. Читайте справку и экспериментируйте – всё у вас получится.

В нашем случае пока не важно, как выполняется программа – эмулятором или операционной системой. Главное – разобраться с вопросом создания программ на языке ассемблера. Поэтому разберём нашу простенькую программку подробно.

#make_COM# – 1-ая строка. Эта команда – специфическая для Emu8086. Она используется для определения типа создаваемого файла. В нашем случае это файл с расширением.COM.

ORG 100h – 2-ая строка. Эта команда устанавливает значение программного счетчика в 100h, потому что при загрузке СОМ-файла в память, DOS выделяет под блок данных PSP первые 256 байт (десятичное число 256 равно шестнадцатеричному 100). Код программы располагается только после этого блока. Все программы, которые компилируются в файлы типа СОМ, должны начинаться с этой директивы.

MOV AH, 02h – 3-я строка. Инструкция (или команда) MOV помещает значение второго операнда в первый операнд. То есть значение 02h помещается в регистр АН. Для чего это делается? 02h – это ДОСовская функция, которая выводит символ на экран. Мы пишем программу для DOS, поэтому используем команды этой операционной системы (ОС). А записываем мы эту функцию (а точнее ее номер) именно в регистр АН, потому что прерывание 21h использует именно этот регистр.

MOV DL, 41h – 4-я строка. Код символа «A» заносится в регистр DL. Код символа «A» по стандарту ASCII – это 41h.

INT 21h – 5-я строка. Это и есть то самое прерывание 21h – команда, которая вызывает системную функцию DOS, заданную в регистре АН (в нашем примере это функция 02h). Команда INT 21h – основное средство взаимодействия программ с ОС.

INT 20h – 6-я строка. Это прерывание, которое сообщает операционной системе о выходе из программы и о передаче управления консольному приложению. Значит, при использовании INT 20h в нашем примере, управление будет передаваться программе Emu8086. А в том случае, если программа уже откомпилирована и запущена из ОС, то команда INT 20h вернет нас в ОС (например, в DOS). В принципе, в случае с Emu8086 эту команду можно было бы пропустить, так как эту же функцию выполняет команда RET, которая вставляется в исходный текст автоматически при создании нового файла по шаблону (как это сделали мы ранее). Но я решил использовать INT 20h и здесь для совместимости с другими ассемблерами.

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

Первые микропроцессоры были 4-битовыми устройствами. Это озна­чает, что за один прием они могли обрабатывать только четыре бита информации. Для обработки более четырех битов они должны были выполнить несколько после­довательных операций. Конечно, это замедляло работу.

Первым промышленным 8-битовым микропроцессором (обрабатывавшим одно­временно 8 битов информации) стал микропроцессор 8008, выпущенный фирмой Intel в 1972 году. Он считается лучшим 8-битовым микропроцессором первого поко­ления. По своей архитектуре этот микропроцессор похож на калькулятор; он имеет аккумулятор, шесть регистров общего назначения, указатель стека (специальный регистр адреса рабочих ячеек), восемь регистров адреса и специальные команды для ввода и вывода данных. В 1973 году фирма Intel выпустила версию второго поколения-микропроцессора 8008, получившую название 8080.

По сравнению с микропроцессором 8008 микропроцессор 8080 мог адресоваться к большей памяти, имел расширенные возможности ввода-вывода, обладал допол­нительными командами и работал быстрее. Хотя идеи архитектуры микропроцес­сора 8008 были в основном перенесены фирмой Intel и на микропроцессор 8080, его внутренняя организация была улучшена настолько, что микропроцессор 8080 стал стандартом de facto для микропроцессоров второго поколения; и когда речь заходит о микропроцессорах, то многим людям первым делом приходит на ум именно микропроцессор 8080.

Достижения технологии позволили фирме Intel в 1976 году выпустить усовер­шенствованную версию микропроцессора 8080, названную 8085. Он отличался от микропроцессора 8080 конструкцией корпуса, имел сброс в начальное состояние (для инициализации микропроцессора), прерывания по вектору (для обслужива­ния периферийных устройств), последовательный порт ввода-вывода (для подклю­чения принтеров и других периферийных устройств). Кроме того, ему требовался только один источник питания +5 В (микропроцессору 8080 требовалось два источ­ника питания).

Ко времени выпуска микропроцессора 8085 фирма Intel встретилась с серьезной конкуренцией на рынке 8-битовых микропроцессоров. Начали пользоваться успехом микропроцессор Z80 фирмы Zilog Corporation, представляющий собой усовершенствование микропроцессора 8080, а также микропроцессоры 6800 фирмы Motorola и 6502 фирмы MOS Technology (ныне фирма Commodore), существенно отличавшиеся от 8080 по своей архитектуре. И вместо того, чтобы продолжать борьбу на переполненном рынке 8-битовых микропроцессоров, фирма Intel сделала качественный шаг вперед и в 1978 году выпустила 16-битовый микропроцессор 8086, который мог обрабатывать данные в 10 раз быстрее, чем микропроцессор 8080.

Микропроцессор 8086 программно совместим с микропроцессором 8080 на уров­не языка ассемблера. Это означает, что после некоторых минимальных преобразо­ваний программы, написанные для микропроцессора 8080, можно заново оттранс­лировать и выполнить на микропроцессоре 8086. Такая совместимость обеспечива­ется за счет того, что регистры микропроцессора 8080 и набор его команд являются как бы подмножествами регистров и набора команд микропроцессора 8086. Это позволило фирме Intel воспользоваться богатым опытом применения микропро­цессора 8080 и получить преимущество в сфере более сложных приложений.

Так как многие проектировщики все еще предпочитали пользоваться в своих 16-битовых системах более дешевыми 8-битовыми вспомогательными и перифе­рийными микросхемами, то фирма Intel выпустила версию микропроцессора 8086, имевшую то же устройство внутри, но 8-битовую Шину данных снаружи. Эта версия (микропроцессор 8088) идентична 8086, но затрачивает больше времени на переда­чи 16-битовых данных, которые выполняются с помощью двух последовательных 8-битовых передач. Однако в приложениях, где обрабатываются в основном 8-бито­вые значения, микропроцессор 8088 отстает по производительности от микропро* цессора 8086 не более чем на 10%.

Таким образом, Вы можете считать, что Ваша персональная ЭВМ фирмы IBM имеет 16-битовый микропроцессор. (И, следовательно, можете пользоваться много­численной литературой по микропроцессору 8086.) Завершим на этом введение и перейдем к обсуждению возможностей микропроцессора 8088.

Похожие статьи