Ассемблер. Арифметические инструкции
Обновл. 16 Сен 2021 |
На этом уроке мы будем разбираться с арифметическими инструкциями в ассемблере на примере INC, DEC, ADD, SUB и пр.
Инструкция INC
Инструкция INC (от англ. «INCREMENT») используется для увеличения операнда на единицу. Она работает с одним операндом, который может находиться либо в регистре, либо в памяти.
Синтаксис инструкции INC:
Операндом место_назначения может быть 8-битный, 16-битный или 32-битный операнд.
Инструкция DEC
Инструкция DEC (от англ. «DECREMENT») используется для уменьшения операнда на единицу. Она работает с одним операндом, который может находиться либо в регистре, либо в памяти.
Синтаксис инструкции DEC:
Операндом место_назначения может быть 8-битный, 16-битный или 32-битный операнд.
Инструкции ADD и SUB
Инструкции ADD и SUB используются для выполнения простого сложения/вычитания двоичных данных размером один byte, word или doubleword, то есть для сложения или вычитания 8-битных, 16-битных или 32-битных операндов, соответственно.
Синтаксис инструкций ADD и SUB:
ADD/SUB место_назначения, источник
Инструкции ADD/SUB могут выполняться между:
регистром и регистром;
памятью и регистром;
регистром и памятью;
памятью и константами.
Однако, как и для других инструкций, операции типа память-в-память невозможны с использованием инструкций ADD/SUB. Операции ADD или SUB устанавливают или сбрасывают флаги переполнения и переноса.
В следующем примере мы спрашиваем у пользователя два числа, сохраняем их в регистрах EAX и EBX, затем выполняем операцию сложения, сохраняем результат в ячейке памяти res и выводим его на экран:
Результат выполнения программы:
Enter a digit:
3
Please enter a second digit:
4
The sum is:
7
Ниже рассмотрен пример, в котором за счет того, что значения переменных для арифметических выражений прописаны в самом коде программы, можно получить код программы короче и проще:
Результат выполнения программы:
Инструкции MUL и IMUL
Есть 2 инструкции для умножения двоичных данных:
инструкция MUL (от англ. «MULTIPLY») обрабатывает данные unsigned;
инструкция IMUL (от англ. «INTEGER MULTIPLY») обрабатывает данные signed.
Обе инструкции влияют на флаги переноса и переполнения.
Синтаксис инструкций MUL/IMUL:
Множимое в обоих случаях будет находиться в аккумуляторе, в зависимости от размера множимого и множителя, и результат умножения также сохраняется в двух регистрах, в зависимости от размера операндов.
Рассмотрим 3 разных сценария:
Сценарий №1: Перемножаются 2 значения типа byte. Множимое находится в регистре AL, а множителем является значение типа byte в памяти или в другом регистре. Результат произведения находится в AX. Старшие 8 бит произведения хранятся в AH, а младшие 8 бит хранятся в AL:
Сценарий №3: Перемножаются 2 значения типа doubleword. Множимое должно находиться в EAX, а множителем является значение типа doubleword, хранящееся в памяти или в другом регистре. Результат умножения сохраняется в регистрах EDX и EAX. Биты старшего порядка сохраняются в регистре EDX, а биты младшего порядка сохраняются в регистре EAX:
Система команд x86
Влияние команды на флаги и форматы команды:
Вычитание imm8 из регистра AL
Вычитание imm16 из регистра AX
Вычитание imm32 из регистра EAX
Вычитание imm16 из r/m16
Вычитание imm32 из r/m32
Вычитание знакорасширенного imm8 из r/m16
Вычитание знакорасширенного imm8 из r/m32
Вычитание байтового регистра из r/m8
Вычитание 16-битного регистра из r/m16
Вычитание 32-битного регистра из r/m32
Вычитание r/m8 из байтового регистра
Вычитание r/m16 из 16-битного регистра
Вычитание r/m32 из 32-битного регистра
Описание:
Команда SUB (Subtract) относится к группе команд целочисленной (или двоичной) арифметики (Binary Arithmetic Instructions) и производит целочисленное вычитание, вычитая из первого операнда (DEST) второй операнд (SRC) (операнды могут быть знаковыми или беззнаковыми). Первый операнд (операнд-назначение, DEST) может быть переменной в регистре или в памяти (r8, r16, r32, r/m8, r/m16, r/m32). Второй операнд (операнд-источник, SRC) — непосредственным значением (imm8, imm16, imm32), переменной в регистре или в памяти. При этом оба операнда одновременно не могут быть переменными в памяти.
Результат вычитания командой SUB помещается на место первого операнда (DEST). Флаги в регистре EFLAGS устанавливаются в соответствии с полученным результатом.
При вычитании непосредственного значения imm8 или imm16 из двухбайтного или четырехбайтного операнда-источника непосредственная величина прежде всего знакорасширяется до размера первого операнда, и только после этого выполняется вычитание.
Команда SUB позволяет манипулировать целочисленными операндами как в беззнаковом формате, так и в формате со знаком. При вычитании данных со знаком флаг знака EFLAGS.SF будет отражать знак полученного результата. Флаг переполнения EFLAGS.OF установится в 1, если при вычитании целочисленных значений со знаком, представленных в обратном коде или в дополнительном коде, произошло переполнение (заем в старший значащий разряд, которому соответствует бит, предшествующий разряду знака), то есть полученный результат превышает доступный размер операнда-назначения (DEST). По сути, это аналогично тому, как флаг EFLAGS.CF отражает переполнение (заем) при вычитании беззнаковых операндов. Например, при вычитании двух 32-битных значений, представленных в дополнительном коде, это может выглядеть следующим образом:
mov eax, operand1 ; EAX = operand1, первое 32-битное слагаемое помещаем в EAX
sub eax, operand2 ; производим вычитание двух 32-битных операндов в дополнительном коде
into ; переход к обработчику прерывания в случае переполнения
Флаг вспомогательного (или дополнительного) переноса EFLAGS.AF помогает манипулировать данными в двоично-десятичном формате (упакованный BCD-формат). Он устанавливается, если при вычитании возникает заем в младшую тетраду из старшей тетрады младшего байта результата. Используя команду DAS сразу же вслед за командой SUB, можно произвести так называемую десятичную коррекцию результата вычитания и получить разность в таком же упакованном BCD-формате, как и исходные операнды.
Команда SUB с операндом-назначением (DEST), являющимся переменной в памяти, может использоваться совместно с префиксом блокировки LOCK, который обеспечит атомарное исполнение команды.
Операция:
IF (разрядность SRC меньше разрядности DEST)
Особые ситуации защищенного режима:
#GP(0), если операнд-назначение (DEST) находится в памяти в сегменте, запрещенном для записи.
#GP(0), если при обращении к операнду в памяти в сегменте DS, ES, FS или GS используется нулевой селектор.
#GP(0), если любая часть операнда в памяти находится вне допустимого пространства эффективных адресов в сегменте CS, DS, ES, FS или GS.
#SS(0), если любая часть операнда в памяти находится вне допустимого пространства эффективных адресов в стековом сегменте SS.
Intel386 … :
#PF(Код ошибки) при страничной ошибке.
#UD при использовании префикса LOCK, если первый операнд команды (DEST) не является значением в памяти.
Intel486 … :
#AC(0) при невыровненной ссылке в память, если активирован контроль выравнивания (CR0.AM = 1, EFLAGS.AC = 1, CPL = 3).
Особые ситуации режима реальной адресации:
#GP, если любая часть операнда в памяти находится вне допустимого для реального режима пространства эффективных адресов в сегменте CS, DS, ES, FS или GS.
#SS, если любая часть операнда в памяти выходит за допустимую для реального режима верхнюю границу стекового сегмента SS.
Intel386 … :
#UD при использовании префикса LOCK, если первый операнд команды (DEST) не является значением в памяти.
Особые ситуации режима V86:
#GP(0), если любая часть операнда в памяти находится вне допустимого пространства эффективных адресов в сегменте CS, DS, ES, FS или GS.
#SS(0), если любая часть операнда в памяти находится вне допустимого пространства эффективных адресов в стековом сегменте SS.
Intel386 … :
#PF(Код ошибки) при страничной ошибке.
#UD при использовании префикса LOCK, если первый операнд команды (DEST) не является значением в памяти.
Intel486 … :
#AC(0) при невыровненной ссылке в память, если активирован контроль выравнивания (CR0.AM = 1, EFLAGS.AC = 1, CPL = 3).
Замечание:
К командам целочисленной арифметики относятся команды ADD, ADC, SUB, SBB, IMUL, MUL, IDIV, DIV, INC, DEC, NEG, CMP.
В свою очередь, сами названные команды целочисленной арифметики делятся на следующие подгруппы:
Sub assembler что это
7.1. Сложение и вычитание.
7.1.1. ADD – команда для сложения двух чисел. Она работает как с числами со знаком, так и без знака.
Логика работы команды:
По сути дела, это – команда сложения с присвоением, аналогичная принятой в языке C / C ++:
Операнды должны иметь одинаковый размер. Результат помещается на место первого операнда.
После выполнения команды изменяются флаги, по которым можно определить характеристики результата:
add dx,cx ;DX = DX + CX
add dx,cl ;Ошибка: разный размер операндов.
Логика работы команды:
По сути дела, это – команда вычитания с присвоением, аналогичная принятой в языке C / C ++:
Операнды должны иметь одинаковый размер. Результат помещается на место первого операнда.
На самом деле вычитание в процессоре реализовано с помощью сложения. Процессор меняет знак второго операнда на противоположный, а затем складывает два числа.
sub b x,cl ;Ошибка: разный размер операндов.
7.1.3. Инкремент и декремент. Очень часто в программах используется операция прибавления или вычитания единицы. Прибавление единицы называется инкрементом, а вычитание — декрементом. Для этих операций существуют специальные команды процессора: INC и DEC. Эти команды не изменяют значение флага CF.
Эти команды содержит один операнд и имеет следующий синтаксис:
Логика работы команд:
7.1.4. NEG – команда для изменения знака операнда.
Логика работы команды:
7.2. Сложение и вычитание с переносом.
В системе команд процессоров x86 имеются специальные команды сложения и вычитания с учётом флага переноса (CF). Для сложения с учётом переноса предназначена команда ADC, а для вычитания — SBB. В общем, эти команды работают почти так же, как ADD и SUB, единственное отличие в том, что к младшему разряду первого операнда прибавляется или вычитается дополнительно значение флага CF.
Они позволяют выполнять сложение и вычитание многобайтных целых чисел, длина которых больше, чем разрядность регистров процессора (в нашем случае 16 бит). Принцип программирования таких операций очень прост — длинные числа складываются (вычитаются) по частям. Младшие разряды складываются(вычитаются) с помощью обычных команд ADD и SUB, а затем последовательно складываются(вычитаются) более старшие части с помощью команд ADC и SBB. Так как эти команды учитывают перенос из старшего разряда, то мы можем быть уверены, что ни один бит не потеряется. Этот способ похож на сложение(вычитание) десятичных чисел в столбик.
На следующем рисунке показано сложение двух двоичных чисел командой ADD:
При сложении происходит перенос из 7-го разряда в 8-й, как раз на границе между байтами. Если мы будем складывать эти числа по частям командой ADD, то перенесённый бит потеряется и в результате мы получим ошибку. К счастью, перенос из старшего разряда всегда сохраняется в флаге CF. Чтобы прибавить этот перенесённый бит, достаточно применить команду ADC:
//Сложение двух чисел с учетом переноса: FFFFFFAA + FFFF
Команды ассемблера
Опкод add имеет следующий синтаксис:
Выполняет вычисление: приемник = приемник + источник.
Имеются также другие формы:
| приемник | источник | пример |
|---|---|---|
| регистр | регистр | add ecx, edx |
| регистр | память | add ecx, dword ptr [104h] / add ecx, [edx] |
| регистр | значение | add eax, 102 |
| память | значение | add dword ptr [401231h], 80 |
| память | регистр | add dword ptr [401231h], edx |
Эта команда очень проста. Она добавляет значение источника к значение приемника и помещает результат в приемник. Другие математические команды:
Поскольку регистры могут содержать только целочисленные значения (то есть числа, не, с плавающей запятой), результат деления разбит на частное и остаток. Теперь, в зависимости от размера источника, частное сохраняется в eax, а остаток в edx:
| размер источника | деление | частное в. | остаток в. |
|---|---|---|---|
| BYTE (8-bits) | ax / делитель | AL | AH |
| WORD (16-bits) | dx:ax* / делитель | AX | DX |
| DWORD (32-bits) | edx:eax* / делитель | EAX | EDX |
Источник операции деления может быть:
Источник не может быть непосредственным значением, потому что тогда процессор не сможет определить размер исходного операнда.
| команда | AND | OR | XOR | NOT | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Бит источника | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 1 |
| Бит приемника | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | X | X |
| Бит результата | 0 | 1 | 0 | 1 | 0 | 1 | 1 | 1 | 0 | 1 | 1 | 0 | 1 | 0 |
AND (логическое И)устанавливает бит результата в 1, если оба бита, бит источника и бит приемника установлены в 1.
OR (логическое ИЛИ)устанавливает бит результата в 1, если один из битов, бит источника или бит приемника установлен в 1.
XOR (НЕ ИЛИ)устанавливает бит результата в 1, если бит источника отличается от бита приемника.
NOTинвертирует бит источника.
Выполнение операции XOR на этими битами:
Если вы выполните инверсию каждого бита, то получите:
Значит после операции NOT, ecx будет содержать 0000FFFFh.
Команда jz выполнит переход, если ecx = 0.
Вот и конец урока. Надеюсь, этот не был скучным. Следующий урок расскажет вам про подпрограммы.
Урок 7
Расширенные арифметические операции с целыми числами
Несмотря на то, что мы привыкли к десятичной арифметике (база 10), компьютер работает только с двоичной арифметикой (база 2). Кроме того, ввиду ограничения, накладываемого 16-битовыми регистрами, большие величины требуют специальной обработки. Данная глава дает сведения об операциях сложения, вычитания, умножения и деления для беззнаковых и знаковых данных. В главе приводятся много примеров и предупреждений о различных ловушках для опрометчивых исследователей мира микропроцессора. В следующей главе будут раскрыты операции преобразования между двоичными данными и ASCII кодами.
Сложение и вычитание
Команды ADD и SUB выполняют сложение и вычитание байтов или слов, содержащих двоичные данные. Вычитание выполняется в компьютере по методу сложения с двоичным дополнением: для второго операнда устанавливаются обратные значения бит и прибавляется 1, а затем проиCXодит сложение с первым операндом. Во всем, кроме первого шага, операции сложения и вычитания идентичны. Возможны следующие пять ситуаций:
Поскольку прямой операции память-память не существует, данная oперация выполняется через регистр. В следующем примере к содержимому слова WORDB прибавляется содержимое слова WORDA, описанных как DW:
Переполнения
Многословное сложение
Беззнаковые данные
При возникновении переполнения при сложении знаковых чисел, результат получается неправильный:
При операциях сложения и вычитания может одновременно возникнуть и переполнение, и перенос:
Умножение
«Байт на байт». Множимое находится в регистре AL, а множи тель в байте памяти или в однобайтовом регистре. После умножения произведение находится в регистре AX. Операция игнорирует и стиpает любые данные, которые находились в регистре AH.
В единственном операнде команд MUL и IMUL указывается множитель. Рассмотрим следующую команду:
Если поле MULTR определено как байт (DB), то операция предполагает умножение содержимого AL на значение байта из поля MULTR. Если поле MULTR определено как слово (DW), то опереция предполагает умножение содержимого AX на значение слова из поля MULTR. Если множитель находится в регистре, то длина регистра определяет тип операции, как это показанно ниже:
Беззнаковое умножение: Команда MUL
Знаковое умножение: Команда IMUL
Многословное умножение
Обычно умножение имеет два типа: «байт на байт» и «слово на слово». Как уже было показано, максимальное знаковое значение в слове ограничено величиной +32767. Умножение больших чисел требует выполнения некоторых дополнительных действий. Рассматриваемый подход предполагает умножение каждого слова отдельно и сложение полученных результатов. Рассмотрим следующее умножение в десятичном формате:
Представим, что десятичная арифметика может умножать только двухзначные числа. Тогда можно умножить 13 и 65 на 12 раздельно, cледующим образом:
Следующим шагом сложим полученные произведения, но поскольку число 13 представляло сотни, то первое произведение в действительности будет 15600:
Ассемблерная программа использует аналогичную технику за исключением того, что данные имеют размерность слов (четыре цифры) в шестнадцатеричном формате.
Так как первая команда ADD может выработать перенос, то второе cложение выполняется командой сложения с переносом ADC (ADd with Carry). В силу обратного представления байтов в словах в процессоpах 8086/8088, область PRODUCT в действи тельности будет содержать значение 8A13 8066 00E4. Программа предполагает, что первое слово в области PRODUCT имеет начальное значение 0000. Умножение «двойного слова на двойное слово». Умножение двух двойных слов включает следующие четыре операции умножения:
=== Сдвиг регистровой пары DX:AX Следующая подпрограмма может быть полезна для сдвига содержимого pегистровой пары DX:AX вправо или влево. Можно придумать более эффективный метод, но данный пример представляет общий подход для любого числа циклов (и, соответственно, сдвигов) в регистре CX. Заметьте, что сдвиг единичного бита за разрядную сетку устанавливает флаг переноса.
Ниже приведен более эффективный способ для сдвига влево, не требующий организации цикла. В этом примере фактор сдвига записывается в регистр CL. Пример написан для сдвига на 4 бита, но может быть адаптирован для других величин сдвигов:
Деление
В единственном операнде команд DIV и IDIV указывается делитель. Рассмотрим следующую команду:
Беззнаковое деление: Команда DIV
Знаковое деление: Команда IDIV
Переполнения и прерывания
Для команды IDIV данная логика должна учитывать тот факт, что либо делимое, либо делитель могут быть отрицательными, а так как сравниваются абсолютные значения, то необходимо использовать команду NEG для временного перевода отрицательного значения в положительное.
Деление вычитанием
Примечание: очень большое частное и малый делитель могут вызвать тысячи циклов.
Преобразование знака
Команда NEG обеспечивает преобразование знака двоичных чисел из положительного в отрицательное и наоборот. Практически команда NEG устанавливает противоположные значения битов и прибавляет 1. Примеры:
Преобразование знака для 35-битового (или большего) числа включает больше шагов. Предположим, что регистровая пара DX:AX содержит 32-битовое двоичное число. Так как команда NEG не может обрабатывать два регистра одновременно, то ее использование приведет к неправильному результату. В следую щем примере показано использование команды NOT:
Математические сопроцессоры (описание)
Системная плата компьютера содержит пустое гнездо, зарезервированное для числового процессора Intel 8087 (или 80287). Сопроцессор 8087 действует совместно с 8088, а сопро цессор 80287 действует совместно с 80286. Каждый сопроцессор имеет собственный набор команд и средства для операций с плавающей запятой для выполнения экспоненциальных, логарифмических и тригонометрических функций. Сопроцессор содержит восемь 80-битовых регистров с плавающей запятой, которые могут представить числовые значения до 10 в 400 сте пени. Математические вычисления в сопроцессоре выполняются примерно в 100 раз быстрее, чем в основном процессоре. Основной процессор выполняет специальные операции и передает числовые данные в сопроцессор, который выполняет необходимые вычисления и возвращает результат. Для ассембли рования с помощью транслятора MASM, необходимо добавлять параметр /E или /R, например, MASM /R.



