Инструкция mov является одной из самых используемых в ассемблере. По разным оценкам, в программе от 25% до 40% всех инструкций составляет инструкция mov
.
Она копирует данные из одного места в другое и имеет следующий синтаксис:
mov destination, source
Инструкция принимает два операнда. Первый операнд - destination
представляет расположение, куда надо поместить данные. В качестве такого места может выступать
регистр процессора или адрес в памяти. Второй операнд - source
указывает на источник данных, в качестве которого может выступать регистр процессора, адрес в
памяти или непосредственный операнд - число. То инструкция mov копирует данные из source в destination. При этом оба операнда не могут быть одновременно адресами в памяти.
Например, определим файл hello.asm со следующей программой на ассемблере:
.code main proc mov rax, 22 ret main endp end
Здесь в регистр rax
помещается непосредственный операнд - число 22. Все операнды, которые представляют числовые или символьные литералы (как в данном случае числовой литерал 22),
называются непосредственными операндами (immediate operand).
Проверим, что после выполнения этой программы в регистре rax действительно 22. Вывод содержимого регистра на консоль требует ряда других инструкций. Но я не зря использовал именно регистр rax. Дело в том, что при выполнении программы в регистр rax помещается код статуса выполнения программы. Обычно считается, что если этот регистр содержит 0, то программа успешно завершила свое выполнение. Другое число обычно означает код ошибки. Но в нашем случае мы можем использовать этот регистр и число в нем для проверки выполнения инструкций ассемблера. (Также можно помещать код статуса и в младшие части rax - регистры eax и al при условии, что остальная часть регистра rax содержит 0)
Так, вначале скомпилируем файл командой
ml64 hello.asm /link /entry:main
Далее выполним скомпилированное приложение, введя его имя
hello
И для проверки результата в регистре rax после выполнения скомпилированного приложения выполним команду
echo %ERRORLEVEL%
Эта команда отображает код ошибки. Полный вывод:
c:\asm>ml64 hello.asm /link /entry:main Microsoft (R) Macro Assembler (x64) Version 14.35.32217.1 Copyright (C) Microsoft Corporation. All rights reserved. Assembling: hello.asm Microsoft (R) Incremental Linker Version 14.35.32217.1 Copyright (C) Microsoft Corporation. All rights reserved. /OUT:hello.exe hello.obj /entry:main c:\asm>hello c:\asm>echo %ERRORLEVEL% 22 c:\asm>
Оба операнда инструкции mov
должны быть одинакового размера. То есть можно поместить значение из 8-битного регистра в другой 8-битный регистр или 8-битную переменную или наоборот.
Подобным образом можно копировать данные между 16-битными или между 32-битными или между 64-битными операндами, но использовать в инструкции операнды разных размеров
нельзя. Константы и непосредственные операнды могут быть меньшего размера, чем регистр, в который они помещаются. Например:
.code main proc mov rdx, 5 ; помещаем в регистр rdx число 5 mov rax, rdx ; помещаем в регистр rax значение из регистра rdx ret main endp end
Здесь вначале помещаем в регистр rdx
число 5. Регистр rdx - 64-разрядный, поэтому в него можно поместить любой целочисленной литерал размером не более 64 бит. В реальности
для числа 5 достаточно 8-битного регистра. И в данном случае помещаем число просто будет расширено до 64 бит.
Далее число из регистра rdx
(то есть число 5) помещается в регистр rax
. Здесь у нас должно быть соответствие по разрядности регистров. Поскольку и
rdx, и rax - 64-разрядные регистры, то проблемы не будет.
То же самое касается и переменных. Например:
.data i32 dword 4 .code main proc mov eax, i32 ; помещаем в регистр eax значение переменной i32 ret main endp end
Здесь в секции .data
определена переменная i32, которая равна 32 битам. Ее значение помещается в 32-битный регистр eax (младшие 32 бита регистра rax).
Другой пример:
.data i8 byte 8 .code main proc mov rax, 0 ; обнуляем регистр rax mov al, i8 ; помещаем в регистр al значение переменной i8 ret main endp end
Здесь значение 8-битной переменной i8 помещается в 8-битный регистр al.
Однако, если мы смешаем операнды с разными размерами, то при компиляции ассемблер укажет нам на ошибку, как в следующем случае:
.data i8 byte 8 .code main proc mov rax, i8 ; помещаем в регистр rax значение переменной i8 ret main endp end
Здесь значение 8-битной переменной i8 помещается в 64-битный регистр rax. И при компиляции ассемблер отобразит нам ошибку:
c:\asm>ml64 hello.asm /link /entry:main Microsoft (R) Macro Assembler (x64) Version 14.35.32217.1 Copyright (C) Microsoft Corporation. All rights reserved. Assembling: hello.asm hello.asm(5) : error A2022:instruction operands must be the same size c:\asm>
Тоже самое касается и констант и числовых литералов, размер которых больше, чем размер регистра:
.code main proc mov al, 512 ; помещаем в регистр al число 512 ret main endp end
Здесь мы пытаемся поместить в 8-разрядный регистр al число 512, которое занимает больше 8 бит. Соответственно при компиляции мы также получим ошибку.
Инструкция mov имеет ограничение - операнды должны совпадать по размеру. Но что, если нам надо поместить в 64-разрядный регистр 8-битное значение?
Архитектура x86-64 также предоставляет дополнительные расширения инструкции mov
- movsx и movzx.
movsx выполняет перемещение с расширением знаковым битом,
которое копирует данные и расширяет данные по знаку во время их копирования. Синтаксис инструкции movsx аналогичен синтаксису mov:
movsxd dest, source ; если dest - 64-разрядный операнд и source - 32-разрядный movsx dest, source ; для всех остальных комбинаций операндов
При этом первый операнд - dest должен быть по разрядности не меньше второго операнда и обязательно должен предоставлять регистр. Эта инструкция также не допускает константные значения. Например
.code main proc mov dl, -5 movsx eax, dl ; EAX = -5 ret main endp end
Здесь значение из регистра DL помещается в регистр EAX. Поскольку в DL число отрицательное, то используем расширение со знаком. В результате в EAX сохранится число -5.
Для беззнакового расширения нулями есть другая инструкция - movzx:
.code main proc mov dl, 5 movzx eax, dl ; EAX = 5 ret main endp end
Если надо расширить 32-битный регистр до 64-битного, можно просто скопировать (32-битный) регистр в самого себя:
.code main proc mov rax, 0ffffffffffffffffh mov eax, eax ; RAX = 00000000ffffffffh - в RAX остались только младшие 32 бита ret main endp end