Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Уч.-пр. пособие.doc
Скачиваний:
27
Добавлен:
26.02.2016
Размер:
327.17 Кб
Скачать

Глава 1. Основы программирования микропроцессоров фирмы Intel

1.1 Архитектура ПК

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

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

Минимальной единицей информации является бит. Бит может быть «выключен», т.е. его значение равно «0», либо «включен», т.е. его значение равно «1».

Группа из девяти бит представляет собой байт, восемь бит которого содержат данные и один бит – контроль на чётность. Восемь бит обеспечивают основу для двоичной арифметики и для представления символов, таких как, например, буква «A» или символ «*».

Восемь бит дают 256 различных комбинаций включенных и выключенных состояний: от “все выключены” (00000000) до “все включены” (11111111).

Например, для буквы «A» комбинация бит выглядит как 01000001, для «*» - 00101010.

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

Требование контроля на четность заключается в том, что количество включенных битов в байте всегда должно быть нечетно. Контрольный бит буквы «A» будет иметь значение «1», для «*» - «0». Когда команда обращается к байту в памяти, компьютер проверяет этот байт. Если количество включенных битов четно, система выдаёт сообщение об ошибке. Ошибка четности может быть результатом сбоя оборудования.

По соглашению биты в байте пронумерованы от 0 до 7 справа налево.

Для измерения больших объемов информации используются следующие единицы измерения:

2байт=1 Кбайт;

2Кбайт=1 Мбайт;

2Мбайт=1 Гбайт;

2Гбайт=1 Тбайт.

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

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

Восьмибитовый ASCII-код обеспечивает представление 256 символов, включая символы для национальных алфавитов.

Так как ЭВМ различает только нулевое и единичное состояние бита, то она работает в системе счисления с основанием 2, или в двоичной системе.

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

Биты: 1 1 1 1 1 1 1 1

Вес:128 64 32 16 8 4 2 1 .

Общая сумма произведений битов на соответствующий вес составит десятичный эквивалент двоичного числа, например:

01100011=11+12+0+0=99.

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

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

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

0+0=0; 0+1=1; 1+1=102; 1+1+1=112.

Например, 00111100 60

00110101 53

01110001 113

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

Для нахождения двоичных дополнений нужно инвертировать все биты и к полученному числу прибавить 1.

Например, 00111100 = 60 11000011

1

110001000 = -60

Чтобы вычесть двоичные числа, нужно заменить вычитаемое на его двоичное дополнение и сложить с уменьшаемым.

Например, 00111100 – 00110101 = 60 – 53 = 7

00111100 11001010

11001011 1

(1)00000111 = 7 11001011

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

Для этого надо разделить каждый байт пополам и каждые 4 бита (тетрада) выразить соответствующими значениями.

b: 0101 1001 0011 0101 1011 1010 1100 1110

d: 5 9 3 5 11 10 12 14

Так как для некоторых чисел требуется две цифры, то расширим десятичную систему счисления: 10=А, 11=В, 12=С, 13=D, 14=E, 15=F. Получаем:

59 35 ВА СЕ

Такая система счисления включает цифры от 0 до F и называется шестнадцатеричной.

Шестнадцатеричный формат нашёл большое применение в ассемблере.

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

При сложении в 16-ричном формате верно правило:

F+1=1016

Например, EF15 _BCD8

C1E8 5EF4

1B0FD 5DE4

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

Для индикации шестнадцатеричного числа в ассемблерной программе непосредственно после числа ставится символ h (например: 25h).

Шестнадцатеричное число всегда начинается с десятичной цифры от 0 до 9, таким образом, B8h записывается как 0B8h.

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

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

Имеется три главных сегмента:

  1. Сегмент кодов: содержит машинные команды, которые будут выполняться. Операционная система передаёт управление по адресу данного сегмента для выполнения программы. Регистр сегмента кодов CS адресует данный сегмент.

  2. Сегмент данных: содержит данные, константы и рабочие области, необходимые в программе. Регистр сегмента данных DS адресует данный сегмент.

  3. Сегмент стека: стек содержит адреса возврата как для программы при возврате в операционную систему, так и для вызовов подпрограмм при возврате в главную программу. Регистр сегмента стека SS адресует данный сегмент.

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

Если программе недостаточно одного сегмента данных, то она имеет возможность использовать ещё три дополнительных сегмента данных. Адреса дополнительных сегментов данных должны содержаться в регистрах es, gs, fs.

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

