Flat Assembler 1.58
Руководство Программиста

Tomasz Grysztar

перевод: Александр Владимиров

Содержание




Глава 1

Введение

Эта глава содержит всю самую важную информацию которая вам потребуется чтобы начать использовать flat ассемблер. Если вы имеете опыт программирования на языке ассемблер, вам потребуется прочитать как минимум эту главу, прежде чем вы начнете использовать этот компилятор.

1.1 Обзор компилятора

Flat ассемблер это быстрый компилятор языка ассемблер для процессоров с архитектурой x86, который выполняет несколько проходов компиляции для оптимизации генерируемого машинного кода. Он способен компилировать собственный исходный код и способен работать в разных операционных системах. Версии для разных операционных систем предназначены для использования из системной командной строки и не отличаются поведением.

Этот документ описывает также IDE версию, разработанную для операционной системы Windows, которая использует графический интерфейс взамен консольного и обладает встроенным текстовым редактором. С точки зрения компиляции она обладает в точности такой же функциональностью как и все консольные версии, и дальнейшие части (начиная с 1.2) этого документа являются общими для всех версий компилятора. Исполняемый файл IDE версии называется fasmw.exe, в то время как консольная версия называется fasm.exe.

1.1.1 Системные требования

Все версии требуют для своей работы 32-битного процессора архитектуры x86 (как минимум 80386), хотя могут также генерировать программы и для 16-битных процессоров. Консольная версия компилятора требует для работы любую Win32 совместимую операционную систему, в то время как версия с графическим интерфейсом требует Win32 GUI операционную систему версии 4.0 и выше, так что компилятор будет работать на любой системе совместимой с Windows 95.

Исходные коды программ-примеров поставляемые с этой версией компилятора для успешной компиляции требуют, чтобы переменная окружения INCLUDE хранила полный путь к папке include, которая является частью пакета. Если такая переменная уже существует в вашей системе и содержит пути используемые другой программой, достаточно добавить к ней новый путь (разные пути разделяются точкой с запятой). Если вы не хотите определять такую переменную в системе, или не знаете как это сделать, то для IDE версии вы можете установить её редактированием файла fasmw.ini в папке с исполняемым файлом компилятора (этот файл создаётся компилятором fasmw.exe при запуске, но может также быть создан вручную). В этом случае вам необходимо добавить значение Include в секцию Environment. Например, если вы распаковали файлы flat ассемблера в папку c:\fasmw, вы должны добавить следующие строки в файл c:\fasmw\fasmw.ini:

[Environment]
Include = c:\fasmw\include

Если вы не определите переменную окружения INCLUDE правильно, вам придётся вручную указывать полный путь к заголовочным файлам Win32 в каждой программе которую вы хотите скомпилировать.

1.1.2 Использование компилятора

Для того, чтобы начать работать с flat ассемблером, просто запустите файл fasmw.exe, или перетащите иконку исходного файла на иконку fasmw.exe в проводнике. Также вы можете открывать новые исходные файлы с помощью команды Open в меню File, или перетаскивая файлы в окно редактора. В редакторе могут быть открыты несколько файлов одновременно, каждый из них представляется закладкой в нижней части окна редактора. Выбрать файл для редактирования можно щёлкнув левой кнопкой мыши на соответствующей закладке. По умолчанию компилятор работает с редактируемым в данный момент файлом, но вы можете принудить его работать с конкретным файлом щёлкнув на сооответствующей закладке правой кнопкой мыши и выбрав в контекстном меню пункт Assign. Одновременно к компилятору может быть привязан только один файл.

Когда ваш исходный файл готов, вы можете выполнить компиляцию выбрав команду Compile из меню Run. Если компиляция пройдёт успешно, компилятор отобразит окно результатов компиляции; иначе он выведет информацию о произошедших ошибках. Окно результатов компиляции содержит информацию о количестве проходов, длительности компиляции и количестве байт записанных в результирующий файл. Оно также содержит текстовое поле называемое Display, в котором отображаются все сообщения от директив display в исходном коде (см. 2.2.3). Сводка ошибок содержит как минимум сообщение об ошибке и текстовое поле Display, того же назначения. Если ошибка связана с конкретной строкой исходного кода, сводка содержит также текстовое поле Instruction, которое содержит препроцессированную форму инструкции вызвавшей ошибку если ошибка произошла после стадии препроцессора (иначе поле пустое) и список Source, который показывает расположение всех строк исходного кода связанных с этой ошибкой, если вы выберете строку из этого списка, она одновременно выбрана в окне редактора (если соответствующий файл не открыт, он будет автоматически загружен).

