Язык ассемблера - Арифметические инструкции
Арифметические инструкции — основа математических операций процессора. В этой главе подробно рассматриваются сложение, вычитание, умножение, деление и связанные с ними инструкции операций с переносом в архитектуре x86.
ADD - Инструкция сложения
ADDДобавляет исходный операнд к операнду назначения и сохраняет результат в операнде назначения.
Пример
; Пример инструкции ADD: вычислить 100 + 200 + 300
section .data
a dd 100
b dd 200
c dd 300
sum dd 0
section .text
global _start
_start:
; Способ 1: регистр += непосредственное значение
mov eax, [a] ; eax = 100
add eax, 200 ; eax = 100 + 200 = 300
; Способ 2: регистр += регистр
mov ebx, [b] ; ebx = 200
add eax, ebx ; eax = 300 + 200 = 500
; Способ 3: регистр += память
add eax, [c] ; eax = 500 + 300 = 800
; Сохранить результат
mov [sum], eax ; sum = 800
; Влияние сложения на флаги
mov eax, 0xFFFFFFFF ; eax = наибольшее 32-битное беззнаковое число
add eax, 1 ; eax = 0 (переполнение приводит к циклическому переносу)
; CF = 1 (сгенерирован перенос)
; ZF = 1 (результат равен 0)
; OF = 0 (нет переполнения с точки зрения знаковых чисел)
mov eax, 1
mov ebx, 0
int 0x80
SUB — инструкция вычитания
SUBВычитает исходный операнд из операнда назначения и сохраняет результат в операнде назначения.
Пример
; Пример инструкции SUB
section .data
x dd 1000
y dd 300
section .text
global _start
_start:
; Простое вычитание
mov eax, [x] ; eax = 1000
sub eax, [y] ; eax = 1000 - 300 = 700
; Влияние вычитания на флаги
mov eax, 10
sub eax, 20 ; eax = -10 (т. е. 0xFFFFFFF6)
; CF = 1 (сгенерирован заём: 10 < 20)
; SF = 1 (результат отрицательный)
; ZF = 0 (результат ненулевой)
; OF = 0 (нет знакового переполнения)
; Вычитание самого из себя: часто используется для обнуления
mov eax, 12345
sub eax, eax ; eax = 0
; ZF = 1, CF = 0
; Это классический способ обнулить регистр (эффективнее, чем mov eax, 0)
mov eax, 1
mov ebx, 0
int 0x80
sub eax, eax— классический приём обнуления регистра; он занимает всего 2 байта, тогда какmov eax, 0занимает 5 байт. Обычно используется в сценариях, требующих экстремальной оптимизации размера (например, в шелл-коде).
INC / DEC — инструкции инкремента/декремента
Инструкции прибавления 1 и вычитания 1 компактнее, чем ADD/SUB:
Пример
section .data
counter dd 0
section .text
global _start
_start:
mov dword [counter], 0 ; counter = 0
inc dword [counter] ; counter = 1 (операнд в памяти)
inc dword [counter] ; counter = 2
mov ecx, 10
dec ecx ; ecx = 9
dec ecx ; ecx = 8
; INC/DEC не влияют на флаг CF (это важное отличие от ADD/SUB)
; но они влияют на ZF, SF, OF, PF
; Использование INC в цикле
mov ecx, 5
mov eax, 0
loop_inc:
inc eax ; eax увеличивается на 1 каждый раз
loop loop_inc ; Повторить 5 раз, в итоге eax = 5
mov ebx, eax ; Возвращаемое значение = 5
mov eax, 1
int 0x80
MUL - беззнаковое умножение
MULВыполняет беззнаковое умножение. Правила умножения несколько специфичны:
| Размер операнда | Множитель | Множимое (неявное) | Место хранения результата |
|---|---|---|---|
| 1 байт | Любой 8-битный регистр или память | AL | AX = AL × операнд |
| 2 байта | Любой 16-битный регистр или память | AX | DX:AX = AX × операнд |
| 4 байта | Любой 32-битный регистр или память | EAX | EDX:EAX = EAX × операнд |
Пример
; Пример беззнакового умножения MUL
section .data
val1 dd 1000
val2 dd 2000
result_low dd 0
result_high dd 0
section .text
global _start
_start:
; 32-битное умножение: EDX:EAX = EAX × операнд
mov eax, [val1] ; eax = 1000
mul dword [val2] ; edx:eax = 1000 × 2000 = 2,000,000
; Результат = 2,000,000 = 0x001E8480
; EAX = 0x001E8480 (младшие 32 бита)
; EDX = 0x00000000 (старшие 32 бита, потому что результат не превысил 32 бита)
; Демонстрация умножения больших чисел (результат превышает 32 бита)
mov eax, 0xFFFFFFFF ; eax = 4,294,967,295
mov ebx, 2 ; ebx = 2
mul ebx ; edx:eax = 0xFFFFFFFF × 2
; EAX = 0xFFFFFFFE
; EDX = 0x00000001 (старшие 32 бита)
; CF = 1 (результат превышает 32 бита)
; Сохранить результат
mov [result_low], eax
mov [result_high], edx
mov eax, 1
mov ebx, 0
int 0x80
IMUL — знаковое умножение
IMULИспользуется для умножения чисел со знаком, имеет три формы:
Пример
; Пример знакового умножения IMUL
section .data
a dd -100
b dd 3
section .text
global _start
_start:
; Однооперандная форма: то же, что и MUL
mov eax, [a] ; eax = -100
imul dword [b] ; edx:eax = -100 × 3 = -300
; EAX = -300(0xFFFFFED4)
; EDX = 0xFFFFFFFF (расширение знака)
; Двухоперандная форма: reg = reg × operand
mov ebx, [a] ; ebx = -100
imul ebx, [b] ; ebx = -100 × 3 = -300
; Результат должен помещаться в 32 bits
; Трёхоперандная форма: reg = operand1 × immediate
imul ecx, [a], 5 ; ecx = -100 × 5 = -500
mov eax, 1
mov ebx, 0
int 0x80
MULиIMULОсновное различие — обработка знаков:MULИнтерпретируются как беззнаковые числа,IMULИнтерпретируются как числа со знаком. Например, 0xFFFFFFFF — это 4294967295 в MUL и -1 в IMUL.
DIV — беззнаковое деление
DIVВыполняет беззнаковое деление, правила симметричны MUL:
| Размер делителя | Делимое (неявное) | Частное | Остаток |
|---|---|---|---|
| 1 байт | AX | AL | AH |
| 2 байта | DX:AX | AX | DX |
| 4 байта | EDX:EAX | EAX | EDX |
Пример
; Пример беззнакового деления (DIV)
section .data
dividend dd 1000
divisor dd 7
quotient dd 0
remainder dd 0
section .text
global _start
_start:
; 32-битное деление: edx:eax / divisor
; Делимое сначала нужно расширить до EDX:EAX
mov eax, [dividend] ; eax = 1000
mov edx, 0 ; edx = 0 (очистить старшие 32 бита)
div dword [divisor] ; edx:eax / 7
; EAX = 142 (частное)
; EDX = 6 (остаток: 1000 = 142×7 + 6)
mov [quotient], eax ; частное = 142
mov [remainder], edx ; остаток = 6
; Пример деления байтов: 55 / 4
mov ax, 55 ; Делимое
mov bl, 4 ; Делитель
div bl ; al = 13 (частное), ah = 3 (остаток)
mov eax, 1
mov ebx, 0
int 0x80
Перед выполнением деления обязательно используйте
mov edx, 0илиxor edx, edxОчистите EDX! Если в EDX остались устаревшие данные, делимое будет неверным. Это самая распространённая ошибка при делении у начинающих.
IDIV - Деление со знаком
IDIVИспользуется для деления со знаком. Перед выполнением деления нужно использоватьCDQкоманда для знакового расширения EAX в EDX:EAX:
Пример
; Вычислить -100 / 3
mov eax, -100 ; eax = -100
cdq ; Знаковое расширение: edx:eax = -100
; cdq копирует знаковый бит eax во все биты edx
mov ebx, 3 ; Делитель
idiv ebx ; eax = -33 (частное), edx = -1 (остаток)
; Проверка: -100 = -33 × 3 + (-1)
ADC / SBB - операции с переносом/заимствованием
Используются дляарифметики с большими числами(данными, превышающими 32 бита):
Пример
; 64-битное сложение: сложение двух 64-битных чисел
section .data
; 64-битное число a = 0x00000001 FFFFFFFF
a_low dd 0xFFFFFFFF
a_high dd 0x00000001
; 64-битное число b = 0x00000000 00000005
b_low dd 0x00000005
b_high dd 0x00000000
; Результат
result_low dd 0
result_high dd 0
section .text
global _start
_start:
; Сложить младшие 32 бита
mov eax, [a_low]
add eax, [b_low] ; eax = 0xFFFFFFFF + 5 = 0x00000004
; CF = 1 (произошёл перенос!)
mov [result_low], eax ; result_low = 0x00000004
; Сложение старших 32 бит с переносом
mov eax, [a_high]
adc eax, [b_high] ; adc = add + CF
; eax = 1 + 0 + 1(перенос) = 2
mov [result_high], eax ; result_high = 2
; Итоговый 64-битный результат: 0x00000002 00000004
; Проверка: 0x1FFFFFFF + 5 = 0x200000004
; Вычитание SBB аналогично (с заимствованием)
mov eax, [a_low]
sub eax, [b_low] ; Вычитание младших 32 бит
mov [result_low], eax
mov eax, [a_high]
sbb eax, [b_high] ; sbb = sub - CF
mov eax, 1
mov ebx, 0
int 0x80
Краткая справочная таблица арифметических инструкций
| Инструкция | Формат | Функция | Затрагиваемые флаги |
|---|---|---|---|
| ADD | add dest, src | dest = dest + src | CF, ZF, SF, OF, PF |
| SUB | sub dest, src | dest = dest - src | CF, ZF, SF, OF, PF |
| INC | inc dest | dest = dest + 1 | ZF, SF, OF, PF (не влияет на CF) |
| DEC | dec dest | dest = dest - 1 | ZF, SF, OF, PF (не влияет на CF) |
| MUL | mul src | Беззнаковое умножение | CF, OF |
| IMUL | imul src | Знаковое умножение | CF, OF |
| DIV | div src | Беззнаковое деление | Не определено (неопределённое) |
| IDIV | idiv src | Знаковое деление | Не определено (неопределённое) |
| ADC | adc dest, src | dest = dest + src + CF | CF, ZF, SF, OF, PF |
| SBB | sbb dest, src | dest = dest - src - CF | CF, ZF, SF, OF, PF |
| NEG | neg dest | dest = -dest (отрицание) | CF, ZF, SF, OF, PF |