Двухбайтовое смещение может быть в приделах от 0000h до ffffh (0-65535). Для обращения к любому адресу в программе компьютер складывает адрес в регистре сегмента и смещение. Например, первый байт в сегменте кода имеет смещение 0; второй байт - 1 и т.д. до смещения 65535.

Например, пусть регистр сегмента данных ds содержит значения 045fh и некоторая команда обращается к ячейке памяти внутри сегмента данных со смещением 0032h. Несмотря на то, что регистр сегмента данных содержит 045fh, на шину адреса подается число 045f0h, т.е. адрес, кратный 16. Реальный адрес искомой ячейки памяти будет:

Адрес в ds: 045f0h

Смещение: 0032h

Реальный адрес: 04622h

Каким образом процессоры 086/088 адресуют память в 1 млн. байт?

В регистре содержатся 16 бит. Так как адрес сегмента всегда на границе параграфа, младшие 4 бита адреса равны 0, ffffh позволяет адресовать до 65520 (плюс смещение) байт. Было решено, что нет смысла иметь место для битов, которые всегда равны 0. Поэтому адрес хранится в сегментном регистре, как nnnnh, а ЭВМ полагает, что имеются ещё 4 нулевых младших бита (одна шестнадцатеричная цифра), т.е. nnnn0h. Таким образом, ffff0h позволяет адресовать до 1.048.560 байт.

Процессор i286 использует 24 бита для адресации так, что fffff0h позволяет адресовать до 16 млн. байт, процессор i386 может адресовать до 4 млрд. байт.

Для управления выполняющейся программой, адресации памяти и арифметических вычислений процессор имеет 16 регистров. Каждый регистр имеет собственное имя. Биты в регистрах нумеруются справа налево, начиная с нулевого.

Существует шесть шестнадцатиразрядных сегментных регистра: cs, ds, ss, es, fs, gs:

  • cs - регистр сегмента кода содержит начальный адрес сегмента кода. Этот адрес и значение смещения в командном указателе ip определяет адрес команды, который должен быть выбран для выполнения.

  • ds - регистр сегмента данных содержит начальный адрес сегмента данных. Этот адрес и значение смещения, указанное в команде, указывает на конкретную ячейку в сегменте данных.

  • ss - регистр сегмента стека содержит начальный адрес сегмента стека.

  • es, fs, gs - дополнительные сегментные регистры, используются для некоторых операций над строками. Если необходимо использовать эти регистры, то они должны быть инициализированы в программе.

Регистры общего назначения:

Эти регистры размером в 32 бита делятся на две половины: старшая часть не имеет имени, не доступна как отдельный объект; младшая часть размером в 16 бит имеет имя и может использоваться как самостоятельный регистр. Левый байт младшей части имеет имя …h, правый байт - …l. Ссылки на регистр возможны по любому из этих имен.

1. eax / ax / ah / al;

15 0

ax

ah

al

31 7 0 7 0

2. ebx / bx / bh / bl;

3. ecx / cx / ch / cl;

4. edx / dx / dh / dl.

- eax – сумматор (используется для операций ввода – вывода, некоторых операций над строками и некоторых арифметических операций).

- ebx – базовый регистр (используется для вычислений и расширения адресации памяти).

- ecx – счетчик (необходим для организации циклов и операций сдвигов).

- edx – регистр данных (применяется для операций ввода – вывода, умножения и деления).

Регистр командного указателя eip/ip содержит смещение на команду, которая должна быть выполнена. В программе не применяется.

Регистровые указатели esp/sp, ebp/bp обеспечивают системе доступ к данным в сегменте стека, реже используются для вычислений.

  • sp – указатель стека, обеспечивает использование стека в памяти, позволяет временно хранить адреса, связан с регистром ss.

  • bp - указатель базы, облегчает доступ к параметрам – данным и адресам, переданным через стек.

Индексные регистры si и di могут применяться для расширенной адресации, в операциях сложения и вычитания, строковых операциях.

  • esi/si – индекс источника, содержит смещение текущего элемента в цепочке-источнике,

  • edi/di – индекс приемника, содержит смещение текущего элемента в цепочке-приемнике.

Флаговый регистр eflags/flags состоит из 32/16 бит, которые называются флагами и определяют текущее состояние результатов выполнения программы:

  • cf – флаг переноса – содержит значение переноса из старшего бита при арифметических операциях;

  • pf – флаг четности – показывает количество единичных бит (1 – четное, 0 – нечетное);

  • af – вспомогательный флаг переноса – содержит перенос из младшей тетрады в старшую;

  • zf - флаг нуля – устанавливается в 1, если результат арифметической операции равен 0, сбрасывается в 0, если результат ненулевой;

  • sf – флаг знака – принимает значение знакового разряда регистра;

  • tf – флаг трассировки – устанавливается в 1 при пошаговом выполнении программы;

  • if – флаг прерывания – указывает на возможность внешних прерываний (1 – прерывания разрешены, 0 – запрещены);

  • df – флаг направления – обозначает левое (0) или правое (1) направление пересылки или сравнении строковых данных;

  • of – флаг переполнения – указывает на переполнение старшего бита при арифметических операциях.

1.2 Подготовка ассемблерной программы к выполнению

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

Основной формат кодирования команд имеет вид:

[метка] команда [операнды]

Метка (если имеется), команда и операнд (если имеется) разделяются, по крайней мере, одним пробелом или символом табуляции. Максимальная длинна строки – 132 символа.

Метка может содержать следующие символы:

  1. Буквы от a до z.

  2. Цифры от 9 до 0.

  3. Специальные символы: ? , @ , _ , $ , . (если не первый символ).

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

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

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

Команда может иметь один или два операнда, или быть без операндов.

Например, a db 0 ; определен байт с именем а и нулевым значением

RET ; вернуться (без операндов)

inc cx ; увеличить сх на 1 (1 операнд)

add ax, 12 ; прибавить 12 к ах (2 операнда)

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

1. Директивы управления листингом PAGE и TITLE.

PAGE указывает количество строк на странице и символов в строке. Количество строк на странице может быть в пределах от 10 до 255, а символов в строке – от 60 до132. По умолчанию устанавливается PAGE 66,80.

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

title <текст>

2. Директива SEGMENT: используется для определения сегментов. Каждый сегмент должен иметь уникальное имя. Директива SEGMENT может содержать 3 типа параметров:

а) выравнивание: определяет границу начала сегмента, обычным значением является PARA, по которому сегмент устанавливается на границу параграфа. В случае отсутствия этого параметра значение PARA принимается по умолчанию;

б) объединение: определяет, объединяется ли данный сегмент с другими сегментами в процессе компоновки;

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

Например,

codesg segment para

3. Директива PROC: используется для определения процедур. Каждая процедура должна иметь имя. Основная процедура в программе должна иметь параметр far, который указывает, что имя этой процедуры является точкой входа для выполнения программы.

Например,

begin proc far

Если процедура не является основной, то она имеет параметр near:

Например,

c10 proc near

4. Директива ASSUME: устанавливает назначение каждого сегмента, т.е. соответствие между сегментными регистрами и именами сегментов.

Например,

ASSUME cs:codesg, ds:datasg, ss:stacksg

5. Директива END: завершает программу и должна содержать имя точки входа в программу:

Например,

end begin

Директива ENDP завершает процедуру, должна содержать имя этой процедуры.

Например,

с10 ENDP

Директива ENDS завершает сегмент, должна содержать имя сегмента.

Например,

codesg ENDS

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

mov ax, bx; пересылка из bx в ax

mov ax,w; пересылка из слова памяти в ax

mov ax, [bx]; пересылка содержимого памяти по адресу в регистре bx в регистр ax

mov ax,25; пересылка 25 в регистр ax

mov ax,[25]; пересылка содержимого со смещением 25

Система DOS имеет четыре требования для инициализации ассемблерной exe - программы:

  1. Указать ассемблеру, какие сегментные регистры должны соответствовать сегментам:

assume cs: codesg, ds: datasg, ss: stacksg

  1. Сохранить в стеке адрес, находящийся в регистре ds, когда программа начнёт выполнение:

push ds

  1. Записать в стек нулевой адрес:

sub ax, ax; вычитание ах из ах

push ax; помещение в стек значения ах

  1. Загрузить в регистр ds адрес сегмента данных:

mov ax, datasg

mov ds, ax

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

Текст программы вводится в текстовом редакторе. Затем необходимо выполнить ассемблирование и компоновку программы. Ассемблирование выполняется с помощью программы tasm или masm. Ассемблер преобразует исходные команды в машинный код и выдает на экран сообщение об ошибках. После исправления ошибок необходимо повторить ассемблирование. Если оно прошло без ошибок, то генерируется объектный модуль, файл с расширением obj; а так же lst, crf.

