...

суббота, 16 января 2021 г.

О специальных макро в ассемблере

Введение

Много лет назад американским специалистом Гарри Килдэллом (Gary Kildall) в рамках создания системы программирования для персональных компьютеров был разработан транслятор с языка ассемблера для процессора Intel 8086, который он назвал RASM-86 (Relocating ASseMbler). Этот во многом типичный для своего времени продукт имел особенность: он позволял, не меняя транслятора, добавлять описания новых команд процессора с помощью специальных макросредств.

Автор статьи, используя и развивая этот транслятор, успешно применял данные средства по мере появления новых поколений процессоров. Конечно, иногда и сам транслятор требовал ряда доработок, например, при переходе на архитектуру IA-32, а затем и на x86-64 (IA-32e). Тем не менее, изначально заложенная идея позволила легко продолжать эволюцию транслятора до настоящего времени. Некоторые итоги этой работы рассматриваются далее.

Организация генерации команд в трансляторе с ассемблера

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

Транслятор RASM имеет общую внутреннюю таблицу всех возможных команд процессора. Элементы таблицы начинаются с текста мнемоники команды, за которым следует связанный список всех возможных форм данной команды в зависимости от числа, типа и размера ее операндов. Содержимое очередной формы команды состоит из последовательности «микрокодов», каждый из которых представляет отдельно обрабатываемую и уже неделимую часть команды.

Сначала транслятор ищет заданную команду в таблице по ее мнемонике, а затем, идя по связанному списку, ищет подходящую комбинацию операндов. Если заданной комбинации операндов (включая и случай отсутствия операндов в команде) не найдено – выдается сообщение об ошибке. Иначе транслятор читает последовательность «микрокодов» и выполняет каждый из них, составляя, таким образом, код заданной команды. При этом частные случаи комбинации операндов должны располагаться ранее общих случаев. Например, если команда имеет отдельную форму для операнда-байта, расширяемого со знаком, такой операнд должен встретиться в связанном списке раньше общего случая операнда-константы иначе, естественно, будет находиться только общий случай.

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

Трехпроходный транслятор RASM на первом проходе переведет это описание в последовательность «микрокодов» и вставит новое описание в общую таблицу команд. В этом принципиальное отличие данных макросредств от «обычных», которыми можно изменить исходный текст программы, но нельзя изменить содержимое самого транслятора. Если такая команда уже существовала, новое описание добавляется в начало имеющегося связанного списка. Если уже существовала и такая команда и такая комбинация операндов для нее – новое описание отменит предыдущее, так как будет вставлена «выше». После этого в тексте программы можно свободно использовать новую команду так же, как если бы она изначально была представлена в таблице транслятора.

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

По виду специальные макросредства RASM похожи на обычные средства макроподстановки: имеются ключевые слова CodeMacro и EndM, между которыми пишется «тело» макроопределения. В первой строке пишется имя макро и, возможно, список его параметров. Например:

CodeMacro AAA
  DB 37H
EndM

CodeMacro DIV divisor:Eb
SEGFIX divisor
  DB 6FH
EndM

CodeMacro OR dst:Re, src:Ee
  SEGFIX src
  DB 0BH
  MODRM dst,src
EndM

Описание формальных параметров

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

Типы формальных параметров:

A – сумматор EAX/AX/AL
C – выражение типа метка 
D – непосредственный операнд
E – адресное выражение, записанное в регистре или памяти
M – адресное выражение, может иметь базовые и индексные регистры
R – один из общих регистров
S – сегментный регистр
X – прямое обращение к памяти при обмене с сумматором

Размеры формальных параметров

n - длина неопределенна
b – байт
w – слово
e – двойное слово
d – длина при использовании адреса смещение+сегмент
sb – знаковый байт, расширяемый до слова
se – знаковый байт, расширяемый до двойного слова

Примеры описания формальных параметров:

CodeMacro IN dst:Aw, port:Rw (DX)
CodeMacro ROR dst:Ee, count:Rb (CL)

Директивы макроопределений

