what does the assembly instruction 'db' actually do?
I’m a bit confused as to what the asm instruction db actually does. I’ve read that it defines a byte, but what does that actually mean? Does it move the value into a certain place in memory, or does it predefine a value before the code is assembled?
2 Answers 2
It literally places that byte right there in the executable.
From ARM manual:
〚label:〛 DB expression 〚, expression . 〛
The DB statement initializes memory with one or more byte values. label is a symbol that is assigned the current memory address. expression is a byte value that is stored in memory. Each expression may be a symbol, a string, or an expression.
so, this statement do following:
- set current memory address value for the "label" variable
- set "byte" values from expressions to current and next memory addresses ("expressions" can be one byte or many bytes — a long string)
After the operation, if we compare the new "current" memory address vs the memory address before the operation (hold by label), we can calculate the length of the "expression" list (because each character is one byte and that is same as increase 1 in memory address).
Getting the assembler to calculate the distance between here ( $ in some assemblers) and a label works around any instruction or pseudo-instruction, not just db , and looks like this in some assembly languages. Exact directive syntax varies by assembler (ARMASM, NASM, MASM, FASM, etc. all use db to emit literal bytes into the output, but differ on the details of equ or something else.)
Ассемблер что такое 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.
2.3. Директивы определения данных
Для описания переменных, с которыми работает программа, в языке ассемблера используются директивы определения данных. Одна из них предназначена для описания данных размером в байт, вторая – для описания данных размером в слово, а третья – для описания данных размером в двойное слово. В остальном эти директивы практически не отличаются друг от друга.
Директива DB
По директиве DB (define byte, определить байт) определяются данные размером в байт. Ее синтаксис (без учета возможного комментария в конце) таков:
Встречая такую директиву, ассемблер вычисляет операнды и записывает их значения в последовательные байты памяти. Первому из этих байтов дается указанное имя, по которому на этот байт можно ссылаться из других мест программы.
Существует два основных способа задания операндов директивы DB:
– ? (знак неопределенного значения);
– константное выражение со значением от -128 до 255.
Остальные способы задания операндов производны от этих двух.
По этой директиве описывается переменная X. Для нее отводится один байт памяти, в который ничего не записывается (в этом байте будет то, что осталось от предыдущей программы, и предугадать, что там находится, нельзя). В этом случае говорят, что переменная не получает начального значения.
Где отводится этот байт? Транслируя программу, ассемблер просматривает предложение за предложением размещает соответствующие им машинные представления в последовательных ячейках памяти. Поэтому, встречая директиву DB, он отводит под указанную переменную первый из еще не занятых байтов памяти. Это следует учитывать и, например, не надо вставлять директиву DB между командами.
Выделив байт под переменную, ассемблер запоминает его адрес. Когда он снова встретит в тексте программы имя этой переменной, то он заменит его на данный адрес (в этой замене и заключается трансляция имен). Таким образом, в отличие от машинного языка, в языке ассемблера уже не надо следить за адресами ячеек, в которых размещаются переменные, а достаточно дать переменным имена и затем ссылаться на них по этим именам, все остальное сделает за нас ассемблер.
Адрес ячейки, выделенной переменной с именем X, принято называть значением имени X (не путать с содержимым ячейки по этому адресу!). Кроме того, по описанию переменной ассемблер запоминает, сколько байтов занимает переменная в памяти. Этот размер называется типом имени переменной. Значение (адрес) и тип (размер) имени переменной однозначно определяют ячейку, обозначаемую этим именем. Напомним, что с одного и того же адреса в ПК могут начинаться ячейки разных размеров – и байт, и слово, и двойное слово, поэтому кроме начального адреса ячейки надо знать и ее размер. В связи с этим ассемблер запоминает как адрес переменной, так и ее размер.
В языке асеемблера имеются так называемые операторы. Это общее название таких понятий, как функции и операции (типа арифметических). Об операторах рассказ впереди, а сейчас рассмотрим лишь один из операторов — оператор типа, который записывается так:
Значением этого оператора является размер (в байтах) ячейки, выделенной под переменную с указанным именем. Если переменная описана по директиве DB, т. е. как байтовая переменная, то для ее имени значение этого оператора равно 1.
Отметим, что в языке ассемблера есть стандартная константа с именем BYTE и значением 1, поэтому можно записать так:
ТУРЕ X = BYTE = 1
Операнд — константное выражение со значением от -128 до 255
Мы рассмотрели, как можно описать переменную, которой не присваивается никакого начального значения. Но язык ассемблера позволяет описывать и переменные с начальными значениями. Для этого в качестве операнда директивы DB указывается выражение, которое ассемблер вычислит, и значение которого запишет в ячейку, отведенную под переменную. Это и есть начальное значение переменной. Позже, при выполнении программы, его можно будет, и изменить, можно будет что-то записать в эту ячейку, но к началу выполнения программы в этой ячейке уже будет находиться данное значение.
В простейшем и наиболее распространенном случае начальное значение байтовой переменной задается в виде числа с величиной от -128 до 255. Например:
В DB -2 ; 0FEh (-256-2=254)
По каждой из этих директив ассемблер отводит один байт под переменную и записывает в этот байт указанное число. Таким образом, к началу выполнения программы переменная А будет иметь значение 254, переменная В – значение -2, а переменная С – значение 17h.
Операнд – число, естественно, переводится ассемблером в двоичную систему. При этом неотрицательные числа записываются в память как числа без знака, а отрицательные числа записываются в дополнительном коде (см. комментарии к директивам). В связи с этим и получается, что в качестве операндов можно указывать числа от -128 до 255. Отсюда же следует, что числа 254 и -2 будут представлены в памяти одним и тем же байтом 0FEh (это для нас данные числа различны, а для машины они одинаковы, и ей безразлично, что обозначает байт 0FEh – число со знаком или без знака).
В другом распространенном случае в качестве начального значения переменной указывается символ. Такое значение можно задать двояко: либо указать числовой код этого символа, либо указать сам символ в кавычках. Например, в системе кодировки ASCII код символа «*» равен 2Ah, поэтому следующие две директивы эквивалентны:
Во втором случае ассемблер сам определит код указанного символа и запишет этот код в ячейку памяти. Ясно, что этот вариант лучше — он нагляднее и не требует знания кодов символов, поэтому его обычно и используют на практике.
Мы рассмотрели два основных случая задания начального значения. В общем же случае такое значение указывается любым константным выражением со значением от -128 до 255. (Если значение выходит за эти пределы, то ассемблер зафиксирует ошибку.) Константные выражения аналогичны арифметическим выражениям языков высокого уровня. Мы их рассмотрим позже, а пока лишь отметим, что к таким выражения относится оператор TYPE, поэтому допустима, скажем, следующая директива (имя Q описано выше):
которая эквивалентна директиве V DB 1.
Директива с несколькими операндами
Мы рассмотрели случаи, когда в директиве DB указывается один операнд. Это удобно, когда надо описать скалярную переменную, но неудобно, когда надо описать переменную-массив. В самом деле, если надо, к примеру, описать массив из 4 байтов с некоторыми начальными значениями, то это можно сделать так:
Отметим попутно, что в массивах имя обычно дается только его первому элементу, а остальные оставляют безымянными, поэтому-то в нашем примере имя указано лишь в первой директиве. Если в директиве DB не указано имя, то по ней байт в памяти отводится, но он остается безымянным.
Ясно, что, если в массиве много элементов, то такой способ описания массива слишком громоздок. Поэтому в языке ассемблера допускается упрощенная форма описания массивов, когда он описывается одной директивой, но с несколькими операндами со столькими, сколько элементов в массиве. Например, вместо наших 4 директив можно выписать только одну:
По директиве DB с несколькими операндами ассемблер выделяет в памяти соседние байты памяти, по одному на каждый операнд, и записывает в эти байты значения операндов (для операнда ? ничего не записывает). В нашем примере ассемблер следующим образом заполнит память (рис. 18).
Рис. 18. Размещение в памяти директивы М DB 2,-2. ‘*’
Отметим, что имя, указанное в начале директивы, именует только первый из этих байтов. В связи с этим тип имени М равен 1: TYPE М = BYTE. Остальные же байты остаются безымянными. Для ссылок на них в языке ассемблера используются выражения вида М+k, где k – целое число: М+1 – для ссылки на байт со значением FE, М+2 — для ссылки на следующий байт и т. д. Особо подчеркнем, что запись М+1 не следует понимать как сложение содержимого ячейки с именем М (т. е. числа 2) с числом 1. В языке ассемблера запись вида <имя>±k означает, что к адресу указанного имени надо прибавить (или отнять) число k, в результате чего получится некоторый новый адрес, и вот уже по этому адресу и осуществляется доступ к памяти. Таким образом, данная запись означает сложение/вычитание адресов.
Операнд – строка
Возможно еще одно сокращение в директиве DB: если в ней несколько соседних операндов – символы, то их можно объединить в одну строку. Например, следующие две директивы эквивалентны:
S DB ‘a’, ‘b’, ‘с’ S DB ‘аbc’
Отметим, что и в этом случае тип имени равен 1 (TYPE S = BYTE), т. к. любая из этих директив является сокращением следующих трех директив:
а здесь ясно видно, что имя S обозначает только первый байт.
Вопрос о том, объединять соседние символы в одну строку или нет, а если объединять, то какие именно, решает сам автор программы. Например, нашу директиву можно записать и так:
S DB ‘ab’ , ‘c’ или S DB ‘а’, ‘bc’
Операнд – конструкция повторения DUP
Рассмотрим еще одно возможное сокращение в записи директивы DB. Довольно часто в директиве приходится указывать одинаковые операнды. Например, если мы хотим описать байтовый массив R из 8 сегментов с начальным значением 0 для каждого из них, то это можно сделать так:
Так вот, эту директиву можно записать и короче:
Здесь в качестве операнда использована так называемая конструкция повторения, в которой сначала указывается коэффициент повторения, затем – служебное слово DUP (duplicate, копировать), а за ним в круглых скобках – повторяемая величина.
В общем случае эта конструкция имеет следующий вид:
где k – константное выражение с положительным значением, n≥1, pj – любой допустимый операнд директивы DB (в частности, это может быть снова конструкция повторения). Данная запись является сокращением для k раз повторенной последовательности указанных в скобках операндов (рис. 19):
Рис. 19. Размещение в памяти данных объявленных директивой k DUP (p1, р2. рn)
Например, директивы слева эквивалентны директивам справа:
X DB 2 DUP(‘ab’. l) X DB ‘ab’. 1,’ab’. l
Y DB -7, 3 DUP(0,2 DUP(?)) Y DB -7,0. 0. 0.
(Тип имен X и Y — BYTE.)
Отметим, что вложенность конструкций DUP можно использоваться для наглядного описания многомерных массивов. Например, директиву
A DB 20 DUP(30 DUP(?))
можно рассматривать как описание байтовой матрицы А размера 20×30, в которой элементы расположены в памяти следующим образом: первые 30 байтов это элементы первой строки матрицы, следующие 30 байтов – это элементы второй строки и т. д.
Директива DW
Директивой DW (define word, определить слово) описываются переменные размером в слово. Она аналогична директиве DB, поэтому следует лишь вкратце рассмотреть допустимые виды ее операндов.
По этой директиве ассемблер отводит под переменную А слово памяти, в которое ничего не записывает, т. е. эта переменная не получает начального значения. Тип переменной равен 2, т. к. она занимает два байта. В языке ассемблера есть стандартная константа с именем WORD и значением 2, поэтому данный факт можно записать так:
TYPE A = WORD = 2.
Константное выражение со значением от -32768 до 65535
По этим директивам под переменные В и С отводится по слову памяти и в эти ячейки записываются указанные числа, которые становятся начальными значениями этих переменных.
Как и в случае директивы DB, неотрицательные числа записываются в память как числа без знака, а отрицательные числа – в дополнительном коде. Поэтому числа, которые могут быть заданы как операнды директивы DW, должны принадлежать отрезку [-2 15 , 2 16 -1].
Но здесь имеется и отличие от директивы DB.В ПК числа размером в слово хранятся в памяти в «перевернутом» виде. Так вот, на языке ассемблера такие числа записываются в нормальном, не перевернутом виде, а «переворачиванием» их занимается сам ассемблер, поэтому по этим двум директивам память заполнится следующим образом, представленным на рис. 20.
Рис. 20. Пример заполнения памяти с использованием
С учетом этого при программировании на языке ассемблера можно, в общем-то, забыть о «перевернутом» представлении чисел в памяти ПК.
Частным случаем рассматриваемого вида операнда директивы DW может быть строка из одного или двух символов, например:
Если указана строка из двух символов, тогда ассемблер берет коды указанных символов (в нашем случае — З0h. (код ‘0’) и 31h (код ‘1’)) и образует из них число-слово (3031h), которое и считается начальным значением описываемой переменной (S1). Но как и любое число размером в слово, данное значение будет записано в память в «перевернутом» виде. Если же в правой части директивы DW указан один символ, тогда к нему слева приписывается символ с кодом 0 и дальнейшие действия ассемблера будут такими же, как и в случае двухсимвольной строки. Поэтому по этим двум директивам память будет заполнена следующим образом, представленным на рис. 21.
Рис. 21. Пример заполнения памяти символами
с использованием директивы DW
В связи с тем, что операнды-строки записываются в память в «перевернутом» виде, что, в общем-то, не характерно для строк, то подобные операнды редко указываются в директиве DW.
Адресное выражение
В качестве операнда директивы DW может быть указано адресное выражение, т. е. выражение, значением которого является адрес. Как записываются такие выражения, будет рассмотрено далее, а пока лишь следует отметить, что основной случай адресного выражения — это имя переменной или метка. Поэтому допустим такой пример:
В этом случае ассемблер записывает в слово, выделенное под переменную D, адрес переменной С, который становится начальным значением переменной D.
Несколько операндов, конструкция повторения
В правой части директивы DW можно указать любое число операндов, а также конструкцию повторения. Возможный пример:
Е DW 40000, 3 DUP(?)
Директива DD
По директиве DD (define double word, определить двойное слово) описываются переменные, под которые отводятся двойные слова. Поэтому имена этих переменных имеют тип 4 или DWORD (значением этой стандартной константы как раз является число 4). В остальном эта директива похожа на две предыдущие.
Допустимые типы операндов этой директивы таковы.
Под переменную А выделяется двойное слово, в которое ассемблер ничего не записывает, т. е. переменная А не получает начального значения.
Целое число со значением от -2 31 до 2 32 -1
В данном случае переменная В получает начальное значение, причем это значение ассемблер записывает в память в «перевернутом» виде (рис. 22).
Рис. 22. Пример заполнения памяти с использованием
Константное выражение (со значением от -2 15 до 2 16 -1)
Следует обратить внимание на диапазон возможных значении выражения – он в два раза меньше диапазона чисел, которые можно записать в двойном слове. Дело в том, что в языке ассемблера все выражения вычисляются в области 16-битовых чисел, т. е. результаты всех операций берутся по модулю 216 (10000h). Поэтому построить выражение, значением которого являлось бы 32-битовое или даже 17-битовое число, не удастся. Единственное исключение – это явно задать в директиве DD «большое» число. Если же будет указана хотя бы одна операция, то ответ тут же будет взят по модулю 216. Например, по директиве
X DD 8000h+8002h
начальным значением переменной X станет число 2, а не число 10002h.
Конечно, такая особенность задания начальных значений для переменных размером в двойное слово не очень-то приятна, но так уж устроен язык ассемблера, и это надо учитывать.
Данные в ассемблере
Секции .data, .data? и .const нужны для определения данных программы. Место под данные резервируется с помощью директив db, dw, dd, dq, dt.
Секция .data наиболее универсальная мы резервируем память под данные и сразу же инициализируем их, т.е. задаём им начальные значения. Все данные из этой секции включаются в исполнимый файл. Секция .data? менее гибкая, так как данные нельзя инициализировать. Все данные в этой секции не включаются в исполнимый файл, поэтому место только резервируется, но начальные значения не задаются. Данные в обеих секциях имеют полный режим доступа. Секция .const предназначена только чтения. Но ошибок не возникает при попытке изменить эти данные (. ). Эта секция самая бесполезная.
Строки.
В ассемблере можно задавать только ANSI строки, Unicode строки сложнее задавать и для их обработки существует целый ряд API функций. В ассемблере также можно вместо присваивания однобайтовой переменной некоторого числа можно присвоить переменно букву. Но, в конечном счете, эта переменная будет равна коду буквы в кодировке ANSI. При инициализации символа можно использовать и кавычки и апострофы — без разницы.
Всё выше написанное тоже самое что и:
При объявлении строк можно просто написать стоку после директивы db. Это воспринимается как последовательность символов
При передаче строк функциям в качестве параметров надо чтобы в конце строки был 0, для того чтобы функция смогла найти конец строки.
Заполнение данными.
Иногда нужно описать много одинаковых переменных примерно штук 30. Вы будете делать так
Это неудобно и некрасиво, тем более можно обсчитаться. Для сделана директива DUP.
в скобках указываем, чем надо заполнять, можно использовать символы в кавычках, обязательно чтобы размер в скобках совпадал с директивой.
С данными всё понятно. Читаем следующий урок. На 6 уроке мы будем говорить о метках и их использовании.
