Программирование на языке ассемблера
.pdfmov ebx, 500000
imul eax, ebx, 100000 ; EAX = EBX * 100000, старшая часть результата теряется
x dd 40
mov eax, 55
imul eax, x |
; EAX = EAX * x |
3.4.2. Команды деления
Деление, как и умножение, реализуется двумя командами, предназначенными для знаковых и беззнаковых чисел:
DIV <операнд> |
; |
Беззнаковое деление |
IDIV <операнд> |
; |
Знаковое деление |
В командах указывается только один операнд – делитель, который может быть регистром или ячейкой памяти, но не может быть непосредственным операндом. Местоположение делимого и результата для команд деления фиксировано.
Если делитель имеет размер 1 байт, то делимое берётся из регистра AX. Если делитель имеет размер 2 байта, то делимое берётся из регистровой пары DX:AX. Если же делитель имеет размер 4 байта, то делимое берётся из регистровой пары EDX:EAX.
Поскольку процессор работает с целыми числами, то в результате деления получается сразу два числа – частное и остаток. Эти два числа также помещаются в определённые регистры. Если делитель имеет размер 1 байт, то частное помещается в регистр AL, а остаток – в регистр AH. Если делитель имеет размер 2 байта, то частное помещается в регистр AX, а остаток – в регистр DX. Если же делитель имеет размер 4 байта, то частное помещается в регистр EAX, а остаток – в регистр EDX.
mov |
ax, 127 |
|
mov |
bl, 5 |
|
div |
bl |
; AL = 19h = 25, AH = 02h = 2 |
mov ax, 127
mov |
bl, -5 |
|
idiv |
bl |
; AL = e7h = -25, AH = 02h = 2 |
mov |
ax, -127 |
|
mov |
bl, 5 |
|
idiv |
bl |
; AL = e7h = -25, AH = feh = -2 |
mov |
ax, -127 |
|
mov |
bl, -5 |
|
idiv |
bl |
; AL = 19h = 25, AH = feh = -2 |
; x = a * b + c |
|
|
mov |
eax, a |
|
imul |
b |
|
add |
eax, c |
; Операнды команды сложения вычисляются |
слева |
направо |
|
mov |
x, eax |
|
; x = a + b * c
mov |
eax, b |
|
imul |
c |
|
add |
eax, a |
; Операнды команды сложения вычисляются |
справа налево |
|
|
mov |
x, eax |
|
3.5.Изменение размера числа
Воперациях деления размер делимого в два раза больше, чем размер делителя. Поэтому нельзя просто загрузить данные в регистр EAX и поделить его на какое-либо значение, т.к. в операции деления будет задействован также и регистр EDX. Поэтому прежде чем выполнять деление, надо установить корректное значение в регистр
EDX, иначе результат будет неправильным. Значение регистра EDX должно зависеть от значения регистра EAX. Тут возможны два варианта
– для знаковых и беззнаковых чисел.
Если мы используем беззнаковые числа, то в любом случае в регистр EDX необходимо записать значение 0: aaaaaaaah → 00000000aaaaaaaah.
Если же мы используем знаковые числа, то значение регистра EDX будет зависеть от знака числа: 55555555h →
0000000055555555h,aaaaaaaah → ffffffffaaaaaaaah.
Записать значение 0 не сложно, а вот для знакового расширения необходимо анализировать знак числа. Однако нет необходимости делать это вручную, т.к. язык ассемблера имеет ряд команд, позволяющих расширять байт до слова, слово до двойного слова и двойное слово до учетверённого слова.
cbw |
; Знаковое расширение AL |
до |
AX |
|
cwd |
; Знаковое расширение AX |
до |
DX:AX |
|
cwde |
; Знаковое расширение |
AX до EAX |
||
cdq |
; Знаковое расширение |
EAX до EDX:EAX |
Таким образом, если делитель имеет размер 2 или 4 байта, то нужно устанавливать значение не только регистра AX/EAX, но и регистра DX/EDX. Если же делитель имеет размер 1 байт, то можно просто записать делимое в регистр AX.
x dd ?
mov |
eax, |
x |
; Заносим в регистр EAX значение переменной |
x, которое |
заранее неизвестно |
||
cdq |
|
|
; Знаковое расширение EAX в EDX:EAX |
mov |
ebx, |
7 |
|
idiv |
ebx |
|
|
В языке ассемблера существуют также команды, позволяющие занести в регистр значение другого регистра или ячейки памяти со знаковым или беззнаковым расширением.
MOVSX <операнд1>, <операнд2> |
; |
Знаковое |
расширение |
– |
старшие биты заполняются знаковым битом |
|
|
|
MOVZX <операнд1>, <операнд2> |
; |
Беззнаковое |
расширение – |
старшие биты заполняются нулём |
|
|
|
Операнд1 и операнд2 могут |
иметь |
любой размер. Понятно, |
|
что операнд1 должен быть больше, чем операнд2. В |
случае равенства |
размера операндов следует использовать обычную команду пересылки MOV, которая выполняется быстрее.
Рассмотрим |
пример: необходимо вычислить x * x * x, |
где x – 1- |
|
байтовая переменная. |
|
||
; Первый вариант |
|
|
|
mov |
al, x |
; Пересылаем x в регистр AL |
|
imul |
al |
; Умножаем регистр AL на себя, AX = x * x |
|
movsx |
bx, x |
; Пересылаем x в регистр BX со |
знаковым |
расширением |
|
|
|
imul |
bx |
; Умножаем AX на BX. Но! – результат |
|
размещается в DX:AX |
|
; Второй вариант |
|
|
|
|
mov |
al, x |
; Пересылаем x в регистр AL |
|
|
imul |
al |
; Умножаем регистр AL на себя, AX = x * x |
||
cwde |
|
; Расширяем AX до EAX |
|
|
movsx |
ebx, x |
; Пересылаем x |
в регистр EBX со |
знаковым |
расширением |
|
|
|
|
imul |
ebx |
; Умножаем EAX на |
EBX. Поскольку |
x – 1- |
байтовая переменная, результат благополучно помещается в EAX
Рассмотрим ещё один пример.
mov |
eax, x |
|
|
mov |
ebx, 429496730 |
; 429496730 = 4294967296 / 10 |
|
imul |
ebx |
; EDX = x / 10. Выполняется в ≈5 раз быстрее, |
чем деление
Чем обусловлено получение такого результата? Всегда ли будет работать этот механизм?
4. Переходы и циклы
Для изменения порядка выполнения команд в языке ассемблера используются команды условного и безусловного перехода, а также команды управления циклом. Все эти команды не меняют флаги.
4.1. Безусловный переход
Команда безусловного перехода имеет следующий синтаксис:
JMP <операнд>
Операнд указывает адрес перехода. Существует два способа указания этого адреса, соответственно различают прямой и косвенный переходы.
4.1.1. Прямой переход
Если в команде перехода указывается метка команды, на которую надо перейти, то переход называется прямым.
jmp L
...
L: mov |
eax, x |
Вообще, любой переход заключается в изменении адреса следующей исполняемой команды, т.е. в изменении значения регистра EIP. Казалось бы, в команде перехода должен задаваться именно адрес перехода. Однако в команде прямого перехода задаётся не абсолютный адрес, а разность между адресом перехода и адресом команды перехода. Действие команды перехода заключается в прибавлении этой величины к текущему значению регистра EIP2. Операнд команды перехода рассматривается как поле со знаком, поэтому при сложении его со значением регистра EIP значение в этом регистре может как увеличиться, так и уменьшиться, т.е. возможен переход и вперёд, и назад.
Запись в команде перехода не абсолютного, а относительного адреса перехода позволяет уменьшить размер команды перехода. Абсолютный адрес должен быть 32-битным, а относительный может быть и 8-битным, и 16-битным.
4.1.2. Косвенный переход
При косвенном переходе в команде перехода указывается не адрес перехода, а регистр или ячейка памяти, где этот адрес находится. Содержимое указанного регистра или ячейки памяти рассматривается как абсолютный адрес перехода. Косвенные переходы используются в
тех случаях, когда адрес перехода становится известен только во время работы программы.
jmp ebx
4.2. Команды сравнения и условного перехода
Команды условного перехода осуществляют переход, который выполняется только в случае истинности некоторого условия. Истинность условия проверяется по значениям флагов. Поэтому обычно непосредственно перед командой условного перехода ставится команда сравнения, которая формирует значения флагов:
CMP <операнд1>, <операнд2>
Команда сравнения эквивалентна команде SUB за исключением того, что вычисленная разность никуда не заносится. Назначение команды CMP– установка и сброс флагов.
Что касается команд условного перехода, то их достаточно много, но все они записываются единообразно:
Jxx <метка>
Все команды условного перехода можно разделить на три группы.
В первую группу входят команды, которые обычно ставятся после команды сравнения. В их мнемокодах указывается тот результат сравнения, при котором надо делать переход.
|
Мнемокод |
|
|
|
Название |
|
|
|
Условие перехода после к |
|
|
|
|
|
|
|
|||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
JE |
|
|
|
Переход если равно |
|
|
|
op1 = op2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
JNE |
|
|
|
Переход если не равно |
|
|
|
op1 ≠ op2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
JL/JNGE |
|
|
|
Переход если меньше |
|
|
|
op1 < op2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
JLE/JNG |
|
|
|
Переход если меньше или равно |
|
|
|
op1 ≤ op2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
JG/JNLE |
|
|
|
Переход если больше |
|
|
|
op1 > op2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
JGE/JNL |
|
|
|
Переход если больше или равно |
|
|
|
op1 ≥ op2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
JB/JNAE |
|
|
|
Переход если ниже |
|
|
|
op1 < op2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
JBE/JNA |
|
|
|
Переход если ниже или равно |
|
|
|
op1 ≤ op2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
JA/JNBE |
|
|
|
Переход если выше |
|
|
|
op1 > op2 |
|
|
|
|
|
|
|
|
|
|
|
JAE/JNB |
|
Переход если выше или равно |
|
op1 ≥ op2 |
|
|
|
|
|
|
|
|
|
|
Рассмотрим |
пример: |
даны |
две |
переменные x и y, |
в |
|
переменную z нужно записать максимальное из чисел x и y. |
|
|||||
mov |
eax, x |
|
|
|
|
|
cmp |
eax, y |
|
|
|
|
|
jge/jae L |
|
; Используем |
JGE для знаковых чисел |
|||
и JAE – для беззнаковых |
|
|
|
|
||
mov |
eax, y |
|
|
|
|
|
L: mov |
z, eax |
|
|
|
|
|
Во вторую группу команд условного перехода входят те, которые обычно ставятся после команд, отличных от команды сравнения, и которые реагируют на то или иное значение какого-либо флага.
|
Мнемокод |
|
|
|
Условие перехода |
|
|
|
|
|
|
|
|
|
|
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
JZ |
|
|
ZF = 1 |
|
|
|
JNZ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
JS |
|
|
SF = 1 |
|
|
|
JNS |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
JC |
|
|
CF = 1 |
|
|
|
JNC |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
JO |
|
|
OF = 1 |
|
|
|
JNO |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
JP |
|
|
PF = 1 |
|
|
|
JNP |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Рассмотрим |
пример: пусть a, b и c – |
беззнаковые переменные |
|||||
размером 1 байт, |
требуется вычислить c = a * a + b, но если результат |
превосходит размер байта, передать управление на метку ERROR.
mov al, a
mul al
jc ERROR
add al, b
jc ERROR
mov c, al
И, наконец, в третью группу входят две команды условного перехода, проверяющие не флаги, а значение регистра ECX или CX:
JCXZ <метка> |
; |
Переход, |
если |
значение |
регистра |
CX |
|
равно 0 |
|
|
|
|
|
|
|
JECXZ |
<метка> |
; |
Переход, |
если |
значение |
регистра |
ECX |
равно |
0 |
|
|
|
|
|
|
Однако эта команда выполняется достаточно долго. Выгоднее провести сравнение с нулём и использовать обычную команду условного перехода.
С помощью команд перехода можно реализовать любые разветвления и циклы.
; if (x > 0) S
cmp x, 0
jle L
... |
; S |
L:
; if (x) S1 else S2
cmp |
x, 0 |
je |
L1 |
... |
; S1 |
jmp |
L2 |
L1: ... |
; S2 |
L2: |
|
; if (a > 0 && b > 0) S
cmp a, 0
jle L
cmp b, 0
jle L
... |
; S |
L:
; if (a > 0 || b > 0) S
cmp a, 0
jg L1
cmp b, 0
jle L2
L1: ... |
; S |
L2:
; if (a > 0 || b > 0 && c > 0) S
cmp a, 0
jg L1
cmp b, 0
jle L2
cmp c, 0
jle L2
L1: ... |
; S |
L2:
; while (x > 0) do S
L1: cmp x, 0 jle L2
... |
; S |
jmp L1
L2:
; do S while (x > 0) |
|
L: ... |
; S |
cmp x, 0
jg L
4.3. Команды управления циклом
4.3.1. Команда LOOP
Команда LOOP позволяет организовать цикл с известным числом повторений:
mov ecx, n
L:...
...
loop L
Команда LOOP требует, чтобы в качестве счётчика цикла использовался регистр ECX. Собственно, команда LOOP вычитает единицу именно из этого регистра, сравнивает полученное значение с нулём и осуществляет переход на указанную метку, если значение в регистре ECX больше 0. Метка определяет смещение перехода, которое не может превышать 128 байт.
При использовании команды LOOP следует также учитывать, что с её помощью реализуется цикл с постусловием, следовательно, тело цикла выполняется хотя бы один раз. Хуже того, если до начала цикла записать в регистр ECX значение 0, то при вычитании единицы, которое выполняется до сравнения с нулём, в регистре ECX окажется ненулевое значение, и цикл будет выполняться 232 раз.
Команда LOOP не относится к самым быстрым командам. В большинстве случаев её можно заменить последовательностью других команд.
4.3.2. Команды LOOPE/LOOPZ и LOOPNE/LOOPNZ
Эти команды похожи на команду LOOP, но позволяют также организовать и досрочный выход из цикла.
LOOPE <метка> ; Команды являются синонимами
LOOPZ <метка>
Действие этой команды можно описать следующим образом: ECX = ECX - 1; if (ECX != 0 && ZF == 1) goto <метка>;