Воробьёвы

(-:

За последние несколько лет сменилось три поколения микропроцессоров семейства Pentium. У процессоров каждого поколения и у разных моделей одного поколения появлялось достаточно много новых инструкций. Например, по сравнению с Pentium III, у Pentium 4 прибавилось сразу 144 инструкции!

Компания Intel заблаговременно информирует разработчиков программного обеспечения о своих планах, поэтому Microsoft достаточно оперативно реагирует на технические нововведения и выпускает новые версии компиляторов. Если по каким-то причинам последняя версия MASM вам недоступна, то возможности устаревших версий можно расширить с помощью макроопределений новых команд.

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

Команда без операндов. Во многих руководствах встречается макроопределение для компиляции инструкции CPUID, которую выполняют все модели микропроцессоров семейства Pentium. Ее могут компилировать только версии MASM, исполняющие директиву .586, в противном случае используется макрос, приведенный в примере 8.

Пример 8. Компиляция инструкции cpuid

CPUID	MACRO        ; заголовок макроопределения
	db 0Fh, 0A2h ; объектный код инструкции cpuid
	ENDM	 ; конец макроопределения

Макровызовом этого определения является запись в исходном тексте программы инструкции cpuid, вместо нее в текст программы будет включена директива db 0Fh, 0A2h.

Инструкция имеет операнды. Подходящих для наших целей одноадресных команд нет, поэтому выберем такую группу двухадресных команд, операнды которых адресуются наиболее просто.

У микропроцессора Pentium Pro появились две группы команд условной пересылки CMOVcc и FCMOVcc. Первая группа относится к категории команд общего назначения. Инструкции второй группы исполняет процессор FPU, они применяются при программировании вычислений с плавающей точкой. В записи конкретной команды вместо маленьких букв cc подставляется мнемоническое обозначение условия, например, CMOVNE или FCMOVB.

Компилировать инструкции этих групп могут только версии MASM, начиная с 6.12 (исполняющие директиву .686), в противном случае надо применять специальные макроопределения. Мы опишем макроопределения для компиляции инструкций группы FCMOVcc. Операнды инструкций этой группы могут находиться только в числовых регистрах, что упрощает анализ и преобразование их имен.

В состав группы входят инструкции, имеющие идентичную структуру кода и компилирующиеся по одной схеме. Это позволяет вместо индивидуальных макроопределений для каждой инструкции использовать специальное мета-макро определение. В отличие от обычного, оно порождает при вызовах макроопределения для компиляции конкретных инструкций, имена которых указываются в специальных списках. Поэтому вместо составления группы идентичных макроопределений составляется список допустимых вариантов вызова мета-макроса, содержащий столько строк, сколько инструкций входит в состав группы.

В примере 9 приведены текст мета-макро определения FPIDEFи список его вызовов, содержащий имена восьми инструкций группы FCMOVcc с соответствующими им кодами операций. При вызове FPIDEF формируется и исполняется одно из 8-ми обычных макроопределений, имена которых совпадают с именем компилируемой инструкции, а параметрами являются ее (инструкции) операнды.

Пример 9. Мета-макро определения инструкций FCMOVcc

FPIDEF	MACRO OPNAME, OPCODE1, OPCODE2 ; мета-макроопределение
 	OPNAME MACRO OP1, OP2 ; заготовка основного определения
;	Проверка имени первого операнда
 	LOCAL fvsym, reg   ; описание локальных переменных
 	IFDIFI	<OP1>, <ST> ; сравнение операндов
 		IFDIFI   <OP1>, <ST(0)> ; сравнение операндов
 		.ERR1 %OUT 'Error: First operand must be st'
 		ENDIF ; конец тела второй директивы IFDIFI
 	ENDIF	; конец тела первой директивы IFDIFI
; 	Проверка имени второго операнда
 	reg SUBSTR <OP2>, 4, 1  ; выделение номера регистра
fvsym	SUBSTR <st(0)st(1)st(2)st(3)st(4)st(5)st(6)st(7)>,1+reg*5,5 ; выбор эталона
 	IFDIFI fvsym, <op2> ; сравнение OP2 с эталоном имени
 		.ERR2
 		%OUT 'Error: Second operand must be st(i)'
 	ENDIF	; конец тела директивы IFDIFI
;	Запись кода команды в объектный модуль
 	db	opcode1, opcode2+reg
 	ENDM	; конец макроопределения opname
ENDM 		; конец макроопределения fpidef
; Список вызовов макроопределения FPIDEF
FPIDEF FCMOVB,	0DAh, 0C0h	; пересылка если меньше
FPIDEF FCMOVE,	0DAh, 0C8h	; пересылка если равно
FPIDEF FCMOVBE,	0DAh, 0D0h	; пересылка если меньше или равно
FPIDEF FCMOVU,	0DAh, 0D8h	; пересылка если неупорядочены
FPIDEF FCMOVNB,	0DBh, 0C0h	; пересылка если больше или равно
FPIDEF FCMOVNE,	0DBh, 0C8h	; пересылка если не равно
FPIDEF FCMOVNBE,0DBh, 0D0h	; пересылка если больше
FPIDEF FCMOVNU,	0DBh, 0D8h	; пересылка если упорядочены

Предположим, что в тексте программы встретилась команда fcmove st, st(4). MASM ищет любые имена в своих списках зарезервированных символов и в таблицах имен, описанных в компилируемой программе. Обнаружив имя fcmove в списке, приведенном в конце текста примера 9, он формирует макровызов "fpidef fcmove, 0DAh, 0C8h". Макрос fpidef, в свою очередь, вместо OPNAME формирует и исполняет макроопределение "fcmove st, st(4)", которое компилирует команду и вставляет в текст программы директиву db 0DAh, 0CCh.

Макроопределение OPNAME проверяет имена операндов, формирует код второго операнда в переменной reg и включает в текст программы указанную выше директиву db, описывающую код команды.

Имя первого операнда проверяется сравнением с его допустимыми образцами st или st(0). Для того чтобы результат не зависел от регистра, на котором набраны буквы имени (st, ST, sT, St), сравнение выполняет директива IFDIFI (если различаются). Буква I в конце ее имени указывает на то, что перед сравнением операндов коды всех букв имен приводятся к одному регистру.

Для проверки второго операнда в переменную reg с помощью директивы SUBSTR помещается четвертый символ его имени. Если имя указано правильно, то это будет цифра от 0 до 7. Затем вторая директива SUBSTR копирует в переменную fvsym образец правильной записи имени второго операнда из списка допустимых имен. Наконец, директива IFDIFI сравнивает содержимое fvsym с именем второго операнда. При несовпадении выдается сообщение об ошибке и компиляция прекращается. В случае совпадения в текст программы вставляется код команды.

Дополнение к примеру 9. При выполнении инструкций группы FCMOVcc проверяется состояние разрядов регистра флагов EFLSGS. Обычным инструкциям FPU регистр флагов недоступен. Поэтому разработчики Pentium Pro ввели 4 новые операции, которые помещают результат сравнения операндов в разряды EFLAGS. Эти четыре инструкции имеют тот же формат, что и инструкции группы FCMOVcc. Поэтому для их компиляции можно использовать макроопределение примера 9 - просто добавьте в конец его текста следующие пять строчек:

; Дополнение списка вызовов макроопределения FPIDEF
FPIDEF FCOMI,	0DBh, 0F0h	; простое сравнение операндов
FPIDEF FCOMIP,	0DFh, 0F0h	; тоже, но с освобождением st
FPIDEF FUCOMI,	0DBh, 0E8h	; сравнение неупорядоченных операндов
FPIDEF FUCOMIP,	0DFh, 0E8h	; тоже, но с освобождением st.

Напомним, что операнды неупорядочены, если значения одного или обоих лежат за пределами допустимого диапазона чисел. Обычно при выполнении операций над неупорядоченными операндами возникает аварийная ситуация при сравнении этого не происходит, а вырабатывается соответствующий признак.

Операнд в оперативной памяти. У новых двухадресных инструкций групп MMX, SSE1 и SSE2 один операнд находится в регистре mmx или xmm, а другой либо в регистре, либо в оперативной памяти. В большинстве случаев в оперативной памяти находится второй операнд, исключением являются инструкции пересылки. Они позволяют перемещать данные как из памяти в регистры, так и в обратном направлении.

Составить макроопределения для анализа и преобразования имен новых регистров mmx и xmm несложно. Намного сложнее выполнять анализ и преобразование адресов операндов, находящихся в оперативной памяти. При работе с 32-х разрядными адресами код операнда содержит переменное количество байтов, а в его мнемонической записи допускается использование арифметических и логических выражений. В качестве примера приведем три вполне корректные формы записи адресов операндов:

dword ptr [ebx + 8]
[eax + 4*ecx + 32]
16[ebx][eax*4];

Очевидно, что для анализа и преобразования подобных выражений нужен разбор всех допустимых случаев, при этом текст макроопределения становится слишком большим и трудно обозримым. Пример макроса, обрабатывающего ограниченный набор способов записи адресов операндов, приведен в Интернет на моем сайте www.macro.aaanet.ru. Но и при разумных ограничениях текст макроса остается достаточно длинным, поэтому некоторые авторы выбирают другой путь. К их числу относится Агнер Фог. Разработанные им макросы для компиляции новых инструкций находятся в Интернет, на сайте www.agner.org.

Подмена компилируемой инструкции. В этом случае в макроопределении выполняется только анализ и преобразование имен новых регистров, а адреса операндов, находящихся в оперативной памяти компилирует MASM. Для того чтобы он мог это сделать, в тексте макроопределения формируется псевдокоманда. Она состоит из имени подходящей инструкции общего назначения и операндов, указанных в реальной команде. После компиляции псевдокоманды в полученном объектном коде изменяется код операции. В качестве примера мы рассмотрим такой трюк (иначе его никак не назовешь) в чистом виде, когда в командах отсутствуют имена новых регистров.

Как уже говорилось, начиная с Pentium Pro, микропроцессоры семейства Pentium поддерживают группу CMOVcc, состоящую из 16-ти инструкций условной пересылки. При работе с версиями MASM 6.0 - 6.11 для компиляции инструкций этой группы нужно специальное макроопределение. Его текст приведен в примере 10.

Пример 10. Макроопределение для компиляции инструкций CMOVcc

CMOVDEF 	MACRO OPNAME, CCODE ; мета-макро определение
 	OPNAME MACRO DST, SRC:VARARG ; заготовка макроопределения 
	LOCAL X, Y	; описание локальных переменных (меток)
	X:		; сохранение адреса начала команды
	BSF  DST, SRC	; компиляция псевдокоманды
	Y:		; сохранение адреса конца команды
	ORG X+1	; возврат ко второму байту кода операции
	DB   CCODE	; изменение кода второго байта команды
	ORG Y	; восстановление текущего адреса
	ENDM	; конец макроопределения OPNAME
ENDM		; конец макроопределения CMOVDEF
    ; Начало списка инструкций группы вызовов CMPVDEF
CMOVDEF CMOVO,	40h	; пересылка если переполнение	
CMOVDEF CMOVNO,	41h	; пересылка если нет переполнения
CMOVDEF CMOVB,	42h	; пересылка если меньше
CMOVDEF CMOVNB,	43h	; пересылка если больше или равно
    ; и так далее вплоть до инструкции CMOVG с кодом 4Fh

Текст примера 6.10 состоит из мета-макро определения CMOVDEF и неполного списка его макровызовов с указанием имен 4-х первых инструкций группы CMOVcc и соответствующих им кодов операций.

Предположим, что в тексте программы встретилась команда "cmovb cx, [bx+2]". MASM находит имя инструкции в списке CMOVDEF и вызывает одноименное мета-макро определение с параметрами CMOVB, 42h. Оно, в свою очередь, формирует и выполняет макроопределение "cmovb cx, [bx+2]", в котором производится подмена имени компилируемой инструкции.

Для подмены взята инструкция BSF, ее исполняют микропроцессоры семейства Intel, начиная с модели 386, и могут компилировать все версии MASM, начиная с 5.1. Таким образом, реально компилируется команда "bsf cx, [bx+2]". В результате MASM получается объектный код "0F BC 4F 02". Остается заменить в этом коде содержимое второго байта (BC) на 42, т. е. сформировать код "0F 42 4F 02", соответствующий исходной команде "cmovb cx, [bx+2]"

Особенности работы с макроопределением. Как уже говорилось, подмена компилируемой инструкции это трюк, а применение любых трюков обычно связано с определенными ограничениями. Обсудим два основных ограничения.

1. При корректировке кода инструкции BSF в примере 10 изменяется содержимое второго байта, но в некоторых случаях, нужный байт может быть не вторым, а третьим или четвертым. Давайте разберемся, в каких случаях это происходит.

Начиная с Intel 386, микропроцессоры могут работать в 16-ти разрядном (реальном) и в 32-х разрядном (защищенном) режимах. Независимо от установленного режима у команд могут быть как 16-ти, так и 32-х разрядные операнды и адреса. Если разрядность операнда или адреса не совпадает с разрядностью установленного режима работы процессора, то у команды появляются один или два префикса. Префикс размера операнда имеет код 66h, а префикс размера адреса имеет код 67h.

При наличии префиксов текущий адрес, сохраненный в примере 10 в переменной X, не соответствует адресу начала кода операции, а содержимое адреса X+1 не является вторым байтом кода операции и его изменять нельзя. Как поступать в таких случаях?

MASM определяет разрядность режима работы микропроцессора исходя из модели памяти, указанной в директиве .MODEL. Допустимы следующие имена моделей: TINY, SMALL, COMPACT, MEDIUM, LARGE, HUGE, FLAT. Из них только модель FLAT соответствует 32-х разрядному режиму работы микропроцессора.

Если компилируемые инструкции работают только с 16-ти разрядными операндами и адресами, то вы можете выбрать любую модель, кроме FLAT. И наоборот, если инструкции работают только с 32-х разрядными операндами и адресами, то надо выбрать модель FLAT. Только при выполнении этих условий у команд отсутствуют префиксы, и макроопределение примера 10 является корректным.

Если по каким-то причинам указанное требование не выполнимо, то надо произвести предварительную компиляцию и получить листинг программы. По листингу уточняется наличие и количество префиксов перед командой BSF и вносится изменение в текст макроопределения примера 10. В зависимости от количества префиксов адрес в директиве ORG X+1 изменяется на X+2 или X+3. Ничего лучшего предложить нельзя.

2. Запись адреса операнда, находящегося в оперативной памяти, может состоять из нескольких слов или выражений, разделенных пробелами. При компиляции таких записей с применением макроопределения примера 10 будет выдано аварийное сообщение "слишком много операндов". Для того чтобы в подобных случаях не возникало аварийной ситуации, запись адреса в команде надо заключить в угловые скобки, например, <dword ptr [eax]>. Сказанное не распространяется на пробелы, используемые при записи индексных выражений, заключенных в квадратные скобки.

Следует подчеркнуть, что указанные особенности или недостатки относятся не только к примеру 10, но и ко всем макроопределениям, в которых применяется подмена компилируемой инструкции.

Условное ассемблирование. Одним из полезных приемов программирования является включение в исходный текст программы условных блоков. Так называют последовательность команд, директив или макроопределений, которые компилируются или не компилируются в зависимости от выполнения заданного условия. Мы опишем пример условного блока, но предварительно поясним его назначение.

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

Программист обычно знает номер версии MASM, с которой он работает, и самостоятельно решает вопрос о необходимости применения соответствующих макроопределений. Но если создается общедоступное макроопределение, то заранее неизвестно кто им воспользуется, и какая версия компилятора окажется в его распоряжении. В таком случае текст макроопределения целесообразно оформить в виде условного блока, перед компиляцией которого проверяется поддержка MASM конкретной группы директив. Текст начала такого блока приведен в примере 11.

Пример 11. Управление компиляцией макроопределения
	IFDEF @version ; если переменная @version определена, то
		IF @version GE 612 ; если версия MASM 6.12 или выше, то
		; MASM поддерживает инструкции группы P6
		.686  ; разрешение компиляции инструкций группы P6
		SUPPORT EQU 1 ; определение переменной SUPPORT
		ENDIF  ; конец действия директивы IF
	ENDIF  ; конец действия директивы IFDEF
	IFNDEF SUPPORT  ; если переменная SUPPORT не определена, то
	; здесь располагается текст макроопределения для компиляции новых
	; инструкций из группы P6, например, полный текст примера 9 или 10.
	ENDIF  ; конец действия директивы IFNDEF SUPPORT

В примере 6.11 проверяется поддержка MASM инструкций общего назначения, входящих в группу P6. Если вы собираетесь компилировать новые инструкции MMX, то, не изменяя версию, замените в тексте примера 6.11 директиву .686 на .mmx. При компиляции инструкций групп SSE надо изменить номер версии на 614, и вместо директивы .686 указать .xmm.

Замечание. Директивы .686, .mmx и .xmm разрешают компиляцию разных категорий инструкций микропроцессоров, поэтому их можно использовать совместно, но директива .686 должна быть указана первой. По крайней мере, так надо поступать при работе с версией MASM 6.15.

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

  [C] pts / HI-TECH