Программирование на ассемблере для начинающих и не только. Создание графических примитивов на ассемблере Assembler 8086 8088 рисование простых фигур эллипс

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

Чтобы использовать средства ассемблера, необходимо ясно представлять себе детали архитектуры микропроцессоров 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 {Если не найден нулевой байт}

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

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

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

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

Язык ассемблер - это низкоуровневый язык программирования или же программа, которая исходный текст программы, написанный на языке ассемблера, переводит в программу на машинный язык. Язык, по некоторым меркам сложный, но ведь создание примитивов графики берет начало именно тут. Я же хочу рассмотреть ассемблер под Windows, а именно MASM , который, на ряду с Visual Studio, не так давно использовал для создания графических примитивов. Об этом с иллюстрациями и подробностями далее.

Приступая к работе
Рассмотрим маломальски простенькую структуру, которая необходима для создания приложений под Windows:
1) помещаем все константы, стpуктуpы и функции, относящиеся к Windows в начале нашего.asm файла - экономим силы и время;
2) используем диpективу includelib, чтобы указать библиотеки импоpта - это укажет компилятоpу на то, что пpогpамма будет использовать функции из этих библиотек импоpта;
3) объявляйте пpототипы API-функций, стpуктуp и/или констант в подключаемом файле с использованием тех же имен, что и в Windows include файлах, по крайней мере старайтесь, поскольку это избавит всех от головной боли в будующем;
4) используйте makefile, чтобы автоматизиpовать пpоцесс компиляции.

Я же отступлю кое-где и кое-как, но в целом у нас должна получиться отличная программа, которая нарисует нам довольно интересный таки примитив. Рассмотрим пример структуры программы на Ассемблере (см. Листинг 1)

Листинг 1. Пример структуры программы
.type_process ; описание типа процессора
.model ; описание модели памяти

Include lib ; подключение inc
includelib lib ; подключение lib

DATA ; иницилизиpуемые данные
; имя класса и окна

DATA? ; неиницилизиpуемые данные
; дескриптор пpогpаммы

CODE ; здесь начинается код программы

Определение графических примитивов
Контекст Устройства и WM_PAINT
В Windows окно само отвечает за перерисовку себя. Для того чтобы окно осуществило перерисовку, оно должно получить сообщение WM_PAINT.

Обычно используют один из трех методов:

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

Установка текущей позиции
Для установки текущей позиции используется функция MoveToEx(), где функция описывается следующим образом:

WINGDIAPI BOOL WINAPI MoveToEx(HDC, int, int, LPPOINT);

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

Рисование линии
Для прорисовки линии используется функцию LineTo(), где функция описывается следующим образом:

WINGDIAPI BOOL WINAPI LineTo(HDC, int, int);

Первый аргумент - контекст устройства, второй и третий аргументы - координаты точек.

Рисование прямоугольника
Для прорисовки прямоугольника используется функция Rectangle(), где функция описывается следующим способом:

WINGDIAPI BOOL WINAPI Rectangle(HDC, int, int, int, int);

Первый аргумент - это контекст устройства, все же остальные аргументы - координаты верхнего левого и нижнего правого углов прямоугольника.

Рисование эллипса
Для прорисовки эллипса необходимо вызвать функцию Ellipse(), где функция описывается следующим образом:

WINGDIAPI BOOL WINAPI Ellipse(HDC, int, int, int, int);

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

Рисование прямоугольника с закругленными краями
Для прорисовки прямоугольника с закругленными краями используется функция RoundRect(), где функция описывается следующим образом:

WINGDIAPI BOOL WINAPI RoundRect(HDC, int, int, int, int, int, int);

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

Написание и разбор.asm кода

Для написания примитива рассмотрим шаги, которые необходимы для создания и отрисовки графики:
1) получение дескриптора для программы;
2) регистрация класса окна;
3) создание окна;
4) отображение окна на экpане;
5) обновление содержимого экpана в окне;
6) выход из пpогpаммы.

Приступим к созданию, но для начала создадим новый проект в Visual Studio: File -> New Project

Выбираем пустой прокт: Empty project

Создаем новый файл: правой кнопкой по Source -> Add -> New Item

Создаем новый файл (.asm):
1-ый способ - дописать при создании нового файла file.asm (я таким способом создавал)
2-ой способ - изменить расширение файлу после его создания (file.txt -> rename -> file.asm)

Используем masm в Visual Studio: нажимаем правой кнопкой по преокту -> Build Customization

Задаем этот самый masm: ставим галочку напротив masm

Приступаем к написанию этого самого примитива, а сам листинг смотрите ниже.

