Ассемблер что такое db
Перейти к содержимому

Ассемблер что такое db

  • автор:

Данные в ассемблере

Секции .data, .data? и .const нужны для определения данных программы. Место под данные резервируется с помощью директив db, dw, dd, dq, dt.

db - 1 байт dw - 2 байта - 1 слово dd - 4 байта - 2 слова dq - 8 байт - 4 слова dt - 10 байт

Секция .data наиболее универсальная мы резервируем память под данные и сразу же инициализируем их, т.е. задаём им начальные значения. Все данные из этой секции включаются в исполнимый файл. Секция .data? менее гибкая, так как данные нельзя инициализировать. Все данные в этой секции не включаются в исполнимый файл, поэтому место только резервируется, но начальные значения не задаются. Данные в обеих секциях имеют полный режим доступа. Секция .const предназначена только чтения. Но ошибок не возникает при попытке изменить эти данные (. ). Эта секция самая бесполезная.

.data Perem dd 0000FF11h X1 dw 01235h Binary db 00111010b dd 11225599h decimal dw 28d large dq 01123456789ABCDEFh dw 1011100101010111b .data? Perem1 dd ? Perem2 dq ? Perem5 dw ? Dd ? Dw ? Perem4 db ? ..const Const1 dd 012345678h dw 768d

Строки.

В ассемблере можно задавать только ANSI строки, Unicode строки сложнее задавать и для их обработки существует целый ряд API функций. В ассемблере также можно вместо присваивания однобайтовой переменной некоторого числа можно присвоить переменно букву. Но, в конечном счете, эта переменная будет равна коду буквы в кодировке ANSI. При инициализации символа можно использовать и кавычки и апострофы — без разницы.

.data Char1 db 'W' Char2 db 'Й' db "r"

Всё выше написанное тоже самое что и:

.data Char1 db 57h Char2 db 0C9h db 72h

При объявлении строк можно просто написать стоку после директивы db. Это воспринимается как последовательность символов

String db "ASM" Тоже самое: String db 'A' Db "S" Db "M"
String db 41h Db 53h Db 4Dh
String db 41h, 53h, 4Dh

При передаче строк функциям в качестве параметров надо чтобы в конце строки был 0, для того чтобы функция смогла найти конец строки.

String db "ASM",0 Msg db "First ASSEMBLER program",0 Ttl db 'Hello, World. ',0

Заполнение данными.

Иногда нужно описать много одинаковых переменных примерно штук 30. Вы будете делать так

db 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ; 30 раз

Это неудобно и некрасиво, тем более можно обсчитаться. Для сделана директива DUP.

db 30 DUP (0) ; тот же результат

в скобках указываем, чем надо заполнять, можно использовать символы в кавычках, обязательно чтобы размер в скобках совпадал с директивой.

Dd 10 DUP ("в") Dw 45h DUP (0DF23h) Dd 100b DUP (12345678h)

С данными всё понятно. Читаем следующий урок. На 6 уроке мы будем говорить о метках и их использовании.

Ассемблер что такое db

Язык:
Русский
English

Директивы встроенного ассемблера DB, DW и DD

Встроенный ассемблер поддерживает три директивы ассемблера: DB (определить байт), DW (определить слово) и DD (определить двойное слово).

Директива Тип операнда Диапазон значений Ассемблер генерирует
DB Константа 128..255 1 байт
Строка Любая длина Последовательность байтов,
символов соответствующих ASCII-кодам
каждого символа
DW Константа -32,768..65,535 1 слово
Указатель Близкий указатель(смещение)
DD Константа -2,147,483,648.. 1 двойное слово
4,294,967,295
Указатель Дальний указатель
(смещение:сегмент)

Данные, созданные директивами DB, DW и DD всегда хранятся в сегменте кода.

Чтобы создать неинициализированные или инициализированные данные в сегменте данных, используйте объявления в стандартных разделах Pascal Var или Const .

Вот примеры директив DB, DW и DD:

Директива Операнд Результат
DB 0FFH Один байт
DB 0,99 Два байта
DB ‘A’ Ord(‘A’)
DB ‘Hello. ‘, 0DH, 0AH Строка+CR/LF
DB 12, «Turbo Pascal» строка стиля Pascal

DW 0FFFFH Одно слово
DW 0,9999 Два слова
DW ‘A’ То же, что и DB ‘A’, 0
DW ‘BA’ То же, что и DB ‘A’, ‘B’
DW MyVar Смещение MyVar
DW MyProc Смещение MyProc

