Глава 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_OKPASCAL - тип вызова, точнее порядок следования параметров. Можно поставить параметр 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").