Первоначально все описания команд х86 свелись к нескольким директивам, часть из которых используются редко. Перевод транслятора на архитектуру IA-32 потребовал добавления лишь одной новой директивы управления префиксами размера/адреса 66H/67H, причем, чтобы не вводить новых ключевых слов используется уже имевшаяся директива, но с другой формой параметра.

Директивы DB, DW и DD

Данные директивы в макро почти эквиваленты обычным операторам ассемблера и используются для задания констант и адресов. Эти директивы характерны и для любых других (не специальных) макросредств.

Директива DW используется для задания адреса (4 байта в 32-х разрядном режиме), а директива DD – для задания адреса в виде смещение+сегмент. Примеры использования DB, DW и DD:

CodeMacro CLC
  DB 0F8H
EndM

CodeMacro XOR dst:Ee,src:De
  SEGFIX dst
  DB 81H
  MODRM 6,dst
  DW src
EndM

CodeMacro CALLF label:Cd
  DB 9AH
  DD label
EndM

Директива адресации MODRM

Это главная из «специальных» директив, определяющая адресацию архитектуры IA-32. Она определяет и основное отличие данных макросредств от обычных. Именно «микрокод», порождаемый этой директивой, указывает транслятору генерировать адресную часть команды, включая и байт режимов адресации и смещение и SIB-байт, если операнды подразумевают это. Директива имеет два параметра. Это или два имени формальных параметров макро или константа-число и имя. Например:

CodeMacro RCR dst:Ee, count:Rb(CL)
  SEGFIX dst
  DB 0D3H
  MODRM 3,dst
EndM

CodeMacro XOR dst:Re,src:Ee
  SEGFIX src
  DB 33H
  MODRM dst,src
EndM

Директивы определения относительного адреса RELB, RELW

Эти директивы используются для описания команд передачи управления по относительному адресу, занимающему или байт или 4 байта для IA-32. Пример:

CodeMacro LOOP place:Cb
  DB 0E2H
  RELB place
EndM

Директива задания кодов DBIT

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

CodeMacro DEC dst:Re
  DBIT 5(9), 3(dst(0))
EndM

Директива формирования префикса сегмента SEGFIX

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

Директива контроля сегментов NOSEGFIX

Директива имеет параметры в виде имени сегментного регистра и имени формального параметра. Она не генерирует кода, а проверяет, что обращение к данному параметру идет с использованием указанного сегментного регистра, иначе сообщает об ошибке. Эта директива требуется лишь в общих формах команд CMPS и MOVS, где один из операндов может адресоваться только через ES.

Данная директива была расширена для управления префиксами размера и адреса 66H/67H. В этом случае в директиве указывается параметр-число: 0 – нет префиксов, 1- может быть префикс 66H, 2 – может быть префикс 67H, 3 – могут быть оба префикса, 4 – всегда есть оба, 5 – никогда нет 66H, 6 – никогда нет 67H и т.п.

Такими простыми средствами удается описать все множество команд IA-32, например:

CodeMacro FLDCW src:Mw
  SEGFIX src
  DB 0D9H
  MODRM 5, src
EndM

CodeMacro CMOVAE dst:Re, src:Ee
  SEGFIX src
  DB 0FH
  DB 43H
  MODRM dst,src
EndM

Некоторое исключение из стройной системы описаний составляют команды FPU, имеющие операнд в памяти. Для простоты в RASM разрядность таких команд указывается прямо в мнемонике, а не определяется по размеру операнда в памяти. Поэтому в RASM есть, например, команды FIST16, FIST32 и FIST64. Однако на практике, с точки зрения ясности текста, указание разрядности операнда прямо в имени команды FPU оказалось вполне приемлемым.

Создание псевдокоманд с помощью макросредств

Используя возможность добавления новых комбинаций операндов можно конструировать новые «команды» процессора. Например, команду MOV ECX,10 часто целесообразно заменять двумя командами с более коротким кодом PUSH 10 и POP ECX. А эти две команды можно описать в виде одного макроопределения:

CodeMacro MOVSX dst:Re, src:Dse
  NOSEGFIX 6
  DB 6AH
  DB src
  DBIT 5(0BH),3(dst(0))
EndM

Такую псевдокоманду можно создать и «обычными» макросредствами, однако назвать ее именем уже существующей команды MOVSX другие трансляторы (кроме RASM), скорее всего, не позволят. С точки зрения результата, это именно выполнение MOVSX с параметром-константой. Но такой команды в процессоре нет. А с макросредствами RASM можно считать, что на самом деле есть такая формы команды. Тот факт, что реально выполнение идет за два приема и стек меняется, а затем восстанавливается, в большинстве случаев можно не учитывать.

За время использования транслятора RASM накопился ряд таких полезных псевдокоманд, например:

MOV X,Y, где X,Y переменные в памяти;

MOV DS,0 или MOV DS,ES;

Команды PUSH и POP для нескольких регистров сразу, т.е. PUSH EAX,EBX,ECX;

Обращение к портам без указания регистра DX и т.п.

Добавление новых типов команд

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

При этом в транслятор иногда приходится добавлять и новую группу имен специальных регистров этих команд (внутри транслятора имена это просто переименованные числа). Так, коды имен регистров CR0-CR7 являются внутри транслятора RASM числами 10H-17H, коды имен регистров MM0-MM7 числами 40H-47H, коды имен регистров XMM0-XMM7 числами 50H-57H, и т.д. Младшая цифра чисел (всегда 0-7) участвует в генерации кода через директиву MODRM, а собственно значения чисел используются для задания допустимого диапазона в формальных параметрах новых макро.

При поиске подходящих операндов транслятор проверит, что указанный в команде регистр входит в допустимый диапазон и поэтому, например, в командах MMX вместо MM0 нельзя указать «чужой» регистр CR0 или XMM0.

Часто в новых множествах команд требуется применить директиву NOSEGFIX 5, выключающую обычные правила использования префикса 66H (в зависимости от размера операндов), поскольку в описываемых командах этот префикс используется по-своему.

Тогда, например, для команд из множества MMX описания выглядят так:

CodeMacro MOVQ dst:Rn(40H,47H),src:Mn
  NOSEGFIX 5
  SEGFIX src
  DB 0FH
  DB 6FH
  MODRM dst,src
EndM

Для команд из множества XMM:

CodeMacro ADDPS dst:Rn(50H,57H),src:Mn
  NOSEGFIX 5
  SEGFIX src
  DB 0FH
  DB 58H
  MODRM dst,src
EndM

Для команд из множества SSE2:

CodeMacro ADDPD dst:Rn(50H,57H),src:Mn
  NOSEGFIX 5
  DB 66H
  SEGFIX src
  DB 0FH
  DB 58H
  MODRM dst,src
EndM

Для команд из множества 3DNow!:

CodeMacro PFACC dst:Rn(40H,47H),src:Mn
  NOSEGFIX 5
  SEGFIX src
  DB 0FH 
  DB 0FH
  MODRM dst,src
  DB 0AEH
EndM

Расширение макросредств для x86-64 (IA-32e), AVX-команд и т.д.

Разумеется, расширение транслятора для генерации 64-х разрядных команд потребовало очередных доработок макросредств в виде добавления новой длины операнда «Q» (64-битный операнд/регистр) и новой директивы REX, формирующей REX-префикс команд. Потребовалось также ввести новые диапазоны регистров, ну и конечно дополнить таблицу служебных слов названиями требуемых регистров, вроде «SPL» или «R14D» или «YMM15».

Однако все эти доработки потребовали именно расширения, но не кардинальной переделки транслятора.

Использование макросредств для генерации команд процессоров другой архитектуры

При выполнении работ по программированию RISC-процессора микроконтроллера AT90S2313 «штатный» транслятор с ассемблера показался автору после работы с RASM непривычным и поэтому неудобным. Возникла идея использовать специальные макросредства и для того, чтобы генерировать коды команд RISC-архитектуры в соответствии с документацией Atmel, но при этом остаться в привычной среде RASM. Дело упрощалось тем, что RASM имеет режим формирования загрузочного модуля сразу, без использования редактора связей.

Анализ показал, что имеется лишь три препятствия такого использования RASM для генерирования команд RISC-архитектуры: конфликт мнемоники команды ST с названием регистра FPU, форма записи инкремента указателя типа X+ и другой способ вычисления относительного адреса, делающий директиву RELW неподходящей.

Первые два препятствия были обойдены с помощью введения новых директив в RASM, позволяющих исключать из лексического анализа заданную лексему (в данном случае ST) и разрешать синтаксические конструкции инкремента типа X+.

Для вычисления относительного адреса команд RISC-архитектуры были доработаны директивы макро RELW и DBIT. В директиве RELW стало возможно указывать необязательные дополнительные параметры в виде «добавки» и «сдвига вправо», позволяющие не просто вычислить адрес относительно текущего места, но и пересчитать его к нужному виду прибавлением «добавки» и сдвигом на заданную величину. При этом новая форма директивы RELW сама уже не генерирует адрес, а запоминает его для последующего использования в директиве DBIT. Доработка DBIT заключалась в возможности использования адреса, вычисленного выше директивой RELW. Для указания такого адреса используется строка “S” вместо имени параметра.

Такие несложные доработки транслятора повысили универсальность макросредств. Все RISC-команды были легко описаны с их помощью, например:

…
CODEMACRO RJMP  k:Cw
  RELW 2,12,k
  DBIT 8('S'(1))
  DBIT 4(0CH),4('S'(9))
ENDM

CODEMACRO LDI   R_d:Db(16,31),K:Dn
  DBIT 4(R_d(0)),4(K(0))
  DBIT 4(0EH),4(K(4))
ENDM

CODEMACRO OUT   P:Dn(0,63),R1_r:Db(0,31)
  DBIT 4(R1_r(0)),4(P(0))
  DBIT 5(17H),2(P(4)),1(R1_r(4))
ENDM
и т.д.

И наконец стало можно программировать микроконтроллер AT90S2313 на RASM:

…
                 ;---- ПЕРЕХОД ПО RESET (0) ----

0000 02C0  0006  rjmp РЕСТАРТ

                 ;---- ПЕРЕХОД ПО INT 0 (1) ----

0002 CBC0  019A  rjmp ПРЕРЫВАНИЕ_ОТ_ГПР

РЕСТАРТ:
                 ;---- ИНИЦИАЛИЗАЦИЯ СТЕКА ----

 0006 BFED       ldi  СЧ_ТМ,СТЕК   ;КОНЕЦ РАБОЧЕЙ ПАМЯТИ
 0008 BDBF       out  SPL,СЧ_ТМ    ;УСТАНОВИЛИ СТЕК

                 ;---- ИНИЦИАЦИЯ ВЫХОДОВ ПОРТА "B" ----

 000A 2FE5       ldi  tmp,РАЗР_B
 000C 27BB       out  DDRB,tmp

                 ;---- ИНИЦИАЦИЯ ВЫХОДОВ ПОРТА "D" ----

 000E 22E0       ldi  tmp,РАЗР_D
 0010 21BB       out  DDRD,tmp

                 ;---- ИНИЦИАЦИЯ RS-232 ----

 0012 24E0       ldi  tmp,4           ;115200 БОД
 0014 29B9       out  UBRR,tmp
 0016 28E1       ldi  tmp,(1 SHL RXEN) OR (1 SHL TXEN)
 0018 2AB9       out  UCR,tmp
…

Заключение

Предложенная много лет назад идея, образно говоря, «оставить открытой дверь» в транслятор с ассемблера оказалось конструктивной и легко реализуемой. Почти не меняя транслятора можно оперативно отслеживать постоянно дополняемое множество команд современных процессоров. Легкость реализации обусловлена тем, что все множество команд состоит из небольшого числа базовых элементов.

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

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

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

Разумеется также, что очередные изменения архитектуры (например, переход к x86-64) не могут свестись в трансляторе только к использованию подобных макросредств, а объективно требуют и доработок самого транслятора. Тем не менее, как бы в дальнейшем ни продолжилась эволюция команд процессоров, данные средства наверняка окажутся полезными.

Литература

1.       «RASM-86 Programmer’s Guide» Digital Research, Сalifornia

https://ift.tt/3iklbKM

2.       М.Гук, В. Юров «Процессоры Pentium 4, Athlon и Duron». СПб.: Из-во Питер, 2001

Let's block ads! (Why?)

Комментариев нет:

Отправить комментарий