Руководство по препроцессору FASM перевод TAJGA FASM Tutorial by vid - FASM preprocessor guide перевел S.T.A.S. Содержание Об этом документе Общие понятия 2.1. Что такое препроцессор 2.2. Комментарии ";" 2.3.Перенос строки "\" 2.4. Директива "INCLUDE" Присваивания 3.1. Директива "EQU" 3.2. Директива "RESTORE" Простые макросы без аргументов 4.1. Определение простых макросов 4.2. Вложенные макросы 4.3. Директива "PURGE" (отмена определения макроса) 4.4. Поведение макросов Макросы с фиксированным количеством аргументов 5.1. Макросы с одним аргументом 5.2. Макросы с несколькими аргументами 5.3. Директива "LOCAL" 5.4. Оператор объединения "#" 5.5. Оператор "`" Макросы с групповыми аргументами 6.1. Определение макросов с групповым аргументом 6.2. Директива "COMMON" 6.3. Директива "FORWARD" 6.4. Директива "REVERSE" 6.5. Комбинирование директив управления группами 6.6. Директива "LOCAL" в макросах с групповыми аргументами 6.7. Макросы с несколькими групповыми аргументами Условный препроцессинг 7.1. Оператор "EQ" 7.2. Оператор "EQTYPE" 7.3. Оператор "IN" Структуры Оператор FIX и макросы внутри макросов 9.1. Explaination of fixes 9.2. Using fixes for nested macro declaration 9.3. Using fixes for moving part of code Заключение
1. Об этом документе Я написал это потому что вижу, как многие задают вопросы на форуме FASM, связанные с непониманием идей или особенностей препроцессора. (Я не отговариваю Вас задавать такие вопросы, непонимание чего-то - это вполне нормально, и если Ваш вопрос не чересчур сложен, кто-нибудь наверняка на него ответит). Если Вам что-нибудь из туториала покажется непонятным, пожалуйста, напишите на форум FASM, форум WASM, автору или переводчику. 2. Общие понятия 2.1. Что такое препроцессор Препроцессор - это программа (или чаще - часть компилятора), которая преобразует исходный текст непосредственно перед компиляцией. К примеру, если Вы используете какой-либо кусок кода довольно часто, можно дать ему некое имя и заставить препроцессор повсеместно заменять это имя в исходном тексте на соответствующий ему код. Другой пример - Вы хотите имитировать инструкцию, которая на самом деле не существует. В таком случае препроцессор может заменять её последовательностью инструкций дающих желаемый эффект. Препроцессор просматривает исходный текст и заменяет некоторые вещи другими. Но как объяснить препроцессору, что именно он должен делать? Для этих целей существуют директивы препроцессора. О них мы и будем говорить. Препроцессор понятия не имеет о инструкциях, директивах компилятора и прочих подобных вещах. Для него существуют собственные команды, и он игнорирует всё остальное. 2.2. Комментарии ";" Подобно большинству ассемблеров, комментарии в FASM начинаются с точки с запятой ";". Всё, что следует за этим символом до конца строки игнорируется и удаляется из исходника. К примеру, исходный текст Код (ASM): ; заполним 100h байтов адресуемых EDI нулями xor eax, eax ; обнуляем eax mov ecx, 100h/4 rep stosd после препроцессора превращается в Код (ASM): xor eax,eax mov ecx,100h/4 rep stosd ПРИМЕЧАНИЕ: ; можно рассматривать как директиву препроцессора, удаляющую текст начиная с этого символа до конца строки. ПРИМЕЧАНИЕ: Строка, полностью состоящая из комментария не будет удалена. Она становится пустой строкой (см. пример выше). Это будет важно в дальнейшем. 2.3. Перенос строки (Line Break "\") Если строка выглядит слишком длинной, возможно разделить её на несколько, используя символ "\". При обработке препроцессором следующая строка будет добавлена к текущей. Например: Код (ASM): db 1, 2, 3,\ 4, 5, 6,\ 7, 8, 9 будет преобразовано в: Код (ASM): db 1,2,3,4,5,6,7,8,9 Конечно, \ в составе текстовой строки или комментария не вызовет объединения строк. Внутри текстовой строки этот символ воспринимается как обычный ASCII символ (как и всё остальное заключённое между кавычками ' или "). Комментарии же удаляются без анализа того, что в них написано. В строке после символа \ могут быть только пробелы или комментарии. Ранее, я упоминал, что строка, состоящая только из комментария не удаляется, а заменяется на пустую строку. Это значит, что код, подобный этому: Код (ASM): db 1, 2, 3,\ ; 4,5,6,\ - закомментировано 7, 8, 9 преобразуется в: Код (ASM): db 1, 2, 3 7, 8, 9 и вызовет ошибку. Выход из положения - помещать символ \ до комментария: Код (ASM): db 1, 2, 3,\ \; 4,5,6 - правильно закомментировано 7, 8, 9 в результате будет: Код (ASM): db 1, 2, 3, 7, 8, 9 как мы и хотели. 2.4. Директива INCLUDE Синтаксис: Код (Text): include <некая строка содержащая имя файла file_name> Эта директива вставляет содержимое файла file_name в исходный текст. Вставленный текст, естественно, тоже будет обработан препроцессором. Имя файла (и путь к нему, если он есть) должны быть заключены в кавычки " или апострофы '. Например: Код (ASM): include 'file.asm' include 'HEADERS\data.inc' include '..\lib\strings.asm' include 'C:\config.sys' Можно также использовать переменные окружения ОС, помещая их имена между символами %: Код (ASM): include '%FASMINC%\win32a.inc' include '%SYSTEMROOT%\somefile.inc' include '%myproject%\headers\something.inc' include 'C:\%myprojectdir%\headers\something.inc' 2.5. Strings preprocessing You may have problem to include ' in string declared using 's or " in string declared using "s. For this reason you must place the character twice into string, in that case it won't end string and begin next as you may think, but it will include character into string literaly. Например: Код (ASM): db 'It''s okay' will generate binary containing string It's okay. It's same for ". 3. Присваивания (Equates) 3.1. Директива EQU Простейшая команда препроцессора. Синтаксис: Код (ASM): <name1> equ <name2> Это команда говорит препроцессору, что необходимо заменить все последующие <name1> на <name2>. Например: Код (ASM): count equ 10 ; это команда препроцессора mov ecx, count преобразуется в: Код (ASM): mov ecx, 10 Ещё пример: Код (ASM): mov eax, count count equ 10 mov ecx, count преобразуется в: Код (ASM): mov eax, count mov ecx,10 потому что препроцессор заменит count только после директивы equ. Даже это работает: Код (ASM): 10 equ 11 mov ecx, 10 после обработки препроцессором, получим: Код (ASM): mov ecx, 11 Обратите внимание, name1 может быть любым идентификатором. Идентификатор - это всего лишь набор символов, завершаемый пробелом (space), символом табуляции (tab), концом строки (EOL), комментарием ;, символом переноса строки \ или оператором, включая операторы ассемблера и/или специальные символы вроде , или }. name2 может быть не только единичным идентификатором, берутся все символы до конца строки. name2 может и отсутствовать, тогда name1 будет заменен на пустое место. Например: Код (ASM): 10 equ 11, 12, 13 db 10 получим: Код (ASM): db 11, 12, 13 3.2. Директива RESTORE Можно заставить препроцессор прекратить заменять идентификаторы, определённые директивой EQU. Это делает директива RESTORE Синтаксис: Код (Text): restore <name1> name1 - это идентификатор определённый ранее в директиве EQU. После этой команды name1 больше не будет заменяться на name2. Например: Код (ASM): mov eax, count count equ 10 mov eax, count restore count mov eax, count получим: Код (ASM): mov eax, count mov eax, 10 mov eax, count Обратите внимание, что для определений сделанных директивой EQU работает принцип стека. То есть, если мы два раза определим один и тот же идентификатор используя EQU, то после однократного использования RESTOREзначение идентификатора будет соответствовать определённому первой директивой EQU. Например: Код (ASM): mov eax, count count equ 1 mov eax, count count equ 2 mov eax, count count equ 3 mov eax, count restore count mov eax, count restore count mov eax,count restore count mov eax,count получим: Код (ASM): mov eax, count mov eax, 1 mov eax, 2 mov eax, 3 mov eax, 2 mov eax, 1 mov eax, count Если попытаться выполнить RESTORE большее количество раз, чем было сделано EQU, никаких предупреждений выдано не будет. Значение идентификатора будет неопределенно. Например: Код (ASM): mov eax, count restore count mov eax, count получим: Код (ASM): mov eax, count mov eax, count
4. Простые макросы без аргументов 4.1. Определение простых макросов Использую EQU можно делать наиболее простые замены в исходном тексте при обработке препроцессором. Большими возможностями обладают макросы. Командой MACRO можно создавать собственные инструкции. Синтаксис: Код (Text): macro name { ; тело макроса } Когда препроцессор находит директиву macro, он определяет макрос с именем name. Далее, встретив в исходном тексте строку, начинающуюся с name, препроцессор заменит name на тело макроса - то, что указано в определении между скобками { и }. Имя макроса может быть любым допустимым идентификатором, а тело макроса - всё, что угодно, за исключением символа }, который означает завершение тела макроса. Например: Код (ASM): macro a { push eax } xor eax, eax a будет заменено на: Код (ASM): xor eax, eax push eax Или: Код (ASM): macro a { push eax } macro b { push ebx } b a получим: Код (ASM): push ebx push eax Разумеется, макросы не обязательно оформлять так, как выше, можно делать и так: Код (ASM): macro push5 {push dword 5} push5 получим: Код (ASM): push dword 5 Или: Код (ASM): macro push5 {push dword 5 } с тем же самым результатом. Скобочки можете размещать как хотите. 4.2. Вложенные макросы Макросы могут быть вложенными один в другой. То есть, если мы переопределим макрос, будет использовано последнее определение. Но если в теле нового определения содержится тот же макрос, то будет использовано предыдущее определение. Посмотрите пример: Код (ASM): macro a { mov ax, 5} macro a { a mov bx, 5 } macro a { a mov cx, 5 } a в результате получим: Код (ASM): mov ax, 5 mov bx, 5 mov cx, 5 Или такой пример: Код (ASM): macro a {1} a macro a { a 2 } a macro a { a 3 } a получим: Код (Text): 1 1 2 1 2 3 4.3. Директива PURGE. Отмена определения макроса Как и в случае с директивой EQU, можно отменить определение макроса. Для этого используется директива PURGE с указанием имени макроса. Синтаксис: Код (Text): purge name Пример: Код (ASM): a macro a {1} a macro a {2} a purge a a purge a a получим: Код (Text): a 1 2 1 a Если применить PURGE к несуществующему макросу, ничего не произойдёт. 4.4. Поведение макросов Имя макроса будет заменено его телом не только в том случае, если оно расположено в начале строки. Макрос может находиться в любом месте исходного текста, где допустима мнемоника инструкции (например, add или mov). Всё потому, что основное предназначение макросов - имитировать инструкции. Единственное исключение из этого правила - макросы недопустимы после префиксов инструкций (rep). Пример: Код (ASM): macro CheckErr { cmp eax, -1 jz error } call Something a: CheckErr ; здесь макросу предшествует метка, всё Ок. получим: Код (ASM): call Something a: cmp eax,-1 jz error Пример #2: Код (ASM): macro stos0 { mov al, 0 stosb } stos0 ;это место инструкции, будет замена. here: stos0 ;это тоже место инструкции. db stos0 ;здесь инструкции не место, замены не будет. получим: Код (ASM): mov al, 0 stosb here: mov al, 0 stosb db stos0 Возможно переопределять (overload) инструкции посредством макросов. Так как препроцессор ничего об инструкциях не знает, он позволяет использовать мнемонику инструкции в качестве имени макроса: Код (ASM): macro pusha { push eax ebx ecx edx ebp esi edi } macro popa { pop edi esi ebp edx ecx ebx eax } эти две новые инструкции будут экономить по четыре байта в стеке, так как не сохраняют ESP (правда, занимают побольше места, чем реальные инструкции . Всё же, переопределение инструкций не всегда хорошая идея - кто-нибудь читая Ваш код может быть введён в заблуждение, если он не знает, что инструкция переопределена. Также, возможно переопределять директивы ассемблера: Код (ASM): macro use32 { align 4 use32 } macro use16 { align 2 use16 } 5. Макросы с фиксированным количеством аргументов 5.1. Макросы с одним аргументом Макросы могут иметь аргумент. Аргумент представляет собой какой-либо идентификатор, который будет повсюду заменён в теле макроса тем, что будет указанно при использовании. Синтаксис: Код (Text): macro <name> <argument> { <тело макроса> } Например: Код (ASM): macro add5 where { add where, 5 } add5 ax add5 [variable] add5 ds add5 ds+2 получим: Код (ASM): add ax, 5 add [variable], 5 add ds, 5 ;такой инструкции не существует ;но препроцессор это не волнует. ;ошибка появится на стадии ассемблирования. add ds+2,5 ;ошибка синтаксиса, как и ранее ;определится при анализе синтаксиса (parsing). (разумеется, комментарии в результате работы препроцессора не появятся 5.2. Макросы с несколькими аргументами У макросов может быть несколько аргументов, разделённых запятыми ",": Код (ASM): macro movv where, what { push what pop where } movv ax, bx movv ds, es movv [var1], [var2] преобразуется в: Код (ASM): push bx pop ax push es pop ds push [var2] pop [var1] Если несколько аргументов имеют одно и тоже имя, то будет использован первый из них . Если при использовании макроса указать меньше аргументов, чем при определении, то значения неуказанных будет пустым: Код (ASM): macro pupush a1, a2, a3, a4 { push a1 a2 a3 a4 pop a4 a3 a2 a1 } pupush eax, dword [3] Если в аргументе макроса необходимо указать запятую как символ (","), тогда необходимо аргумент заключить в скобочки из символов < и >. Код (ASM): macro safe_declare name, what { if used name name what end if} safe_declare var1, db 5 safe_declare array5, <dd 1,2,3,4,5> safe_declare string, <db "привет, я просто строка",0> получим: Код (ASM): if used var1 var1 db 5 end if if used array5 array5 dd 1,2,3,4,5 end if if used string string db "привет, я просто строка",0 end if Конечно же, можно использовать символы < и > и внутри тела макроса: Код (ASM): macro a arg {db arg} macro b arg1,arg2 {a <arg1,arg2,3>} b <1,1>,2 получим: db 1,1,2,3 5.3. Директива "LOCAL" Возможно, появится необходимость объявить метку внутри тела макроса: Код (ASM): macro pushstr string { call behind ;помещаем в стек адрес string и переходим к behind db string, 0 behind: } но если использовать такой макрос 2 раза, то и метка behind будет объявлена дважды, что приведёт к ошибке. Эта проблема решается объявлением локальной метки behind. Это и делает директива LOCAL. Синтаксис: Код (Text): local label_name Директива должна применяться внутри тела макроса. Все метки label_name внутри макроса становятся локальными. Так что, если макрос используется дважды никаких проблем не появляется: Код (ASM): macro pushstr string { local behind call behind db string,0 behind: } pushstr 'aaaaa' pushstr 'bbbbbbbb' call something На самом деле, behind заменяется на behind?XXXXXXXX, где XXXXXXXX - какой-то шестнадцатеричный номер генерируемый препроцессором. Последний пример может быть преобразован к чему-то вроде: Код (ASM): call behind?00000001 db 'aaaaa', 0 behind?00000001: call behind?00000002 db 'bbbbbbbb', 0 behind?00000002: call something Заметьте, Вы не сможете напрямую обратиться к метке содержащей ?, так как это специальный символ в FASM, поэтому он и используется в локальных метках. К примеру, aa?bb рассматривается как идентификатор aa, специальный символ ? и идентификатор bb. Если Вам нужно несколько локальных меток - не проблема, их можно указать в одной директиве LOCAL, разделив запятыми: Код (ASM): macro pushstr string ;делает то же, что и предыдущий макрос { local addr, behind push addr jmp behind addr db string,0 behind: } Всегда хорошо бы начинать все локальные метки макросов с двух точек .. - это значит, что они не будут менять текущую глобальную метку. К примеру Код (ASM): macro pushstr string { local behind call behind db string, 0 behind: } MyProc: pushstr 'aaaa' .a: будет преобразовано в: Код (ASM): MyProc: call behind?00000001 db 'aaaa', 0 behind?00000001: .a: в результате получим метку behind?00000001.a вместо MyProc.a. Но если в примере выше behind заменить на ..behind, текущая глобальная метка не изменится и будет определена метка MyProc.a: Код (ASM): macro pushstr string { local ..behind call ..behind db string,0 ..behind: } MyProc: pushstr 'aaaa' .a:
5.4. Оператор объединения # У макроязыка FASMа есть ещё одна возможность - манипуляции с идентификаторами. Делается это оператором #, который объединяет два идентификатора в один. К примеру, a#b становится ab, а aaa bbb#ccc ddd -- aaa bbbccc ddd. Оператор # может быть использован только внутри тел макросов, а объединение символов происходит после замены аргументов макроса параметрами. Так что его можно использовать для создания новых идентификаторов из переданных в макрос параметров: Код (ASM): macro string name, data { local ..start ..start: name db data,0 sizeof.#name = $ - ..start } string s1,'нудные макросы' string s2,<'а вот и я',13,10,'заставлю тебя их видеть во сне'> получим: Код (ASM): ..start?00000001: s1 db 'нудные макросы',0 sizeof.s1 = $ - ..start?00000001 ..start?00000002: s2 db 'а вот и я',13,10,'заставлю тебя их видеть во сне',0 sizeof.s2 = $ - ..start?00000002 так что для всех строк, создаваемых этим макросом будет определён идентификатор sizeof.имя строки, равный количеству байт строки. Оператор # способен так же объединять символьные строки: Код (ASM): macro debug name { db 'name: '#b,0 } debug '1' debug 'foobar' будет: Код (ASM): db 'name: 1',0 db 'name: foobar',0 Это полезно при передаче аргументов из макроса в макрос: Код (ASM): macro pushstring string { local ..behind call ..behind db string,0 ..behind: } macro debug string { push MB_OK push 0 ;empty caption pushstring 'debug: '#string ;принимает один аргумент push 0 ;нет окна-предка call [MessageBox] } Обратите внимание, нельзя использовать # совместно с идентификаторами, определёнными local, так как localобрабатывается препроцессором раньше, чем #. Из-за этого подобный код работать не будет: Код (ASM): macro a arg { local name_#arg } a foo 5.5. Оператор "`" Существует оператор, преобразующий идентификатор в символьную строку. Он так же может быть использован только внутри макросов: Код (ASM): macro proc name { name: log `name ;log - макрос, принимающий параметр-строку } proc DummyProc получим: Код (ASM): DummyProc: log 'DummyProc' Пример посложнее, с использованием "#" Код (ASM): macro proc name { name: log 'начинается подпрограмма: '#`name } proc DummyProc retn proc Proc2 retn будет: Код (ASM): DummyProc: log 'начинается подпрограмма: DummyProc' retn Proc2: log 'начинается подпрограмма: Proc2' retn 6. Макросы с групповыми аргументами 6.1. Определение макросов с групповым аргументом У макросов могут быть так называемые групповые аргументы. Это позволяет использовать переменное количество аргументов. При определении макроса, групповой аргумент заключается в квадратные скобочки "[" и "]": Синтаксис: Код (ASM): macro name arg1, arg2, [grouparg] { <тело макроса> } Среди аргументов в определении макроса, групповой аргумент должен быть последним. Групповой аргумент может содержать несколько значений: Код (ASM): macro name arg1,arg2,[grouparg] {} name 1,2,3,4,5,6 В этом примере значение arg1 равно 1, arg2 равно 2, а grouparg равно 3,4,5 и 6 6.2. Директива "COMMON" Для работы с групповыми аргументами применяются специальные директивы препроцессора. Они могут быть использованы только внутри тела макроса имеющего групповой аргумент. Первая такая директива - это "COMMON". Она означает, что после нее имя группового аргумента будет замещаться всеми аргументами сразу: Код (ASM): macro string [grp] { common db grp,0 } string 'aaaaaa' string 'line1',13,10,'line2' string 1,2,3,4,5 получим: Код (ASM): db 'aaaaaa',0 db 'line1',13,10,'line2',0 db 1,2,3,4,5,0 6.3. Директива "FORWARD" Аргументы можно обрабатывать и по-отдельности. Для этого служит директива "FORWARD". Часть тела макроса после этой директивы обрабатывается препроцессором для каждого аргумента из группы: Код (ASM): macro a arg1,[grparg] { forward db arg1 db grparg } a 1,'a','b','c' a -1, 10, 20 будет: Код (ASM): db 1 db 'a' db 1 db 'b' db 1 db 'c' db -1 db 10 db -1 db 20 Директива "FORWARD" работает по умолчанию для макросов с групповыми аргументами, так что предыдущий пример можно сделать так: Код (ASM): macro a arg1,[grparg] { db arg1 db grparg } 6.4. Директива "REVERSE" "REVERSE" - это аналог "FORWARD", но обрабатывает группу аргументов в обратном порядке - от последнего к первому: Код (ASM): macro a arg1,[grparg] { reverse db arg1 db grparg } a 1,'a','b','c' получим: Код (ASM): db 1 db 'c' db 1 db 'b' db 1 db 'a' 6.5. Комбинирование директив управления группами Три вышеупомянутые директивы могут разделять тело макроса на блоки. Каждый блок обработается препроцессором после предыдущего. Например: Код (ASM): macro a [grparg] { forward f_#grparg: ;оператор объединения common db grparg reverse r_#grparg: } a 1,2,3,4 будет: Код (ASM): f_1: f_2: f_3: f_4: db 1,2,3,4 r_4: r_3: r_2: r_1: 6.6. Директива LOCAL в макросах с групповыми аргументами У локальных меток в макросах есть ещё одно полезное свойство. Если директива "LOCAL" находится внутри блока "FORWARD" или "REVERSE", то уникальное имя метки сгенерируется для каждого аргумента из группы, и в последующих блоках "FORWARD" и/или "REVERSE" для каждого аргумента будет использована соответствующая ему метка: Код (ASM): macro string_table [string] { forward ;таблица указателей на строки local addr ;локальная метка для строки dd addr ;указатель на строку forward ;строки addr db string,0 ;создаём и завершаем нулём } string_table 'aaaaa','bbbbbb','5' получим: Код (ASM): dd addr?00000001 dd addr?00000002 dd addr?00000003 addr?00000001 db 'aaaaa',0 addr?00000002 db 'bbbbbb',0 addr?00000003 db '5',0 Другой пример с блоком "REVERSE": Код (ASM): macro a [x] { forward local here here db x reverse dd here } a 1,2,3 будет: Код (ASM): here?00000001 db 1 here?00000002 db 2 here?00000003 db 3 dd here?00000003 dd here?00000002 dd here?00000001 Как видно, метки используется с соответствующими аргументами и в "FORWARD"- и в "REVERSE"-блоках. 6.7. Макросы с несколькими групповыми аргументами Возможно использовать и несколько групповых аргументов. В этом случае определение макроса не будет выглядеть как: Код (Text): macro a [grp1],[grp2] так как тут не ясно какой аргумент какой группе принадлежит. Исходя из этого делают так: Код (ASM): macro a [grp1,grp2] В этом случае каждый нечётный аргумент относится к группе grp1, а каждый чётный - к grp2: Код (ASM): macro a [grp1,grp2] { forward l_#grp1: forward l_#grp2: } a 1,2,3,4,5,6 будет: Код (ASM): l_1: l_3: l_5: l_2: l_4: l_6: Или ещё: Код (ASM): macro ErrorList [name,value] { forward ERROR_#name = value } ErrorList \ NONE,0,\ OUTOFMEMORY,10,\ INTERNAL,20[/asm]получим:[code]ERROR_NONE = 0 ERROR_OUTOFMEMORY = 10 ERROR_INTERNAL = 20 Конечно же, может быть больше двух групп аргументов: Код (ASM): macro a [g1,g2,g3] { common db g1 db g2 db g3 } a 1,2,3,4,5,6,7,8,9,10,11 будет: Код (ASM): db 1,4,7,10 db 2,5,8,11 db 3,6,9 7. Условный препроцессинг В действительности, FASM не имеет директив для условного препроцессинга. Но директива ассемблера "if" может быть использована совместно с возможностями препроцессора для получения тех же результатов, что и при условном препроцессинге. (Но в этом случае увеличивается расход памяти и времени). Как известно, оператор "if" обрабатывается во время ассемблирования. Это значит, что условие в этом операторе проверяется после обработки исходного текста препроцессором. Именно это обеспечивает работу некоторых логических операций. Я не буду рассказывать о деталях времени ассемблирования (логических операциях вроде "&", "|" и тому подобном) - читайте об этом в документации FASM. Я лишь расскажу об операторах проверки условия используемых препроцессором. 7.1. Оператор "EQ" Простейший логический оператор - это "EQ". Он всего лишь сравнивает два идентификатора - одинаковы ли их значение. Значение abcd eq abcd - истина, а abcd eq 1 - ложь и так далее... Это полезно для сравнения символов, которые будут обработаны препроцессором: Код (ASM): STRINGS equ ASCII if STRINGS eq ASCII db 'Oh yeah',0 else if STRINGS eq UNICODE du 'Oh yeah',0 else display 'unknown string type' end if после обработки препроцессором, это примет вид: Код (ASM): if ASCII eq ASCII db 'Oh yeah',0 else if ASCII eq UNICODE du 'Oh yeah',0 else display 'unknown string type' end if Здесь только первое условие (ASCII eq ASCII) выполняется, так что будет ассемблировано только Код (ASM): db 'Oh yeah',0 Другой вариант: Код (ASM): STRINGS equ UNICODE ;разница здесь, UNICODE вместо ASCII if STRINGS eq ASCII db 'Oh yeah',0 else if STRINGS eq UNICODE du 'Oh yeah',0 else display 'unknown string type' end if получим: Код (ASM): if UNICODE eq ASCII db 'Oh yeah',0 else if UNICODE eq UNICODE du 'Oh yeah',0 else display 'unknown string type' end if Тут уже первое условие (UNICODE eq ASCII) будет ложно, второе (UNICODE eq UNICODE) - верно, будет ассемблироваться Код (ASM): du 'Oh yeah',0
Несколько лучшее применение этого - проверка аргументов макросов, вроде: Код (ASM): macro item type,value { if type eq BYTE db value else if type eq WORD dw value else if type eq DWORD dd value else if type eq STRING db value,0 end if } item BYTE,1 item STRING,'aaaaaa'[/asm]будет:[code=asm]if BYTE eq BYTE db 1 else if BYTE eq WORD dw 1 else if BYTE eq DWORD dd 1 else if BYTE eq STRING db 1,0 end if if STRING eq BYTE db 'aaaaaa' else if STRING eq WORD dw 'aaaaaa' else if STRING eq DWORD dd 'aaaaaa' else if STRING eq STRING db 'aaaaaa',0 end if[/asm]ассемблироваться будут только две команды:[code=asm]db 1 db 'aaaaaa',0 Подобно всем другим операторам препроцессора, "EQ" может работать с пустыми аргументами. Это значит, что, например, "if eq" верно, а if 5 eq - ложно и тому подобное. Пример макроса: Код (ASM): macro mov dest,src,src2 { if src2 eq mov dest, src else mov dest, src mov src, src2 end if } здесь, если есть третий аргумент, то будут ассемблироваться две последних команды, если нет - то только первая. 7.2. Оператор "EQTYPE" Ещё один оператор - "EQTYPE". Он определяет, одинаков ли тип идентификаторов. Существующие типы: отдельные строки символов, заключённые в кавычки (те, которые не являются частью численных выражений) вещественные числа любые численные выражения, например, 2+2 (любой неизвестный символ будет рассматриваться как метка, так что он будет считаться подобным выражением) адреса - численные выражения в квадратных скобках (учитывая оператор размерности и префикс сегмента) мнемоники инструкций регистры операторы размерности операторы NEAR и FAR операторы USE16 и USE32 пустые аргументы (пробелы, символы табуляции) Пример макроса, который позволяет использовать переменную в памяти в качестве счётчика в инструкции SHL (например shl ax, [myvar]): Код (ASM): macro shl dest, count { if count eqtype [0] ;если count - ячейка памяти push cx mov cl, count shl dest, cl pop cx else ;если count другого типа shl dest, count ;просто используем обычную shl end if } shl ax, 5 byte_variable db 5 shl ax, [byte_variable] получится: Код (ASM): if 5 eqtype [0] push cx mov cl, 5 shl ax, cl pop cx else shl ax, 5 end if byte_variable db 5 if [byte_variable] eqtype [0] push cx mov cl, [byte_variable] shl ax, cl pop cx else shl ax, [byte_variable] end if в результате обработки условий конечный результат будет: Код (ASM): shl ax, 5 byte_variable db 5 push cx mov cl, [byte variable] shl ax, cl pop cx Заметьте, что shl ax, byte [myvar] не будет работать с этим макросом, так как условие byte [variable] eqtype [0] не выполняется. Читаем дальше.Когда мы сравниваем что-то посредством "EQTYPE", то это что-то может быть не только единичным идентификатором, но и их комбинацией. В таком случае, результат eqtype истина, если не только типы, но и порядок идентификаторов совпадают. К примеру, if eax 4 eqtype ebx name - верно, так как name - это метка, и её тип - численное выражение.Пример расширенной инструкции mov, которая позволяет перемещать данные между двумя ячейками памяти: Код (ASM): macro mov dest,src { if dest src eqtype [0] [0] push src pop dest else mov dest,src end if } mov [var1], 5 mov [var1], [var2] преобразуется препроцессором в: Код (ASM): if [var1] 5 eqtype [0] [0] ;не верно push 5 pop [var1] else mov [var1],5 end if if [var1] [var2] eqtype [0] [0] ;верно push [var2] pop [var1] else mov [var1], [var2] end if и будет ассемблировано в: Код (ASM): mov [var1], 5 push [var2] pop [var1] Хотя более удобно для восприятия реализовать макрос используя логический оператор И - &: Код (ASM): macro mov dest,src { if (dest eqtype [0]) & (src eqtype [0]) push src pop dest else mov dest, src end if } Пример с использованием "EQTYPE" с четырьмя аргументами приведён для демонстрации возможностей, обычно проще использовать в таких случаях "&". Кстати, в качестве аргументов, возможно использовать некорректные выражения - достаточно, чтобы лексический анализатор распознал их тип. Но это не является документированным, так что не будем этот обсуждать. 7.3. Оператор "IN" Бывают случаи, когда в условии присутствует слишком много "EQ": Код (ASM): macro mov a,b { if (a eq cs) | (a eq ds) | (a eq es) | (a eq fs) | \ (a eq gs) | (a eq ss) push b pop a else mov a, b end if } Вместо применения множества логических операторов ИЛИ - |, можно использовать специальный оператор "IN". Он проверяет, присутствует ли идентификатор слева, в списке идентификаторов справа. Список должен быть заключён в скобочки "<" и ">", а идентификаторы в нём разделяются запятыми: Код (ASM): macro mov a,b { if a in <cs,ds,es,fs,gs,ss> push b pop a else mov a, b end if } Это так же работает для нескольких идентификаторов (как и "EQ"): Код (ASM): if dword [eax] in <[eax], dword [eax], ptr eax, dword ptr eax> 8. Структуры В FASM, структуры практически тоже самое, что и макросы. Определяются они посредством директивы STRUC: Синтаксис: Код (Text): struc { <тело структуры> } Отличие от макросов заключается в том, что в исходном тексте перед структурой должна находиться некое "имя" - имя объекта-структуры. Например: Код (ASM): struc a {db 5} a это не будет работать. Структуры распознаются только после имен, как здесь: Код (ASM): struc a {db 5} name a подобно макросу, это преобразуется препроцессором в: Код (ASM): db 5 Смысл имени в следующем - оно будет добавлена ко всем идентификаторам из тела структуры, которые начинаются с точки. Например: Код (ASM): struc a {.local:} name1 a name2 a[/asm]будет:[code=asm]name1.local: name2.local: Таким образом можно создавать структуры вроде тех, что есть в языках высокого уровня абстракции: Код (ASM): struc rect left,right,top,bottom ;аргументы как у макроса { .left dd left .right dd right .top dd top .bottom dd bottom } r1 rect 0,20,10,30 r2 rect ?,?,?,?[/asm]получим:[code=asm]r1.left dd 0 r1.right dd 20 r1.top dd 10 r1.bottom dd 30 r2.left dd ? r2.right dd ? r2.top dd ? r2.bottom dd ? Поскольку, используемой структуре всегда должно предшествовать имя, препроцессор однозначно отличает их от макросов. Поэтому имя структуры может совпадать с именем макроса - в каждом случае будет выполняться нужная обработка.Существуют хитрый приём, позволяющий не указывать аргументы, если они равны 0: Код (ASM): struc ymmv arg { .member dd arg+0 } y1 ymmv 0xACDC y2 ymmv[/asm]будет:[code=asm]y1.member dd 0xACDC+0 y2.member dd +0 Как говорилось ранее, если значение аргумента не указанно, то в теле макроса или структуры вместо него ничего не подставляется. В этом примере "плюс" ("+") используется или как бинарный оператор (то есть с двумя операндами), или как унарный (с одним операндом) оператор.ПРИМЕЧАНИЕ: часто используется так же макрос или структура struct, которая определяется для расширения возможностей при определении структур. Не путайте struct и struc. 9. Оператор FIX и макросы внутри макросов В стародавние времена, в FASMе отсутствовала одна полезная возможность - создавать макросы внутри других макросов. Например, что бы при развёртывании макроса был бы определён новый макрос. Что-то вроде гипотетичного: Код (ASM): macro declare_macro_AAA { macro AAA { db 'AAA',0 } ;завершаем определение AAA } ;завершаем определение declare_macro_AAA[/asm]Проблема в том, что когда макрос[code=asm]declare_macro_AAA обрабатывается препроцессором, первая найденная скобочка "}" считается завершением определения его, а не так как хотелось бы. Так же происходит и с другими символами и/или операторами (например, "#", "`", "forward", "local"). 9.1. Explaination of fixes Но со временем, была добавлена новая директива. Она работает подобно "EQU", но обрабатывается до любого другого препроцессинга. (За исключением предварительных операций, про которые говорится в разделе "Общие понятия" - они выполняются как бы до самого препроцессинга, но это уже внутренние детали, не слишком интересные). Директива эта называется FIX: Синтаксис : Код (Text): <name1> <fix name2> Видно, что синтаксис такой же как у "EQU", но как я сказал, когда препроцессор обрабатывает часть кода, он смотрит, есть ли "FIX", а потом уже делает всё остальное. Например код: Код (ASM): a equ 1 b equ a a b Then preprocesisng happens like this: Preprocessing line 1: a - Preprocessor finds unknown word, skips it. equ - "equ" is second word of line, so it remembers "a" equals rest of line ("b") and deletes line Preprocessing line 2: b - Preprocessor finds unknown word, skips it. equ - "equ" is second word of line, so it remembers "b" equals rest of line ("a") and deletes line Preprocessing line 3: a - Preprocessor replaces "a" with "1" b - Preprocessor replaces "b" with "a" So it becomes: Код (ASM): 1 a But if we have Код (ASM): a fix 1 b fix a a b
then it looks like: Fixing line 1: No symbols to be fixed Preprocessing line 1: a - Preprocessor finds unknown word, skips it. fix - "fix" is second word of line, so it remembers "a" is fixed to rest of line ("b") and deletes line Fixing line 2: "a" is fixed to "1", so line becomes "b fix 1" Preprocessing line 2: b - Preprocessor finds unknown word, skips it. fix - "fix" is second word of line, so it remembers "b" is fixed to rest of line ("1") and deletes line Fixing line 3: "a" is fixed to "1", "b" is fixed to "1" so line becomes "1 1" Preprocessing line 3: 1 - Preprocessor finds unknown word, skips it. 1 - Preprocessor finds unknown word, skips it. This was only example to see how fixing works, usually it isn't used in this manner. 9.2. Using fixes for nested macro declaration Now back to declaring macro inside macro - First, we need to know how are macros preprocessed. You can quite easily make it out yourself - on macro declaration macro body is saved, and when macro is being expanded preprocessor replaces line with macro usage by macro body and internally declares equates to handle arguments and continues with preprocessing of macro body. (of course it is more complicated but this is enough for understanding fixes). So where was problem with declaring macro inside macro? First time compiler found "}" inside macro body it took it as end of macro body declaration, so there wasn't any way to include "}" in macro body. So we can easily fix this Код (ASM): macro a { macro b %_ display 'Never fix before something really needs to be fixed' _% } %_ fix { _% fix } a b Now preprocessing looks like (simplified) Preprocessor loads declaration of macro "a" Preprocessor loads declaration of fixes "%_" and "_%" Preprocessor expands macro "a" Preprocessor loads macro "b" declaration ("_%" and "%_" are fixed in each line before being handled by rest of preprocessor) Preprocessor expands macro "b" Here you see how important is placing of declaration of fixes, because macro body is fixed too before it's loaded by preprocessor. For example this won't work: Код (ASM): %_ fix { _% fix } macro a { macro b %_ display 'Never fix before something really needs to be fixed, here you see it' _% } a b Because "%_" and "_%" will be fixed before loading macro "a", so loading macro body will end at "_%" fixed to "}" and second "}" will remain there. NOTE: Character "%" isn't special character for FASM's preprocessor, so you use it just like any normal character, like "a" or "9". It has special meaning AFTER preprocessing, and only when it is only char in whole word ("%" not "anything%anything"). We also need to fix other macro-releated operators: Код (ASM): %_ fix { _% fix } %local fix local %forward fix forward %reverse fix revese %common fix common %tostring fix `[/asm]Only # is special case, you can fix it, but there is a easier way. Every time preprocessor finds multiple #s, it removes one, so it is something like (this won't really work)[code=asm]etc... ###### fix ##### ##### fix #### #### fix ### ### fix ## ## fix # So instead of using symbol fixed to "#" you can just use "##" etc. 9.3. Using fixes for moving parts of codesYou can also use fixes to move parts of code. In assembly programming is this useful especially when you break code into modules, but you want to have data and code grouped in separate segment/section, but defined in one file. Right now this part of tutorial is TODO, I hope I will write it soon, for now you can look at JohnFound's Fresh's macro library, file Код (ASM): INCLUDE\MACRO\globals.inc Я знаю, FIXы могут смутить, и хорошо бы понимать внутренние детали работы препроцессора, но они предоставляют очень большие возможности. Создатель FASM'a сделал его настолько мощным, на сколько это возможно, даже за счёт некоторого ущерба удобочитаемости. Код (ASM): a equ 10 b fix 10 mov ax, a mov bx, b будет преобразован в: Код (ASM): mov ax, 10 mov bx, 10 Но при обработке такого кода: Код (ASM): equ fix = a equ 10 mov ax, a в первой строк директива FIX скажет препроцессору поменять все EQU на =. Далее, перед обработкой следующей строки, препроцессор проверит, нет ли там пофиксеных идентификаторов. Так что в нашей второй строке equ будет заменено на =, и строка примет вид a = 10. Так что никакой другой обработки этой строки не будет выполнено. А значит, и третья строка не будет преобразовываться препроцессором, так как идентификатор a не будет определён директивой EQU. Результат всего этого будет такой: Код (ASM): a = 10 mov ax, a Директива FIX может быть использован и для определения макросов в макросах - того, что мы хотели сделать в нашем гипотетичном примере. Делается это подобным образом: Код (ASM): macro declare_macro_AAA { macro AAA %_ db 'aaa',0 _% } %_ fix { _% fix } declare_macro_AAA Здесь, препроцессор найдёт объявление макроса declare_macro_AAA и определит его, далее будет два FIX, и потом использование макроса declare_macro_AAA. Так что он преобразует это в: Код (ASM): macro declare_macro_AAA { macro AAA %_ db 'aaa',0 _% } %_ fix { _% fix } macro AAA %_ db 'aaa',0 _% и теперь уже содержимое нового макроса будет обработано препроцессором. Далее будут заменены аргументы FIXов, и получится: Код (ASM): macro declare_macro_AAA { macro AAA %_ db 'aaa',0 _% } macro AAA { db 'aaa',0 } как мы и хотели. Подобным образом можно пофиксить все остальные проблематичные вещи: Код (ASM): macro declare_macro_TEXT { macro TEXT [arg] %_ %forward db %x arg _% } %_ fix { _% fix } %forward fix forward declare_macro_TEXT %x fix ` TEXT abc,def В этом примере нужно обратить внимание на один момент: строка %x fix ` должна находиться после declare_macro_TEXT. Если б она находилась до, то %x было бы пофиксено во время развёртывания макроса, и тогда `arg приняло бы вид 'arg', следовательно макрос TEXT был бы объявлен так: Код (ASM): macro TEXT [arg] { forward db 'arg' ;строка не зависит от аргументов } Но, в нашем случае он будет: Код (ASM): macro TEXT [arg] { forward db `arg ;имена аргументов превращаются в строки } Этот пример показывает, как важно местонахождение FIX. Иногда необходимо фиксить идентификаторы дважды: Код (ASM): macro m1 { macro m2 %_ macro m3 [arg] %%_ db arg _%% _% } %%_ fix %_ _%% fix _% %_ fix { %_ fix } m1 m2 m3 Символы фиксятся даже во время препроцессинга других FIX, так что код выше не будет работать, если порядок будет такой: Код (ASM): %_ fix { %_ fix } %%_ fix %_ _%% fix _% В этом случае строка Код (ASM): %%_ fix %_ была бы пофиксена сразу же после Код (ASM): %_ fix { так что все последующие %%_ сразу же преобразовались бы в }. То же самое и для Код (ASM): _%% fix _% Заключение Не забывайте читать документацию FASM. Практически всё, что есть в туториале, можно найти там. Может быть написано и немного сложнее для изучения, но лучше подойдёт в качестве справочной информации. Не так сложно запомнить - 99% пользователей FASM научились его использовать по этой документации и при помощи форума.