Команда Run также выполняет компиляцию, и в случае успешного её завершения запускает скомпилированную программу в том случае, если она относится к одному из форматов, запускаемых в среде Windows, иначе выводится сообщение о том, что такой тип файла не может быть запущен. Если при компиляции возникают ошибки, выводится сводка по ошибкам, как для команды Compile.

Если компилятору не хватает памяти, вы можете увеличить используемый её объём, открыв окно Compiler Setup из меню Options. В нём вы можете указать объём памяти в килобайтах который компилятор должен использовать и также задать приоритет потока компилятора.

1.1.3 Запуск компилятора из командной строки

Чтобы выполнить компиляцию из командной строки вам потребуется запустить файл fasm.exe с двумя параметрами - именем входного файла и именем выходного файла соответственно. Если второй параметр не указан, имя выходного файла определяется автоматически. После вывода краткой информации об имени программы и её версии, компилятор прочитает данные из исходного файла и скомпилирует их. Если компиляция пройдёт успешно, компилятор запишет сгенерированный код в выходной файл и выведет информацию о результатах компиляции; иначе он выведет информацию о произошедших ошибках.

Исходный файл должен являться текстовым, и может быть создан в любом текстовом редакторе. Символы конца строки воспринимаются как в стандарте DOS, так и в стандарте UNIX. Символы табуляции рассматриваются как пробелы.

В командной строке вы можете также указать параметр -m за которым должно следовать число, указывающее количество памяти в килобайтах, которое flat ассемблер может использовать как максимум. Для версии DOS этот параметр ограничивает лишь использование расширенной (extended) памяти. Параметр -p с последующим числом может быть использован для указания числа проходов компиляции которые компилятор выполняет. Если код не может быть сгенерирован с указанным числом проходов, ассемблирование будет прервано с сообщением об ошибке. Максимальное значение этого параметра раво 65536, в то время как пределом используемым по умолчанию, когда такой параметр не указан, является 100. Также можно ограничить количество проходов которые компилятор может выполнить указав в параметре -p максимальное их количество.

Параметров командной строки влияющих на выход компилятора во flat ассемблере нет, всю необходимую информацию он получает из исходных файлов. Например для указания формата выходного файла применяется директива format указываемая в начале исходного файла.

1.1.4 Сообщения консольного компилятора

Как было сказано ранее, после успешной компиляции, компилятор выводит сообщение о результатах компиляции. Оно включает в себя количество выполненных проходов, длительность компиляции и количество байт записанных в выходной файл. Вот пример сообщения о результатах компиляции:

flat assembler version 1.58
38 passes, 5.3 seconds, 77824 bytes.

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

flat assembler version 1.58
error: source file not found.

Если ошибка связана с конкретной частью исходного кода, строка вызвавшая ошибку тоже будет отображена. Также будет выведено расположение этой строки в исходном файле, чтобы помочь вам найти ошибку, например:

flat assembler version 1.58
example.asm [3]:
mob ax,1
error: illegal instruction.

Это значит, что в третьей строке файла example.asm компилятором обнаружена неизвестная ему инструкция. Когда строка вызвавшая ошибку содержит макроинструкцию, также выводится строка в определении макроинструкции, сгенерировавшая ошибочную инструкцию:

flat assembler version 1.58
example.asm [6]:
stoschar 7
example.asm [3]: stoschar [1]:
mob al,char
error: illegal instruction.

Это сообщение говорит, что макроинструкция в шестой строке файла example.asm сгенерировала неизвестную инструкцию в первой строке своего определения.

1.1.5 Форматы выходных файлов

По умолчанию, когда в исходном файле отсутствует директива format, flat ассемблер просто записывает генерируемые коды инструкций в выходной файл, создавая таким образом плоский двоичный файл. По умолчанию он генерирует 16-битный код, но вы всегда можете переключить вывод в 16- или 32-битный режим с помощью директив use16 или use32. Некоторые выходные форматы при указании в директиве format переключают вывод в 32-битный режим. Подробнее доступные выходные форматы описаны в разделе 2.4.

Расширение выходного файла выбирается компилятором автоматически, в зависимости от выбранного формата.

Выходной код всегда следует в том порядке, в каком он следует в исходном коде.

1.2 Синтаксис ассемблера