DD 0FFFFFFFFH Одно двойное слово
DD 0,999999999 Два двойных слова
DD ‘A’ То же, что и DB ‘A’, 0, 0, 0
DD ‘DCBA’ То же, что и DB ‘A’, ‘B’, ‘C’, ‘D’
DD MyVar Указатель на MyVar
DD MyProc Указатель на MyProc

Единственый вид символов, которые могут быть определены в ассемблерном операторе — это метки . Все переменные должны быть объявлены с использованием синтаксиса Pascal.

Ассемблер что такое db

Компилятор поддерживает ряд директив. Директивы не транслируются непосредственно в код. Вместо этого они используются для указания положения в программной памяти, определения макросов, инициализации памяти и т.д. Список директив приведён в таблице 3.5

Таблица 3.5 Список директив ассемблера

Директива Описание
BYTE Зарезервировать байты в ОЗУ
CSEG Программный сегмент
DB Определить байты во флэш или EEPROM
DEF Назначить регистру символическое имя
DEVICE Определить устройство для которого компилируется программа
DSEG Сегмент данных
DW Определить слова во флэш или EEPROM
ENDM Конец макроса
EQU Установить постоянное выражение
ESEG Сегмент EEPROM
EXIT Выйти из файла
INCLUDE Вложить другой файл
LIST Включить генерацию листинга
LISTMAC Включить разворачивание макросов в листинге
MACRO Начало макроса
NOLIST Выключить генерацию листинга
ORG Установить положение в сегменте
SET Установить переменный символический эквивалент выражения

Все директивы предваряются точкой.

BYTE — Зарезервировать байты в ОЗУ

Директива BYTE резервирует байты в ОЗУ. Если Вы хотите иметь возможность ссылаться на выделенную область памяти, то директива BYTE должна быть предварена меткой. Директива принимает один обязательный параметр, который указывает количество выделяемых байт. Эта директива может использоваться только в сегменте данных(смотреть директивы CSEG и DSEG). Выделенные байты не инициализируются.

МЕТКА: .BYTE выражение

.DSEG var1: .BYTE 1 ; резервирует 1 байт для var1 table: .BYTE tab_size ; резервирует tab_size байт .CSEG ldi r30,low(var1) ; Загружает младший байт регистра Z ldi r31,high(var1) ; Загружает старший байт регистра Z ld r1,Z ; Загружает VAR1 в регистр 1

CSEG — Программный сегмент

Директива CSEG определяет начало программного сегмента. Исходный файл может состоять из нескольких программных сегментов, которые объединяются в один программный сегмент при компиляции. Программный сегмент является сегментом по умолчанию. Программные сегменты имеют свои собственные счётчики положения, которые считают не побайтно, а пословно. Директива ORG может быть использована для размещения кода и констант в необходимом месте сегмента. Директива CSEG не имеет параметров.

.DSEG ; Начало сегмента данных vartab: .BYTE 4 ; Резервирует 4 байта в ОЗУ .CSEG ; Начало кодового сегмента const: .DW 2 ; Разместить константу 0x0002 в памяти программ mov r1,r0 ; Выполнить действия

DB — Определить байты во флэш или EEPROM

Директива DB резервирует необходимое количество байт в памяти программ или в EEPROM. Если Вы хотите иметь возможность ссылаться на выделенную область памяти, то директива DB должна быть предварена меткой. Директива DB должна иметь хотя бы один параметр. Данная директива может быть размещена только в сегменте программ (CSEG) или в сегменте EEPROM (ESEG).

Параметры, передаваемые директиве — это последовательность выражений разделённых запятыми. Каждое выражение должно быть или числом в диапазоне (-128..255), или в результате вычисления должно давать результат в этом же диапазоне, в противном случае число усекается до байта, причём БЕЗ выдачи предупреждений.

Если директива получает более одного параметра и текущим является программный сегмент, то параметры упаковываются в слова (первый параметр — младший байт), и если число параметров нечётно, то последнее выражение будет усечено до байта и записано как слово со старшим байтом равным нулю, даже если далее идет ещё одна директива DB.

МЕТКА: .DB список_выражений

.CSEG consts: .DB 0, 255, 0b01010101, -128, 0xaa .ESEG const2: .DB 1,2,3

DEF — Назначить регистру символическое имя

