Основы ассемблера MASM для Intel x86-64

Определение и типы данных. Секция .data

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

Ассемблер позволяет определять в программе объекты, которые хранят некоторые данные. Использование переменных значительно упрощает использование данных в программы. Объявления данных имеют следующую форму:

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

Переменные определяются в секции данных, которая задается с помощью директивы .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 байт, где переменные будут располагаться следующим образом:

Секция data в ассемблере

Допустим, для переменной 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? применяется для определения неинициализированных значений:

.data?
i64 qword ?
text byte 5 dup (?)

Секция .const

Секция .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. Определение псевдонимов

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