Информация предоставляемая далее, предназначена в основном для программистов, обладающих базовыми знаниями о языке ассемблера, желательно имеющих опыт работы с компиляторами ассемблера. Если вы не обладаете базовыми знаниями об архитектуре ЭВМ и программировании на ассемблере, вам следует изучить дополнительные материалы по соответствующим вопросам.

Flat ассемблер по умолчанию использует синтаксис Intel для ассемблерных инструкций, хотя это можно и изменить используя возможности препроцессора (макроинструкции и символические константы). Он также обладает собственным набором директив - инструкций для компилятора.

Все символические имена определяемые в исходном коде чувствительны к регистру написания символов.

Оператор Бит Байт
byte 8 1
word 16 2
dword 32 4
fword 48 6
pword 48 6
qword 64 8
tword 80 10
dqword 128 16
Таблица 1.1: Операторы размера

1.2.1 Синтаксис инструкций

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

Каждая инструкция состоит из мнемоники, и различного количества операндов, разделённых запятыми. Операндами могут быть регистры, непосредственные значения либо данные адресованные в памяти, они могут также предваряться оператором размера для определения или переопределения их размера (таблица 1.1). Названия доступных регистров можно найти в таблице 1.2, их размер не может быть переопределён. Непосредственное значение может быть задано любым числовым выражением.

Когда операндом являются данные по адресу в памяти, адрес этих данных (также любое числовое выражение, может содержать имена регистров) должен быть заключён в квадратные скобки либо перед ним должен стоять оператор ptr. Например инструкция mov eax,3 поместит непосредственное значение 3 в регистр eax, инструкция mov eax,[7] поместит 32-битное значение хранящееся по адресу 7 в регистр eax, и инструкция mov byte [7],3 поместит непосредственное значение 3 в байт по адресу 7, это можно записать и как mov byte ptr 7,3. Чтобы указать, какой сегментный регистр будет использован для адресации, необходимо указать имя сегментного регистра прямо перед значением адреса отделив их точкой (внутри квадратных скобок или после оператора ptr).

Тип Разрядность Название
Общего назначения 8
16
32
al	cl	dl	bl	ah	ch	dh	bh
ax cx dx bx sp bp si di
eax ecx edx ebx esp ebp esi edi
Сегментные 16
es	cs	ss	ds	fs	gs
Регистры управления 32
cr0		cr2	cr3	cr4
Отладочные 32
dr0	dr1	dr2	dr3			dr6	dr7
FPU 80
st0	st1	st2	st3	st4	st5	st6	st7
MMX 64
mm0	mm1	mm2	mm3	mm4	mm5	mm6	mm7
SSE 128
xmm0	xmm1	xmm2	xmm3	xmm4	xmm5	xmm6	xmm7
Таблица 1.2: Регистры

1.2.2 Объявление данных

Чтобы объявить данные или зарезервировать для них место, используется одна из директив, перечисленных в таблице 1.3. Директивы объявления данных должны завершаться одним или несколькими числовыми выражениями, разделёнными запятыми. Эти выражения задают значения для ячеек данных, размер которых зависит от используемой директивы. Например db 1,2,3 объявит три байта со значениями 1, 2 и 3 соответственно.

Директивы db и du также принимают операнды в виде строк произвольной длины, заключённых в кавычки, которые конвертируются в цепочку байт при использовании db и в цепочку слов с нулевым старшим байтом при использовании du. Например инструкция db 'abc' задаёт три байта со значениями 61, 62 и 63

Директива dp и её синоним df принимают значения состоящие из двух числовых выражений разделённых запятой, первое значение становится старшим словом, а второе - младшим двойным словом значения дальнего указателя. Также dd принимает такие указатели, состоящие из двух слов разделённых запятой. Директива dt принимает только вещественные числа и создаёт данные в формате двойной расширенной точности для FPU.

Директива file - специальная директива, и её синтаксис отличен. Эта директива вставляет последовательность байт из файла, и за ней должно следовать имя файла заключённое в кавычки, за ним, необязательно, числовое выражение отделённое запятой, определяющее смещение в файле, и затем, также необязательно и через запятую, числовое выражение определяющее количество байт (если не указано, вставляются все данные до конца файла).

Размер
(байт)
Объявление
данных
Резервирование
данных
1 db
file
rb
2 dw
du
rw
4 dd rd
6 dp
df
rp
rf
8 dq rq
10 dt rt
Таблица 1.3: Директивы объявления данных