Файл с расширением obj содержит машинный код в шестнадцатеричной форме. Его компоновка осуществляется с помощью программы link или think. В результате получаем файлы с расширением exe, map, lib. Файл .exe является исполнительным. Чтобы исправить некоторые действия программы, надо отредактировать исходный модуль .asm. Затем снова выполнить ассемблирование и компоновку. Файл с расширением lst используется для просмотра листинга ассемблированной программы. А crf-файл содержит ссылки на поля данных. Map-файл содержит таблицу имен и размеров сегментов, а также ошибки, которые обнаружит link. Lib-файл относится к библиотечным средствам.

Сегмент данных предназначен для определения констант, рабочих полей и областей ввода-вывода. В ассемблере разрешено определение данных следующей длинны: DB - байт, DW - слово, DD - двойное слово, DQ - учетверенное слово, DT - 10 байт.

Формат директивы данных:

[имя] Dn <выражение>

Имя может отсутствовать, но если в программе есть ссылки на некоторый элемент, то это делается посредством имени. Выражение может содержать константу или «?» для неопределенного значения.

Напрмер, a db 25;

b dw ?

Выражение может содержать несколько констант, разделенных запятой:

C db 11,12,13,14

Ассемблер определяет эти константы в виде последовательности смежных байтов. Ссылка по имени С указывает на первую константу - 11, С+1 - на вторую - 12 и т.д. Выражение допускает повторение константы в следующем формате:

[имя] Dn число повторений DUP(выражение)

Например, d dw 10 dup (?); 10 неопределенных слов

e db 5 dup (14); 5 байт, содержащих 14.

Символьная строка определяется только директивой db, в апострофах записывается сама строка:

Например, f db ‘ primer’

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

Например, name equ 10.

Ассемблирование и компоновка программы приводит к созданию exe-файла. Компоновщик Link автоматически генерирует особый формат для exe-файлов, в котором присутствует начальный блок размером не менее 512 байт. Для выполнения программ можно создавать com-файлы. Программа exe2bin.com в Dos преобразует exe-файлы в com-файлы.

Различия между программами в exe- и com-файлах.

  1. Размер программы: программа в формате exe- может иметь любой размер; com-файл ограничен размером одного сегмента и не превышает 64 Кбайт. Размер com-файла меньше, чем размер соответствующего exe-файла из-за отсутствия в com-файле 512-байтового заголовка exe-файла.

  2. Сегмент стека: в exe-программе определяется сегмент стека, com-программа генерирует его автоматически, т.е. при создании программы для com-формата стек не определяется.

  3. Сегмент данных: в exe-программе определяется сегмент данных, а регистр ds инициализируется адресом этого сегмента. В com-программе все данные должны быть определены в сегменте кода.

  4. Инициализация: в exe-программе выполняются запись нулевого слова в стек и инициализация регистра ds. Так как в com-программе стек и сегмент данных не определены, то эти шаги отсутствуют. Когда com-программа начинает выполнение, все сегментные регистры содержат адрес префикса программного сегмента PSP – 256-байтовый (100h) блок, который резервируется операционной системой Dos непосредственно перед com- или exe-программой в памяти. Так как адресация начинается со смещения 100h от начала PSP, то в программе после оператора segment, кодируется директива org 100h.

  5. Обработка: для программ в exe- и com-форматах выполняются ассемблирование для получения obj-файла и компоновка для получения exe-файла. Если программа создаётся для выполнения как exe-файл, то её уже можно выполнить. Если же программа создаётся для выполнения как com-файл, то компоновщиком будет выдано сообщение:

Warning: no stack segment

Это сообщение можно игнорировать, так как определение стека в программе не предполагалось. Для преобразования exe-файла в com-файл используется программа exe2bin.

1.3 Экранные операции

Большинство программ требуют ввода данных с клавиатуры и обеспечивают вывод данных на экран. Данные, предназначенные для вывода на экран и ввода с клавиатуры, имеют ASCII – формат.

Для выполнения ввода и вывода используется команда int, осуществляющая прерывание в программе. Она передает управление в DOS или BIOS для определенного действия и затем возвращает управление в прерванную программу для продолжения обработки по следующему алгоритму:

  1. уменьшает указатель стека на 2 и заносит в вершину стека содержимое флагового регистра;

  2. сбрасывает флаги tf и if.

  3. уменьшает указатель стека на 2 и заносит содержимое регистра cs в стек.

  4. уменьшает указатель стека на 2 и заносит в стек значение командного указателя.

  5. выполняются необходимые действия.

  6. восстанавливаются из стека значения регистров и возвращается управление в прерванную программу на команду, следующей после int.

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

1. Очистка экрана