Директива DEF позволяет ссылаться на регистр через некоторое символическое имя. Назначенное имя может использоваться во всей нижеследующей части программы для обращений к данному регистру. Регистр может иметь несколько различных имен. Символическое имя может быть переназначено позднее в программе.

.DEF Символическое_имя = Регистр

.DEF temp=R16 .DEF ior=R0 .CSEG ldi temp,0xf0 ; Загрузить 0xf0 в регистр temp (R16) in ior,0x3f ; Прочитать SREG в регистр ior (R0) eor temp,ior ; Регистры temp и ior складываются по исключающему или

DEVICE — Определить устройство для которого компилируется программа

Директива DEVICE позволяет указать для какого устройства компилируется программа. При использовании данной директивы компилятор выдаст предупреждение, если будет найдена инструкция, которую не поддерживает данный микроконтроллер. Также будет выдано предупреждение, если программный сегмент, либо сегмент EEPROM превысят размер допускаемый устройством. Если же директива не используется то все инструкции считаются допустимыми, и отсутствуют ограничения на размер сегментов.

.DEVICE AT90S1200 |AT90S2313 | AT90S2323 | AT90S2333 | AT90S2343 | AT90S4414 | AT90S4433 | AT90S4434 | AT90S8515 | AT90S8534 | AT90S8535 | ATtiny11 | ATtiny12 | ATtiny22 | ATmega603 | ATmega103

.DEVICE AT90S1200 ; Используется AT90S1200 .CSEG push r30 ; Эта инструкция вызовет предупреждение ; поскольку AT90S1200 её не имеет

DSEG — Сегмент данных

Директива DSEG определяет начало сегмента данных. Исходный файл может состоять из нескольких сегментов данных, которые объединяются в один сегмент при компиляции. Сегмент данных обычно состоит только из директив BYTE и меток. Сегменты данных имеют свои собственные побайтные счётчики положения. Директива ORG может быть использована для размещения переменных в необходимом месте ОЗУ. Директива не имеет параметров.

.DSEG ; Начало сегмента данных var1: .BYTE 1 ; зарезервировать 1 байт для var1 table: .BYTE tab_size ; зарезервировать tab_size байт. .CSEG ldi r30,low(var1) ; Загрузить младший байт регистра Z ldi r31,high(var1) ; Загрузить старший байт регистра Z ld r1,Z ; Загрузить var1 в регистр r1

DW — Определить слова во флэш или EEPROM

Директива DW резервирует необходимое количество слов в памяти программ или в EEPROM. Если Вы хотите иметь возможность ссылаться на выделенную область памяти, то директива DW должна быть предварена меткой. Директива DW должна иметь хотя бы один параметр. Данная директива может быть размещена только в сегменте программ (CSEG) или в сегменте EEPROM (ESEG). Параметры передаваемые директиве — это последовательность выражений разделённых запятыми. Каждое выражение должно быть или числом в диапазоне (-32768..65535), или в результате вычисления должно давать результат в этом же диапазоне, в противном случае число усекается до слова, причем БЕЗ выдачи предупреждений.

МЕТКА: .DW expressionlist

.CSEG varlist: .DW 0, 0xffff, 0b1001110001010101, -32768, 65535 .ESEG eevarlst: .DW 0,0xffff,10

ENDMACRO — Конец макроса

Директива определяет конец макроопределения, и не принимает никаких параметров. Для информации по определению макросов смотрите директиву MACRO.

.MACRO SUBI16 ; Начало определения макроса subi r16,low(@0) ; Вычесть младший байт первого параметра sbci r17,high(@0) ; Вычесть старший байт первого параметра .ENDMACRO

EQU — Установить постоянное выражение

Директива EQU присваивает метке значение. Эта метка может позднее использоваться в выражениях. Метка которой присвоено значение данной директивой не может быть переназначена и её значение не может быть изменено.

.EQU метка = выражение

.EQU io_offset = 0x23 .EQU porta = io_offset + 2 .CSEG ; Начало сегмента данных clr r2 ; Очистить регистр r2 out porta,r2 ; Записать в порт A

ESEG — Сегмент EEPROM

Директива ESEG определяет начало сегмента EEPROM. Исходный файл может состоять из нескольких сегментов EEPROM, которые объединяются в один сегмент при компиляции. Сегмент EEPROM обычно состоит только из директив DB, DW и меток. Сегменты EEPROM имеют свои собственные побайтные счётчики положения. Директива ORG может быть использована для размещения переменных в необходимом месте EEPROM. Директива не имеет параметров.