Листинг 2. Написание кода на ассемблере
.386
.model stdcall, flat
option casemap:none

Includelib kernel32.lib
include kernel32.inc
includelib user32.lib
include user32.inc
include windows.inc
include gdi32.inc

Hwnd dd 0
hInst dd 0
szTitleName db "АиПОС. Лабороторная работа №6", 0
szClassName db "Приложение Win32", 0
msg MONMSGSTRUCT
wc WNDCLASS
ps PAINTSTRUCT

Main PROC
invoke GetModuleHandle, 0 ;получение значения баз. адреса,
mov hInst, eax ;по которому загружен модуль.
mov wc.style, CS_HREDRAW + CS_VREDRAW + CS_GLOBALCLASS
mov wc.lpfnWndProc, offset WndProc ;адрес оконной процедуры
mov wc.cbClsExtra, 0
mov wc.cbWndExtra, 0
mov eax, hInst ;дескриптор приложения
mov wc.hInstance, eax ;в поле hInstance
invoke LoadIcon, 0, IDI_APPLICATION
mov wc.hIcon, eax ;дескриптор значка в поле hIcon
invoke LoadCursorA, 0, IDC_ARROW
mov wc.hCursor, eax ;дескриптор курсора в поле hCursor
mov wc.hbrBackground, WHITE_BRUSH ;цвет бекграунда окна белый
mov dword ptr wc.lpszMenuName, 0 ;главного меню нет
mov dword ptr wc.lpszClassName, offset szClassName ;имя класса окна
invoke RegisterClassA, offset wc ;регистрация класас окна
invoke CreateWindowEx, 0, offset szClassName, offset szTitleName, \
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, \
CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInst, 0
mov hwnd, eax ;создание окна
invoke ShowWindow, hwnd, SW_SHOWNORMAL ;показ окна
invoke UpdateWindow, hwnd ;перерисовывка содержимого окна
cycle1: ;цикл сообщений
invoke GetMessage, offset msg, 0, 0, 0
cmp ax, 0
je end_c
invoke TranslateMessage, offset msg ;трансляция ввода с клавиатуры
invoke DispatchMessage, offset msg ;отправляем сообщение
;оконной процедуре
jmp cycle1
end_c:
invoke ExitProcess, 0 ;выход из приложения
Main ENDP

WndProc PROC USES ebx edi esi, _hwnd:DWORD, _wmsg:DWORD, _wparam:DWORD, _lparam:DWORD
local _hdc:DWORD
cmp _wmsg, WM_DESTROY
je wmdestroy
cmp _wmsg, WM_PAINT
je wmpaint
invoke DefWindowProcA, _hwnd, _wmsg, _wparam, _lparam ;обраб. по умолчанию
jmp exit_proc
wmpaint:
invoke BeginPaint, _hwnd, offset ps ;получаем контекст устройства
mov _hdc, eax
invoke Rectangle, _hdc, 170, 120, 310, 260 ;тело
invoke Rectangle, _hdc, 120, 120, 170, 140 ;левая лапа
invoke Rectangle, _hdc, 310, 120, 360, 140 ;правая лапа
invoke Rectangle, _hdc, 170, 260, 190, 310 ;левая ноголапа
invoke Rectangle, _hdc, 290, 260, 310, 310 ;правая ноголапа
invoke Rectangle, _hdc, 210, 80, 270, 120 ;башка
invoke Rectangle, _hdc, 220, 85, 225, 90 ;левый глаз
invoke Rectangle, _hdc, 250, 85, 255, 90 ;правый глаз
invoke Rectangle, _hdc, 225, 105, 255, 120 ;рот
invoke EndPaint, _hdc, offset ps ;освобождаем контекст

jmp exit_proc
wmdestroy:
invoke PostQuitMessage, 0 ;послать сообщение WM_QUIT
mov eax, 0 ;возвращаемое значение - 0
exit_proc:
ret
WndProc ENDP
END Main

Результат

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

Разбор полётов
Строка с.386 передает MASM, что используется набор инструкций пpоцессоpа 80386. Строка.model stdcall, flat передает MASM, что будет использоваться плоская модель памяти. А саму передачу паpаметpов использовали типом STDCALL как по умолчанию.
Подключил windows.inc в начале кода, поскольку он содеpжит системный стpуктуpы и константы, котоpые потpебовались для реализации примитивов в пpогpамме. Поскольку пpогpамма вызывает API функции Windows, которые находятся в user32.dll (CreateWindowEx и другие) и kernel32.dll (ExitPocess и другие) их необходимо тоже прописать.
Описываем прототип главной функции PROC.
Следом идёт.data, где: szClassName - имя нашего класса окна и szTitleName - имя нашего окна.
В.code содеpжит все инстpукции, где код должен pасполагаться между <имя метки> и end <имя метки>.
Пеpвая же инстpукция - вызов GetModuleHandle, чтобы получить дескриптор нашей пpогpаммы. Она используется как паpаметp, пеpедаваемый функциям API, которые вызываются нашей пpогpаммой.

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

