Flat Assembler 1.64. Мануал программера - Tomasz Grysztar
Шрифт:
Интервал:
Закладка:
Продемонстрируем это на примере. Пусть «foo» — это макрос, а «bar» — это структура. Эти строки:
foo equ
foo bar
обе будут интерпретированы как вызовы макроса «foo», так как значение первого символа берет верх над значением второго.
Макроинструкции генерируют новые строки от их блоков определения, заменяя параметры на их значения и далее обрабатывая операторы «#» и «`». Оператор конверсии имеет высший приоритет, чем оператор сцепления.
После завершения этого, заново сгенерированная строка проходит через стандартный препроцессинг, как описано выше.
Хотя обычно символьные константы заменяются исключительно в строках, нет ни директив препроцессора, ни макроинструкций, встречается несколько особых ситуаций, где замены проводятся в частях строк, содержащих директивы. Первая — это определение символьной константы, где замены производятся везде после слова «equ» и результирующее значение присваивается новой константе (смотрите 2.3.2). Вторая такая ситуация — это директива «match», где замены производятся в символах, следующих за запятой перед сопоставлением их с образцом. Эти свойства могут использоваться, например, для сохранения списков, как, например, эта совокупность определений:
list equ
macro append item
{
match any, list { list equ list,item }
match, list { list equ item }
}
Здесь константа «list» инициализируется с пустым значением, и макрос «append» может использоваться для добавления новых пунктов к списку, разделяя их запятыми. Первое сопоставление в этом макросе происходит, только если значение списка непусто (смотрите 2.3.6), таким образом новое его значение — это предыдущее с запятой и новым пунктом, добавленным в конец. Второе сопоставление происходит, только если список все еще пуст, и таким образом список определяется как содержащий только лишь новый пункт. Так, начиная с пустого списка, «append 1» определит «list equ 1», а «append 2», следующий за ним, определит «list equ 1,2». Может потребоваться использовать этот список как параметры к некоторому макросу. Но это нельзя сделать прямо — если «foo» это макрос, то в строке «foo list» символ «list» просто прошел бы как параметр к макросу, поскольку символьные константы на этой стадии ещё не развернуты. Для этой цели снова оказывается удобна директива «match»:
match params, list { foo params }
Значение «list», если оно не пустое, соответствует ключевому слову «params», которое далее во время генерации строк, заключенных в фигурные скобки, заменяется на соответственное значение. Так, если «list» имеет значение «1,2», строка, указанная выше, сгенерирует строку, содержащую «foo 1,2», которая далее пройдет стандартный препроцессинг.
Есть ещё один особый случай — когда препроцессор собирается проверить второй символ и натыкается на двоеточие (что далее интерпретируется ассемблером как определение метки), он останавливается в этом месте и заканчивает препроцессинг первого символа (то есть если это символьная константа, она развертывается) и если это все еще выглядит меткой, совершается стандартный препроцессинг, начиная с места после метки. Это позволяет поместить директивы препроцессора и макроинструкции после меток, аналогично инструкциям и директивам, обрабатываемым ассемблером, например:
start: include 'start.inc'
Однако если метка во время препроцессинга разрушается (например, если у символьной константы пустое значение), происходит только замена символьных констант до конца строки.
2.4 Директивы форматирования
«format» со следующим за ним идентификатором формата позволяет выбрать формат вывода. Эта директива должна стоять в начале кода. Формат вывода по умолчанию — это простой двоичный файл, он может быть также выбран директивой «format binary».
«use16» и «use32» указывают ассемблеру генерировать 16-битный или 32-битный код, пренебрегая настройкой по умолчанию для выбранного формата вывода. «use64» включает генерирование кода для длинного режима процессоров x86.
Ниже описаны разные форматы вывода со специфичными для них директивами.
2.4.1 MZ
Чтобы выбрать формат вывода MZ, используйте директиву «format MZ». По умолчанию код для этого формата 16-битный.
«segment» определяет новый сегмент, за ним должна следовать метка, чьим значением будет номер определяемого сегмента. Опционально за этой директивой может следовать «use16» или «use32», чтобы указать битность кода в сегменте. Начало сегмента выровнено по параграфу (16 байт). Все метки, определенные далее, будут иметь значения относительно начала этого сегмента.
«entry» устанавливает точку входа для формата MZ, за ней должен следовать дальний адрес (имя сегмента, двоеточие и смещение в сегменте) желаемой точки входа.
«stack» устанавливает стек для MZ. За директивой может следовать числовое выражение, указывающее размер стека для автоматического создания, либо дальний адрес начального стекового фрейма, если вы хотите установить стек вручную. Если стек не определен, он будет создан с размером по умолчанию в 4096 байт.
«heap» со следующим за ней значением определяет максимальный размер дополнительного места в параграфах (это место в добавление к стеку и для неопределенных данных). Используйте «heap 0», Чтобы всегда отводить только память, которая программе действительно нужна.
2.4.2 PE
Чтобы выбрать формат вывода PE, используйте директиву «format PE», за ней могут следовать дополнительные настройки формата: используйте «console», «GUI» или оператор «native», чтобы выбрать целевую субсистему (далее может следовать значение с плавающей точкой, указывающее версию субсистемы), «DLL» помечает файл вывода как динамическую связывающую библиотеку. Далее может следовать оператор «at» и числовое выражение, указывающее базу образа PE и далее опционально оператор «on» со следующей за ним строкой в кавычках, содержащей имя файла, выбирающей заглушку MZ для PE программы (если указанный файл не в формате MZ, то он трактуется как простой двоичный исполняемый файл и конвертируется в формат MZ). По умолчанию код для этого формата 32-битный. Пример объявления формата PE со всеми свойствами:
format PE GUI 4.0 DLL at 7000000h on 'stub.exe'
«section» определяет новую секцию, за ней должна следовать строка в кавычках, определяющая имя секции, и далее могут следовать один или больше флагов секций. Возможные флаги такие: «code», «data», «readable», «writeable», «executable», «shareable», «discardable», «notpageable». Начало секции выравнивается по странице (4096 байт). Пример объявления секции PE:
section '.text' code readable executable
Вместе с флагами также может быть определен один из специальных идентификаторов данных PE, отмечающий всю секцию как специальные данные, возможные идентификаторы: «export», «import», «resource» и «fixups». Если секция помечена для содержания настроек адресов, они генерируются автоматически, и никаких данных определять больше не требуется. Также данные ресурсов могут быть сгенерированы автоматически из файлов ресурсов, этого можно добиться, написав после идентификатора «resourse» оператор «from» и имя файла в кавычках. Ниже вы можете увидеть примеры секций, содержащих некоторые специальные данные:
section '.reloc' data discardable fixups
section '.rsrc' data readable resource from 'my.res'
«entry» создает точку входа для PE, далее должно следовать значение точки входа.
«stack» устанавливает размер стека для PE, далее должно следовать значение зарезервированного размера стека, опционально может следовать отделенное запятой значение начала стека. Если стек не определен, ему присваивается размер по умолчанию, равный 4096 байт.
«heap» выбирает размер дополнительного места для PE, далее должно следовать значение для зарезервированного для него места, опционально ещё может быть значение его начала, отделенное запятой. Если дополнительное место не определено, оно ставится по умолчанию равным 65536 байт, если не указано его начало, то оно устанавливается равным 0.
«data» начинает определение специальных данных PE, за директивой должен следовать один из идентификаторов данных («export», «import», «resource» или «fixups») или номер записи данных в заголовке PE. Данные должны быть определены на следующих строках и заканчиваться директивой «end data». Если выбрано определение настроек адресов, они генерируются автоматически, и никаких данных определять больше не требуется. То же самое относится к ресурсам, если за идентификатором «resourse» следует оператор «from» и имя файла в кавычках — в этом случае данные берутся из этого файла ресурсов.