ЭЛЕКТРОННАЯ БИБЛИОТЕКА КОАПП
Сборники Художественной, Технической, Справочной, Английской, Нормативной, Исторической, и др. литературы.



 

Часть 25

Глава 23. Компоновка с программами на языке Ассемблера
С помощью директивы компилятора $L можно выполнить компоновку программ или модулей на языке Паскаль и процедур и функций на языке Ассемблера. Из исходного файла на языке Ассемблера можно с помощью Ассемблера получить объектный файл (с расширением .ОВJ). 
Используя компоновщик, несколько объектных файлов можно скомпоновать с программой или модулем. При этом используется директива компилятора $L.
     В программе или модуле на языке Паскаль процедуры или функции, написанные на языке Ассемблера, должны быть описаны, как внешние (extrernal). Например: 
     function LoCase(Ch : char): char; external;

     В соответствующем файле на языке Ассемблера все процедуры или функции должны находиться в сегменте с именем CОDЕ или CSEG, или в сегменте, имя которого заканчивается на _TEXT, а имена внешних процедур и функций должны быть указаны в директивах РUВLI
С. 
     Вы должны обеспечить соответствие процедуры или функции ее определению в Паскале. Это относится в типу ее вызова (ближний или дальний), числу и типу параметров и типу результата. 
     В исходном файле на языке Ассемблера могут описываться инициализированные переменные, содержащиеся в сегменте с именем CONST или в сегменте, оканчивающемся на _DAТA, и неинициализированные переменные в сегменте с именем DATA или DSEG, или в сегменте
, имя которого оканчивается на _BSS. В исходном файле на языке Ассемблера эти переменные являются частными, и на них нельзя ссылаться из модуля или программы на Паскале. Они, однако, находятся в том же сегменте, что и глобальные переменные Паскаля, и дос
тупны через сегментный регистр DS. 
     На все процедуры, функции и переменные, описанные в модуле или программе на Паскале и на те из них, которые описаны в интерфейсной секции используемых модулей, можно ссылаться из исходного файла на языке Ассемблера с помощью директивы EXTRN. При это
м обязанность обеспечить корректный тип в определении EXTRN также возлагается на вас. 
     Когда объектный файл указывается в директиве $L, Турбо Паскаль преобразует файл из формата перемещаемых объектных модулей (.ОВJ) фирмы Intel в свой собственный внутренний формат перемещаемых модулей. Это преобразование возможно лишь при соблюдении н
екоторых правил: 
     1. Все процедуры и функции должны быть помещены в сегмент с именем CODЕ или CSEG, или в сегмент, имя которого оканчивается на _TEXT. Все инициализированные частные переменные должны помещаться в сегмент с именем Const или в сегмент, имя которого ока
