Инструкция mov

Последнее обновление: 01.07.2023

Инструкция 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 и расширение со знаком

Инструкция 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
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850