1) cbSize: задает размеp общей стpуктуpы WDNCLASSEX в байтах;
2) style: задает стиль окона;
3) cbClsExtra: задается количество дополнительных байтов, котоpые нужно будет зарезервировать для самой программы;
4) hInstance: задает дескриптор модуля;
5) hIcon: задает дескриптор иконки, а его получение просходит посредством обращения функции LoadIcon;
6) hCursor: задает дескриптор куpсоpа, а его получение просходит посредством обращения функции LoadCursor;
7) hbrBackground: задает цвет фона;
8) lpszMenuName: задается дескриптор меню для окон;
9) lpszClassName: задается имя класса окна.

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

Основной и немаловажной является процедура WndProc PROC USES ebx edi esi, _hwnd:DWORD, _wmsg:DWORD, _wparam:DWORD, _lparam:DWORD.Не обязательно ее было называть ее WndProc, где пеpвый паpаметp, _hwnd - это хэндл окна, котоpому пpедназначается сообщение,_wmsg - передаваемое сообщение. Стоит сказать, что _wmsg - это не msg стpуктуpа, но это всего лишь число. _wparam и _lparam - это дополнительные паpаметpы, которые используются некоторыми сообщениями.

В конце концов подошли к заключительной части, где и описываются задаваемые фигуры, их координаты и возвращаемые значения. Это ключевая часть, поскольку именно здесь pасполагается логика действий пpогpаммы. Тут же описываем освобождение контекста и возравщаем значения, где далее посылаем сообщение о завершении. Единственное сообщение, которое осталось обработать - wmdestroy - это сообщение будет посылаться окну, когда оно закpывается. В то вpемя, когда пpоцедуpа окна его получает, окно уже исчезло с экpана. После выполнения wmdestroy вызывается PostQuitMessage, котоpый посылает сообщение о выходе и это вынуждает GetMessage веpнуть нулевое значение в eax, а это уже выход из программы.

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

Все внутренние регистры процессора Intel 8086 являются 16-битными:

Всего процессор содержит 12 программно-доступных регистров, а также регистр флагов (FLAGS) и указатель команд (IP).

Регистры общего назначения (РОН) AX, BX, CX и DX используются для хранения данных и выполнения различных арифметических и логических операций. Кроме того, каждый из этих регистров поделён на 2 части по 8-бит, с которыми можно работать как с 8-битными регистрами (AH, AL, BH, BL, CH, CL, DH, DL). Младшие части регистров имеют в названии букву L (от слова Low ), а старшие H (от слова High ). Некоторые команды неявно используют определённый регистр, например, CX может выполнять роль счетчика цикла.

Индексные регистры предназначены для хранения индексов при работе с массивами. SI (Source Index ) содержит индекс источника, а DI (Destination Index ) - индекс приёмника, хотя их можно использовать и как регистры общего назначения.

Регистры-указатели BP и SP используются для работы со стеком. BP (Base Pointer ) позволяет работать с переменными в стеке. Его также можно использовать в других целях. SP (Stack Pointer ) указывает на вершину стека. Он используется командами, которые работают со стеком. (Про стек я подробно расскажу в отдельной части учебного курса)

Сегментные регистры CS (Code Segment ), DS (Data Segment ), SS (Stack Segment ) и ES (Enhanced Segment ) предназначены для обеспечения сегментной адресации. Код находится в сегменте кода, данные - в сегменте данных, стек - в сегменте стека и есть еще дополнительный сегмент данных. Реальный физический адрес получется путём сдвига содержимого сегментного регистра на 4 бита влево и прибавления к нему смещения (относительного адреса внутри сегмента). Подробнее о сегментной адресации рассказывается в части 31 .

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

Указатель команд IP (Instruction Pointer ) содержит адрес команды (в сегменте кода). Напрямую изменять его содержимое нельзя, но процессор делает это сам. При выполнении обычных команд значение IP увеличивается на размер выполненной команды. Существуют также команды передачи управления, которые изменяют значение IP для осуществления переходов внутри программы.