нчивается на _DATA. Все неинициализированные частные переменные должны помещаться в сегмент, имя которого оканчивается на _DAТA. Неинициализированные частные переменные должны помещаться в сегмент с именем DATA или DSEG, или в сегмент, имя которого оканч
ивается на _BSS. Все другие сегменты игнорируются, поэтому имеется директива GRОUР. В определениях сегмента может задаваться выравнивание на границу слова или байта (WORD или ВYTE). При компоновке они всегда выравниваются на границу слова. В определениях
 сегментов могут указываться директивы PUВLIС и имя класса (они игнорируются). 
     2. Турбо Паскаль игнорирует все данные для сегментов, отличных от сегмента кода (CODE, CSEG или xxxx_TEXT) и инициализированного сегмента данных (CONST или xxxx_DATA). Поэтому при описании переменных в сегменте неинициализированных данных (DAТA, DSE
G или xxxx_BSS) для определения значения всегда используйте вопросительный знак (?). Например: 
          Count   DW  ?
          Buffer  DB  128 DUP(?)

     3. Байтовые ссылки на идентификаторы типа EXTRN недопустимы. Это означает, например, что операторы НIGНТ и LОW нельзя использовать с идентификаторами типа EXTRN. 
                 Турбо Ассемблер и Турбо Паскаль

     Турбо Ассемблер (TASM) значительно облегчает разработку программ на языке Ассемблера и организации в них интерфейса с программами на Турбо Паскале. Турбо Ассемблер поддерживает специфическое использование сегментов, схему памяти и языковую поддержку
 для программистов, работающих на Турбо Паскале. 
     Директива .MODEL задает в модуле Ассемблера, использующем упрощенные директивы определения сегментов, модель памяти. Для компоновки с программами на Паскале синтаксис директивы .MODEL должен иметь вид: 
     .MODEL[ xxxx, PASCAL
 гду xxxx -  модель памяти (обычно lagre). 
     Указание в директиве .MODEL ключевого слова PASCAL сообщает Турбо Ассемблеру, что аргументы были занесены в стек слева-направо, в том порядке, как они указаны в операторе исходного кода, вызывающем данную процедуру. 
     Директива PROC позволяет вам задать параметры в том же порядке, как они определены в программе на Турбо Паскале. Если вы определяете функцию, которая возвращает строку, обратите внимание на то, что директива 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

     Определение функции в Турбо Паскале будет выглядеть следующим образом: 
     function MyProc(i,j : char) : string external;

     Информацию об интерфейсе Турбо Ассемблера с Турбо Паскалем можно найти также в Главе 6 "Руководства пользователя". 
          Примеры подпрограмм на языке Ассемблера

     Следующая программа является примером модуля и представляет собой две подпрограммы на Ассемблере, предназначенные для обработки строк. Функция 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е. Перед компиляцией модуля Stringer этот файл должен быть ассемблирован в файл с именем STRS.OВJ. Обратите внимание на то, что в программах используется дал
ьний тип вызова, так как они описаны в интерфейсной секции модуля. 
      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            ; сохранить регистр ВР
            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            ; поместить значение счетчика в СХ
         XOR  CH,CH
         MOV  AL,StrOfChar     ; загрузить символ
         REP  STOSB            ; сохранить строку символов
         POP                   ; восстановить ВР
         RET                   ; извлечь параметры и выйти
      SrtingOf    ENDP
      CODE       ENDS
                 END

     Чтобы ассемблировать этот пример и скомпилировать модуль, можно использовать следующие команды: 
     TASM STR5
     TPCW stringer

     В следующем примере показана программа на Ассемблере, которая может ссылаться на программы и переменные Паскаля. Программа Numbers считывает до 100 целых значений и затем для проверки границ каждого из этих значений вызывает программу на языке Ассем
блера. Если значение выходит за границы, процедура, написанная на Ассемблере, вызывает для их распечатки процедуру, написанную на языке Паскаль. 
     program Numbers;
     {$L CHECK}
     var
       Data: array[1..100] of integer;
       Count,I: integer;
     procedure RangeError(N: integer);
       begin
         Writeln('Range error: ',N);
       end;
     procedure CheckRange(Min,Max: integer); external;
       begin
         Count := 0;
         while not Eof and (Count < 100) do
         begin
           Count := Count + 1;
           Readln(Data[Count]);
         end;
         CheckRange(-10,10);
       end;

     Файл с программой на Ассемблере, реализующий процедуру СheckRаngе, приводится ниже. Перед компиляцией программы Numbers его нужно ассемблировать в файл с именем CHECK.ОВJ. Заметим, что для процедуры используется ближний тип вызова, поскольку это опи
сано в программе. 
     DATA SEGMENT WORD PUBLIC
          EXTRN Data: WORD, Count: Word ; переменные Паскаля
     DATA ENDS
     CODE SEGMENT BYTE PUBLIC
          ASSUME CS: CODE, DS: DATA
          EXTRN RangeError: NEAR ; реализовано на Паскале
          PUBLIC CheckRange      ; реализованы здесь
     CheckRange PROC NEAR
        MOV BX,SP                ; получить указатель параметров
        MOV AX,SS:[BX+4]         ; загрузить Мin
        MOV DX,SS:[BX+2]         ; загрузить Мах
        XOR BX,BX                ; очистисть индекс данных
        MOV CX,Count             ; загрузить счетчик
        JCXZ SD4                 ; пропустить если равно 0
   SD1: CMP Data[Bx],AX          ; слишком мало?;
        JL  SD2                  ; да, перейти
        CMP Data[BX],DX          ; слишком велико?
        JLE SD3                  ; нет, перейти
   SD2: PUSH AX                  ; сохранить регистры
        PUSH BX
        PUSH CX
        PUSH DX
        PUSH Data[BX]            ; передать выходящее за границы
                                 ; значение в Паскаль
        CALL RangeError          ; вызвать процедуру на Паскале
        POP DX                   ; восстановить регистры
        POP CX
        POP BX
        POP AX
   SD3: INC BX                   ; установить указатель на
                                 ; следующий элемент
        INC BX
        LOOP SD1                 ; повторить цикл для каждого
                                 ; элемента
   SD4: RET 4                    ; очистить стек и возвратить
                                 ; управление
   CheckRange ENDS
              END

                   Пример на Турбо Ассемблере

     Приведем пример программы на Турбо Ассемблере, в котором используются все преимущества поддержки в TASM средств связи с Турбо Паскалем. Этот пример является модификацией предыдущей программы. 
     .MODEL large, PASCAL         ; модель кода Турбо Паскаля
     LOCALS @@                    ; определить локальный
                                  ; префикс меток
     .DATA                        ; сегмент данных
     EXTRN Buffer:WORD,COunt:WORD ; перменные Паскаля
     .CODE                        ; сегмент кода
           EXTRN RangeError: NEAR ; реализовано на Паскале
           PUBLIC CheckRange      ; реализованы здесь
     CheckRange PROC NEAR
         MOV BX,SP                ; получить указатель параметров
         MOV AX,SS:[BX+4]         ; загрузить Мin
         MOV DX,SS:[BX+2]         ; загрузить Мах
         XOR BX,BX                ; очистить индекс данных
         MOV CX,Count             ; загрузить счетчик
         JCXZ SD4                 ; пропустить если равно 0
   @@1:  CMP Data[Bx],AX          ; слишком мало?;
         JL  SD2                  ; да, перейти
         CMP Data[BX],DX          ; слишком велико?
         JLE SD3                  ; нет, перейти
   @@2:  PUSH AX                  ; сохранить регистры
         PUSH BX
         PUSH CX
         PUSH DX
         PUSH Data[BX]            ; передать выходящее за
                                  ; границы значение в Паскаль
         CALL RangeError          ; вызвать процедуру на Паскале
         POP DX                   ; восстановить регистры
         POP CX
         POP BX
         POP AX
   @@3:  INC BX                   ; установить указатель на
                                  ; следующий элемент
         INC BX
         LOOP SD1                 ; повторить цикл для каждого
                                  ; элемента
   @@4:  RET                      ; очистить стек и возвратить
                                  ; управление
   CheckRange ENDS
           END

                     Встроенный машинный код

     Для небольших подпрограмм на языке Ассемблера очень удобно использовать встроенние директивы и операторы Турбо Паскаля (операторы inline). Они позволяют вставлять инструкции машинного кода непосредственно в программу или текст блока, вместо того, чт
обы использовать объектный файл. 
                        Операторы Inline

     Оператор inline состоит из зарезервированного слова Inline, за которым следует однa или более встроенних записей (записей машинного кода), разделенных косой чертой и заключенных в круглые скобки: 
     inline(10/$2345/Count+1/Data-Offset);

     Оператор inline имеет следующий синтаксис:

              ХННННННННё  ХНННё     ЪДДДДДДДДДДДДї      ХНННё
 оператор  ДДі inline ГДі ( ГДДДДі запись во  ГДВДДДі ) ГДД
  inline      ФННННННННѕ  ФНННѕ    і встроеннем і і    ФНННѕ
                                і   і машинном   і і
                                і   і коде       і і
                                і   АДДДДДДДДДДДДЩ і
                                і                  і
                                і      ХНННё       і
                                АДДДДДДґ / іДДДДДДЩ
                                       ФНННѕ

     Каждый оператор inline состоит из необязательного спецификатора размера, < или >, и константы или идентификатора переменой, за которой следуют ноль или более спецификаторов смещения (см. описанный далее синтаксис). Спецификатор смещения состоит из +
 или -, за которым следует константа. 
                                   ЪДДДДДДДДДДДї
 запись во ДДВДДДДДДДДДДДДДДДДДДДДі константа ГДДДДДДДДДДДДДДДД
 встроеннем  і   ХНННё            АДДДДДДДДДДДЩ       
 машинном    ГДДі < ГДДДДДДґ                          і
 коде        і   ФНННѕ      і                          і
             і   ХНННё      і                          і
             ГДДі > ГДДДДДДЩ                          і
             і   ФНННѕ                                 і
             і  ЪДДДДДДДДДДДДДДДї                      і
             АДі идентификатор ГДВДДДДДДДДДДДДДДДДДДДДЩ
                і  переменной   і і                  
                АДДДДДДДДДДДДДДДЩ і                  і
                             ЪДДДДЩ                  АДДДДДДДДДї
                             і      ЪДДДДї   ЪДДДДДДДДДї       і
                             АДДДДДізнакГДДіконстантаіДДВДДДДЩ
                                   АДДДДЩ   АДДДДДДДДДЩ  і
                                і                         і
                                АДДДДДДДДДДДДДДДДДДДДДДДДДЩ

     Каждая запись inline порождает 1 байт или одно слово кода. Значения вычисляется, исходя из значения первой константы или смещения идентификатора переменной, к которому добавляется или из которого вычитается значение каждой из последующих констант. 
     Если запись в машинном коде состоит только из констант и если ее значение лежит в 8-битовом диапазоне (0..255), то она порождает один байт кода. Если значение выходит за границу 8-битового диапазона или если запись inline ссылается на переменную, то
 генерируется одно слово кода (младший байт следует первым). 
     Операции < и > могут использоваться для отмены автоматического выбора размера, который был описан ранее. Если оператор inline начинается с операции <, то в код включается только младший значаший байт значения, даже если это 16-битовое значение. Если
 оператор inline начинается с операции >, то в код включается всегда слово, даже если старший значащий байт равен 0. Например, оператор: 
     inline(<$1234/>$44);
 гененирует код длиной три байта: $34,$44,$00. 
     Значение идентификатора переменной в записи inline представляет собой адрес смещения переменной внутри ее базового сегмента. Базовый сегмент глобальных переменных (переменных, описанных на самом внешнем уровне в модуле или программе) и типизованные 
константы, доступ к которым организован через регистр DS, представляют собой сегмент данных. Базовый сегмент локальных переменных (переменных, описанных внутри подпрограммы) явяется сегментом стека. В этом случае смещение переменной относится к регистру 
ВР, что автоматически влечет за собой выбор сегмента стека. 
     Примечание: Регистры ВР, SР, 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 то генерируется один байт кода - инструкция СLI. 
     Процедуры или функции, описанные с помощью директив 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, Оffs и Seg. 


Яндекс цитирования