Перед выполнением программы можно использовать очистку экрана. Экран можно представить в виде двумерного пространства с адресуемыми позициями. Верхний левый угол – 0000h, нижний правый угол – 184Fh. Очищаемая область может начинаться в любой позиции и заканчивается в позиции с большим номером. Начальное значение строки и столбца заносится в регистр CX, конечное в DX.

Например, очистка всего экрана:

mov ax,0600h; номер функции очистки экрана

mov bh, 07; черно-белый экран

mov cx, 0000h; верхний левый угол

mov dx, 184Fh; нижний правый угол

int 10h; передача управления в BIOS

2. Установка курсора

Чтобы вывести на экран символы в определенной позиции, необходимо установить курсор в заданное положение.

Например, установить курсор в 5-ю строку и 12-й столбец:

mov ah,02; номер функции установки курсора

mov bh,0; номер экрана

mov dh,05; номер строки

mov dl,12; номер столбца

int 10h; передача управления в BIOS

Для установки строки и столбца можно использовать команду:

mov dx, 050Ch.

3. Вывод символов на экран

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

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

В регистр ah заносится значение 09 – номер функции вывода на экран из памяти, в регистр dх – адрес выводимой области, затем следует команда для DOS int 21h.

Например,

n db ‘Укажите число’,’$’

…………

Mov ah, 09; номер функции вывода на экран из памяти

Lea dx, n; загрузка адреса сообщения

int 21h

Команда Lea загружает адрес области n в регистр dx для передачи в DOS адреса выводимой информации. Он является относительным, поэтому для вычисления абсолютного адреса данных DOS складывает ds и dx.

Для прямого вывода на экран одного символа, его ASCII – код помещают в регистр dl, номер функции вывода 02 или 06 в регистр ah и выполняют прерывание int 21h.

Например, вывод символа «А»

mov ah,02

mov dl,’A’

int 21h

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

Например, вывести на экран из регистра al цифру «7»:

mov dl,al; пересылаем содержимое al в dl

add dl,30h; прибавляем к значению dl число 30h, чтобы получить ASCII-код выводимой цифры

mov ah,02; номер функции вывода на экран из dl

int 21h; прерывание DOS

4. Ввод символов с клавиатуры

При вводе данных с клавиатуры в память должен быть определен список параметров. Он включает в себя максимальную длину вводимого текста, фактическую длину текста и область памяти для вводимых символов. Список параметров находится в сегменте данных. Для запроса на ввод в сегменте кода в регистр ah загружаем номер функции – 0Ah, адрес списка параметров в регистр dx и выполняем прерывание int 21h.

Например,

n label byte; директива списка параметров с атрибутом byte

m db 20; макс. длина текста

l db ?; фактическая длина, значение которой изначально неизвестно

s db 20 dup (‘ ’); область памяти для хранения введенных символов

…………………..

mov ah, 0ah; запрос функции ввода

lea dx, n; загрузка адресов списка параметров

int 21h

Для прямого ввода в регистр необходимо в ah занести номер функции 01 и выполнить прерывание int 21h. Введенное с клавиатуры значение будет помещено в регистр al в ASCII-формате. Если это числовое значение, то для выполнения с ним арифметических действий надо предварительно перевести его из ASCII-кода, отняв 30h.

1.4 Арифметические операции

Команды сложения и вычитания применяются для байтов, слов или двойных слов.

Вычитание осуществляется по методу сложения с двоичным дополнением.

Возможны следующие варианты:

  • сложение/вычитание регистр – регистр;

  • сложение/вычитание регистр – память;

  • сложение/вычитание регистр - непосредственное значение;

  • сложение/вычитание память - непосредственное значение.

Прямой операции память – память не существует. Для этого значение одной из переменной помещают в регистр.

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

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

  1. inc <операнд> - увеличивает значение операнда на 1.

  2. add <операнд1>,<операнд2> - складывает значение операндов, результат помещается в операнд 1.

  3. adc <операнд1>,<операнд2> - сложение операндов с учетом флага переноса: операнд1 = операнд 1 + операнд 2 + cf.

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

  1. dec <операнд 1> - уменьшает значение операнда на 1.

  2. sub <операнд 1>, <операнд 2> - вычитает из операнда 1 операнд 2, результат помещается в операнд 1.

  3. sbb <операнд 1>, <операнд 2> - вычитание с учетом заёма, операнд1 = операнд 1 - операнд 2 - cf.

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

mul <множитель 1> - умножение двоичных чисел без знака. Множитель 1 находится в памяти или регистре. Второй множитель задается неявно. Его место фиксировано и зависит от размеров операндов.