Регистр флагов FLAGS содержит отдельные биты: флаги управления и признаки результата. Флаги управления меняют режим работы процессора:

  • D (Direction ) - флаг направления. Управляет направлением обработки строк данных: DF=0 - от младших адресов к старшим, DF=1 - от старших адресов к младшим (для специальных строковых команд).
  • I (Interrupt ) - флаг прерывания. Если значение этого бита равно 1, то прерывания разрешены, иначе - запрещены.
  • T (Trap ) - флаг трассировки. Используется отладчиком для выполнения программы по шагам.

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

  • S (Sign ) - знак результата, равен знаковому биту результата операции. Если равен 1, то результат - отрицательный.
  • Z (Zero ) - флаг нулевого результата. ZF=1, если результат равен нулю.
  • P (Parity ) - признак чётности результата.
  • C (Carry ) - флаг переноса. CF=1, если при сложении/вычитании возникает перенос/заём из старшего разряда. При сдвигах хранит значение выдвигаемого бита.
  • A (Auxiliary ) - флаг дополнительного переноса. Используется в операциях с упакованными двоично-десятичными числами.
  • O (Overflow ) - флаг переполнения. CF=1, если получен результат за пределами допустимого диапазона значений.

Не волнуйтесь, если что-то показалось непонятным. Из дальнейшего объяснения станет ясно, что к чему и как всем этим пользоваться 🙂

Регистр, как мы уже рассматривали раньше, - это, просто говоря, специально отведенная память для временного хранения каких-то данных, переменная.

Микропроцессор 8086 имеет 14 регистров. В прошлом выпуске мы встретили два из них: AH и DX. В Т аблицах N 1, 2 и 3 приведены списки всех регистров , кроме IP и регистра флагов, которые со временем будут рассматриваться отдельно:

AX

BX

CX

DX

AH BH CH DH

Таблица N 1. Регистры данных

Таблица N 3. Сегментные регистры

Регистры данных (Таблица N 1) .

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

В верхнем ряду Таблицы (AX (аккумулятор), BX (база), CX (счетчик), DX (регистр данных)) находятся шестнадцатиразрядные регистры, которые могут хранить числа от 0 до 65.535 (от 0 h до FFFFh в шестнадцатеричной системе (вспоминаем прошлый выпуск) ) . Под ним идет ряд восьмиразрядных регистров (AH, AL, BH, BL, CH, CL, DH, DL), которые могут хранить максимальное число 255 (FFh) . Это половинки (старшая или младшая) шестнадцатиразрядных регистров.

Например:

Мы уже знаем оператор mov , который предназначен для загрузки числа в регистр. Чтобы присвоить, к примеру, регистру AL число 35 h , нам необходимо записать так:

mov al,35h

а регистру AX число 346 Ah так:

mov ax,346Ah

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

Например, следующие записи будут ошибочны:

Mov ah,123h ---> максимум FFh

Mov bx,12345h ---> максимум FFFFh

Mov dl,100h ---> максимум FFh

Здесь надо отметить, что если шестнадцатеричное число начинается не с цифры (напр.: 12 h) , а с буквы (напр.: С 5h), то перед таким числом ставится нуль : 0 C5h . Это необходимо для того, чтобы Ассемблер мог отличить где шестнадцатеричное число, а где метка. Ниже мы рассмотрим это на примере.

Допустим, мы выполнили команду mov ax,1234h . В этом случае в регистре ah будет находится число 12 h , а в регистре al 34h . Т.е. ah, al, bh, bl, ch, cl, dh и dl это младшие (L ow) или старшие (H igh) половинки шестнадцатиразрядных регистров (см. Таблицу N 4).

Таблица N 4. Результаты выполнения различных команд

Новые операторы

Рассмотрим еще два оператора: add и sub .

Оператор ADD имеет следующий формат (в последствии мы всегда будем оформлять новые команды в такие таблицы):

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

Команда ADD производит сложение двух чисел.

Примеры:

mov al,10 ---> загружаем в регистр AL число 10

add al,15 ---> al = 25; al - приемник, 15 - источник

mov ax,25000 ---> загружаем в регистр AX число 25000

add ax,10000 ---> ax = 35000; ax - приемник, 10000 - источник

mov cx,200 ---> загружаем в регистр CX число 200

mov bx,760 ---> а в регистр BX --- 760

add cx,bx ---> cx = 960, bx = 760 (bx не меняется); cx - приемник, bx - источник

Команда Перевод ( с англ.) Назначение Процессор
SUB приемник, источник Subtraction вычитание Вычитание 8086

Команда SUB производит вычитание двух чисел.

Примеры:

mov al,10

sub al,7 ---> al = 3; al - приемник, 7 - источник

mov ax,25000

sub ax,10000 ---> ax = 15000; ax - приемник, 10000 - источник

mov cx,100

mov bx,15

sub cx,bx ---> cx = 85, bx = 15 (bx не меняется); cx - приемник, bx - источник

Это интересно

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

Каждая команда процессора выполняется определенное количество тактов. Когда говорят, что тактовая частота процессора 100Mhz, то это значит, что за секунду проходит 100 миллионов тактов.

Чтобы сложить два числа в Ассемблере нужно выполнить следующие команды:

mov ax,2700

mov bx,15000

add ax,bx

В результате выполнения данных инструкций, в регистре AX будет число 17700, а в регистре BX - 15000. Команда add ax,bx выполняется за один такт на процессоре 80486. Получается, что компьютер 486 DX2-66Mhz за одну секунду сложит два числа любых числа (от 0 до 0FFFFh) 66 миллионов раз!

Регистры -указатели (Таблица N 2 ) .

Регистры SI (индекс источника) и DI (индекс приемника) используются в строковых операциях. Регистры BP и SP необходимы при работе со стеком. Мы их будем подробно рассматривать в последующих выпусках.

Сегментные регистры (Таблица N 3 ) .

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

Новые операторы

Команда INC увеличивает на единицу регистр. Она эквивалентна команде

ADD источник, 1

только выполняется гораздо быстрее.

Примеры:

mov al,15

inc al ---> теперь AL = 16 (эквивалентна add al,1)

mov dh,39h

inc dh ---> DH = 3Ah (эквивалентна add dh,1)

mov cl,4Fh

inc cl ---> CL = 50h (эквивалентна add cl,1)

_____________________

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

Управлять клавиатурой позволяет прерывание 16h. Это прерывание BIOS (ПЗУ), а не MS-DOS (как 21h). Его можно вызывать даже до загрузки операционной системы, в то время, как прерывание 21h доступно только после загрузки COMMAND.COM.

Чтобы остановить программу до нажатия любой клавиши следует вызвать функцию 10h прерывания 16h. Вот как это выглядит (после стрелки (--->) идет комментарий):

mov ah,10h ---> в AH всегда указывается номер функции

int 16h ---> вызываем прерывание 16h - сервис работы с клавиатурой BIOS (ПЗУ)

После нажатия на любую клавишу, компьютер продолжит выполнять программу, а регистр AX будет содержать код клавиши, которую нажал пользователь.

Следующая программа выводит на экран сообщение и ждет нажатия любой клавиши (равнозначна команде pause в *.bat файлах):

(1) CSEG segment

(6) mov dx,offset String

(14) String db "Нажмите любую клавишу...$"

Строки с номерами (1), (2) и (15) пока опускаем. В строках (5) - (7), как вы уже знаете, выводим на экран сообщение. Затем, строки (9) - (10), ждем нажатия клавиши. И, наконец, строка (20) выходит из нашей программы.

Мы уже знаем команды INC, ADD и SUB. Можно поэксперементировать с вызовом прерывания. Например, так:

mov ah,0Fh

inc ah

int 16h

Это позволить Вам лучше запомнить новые операторы.

Общий формат оператора Ассемблера имеет следующий вид:

[Метка[:]] Мнемоника [Операнд1 [{,Операнд2}]] [;Комментарий]

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

Метка - это идентификатор, связанный с адресом первого байта того оператора, в котором она появляется. Мнемоника - это мнемоническое обозначение соответствующей команды процессора или директивы Ассемблера. Комментарий - это любая последовательность символов, начиная с символа ";" до конца строки, которая поясняет соответствующий оператор.

Метки используются как операнды в операторах программы для ссылки на адреса команд (например, при условных и безусловных переходах) и данных (например, переменных, массивов, структур). Имена меток могут состоять из следующих символов: "A" - "Z", "a" - "z", "_", "@", "$", "?", "0" - "9". Символы "0" - "9" не могут использоваться в качестве первых символов имени метки. Символы "$" и "?" имеют специальное значение, поэтому их не следует использовать в именах пользовательских меток. Имена меток не должны совпадать с именами регистров, мнемониками команд процессора, а также с ключевыми словами Ассемблера (встроенными переменными, операциями, директивами).

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

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

Основным полем в строке программы на Ассемблере является поле мнемоники. Мнемоники команды компилируются непосредственно в те команды процессора Intel 8086, которым они соответствуют. В отличие от мнемоник команд, директивы не компилируются в исполняемый код, они лишь управляют различными аспектами работы компилятора - от типа генерируемого кода (для процессоров 8086, 80286, 80386 и т. д.) до определения сегментов и формата создаваемых файлов листингов, и, таким образом, обеспечивают высокоуровневые средства программирования на Ассемблере.

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

Система команд процессора Intel 8086

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

Статьи по теме: