Глава 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 ГД>і ( ГДДДД>і запись в ГДВД>і ) ГД>

         оператор         АДДДДДДДДЩ  АДДДЩ ^   і машинном і і  АДДДЩ

                                            і   і   коде   і і

                                            і   АДДДДДДДДДДЩ і

                                            і      ЪДДДї     і

                                            АДДДДДДґ / і<ДДДДЩ

                                                   АДДДЩ

 

             Каждый оператор inline состоит из необязательного специфика-

        тора размера,  < или >, и константы или идентификатора переменой,

        за  которой  следуют  ноль или более спецификаторов смещения (см.

        описанный далее синтаксис).  Спецификатор смещения состоит  из  +

        или -, за которым следует константа.

 

                                           ЪДДДДДДДДДДДї

         запись во ДДВДДДДДДДДДДДДДДДДДДДД>і константа ГДДДДДДДДДДДДДДД>

         встроенном  і   ЪДДДї      ^      АДДДДДДДДДДДЩ       ^

         машинном    ГДД>і < ГДДДДДДґ                          і

         коде        і   АДДДЩ      і                          і

                     і   ЪДДДї      і                          і

                     ГДД>і > ГДДДДДДЩ                          і

                     і   АДДДЩ                                 і

                     і  ЪДДДДДДДДДДДДДДДї                      і

                     АД>і идентификатор ГДВДДДДДДДДДДДДДДДДДДДДЩ

                        і  переменной   і і                  ^

                        АДДДДДДДДДДДДДДДЩ і                  і

                                     ЪДДДДЩ                  АДДДДДДДДДї

                                     і      ЪДДДДї   ЪДДДДДДДДДї       і

                                     АДДДДД>ізнакГДД>іконстантаіДДВДДДДЩ

                                        ^   АДДДДЩ   АДДДДДДДДДЩ  і

                                        АДДДДДДДДДДДДДДДДДДДДДДДДДЩ

 

             Каждая запись  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    і

                                              АДДДДДДДДДДДДЩ

 

             При вызове обычной процедуры или функции (включая те,  кото-

        рые содержат в себе операторы 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.