.DSEG ; Начало сегмента данных var1: .BYTE 1 ; зарезервировать 1 байт для var1 table: .BYTE tab_size ; зарезервировать tab_size байт. .ESEG eevar1: .DW 0xffff ; проинициализировать 1 слово в EEPROM

EXIT — Выйти из файла

Встретив директиву EXIT компилятор прекращает компиляцию данного файла. Если директива использована во вложенном файле (см. директиву INCLUDE), то компиляция продолжается со строки следующей после директивы INCLUDE. Если же файл не является вложенным, то компиляция прекращается.

.EXIT ; Выйти из данного файла

INCLUDE — Вложить другой файл

Встретив директиву INCLUDE компилятор открывает указанный в ней файл, компилирует его пока файл не закончится или не встретится директива EXIT, после этого продолжает компиляцию начального файла со строки следующей за директивой INCLUDE. Вложенный файл может также содержать директивы INCLUDE.

; файл iodefs.asm: .EQU sreg = 0x3f ; Регистр статуса .EQU sphigh = 0x3e ; Старший байт указателя стека .EQU splow = 0x3d ; Младший байт указателя стека ; файл incdemo.asm .INCLUDE iodefs.asm ; Вложить определения портов in r0,sreg ; Прочитать регистр статуса

LIST — Включить генерацию листинга

Директива LIST указывает компилятору на необходимость создания листинга. Листинг представляет из себя комбинацию ассемблерного кода, адресов и кодов операций. По умолчанию генерация листинга включена, однако данная директива используется совместно с директивой NOLIST для получения листингов отдельных частей исходных файлов.

.NOLIST ; Отключить генерацию листинга .INCLUDE "macro.inc" ; Вложенные файлы не будут .INCLUDE "const.def" ; отображены в листинге .LIST ; Включить генерацию листинга

LISTMAC — Включить разворачивание макросов в листинге

После директивы LISTMAC компилятор будет показывать в листинге содержимое макроса. По умолчанию в листинге показывается только вызов макроса и передаваемые параметры.

.MACRO MACX ; Определение макроса add r0,@0 ; Тело макроса eor r1,@1 .ENDMACRO ; Конец макроопределения .LISTMAC ; Включить разворачивание макросов MACX r2,r1 ; Вызов макроса (в листинге будет показано тело макроса)

MACRO — Начало макроса

С директивы MACRO начинается определение макроса. В качестве параметра директиве передаётся имя макроса. При встрече имени макроса позднее в тексте программы, компилятор заменяет это имя на тело макроса. Макрос может иметь до 10 параметров, к которым в его теле обращаются через @0-@9. При вызове параметры перечисляются через запятые. Определение макроса заканчивается директивой ENDMACRO.

По умолчанию в листинг включается только вызов макроса, для разворачивания макроса необходимо использовать директиву LISTMAC. Макрос в листинге показывается знаком +.

.MACRO SUBI16 ; Начало макроопределения subi @1,low(@0) ; Вычесть младший байт параметра 0 из параметра 1 sbci @2,high(@0) ; Вычесть старший байт параметра 0 из параметра 2 .ENDMACRO ; Конец макроопределения .CSEG ; Начало программного сегмента SUBI16 0x1234,r16,r17 ; Вычесть 0x1234 из r17:r16

NOLIST — Выключить генерацию листинга

Директива NOLIST указывает компилятору на необходимость прекращения генерации листинга. Листинг представляет из себя комбинацию ассемблерного кода, адресов и кодов операций. По умолчанию генерация листинга включена, однако может быть отключена данной директивой. Кроме того данная директива может быть использована совместно с директивой LIST для получения листингов отдельных частей исходных файлов

.NOLIST ; Отключить генерацию листинга .INCLUDE "macro.inc" ; Вложенные файлы не будут .INCLUDE "const.def" ; отображены в листинге .LIST ; Включить генерацию листинга

ORG — Установить положение в сегменте

Директива ORG устанавливает счётчик положения равным заданной величине, которая передаётся как параметр. Для сегмента данных она устанавливает счётчик положения в SRAM (ОЗУ), для сегмента программ это программный счётчик, а для сегмента EEPROM это положение в EEPROM. Если директиве предшествует метка (в той же строке) то метка размещается по адресу указанному в параметре директивы. Перед началом компиляции программный счётчик и счётчик EEPROM равны нулю, а счётчик ОЗУ равен 32 (поскольку адреса 0-31 заняты регистрами). Обратите внимание что для ОЗУ и EEPROM используются побайтные счётчики а для программного сегмента — пословный.