За директивами резервирования данных должноследовать одно числовое выражение, и его значение определяет количество ячеек заданного размера резервируемых директивой. Все директивы объявления также принимают значение ?, которое указывает, что ячейка не должна быть инициализирована, и даёт эффект директивы резервирования. Неинициализированные данные не могут быть включены в выходной файл, поэтому их значения всегда должны полагаться неизвестными.

1.2.3 Константы и метки

В числовых выражениях вместо чисел можно также использовать константы или метки. Для объявления константы или метки необходимо использовать специальные директивы. Каждая метка может быть определена только один раз, и может быть использована в любом месте программы (даже раньше своего объявления). Константа может переопределяться произвольное количество раз, но в этом случае она доступна только после объявления, и всегда имеет значение данное ей в последнем переопределении перед местом использования. Если константа объявлена в коде только один раз, она как и метка доступна отовсюду.

Объявление константы состоит из имени константы, символа =, и числового выражения, которое после вычисления станет значением константы. Это значение всегда вычисляется в тот момент, когда константа объявляется. Например можно объявить константу count используя директиву count = 17, и затем использовать её в инструкции ассемблера, например mov cx,count - которая преобразуется в mov cx,17 во время компиляции.

Существуют разные способы объявления меток. Самый простой это поставить двоеточие после имени метки, за этой директивой может следовать инструкция в той же строке. Она определяет метку, значение которой равно смещению точки объявления. Этот метод обычно используется чтобы помечать точки кода. Другой способ объявления метки - записать после имени метки директиву объявления данных (без двоеточия). Так объявляется метка, значение которой равно смещению начала зарезервированной области данных, такая метка становится меткой для данных, с размером ячейки определяемым директивой объявления данных согласно таблице 1.3.

Метка может рассматриваться как константа со значением равным смещению помеченного кода или данных. Например если данные объявлены с использованием метки - char db 224, для того, чтобы поместить смещение байта адресуемого меткой char в регистр bx необходимо использовать инструкцию mov bx,char, а чтобы поместить значение этого байта в регистр dl, необходимо использовать инструкцию mov dl,[char] (или mov dl,ptr char). Но когда вы попытаетесь ассемблировать mov ax,[char], это вызовет ошибку, потому что fasm сравнивает размеры операндов, а они должны быть одинаковыми. Такую инструкцию можно ассемблировать, принудительно задав размер операндов: mov ax,word [char], но помните, что эта инструкция прочитает два байта начиная с адреса char, в то время как метка была объявлена для однобайтовой области памяти.

Последний, и самый гибкий способ объявления меток, это использование директивы label. За этой директивой должно следовать имя метки, затем, необязательно, оператор размера, и затем, тоже необязательно, оператор at и числовое выражение задающее адрес для которого должна быть объявлена метка. Например label wchar word at char объявит новую метку для 16-битной области данных по адресу char. Тогда инструкция mov ax,[wchar] после компиляции будет идентична инструкции mov ax,word [char]. Если адрес не указан, директива label объявляет метку по текущему смещению. Так mov [wchar],57568 скопирует два байта, в то время как mov [char],224 скопирует один байт по одному и тому же адресу.

Метка, имя которой начинается с точки считается локальной меткой, и её имя присоединяется к имени последней предшествующей глобальной метки (имя которой не начинается с точки) чтобы составить полное имя этой метки. Короткое имя (начинающееся с точки) можно использовать в коде до определения следующей глобальной метки, а во всех остальных участках кода необходимо использовать полное имя метки. Метки, имя которых начинается с двух точек исключение - они подобны глобальным, но они не становятся новым префиксом для локальных меток.

Имя @@ обозначает безымянную метку, в коде их может быть определено произвольное количество. Символ @b (или эквивалент - @r) ссылается на ближайшую предшествующую безымянную метку, символ @f ссылается на ближайшую последующую безымянную метку. Эти спецсимволы не чувствительны к регистру написания.

Директива load позволяет объявить константу с двоичным значением, загруженным из уже ассемблированного кода. За директивой должно следовать имя константы, затем, необязательно, оператор размера, а затем оператор from и числовое выражение указывающее корректный адрес в генерируемом в данный момент кодовом пространстве. Оператор размера в этом случае имеет специальный смысл - он указывает, сколько байт (вплоть до 8) должно быть загружено, чтобы сформировать двоичное значение константы. Если оператор размера не указан, загружается один байт (значение в диапазоне от 0 до 255). Загруженные данные не могут превосходить текущего смещения.

1.2.4 Числовые выражения

В предыдущих примерах все числовые выражения были обыкновенными числами, константами или метками. Но они могут быть более сложными, с использованием арифметических или логических операторов для вычислений на этапе компиляции. Все эти операции с их приоритетами выполнения перечислены в таблице 1.4. Операции с более высоким приоритетом будут вычислены первыми, хотя конечно можно изменить порядок вычисления заключив части выражения в скобки. Операторы +,-,* и / это стандартные арифметические операции, mod вычисляет остаток от деления. Операторы and, or, xor, shl, shr и not выполняют те же логические операции что и ассемблерные инструкции с соответствующими названиями. Оператор rva специфичен для выходного формата PE и выполняет преобразование адреса в RVA.

Числа в числовых выражениях по умолчанию считаются десятичными, двоичные числа должны быть записаны с буквой b на конце, восьмеричные заканчиваться буквой o, шестнадцатеричные числа должны начинаться с символов 0x (как в языке C) или с символа $ (как в языке Pascal) или заканчиваться символом h. Также строки в кавычках в числовом выражении будут преобразованы в число - первый символ станет самым младшим байтом числа.

Приоритет Операторы
0 +
-
1 *
/
2 mod
3 and
or
xor
4 shl
shr
5 not
6 rva
Таблица 1.4: Арифметические и логические операторы по приоритетам

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

Существует также несколько специальных символов которые могут быть использованы в числовом выражении. Первый это $, который всегда равен сначению текущего смещения. Второй это %, который равен номеру текущего повторения в частях кода которые повторяются с использованием некоторых специальных директив (см. 2.2). Существует также символ %t, который всегда равен текущему времени.

Любое числовое выражение может также состоять из одиночного вещественного значения с плавающей точкой (flat ассемблер не позволяет производить вычисления с плавающей точкой на этапе компиляции) в научной (экспоненциальной) записи, они могут заканчиваться буквой f чтобы быть распознанными, иначе они должны содержать хотя бы один из символов . или E. Так что 1.0, 1E0 и 1f задают одно и тоже значение с плавающей точкой, в то время как 1 задаёт целое число.

1.2.5 Переходы и вызовы

Операнд любой инструкции перехода или вызова может быть предварён не только оператором размера, но также и одним из операторов указывающих тип перехода: near или far (ближний или дальний). Например, когда ассемблер находится в 16-битном режиме, инструкция jmp dword [0] станет дальним переходом, а когда ассемблер в 32-битном режиме, она станет ближним переходом. Чтобы принудительно изменить тип перехода используются формы jmp near dword [0] или jmp far dword [0].

Когда операндом ближнего перехода является непосредственное значение, ассемблер сгенерирует кратчайший вариант этой инструкции перехода если это возможно (но не создаст 32-битной инструкции в 16-битном режиме, как и 16-битной в 32-битном режиме, если только нет принуждающего оператора размера). Указывая оператор размера вы всегда сможете принудительно генерировать длинный вариант (например jmp word 0 в 16-битном режиме и jmp dword 0 в 32-битном режиме) или всегда генерировать короткий вариант и получать ошибку когда это невозможно (например jmp byte 0).

1.2.6 Задание размера

Когда инструкция использует адресацию в памяти, по умолчанию генерируется кратчайшая 8-битная форма если адрес находится в подходящем диапазоне, но поведение ассемблера может быть изменено применением операторов word или dword перед адресом внутри квадратных скобок (или после оператора ptr). Такое расположение оператора размера может также быть использовано для принудительного изменения размера адреса на отличный от размера по умолчанию для данного режима.

Инструкции adc, add, and, cmp, or, sbb, sub и xor с 16- или 32-битным первым операндом по умолчанию генерируются в сокращённой 8-битной форме, когда второй операнд является непосредственным значением умещающимся в диапазон для 8-битных значений со знаком. Это поведение можно изменить указанием оператора word или dword перед непосредственным значением. Те же правила распространяются и на инструкцию imul, с непосредственным значением в качестве второго операнда.

Непосредственное значение в качестве операнда инструкции push без оператора размера по умолчанию рассматривается как слово в 16-битном режиме ассемблера, и как двойное слово в 32-битном режиме ассемблера, короткая 8-битная форма этой инструкции используется там где это возможно, операторы размера word или dword принудительно генерируют инструкцию push в длинной форме для указанного размера. Мнемоники pushw и pushd принуждают ассемблер сгенерировать 16- или 32-битный код без использования длинной формы инструкции.

Глава 2

Набор инструкций