Глава 25. Компоновка с программами на языке ассемблера
С помощью директивы компилятора $L можно выполнить компонов-
ку программ или модулей на языке Паскаль и процедур и функций на
языке ассемблера. Из исходного файла на языке ассемблера можно с
помощью ассемблера получить объектный файл (с расширением .OBJ).
Используя компоновщик, несколько объектных файлов можно скомпоно-
вать с программой или модулем. При этом используется директива
компилятора $L.
В программе или модуле на языке Паскаль процедуры или функ-
ции, написанные на языке ассемблера, должны быть описаны как
внешние. Например:
function LoCase(Ch : Char): Char; external;
В соответствующем файле на языке ассемблера все процедуры
или функции должны находиться в сегменте с именем CОDЕ или CSEG,
или в сегменте, имя которого заканчивается на _TEXT, а имена
внешних процедур и функций должны быть указаны в директивах
PUВLIC.
Вы должны обеспечить соответствие процедуры или функции ее
определению в Паскале. Это относится в типу ее вызова (ближний
или дальний), числу и типу параметров и типу результата.
В исходном файле на языке ассемблера могут описываться ини-
циализированные переменные, содержащиеся в сегменте с именем
CONST или в сегменте, оканчивающемся на _DAТA, и неинициализиро-
ванные переменные в сегменте с именем DATA или DSEG, или в сег-
менте, имя которого оканчивается на _BSS. В исходном файле на
языке ассемблера эти переменные являются частными, и на них нель-
зя ссылаться из модуля или программы на Паскале. Они, однако, на-
ходятся в том же сегменте, что и глобальные переменные Паскаля, и
доступны через регистр сегмента DS.
На все процедуры, функции и переменные, описанные в модуле
или программе на Паскале и на те из них, которые описаны в интер-
фейсной секции используемых модулей, можно ссылаться из исходного
файла на языке ассемблера с помощью директивы EXTRN. При этом
обязанность обеспечить корректный тип в определении EXTRN также
возлагается на вас.
Когда объектный файл указывается в директиве $L, Borland
Pascal преобразует файл из формата перемещаемых объектных модулей
(.OBJ) фирмы Intel в свой собственный внутренний формат перемеща-
емых модулей. Это преобразование возможно лишь при соблюдении не-
которых правил:
1. Все процедуры и функции должны быть помещены в сегмент с
именем CODЕ или CSEG, или в сегмент, имя которого окан-
чивается на _TEXT. Все инициализированные частные пере-
менные должны помещаться в сегмент с именем Const или в
сегмент, имя которого оканчивается на _DATA. Все неини-
циализированные частные переменные должны быть помещены
в сегмент, имя которого оканчивается на _DAТA. Неинициа-
лизированные локальные переменные должны помещаться в
сегмент с именем DATA или DSEG, или в сегмент, имя кото-
рого оканчивается на _BSS. Все другие сегменты игнориру-
ются, поэтому имеется директива GRОUР. В определениях
сегмента может задаваться выравнивание на границу слова
или байта (WORD или ВYTE). При компоновке они всегда вы-
равниваются на границу слова. В определениях сегментов
могут указываться директивы PUВLIС и имя класса (они иг-
норируются).
2. Borland Pascal игнорирует все данные для сегментов, от-
личных от сегмента кода (CODE, CSEG или xxxx_TEXT) и
инициализированного сегмента данных (CONST или
xxxx_DATA). Поэтому при описании переменных в сегменте
неинициализированных данных (DAТA, DSEG или xxxx_BSS)
для определения значения всегда используйте вопроситель-
ный знак (?). Например:
Count DW ?
Buffer DB 128 DUP(?)
3. Байтовые ссылки на идентификаторы типа EXTRN недопусти-
мы. Это означает, например, что операторы НIGНТ и LОW
нельзя использовать с идентификаторами типа EXTRN.
Турбо Ассемблер и Borland Pascal
Турбо Ассемблер (TASM) значительно облегчает разработку
программ на языке ассемблера и организации в них интерфейса с
программами Borland Pascal. Турбо Ассемблер поддерживает специфи-
ческое использование сегментов, схему памяти и языковую поддержку
для программистов, работающих на Borland Pascal.
Используя ключевое слово PASCAL и директиву .MODEL, можно
обеспечить соблюдение соглашений о вызовах с Borland Pascal, оп-
ределить имена сегментов, выполнить инструкции PUSH BP и MOV
PB,SP, а также обеспечить возврат управления с помощью операторов
POP BP и RET N (где N - это число байт параметра). Директива
.MODEL имеет следующий синтаксис:
.MODEL xxxx, PASCAL
где xxxx - это модель памяти (обычно LARGE).
Задание в директиве .MODEL языка PASCAL сообщает Турбо
Ассемблеру, что параметры были занесены в стек слева-направо - в
том порядке, в котором они обнаружены в исходном операторе, вызы-
вающем процедуру.
Директива PROC позволяет вам задать параметры в том же по-
рядке, как они определены в программе Borland Pascal. Если вы оп-
ределяете функцию, которая возвращает строку, обратите внимание
на то, что директива PROC имеет опцию RETURNS, позволяющую вам
получить доступ к временному указателю строки в стеке и не оказы-
вающую влияния на число байт параметра, добавляемых в операторе
RET.
Приведем примеры кода, в которых используются директивы
.MODEL и PROC:
.MODEL LARGE, PASCAL
.CODE
MyProc PROC FAR 1:BYTE, j : BYTE RETURNS result : DWORD
PUBLIC MyProc
les di,result ; получить адрес временной строки
mov al,i ; получить первый параметр i
mov bl,j ; получить второй параметр j
.
.
.
ret
Определение функции в Borland Pascal будет выглядеть следую-
щим образом:
function MyProc(i,j : char) : string; external;
Примеры программ на языке ассемблера
Следующая программа является примером модуля и представляет
собой две программы на ассемблере, предназначенные для обработки
строк. Функция UppеrCаsе преобразует символы строки в прописные
буквы, а функция StringOf возвращает строку символов заданной
длины.
unit Strings;
interface
function UpperCase(S: string): string;
function StringOf(Ch: char; Count: byte): string;
inplementation
{$L STRS}
function UpperCase; external;
function StringOf; external;
end.
Далее приведен файл на языке ассемблера, в котором реализо-
ваны программы StringOf и UppеrCаsе. Перед компиляцией модуля
Strings этот файл должен быть ассемблирован в файл с именем
STRS.OBJ. Обратите внимание на то, что в программах используется
дальний тип вызова, так как они описаны в интерфейсной секции
блока.
CODE SEGMENT BYTE PUBLIC
ASSUME CS:CODE
PUBLIC UpperCase, StringOf ; объявить имена
function Uppercase(S: String): String
UpperRes EQU DWORD PTR [BP+10]
UpperStr EQU DWORD PTR [BP+6]
Uppercase PROC FAR
PUSH BP ; сохранить регистр BP
MOV BP,SP ; установить стек
PUSH DS ; сохранить регистр DS
LDS SI,UpperStr ; загрузить адрес строки
LES DI,UpperRes ; загрузить адрес результата
CLD ; переместить строку
LODSB ; загрузить длину строки
STOSB ; скопировать результат
MOV CL,AL ; поместить длину строки в СХ
XOR CH,CH
JCXZ U3 ; пропустить в случае пустой
; строки
U1: LODSB ; пропустить, если символ отличен
; от 'а'...'z'
CPM AL,'a'
JB U2
CPM AL,'z'
JA U2 ; переместить строку
SUB AL,'a'-'A' ; преобразовать в прописные буквы
U2: STOBS ; сохранить результат
LOOP U1 ; цикл по всем символам
U3: POP DS ; восстановить регистр DS
POP BP ; восстановить регистр ВР
RET 4 ; удалить параметры и возвратить
; управление
UpperCase ENDP
; function StringOf(Ch: Char; Count: Byte): String
StrOfRes EQU DWORD PTR [BP + 10]
StrOfChar EQU BYTE PTR [BP + 8]
StrOfCOunt EQU BYTE PTR [BP + 6]
StringOf PROC FAR
PUSH BP ; сохранить регистр ВР
MOV BP,SP ; установить границы стека
LES DI,StrOfRes ; загрузить адрес результата
MOV AL,StrOfCount ; загрузить счетчик
CLD ; продвинуться на строку
STOSB ; сохранить длину
MOV CL,AL ; поместить значение счетчика в CX
XOR CH,CH
MOV AL,StrOfChar ; загрузить символ
REP STOSB ; сохранить строку символов
POP ; восстановить ВР
RET ; извлечь параметры и выйти
SrtingOf ENDP
CODE ENDS
END
Чтобы ассемблировать этот пример и скомпилировать модуль,
можно использовать следующие команды:
TASM STR5
BPC stringer
Методы на языке ассемблера
Методы, реализованные на языке ассемблера, можно скомпоно-
вать с программами Borland Pascal с помощью директивы компилятора
$L и зарезервированного ключевого слова external. Описание внеш-
него метода в объектном типе не отличается от обычного метода;
однако в реализации метода перечисляется только заголовок метода,
за которым следует зарезервированной слово external. В исходном
тексте на ассемблере вместо точки (.) для записи уточненных иден-
тификаторов следует использовать операцию @ (точка в ассемблере
уже имеет другой смысл и не может быть частью идентификатора).
Например, идентификатор Паскаля Rect.Init записывается на ассемб-
лере как Rest@Init. Синтаксис @ можно использовать как в иденти-
фикаторах PUBLIC, так и EXTRN.
Включаемый машинный код
Для небольших подпрограмм на языке ассемблера очень удобно
использовать внутренние директивы и операторы Borland Pascal
(операторы inline). Они позволяют вставлять инструкции машинного
кода непосредственно в программу или текст блока, вместо того,
чтобы использовать объектный файл.
Операторы Inline
Оператор inline состоит из зарезервированного слова Inline,
за которым следует одна или более встроенных записей (записей ма-
шинного кода), разделенных косой чертой и заключенных в круглые
скобки:
inline(10/$2345/Count+1/Data-Offset);
Оператор inline имеет следующий синтаксис:
--------- ---- ----------- ----
подставляемый -->¦ inline +->¦ ( +---->¦ запись в +-T->¦ ) +->
оператор L--------- L---- ^ ¦ машинном ¦ ¦ L----
¦ ¦ коде ¦ ¦
¦ L----------- ¦
¦ ---- ¦
L------+ / ¦<-----
L----
Каждый оператор inline состоит из необязательного специфика-
тора размера, < или >, и константы или идентификатора переменой,
за которой следуют ноль или более спецификаторов смещения (см.
описанный далее синтаксис). Спецификатор смещения состоит из +
или -, за которым следует константа.
------------
запись во --T-------------------->¦ константа +--------------->
встроенном ¦ ---- ^ L------------ ^
машинном +-->¦ < +------+ ¦
коде ¦ L---- ¦ ¦
¦ ---- ¦ ¦
+-->¦ > +------- ¦
¦ L---- ¦
¦ ---------------- ¦
L->¦ идентификатор +-T---------------------
¦ переменной ¦ ¦ ^
L---------------- ¦ ¦
------ L---------
¦ ----- ---------- ¦
L----->¦знак+-->¦константа¦--T-----
^ L----- L---------- ¦
L--------------------------
Каждая запись inline порождает 1 байт или одно слово кода.
Значения вычисляется, исходя из значения первой константы или
смещения идентификатора переменной, к которому добавляется или из
которого вычитается значение каждой из последующих констант.
Если запись в машинном коде состоит только из констант и,
если ее значение лежит в 8-битовом диапазоне (0..255), то она по-
рождает один байт кода. Если значение выходит за границу 8-бито-
вого диапазона или если запись inline ссылается на переменную, то
генерируется одно слово кода (младший байт следует первым).
Операции < и > могут использоваться для отмены автоматичес-
кого выбора размера, который был описан ранее. Если оператор
inline начинается с операции <, то в код включается только млад-
ший значащий байт значения, даже если это 16-битовое значение.
Если оператор inline начинается с операции >, то в код включается
всегда слово, даже если старший значащий байт равен 0. Например,
оператор:
inline(<$1234/>$44);
гененирует код длиной три байта: $34,$44,$00.
Значение идентификатора переменной в записи inline представ-
ляет собой адрес смещения переменной внутри ее базового сегмента.
Базовый сегмент глобальных переменных (переменных, описанных на
самом внешнем уровне в модуле или программе) и типизованные конс-
танты, доступ к которым организован через регистр DS, представля-
ют собой сегмент данных. Базовый сегмент локальных переменных
(переменных, описанных внутри подпрограммы) является сегментом
стека. В этом случае смещение переменной относится к регистру ВР,
что автоматически влечет за собой выбор сегмента стека.
Примечание: Регистры BP, SP, SS и DS должны сохранять-
ся с помощью операторов inline. Значение всех других ре-
гистров можно изменять.
В следующем примере оператора inline генерируется машинный
код для записи заданного числа слов или данных в указанную пере-
менную. При вызове процедуры FillWord Count слов со значением
Data записывается в памяти, начиная с первого байта, обозначенно-
го как Dest.
procedure FillWord(var Dest, Count, Data: word);
begin
inline(
$C4/$BE/Dest/ { LES DI,Dest[BP] }
$8B/$8e/Count/ { MOV CX,Xount[BP] }
$8B/$86/Data/ { MOV AX,Data[BP] }
$FC/ { CLD }
$F3/$AB); { REP STOSW }
В операторной части блока операторы inline могут свободно
чередоваться с другими операторами.
Директивы inline
Директивы inline позволяют писать процедуры и функции, кото-
рые преобразуются при каждом вызове в заданную последовательность
инструкций, представляющих собой машинный код. Синтаксис у дирек-
тивы inline такой же, как у оператора inline:
-------------
директива ---------------------->¦ оператор +------------>
inline ¦ inline ¦
L-------------
При вызове обычной процедуры или функции (включая те, кото-
рые содержат в себе операторы inline) компилятором генерируется
такой код, в котором параметры (если они имеются) помещаются в
стек, а затем уже для обращения к процедуре или функции генериру-
ется инструкция CALL. Однако, когда вы обращаетесь к процедуре
или функции типа inline, компилятор вместо инструкции CALL гене-
рирует код из директивы inline. Вот короткий пример двух директив
inline:
procedure DisableInterrupts; inline($FA); { CLI }
procedure EnableInterrupts; inline($FB); { STI }
Когда вызывается процедура DisableInterrupt то генерируется
один байт кода - инструкция CLI.
Процедуры или функции, описанные с помощью директив inline,
могут иметь параметры, однако на параметры нельзя ссылаться сим-
волически (хотя для других переменных это допускается). К тому
же, поскольку такие процедуры или функции фактически являются
макрокомандами, у них отсутствуют автоматический код с инструкци-
ями входа или выхода и никаких инструкций возврата управления не
требуется.
Следующая функция выполняет умножение двух целых значений, в
результате чего получается число длинного целого типа:
function LongMul(X,Y : Integer): Longint;
inline(
$58/ { POP DS ; извлечь из стека Y }
$5A/ { POP AX ; извлечь из стека X }
$F7/$EA); { IMUL DX ; DX:AX = X*Y }
Обратите внимание на отсутствие инструкций входа и выхода и
инструкции возврата управления. Их присутствия не требуется, пос-
кольку при вызове этой функции содержащиеся в ней четыре байта
просто включаются в текст программы.
Директивы inline предназначены только для очень коротких
(менее 10 байт) процедур и функций.
Из-за того, что процедуры и функции типа inline имеют харак-
тер макроопределений, они не могут использоваться в качестве ар-
гумента операции @ или в функциях Addr, Offs и Seg.
Назад | Содержание