.DSEG ; Начало сегмента данных .ORG 0x37 ; Установить адрес SRAM равным 0x37 variable: .BYTE 1 ; Зарезервировать байт по адресу 0x37H .CSEG .ORG 0x10 ; Установить программный счётчик равным 0x10 mov r0,r1 ; Данная команда будет размещена по адресу 0x10

SET — Установить переменный символический эквивалент выражения

Директива SET присваивает имени некоторое значение. Это имя позднее может быть использовано в выражениях. Причем в отличии от директивы EQU значение имени может быть изменено другой директивой SET.

.SET имя = выражение

.SET io_offset = 0x23 .SET porta = io_offset + 2 .CSEG ; Начало кодового сегмента clr r2 ; Очистить регистр 2 out porta,r2 ; Записать в порт A
Выражения

Компилятор позволяет использовать в программе выражения которые могут состоять операндов, операторов и функций. Все выражения являются 32-битными.

Операнды

Могут быть использованы следующие операнды:

  • Метки определённые пользователем (дают значение своего положения).
  • Переменные, определённые директивой SET
  • Константы, определённые директивой EQU
  • Числа, заданные в формате:
    Десятичном (принят по умолчанию): 10, 255 Шестнадцатеричном (два варианта записи): 0x0a, $0a, 0xff, $ff Двоичном: 0b00001010, 0b11111111 Восьмеричном (начинаются с нуля): 010, 077
  • PC — текущее значение программного счётчика (Program Counter)

Работа с данными и памятью

Все используемые в программе данные имеют определенный тип, который определяется разрядностью данных. Архитектура Intel x86-64 поддерживает следующие типы данных:

  • byte : 8-разрядное целое число
  • word : 16-разрядное целое число или слово
  • dword : 32-разрядное целое число или двойное слово
  • qword : 64-разрядное целое число или четверное слово

Например, 8-разрядный регистр AL по сути представляет байт, 16-разрядный AX — слово, 32-разрядный EAX — двойное слово, а 64-разрядный регистр RAX — четверное слово.

Определение данных в программе

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

label directive value

label или метка представляет название переменной, которая может представлять произвольный идентифкатор. После названия переменной идет directive — директива, которая устанавливает тип данных. Это может быть одна из следующих директив:

  • db (define byte): определяет целое число размером в 1 байт
  • dw (define word): определяет целое число размером в 2 байта (слово)
  • dd (define dword): определяет целое число размером в 4 байта (так называемое двойное слово)
  • dq (define qword/quad): определяет целое число размером в 8 байт (четверное слово)

После директивы данных идет собственно значение. Например:

number dq 22

Стоит отметить, что после названия переменной можно указывать двоеточие, как для обычной метки, но оно в принципе необязательно:

number: dq 22

Здесь определена переменная number, которая имеет тип qword , то есть представляет 8 байт, и которая равна 22.

С каждой переменной ассемблер будет ассоциировать некоторый свободный участок памяти. Например, переменная number имеет тип qword и занимает 8 байт, соответственно ассемблер найдет в памяти свободный 8 байт и ассоциирует их с этой переменной.

Расположение и использование данных

Где определяются данные? В программе на ассемблере можно опредлять данные в различных секциях.

Определение данных в секции .text

Например, в секции кода .text :

global _start section .text number: dq 123 ; определяем объект number внутри секции .text _start: ; остальной код программы

Единственное, что не следует смешивать определение данных с инструкциями, поэтому при определении данных в секции .text данные должны определяться либо до первой инструкции, либо после последней инструкции.

После определения переменные можно использовать в программе как обычные данные. Например, можно помещать в регистр или, наоборот, сохранять значение из регистра в переменную. Как же получить переменную, например, в регистр?

Стоит сразу сказать, что имя переменной, например, «number», само по себе представляет ее адрес, а не непосредственное значение. Например, возьмем следующую программу на Linux:

global _start section .text number: dq 124 ; определяем объект number внутри секции .text _start: mov rdi, number mov rax, 60 syscall

Здесь в регистр RDI помещается адрес переменной number, но не само значение. Чтобы получить значение, надо поместить название переменной в квадратные скобки:

[название_переменной]

Например, возьмем следующую программу на Linux :

global _start section .text number: dq 124 ; определяем объект number внутри секции .text _start: mov rdi, [number] ; rdi = 124 mov rax, 60 syscall