Множитель 1

Множитель 2

Результат

Байт

al

16 бит в ax:

ah– старшая часть

dl – младшая часть

Слово

ax

32 бит в dx:ax:

dx– старшая часть

ax– младшая часть

Двойное слово

eax

64 бит в edx:eax:

edx– старшая часть

eax- младшая часть

Если результат мал, и уместится в младшей части, то старшая часть обнулится и флаги cf=0, of=0.

Если эти флаги ненулевые, то результат вышел за пределы младшей части в старшую часть регистра.

imul операнд 1,[операнд2, операнд3].

Эта команда работает аналогично команде mul, но учитывает знак результата.

Если результат imul умещается в одном регистре (cf=of=0), то содержимое старшей части результата является расширением знака, т.е. все биты равны знаковому биту младшей части результата.

Иначе (cf=of=1) знаком результата будет знаковый бит старшей части, а знаковый бит младшей части будет значащим битом двоичного кода.

В команде с двумя операндами первый операнд является первым множителем, второй операнд - вторым множителем; результат помещается в операнд 1.

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

Команды деления

div <делитель> - деление чисел без знака.

Делитель может находиться в памяти или регистре и иметь размер 8, 16 или 32 бит.

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

Результатом деления является частное и остаток.

Делимое

Делитель

Частное

Остаток

16 битов в ax

8-битный регистр или ячейка памяти

8 бит в al

8 бит вah

32 бита в dx:ax

16- битный регистр или ячейка памяти

16бит вax

16бит вdx

64 бита в edx:eax

32- битный регистр или ячейка памяти

32бит вeax

32бит вedx

При делении возможно прерывания деления на 0. Оно возникает, если:

  1. делитель равен 0.

  2. при делении слова на байт, если делимое более чем в 256 раз больше делителя.

  3. при делении двоичного слова на слово, если делимое более чем в 65536 раз больше делимого.

  4. при делении учетверенного слова на двойное слово, если делимое более чем в 232 раз больше делителя.

idiv <делитель> - деление чисел со знаком.

Эта команда работает аналогично команде div.

Прерывание деления на 0 возникает в пределах в 2 раза меньших по модулю, чем для div.

Команда neg <операнд> меняет знак операнда, т.е. инвертирует все биты операнда и прибавляет единицу.

Команда cmp <операнд1>,<операнд2> сравнивает операнд1 с операндом2. Возможны сравнения:

- регистр – регистр;

- регистр – память;

- регистр – непосредственное значение;

- память – непосредственное значение.

Сравнение происходит на основе вычитания из первого операнда значения второго операнда.Сами операнды не изменяются. Воздействует на флаги: af, cf, of, sf, pf, zf.

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

Например, 23 - десятичный формат;

00010111b - двоичный формат;

0203h – двоично-десятичный формат;

3233h – ASCII-формат.

В двоично-десятичном коде и коде -ASCII на каждый символ требуется один байт.

Команды коррекции арифметических операций над двоично-десятичными числами

1.ааа – коррекция для сложения;

2.ааs – коррекция вычитания;

3.ааm – коррекция умножения;

4.ааd – коррекция деления.

Эти команды кодируются без операндов и выполняют автоматическую коррекцию в регистре ax.

Например, +8 +08h

4 04h

12 0ch - неверно ни для двоично-десятичного, ни для десятичного формата.

Пусть Ax=0008h, Вх=0004h

аdd al,bl; результат в ах = 000сh

аaa; коррекция Ax = 0102h.

Команда ааа проверяет правую шестнадцатеричную цифру (4бит) в AL.

Если она находится между 0Аh и 0Fh, то флаг АF устанавливается в 1, и к AL прибавляется 6, а к ah - 1, левая шестнадцатеричная цифра al обнуляется.

Получим двоично-десятичный формат суммы, для представления ее в ASCII- кодах надо прибавить к Ax 3030h.

Это применяется для однозначных чисел. Сложение многобайтовых ASCII-чисел требует организации цикла, который выполняет обработку слагаемых справа налево с учетом переноса.

Коррекция вычитания проводится аналогично.

Команда aam делит содержимое регистра al, на 10 и записывает частное в ah, а остаток – в al.

Пусть в al находится 05h, в cl - 09h.

mul cl; 9*5=45=2dh

aam; преобразует 2dh в 0405h

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

Команда aad выполняет корректировку двоично-десятичного кода делимого до непосредственного деления.

Например, пусть аx = 0208h, cl = 07h

mov ax,0208h

mov cl,07h

aad; преобразует 0208h в 28

div cl; делит 28 на 7.

Двоично-десятичный формат может быть упакованным и распакованным.

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

Например, 0207h=>27d.

В упакованном формате в одном байте кодируется 2 цифры от 0до 9. Старшая цифра - в старшей тетраде, младшая цифра - в младшей тетраде.

Сложение и вычитание можно выполнять и в упакованном формате. Для коррекции служат команды:

daa – коррекция сложения;

das – коррекция вычитания.

Обработка полей осуществляется также по одному байту за одно выполнение.

Арифметические операции над числами в ASCII и bcd-форматах удобны лишь для коротких полей. Чаще для арифметических операций используется преобразование в двоичный формат.

Для преобразования ASCII-формата в двоичный формат выполняют следующие действия:

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

2. умножают оставшиеся ASCII-цифры на 1,10,100 и т.д. и складывают результаты.

Для преобразования двоичного формата в ASCII-формат выполняют следующее:

1. делят двоичное число на степени числа 10;

2. к каждой полученной цифре прибавляют 30h.

1.5 Логика и организация программы

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

Существует 4 способа передачи управления:

  1. безусловный переход jmp.

  2. Цикл: loop.

  3. Условный переход: jnnn.

  4. Вызов процедуры: call.

Существует три типа адресов: short, near, far.

Адресация short используется в циклах, условных переходах и некоторых безусловных переходах.

Адресация near и far применяется для вызова процедур и безусловных переходах, не являющихся типа short. Все три типа передачи управления воздействуют на содержимое регистра ip; тип far также изменяет регистр cs.

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

Например, mov ax,01; пересылка в ax 01.

mov bx,01; пересылка в bx 01.

a20:

add ax,01; прибавить к ax 01.

add bx,ax; прибавить к bx ax.

jmp a20; переход на метку а20

Это пример бесконечного цикла. Адрес a20 указывает на первый байт команды add. Двоеточие в метке a20 указывает на тип метки - near.

Отсутствие двоеточия в метке является ошибкой. В примере a20 соответствует – семи (-7) байтам от команды jmp, т.к. осуществляется переход назад. Команда jmp прибавляет (-7) к значению командного указателя eip, который содержит адрес команды после jmp.

Команда jmp для перехода в пределах от -128 до+127 байт имеет тип short. Ассемблер генерирует в этом случае однобайтовый операнд в пределах от 00 до FF.

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

Ассемблер в первом просмотре исходной программы определяет длину каждой команды. При переходе назад для команды jmp сразу вычисляется значение операнда, и если переход – короткий, генерируется двухбайтовая команда (1 байт - jmp, 1 байт - операнд). При переходе вперёд в первом просмотре ассемблер не может вычислить значение операнда и автоматически генерирует трёхбайтовую команду (1 байт - jmp, 2 байт - операнд).

Если необходимо указать на короткий тип перехода, следует использовать оператор short:

jmp short a1

…………

a1: …….

  1. Команда loop используется для организации цикла. Количество повторений предварительно помещается в регистр cx. Оно автоматически уменьшается при выполнении каждого цикла. Пока cx0 управление передаётся по адресу, указанному в операнде. Если cx=0, то будет выполняться следующий после команды loop оператор.

mov cx,10

mov ax,01

mov bx,01

a20:

add ax,01

add bx,ax

loop a20

Операнд команды loop определяет расстояние от конца команды loop до адреса метки a20, которое прибавляется к содержимому командного указателя. Для loop это расстояние должно быть в пределах от -128 до +127. Если операнд превышает эти пределы. то выдаётся сообщение “relative jump out of range”.

Дополнительно существуют две разновидности команды loop: loope и loopne. Обе команды уменьшают cx на 1. loope передаёт управление по адресу операнда, если cx>0 и zf=1. loopne передаёт управление по адресу операнда, если cx>0 и zf=0.

  1. Переходы для беззнаковых и знаковых данных:

  1. je/jz – переход, если равно/переход, если нуль. Команда передаёт управление по адресу, указанному в метке, если zf=1, иначе будет выполняться следующая за переходом команда. Переход осуществляется в диапазоне от -128 до +127.

  2. jne/jnz – переход по неравенству/переход, если не нуль. Переход выполняется, если zf=0.

