Инструкции mul и imul умножает два целых числа. imul умножает числа со знаком, а mul - беззнаковые числа. Обе инструкции принимают один операнд - регистр или адрес в памяти, который умножается на значение в регистре RAX. Результат помещается в регистры RAX/RDX:
mul operand8 ; если операнд 8-разрядный, результат в AX mul operand16 ; если операнд 16-разрядный, результат в DX:AX mul operand32 ; если операнд 32-разрядный, результат в EDX:EAX mul operand64 ; если операнд 64-разрядный, результат в RDX:RAX
В качестве операнда можно передавать регистр или переменную/адрес в памяти. Единственный операнд умножается на соответствующий его размеру регистр AL/AX/EAX/RAX. Результат умножения двух n-битных значений может потребовать 2×n бит. Следовательно, если операнд - 8-битное число, то для результата может потребоваться 16 бит. Аналогично, 16-битный операнд дает 32-битный результат и т.д.
mul operand8 | AX = AL × operand8 |
mul operand16 | DX:AX = AX × operand16 |
mul operand32 | EDX:EAX = EAX × operand32 |
mul operand64 | RDX:RAX = RAX × operand64 |
В AX/EAX/RAX помещается младшая часть результата, а в DX/EDX/RDX - старшая.
Если результат умножения по количеству бит больше разрядности операндов, то инструкция mul
устанавливает флаг переноса CF и флаг переполнения OF. Соответственно,
если мы проверим флаг переноса CF и обнаружим, что он установлен, это значит, что старшая часть помещена в регистр RDX.
Пример умножения на Linux
global _start section .text _start: mov rdi, 2 mov rax, 4 mul rdi ; RAX = RAX * RDI mov rdi, rax ; RDI = RAX = 8 mov rax, 60 syscall
Здесь в регистр RAX помещается число 4, а в регистр RDI - число 2. При выполнении инструкции mul rdi
значения регистров RAX и RDI будут перемножаться, и результат
помещен в регистр RAX. То есть в RAX будет число 2 * 4 = 8. Затем из RAX число копируем в регистр RDI.
Аналогичный пример на Windows:
global _start section .text _start: mov rdi, 2 mov rax, 4 mul rdi ; RAX = RAX * RDI = 8 ret
Возьмем чуть по сложнее пример, где результат умножения раскидан по регистрам RDX/RAX:
global _start section .text _start: mov rdi, 0xC000000000000002 mov rax, 4 mul rdi ; RDX:RAX = RDI * RAX mov rdi, rdx ; помещаем в RDI старшие 64 бита результата - RDI = RDX = 3 mov rax, 60 syscall
Здесь 8-байтное число 0xC000000000000002
из регистра RDI умножается на число 4. Результатом будет шестнадцатеричное число 0x03_0000000000000008
(в десятичной системе
это число 55340232221128654856). Однако для этого числа требуется 66 бит (точнее 9 байт), что превышает размер регистра RAX. Поэтому старшие 2 бита помещаются в регистр RDX.
Соответственно после выполнения инструкции mov rdi, rdx
в регистре RDI будет число 3.
Инструкция imul также может принимать два и три операнда:
imul dest, source imul dest, source, constant
Если инструкции передается два операнда, то она умножает первый операнд на второй и результат присваивает первому операнду:
dest = dest * source
Первый операнд - всегда регистр. Второй операнд может представлять регистр, переменную или константу. Оба операнда должны совпадать по размеру и могут быть 16-, 32- и 64-разрядными, за исключением констант, которые не могут превышать 32 бит (Если первый операнд 64-разрядный, то константы автоматически расширяются со знаком до 64 бит).
Пример умножения:
global _start section .text _start: mov rdi, 3 imul rdi, 5 ; RDI = RDI * 5 = 3 * 5 = 15 mov rax, 60 syscall
Если инструкция принимает три операнда, то умножаются втрой и третий операнд, а результат помещается в первый операнд:
dest = source * constant
Первый операнд - по прежнему регистр. Второй операнд - регистр или переменная. А третий операнд - константа. При этом операнды также должны соответствовать по разрядности, и могут быть 16-, 32- и 64-разрядными, за исключением констант, которые опять же не должны превышать 32 бит. Пример:
global _start section .text _start: mov rdx, 3 imul rdi, rdx, 4 ; RDI = RDX * 4 = 3 * 4 = 12 mov rax, 60 syscall