Инструкция mov rdi, [number] помещает в регистр rdi значение переменной number, то есть число 124.

Выше мы рассмотрели программу получения значения переменной на Linux . На Windows все немного сложнее, многое зависит от компоновщика, который применяется при сборке программы в исполняемое приложение. Например, возьмем следующую программу на Windows :

global _start section .text number: db 124 ; определяем объект number внутри секции .text _start: mov rax, [number] ret

При использовании компоновщика ld , который идет в комплекте GCC, мы столкнемся с ошибкой релокаций:

c:\asm>nasm -f win64 hello.asm -o hello.o c:\asm>ld hello.o -o hello.exe hello.o:hello.asm:(.text+0x5): relocation truncated to fit: IMAGE_REL_AMD64_ADDR32 against `.text' c:\asm>

Если мы используем компоновщик от Microsoft — link.exe , то мы также столкнемся с ошибкой релокаций, хотя само сообщение об ошибке будет выглядеть несколько иначе:

c:\asm>nasm -f win64 hello.asm -o hello.o c:\asm>link hello.o /entry:_start /subsystem:console /out:hello.exe Microsoft (R) Incremental Linker Version 14.37.32824.0 Copyright (C) Microsoft Corporation. All rights reserved. hello.o : error LNK2017: 'ADDR32' relocation to '.text' invalid without /LARGEADDRESSAWARE:NO LINK : fatal error LNK1165: link failed because of fixup errors c:\asm>

Суть ошибки: мы пытаемся создать 64-разрядное приложение, которое использует 32-разрядные адреса. В принципе ошибка подсказывает один из вариантов решений — добавить к вызову компоновщика дополнительную опцию — /LARGEADDRESSAWARE:NO :

link hello.o /entry:_start /subsystem:console /out:hello.exe /LARGEADDRESSAWARE:NO

Тем не менее это решает проблему отчасти. В качестве решения мы можем использовать оператор rel , который позволяет использовать адрес относительно регистра RIP (указателя инструкций):

[rel название_переменной]

Так, изменим программу для Windows:

global _start section .text number: dq 124 ; определяем объект number внутри секции .text _start: mov rax, [rel number] ; используем адрес number относительно регистра RIP ret

Теперь при использовании компоновщика от Microsoft у нас не возникнет проблем:

c:\asm>nasm -f win64 hello.asm -o hello.o c:\asm>link hello.o /entry:_start /subsystem:console /out:hello2.exe Microsoft (R) Incremental Linker Version 14.37.32824.0 Copyright (C) Microsoft Corporation. All rights reserved. c:\asm>hello2.exe c:\asm>echo 124 124 c:\asm>

Тем не менее при использовании компоновщика ld от GCC мы хоть и скомпонуем программу, но получим некорреткные результаты:

c:\asm>nasm -f win64 hello.asm -o hello.o c:\asm>ld hello.o -o hello.exe c:\asm>hello.exe c:\asm>echo -1073741819 -1073741819 c:\asm>

Таким образом, в зависимости от используемого компоновщика мы можем получить разные результаты. Мы можем играть с параметрами компоновщиков, но в любом случае секция .text не слишком хорошо подходит для хранения данных.

Секция .data

Секция данных задается с помощью директивы .data . Эта директива сообщает ассемблеру, что дальше идут объявления данных. Например, программа на Linux:

global _start section .data ; определение секции .data number dq 125 ; определяем объект number внутри секции .data section .text _start: mov rdi, [number] ; получаем данные из секции .data mov rax, 60 syscall

Аналогичная программа для Windows:

global _start section .data ; определение секции .data number dq 125 ; определяем объект number внутри секции .data section .text _start: mov rax, [rel number] ret

Стоит отметить, что в отличие от программы для Linux, здесь применяется оператор rel . Тем не менее и компоновщик ld от GCC, и компоновщик от Microsoft успешно скомпонуют данную программу.

Если секция .data содержит несколько переменных, то ассемблер с каждой из этих переменной ассоциирует некоторый участок памяти. Причем в памяти все переменные будут расположены друг за другом. Например, возьмем следующую секцию данных

section .data i64 dq 8 i32 dd 4 i16 dw 2 i8 db 1

Для переменной i8 выделяется 1 байт, для i16 — 2 байта, для i32 — 4 байта и для i64 — 8 байт. В итоге для секции .data будет выделен блок памяти, который занимает не менее 15 байт, где переменные будут располагаться следующим образом:

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

Допустим, для переменной i64 выделено 8 байт по адресу 0x0000, тогда i32 располагается по адресу 0x0008, а i16 — по адресу 0x000С и i8 по адресу 0x000E.

Изменение данных

Eще один момент, который надо учитывать при определении данных в секции .text : данные, определенные в этой секции, по сути являются константами, то есть их значения нельзя изменить (хотя для простоты все определенные таким образом данные в программе именуют переменными). Например, возьмем следующую программу под Linux:

global _start section .text number dq 124 ; определяем объект number внутри секции .text _start: mov rdx, 111 mov [number], rdx ; пытаемся записать в number число 111 mov rdi, [number] mov rax, 60 syscall

Здесь мы пытаемся поместить значение регистра RDX в переменную number:

mov [number], rdx

Однако объект number определен в секции .text , поэтому он не изменяем. И хотя ассемблер может успешно скомпилировать программу, Linux сгенерирует общую ошибку защиты (general protection fault), если мы попытаемся сохранить какие-либо данные в секции кода:

eugene@Eugene:~/asm# nasm -f elf64 hello.asm -o hello.o eugene@Eugene:~/asm# ld hello.o -o hello eugene@Eugene:~/asm# ./hello Segmentation fault eugene@Eugene:~/asm#

Собственно при определении объектов в секции .text у нас получаются константы — объекты, значения которых нельзя изменить. Поэтому для определения переменных и в целом для определения данных больше подходят другие секции, в частности, секция .data. Изменим программу, перенеся данные в секуцию .data :

global _start section .data ; определение секции .data number dq 125 ; определяем объект number внутри секции .data section .text _start: mov rdx, 111 mov [number], rdx ; пытаемся записать в number число 111 mov rdi, [number] mov rax, 60 syscall

Теперь при выполнении проблем не возникнет:

eugene@Eugene:~/asm# nasm -f elf64 hello.asm -o hello.o eugene@Eugene:~/asm# ld hello.o -o hello eugene@Eugene:~/asm# ./hello eugene@Eugene:~/asm# echo $? 111 eugene@Eugene:~/asm#

Аналогичный пример для Windows:

global _start section .data ; определение секции .data number dq 125 ; определяем объект number внутри секции .data section .text _start: mov rdx, 111 mov [rel number], rdx ; пытаемся записать в number число 111 mov rax, [rel number] ret

Определение массивов данных

Ассемблер NASM позволяет определить набор данных. Если нам известны все элементы набора, то мы их можем перечислить через запятую:

nums dq 11, 12, 13, 14, 15, 16, 17

Здесь переменная nums представляет набор из 7 чисел, каждое из которых занимает 8 байтов. При при обращении к этому набору по имени переменной мы фактически обращаемся к первому элементу этого набора:

global _start section .data nums dq 11, 12, 13, 14, 15, 16, 17 ; набор из 7-ми 8-разрядных чисел section .text _start: mov rdi, [nums] ; rdi = 11 mov rax, 60 syscall

С помощью директивы times можно определить массив, в котором некоторое значение повторяется определенное количество раз:

times количество тип значение

Первый параметр директивы указывает на количество — сколько определить чисел. Второй параметр представляет тип чисел, например, db , dq и т.д. Третий параметр указывает на само значение. Например:

numbers: times 10 db 2

Здесь переменная numbers представляет массив из 10 чисел, каждое из которых равно 2 и представляет 1 байт (тип db )

Для упрошения определения наборов большего размера NASM имеет дополнительные директивы resX . Вместо X указывается суффикс, обозначающий тип данных:

  • resb : выделяет некоторое количество байт
  • resw : выделяет некоторое количество слов (2-х байтовых чисел)
  • resd : выделяет некоторое количество двойных слов (4-х байтовых чисел)
  • resq : выделяет некоторое количество четверных слов (8-х байтовых чисел)

В качестве параметра этим директивам передается количество чисел в создаваемом наборе. По умолчанию каждое такое число будет инициализировано нулем. Например:

buffer resb 10

Определяет переменную buffer — набор из 10 байт. Другой пример:

numbers resq 5

Определяет набор из 5 четверных слов.

Дополнительные секции данных

Кроме собственно секции .data в программе на ассемблере может использоваться еще ряд секций. Секция .rodata содержит данные, которые нельзя изменить (то есть по сути константы). Он загружается в память при загрузке приложения и помечается как доступный только для чтения. Попытки записи в эту память приведут к остановке программы.

global _start section .rodata ; определение секции .rodata number dq 125 ; число можно получить, но нельзя изменить section .text _start: mov rdi, [number] ; mov [number], rdi ; при попытке записи - Segmentation fault mov rax, 60 syscall

Секция .bss (сокращение от Block Started by Symbol) содержит неинициализированные данные, для которых известен размер, но неизвестно значение. Это экономит место в исполняемом файле, особенно если здесь большой объем данных. Операционная система инициализирует раздел .bss всеми нулями. Можно зарезервировать данные в разделе .bss, используя директивы resb/resw/resd/resq . Например, если нам нужен массив, значения которого в начале программы не столь важны и который инициализируется уже по ходу программу, например, мы в него загружаем данные из файла, то подобный массив как раз можно определить в секции .bss:

global _start section .bss buffer resq 1024 ; массив из 1024-х 8-байтовых чисел section .text _start: mov rdi, [buffer] ; rdi = 0 mov rax, 60 syscall

В данном случае в секции .bss определен массив из 1024-х 8-байтовых чисел, который в сумме занимает 1024 * 8 = 8184 байт. Однако поскольку этот массив размещен в секции .bss , то эти байты не входят в размер файла, они будут выделяться при запуске программы.

Преобразования данных

При работе с данными в ассемблере мы вынуждены учитывать их разрядность. Например, возьмем следующий код программы для Linux:

global _start section .data number db 12 ; однобайтовое число section .text _start: mov rdi, [number] mov rax, 60 syscall

Здесь значение 8-разрядного числа number помещается в 64-разрядный регистр RDI. При компиляции и выполнении программ у нас не возникнет особых проблем:

eugene@Eugene:~/asm# nasm -f elf64 hello.asm -o hello.o eugene@Eugene:~/asm# ld hello.o -o hello eugene@Eugene:~/asm# ./hello eugene@Eugene:~/asm# echo $? 12 eugene@Eugene:~/asm#

Аналогично не возникнет проблем с программой для Windows:

global _start section .data number db 12 ; однобайтовое число section .text _start: mov rax, [rel number] ret
c:\asm>nasm -f win64 hello.asm -o hello.o c:\asm>ld hello.o -o hello.exe c:\asm>hello.exe c:\asm>echo %ERRORLEVEL% 12 c:\asm>

Изменим программу для Windows следующим образом:

global _start section .data nums db 1, 2, 0, 0, 0, 0, 0, 0 ; 8 однобайтовых числа section .text _start: mov rax, [rel nums] ret

Тепепь nums представляет массив из 8 байтов. Имя переменной — адрес первого элемента массива, соответственно мы ожидаем получить в регистр RAX первый элемент массива — число 1. Однако статусный код возврата покажет что-то не то:

c:\asm>nasm -f win64 hello.asm -o hello.o c:\asm>ld hello.o -o hello.exe c:\asm>hello.exe c:\asm>echo %ERRORLEVEL% 513 c:\asm>

Я специально выбрал версию для Windows, потому что на Linux в принципе мы можем и не столкнуться с этой проблемой. Почему мы получаем число 513? Число 513 в двоичной системе равно 1000000001 или добавим в начало ненужные нули и получим 00000010_00000001 . Младший байт этого числа 00000001 равен 1, а следующий байт 00000010 в десятичной системе равен 2. То есть 8 байтов массива были последовательно загружены в регистр RAX. Что неудивительно, так как регистр RAX имеет размер 8 байт. Но, допустим, нам надо загрузить только первый один единственный байт. В этом случае нам надо применить преобразования. Преобразования производятся с помощью операторов-названий типов данных:

  • byte : преобразует в байт
  • word : преобразует в слово
  • dword : преобразует в двойное слов
  • qword : преобразует в четверное слово
mov rax, [rel nums]

нам надо изменить на строку

movzx rax, byte [rel nums]

Выражение byte [rel nums] или byte [nums] получает из массива nums первый байт, а инструкция movzx помещает этот байт в регистр (в младший байт), заполняя остальные байты регистра нулями

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

mov byte [nums], 101

Например, в программе для Linux поместим байт в массив:

global _start section .data nums db 12, 13, 14, 15 ; 4 однобайтовых числа число section .text _start: mov byte [nums], 101 ; nums = 101, 13, 14, 15 movzx rdi, byte [nums] ; rdi = 101 mov rax, 60 syscall

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *