Глава 24. Встроенный ассемблер
Встроенный ассемблер Borland Pascal позволяет вам непосредс-
твенно в программах Паскаля записывать код ассемблера процессоров
8087/8087 и 80286/80287. Вы, конечно, если требуется чередовать
код Паскаля и ассемблера, можете преобразовать код ассемблера в
машинные инструкции вручную и воспользоваться затем операторами
inline, либо выполнять компоновку с файлами .OBJ, которые содер-
жат внешние процедуры и функции (external).
Встроенные операторы ассемблера представляют собой большое
подмножество синтаксиса, поддерживаемого Турбо Ассемблером и Мак-
роассемблером фирмы Microsoft. Встроенный ассемблер поддерживает
все коды операций процессором 8086/8087 и 80286/80287 и некоторые
из операций, используемых в выражениях Турбо Ассемблера.
За исключением директив DB (определить байт), DW (определить
слово) и DD (определить двойное слово) никакие другие директивы
Турбо Ассемблера, типа EQU, STRUC, SEGMENT или MACRO, встроенным
ассемблером не поддерживаются. Однако, операции, реализуемые с
помощью директив Турбо Ассемблера, близко соответствуют конструк-
циям Borland Pascal. Например, большинство директив EQU соответс-
твуют описаниям Borland Pascal const, var и type, директива PROC
- описаниям procedure и function, а директива STRUC - типам
record Borland Pascal. Фактически, встроенный ассемблер Borland
Pascal можно рассматривать, как компилятор языка ассемблера, ис-
пользующий для всех описаний синтаксис Паскаля.
Оператор asm
Встроенный ассемблер становится доступным с помощью операто-
ров asm. Оператор asm имеет следующий синтаксис:
asm оператор_ассемблера < разделитель оператор_ассемблера > end
где "оператор_ассемблера" представляет собой оператор ассемблера,
а "разделитель " - это точка с запятой, новая строка или коммен-
тарий Паскаля. Приведем некоторые примеры операторов asm:
asm
mov ah,0 { считать с клавиатуры код функции }
int 16H { для чтения клавиши вызвать BIOS }
mov CharCode,al { сохранить код ASCII }
mov ScanCode,ah { сохранить код опроса }
end;
asm
push ds { сохранить DS }
lds si,Source { загрузить указатель источника }
les di,Dest { загрузить указатель приемника }
mov cx,Count { загрузить размер блока }
cld { переместить }
rep movsb { скопировать блок }
pop ds { восстановить DS }
end;
Заметим, что на одной строке можно разместить несколько опе-
раторов ассемблера, разделив их точками с запятой. Кроме того
следует отметить, что если операторы ассемблера размещаются на
разных строках, разделять их точками с запятой не требуется. За-
метим также, что точка с запятой не говорит о том, что остальная
часть строки представляет собой комментарий. Комментарии следует
записывать, используя синтаксис Паскаля: с помощью { и } или (* и
*).
Использование регистров
Правила использования регистров в операторе asm в основном
совпадают с этими правилами для внешних процедур и функций. Опе-
ратор asm должен сохранять регистры BP, SP, SS и DS, но может
свободно изменять AX, BX, CX, DX, SI, DI, ES и регистр флагов. На
входе в оператор asm BP указывает на текущую рамку стека, SP ука-
зывает на вершину стека, SS содержит адрес сегмента стека, а DS -
адрес сегмента данных. За исключением регистров BP, SP, SS и DS
оператор asm не может делать никаких предположений относительно
содержимого других регистров на входе в этот оператор.
Синтаксис операторa ассемблера
Оператор ассемблера имеет следующий синтаксис:
[ метка":" ] < префикс > [код_операции [операнд < "," операнд >]]
где "метка" - это идентификатор метки, "префикс" - префикс кода
операции ассемблера. "Код_операции" - код инструкции или директи-
ва ассемблера, а "операнд" - выражение ассемблера.
Между операторами ассемблера (но не в них) допускается вклю-
чать комментарии. Допустимо, например, следующее:
asm
mov ax,1 { начальное значение }
mov cx,100 { счетчик }
end;
однако следующая запись ошибочна:
asm
mov { начальное значение } ax,1
mov cx, { счетчик } 100
end;
Метки
Метки в ассемблере определяются также, как в Паскале: перед
оператором записывается идентификатор метки и двоеточие. Как и в
Паскале, метки в ассемблере должны описываться в объявлении label
того блока, который содержит оператор asm. Однако из этого прави-
ла есть одно исключение. Это локальные метки.
Локальные метки - это метки, которые начинаются с символа @.
Поскольку этот символ не может быть частью идентификатора Паска-
ля, такие локальные метки автоматически ограничиваются использо-
ванием их в операторах asm. Локальная метка известна только в оп-
ределяющем ее операторе asm (то есть область действия локальной
метки начинается от ключевого слова asm и заканчивается ключевым
словом end оператора asm, который ее содержит).
В отличие от обычной метки, локальную метку перед ее исполь-
зованием не требуется описывать в объявлении label.
Идентификатор локальной метки состоит из символа @, за кото-
рым следует одна или более букв (A..Z) цифр (0..9) символов под-
черкивания или символов @. Как и все метки, идентификатор завер-
шается двоеточием.
Коды инструкций
Встроенный ассемблер поддерживает инструкции процессоров
8086/8087 и 80286/80287. Инструкции процессора 8087 доступны
только в состоянии {$N+} (разрешено использование сопроцессора),
инструкции процессора 80286 - только в состоянии {$G+} (разрешена
генерация кода для процессора 80286), а инструкции сопроцессора
80287 - только в состоянии {$G+,N+}.
Полное описание каждой инструкции содержится в справочных
материалах по процессорам 80х86 и 80х87.
Размер инструкции RET
Инструкция REP генерирует код машинной инструкции возврата
ближнего или дальнего типа, в зависимости от модели вызова теку-
щей процедуры или функции.
procedure NearProc; near;
begin
asm
ret { генерируется ближний возврат }
end;
end;
procedure FarProc; far
begin
asm
ret { генерируется дальний возврат }
end;
end;
С другой стороны, инструкции RETN и RETF всегда генерируют
ближний или дальний возврат соответственно, независим от модели
вызова текущей процедуры или функции.
Автоматическое определение размера перехода
Если не указывается противное, встроенный ассемблер оптими-
зирует инструкции перехода, автоматически выбирая наиболее корот-
кую, и, следовательно, наиболее эффективную форму инструкции пе-
рехода. Такое автоматическое определение размера перехода
применяется к инструкции безусловного перехода (JMP) и всем инс-
трукциям условного перехода, когда переход выполняется на метку,
а не процедуру или функцию.
Для инструкции безусловного перехода встроенный ассемблер
генерирует короткий переход (один байт кода операции, за которым
следует один байт смещения), если расстояние до целевой метки на-
ходится в границах от -128 до 127 байт. В противном случае гене-
рируется ближний переход (один байт кода операции, за которым
следую два байта смещения).
Для инструкций условного перехода короткий переход (один
байт кода операции, за которым следует один байт смещения) гене-
рируется, если расстояние до целевой метки находится в пределах
от -128 до 127 байт, в противном случае встроенный ассемблер ге-
нерирует короткий переход с обратным условием, который выполняет
переход на целевую метку через ближний переход (в общем случае 5
байт). Например, оператор ассемблера:
JC Stop
где Stop не находится в границах короткого перехода, преобразует-
ся в последовательность машинных кодов, соответствующих инструк-
циям:
jnc Skip
jmp Stop
Skip:
Переходы на точки входа в процедуру или функцию всегда имеют
ближний или дальний тип (но не короткий), а условные переходы на
процедуру или функцию не допускаются. Вы можете указать встроен-
ному ассемблеру, что нужно генерировать ближний или дальний пере-
ход, используя конструкцию NEAR PTR или FAR PTR. Например, опера-
торы ассемблера:
jmp NEAR PTR Stop
jmp FAR PTR Stop
будут всегда генерировать соответственно ближний и дальний пере-
ход, даже если на метку Stop можно перейти с помощью короткого
перехода.
Директивы ассемблера
Встроенный ассемблер Borland Pascal поддерживает три дирек-
тивы ассемблера: DB (определить байт), DW (определить слово) и DD
(определить двойное слово). Каждая из них генерирует данные, со-
ответствующие разделенным запятым операндам, которые следуют за
директивой.
Директива DB генерирует последовательность байт. Каждый опе-
ранд может представлять собой выражение-константу со значением от
-128 до 255, или строку символов любой длины. Выражение-константа
генерирует 1 байт кода, а строки генерируют последовательность
байт со значениями, соответствующим коду ASCII каждого символа.
Директива DW генерирует последовательность слов. Каждый опе-
ранд может представлять собой выражение-константу со значением от
-32768 до 65535, или адресное выражение. Для адресного выражения
встроенный ассемблер генерирует указатель ближнего типа, что есть
слово, содержащие смещения адреса.
Директива DD генерирует последовательность двойных слов.
Каждый операнд может представлять собой выражение-константу со
значением от -2147483648 до 4294967295 или адресное выражение.
Для адресного выражения встроенный ассемблер генерирует указатель
дальнего типа, что есть слово, содержащие смещения адреса, за ко-
торым следует слово, содержащее сегментную часть адреса.
Данные, генерируемые по директивам DB, DW и DD, всегда запи-
сываются в сегмент кода, аналогично коду, генерируемому другими
операторами встроенного ассемблера. Чтобы сгенерировать инициали-
зированные или неинициализированные данные в сегменте данных, вам
следует использовать обычные описания Паскаля типа var или const.
Приведем некоторые примеры директив DB, DW и DD:
asm
DB 00FH { 1 байт }
DB 0,99 { 2 байта }
DB 'A' { Ord('A) }
DB 'Пример',0DH,OAH { строка, за которой
следуют возврат каретки и перевод строки }
DB 12,"Borland Pascal" { строка Паскаля }
DW 0FFFFH { 1 слово }
DW 0,9999 { 2 слова }
DW 'A' { эквивалентно DB 'A',0 }
DW 'BA' { эквивалентно DB 'A','B' }
DW MyVar { смещение MyVar }
DW MyProc { смещение MyProc }
DD 0FFFFFFFH { 1 двойное слово }
DD 0,99999999 { 2 двойных слова }
DD 'A' { эквивалентно DB 'A',0,0,0 }
DD 'DBCA' { эквивалентно DS 'A','B','C','D' }
DD MyVar { указатель на MyVar }
DD MyProc { указатель на MyProc }
end;
В Турбо Ассемблере, когда перед идентификатором указывается
DB, DW или DD, это приводит к генерации в том месте, где указана
директива, переменной размером в байт, слово или двойное слово.
Например, Турбо Ассемблер допускает следующее:
ByteVar DB ?
WordVar DW ?
.
.
.
mov al,ByteVar
mov bx,WordVar
Встроенный ассемблер не поддерживает такие описания перемен-
ных. В Borland Pascal единственным видом идентификатора, который
можно определить в операторе встроенного ассемблера, является
метка. Все переменные должны описываться с помощью синтаксиса
Паскаля, и предыдущая конструкция соответствует следующему:
var
ByteVar: Byte;
WordWat: Word;
.
.
.
asm
mov al,ByteVar
mov bx,WordVar
end;
Операнды
Операнды встроенного ассемблера представляют собой выраже-
ния, которые состоят из сочетания констант, регистров, идентифи-
каторов и операций. Хотя выражения встроенного ассемблера форми-
руются с использованием тех же основных принципов, что и
выражения Паскаля, имеется ряд важных отличий, которые необходимо
пояснить.
Во встроенном ассемблере предопределенный смысл имеют следу-
ющие зарезервированные слова:
AH CL FAR SEG
AL CS HIGH SHL
AND CX LOW SHR
AX DH MOD SI
BH DI NEAR SP
BL DL NOT SS
BP DS OFFSET ST
BX DWORD OR TBYTE
BYTE DX PTR TYPE
CH ES WQORD WORD
XOR
Зарезервированные слова всегда имеют больший приоритет, чем
определенные пользователем идентификаторы. Например, во фрагменте
программы:
var
ch: Char;
...
asm
mov ch,1
end;
1 будет загружаться в регистр CH, а не в переменную CH. Для дос-
тупа к определенному пользователем имени нужно использовать ам-
персанд - операцию переопределения идентификатора (&).
asm
mov &ch,1
end;
Мы настоятельно рекомендуем не использовать определенные
пользователем идентификаторы с теми же именами, что и зарезерви-
рованные слова встроенного ассемблера, поскольку такая путаница
имен может легко приводить к очень трудноуловимым ошибкам.
Выражения
Встроенный ассемблер вычисляет все выражения, как 32-разряд-
ные значения-указатели. Он не поддерживает значения с плавающей
точкой и строковые значения, за исключением строковых констант.
Выражения встроенного ассемблера строятся из элементов выра-
жений и операций, а каждая операция имеет соответствующий класс
выражения и тип выражения. Эти принципы поясняются в следующих
разделах.
Различия между выражениями Паскаля и ассемблера
Большинство важных различий между выражениями Паскаля и вы-
ражениями встроенного ассемблера состоит в том, что выражения
встроенного ассемблера должны при вычислении сводиться к значе-
нию-константе, другими словами, к значению, которое можно вычис-
лить на этапе компиляции. Например, с учетом описаний:
const
X = 10;
Y = 20;
var
Z: Integer;
следующий оператор является во встроенном ассемблере допустимым:
asm
mov Z,X+Y
end;
Поскольку X и Y - это константы, выражение X + Y представля-
ет собой просто удобный способ записи константы 30, и полученная
в результате инструкция помещает непосредственное значение 30 в
переменную Z размером в слово. Но если вы опишете X и Y, как пе-
ременные:
var
X, Y: Integer;
то встроенный ассемблер не сможет на этапе компиляции вычислить
значение X + Y. Корректной конструкцией встроенного ассемблера в
этом случае будет:
asm
mov ax,X
add ax,Y
mov Z,ax
end;
Другим важным отличием выражений Паскаля и встроенного Ас-
семблера является способ интерпретации переменных. В выражении
Паскаля ссылка не переменную интерпретируется, как содержимое пе-
ременной, но в выражении встроенного ассемблера ссылка на пере-
менную означает адрес переменной. Например, в Паскале выражение X
+ 4, где X - переменная, означает содержимое X, плюс 4, а во
встроенном ассемблере это означает содержимое в слове по адресу
на 4 байта выше, чем адрес X. Поэтому, хотя допустима запись:
asm
mov ax,X+4
end;
этот код не загружает значения X, плюс 4 в AX, а загружает значе-
ние слова, записанного через 4 байта после X. Корректной записью
сложения 4 с содержимым X будет:
asm
MOV AX,X
ADD AX,4
end;
Элементы выражений
Основными элементами выражения являются константы, регистры
и идентификаторы.
Константы
Встроенный ассемблер поддерживает два типа констант: число-
вые константы и строковые константы.
Числовые константы
Числовые константы должны быть целыми и принимать значения в
диапазоне от -2147483648 до 4294967295.
По умолчанию числовые константы являются десятичными, однако
встроенный ассемблер поддерживает также двоичные, восьмеричные и
шестнадцатиричные константы. Двоичное представление обозначается
записью после числа B, восьмеричное - записью буквы O, а шестнад-
цатиричное - записью после числа H или указанием перед числом $.
В выражениях Паскаля суффиксы B, O и H не поддерживаются.
Выражения Паскаля допускают только десятичную (по умолчанию) и
шестнадцатиричную запись (используется префикс $).
Числовые константы должны начинаться с одной из цифр или
символа $. Таким образом, когда вы записываете шестнадцатиричную
константу с помощью суффикса H, то если первой значащей цифрой
является одна из шестнадцатиричных цифр от A до F, то требуется
дополнительный ноль. Например, 0BAD4H и $BAD4 представляют собой
шестнадцатиричные константы, а BAD4H - это идентификатор, так как
он начинается с буквы, а не с цифры.
Строковые константы
Строковые константы должны заключаться в одиночные или двой-
ные кавычки. Указание двух последовательных кавычек одного типа в
качестве закрывающих кавычек считается за один символ. Приведем
некоторые примеры строковых констант:
'Z'
'Borland Pascal'
"That's all folks"
'"That''s all falks," he said.'
'100
'"'
"'"
Заметим, что в четвертой строке для обозначения одиночного
символы кавычки используется две последовательных одиночных ка-
вычки.
В директивах DB допускаются строковые кавычки любой длины.
Это приводит к выделению последовательности байт, содержащих зна-
чения (ASCII) символов строки. Во всех других случаях строковые
константы не могут превышать четырех символов и обозначают число-
вое значение, которое может участвовать в выражениях. Числовое
значение строки вычисляется следующим образом:
Ord(Ch1) + Ord(Ch2) shl 8 + Ord(Ch3) shl 16 + Ord(Ch4) shl 24
где Ch1 - это самый правый (последний) символ, а Ch4 - самый ле-
вый (первый) символ. Если строка короче 4 символов, то самые ле-
вые (первые) символы считаются нулевыми. Приведем некоторые при-
меры строковых констант и их значений:
Примеры строк и их значения
Таблица 24.1
---------------T---------------------
¦ Строка ¦ Значение ¦
+--------------+---------------------+
¦ 'a' ¦ 00000061H ¦
¦ 'ba' ¦ 00006261H ¦
¦ 'cba' ¦ 00636261H ¦
¦ 'dcba' ¦ 64636261H ¦
¦ 'a' ¦ 00006120H ¦
¦ ' a' ¦ 20202061H ¦
¦ 'a'*2 ¦ 000000E2H ¦
¦ 'a'-'A' ¦ 00000020H ¦
¦ not 'a' ¦ FFFFFF9EH ¦
L--------------+----------------------
Регистры
Следующие зарезервированные идентификаторы обозначают ре-
гистры ЦП:
Регистры ЦП Таблица 24.2
-----------------------------------------------------------------
16-разрядные регистры общего назначения: AX BX CX DX
8-разрядные младшие полурегистры: AL BL CL DL
8-разрядные старшие полурегистры: AH BH CH DH
16-разрядные указатели или индексные регистры: SP BP SI DI
16-разрядные сегментные регистры: CS DS SS ES
регистр стека процессора 8087 ST
-----------------------------------------------------------------
Когда операнд состоит исключительно из имени регистра, он
называется регистровым операндом. Все регистры можно использо-
вать, как регистровые операнды. Кроме того, некоторые регистры
могут использоваться в других контекстах.
Базовые регистры (BX или BP) и индексные регистры (SI или
DI) можно записывать в квадратных скобках для указания индекса-
ции. Допустимым сочетанием базового/индексного регистра являются
[BX], [BP], [SI], [DI], [BX+SI], [BX+DI], [BP+SI] и [BP+DI].
Сегментные регистры (ES, CS, SS и DS) могут использоваться
вместе с операцией переопределения сегмента (:) и указывать на
другой сегмент, отличный от того, который процессор выбирает по
умолчанию. На каждый из 8 регистров с плавающей точкой можно ссы-
латься с помощью ST(x), где x - константа от 0 до 7, указывающая
на расстояние от вершины стека регистров.
Идентификаторы
Встроенный ассемблер позволяет в выражениях ассемблера полу-
чить доступ ко всем идентификаторам Паскаля, включая метки, конс-
танты, типы, переменные, процедуры и функции. Кроме того, во
встроенном ассемблере реализованы следующие специальные идентифи-
каторы:
@Code @Data @Result
Идентификаторы @Code и @Data представляют текущие сегменты
кода и данных соответственно. Их следует использовать только в
сочетании с операцией SEG:
asm
mov ax,SEG @Data
mov ds,ax
end;
Идентификатор @Result в операторной части функции переменную
- результат функции. Например, в функции:
function Sum(X, Y: Integer): Integer;
begin
Sum := X + Y;
end;
в операторе, присваивающем результат функции переменной Sum, мож-
но было бы при записи на встроенном ассемблере использовать пере-
менную @Result:
function Sum(X, Y: Integer): Integer;
begin
asm
mov ax,X
add ax,Y
mov @Result,ax
end;
end;
В выражениях встроенного ассемблера нельзя использовать сле-
дующие идентификаторы:
- стандартные процедуры и функции (например, WriteLn, Chr);
- специальные массивы Mem, MemW, MemL, Port, PortW;
- строки, значения с плавающей точкой и константы множест-
венного типа;
- метки, которые не описаны в текущем блоке;
- идентификатор @Result вне функции.
В Таблице 24.3 приведены значение, класс и тип различного
вида идентификаторов, которые можно использовать в выражениях
встроенного ассемблера (классы и типы выражений описываются в
следующем разделе):
Значения, классы и типы идентификаторов Таблица 24.3
-------------T--------------------T----------------T------------
¦Идентификат.¦ Значение ¦ Класс ¦ Тип ¦
+------------+--------------------+----------------+------------+
¦ Метка ¦ Адрес метки ¦ Память ¦ SHORT ¦
¦ Константа ¦ Значение константы ¦ Непосредствен- ¦ 0 ¦
¦ ¦ ¦ ный ¦ ¦
¦ Тип ¦ 0 ¦ Память ¦ Размер типа¦
¦ Поле ¦ Смещение поля ¦ Память ¦ Размер типа¦
¦ Переменная ¦ Адрес переменной ¦ Память ¦ Размер типа¦
¦ Процедура ¦ Адрес процедуры ¦ Память ¦ NEAR / FAR¦
¦ Функция ¦ Адрес функции ¦ Память ¦ NEAR / FAR¦
¦ Модуль ¦ 0 ¦ Непосредствен- ¦ 0 ¦
¦ ¦ ¦ ный ¦ ¦
¦ @Code ¦ Адрес сегмента кода¦ Память ¦ 0FFF0H ¦
¦ @Data ¦ Адрес сегмента ¦ Память ¦ 0FFF0H ¦
¦ ¦ данных ¦ ¦ ¦
¦ @Result ¦ Смещение перемен- ¦ Память ¦ Размер типа¦
¦ ¦ ной результата ¦ ¦ ¦
L------------+--------------------+----------------+-------------
Локальные переменные (переменные, описанные в процедурах и
Функциях) всегда распределяются в стеке и доступны относительно
SS:BP, а значение идентификатора локальной переменной представля-
ет собой ее смещение со знаком от SS:BP. ассемблер автоматически
добавляет [BP] к ссылкам на локальные переменные. Например, с
учетом описаний:
procedure Test;
var
Count: Integer;
инструкции:
asm
mov ax,Count
end;
ассемблируются в MOV AX,[BP-2].
Встроенный ассемблер всегда интерпретирует параметр-перемен-
ную, как 32-разрядный указатель, а размер параметра-переменной
всегда равен 4 (размеру 32-разрядного указателя). В Паскале син-
таксис для доступа к параметру-переменной и к значению параметра
одинаков. В случае встроенного ассемблера это не так. Поэтому для
доступа к содержимому параметра-переменной вам сначала придется
загрузить 32-разрядный указатель, а затем обратиться к ячейке, на
которую он указывает. Например, если X и Y - параметры-переменные
приведенной выше функции Sum, то она может выглядеть следующим
образом:
function Sum(var X, Y: Integer): Integer;
begin
asm
les bx,X
mov ax,es:[bx]
les bx,Y
add ax,es:[bx]
mov @Result,ax
end;
end;
Некоторые идентификаторы, такие, как переменные типа запись,
имеют область действия, позволяющую обращаться к ним с помощью
операции выбора элементы структуры - точки (.). Например, с уче-
том описаний:
type
Point = record
X, Y: Integer;
end;
Rect = record
A, B: Point;
end;
var
P: Point;
R: Rect;
для доступа к полям в переменных P и R можно использовать следую-
щие конструкции:
asm
mov ax,P.X
mov dx,P.Y
mov cx,R.A.X
mov bx,R.B.Y
end;
Для непосредственного построения переменной можно использо-
вать идентификатор типа. Каждая из приведенных ниже инструкций
генерирует один и тот же машинный код, загружающий в AX
ES:[DI+4]:
asm
mov ax,(Rect PTR es:[di]).B.X
mov ax,Rect(es:[di].B.X
mov ax,es:Rect[di].B.X
mov ax,Rect[es:di].B.X
mov ax,es:[di].Rect.B.X
end;
Область действия задается типов, полем и идентификатором пе-
ременной типа записи или объектного типа. Кроме того, идентифика-
тор модуля открывает область действия конкретного модуля (как
полностью уточненный идентификатор в Паскале).
Классы выражений
Выражения встроенного ассемблера подразделяются на три клас-
са: регистровые значения, ссылки на память и непосредственные
значения.
Выражение, состоящее только из имени регистра, является ре-
гистровым значением. Примерами регистровых значений являются AX,
CL, DI и ES. Используемые в качестве операндов, регистровые выра-
жения указывают ассемблеру на необходимость генерировать инструк-
ции, которые работают с регистрами ЦП.
Выражения, обозначающие адреса памяти, являются ссылками на
память. К этой категории относятся метки Паскаля, переменные, ти-
пизованные константы, процедуры и функции.
Выражения, которые не являются регистровыми и не связаны с
ячейками памяти, представляют собой непосредственные значения.
Эта группа включает в себя нетипизированные константы и идентифи-
каторы типа.
Непосредственные значения и ссылки на память при использова-
нии их в качестве операндов приводят к генерации различного кода.
Например:
const
Start = 10;
var
Count: Integer;
.
.
.
asm
mov ax,Start { MOV AX,xxxx }
mov bx,Count { MOV BX,[xxxx] }
mov cx,[Start] { MOV CX,[xxxx] }
mov dx,OFFSET Count { MOV DX,xxxx }
end;
Поскольку Start - это непосредственное значение, первая инс-
трукция MOV ассемблируется в непосредственную инструкцию. Однако
вторая инструкция MOV транслируется в инструкцию, ссылающуюся на
память, так как Count - это ссылка на память. В третьей инструк-
ции MOV для преобразования Start в ссылку на память (в данном
случае слово со смещением 10 в сегменте данных) используется опе-
рация квадратных скобок. В четвертой инструкции MOV для преобра-
зования Count в непосредственное значение (смещение Count в сег-
менте данных) используется операция OFFSET.
Как вы можете видеть, квадратные скобки и операция OFFSET
дополняют друг друга. В терминах результирующего машинного кода
следующий оператор asm идентичен первым двум строкам предыдущего
оператора asm:
asm
mov ax,OFFSET [Start]
mov bx,[OFFSET Count]
end;
Ссылки на память и непосредственные значения классифицируют-
ся, в свою очередь, как перемещаемые и абсолютные выражения. Пе-
ремещаемое выражение обозначает значение, которое требует на эта-
пе компоновки перемещения, а абсолютное выражение обозначает зна-
чение, которое такого перемещения не требует. Обычно выражение со
ссылкой на метку, переменную процедуру или функцию является пере-
мещаемым, а выражение, где операции выполняются исключительно с
константами - абсолютным.
Перемещение является процессом, с помощью которого компонов-
щик присваивает идентификаторам абсолютные адреса. На этапе ком-
поновки компилятору неизвестны конечные адреса метки, переменной,
процедуры или функции. Они не будут известны до этапа компоновки,
на котором компоновщик присваивает идентификатору конкретный аб-
солютный адрес.
Встроенный ассемблер позволяет вам выполнять любую операцию
с абсолютным значением, но операции с перемещаемыми значениями
ограничиваются сложением и вычитанием констант.
Типы выражений
Каждое выражение встроенного ассемблера имеет соответствую-
щий тип, или, если говорить точнее, размер, поскольку встроенный
Ассемблер рассматривает тип выражения просто как его размер в па-
мяти. Например, тип (размер) переменной Integer равен 2, так как
она занимает два байта.
Там, где это возможно, встроенный ассемблер выполняет про-
верку типов, поэтому в инструкциях:
var
QuitFlag: Boolean;
OutBufPtr: Word;
.
.
.
asm
mov al,QuitFlag
mov bx,OutBufPtr
end;
встроенный ассемблер проверяет, что размер QuitFlag равен 1
(байт), а размер OutBufPtr - двум (слово). Если проверка типа об-
наруживает несоответствие, возникает ошибка. Например, следующее
недопустимо:
asm
mov dl,OutBufPtr
end;
так как DL - это байтовый регистр, а OutBufPtr - слово. Тип ссыл-
ки на память можно изменить с помощью назначения типа. Корректным
способом записи предыдущих инструкций будет следующий:
asm
mov dl,BYTE PTR OutBufPtr
mov dl,Byte(OutBufPtr)
mov dl,OutBufPtr.Byte
end;
Все эти инструкции ссылаются на первый (менее значащий) байт
переменной OutBufPtr.
В некоторых случаях ссылка на память является нетипизирован-
ной, то есть не имеет соответствующего типа. Приведем пример с
непосредственным значением, заключенным в квадратные скобки:
asm
mov al,[100H]
mov bx,[100H]
end;
Встроенный ассемблер допускает обе этих функции, поскольку
выражение [100H] не имеет соответствующего типа, оно просто озна-
чает "содержимое по адресу 100H в сегменте данных", а тип можно
определить из первого операнда (байт для AL, слово для BX). В том
случае, когда тип нельзя определить из другого операнда, встроен-
ный ассемблер требует явного назначения типа:
asm
mov BYTE PTR [100H]
mov WORD PTR [100H]
end;
В Таблице 24.4 приведены предопределенные идентификаторы ти-
па, которые предусмотрены во встроенном ассемблере дополнительно
к типам, описанным в Паскале:
Предопределенные идентификаторы типа Таблица 24.4
---------------------T---------------------------------
¦ Идентификатор ¦ Тип ¦
+--------------------+---------------------------------+
¦ BYTE ¦ 1 ¦
¦ WORD ¦ 2 ¦
¦ DWORD ¦ 4 ¦
¦ QWORD ¦ 8 ¦
¦ TBYTE ¦ 10 ¦
¦ NEAR ¦ 0FFFEH ¦
¦ FAR ¦ 0FFFFH ¦
L--------------------+----------------------------------
Заметим в частности, что NEAR и FAR - это псевдотипы, кото-
рые используются с идентификаторами процедур и функций для указа-
ния их модели вызова. Вы можете использовать назначения типа NEAR
и FAR аналогично другим идентификаторам. Например, если FarProc -
процедура с дальним типом вызова (FAR):
procedure FarProc; far;
и если вы записываете код встроенного ассемблера в том же модуле,
где находится FarProc, то вы можете использовать для ее вызова
более эффективную инструкцию NEAR:
asm
push cs
call NEAR PTR FarProc
end
Операции в выражениях
Встроенный ассемблер предусматривает множество операций,
подразделяемых по старшинству на 12 классов. В Таблице 24.5 пере-
числены операции, использующиеся в выражениях встроенного ассемб-
лера в порядке убывания их старшинства:
Встроенные операции ассемблера Таблица 24.5
-------------------------------T--------------------------------
¦ Операция ¦ Комментарий ¦
+------------------------------+--------------------------------+
¦ & ¦ Операция переопределения иден-¦
¦ ¦ тификатора. ¦
+------------------------------+--------------------------------+
¦ (), [], * ¦ Выбор элемента структуры. ¦
+------------------------------+--------------------------------+
¦ HIGH, LOW ¦ Унарные операции. ¦
¦ +, - ¦ ¦
+------------------------------+--------------------------------+
¦ : ¦ Операция переопределения сег-¦
¦ ¦ мента. ¦
¦ OFFSET, SEG, TYPE, PTR, ¦ ¦
¦ *, /, MOD, SHL, SHR ¦ ¦
+------------------------------+--------------------------------+
¦ +, - ¦ Бинарные операции сложения/вы- ¦
¦ ¦ читания. ¦
+------------------------------+--------------------------------+
¦ NOT, AND, OR, XOR ¦ Поразрядные операции. ¦
L------------------------------+---------------------------------
Определения операций встроенного ассемблера Таблица 24.6
-------T--------------------------------------------------------
¦Опер. ¦ Описание ¦
+------+--------------------------------------------------------+
¦ & ¦ Переопределение идентификатора. Идентификатор, непос-¦
¦ ¦ редственно следующий за амперсантом, интерпретируется,¦
¦ ¦ как идентификатор, определяемый пользователем, даже ес-¦
¦ ¦ ли он соответствует зарезервированному слову встроенно-¦
¦ ¦ го ассемблера. ¦
+------+--------------------------------------------------------+
¦ (...)¦ Подвыражение. Выражение в скобках полностью вычисляет-¦
¦ ¦ ся, после чего интерпретируется, как один элемент. Вы-¦
¦ ¦ ражению в скобках может предшествовать другое выраже-¦
¦ ¦ ние. Результатом в этом случае будет сумма значений¦
¦ ¦ двух выражений с типом первого выражения. ¦
+------+--------------------------------------------------------+
¦ [...]¦ Ссылка на память. Выражение в квадратных скобках пол-¦
¦ ¦ ностью вычисляется, после чего интерпретируется, как¦
¦ ¦ один элемент. Выражение в квадратных скобках может ком-¦
¦ ¦ бинироваться с регистрами BX, BP, SI, DI с помощью опе-¦
¦ ¦ рации +, что указывает на индексирование регистра ЦП.¦
¦ ¦ Выражению в квадратных скобках может предшествовать¦
¦ ¦ другое выражение. Результатом в этом случае будет сумма¦
¦ ¦ значений двух выражений с типом первого выражения. Ре-¦
¦ ¦ зультатом всегда будет ссылка на память. ¦
+------+--------------------------------------------------------+
¦ . ¦ Выбор элемента структуры. Результатом будет сумма выра-¦
¦ ¦ жения перед точкой и выражения после точки с типом вы-¦
¦ ¦ ражения после точки. Идентификаторы, относящиеся к об-¦
¦ ¦ ласти действия, и указанные в выражении перед точкой¦
¦ ¦ доступны в выражении после точки. ¦
+------+--------------------------------------------------------+
¦ HIGH ¦ Возвращает старшие 8 бит выражения размером в слово,¦
¦ ¦ следующего за операцией. Выражение должно представлять¦
¦ ¦ собой непосредственное абсолютное значение. ¦
+------+--------------------------------------------------------+
¦ LOW ¦ Возвращает младшие 8 бит выражения размером в слово,¦
¦ ¦ следующего за операцией. Выражение должно представлять¦
¦ ¦ собой непосредственное абсолютное значение. ¦
+------+--------------------------------------------------------+
¦ + ¦ Унарный плюс. Возвращает следующее за плюсом выражение¦
¦ ¦ без изменений. Выражение должно представлять собой не-¦
¦ ¦ посредственное абсолютное значение. ¦
+------+--------------------------------------------------------+
¦ - ¦ Унарный минус. Возвращает следующее за минусом выраже-¦
¦ ¦ ние с обратным знаком. Выражение должно представлять¦
¦ ¦ собой непосредственное абсолютное значение. ¦
+------+--------------------------------------------------------+
¦ : ¦ Переопределение сегмента. Указывает ассемблеру, что вы-¦
¦ ¦ ражение после двоеточия относится к сегменту, заданному¦
¦ ¦ именем сегментного регистра (CS, DS, SS или ES) перед¦
¦ ¦ двоеточием. Результатом является ссылка на память со¦
¦ ¦ значением выражения после двоеточия. Когда переопреде-¦
¦ ¦ ление сегмента используется в операнде инструкции, инс-¦
¦ ¦ трукции предшествует соответствующий префикс переопре-¦
¦ ¦ деления сегмента, обеспечивающий выбор указанного¦
¦ ¦ сегмента. ¦
+------+--------------------------------------------------------+
¦OFFSET¦ Возвращает смещение следующего за операцией выражения¦
¦ ¦ (младшее слово). Результатом будет непосредственное¦
¦ ¦ значение. ¦
+------+--------------------------------------------------------+
¦ SEG ¦ Возвращает сегмент следующего за операцией выражения¦
¦ ¦ (старшее слово). Результатом будет непосредственное¦
¦ ¦ значение. ¦
+------+--------------------------------------------------------+
¦ TYPE ¦ Возвращает тип (размер в байтах) следующего за операци-¦
¦ ¦ ей выражения. Типом непосредственного значения будет 0.¦
+------+--------------------------------------------------------+
¦ PTR ¦ Операция назначения типа. Результатом будет ссылка на¦
¦ ¦ память со значением выражения, следующего за операцией¦
¦ ¦ и типом выражения перед операцией. ¦
+------+--------------------------------------------------------+
¦ * ¦ Умножение. Оба выражения должны представлять собой не-¦
¦ ¦ посредственные абсолютные значения. Результатом будет¦
¦ ¦ непосредственное абсолютное значение. ¦
+------+--------------------------------------------------------+
¦ / ¦ Целочисленное деление. Оба выражения должны представ-¦
¦ ¦ лять собой непосредственные абсолютные значения. Ре-¦
¦ ¦ зультатом будет непосредственное абсолютное значение. ¦
+------+--------------------------------------------------------+
¦ MOD ¦ Остаток целочисленного деления. Оба выражения должны¦
¦ ¦ представлять собой непосредственные абсолютные значе-¦
¦ ¦ ния. Результатом будет непосредственное абсолютное зна-¦
¦ ¦ чение. ¦
+------+--------------------------------------------------------+
¦ SHL ¦ Логический сдвиг влево. Оба выражения должны представ-¦
¦ ¦ лять собой непосредственные абсолютные значения. Ре-¦
¦ ¦ зультатом будет непосредственное абсолютное значение. ¦
+------+--------------------------------------------------------+
¦ SHR ¦ Логический сдвиг вправо. Оба выражения должны представ-¦
¦ ¦ лять собой непосредственные абсолютные значения. Ре-¦
¦ ¦ зультатом будет непосредственное абсолютное значение. ¦
+------+--------------------------------------------------------+
¦ + ¦ Сложение. Выражения могут представлять собой непосредс-¦
¦ ¦ твенные абсолютные значения или ссылки на память, но¦
¦ ¦ перемещаемым значением может быть только одно выраже-¦
¦ ¦ ние. Если одно из выражений - перемещаемое значение, то¦
¦ ¦ результатом также будет перемещаемое значение. Если од-¦
¦ ¦ но из выражений - ссылка на память, то результатом так-¦
¦ ¦ же будет ссылка на память. ¦
+------+--------------------------------------------------------+
¦ - ¦ Вычитание. Первое выражение может иметь любой класс, а¦
¦ ¦ второе выражение должно быть непосредственным абсолют-¦
¦ ¦ ным выражением. Результат имеет тот же тип, что и пер-¦
¦ ¦ вое выражение. ¦
+------+--------------------------------------------------------+
¦ NOT ¦ Поразрядное отрицание. Выражение должно представлять¦
¦ ¦ собой непосредственные абсолютные значения. Результатом¦
¦ ¦ будет непосредственное абсолютное значение. ¦
+------+--------------------------------------------------------+
¦ AND ¦ Поразрядная операция AND (И). Оба выражения должны¦
¦ ¦ представлять собой непосредственные абсолютные значе-¦
¦ ¦ ния. Результатом будет непосредственное абсолютное зна-¦
¦ ¦ чение. ¦
+------+--------------------------------------------------------+
¦ OR ¦ Поразрядная операция OR (ИЛИ). Оба выражения должны¦
¦ ¦ представлять собой непосредственные абсолютные значе-¦
¦ ¦ ния. Результатом будет непосредственное абсолютное зна-¦
¦ ¦ чение. ¦
+------+--------------------------------------------------------+
¦ XOR ¦ Поразрядная операция XOR (исключающее ИЛИ). Оба выраже-¦
¦ ¦ ния должны представлять собой непосредственные абсолют-¦
¦ ¦ ные значения. Результатом будет непосредственное абсо-¦
¦ ¦ лютное значение. ¦
L------+---------------------------------------------------------
Процедуры и функции ассемблера
До сих пор мы рассматривали конструкцию asm...end, как опе-
ратор с обычной частью begin...end. Директива assembler в Borland
Pascal позволяет вам писать на встроенном ассемблере целиком про-
цедуры и функции без необходимости begin...end. Приведем пример
функции на ассемблере:
function LongMul(X, Y: Integer) : Longint; assembler;
asm
mov ax,X
imul Y
end;
Директива assembler приводит к тому, что Borland Pascal вы-
полняет при генерации кода следующую оптимизацию:
- Компилятор не генерирует код для копирования парамет-
ров-значений в локальные переменные. Это влияет на все па-
раметры-значения строкового типа и другие значения-пара-
метры, размер которых не равен 1, 2 или 4 байтам. Внутри
процедуры или функции такие параметры должны интерпретиро-
ваться, как если бы они были параметрами-переменными.
- Компилятор не выделяет память для результата функции, и
ссылка на идентификатор @Result будет ошибкой. Однако
строковые функции являются исключением из этого правила -
они всегда имеют указатель @Result, который распределяется
пользователем.
- Для процедур и функций, не имеющих параметров и локальных
переменных, компилятор не генерирует кадров стека.
- Для процедуры и функции на ассемблере автоматически гене-
рируется код выхода:
push bp ; присутствует, если Locals <> 0 или
; Params <> 0
mov bp,sp ; присутствует, если Locals <> 0 или
; Params <> 0
sub sp,Locals ; присутствует, если Locals <> 0
...
mov sp,bp ; присутствует, если Locals <> 0
pop bp ; присутствует, если Locals <> 0 или
; Params <> 0
ret Params ; всегда присутствует
где Locals - размер локальных переменных, а Params - раз-
мер параметров. Если и Locals и Params = 0, то кода входа
не будет, и код выхода состоит просто из инструкции RET.
Функции, использующие директиву assembler, должны возвращать
результат следующим образом:
- результаты функции порядкового типа (Integer, Char,
Boolean, перечислимые типы) возвращаются в AL (8-разрядное
значение), AX (16-разрядное значение) или DX:AX (32-раз-
рядное значение);
- результаты функции вещественного типа (Real) возвращаются
в DX:BX:AX;
- результаты функции типов 8087 (Single, Double, Extended,
Comp) возвращаются в ST(0) (регистр стека сопроцессора
8087);
- результаты функции типа указатель возвращаются в DX:AX;
- результаты функции строкового типа возвращаются во времен-
ной ячейке, на которую указывает @Result.
Директива assembler во многом похожа на директиву external.
Процедуры и функции на ассемблере должны должны подчиняться тем
же правилам, что и процедуры и функции типа external. Следующие
примеры показывают некоторые отличия операторов asm в обычных
процедурах и функциях от процедур и функций ассемблера. В первом
примере оператор asm используется в обычной функции для преобра-
зования строки в верхний регистр. Заметим, что значение параметра
Str в этом случае ссылается на локальную переменную, поскольку
компилятор автоматически генерирует код входа, копирующий факти-
ческий параметр в локальную память.
function UpperCase(Str: String): String;
begin
asm
cld
lea si,Str
les di,@Result
SEGSS lodsb
stosb
xor ah,ah
xchg ax,cx
jcxz @3
@1:
SEGSS lodsb
cmp al,'a'
ja @2
cmp al,'a'
ja @2
cmp al,'z'
jb @2
sub al,20H
@2:
stosb
loop @1
@3:
end;
end;
Второй пример на ассемблере представляет собой версию функ-
ции UpperCase. В этом случае Str не копируется в локальную па-
мять, и функция должна интерпретировать Str, как параметр-пере-
менную.
function UpperCase(S: String): String; assembler;
asm
push ds
cld
lds si,Str
les di@Result
lodsb
stosb
xor ah,ah
xchg ax,cx
jcxz @3
@1:
lodsb
cmp al,'a'
ja @2
cmp al,'z'
jb @2
sub al,20H
@2:
stosb
loop @1
@3:
pop ds
end;
|