Ассемблер позволяет определять в программе объекты, которые хранят некоторые данные. Использование переменных значительно упрощает использование данных в программы. Объявления данных имеют следующую форму:
label directive value
label
представляет название переменной, которая может представлять любой валидный идентификатор. Например, в MASM идентификаторы могут начинаться со символа доллара ($), подчеркивания (_) или алфавитного символа, после которых может следовать произвольное количество
алфавитно-цифровых символов, символа доллара или подчеркивания. Идентификатор не может состоять из одного символа $ сам по себе (это имеет особое значение для MASM).
После названия переменной идет directive
- директива, которая устанавливает тип данных. Это может быть одна из следующих директив:
byte (или db): 8-битное целое число без знака
sbyte: 8-битное целое число со знаком
word (или dw): 16-битное целое число без знака (или слово)
sword: 16-битное целое число со знаком
dword (или dd): 32-битное целое число без знака (или двойное слово)
sdword: 32-битное целое число со знаком
qword (или dq): 64-битное целое число без знака (или четверное слово)
sqword: 64-битное целое число со знаком
tbyte (или dt): 80-битное целое число без знака
oword: 128-битное целое число без знака (восьмерное слово)
real4: число с плавающей точкой одинарной точности (32 бита)
real8: число с плавающей точкой двойной точности (64 бита)
real10: число с плавающей точкой расширенной точности (80 бит)
xmmword: вектор размером 128 байт, применяется в операциях SIMD для работы с регистрами XMM
ymmword: вектор размером 256 байт, применяется в операциях SIMD для работы с регистрами YMM
zmmword: вектор размером 512 байт, применяется в операциях SIMD для работы с регистрами ZMM
После директивы данных идет собственно значение. Например:
number byte 22
Здесь определена переменная number, которая имеет тип byte
и которая равна 22. Можно определить набор данных:
nums byte 1, 2, 3, 4, 5, 10, 0
Здесь переменная nums представляет набор значений byte.
С каждой переменной MASM будет ассоциировать некоторый свободный участок памяти. Например, переменная nums имеет тип byte и занимает 7 байт, соответственно MASM найдет в памяти свободные 7 байт и ассоциирует с этой переменной.
Где определяются данные? В программе на ассемблере можно опредлять данные в различных секциях. Например, в секции .code
:
.code i32 dword 123 ; определеяем объект i32 main proc mov eax, i32 ; помещаем число i32 в регистр eax ret main endp end
Однако секция .code
больше подходит для инструкций, которые выполняют некоторые действия. Кроме того, по умолчанию, когда MASM компонует программу,
он сообщает системе, что программа может выполнять инструкции и считывать данные из секции .code
, но не может записывать
данные в эту секцию. Поэтому операционная система сгенерирует общую ошибку защиты (general protection fault), если мы попытаемся сохранить какие-либо данные в секции кода.
Поэтому для определения данных больше подходят другие секции.
Переменные определяются в секции данных, которая задается с помощью директивы .data. Эта директива сообщает MASM, что дальше
(до следующей директивы, которая определяет секцию, например, .code
) идут объявления данных.
.data i32 dword 5 ; определяем переменную i32 .code main proc mov eax, i32 ; помещаем значение переменной в регистр EAX ret main endp end
Если секция .data
содержит несколько переменных, то MASM с каждой из этих переменной ассоциирует некоторый участок памяти. Причем
в памяти все переменные будут расположены друг за другом. Например, возьмем следующую секцию данных
.data i8 byte 1 i16 word 2 i32 dword 3 i64 qword 4
Для переменной i8 выделяется 1 байт, для i16 - 2 байта, для i32 - 4 байта и для i64 - 8 байт. В итоге для секции .data будет выделен блок памяти, который занимает не менее 15 байт, где переменные будут располагаться следующим образом:
Допустим, для переменной i8 выделен 1 байт по адресу 0x100, тогда i16 располагается по адресу 0x101, а i32 - по адресу 0x103.
С помощью оператора dup можно определить повторяющийся набор данных:
variable type size dup (value)
Вначале также указывается имя переменной, затем тип. Далее идет размер - сколько элементов указанного типа будет содержать переменная. В конце в скобках указывается значение, которое получит каждый элемент. Например:
numbers byte 5 dup (0)
Здесь переменная numbers состоит из 5 байт, каждый из которых равен 0.
Также можно определить все элементы по отдельности:
numbers byte 1, 2, 3, 4, 5
Здесь опять же numbers состоит из 5 байт.
При определении набора данных отдельные значения можно переносить на другую строку:
numbers byte 1, 2, 3, 4 5, 6, 7, 8
Здесь numbers состоит из 8 байт. Подобным образом легко представлять двухмерные массивы.
Если мы хотим определить переменную, но не хотим инициализировать ее никаким значением, то вместо конкретного значения указывается знак вопроса:
.data i64 qword ? text byte 5 dup (?)
Здесь переменная i64 хранит произвольное значение (обычно это 0), а переменная text - набор произвольных значений (5 нулевых байтов).
Секция .data?
применяется для определения неинициализированных значений:
.data? i64 qword ? text byte 5 dup (?)
Секция .const применяется для определения таких значений, которые нельзя изменить и которые доступны только для чтения:
.const c32 dword 22 ; константа - ее нельзя изменить .code main proc mov eax, c32 ; помещаем значение константы в регистр EAX ret main endp end
Стоит отметить, что эти секции могут идти в произвольном порядке и могут определяться по нескольку раз в одном и том же файле программы:
.data n1 dword 1 .const c1 dword 11 .data n2 dword 2 .const c2 dword 12 .code main proc mov eax, c2 ret main endp end
Директива typedef применяется для определения псевдонимов:
int32 typedef sdword long typedef sqword int16 typedef sword .data n1 int32 32 n2 long 64 n3 int16 16 .code main proc mov eax, n1 ret main endp end
В данном случае вместо типа sdword можно использовать его псевдоним int32, а вместо типов sqword и sword - псевдонимы long и int16. Стоит учитывать, что некоторые идентификаторы являются
зарезервированными словами, например, int
, поэтому их нельзя использовать в качестве псевдонимов.
Ассемблер позволяет использовать числа в различных системах исчисления. По умолчанию все числа рассматриваются как числа десятичной системы. Чтобы указать, что число преставляет двоичную систему, после числа указывается суффикс b. А для индикации числа в шестнадцатиричной системе применяется суффикс h:
.data i64 qword 80h ; число в 16-й системе - 128 i8 byte 00001011b ; число в двоичной системе - 11
Здесь переменная i64 инициализирована число в шестнадцатиричной системе - 80h. В десятичной системе это 128. А переменная i8 представляет число в двоичной системе, которое в десятичной равно 11.
Если щестнадцатеричное число начинается на алфавитный символ (A-F), то перед ним указывается 0:
.data n dword 0FFh ; n = 255