Тип word в ассемблере
Типы данных на языке ассемблера и подробные определения данных
Ассемблер распознает набор основных внутренних типов данных (внутренние типы данных) и описывает их типы в соответствии с размером данных (байты, слова, двойные слова и т. Д.), Подписаны ли они, являются ли они целым или действительным числом. Эти типы в значительной степени перекрываются: например, тип DWORD (32-битное целое число без знака) может быть заменен типом SDWORD (32-битное целое число со знаком).
Некоторые люди могут сказать, что программист использует SDWORD, чтобы сообщить читателю, что это значение подписано, но это не обязательно для ассемблера. Ассемблер оценивает только размер операнда. Поэтому, например, программисты могут указывать только 32-разрядные целые числа как типы DWORD, SDWORD или REAL4.
В следующей таблице приведен список всех внутренних типов данных.Символы IEEE в некоторых записях относятся к стандартному формату вещественных чисел, опубликованному IEEE Computer Society.
Заявление об определении данных
Оператор определения данных (оператор определения данных) резервирует место для хранения переменных в памяти и присваивает дополнительное имя. Оператор определения данных определяет переменную в соответствии с внутренним типом данных (таблица выше).
Синтаксис определения данных следующий:
[name] directive initializer [,initializer]…
Ниже приводится пример оператора определения данных:
count DWORD 12345
- Имя: необязательное имя, присвоенное переменной, должно соответствовать спецификации идентификатора.
- Псевдо-инструкция: псевдо-инструкция в операторе определения данных может быть BYTE, WORD, DWORD, SBTYE, SWORD или другими типами, перечисленными в приведенной выше таблице. Кроме того, это также может быть традиционная директива определения данных, как показано в следующей таблице.
В определении данных должно быть хотя бы одно начальное значение, даже если значение равно 0. Остальные начальные значения, если есть, разделяются запятыми. Для целочисленных типов данных начальное значение (инициализатор) представляет собой целочисленную константу или целочисленное выражение, которое соответствует типу переменной, например BYTE или WORD.
Если программист не хочет инициализировать переменную (присвоить значение случайным образом), символ? Можно использовать в качестве начального значения. Все начальные значения, независимо от их формата, ассемблер преобразует в двоичные данные. Начальные значения 0011, 0010b, 32h и 50d имеют одинаковое двоичное значение.
Добавьте переменную в программу AddTwo
Программа AddTwo была представлена в предыдущем разделе «Сложение и вычитание целых чисел», а теперь создана ее новая версия, которая называется AddTwoSum. Эта версия вводит переменную сумму, которая появляется в полном листинге программы:
Вы можете установить точку останова в строке 13, выполнять по одной строке за раз и пошагово выполнять программу в отладчике. После выполнения строки 15 наведите указатель мыши на сумму переменной, чтобы просмотреть ее значение. Или откройте окно Watch.Процесс открытия выглядит следующим образом: выберите Windows в меню Debug (в сеансе отладки), выберите Watch и выберите один из четырех доступных вариантов (Watch1, Watch2, Watch3 или Watch4). Затем с помощью мыши выделите переменную суммы и перетащите ее в окно Watch. На следующем рисунке показан пример, где большая стрелка указывает текущее значение суммы после выполнения строки 15.
Определите данные BYTE и SBYTE
Начальное значение вопросительного знака (?) Делает переменную неинициализированной, что означает присвоение значения переменной во время выполнения:
Необязательное имя - это метка, которая определяет смещение от начала переменной, содержащей раздел, до переменной. Например, если value1 находится по смещению 0000 сегмента данных и занимает один байт в памяти, то value2 автоматически находится по смещению 0001:
value1 BYTE 10h
value2 BYTE 20h
val1 DB 255; беззнаковый байт
val2 DB -128; Байт со знаком
1) Несколько начальных значений
Если в одном определении данных используется несколько начальных значений, его метка указывает только смещение первого начального значения. В следующем примере предположим, что смещение списка равно 0000. Тогда смещение 10 равно 0000, смещение 20 равно 0001, смещение 30 равно 0002, а смещение 40 равно 0003:
list BYTE 10,20,30,40
На следующем рисунке показан список последовательности байтов, показывающий каждый байт и его смещение.
Не все определения данных должны использовать метки. Например, если вы продолжите добавлять массивы байтов после списка, вы можете определить их в следующей строке:
В одном определении данных для его начального значения могут использоваться разные базы. Также можно произвольно комбинировать символы и строковые константы. В следующем примере list1 и list2 имеют одинаковое содержимое:
2) Определить строку
Чтобы определить строку, заключите ее в одинарные или двойные кавычки. Наиболее распространенный тип строки - использование нулевого байта (значение 0) в качестве конечного тега, который называется строкой с завершающим нулем. Этот тип строки используется во многих языках программирования:
Каждый символ занимает один байт памяти. Строки являются исключением из правила, согласно которому значения байтов должны разделяться запятыми. Если бы таких исключений не было, приветствие1 было бы определено как:
greeting1 BYTE 'G', 'o', 'o', 'd'….etc.
Это очень долго. Строку можно разделить на несколько строк, и нет необходимости добавлять метку к каждой строке:
Шестнадцатеричные коды 0Dh и 0Ah также называются CR / LF (возврат каретки и перевод строки) или символы конца строки. При записи стандартного вывода они перемещают курсор в левую часть строки рядом с текущей строкой.
Символ продолжения строки () соединяет две строки исходного кода в оператор, и он должен быть последним символом строки. Следующие утверждения эквивалентны:
greeting1 BYTE "Welcome to the Encryption Demo program "
и
greeting1
BYTE "Welcome to the Encryption Demo program "
Оператор DUP использует целочисленное выражение в качестве счетчика для выделения пространства хранения для нескольких элементов данных. Этот оператор очень полезен при выделении места для хранения строк или массивов. Он может использовать инициализированные или неинициализированные данные:
Определите данные WORD и SWORD
Директивы WORD (слово определения) и SWORD (слово со знаком определения) выделяют место для хранения одного или нескольких 16-битных целых чисел:
Вы также можете использовать традиционную псевдо-инструкцию DW:
Массив 16-битных слов создается путем перечисления элементов или с помощью оператора DUP. Следующий массив содержит набор значений:
myList WORD 1,2,3,4,5
На рисунке ниже представлена схематическая диаграмма массива в памяти при условии, что смещение начальной позиции myList равно 0000. Поскольку каждое значение занимает два байта, приращение его адреса равно 2.
Оператор DUP предоставляет удобный способ объявления массивов:
массив WORD 5 DUP (?); 5 значений, не инициализировано
Определите данные DWORD и SDWORD
Псевдоинструкции DWORD (определение двойного слова) и SDWORD (определение двойного слова со знаком) выделяют место для хранения одного или нескольких 32-битных целых чисел:
Традиционная псевдо-инструкция DD также может использоваться для определения данных двойного слова:
DWORD также можно использовать для объявления переменной, содержащей 32-битное смещение другой переменной. Как показано ниже, pVal содержит смещение val3:
pVal DWORD val3
32-битный массив двойных слов
Теперь определите массив двойных слов и явно инициализируйте каждое его значение:
myList DWORD 1,2,3,4,5
На следующем рисунке показана схематическая диаграмма этого массива в памяти.Предположим, что смещение начальной позиции myList равно 0000, а приращение смещения равно 4.
Определить данные QWORD
Псевдо-инструкция QWORD (определить четыре слова) выделяет место для хранения 64-битных (8 байтов) значений:
quad1 QWORD 1234567812345678h
Традиционная псевдо-инструкция DQ также может использоваться для определения данных из четырех слов:
quad1 DQ 1234567812345678h
Определить сжатые данные BCD (TBYTE)
Intel хранит сжатое двоично-десятичное (BCD, двоично-десятичное) целое число в 10-байтовом пакете. Каждый байт (кроме самого старшего) содержит две десятичные цифры. В младших 9 байтах памяти каждый полубайт хранит десятичное число. В старшем байте самый старший бит представляет бит знака числа. Если старший байт равен 80h, число отрицательное; если старший байт равен 00h, число положительное. Диапазон целых чисел: от -999 999 999 999 999 999 до +999 999 999 999 999 999.
Пример В следующей таблице перечислены шестнадцатеричные байты хранения положительных и отрицательных десятичных чисел 1234, в порядке от младшего до самого старшего байта:
Десятичное значение | Байты памяти |
---|---|
+1234 | 34 12 00 00 00 00 00 00 00 00 |
-1234 | 34 12 00 00 00 00 00 00 00 80 |
MASM использует псевдо-инструкцию TBYTE для определения сжатых переменных BCD. Начальное значение константы должно быть в шестнадцатеричном формате, поскольку ассемблер не будет автоматически преобразовывать начальное десятичное значение в код BCD. В следующих двух примерах показаны допустимые и недопустимые выражения десятичного числа -1234:
Второй пример недействителен, поскольку MASM кодирует константы как двоичные целые числа вместо сжатия целых чисел BCD.
Если вы хотите закодировать действительное число в сжатый код BCD, вы можете использовать инструкцию FLD для загрузки действительного числа в стек регистров с плавающей запятой, а затем использовать инструкцию FBSTP для преобразования его в сжатый код BCD. Эта инструкция округляет значение до ближайшего Целое число:
Если posVal равно 1,5, результирующее значение BCD равно 2.
Определить тип с плавающей запятой
В следующей таблице описано минимальное количество значащих цифр и приблизительные диапазоны стандартных вещественных типов:
тип данных | эффективное число | Приблизительный диапазон |
---|---|---|
Короткий реальный номер | 6 | 1.18x 10-38 to 3.40 x 1038 |
Длинное действительное число | 15 | 2.23 x 10-308 to 1.79 x 10308 |
Действительное число повышенной точности | 19 | 3.37 x 10-4932 to 1.18 x 104932 |
Псевдо-инструкции DD, DQ и DT также могут определять действительные числа:
Ассемблер MASM включает такие типы данных, как wal4 и real8, которые указывают на то, что значение является действительным числом. Чтобы быть более точным, эти значения представляют собой числа с плавающей запятой с ограниченной точностью и диапазоном. С математической точки зрения точность и размер действительных чисел неограниченны.
Программа сложения переменных
До сих пор в примерах программ в этом разделе реализовано целочисленное сложение, хранящееся в регистрах. Теперь, когда у вас есть некоторое представление о том, как определять данные, вы можете изменить ту же программу, чтобы добавить три целочисленные переменные и сохранить сумму в четвертой переменной.
Обратите внимание, что три переменные инициализированы ненулевыми значениями (строки с 9 по 11). Добавьте переменные в строки 16-18. Набор инструкций x86 не позволяет напрямую добавлять одну переменную к другой переменной, но позволяет добавлять одну переменную в регистр. Вот почему EAX используется как аккумулятор в строках 16-17:
mov eax,firstval
add eax,secondval
После строки 17 EAX содержит сумму firstval и secondval. Затем в строке 18 добавьте третье значение к сумме в EAX:
Наконец, в строке 19 сумма копируется в переменную с именем sum:
В качестве упражнения всем рекомендуется запускать эту программу в сеансе отладки и проверять каждый регистр после выполнения каждой инструкции. Окончательная сумма должна быть 53335333 в шестнадцатеричной системе.
Если во время сеанса отладки вы хотите, чтобы переменная отображалась в шестнадцатеричном формате, выполните следующие действия: наведите указатель мыши на переменную или зарегистрируйтесь в течение 1 секунды, пока под курсором мыши не появится серый прямоугольник. Щелкните прямоугольник правой кнопкой мыши и выберите во всплывающем меню «Шестнадцатеричный формат».
Little endian
Процессор x86 хранит и извлекает данные в памяти в обратном порядке (от младшего к большему). Младший байт сохраняется в первом адресе памяти, присвоенном данным, а остальные байты сохраняются в последующих последовательных ячейках памяти. Рассмотрим двойное слово 12345678h. Если он сохраняется по смещению 0000, 78h сохраняется в первом байте, 56h сохраняется во втором байте, а оставшиеся байты сохраняются по смещениям адресов 0002 и 0003, как показано на следующем рисунке. .
Некоторые другие компьютерные системы используют прямой порядок байтов (от старшего к младшему). На следующем рисунке показано, что 12345678h хранится в обратном порядке, начиная со смещения 0000.
Объявить неинициализированные данные
.DATA? Директива объявляет неинициализированные данные. При определении большого количества неинициализированных данных директива .DATA? Уменьшает размер компилятора. Например, следующий код является допустимым утверждением:
С другой стороны, скомпилированная программа, сгенерированная следующим кодом, будет иметь дополнительные 20 000 байтов:
Гибридный ассемблер кода и данных позволяет переключать код и данные в программе туда и обратно. Например, вы хотите объявить переменную, чтобы ее можно было использовать только в локальной области программы. В следующем примере между двумя операторами кода вставляется переменная с именем temp:
Хотя присутствие оператора temp прерывает поток исполняемых инструкций, MASM поместит temp в раздел данных и отделит его от раздела кода, который остается скомпилированным. Однако в то же время смешивание директив .code и .data может затруднить чтение программы.
Данные, обрабатываемые вычислительной машиной, можно разделить на 4 группы:
- целочисленные;
- вещественные.
- символьные;
- логические;
Целочисленные данные
Беззнаковые целые числа представляются в виде последовательности битов в диапазоне от 0 до 2 n -1, где n- количество занимаемых битов.
Знаковые целые числа представляются в диапазоне -2 n-1 … +2 n-1 -1. При этом старший бит данного отводится под знак числа (0 соответствует положительному числу, 1 – отрицательному).
Вещественные данные
Вещественные данные могут быть 4, 8 или 10-байтными и обрабатываются математическим сопроцессором.
Логические данные
Логические данные представляют собой бит информации и могут записываться в виде последовательности битов. Каждый бит может принимать значение 0 (ЛОЖЬ) или 1 (ИСТИНА). Логические данные могут начинаться с любой позиции в байте.
Символьные данные
Символьные данные задаются в кодах и имеют длину, как правило, 1 байт (для кодировки ASCII) или 2 байта (для кодировки Unicode) .
Числа в двоично-десятичном формате
В двоично-десятичном коде представляются беззнаковые целые числа, кодирующие цифры от 0 до 9. Числа в двоично-десятичном формате могут использоваться в одном из двух видов:
В неупакованном виде в каждом байте хранится одна цифра, размещенная в младшей половине байта (биты 3…0).
Упакованный вид допускает хранение двух десятичных цифр в одном байте, причем старшая половина байта отводится под старший разряд.
Числовые константы
Числовые константы используются для обозначения арифметических операндов и адресов памяти. Для числовых констант в Ассемблере могут использоваться следующие числовые форматы.
Десятичный формат – допускает использование десятичных цифр от 0 до 9 и обозначается последней буквой d, которую можно не указывать, например, 125 или 125d. Ассемблер сам преобразует значения в десятичном формате в объектный шестнадцатеричный код и записывает байты в обратной последовательности для реализации прямой адресации.
Шестнадцатеричный формат – допускает использование шестнадцатеричных цифр от 0 до F и обозначается последней буквой h, например 7Dh. Так как ассемблер полагает, что с буквы начинаются идентификаторы, то первым символом шестнадцатеричной константы должна быть цифра от 0 до 9. Например, 0Eh.
Двоичный формат – допускает использование цифр 0 и 1 и обозначается последней буквой b. Двоичный формат обычно используется для более четкого представления битовых значений в логических командах (AND, OR, XOR).
Восьмеричный формат – допускает использование цифр от 0 до 7 и обозначается последней буквой q или o, например, 253q.
Массивы и цепочки
Массивом называется последовательный набор однотипных данных, именованный одним идентификатором.
Цепочка — массив, имеющий фиксированный набор начальных значений.
Примеры инициализации цепочек
Каждая из записей выделяет десять последовательных 4-байтных ячеек памяти и записывает в них значения 0, 1, 2, 3, 4, 5, 6, 7, 8, 9.
Идентификатор M1 определяет смещение начала этой области в сегменте данных . DATA .
Для инициализации всех элементов массива одинаковыми значениями используется оператор DUP :
Идентификатор Тип Размер DUP (Значение)
Идентификатор — имя массива;
Тип — определяет количество байт, занимаемое одним элементом;
Размер — константа, характеризующая количество элементов в массиве
Значение — начальное значение элементов.
описывает массив a из 20 элементов, начальные значения которых равны 0.
Если необходимо выделить память, но не инициализировать ее, в качестве поля Значение используется знак ?. Например,
Символьные строки
Символьные строки представляют собой набор символов для вывода на экран. Содержимое строки отмечается
- одиночными кавычками », например, ‘строка’
- двойными кавычками «», например «строка»
Символьная строка определяется только директивой DB , в которой указывается более одного символа в последовательности слева направо.
Символьная строка, предназначенная для корректного вывода, должна заканчиваться нуль-символом ‘\0’ с кодом, равным 0.
Переменные хранятся в памяти по определенным адресам. Программисту проще иметь дело именами переменных, чем с адресами в памяти. Например, переменная с именем "var1" будет более понятна в коде программы, чем адрес 5A73:235B, особенно когда количество переменных велико.
Наш компилятор поддерживает два типа переменных: BYTE и WORD.
имя DB значение
имя DW значение
DB - Define Byte - определяет байт.
DW - Define Word - определяет слово.
имя - может быть любой комбинацией букв или цифр, но должно начинаться с буквы. Можно объявлять безымянные переменные, которые имеют адрес, но не имеют имени.
Как вы уже знаете из части 2 этих уроков, команда MOV используется для копирования значения из источника в приемник.
Давайте посмотрим другой пример с командой MOV:
Скопируйте вышеприведенный код в редактор кода Emu8086 и нажмите клавишу F5, чтобы откомпилировать и загрузить этот код в эмулятор. Вы увидите примерно такую картину:
На рисунке вы можете заметить команды, похожие на те, что используются в нашем примере. Только переменные заменены фактическими местоположениями в памяти. Когда компилятор создает машинный код, он автоматически заменяет имена всех переменных их смещениями. По умолчанию сегмент загружен в регистр DS (в COM-файлах значение регистра DS устанавливается таким же, что и значение в регистре CS - сегменте кода).
В таблице памяти (memory) первый столбец - это смещение, второй столбец - это шестнадцатиричное значение, третий столбец - десятичное значение, а последний столбец - это символ ASCII, соответствующий данному числу.
Компилятор не чувствителен к регистру, поэтому "VAR1" и "var1" - это одно и то же.
Смещение переменной VAR1 - это 0108h, а полный адрес - 0B56:0108.
Смещение переменной var2 - это 0109h, а полный адрес - 0B56:0109. Эта переменная имеет тип WORD, поэтому занимает 2 БАЙТА. Принято младший байт записывать по меньшему адресу, поэтому 34h размещается перед 12h.
Вы можете увидеть некоторые другие инструкции после команды RET. Это случается потому, что дизассемблер не знает, где начинаются данные. Он только обрабатывает значения в памяти и понимает их как имеющие силу инструкции процессора 8086 (мы изучим их позже).
Вы можете даже написать программу, используя только директиву DB:
Скопируйте вышеприведенный код в редактор кода Emu8086 и нажмите клавишу F5, чтобы откомпилировать и загрузить этот код в эмулятор. Вы получите тот же самый дизассемблированный код и тот же самый результат работы программы!
Как вы можете догадаться, компилятор только преобразует исходный код программы в набор байтов. Этот набор байтов называется машинным кодом. Процессор обрабатывает машинный код и выполняет его.
ORG 100h - это директива компилятора (она указывает компилятору как обрабатывать исходный код). Эта директива очень важна при работе с переменными. Она указывает компилятору, какой исполняемый файл будет загружаться в смещение (offset) 100h (256 байтов), так что компилятор должен вычислить правильный адрес для всех переменных, когда он размещает имена переменных с их смещениями. Директивы никогда не преобразуются в какой-либо реальный машинный код. Почему исполняемый файл загружается по смещению 100h? Операционная система хранит некоторые данные о программе в первых 256 байтах, начиная от CS (сегмента кода), такие как параметры командной строки и т.д. Все это справедливо только для COM-файлов, файлы EXE загружаются по смещению 0000, и обычно используют специальный сегмент для переменных. Может быть, мы поговорим об EXE-файлах позже.
Массив можно рассматривать как цепочку переменных. Текстовая строка - это пример массива байтов, в котором каждый символ представлен значением ASCII-кода (0..255).
Вот некоторые примеры определения массивов:
a DB 48h, 65h, 6Ch, 6Ch, 6Fh, 00h
b DB 'Hello', 0
b - это точная копия массива a - когда компилятор видит строку, заключенную в кавычки, он автоматически преобразует ее в набор байтов. Эта таблица показывает участок памяти, где эти массивы объявлены:
Вы можете получить значение любого элемента массива, используя квадратные скобки, например:
Вы можете также использовать какой-либо из регистров BX, SI, DI, BP, например:
MOV SI, 3
MOV AL, a[SI]
Если необходимо объявить большой массив, вы можете использовать оператор DUP.
Синтаксис для DUP:
количество DUP ( значение(я) )
количество - количество дубликатов (любая константа).
значение - выражение, которое будет дублироваться оператором DUP.
а это альтернативный способ объявления:
еще один пример:
а это альтернативный способ объявления:
d DB 1, 2, 1, 2, 1, 2, 1, 2, 1, 2
Конечно, вы можете использовать DW вместо DB, если требуется хранить числа более 255, или менее -128. DW не может быть использован для объявления строк!
Оператор DUP не может содержать более 1020 знаков в качестве операнда! (в последнем примере 13 знаков). Если вам необходимо объявить очень большой массив, разделите его на две строки (вы получите один большой массив в памяти).
Получение адреса переменной
Есть такая команда LEA (Load Effective Address) и альтернативный оператор OFFSET. Как OFFSET так и LEA могут быть использованы для получения смещения адреса переменной.
LEA более мощная, т.к. она также позволяет вам получить адрес индексированных переменных. Получение адреса переменной может быть очень полезно в различных ситуациях, например, если вам необходимо поместить параметр в процедуру.
Напоминание:
Чтобы указать компилятору тип данных, вы должны использовать следующие префиксы:
BYTE PTR - для байта.
WORD PTR - для слова (два байта).
Например:
Emu8086 поддерживает короткие префиксы:
b. - для BYTE PTR
w. - для WORD PTR
иногда компилятор может вычислить тип данных автоматически, но вы не можете и не должны полагаться на это, если один из операндов является непосредственным значением.
Здесь первый пример:
Здесь другой пример, который использует OFFSET вместо LEA:
Оба примера функционально идентичны.
LEA BX, VAR1
MOV BX, OFFSET VAR1
даже компилируются в одинаковый машинный код: MOV BX, num
num - это 16-битовое значение смещения переменной.
Пожалуйста учтите, что только эти регистры могут использоваться внутри квадратных скобок (как указатели памяти):
BX, SI, DI, BP!
(См. предыдущую часть уроков).
Константы подобны переменным, но они существуют до того, как ваша программа откомпилирована (ассемблирована). После определения константы ее значение не может быть изменено. Для определения константы используется директива EQU:
Этот пример функционально идентичен коду:
Вы можете наблюдать переменные во время выполнения программы, если выберите пункт "Variables" в меню "View" эмулятора.
Чтобы наблюдать массивы, вы должны щелкнуть по переменной и установить свойство Elements - размер массива. В Ассемблере нет строгих типов данных, поэтому любые переменные могут быть представлены как массив.
- HEX - шестнадцатиричная (основа 16).
- BIN - двоичная (основа 2).
- OCT - восмеричная (основа 8).
- SIGNED - десятичная со знаком (основа 10).
- UNSIGNED - десятичная без знака (основа 10).
- CHAR - коды ASCII-символов (всего 256 символов, некоторые символы невидимы).
Можно вводить числа в любой системе, шестнадцатиричные цифры должны иметь суффикс "h", двоичные - суффикс "b", восмеричные - суффикс "o", десятичные цифры не требуют суффикса. Строка может быть введена следующим способом:
'hello world', 0
(эта строка заканчивается нулем).
Массив может быть введен следующим способом:
(массив может быть массивом байтов или слов, это зависит от того, выбран ли BYTE или WORD для введенной переменной).
Встроенный ассемблер вызывается ключевым словом asm, которое является операторной скобкой. Синтаксис: Слово asm может появляться везде, где Паскаль позволяет вставлять операторные скобки.
Ассемблерная процедура
Встроенный ассемблер может также использоваться для написания полноценных процедур на языке Ассемблера. Такие процедуры должны иметь ключевое слово Assembler, добавленное после заголовка процедуры. Эта функция использует особенность процессора i80386 для очень быстрого умножения на 9.
Процедуры Ассемблера отличаются от стандартных процедур Паскаля следующими деталями:
Нет возвращения переменной
Структурированные аргументы (такие как строки, объекты, записи) не копируются в локальные переменные. Они должны быть обработаны как параметры Var.
Граница стека
Процедуры Ассемблера не имеют границ стека, если они не содержат аргументов и локальных символов. Вообще, граница стека во встроенном ассемблере, это Здесь Locals - это общий размер локальных параметров, Params - общий размер параметров процедуры.
Сохранение регистров
Код Ассемблера не должен изменять значение следующих регистров: DS, CS, SS, ES, EBP и ESP. Все другие регистры могут перезаписываться. Учитывайте включение регистра ES. TMT Паскаль всегда предполагает, что ES равен DS.
Кроме того, вы не должны изменять сегмент, страницу и таблицу прерываний, также как управление, отладку и тестирование регистров, если вы не очень хорошо знакомы с архитектурой 386-го в защищенном режиме. Привилегированные команды, подобные LGDT и LIDT, поддерживаются встроенным ассемблером. Однако, избегайте использовать их, если вы не уверены в своих действиях.
Кодовая процедура
Помимо процедур ассемблерных, вы можете использовать кодовые процедуры. Они имеют следующие отличия: компилятор не исполняет команду ограничения на ввод и возвращает из процедуры (включая команду RET), а локальные параметры являются основанными на ESP на момент ввода.
Синтаксис команд
Общий синтаксис операторов ассемблера
[label:]
[prefixes]
[[opcode [operand1 [,operand2 [,operand3]]]]
Метки
-
Глобальные метки объявляются в программе Паскаля в пределах описания метки. Локальные метки не объявляются. Они должны начинаться с символа @ и содержать буквы, цифры или символ подчеркивания. Локальные метки имеют силу только в пределах операторных скобок asm.
Префиксы
Префиксы являются модификаторами для следующих команд. ТМТ Паскаль позволяет следующие мнемоники префиксов:
Коды операций
Код операции - это мнемоническая команда или директива ассемблера. Список поддерживаемых "опкодов" приведен ниже. ТМТ Паскаль допускает только следующие директивы ассемблера: DB, DW и DD.
Пример: Директивы DB, DW и DD позволяют объявить некоторое количество аргументов, разделенных запятыми. Другие обычно используемые директивы ассемблера могут быть эмулированы при помощи операторов Паскаля. Например, директива EQU может быть заменена на const, а STRUCT может быть определена описанием типа record.
Регистры
Следующие регистры могут быть использованы встроенным ассемблером: Сегментный регистр может использоваться для отмены сегмента. 32-битные регистры могут использоваться для индексации на процессорах 80386 и выше. 16-битные регистры никогда не должны использоваться для адресации, если ваша программа превышает размер 64кБ. Но и в том случае, когда программа имеет размер менее 64кБ, 16-битная адресация неэффективна. Вообще, адрес формируется так:
Базовый адрес + Индекс * Масштаб + Смещение
Где "Базовый адрес" - это любой 32-битный регистр, "Индекс" - любой 32-битный регистр, кроме ESP, "Масштаб" должен быть 1, 2, 4 или 8. "Смещение" - целое число.
Ниже приведены некоторые правильные и неправильные способы адресации: Подробности ищите в справочных руководствах программиста Intel™ 80386/80486.
Мнемонические коды операций
Этот раздел представляет список значений мнемонических кодов операций. Подробности ищите в справочных руководствах программиста 80386. В списке используются следующие сокращения:
Выражения операндов
Выражения операндов строятся из операндов и операторов. Операнды - это константы, регистры, метки и адреса памяти. Операторы комбинируют операндами и изменяют их свойства. Каждая команда допускает только некоторые комбинации операндов.
-
Непосредственные операнды или константы. Регистры. Операнды памяти и метки.
Встроенный ассемблер ТМТ Паскаля позволяет использовать 8, 16 или 32-битные 80386 регистры. Операнды памяти и метки
Операнды памяти ссылаются на данные, записанные в памяти. Обычно используются квадратные скобки [..] или оператор типа ptr - это гарантирует, что аргумент будет обработан как адрес памяти. Операнды-метки ссылаются на определенное место в коде. Память и непосредственные операнды могут быть либо абсолютными, либо неопределенными. Абсолютный операнд - это операнд, значение или смещение которого известны во время компиляции. Неопределенный операнд - это операнд, смещение которого станет известно только во время компоновки.
Операнды
ТМТ Паскаль допускает следующие операнды: Числовые константы, Строки, Регистры, Символы Паскаля и специальные символы.
Числовые константы
Числовые константы это 32-битные целые числа, например, целое число в диапазоне -2147483648..4294967295. Числовые константы могут вводиться как десятичные числа, двоичные числа (используется суффикс 'B'), восмеричные числа (используются суффиксы 'Q' или 'O'), шестнадцатиричные числа (используются суффикс 'H' или префикс '$'). Учтите, что шестнадцатиричные числа должны начинаться с цифры.
Строки заключаются в одинарные или двойные кавычки. Если между кавычками встречается кавычка такого же типа, то она обрабатывается как символ. Строковые константы произвольной длины могут иметь место только в директиве 'DB'. Во всех других случаях строка не должна превышать четырех символов и ее значение преобразуется в целое число.
Использование регистров было описано выше.
Символы Паскаля
Со встроенным ассемблером Вы можете обращаться к большинству символов Паскаля. Они включают метки, константы, переменные, типы и процедуры. Значения, классы и типы символов Паскаля сведены в таблицу: Специальные Символы Ассемблера
Встроенный ассемблер поддерживает пять специальных символов: @CODE, @DATA, @RESULT, @PARAMS и @LOCALS. @CODE и @DATA в реальности не используются в плоской модели. Они всегда возвращают 0Ch и 14h, которые являются стандартными селекторами сегментов для сегментов данных и кода. Символ @RESULT указывает на псевдопеременную, которая содержит возвращенный функцией результат, @PARAMS и @LOCALS возвращают размер параметра и локальных областей в стеке.
Операторы
Встроенный Ассемблер ТМТ Паскаля допускает операторы, перечисленные в следующем списке:
& | Оператор отмены Идентификатора. Следующий идентификатор рассматривается как определенный пользователем символ, даже если он совпадает с зарезервированным словом ассемблера. |
( ) | Круглые скобки. Выражения, заключенные в круглые скобки имеют приоритет и выполняются первыми. |
[ ] | Указатель памяти. Выражение в квадратных скобках обрабатывается первым. Это выражение должно быть значением адреса, принятым в архитектуре процессора 386. Результат выражения всегда обрабатывается как адрес памяти. |
HIGH, LOW | Выбор старшего и младшего байта. Эти операторы возвращают старшие и младшие 8 бит выражения, которое имеет размер слова и следует после этих операторов. |
+, - | Операторы унарного сложения и вычитания. Выражение должно быть абсолютным непосредственным. |
SMALL, LARGE | Заставляет встроенный ассемблер обрабатывать последующие операнды как 16-ти или 32-битные. |
OFFSET | Возвращает 32-битное смещение следующего за ним выражения. |
SEG | Возвращает сегментную часть операнда. |
TYPE | Возвращает тип операнда. Тип символа NEAR - это -1, тип символа FAR - это -2. Тип операнда памяти - это его размер. Тип непосредственного значения - 0. |
PTR | Преобразует следующее за ним выражение в тип, который указан перед оператором PRT. Допустимые значения: BYTE PRT, WORD PRT, DWORD PRT, FWORD PRT, TBYTE PRT, QWORD PRT, NEAR PRT и FAR PRT. |
* | Умножение. Оба аргумента должны быть непосредственными значениями, или один из аргументов должен быть индексирующим регистром, а другой - масштабирующим множителем (1,2,4 или 8). |
/ | Деление. Оба аргумента должны быть непосредственными значениями. |
MOD | Остаток от целого деления. Оба аргумента должны быть непосредственными значениями. |
SHL, SHR | Сдвиг влево и сдвиг вправо. Оба аргумента должны быть непосредственными значениями. |
+, - | Сложение и вычитание. Больший из аргументов может быть неопределенным значением, но это не может быть вычитаемый аргумент. Другой аргумент должен быть абсолютным непосредственным значением. |
NOT | Двоичное дополнение. Аргумент должен быть абсолютным непосредственным значением. |
AND | Поразрядное И. Аргумент должен быть абсолютным непосредственным значением. |
OR | Поразрядное ИЛИ. Аргумент должен быть абсолютным непосредственным значением. |
XOR | Поразрядное ИСКЛЮЧАЮЩЕЕ ИЛИ. Аргумент должен быть абсолютным непосредственным значением. |
Приоритет операторов Ассемблера
Приоритет этих операторов показан в следующей таблице (от самого высокого приоритета до самого низкого):
Различия между 16-ти и 32-разрядным кодом
Это руководство не обучает 32-битному программированию. Мы только перечислим здесь несколько соображений, важных при создании 32-битных программ на ассемблере. Эти соображения могут быть полезны для программиста на 16 битов, который начинает осваивать 32-битное программирование.
Избегайте использовать 16-битные регистры для индексации.
Встроенный ассемблер будет правильно обрабатывать команды, подобные этим: Однако, эти программы не будут работать правильно, если размер вашей программы превышает 64КБ и "таблица" переменных размещена за пределами этого диапазона. Это потому, что 16-битные адреса охватывают только сегмент, размером 64КБ. Последний из приведенных примеров наиболее опасен, т.к. существует вероятность разрушения системы.
Jump таблицы
Jump таблицы должны строиться как таблицы 32-битных адресов, а не 16-битные адреса.
"Длинная" (32-bit) Арифметика
Старайтесь использовать "длинную" арифметику как можно чаще. 16-битные команды часто занимают больше места, чем соответствующие 32-битные команды. В коде было бы лучше заменить первую команду на которая является на один байт короче. Кроме того, если data1 и data2 могут быть заменены на 32-битные значения, вы можете сохранить больше места (и времени) в программе как в разделе Ассемблера, так и в разделе Паскаля.
ECX против CX
Циклы и команды повторения в 32-битном режиме используют регистр ЕСХ быстрее, чем СХ. Следующий сегмент программы, возможно, создаст проблемы: Также учтите, что регистры индексов источника и приемника ESI и EDI быстрее, чем SI и DI.
Используйте POPAD и PUSHAD вместо POPA и PUSHA. Последние помещают в стек только 16 бит.
Используйте POPFD и PUSHFD вместо POPF и PUSHF. Последние помещают в стек только 16 бит.
Используйте 'IRETD' вместо 'IRET'. Последняя "выталкивает" 16-битные регистры.
Строковые команды
При выполнении операций со строками, используйте команды с двойным словом вместо байта или слова. Используйте MOVSD вместо MOVSW или MOVSB.
JECXZ против JCXZ
Различают команды JCXZ и JECXZ. Первая проверяет регистр СХ, в то время как вторая проверяет регистр ЕСХ. Использование JCXZ вместо JECXZ может привести к труднонаходимым ошибкам. Точно также LOOP проверяет ECX, а LOOP16 проверяет CX.
Результаты функций
Помните, что 32-битный результат нужно возвращать в ЕАХ, а не DX:AX.
ES: сохранение
Не изменяйте значение регистра ES. TMT Pascal определяет ES = DS.
Immediate PUSH
TMT Pascal предполагает, что в стек помещается непосредственное значение командой, подобной этим: Заметьте, что подобно TASM и в отличие от ассемблера PharLap, TMT Pascal обработает команду Как будто это Var-параметры
Подобно 16-битному режиму, var-параметры - это 32-битные указатели. Однако, в ТМТ Паскале указатели являются только 32-битным смещением в пределах сегмента данных. Поэтому var-параметры возвращаются командой MOV, а не LES или LDS.
Локальные символы
Локальные символы и параметры адресованы через регистр ЕВР. Например, в последняя строка ассемблируется в
Приветствуем всех подписчиков! Продолжаем разбираться в хитростях ассемблера. Нам с вами пора наконец-то разобраться, что такое типы данных в асме, ведь они там совсем другие.
В прошлом посте был краткий обзор кода на MASM и там как раз мелькнула переменная, которая объявлялась следующим образом:
Давайте подробнее разберём каждую часть этой записи.
- Самое начало — ExitCode — это просто название переменной. Оно может быть любым, и единственное ограничение здесь — для названия переменных нельзя использовать команды ассемблера. Поэтому, ExitSome — можно, а Mov, к примеру, — нельзя.
- Следующая часть записи — это как раз таки тип данных. И о нём мы подробно поговорим буквально через мгновенье.
- Последняя часть записи — 0 (ноль) — это то значение, которое будет содержать переменная. Можно писать как в десятичной системе счисления, так и в шестнадцатеричной или двоичной. Что больше по вкусу.
А теперь про типы данных и их запись. В ассемблере нет привычных нам строк, чисел, чисел с плавающей точкой и тд. В нём всё есть числа, причём, если смотреть на низком уровне, убирая все удобства, то числа эти — двоичные. Всё остальное: записи в других системах счисления, или ещё более высокоуровневая способность — писать не только числа, но и символы (строки), это просто упрощение, “сахарок” так сказать.
Это краткое отступление поможет вам понять, почему типы данных в ассемблере именно такие, а не какие либо другие.
Итак, разберёмся сперва с названиями типов и с тем, что они из себя представляют. Всего в ассемблере существует 5 типов данных: Byte, Word. Double Word, Quad Word и Ten Byte. Посмотрим подробнее на каждый.
- Byte -это просто байт, или 8 бит. Нечего особенного. Этот тип указывает на то, что в переменной содержится ровно байт информации (на самом деле само число хранящееся в переменной может занимать меньше пространства. В таком случае оставшееся место заполниться нулями).
- Word — это так называемое “слово”. Оно состоит из двух байт, и, соответственно, способно вместить в себя большие значения.
- Double Word — двойное “слово”. Этот тип данных вдвое больше обыкновенного Word. Если считать в байтах, то двойное слово состоит из 4 байтов.
- Quad Word — думаю, вы уже поняли смысл. Этот тип данных вмещает в себя ещё большие значения, чем все предыдущие. Он равен двум Double Word, или четырём Word. Собственно название говорит само за себя — четверное “слово”. Если перевести это дело в обычные байты, то получится 8 байт.
- И последний, редко используемый тип данных — Ten Byte. Из на звания уже понятно, что он состоит из десяти обычных байт. И конечно же этот тип данных способен вместить в себя больше всего информации, самые большие значения.
При объявлении переменных используется один из этих типов, такой подход позволяет чётко задать диапазон значений, которое в себя может вместить переменная. Для указания типа данных используются специальные сокращения. Все они начинаются с D, что означает define (определить).
Регистр в данном случае не имеет значения. Вы можете писать как DD, так и dd
Вот список этих сокращений, их мало и запомнить очень просто:
AfterWords
В этом посте мы рассказали вам про основные 5 типов данных, которые используются в ассемблере. На них основывается работа со всем остальным, со строками, например. В следующем посте мы ещё немного поговорим про переменные, а также разберёмся с положительными и отрицательными числами. До скорых встреч!)
Не забывайте, что множество интересных статей можно найти на нашем Telegram канале и в Telegram боте, также все статьи мы публикуем в Twitter и Facebook
Читайте также: