end if
display d
end repeat
display 13,10
Этот блок директив рассчитывает четыре цифры 16-битного значения и конвертирует их в знаки для показа. Помните что это не будет работать, если адреса в текущем адресном пространстве перемещаемы (как это может быть с объектным форматом вывода и форматом PE), так как таким образом могут быть использованы только абсолютные значения. Абсолютное значение может быть получено вычислением относительного адреса, например «$-$$
» или «rva $
» в случае формата PE.
2.2.5 Множественные проходы
Так как ассемблер позволяет ссылаться на некоторые метки и константы перед тем, как они фактически определены, приходится прогнозировать значения этих меток и если есть даже подозрение, что прогноз окажется неверным хотя бы один раз, делается еще один проход, ассемблирующий весь код, и в это время делается лучший прогноз, базирующееся на значениях меток, полученных в предыдущий проход.
Изменение значений меток может быть причиной того, что некоторые инструкции перекодируются с другими длинами и это снова повлечет изменение меток. И так как метки и константы ещё могут использоваться внутри выражений, которые влияют на поведение директив управления, весь блок инструкций в новый проход может ассемблироваться абсолютно по-другому. Поэтому ассемблер делает проходы снова и снова, каждый раз пытаясь создать лучшие прогнозы, чтобы приблизиться к финальному решению, когда все значения спрогнозированы правильно. Для прогнозов используются разные методы, которые выбираются с тем, чтобы найти с как можно меньшим количеством проходов решение наименьшей возможной длины для большинства программ.
О некоторых ошибках, таких как непопадание значений в заданные границе, не сигнализируется во время этих промежуточных проходов, пока может случиться такое, что если какие-то значения будут спрогнозированы лучше, эти ошибки исчезнут сами собой. Однако, если ассемблер встречает какую-то недопустимую синтаксическую конструкцию или неизвестную инструкцию, он всегда останавливается немедленно. Такую же ошибку вызывает определение метки более, чем один раз, так как это делает прогнозы необоснованными.
Если в коде встречается директива «display
», фактически отображаются только сообщения, созданные в последний совершённый проход. В случае, если ассемблер остановился из-за ошибки, эти сообщения могут отражать спрогнозированные значения, которые еще не разрешены правильно.
Разрешение иногда может не создаться и в таких случаях ассемблер никогда не сумеет создать правильные прогнозы — по этой причине существует предел количества походов, и когда ассемблер исчерпает этот лимит, он остановится отобразит сообщение, что невозможно сгенерировать корректный вывод. Рассмотрим следующий пример:
if ~ defined alpha
alpha:
end if
Если оператор «defined
» выдает значение истина, если выражение, следующее за ним, в этом месте может быть вычислено, что в данном случае означает, что метка «alpha
» где-то определена. Но блок выше определяет эту метку только, если значение, данное оператором «defined
» ложь, что ведет к противоречию и делает невозможным разрешить такой код. Если, обрабатывая директиву «if
» ассемблер должен прогнозировать, будет ли где-нибудь определена метка «alpha
» (этого делать не приходится только если метка уже определена раньше), то какой бы ни был прогноз, всегда происходит противоположное. Поэтому ассемблирование остановится, если только метка «alpha
» не определена где-то в коде перед вышеуказанным блоком — в этом случае, как уже было отмечено прогнозирование не требуется и блок просто будет пропущен.
Предыдущий пример может быть создан как попытка определить метку, только если этого все ещё не сделано. Эти строк неправильны, поскольку оператор «defined
» проверяет определена ли метка где-либо вообще, и это включает определение внутри этого условного блока. Однако есть способ обойти эту проблему:
if ~ defined alpha | defined @f
alpha:
@@:
end if
«@f
» это всегда та же метка, что ближайший следующий за ним символ «@@
», поэтому предыдущий пример значит то же, как если бы вместо анонимной метки было определено любое уникальное имя. Если метка «alpha
» ещё не определена, ассемблер спрогнозирует значение «defined alpha
» как ложь, это будет однако значить, что будут определены обе метки. Но на следующем проходе ассемблер спрогнозирует, что определены обе метки, что заставит определить их вновь — так прогноз будет совпадать с результатом и процесс ассемблиования придет к правильному решению. Анонимная метка выступает здесь как маркер того, что метка «alpha
» определена в этом месте.
Из этого примера вы можете заключить, что прогноз для оператора «defined
» очень прямолинейный — метка прогнозируется как определенная только если она была определена в предыдущий проход (а если она была определена в текущий проход, прогноз не требуется). То же самое относится к оператору «used
». Однако прогнозы для значений меток не так просты и вам никогда не следует полагать, что ассемблер работает таким способом.
2.3 Директивы препроцессора
Все директивы препроцессора выполняются перед основным ассемблированием, и таким образом директивы управления на них никак не влияют. В это время также удаляются все комментарии.
2.3.1 Включение файлов-исходников
«include
» включает указанный файл-исходник туда, где эта директива используется. За ней должно следовать в кавычках имя файла, который должен быть включен, например:
include 'macros.inc'
Весь включенный файл обрабатывается препроцессором перед обработкой строк, следующих за содержащей директиву «include
». Нет предела для количества включаемых файлов, пока они умещаются в память.
Путь, заключенный в скобки, может содержать окружающие переменные, заключенные в знаки «%
», они будут заменены на их значения внутри пути. Знаки «\
» и «/
» трактуются как разделители пути. Если не указан абсолютный путь, сначала файл ищется в директории, содержащей файл, в который он включается, и, далее, если его там нет, в директории, содержащей главный файл-исходник (указанный в командной строке). Эти правила так же относятся к путям, которые указываются в директиве «file
».
2.3.2 Символьные константы
Символьные константы отличаются от числовых констант тем, что перед процессом ассемблирования они заменяются на их значения во всех строках кода, следующих за их определением, и все может стать их значением.
Определение символьных констант состоит из имени константы, за которой следует директива «equ
». Все, что следует за этой директивой, станет значением константы. Если значение символьной константы содержит другие символьные константы, они заменяются на их значения перед присвоением значения новой константе. Например:
d equ dword
NULL equ d 0
d equ edx
После этих трех определений значение «NULL
» будет «dword 0
», а значение «d
» будет «edx
». Так, например, «push NULL
» будет сассемблировано как «push dword 0
», а «push d
» как «push edx
». А, например, в такой строке:
d equ d,eax
константе «d
» будет присвоено новое значение «edx,eax
». Таким образом могут определяться растущие списки символов.
«restore
» позволяет присвоить назад предыдущее значение переопределенной константы. За ней должно следовать одно или больше имен символьных констант, разделенных запятыми. Так, «restore d
» после предыдущего переопределения вернет константе значение «edx
», следующее применение этой директивы вернет ей значение «dword
», а ещё одно применение восстановит первоначальное значение, как будто такая константа не определялась. Если не константа с заданным именем не определена, то «restore
» не вызовет ошибку, а будет просто проигнорирована.
Символьные константы могут использоваться для адаптации синтаксиса ассемблера к персональным предпочтениям. Например, следующие определения создают удобные ярлыки для всех операторов размера:
b equ byte
w equ word
d equ dword
p equ pword
f equ fword
q equ qword
t equ tword
x equ dqword
Так как символьная константа может так же иметь пустое значение, она может использоваться для того, чтобы допустить синтаксис со словом «offset
» перед каким-нибудь значением адреса:
offset equ
После такого определения «mov ax, offset char
» будет правильной конструкцией, которая будет копировать смещение переменной «char
» в регистр «ax
», так как «offset
» заменяется пустым значением, и поэтому игнорируется.
Символьные константы могут также быть определены директивой «fix
», которая имеет такой же синтаксис, как «equ
», но определяет константы высшего приоритета — они заменяются их символическим значением даже перед совершением директив препроцессора и макроинструкций. Исключением является сама директива «fix
», которая имеет наивысший возможный приоритет, и поэтому допускает переопределение констант, заданных таким путем. Но если такие константы высшего приоритета находятся внутри значения, следующего за директивой «fix
», они заменяются их значениями перед присвоением этого значения новой константе.
Директива «fix
» может использоваться для адаптирования директив препроцессора, что нельзя сделать директивой «equ
». Например:
incl fix include
определяет короткое имя для директивы «include
», тогда как такое же определение директивой «equ
» не даст такого результата, так как стандартные символьные константы заменяются на из значения после поиска строк с директивами препроцессора.
2.3.3 Макроинструкции
«macro
» позволяет вам определить собственный комплекс инструкций, называемых макроинструкциями. Их использование может существенно упростить процесс программирования. В своей простейшей форме директива похожа на описание символьной константы. Например, следующая строка определяет ярлык для инструкции «test al,0xFF
»:
macro tst {test al,0xFF}
После директивы «macro
» должно идти имя макроинструкции и далее её содержание, заключенное между знаками «{
» и «}
». Вы можете использовать инструкцию «tst
» в любом месте после её определения и она будет ассемблирована как «test al,0xFF
». Определение символьной константы с таким значением даст похожий результат, различие лишь в том, что имя макроинструкции будет распознаваться только как мнемоник инструкции. Также, макроинструкции заменяются соответствующим кодом даже перед заменой символьных констант на их значения. То есть, если вы вы определите макроинструкцию и символьную константу под одним и тем же именем и используете это имя как мнемоник инструкции, оно будет заменено на содержание макроинструкции, но если вы используете его внутри операндов, имя будет заменено на значение символьной константы.
Определение макроинструкции может сотоять из нескольких строк, потому что знаки «{
» и «}
» не обязательно должны находиться на одной строке директивой «macro
». Например:
macro stos0
{
xor al,al
stosb
}
Макроинструкция «stos0
» будет заменена на эти две инструкции ассемблера, где бы он не использовался.
Как и инструкции, которым требуются несколько операндов, для макроинструкции можно задать требование нескольких аргументов, разделяя их запятыми. Имена этих аргументов должны следовать за именем макроинструкции на строке с директивой «macro
». В любом месте в макроинструкции, где эти имена появятся, они будут заменены соостветствующими значениями, указанными там, где макроинструкция используется. Вот пример макроинструкции, которая делает выравнивание данных для двоичного формата вывода:
macro align value { rb (value-1)-($+value-1) mod value }
Когда инструкция «align 4
» встречается после этого задания макроинструкции, она заменяется на его содержание, и здесь «value
» станет 4, а результат будет «rb (4–1)-($+4–1) mod 4
».
Если в определении макроинструкции встречается её же имя, то используется предыдущее значение этого имени. Таким образом могут быть сделаны полезные переопределения макросинструкций, например:
macro mov op1,op2
{
if op1 in
& op2 in
push op2
pop op1
else
mov op1,op2
end if
}
Эта макроинструкция расширяет синтаксис инструкции «mov
», позволяя обоим операндам быть сегментными регистрами. Например, «mov ds,es
» будет ассемблировано как «push es
» и «pop ds
». Во всех других случаях будет использована стандартная инструкция «mov
». Синтаксис этого «mov
» может быть расширен далее определением следующей макроинструкции с таким именем, который будет использовать предыдущий:
macro mov op1,op2,op3
{
if op3 eq
mov op1,op2
else
mov op1,op2
mov op2,op3
end if
}
Это позволяет инструкции «mov
» иметь три операнда, но она так же все ещё может иметь два операнда, так как если макроинструкции задается меньше аргументов, чем ему требуется, оставшиеся заполняются пустыми значениями. Если заданы три операнда, то макроинструкция превратится в две ранее определенных, то есть «mov es,ds,dx
» будет ассемблировано как «push ds
», «pop es
» и «mov ds,dx
».
Если требуется создать макроинструкцию с аргументом, который содержит запятые, этот аргумент следует заключить между «<
» и «>
». Если он содержит больше одного знака «<
», то для окончания его описания должно быть использовано такое же количество «>
».
«purge
» позволяет отменить последнее определение указанной макроинструкции. За директивой должно следовать одно или больше имен макроинструкций, разделенных запятыми. Если указанная макроинструкция не определена, это не вызовет ошибку. Например, после расширения синтаксиса «mov
» вышеуказанными макроинструкциями вы можете отключить синтаксис с тремя операндами, используя директиву «purge mov
». Следующее «purge mov
» отключит синтаксис для сегментных регистров, а дальнейшее применение этой директивы не возымеет эффекта.
Если после директивы «macro
» вы заключаете некоторую группу аргументов в квадратные скобки, это позволит при использовании макроинструкции задать данной группе аргументов больше значений. Любой следующий аргумент данный после последнего аргумента данной группы начнет новую группу и станет её первым членом. Поэтому после закрытия квадратных скобок не должно быть имен аргументов. Содержание макроинструкции будет обрабатываться для каждой такой группы аргументов отдельно. Простейший пример — это заключение одного имени аргумента в квадратные скобки:
macro stoschar [char]
{
mov al,char
stosb
}
Эта макроинструкция допускает неограниченное число аргументов, и каждый будет обработан этими двумя инструкциями отдельно. Например, «stoschar 1,2,3
» будет ассемблирован как следующие инструкции: