Assembler для Windows

           

Глава 6. Макросредства ассемблера и программирование в Windows

I

Большая часть данной главы будет носить справочный характер. Почему я привожу справочный материал в середине книги, а не в ее начале? Просто я убежден, что справка в начале книги может отбить всякую охоту читать дальше. Многое из того, что я привожу в данной главе. Вы уже знаете и, следовательно, Вам интересно будет читать материал этой главы.

Метки

1. Метка с двоеточием после имени определяет адрес следующей за меткой команды.

2. Директива LABEL позволяет определить явно тип метки. Значение же определенной таким образом метки равно адресу команды или данных, стоящих далее. Например,


LABEL L1 DWORD.

3. Выражение ИМЯ PROC определяет метку, переход на которую обычно происходит по команде CALL. Блок кода, начинающийся с такой метки, называют процедурой. Впрочем, переход на такую метку можно осуществлять и с помощью JMP, как, впрочем, и команду CALL можно использовать для перехода на обычную метку. В этом, несомненно, состоит сила и гибкость ассемблера.

4. В строке за меткой может стоять директива резервирования данных, например: ERR DB 'Ошибка' или NUM DWORD 0. С точки зрения языка высокого уровня таким образом мы определяем глобальную переменную. С точки же зрения ассемблера нет никакой разницы между командой и данными, поэтому между меткой, определяющей команду, и меткой, определяющей данные, нет никакой разницы. Раз уж речь пошла о данных, перечислю их типы:
BYTE (DB) - байт,
WORD (DW) - 2 байта,
DWORD (DD) - 4 байта,
FWORD (DF) - 6 байт,
QWORD (DQ) - 8 байт,
TBYTE (DT) - 10 байт.

5. С помощью директивы EQU в терминах языков высокого уровня определяются константы. Например - MES EQU "ERROR!", LAB EQU 145Н. С помощью EQU значение данной метке может быть присвоено только один раз. С правой стороны от EQU может стоять выражение с использованием арифметических, логических и битовых операций. Вот эти операции: "+", "-", "*", "/", "MOD"-ocтaток от деления, "AND", "OR", "NOT", "XOR", "SHR", "SHL". Используются также операции сравнения: EQ, GE, GT, LE, LT, NE. Выражение с операцией сравнения считается логическим и принимает значение 0, если условие не выполняется, и 1 - если выполняется. С помощью директивы "=" можно присваивать только целые значения, но зато производить переприсваивание. Заметим, что выражение может являться операндом команды: MOV EAX,16*16-1.

6. Метка "$" всегда определяет текущий адрес.

7. В MASM метки, стоящие в процедуре, автоматически считаются локальными и, следовательно, имена меток в процедурах могут дублироваться. В TASM все метки по умолчанию считаются глобальными. Чтобы сделать метки, стоящие в процедуре локальными, они должны иметь префикс @@, а в начале программы следует указать директиву LOCALS (см. предыдущую главу).

Структура

1. Директива STRUC позволяет объединить несколько разнородных данных в одно целое. Эти данные называются полями. Вначале при помощи STRUC определяется шаблон структуры, затем с помощью директивы < > можно определить любое количество структур. Рассмотрим пример:

STRUC COMPLEX
RE DD ?
IM DD ?
STRUC ENDS
...
;в сегменте данных
COMP1 COMPLEX <?>
COMP2 COMPLEX <?>

Доступ к полям структуры осуществляется посредством точки: COMP1.RE.

2. Объединение. Объединение определяется при помощи ключевого слова UNION. От структуры объединение отличается только тем, что все поля располагаются в структуре с нулевым смещением, т.е. накладываются друг на друга.

Условное ассемблирование

1. Условное ассемблирование дает возможность при трансляции обходить тот или иной участок программы. Существует три вида условного ассемблирования.

а)
IF выражение
...
ENDIF
б)
IF выражение
...
ELSE
...
ENDIF
в)
IF выражение 1
...
ELSEIF выражение 2
...
ELSEIF выражение 3
...
ELSE
...
ENDIF
Условие считается не выполненным, если выражение принимает значение 0 и выполненным, если выражение отлично от нуля.

2. Ассемблеры MASM и TASM поддерживают также несколько условных специальных директив, назовем некоторые из них.

а)

IFE выражение
...
ELSEIFE
...
ENDIFE

б) Операторы IF1 и IF2 проверяют первый и второй проход при ассемблировании.

в) Оператор IFDEF - проверяет, определено ли в программе символическое имя, IFDEFN - обратный оператор. И другие IF операторы. Они имеются в любом справочнике по ассемблеру.

г) Имеется целый набор директив, начинающихся с .ERR. Например, .ERRE выражение - вызовет прекращение трансляции и сообщение об ошибке, если выражение станет равным 0.

Условное ассемблирование понадобится нам в конце главы для написания программы, транслируемой как в MASM, так и TASM.

Вызов процедур

1. С упрощенным вызовом процедур в MASM Вы уже познакомились. Это директива INVOKE. Процедура должна быть заранее определена с использованием ключевого слова PROTO. Например:

MessageBoxA PROTO :DWORD,:DWORD,:DWORD,:DWORD
...
invoke MessageBox, h, ADDR TheMsg, ADDR TitleW, MB_OK
Здесь h - дескриптор окна, откуда вызывается сообщение, TheMsg - строка сообщения, TitleW - заголовок окна, MB_OK - тип сообщения. ADDR в данном случае синоним OFFSET.

2. Оказывается, в синтаксисе TASM тоже имеется свой упрощенный вызов.

EXTERN MESSAGEBOX:PROC
...
call MessageBox PASCAL,h,ADDR TheMsg,ADDR TitleW, MB_OK
PASCAL - тип вызова, точнее порядок следования параметров. Можно поставить параметр C, тогда порядок будет обратным.

Макроповторения

1. Повторение, заданное опеделенное число раз. Используется макродиректива REPT. Например:

A EQU 10
REPT 100
DB A
ENDM
Будет сгенерировано 100 директив DB 10. С этой директивой удобно использовать оператор "=", который позволяет изменять значение переменной многократно, т.е. использовать выражение типа А = А + 5.

2. Директива IRP.

IRP параметр,<список>
...
ENDM
Блок будет вызываться столько раз, сколько параметров в списке. Например:
IRP REG, <EAX,EBX,ECX,EDX,ESI,EDI>
PUSH REG
ENDM
Приведет к генерации следующих строк:
PUSH EAX
PUSH EBX
PUSH ECX
PUSH EDX
PUSH ESI
PUSH EDI

3. Директива IRPC.

IRPC параметр, строка
Операторы
ENDM

Пример:

IRPC CHAR,azklg
CMP AL,'&CHAR&'
JZ EndC
ENDM
EndC:

Данный фрагмент эквивалентен следующей последовательности:
CMP AL,'a'
JZ EndC
CMP AL,'z'
JZ EndC
CMP AL,'k'
JZ EndC
CMP AL,'l'
JZ EndC
CMP AL,'g'
JZ EndC
EndC:

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

Макроопределения

Общий вид макроопределения.

Имя MACRO параетры
...
ENDM
Определив блок один раз, можно использовать его в программе многократно. Причем в зависимости от значений параметров заменяемый участок может иметь разные значения. Если заданный участок предполагается многократно использовать, например в цикле, макроопределение имеет несомненные преимущества перед процедурой, т.к. несколько убыстряет выполнение кода. Пример:
ЕХС MACRO par1,par2
PUSH par1
POP par2
ENDM
Данное макроопределение приводит к обмену содержимым между параметрами.

ЕХС EAX,EBX эквивалентно PUSH EAX\POP EAX; ЕХС MEM1,ESI - PUSH MEM1\POP ESI и т. д. Заметим, что если первый параметр будет непосредственно числом, то это приведет к загрузке данного числа во второй операнд.

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

ЕХС MACRO par1,par2
LOCAL EXI
CMP par1,par2
JE EXI
PUSH par1
POP par2
EXI:
ENDM

Данное макроопределение можно использовать сколь угодно много раз - при каждой подстановке ассемблер будет генерировать уникальную метку. Для выхода из макроопределения (т.е. для прекращения генерации макроопределения) применяется директива EXITM. Она может понадобиться, если в макроопределении Вы используете условные конструкции типа IF..ENDIF.

Некоторые другие директивы транслятора ассемблера

1. Кроме объявлений с использованием директив PUBLIC и EXTERN, возможно объявление при помощи директивы GLOBAL, которая действует, как PUBLIC и EXTERN одновременно.

2. PURGE имя макроса. Отменяет загрузку макроса. Используется при работе с библиотекой макросов, чтобы не перегружать память38.

3. LENGTHOF - определяет число элементов данных. SIZEOF - определяет размер данных (отсутствуют в TASM).

4. Директивы задания набора команд.
.8086 - разрешены только команды микропроцессора 8086. Данная директива работает по умолчанию.
.186 - разрешены команды 186.
.286 и .286Р - разрешены команды 286-ого микропроцессора. Добавка "P" здесь и далее означает разрешение команд защищенного режима.
.386 и .386P - разрешение команд 386-ого микропроцессора.
.486 и .486Р - разрешение команд 486-ого процессора.
.586 и .586Р - разрешены команды Р5 (Pentium).
.686 и .686Р - разрешены команды Pб (Pentium Pro, Pentium II).
.8087 - разрешены команды арифметического сопроцессора 8087.
.287 - разрешены команды арифметического сопроцессора 287.
.387 - разрешены команды арифметического сопроцессора 387.
.MMX - разрешены команды расширения ММХ.

5. Директивы управления листингом.
NAME - задать имя модуля.
TITLE - определяет заголовок листинга.
По умолчанию и имя модуля, и заголовок листинга совпадают с именем файла.
SUBTTL - определяет подзаголовок листинга.
PAGE - определяет размеры страницы листинга: длина, ширина. Директива PAGE без аргументов начинает новую страницу листинга.
.LIST - выдавать листинг.
.XLIST - запретить выдачу листинга.
.SALL - подавить печать макроопределений.
.SFCOND - подвить печать условных блоков с ложными условиями.
.LFCOND - печатать условные блоки с ложными условиями.
.CREF - разрешить листинг перекрестных ссылок.
.XCREF - запретить листинг перекрестных ссылок.


38 В операционной системе MS DOS это было существенно.


Конструкции времени исполнения программы

1. Условные конструкции.

а)
.IF условие
...
.ENDIF
б)
.IF условие
...
.ELSE
...
.ENDIF
в)
.IF условие 1
...
.ELSEIF условие 2
...
.ELSEIF условие 3
...
...
.ELSE
...
.ENDIF

Рассмотрим следующий фрагмент, содержащий условную конструкцию и соответствующий ей ассемблерный код.

.IF EAX==12H
MOV EAX,10H
.ELSE
MOV EAX,15Н
.ENDIF
эквивалентен следующему ассемблерному коду:
CMP EAX,12Н
JNE NO_EQ
MOV EAX,10H
JMP EX_BLOK
NO_EQ:
MOV EAX,15Н
EX_BLOK:
Весьма удобная штука, но не увлекайтесь: на мой взгляд, это сильно расслабляет.

2. Цикл "пока".

.WHILE условие
...
.ENDW

Пример.

WHILE EAX<64H
ADD EAX,10
ENDW

Для MASM:

JMP L2
L1:
ADD EAX,10Н
L2:
CMP EAX,64Н
JB L1

Для TASM:

L1:
CМР EAX,64Н
JNB EXI
ADD EAX,10Н
JMP L1
EXI:

Есть некоторое отличие в том, как два ассемблера транслируют директивы .IF и .WHILE. Транслятор TASM32 производит автоматически оптимизацию на предмет выравнивания по границе учетверенного слова, добавляя дополнительно команды NOP. Это несколько ускоряет выполнение программы, но увеличивает ее объем. Мне ближе позиция MASM.

II

Сейчас мы рассмотрим вопрос о написании программ, которые одинаково транслировались бы и в MASM, и в TASM. Для этого прекрасно подходит условное ассемблирование. Удобнее всего использовать IFDEF и возможности трансляторов задавать символьную константу, все равно - TASM или MASM. И в ML, и в TASM32 определен ключ /D, позволяющий задавать такую константу.

На Рис. 2.6.1 представлена программа, транслируемая и в MASM, и TASM. Программа весьма проста, но рассмотрения ее вполне достаточно для создания более сложных подобных совместимых программ.

.386P
; плоская модель
.MODEL FLAT, STDCALL
; проверить, определена символьная константа MASM или нет
IFDEF MASM
; работаем в MASM
EXTERN ExitProcess@4:NEAR
EXTERN MessageBoxA@16:NEAR
includelib с:\masm32\lib\kernel32.lib
includelib c:\masm32\lib\user32.lib
ELSE
; работаем в TASM
EXTERN ExitProcess:NEAR
EXTERN MessageBoxA:NEAR
includelib c:\tasm32\lib\import32.lib
ExitProcess@4 = ExitProcess
MessageBoxA@16 = MessageBoxA
ENDIF
;-----------------------------------------------------------
;сегмент данных
_DATA SEGMENT DWORD PUBLIC USE32 'DATA'
MSG DB "Простая программа",0
TIT DB "Заголовок",0
_DATA ENDS
;сегмент кода
_TEXT SEGMENT DWORD PUBLIC USE32 'CODE'
START:
PUSH 0
PUSH OFFSET TIT
PUSH OFFSET MSG
PUSH 0 ; дескриптор экрана
CALL MessageBoxA@16
;-----------------------------------
PUSH 0
CALL ExitProcess@4
_TEXT ENDS
END START

Рис. 2.6.1. Пример использования условного ассемблирования для написания совместимой программы.

Трансляция в MASM:

ML /с /coff /DMASM PROG.ASM
LINK /SUBSYSTEM:WINDOWS PROG.OBJ

Трансляция в TASM:

TASM32 /ml PROG.ASM
TLINK32 -aa PROG.OBJ

Как видите, все сводится к проверке, определена символьная константа MASM или нет (ключ /DMASM). Еще одна сложность - добавка в конце имени @N. Эту проблему мы обходим, используя оператор "=", с помощью которого переопределяем имена (см. секцию "работаем в TASM").