Глава 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.
                       Назад | Содержание