Переходы для беззнаковых данных:

  1. ja/jnbe – переход, если больше/переход, если не меньше и не равно. Переход выполняется, если cf=zf=0.

  2. jae/jnb–переход, если >=/переход, если не <. Переход осуществляется, если cf=0.

  3. jb/jnae – переход, если </переход, если не больше и не равно. Переход осуществляется при cf=1.

  4. jbe/jna–переход, если <=/переход, если не >. Переход осуществляется, если cf=1 или zf=1.

Переход для знаковых данных:

  1. jg/jnle–переход, если >/переход, если не <=. Переход осуществляется если sf=of и zf=0.

  2. jge/jnl–переход, если >=/переход, если не <. Переход осуществляется если sf=of.

  3. jl/jnge–переход, если </переход, если не >=. Переход осуществляется, если sf=of.

  4. jle/jng – переход, если <=/переход, если не >. Преход осуществляется, если sf=of, zf=1.

Специальные арифметические проверки:

  1. js –переход, если sf=1

  2. jns – переход, если sf=0

  3. jc – переход, если cf=1

  4. jnc – переход, если cf=0

  5. jo – переход, если of=1

  6. jno – переход, если of=0

  7. jp – переход, если pf=1

  8. jnp – переход, если pf=0

  9. jcхz – переход, если cx=0

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

Типичная организация многопроцедурной программы:

Директивы proc по меткам p1 и p2 имеют операнд near для указания того, что эти процедуры находятся в текущем кодовом сегменте. При отсутствии этого операнда ассемблер принимает его по умолчанию.

Для передачи управления вспомогательным процедурам в основной процедуре begin есть команды call p1 и call p2. В первом случае управление передаётся процедуре p1 и начинается её выполнение. Достигнув команды ret, управление возвращается на команду, следующую за командой call p1. Если бы процедура p1 не имела бы команды ret, то выполнение команд продолжится из p1 непосредственно в процедуре p2, пока не встретиться команда ret. Операнды для команды call могут иметь значения, выходящие за границу -128 +127 байт. (+1+127-01h до 7fh;-128-1-ffh до 80h)

При организации программы на стек сначала воздействуют две команды push.

Команда call автоматически записывает в стек относительный адрес команды, следующий непосредственно за call. А команда ret использует этот адрес для возврата в вызывающую процедуру.

Стек работает по принципу: “последний записанный в стек адрес будет считываться первым”. Поэтому первый адрес из регистра ds будет записан в самое старшее слово.

Команда call помещая в стек адрес для возврата, будет уменьшать указатель вершины стека, а команда ret, забирая адрес для возврата, увеличивает указатель вершины стека.

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

1. AND <операнд1>, <операнд2> - логическое умножение: результирующий бит равен 1, если оба соответствующих битов операндов равны 1. операнд1 может быть регистром или ячейкой памяти, а операнд 2 может быть задан непосредственным **словым значением. Результат помещается в операнд 1.

2. OR <операнд1>, <операнд2> - логическое сложение: результирующий бит равен 1, ели хотя бы 1 из проверяемых битов равен 1.

3. XOR <операнд1>, <операнд2>, - исключающие или: результирующий бит равен 1, если проверяемые биты не равны между собой.

4. TEST <операнд1>, <операнд2> - работает как and, но не изменяет значения операндов, а устанавливает флаги.

5. NOT <операнд1>, <операнд2>, - инвертирует биты в регистре или памяти not не эквивалентна команде neg, которая меняет значение операнда с положительного на отрицательное и наоборот посредством инвертирования битов и прибавления 1.

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

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

- обрабатывают байт, слово или двойное слово в регистре или памяти;

- сдвигают вправо или влево на количество бит, закодированное в регистре сх;

- сдвигают линейно или циклически с сохранением знака или без.

Значение сдвига на один бит можно кодировать непосредственно числом.

При выполнении сдвига флаг сf всегда содержит значение последнего выдвинутого бита.

Команды линейного сдвига

  1. shl – линейный сдвиг влево без учета знака (логический).

  2. shr – линейный сдвиг вправо без учета знака.

  3. sal – арифметический линейный сдвиг влево

  4. sar – арифметический линейный сдвиг вправо (восстанавливает знаковый бит после каждого сдвига).

На освободившиеся позиции записываются 0.

Команды циклического сдвига

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

  1. rol – цикл. сдвиг влево.

  2. ror – цикл. сдвиг вправо.

  3. rcl – цикл. сдвиг влево через флаг переноса.

  4. rcr – цикл. сдвиг вправо через флаг переноса.

Сдвиг операнда влево на 1 бит аналогично умножению на 2, сдвиг вправо – деление на 2.