Мир программирования

 


Найти: на:


Меню
Партнеры
Счетчики
Реклама

ГЛАВА 23. РЕДАКТИРОВАНИЕ АССЕМБЛЕРНОГО КОДА


 Предыдущая страница     |     Следующая страница  
Добавить в избанное Обсудить в форуме Написать автору сайта Версия для печати

           Процедуры и  функции,  написанные  на  ассемблере  могут  быть  связаны  с  программами и модулями Turbo Pascal с помощью директивы компилятора  $L.  Исходный   файл   на   ассемблере   должен   быть ассемблирован в  объектный файл (.OВJ) с помощью Turbo Assembler. С программой или модулем можно связать несколько объектных  файлов  с помощью нескольких директив $L.

     Процедуры и функции,  написанные на  ассемблере,  должны  быть объявлены   в  программе  или  модуле  на  Паскале,  как  external, например:

            function LoCase(Ch: Char): Char; external;

      В соответствующей  исходной  программе  на   ассемблере,   все процедуры  и  функции  должны  быть расположены в сегменте с именем CODE или CSEG,  или в сегменте,  чье имя оканчивается _TEXT и имена внешних процедур и функций должны быть в директиве PUBLIC.

     Вы должны  быть  уверены,  что  процедура   или   функция   на ассемблере соответствует ее определению на Паскале по модели вызова (NEAR  или  FAR),  числу  параметров,  типу   параметров   и   типу результата.

     Исходный файл на ассемблере может объявлять инициализированные переменные  в  сегменте  с  именем  CONST  или в сегменте,  чье имя оканчивается _DATA и неинициализированные переменные в  сегменте  с именем  DATA или DSEG,  или в сегменте,  чье имя оканчивается _BSS. Такие переменные являются локальными в программе на Ассемблере  и к ним  нельзя обратиться из программы или модуля на Паскале.  Однако, они размещаются в том же  сегменте,  что  и  глобальные  переменные Паскаля и к ним можно обратиться через сегментный регистр DS.

     Ко всем  процедурам,  функциям  и  переменным,  объявленным  в программе или  модуле  на  Паскале и объявленным в секции interface используемых модулей,  можно обратиться из программы на  Ассемблере через директиву EXTRN.  Конечно,  Вы должны использовать корректный тип в описании EXTRN.

     Когда в  директиве $L появляется объектный файл,  Turbo Pascal преобразует этот файл из перемещаемого  формата  объектного  модуля Intel   (.OBJ)   в   свой   внутренний   перемещаемый  формат.  Это преобразование возможно только при соблюдении правил:

      - Все процедуры должны быть размещены в сегменте с именем CODE или CSEG,  или  в  сегменте,  чье  имя  оканчивается на _TEXT.  Все инициализированные локальные  переменные   должны   размещаться   в сегменте с  именем  CONST или сегменте с именем,  оканчивающимся на _DATA. Все неинициализированные локальные  переменные  должны  быть размещены в  сегменте  с  именем  DATA  или DSEG,  или в сегменте с именем, оканчивающимся на _BSS.  Все другие сегменты  игнорируются, также, как  и  директива GROUP.  Описания сегментов могут указывать выравнивание BYTE или WORD;  при  редактировании  кодовые  сегменты всегда выравнены  на  байт,  а  сегменты данных всегда выравнены на слово. Описание сегментов могут указывать PUBLIC и имя  класса,  но они игнорируются.

      - Turbo  Pascal игнорирует все данные для сегментов,  отличных от сегментов  кода  (CODE,   CSEG   или   xxxх_TEXT)   и   сегменты инициализированных данных (CONST или xxxx_DATA).  Когда объявляется переменные в сегменте неинициализированных данных (DATA,  DSEG  или xxxx_BSS), всегда  используйте  знак  (?)  для  указания  значений, например:

      Count   DW ?

     Buffer  DB 128 DUP(?)

      - Однобайтовые   ссылки  к  EXTRN  символам  недопустимы.  Это означает,  что операторы HIGH и LOW не могут  быть  использованы  с EXTRN символами.

[начало] [оглавление]

   

Turbo Assembler и Turbo Pascal.

      Turbo Assembler (TASM) позволяет просто  писать  программы  на ассемблере  и  связывать  их  с Вашими программами на Turbo Pascal. Turbo Assembler обеспечивает простую сегментацию,  модели памяти  и языковую поддержку для программистов на Turbo Pascal.

     Использование TPASCAL  с   директивой   .MODEL   устанавливает соглашения  о вызовах Паскаля,  определяет имена сегментов,  делает PUSH BP и MOV BP,SP и также устанавливает возврат через  POP  BP  и RET N (где N число байтов параметров).

     Директива PROC позволяет Вам определять ваши параметры  в  том же порядке,  как они определены в Вашей программе на Паскале.  Если Вы определяете функцию,  которая возвращает  строку,  помните,  что директива PROC   имеет   опцию   RETURNS,   которая  позволяет  Вам обращаться к указателю на временную строку в стеке без  учета числа байт параметров, добавленных к оператору RET.

     Пример кода с использованием директив .MODEL и PROC:

         .MODEL TPASCAL

        .CODE

 MyProc  PROC FAR I: 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;

[начало] [оглавление]

   

Примеры программ на Ассемблере.

      Здесь представлен   пример   модуля,   который  реализует  две программы  обработки  строк  на   Ассемблере.   Функция   UpperCase преобразует  все  символы  в  строке в заглавные.  Функция StringOf возвращает строку символов заданной длины.

      unit Strings;

     interfase

     function UpperCase(S: String): String;

     function StringOf(Ch: Char; Count: Byte): String;

     implementation

     {$L STRS}

     function UpperCase; external;

     function StringOf; external;

     end.

      Программа на  Ассемблере,  реализующая  функции  UpperCase   и StringOf,  показана  ниже.  Она  должна  быть ассемблирована в файл Strs.OBJ до  компиляции  модуля  Strings.  Заметим,  что  программы используют   дальнюю   модель  вызова,  так  как  они  объявлены  в интерфейсной части модуля.

     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          ; длину строки в CX

         xor    ch,ch

         jcxz   U3             ; пропустить, если строка пустая

     U1: lodsb                 ; загрузить символ

         cmp  al,'a'           ; пропустить если не 'a'..'z'

         jb   U2

         cmp  al,'z'

         ja   U2

         sub  al,'a'-'a'       ; преобразовать в заглавные

     U2: stosb                 ; запомнить в результат

         loop U1               ; цикл для всех символов

     U3: pop  ds               ; востановить ds

         pop  bp               ; востановить bp

         ret  4                ; удалить параметр и возврат

     UpperCase  ENDP

      ; procedure StringOf(var S :string; Ch : char; Count: byte)

      StrOfs     EQU   DWORD PTR [BP + 10]

      StrOfChar  EQU   BYTE PTR [BP + 8]

      StrOfCount EQU   BYTE PTR [BP + 6]

      StringOf   Proc FAR

          push  bp              ; сохранить 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   bp              ; восстановить bp

          ret   8               ; удалить параметр и возврат

      StringOf  ENDP

      CODE      ENDS

                END

     Для того чтобы ассемблировать пример и откомпилировать модуль, используйте команды:

      TASM STRS

     TPC strings

      Следующий пример показывает, как программа на Ассемблере может обращаться  к  программам  и переменным Паскаля.  Программа Numbers читает  до  100  целых  значений  и  затем  вызывает  процедуру  на Ассемблере, чтобы проверить диапазон каждого из этих значений. Если значение выходит из диапазона,  процедура  на  Ассемблере  вызывает процедуру на Паскале для его печати.

     program Numbers;

     {$L CHECK}

     var

        Buffer: array[1..100] of Integer;

        Count: 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(Buffer[Count]);

     {закончится, когда пользователь введет CTRL-Z или

      после 100 итераций}

        end;

        CheckRange(-10,10);

      end.

     Программа на  Ассемблере,  реализующая  процедуру  CheckRange, приведена ниже.

     Она должна быть ассемблирована в файл Check.OBJ  до компиляции программы Numbers. Заметим, что процедура использует ближнюю модель вызова, так как объявлена в программе.

      DATA SEGMENT WORD PUBLIC

          EXTRN   Buffer: WORD, Count: WORD;   ;Переменные Паскаля

     DATA ENDS

     CODE SEGMENT BYTE PUBLIC

          ASSUME CS: CODE, DS: Buffer

          EXTRN  RangeError: NEAR        ;реализован в Паскале

          PUBLIC CheckRange              ;реализован здесь

     CheckRange PROC NEAR

          mov   bx,sp           ;получить указатель параметров

          mov   ax,ss:[bx+4]    ;загрузить Min

          mov   dx,ss:[bx+2]    ;загрузить Max

          xor   bx,bx           ;очистить индекс данных

          mov   cx,Count        ;загрузить Count

          jcxz  SD4             ;пропустить если 0

     SD1: cmp   Buffer[BX],AX   ;слишком мал?

          jl    SD2             ;да, перейти

          cmp   Buffer[BX],DX   ;слишком велик?

          jle   SD3             ;нет, перейти

     SD2: push  ax              ;сохранить регистр

          push  bx

          push  cx

          push  dx

          push  Buffer[BX]      ;передать значение в Паскаль

          CALL  RangeError      ;вызвать процедуру Паскаля

          pop   dx              ;восстановить регистры

          pop   cx

          pop   bx

          pop   ax

     SD3: inc   BX              ;перейти к следующему элементу

          inc   BX

          loop  SD1             ;цикл для каждого элемента

     SD4: ret                   ;возврат

      CheckRange ENDP

     CODE       ENDS

     END

 [начало] [оглавление]

 

Пример на Turbo Assembler.

      Здесь представлена версия предыдущей программы  на Ассемблере, которая показывает  преимущества  применения  Turbo  Assembler  при стыковке с Паскалем:

      .MODEL  TPASCAL         ;модель кода Турбо-Паскаля

     LOCALS  @@              ;определить префикс локальных меток

     .DATA                   ;сегмент данных

     EXTRN   Buffer: WORD, Count: WORD;      ;Переменные Паскаля

     .CODE                          ;сегмент кода

     EXTRN  RangeError: NEAR        ;реализован в Паскале

     PUBLIC CheckRange              ;реализован здесь

 ChechRange   Proc NEAR Min : WORD, Max : WORD

      mov   ax,Min        ;загрузить Min в ax

     mov   dx,Max        ;загрузить Max в dx

     xor   bx,bx         ;очистить индекс данных

     mov   cx,Count      ;загрузить Count

     jcxz  @@4           ;пропустить если 0

@@1: cmp   ax,Buffer[BX] ;слишком мал?

     jg    @@2           ;да, перейти на @@2

     cmp   dx,Buffer[BX] ;слишком велик?

     jge   @@3           ;нет, перейти на @@3

@@2: push  ax            ;сохранить регистр

     push  bx

     push  cx

     push  dx

     push  Buffer[BX]    ;передать значение в Паскаль

     call  RangeError    ;вызвать процедуру Паскаля

     pop   dx            ;восстановить регистры

     pop   cx

     pop   bx

     pop   ax

@@3: inc   BX            ;перейти к следующему элементу

     inc   BX

     loop  @@1           ;цикл для каждого элемента

@@4: ret                 ;возврат

 CheckRange ENDP

     END

      Заметим, что  .MODEL  TPASCAL  Turbo  Assembler  автоматически генерирует код входа до первой инструкции и код выхода для RET.

[начало] [оглавление] 

 

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

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

[начало] [оглавление] 

 

Оператор Inline.

      Оператор Inline  состоит  из зарезервированного слова Inline и следующих за ним одного или более  элементов,  разделенных  слэшами (/) и заключенных в скобки:

      inline(10/$2345/Count+1/Data-OffSet);

      Синтаксис оператора Inline:

                     ┌──────┐   ┌─┐     ┌──────────────┐    ┌─┐

оператор Inline ───Ў│inline├──Ў│(├────Ў│элемент inline├─┬─Ў│)├──Ў

                    └──────┘   └─┘  °  └──────────────┘ │  └─┘

                                    │       ┌─┐         │

                                    └───────┤/│ў────────┘

                                            └─┘

      Каждый элемент   оператора   Inline   состоит   из  возможного указателя  размера,  <  или  >  и  константы   или   идентификатора переменной  идущими  за  0  или  более  указателями  смещения  (см. синтаксис  ниже).  Указатель  смещения  состоит  из  +  или   -   с константой.

 элемент inline

 │              ┌────────┐

 └─┬───────────Ў│сonstant│─────────────────────────────────────Ў

   │       °    └────────┘                                  °

   │  ┌─┐  │                                                │

   ├─Ў│<├──┤                                                │

   │  └─┘  │                                                │

   │  ┌─┐  │                                                │

   ├─Ў│<├──┘                                                │

   │  └─┘                                                   │

   │  ┌────────────────────────┐                            │

   └─Ў│идентификатор переменной├─┬──────────────────────────┘

      └────────────────────────┘ │                        °

                                 │   ┌────┐  ┌─────────┐  │

                                 └──Ў│знак├─Ў│константа├─┬┘

                                   ° └────┘  └─────────┘ │

                                   └─────────────────────┘

      Каждый элемент оператора Inline генерирует 1 байт или  1 слово кода.   Значения  вычисляются  из  значения  первой  константы  или смещения идентификатора  переменной,  к  которому добавлено/вычтено значение каждой из констант, которые следуют за ним.

     Элемент Inline генерирует 1 байт кода,  если он состоит только из  констант  и  если  их  значения  внутри  8-и битового диапазона (0..255).  Если значение выходит за 8-и битовый диапазон,  или если элемент  Inline  ссылается к переменной - генерируется код длиной в одно слово (меньший значащий байт стоит первым).

     Операторы <  и  >  могут  быть  использованы  для того,  чтобы перекрыть  автоматический  выбор  размера,  описанный  ранее.  Если элемент  Inline  начинается  с  оператора  <,  только  меньший байт значения будет кодироваться,  даже если это 16-и битовое  значение. Если элемент Inline начинается с оператора >, то будет кодироваться слово, даже если наибольший байт равен 0. Например, оператор

      Inline(<$1234/>$44);

генерирует 3 байта кода: $34,$44,$00.

     Значение идентификатора    переменной    в   элементе   Inline представляет собой смещение адреса переменной  внутри  ее  базового сегмента.   Базовой  сегмент  глобальной  переменной  -  переменной объявленной  на  внешнем  уровне  в  программе   или   модуле   или типированной константы - это сегмент данных,  к которому обращаются через  регистр  DS.  Базовый   сегмент   локальной   переменной   - переменной,  объявленной  внутри текущей подпрограммы - это сегмент стека.  В этом случае переменная смещена относительно регистра  BP, который автоматически ссылается на сегмент стека.

      Примечание: Регистры  BP,  SP,  SS  и DS должны быть сохранены оператором Inlile; все остальные регистры могут изменяться.

      Следующий пример оператора 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, Count[BP]}

           $8B/$86/Data/       {MOV AX, Data[BP]}

           $FC/                {CLD}

           $F3/$AB);           {REP STOSW}

     end;

      Оператор Inline   может   быть   свободно   смешан  с  другими операторами в операторной части блока.

 [начало] [оглавление]

 

Директива Inline.

      Директива Inline позволяет Вам  писать  процедуры  и  функции, вместо  которых  вставляется  данная  последовательность инструкций машинного кода,  в том  месте,  где  они  вызываются.  Они  подобны макросам  в  Ассемблере.  Синтаксис директивы Inline такой же как и оператора Inline:

                      ┌────────────────┐

директива Inline ───Ў│оператор  inline│

                     └────────────────┘

      Когда вызывается обычная процедура или функция (включая  и те, которые  содержат  операторы  Inline),  компилятор  генерирует код, который  помещает  параметры  (если  они  есть)  в  стек  и   затем генерирует  инструкцию  CALL  для  вызова  процедуры  или  функции. Однако, когда вы вызываете Inline процедуру или функцию, компилятор вместо  генерации  CALL  вставляет  код  из  этой директивы Inline. Короткий пример двух процедур Inline:

      procedure DisableInterrupts; Inline($FA);      {CLI}

     procedure EnableInterrupts; Inline($FB);       {STI}

      Когда вызывается  DisableInterrupts - генерируется 1 байт кода - инструкция CLI.

     Процедуры и  функции,  объявленные с директивой Inline,  могут иметь параметры;  однако,  к параметрам нельзя обращаться по  имени (но  к  другим  переменным  можно).  Также  из-за  того,  что такие процедуры и  функции  в   действительности   макро,   в   них   нет автоматического  входного  и  выходного  кода,  и  не  должно  быть инструкции возврата.

     Следующая функция   умножает   два  целых  значения,  создавая результат типа LongInt:

      function LongMul(X, Y: Integer): Longint;

     Inline (

         $5A/          {POP AX; POP X}

         $5E/          {POP DX; POP Y}

         $F7/$EA);     { IMUL DX; DX : AX = X * Y}

      Заметьте отсутствие   входного  и  выходного  кода  и  пропуск инструкции выхода.  Здесь они не требуются,  так как  эти  4  байта вставляются в остальной код при вызове LongMul.

     Директива Inline  применяется  только   для   очень   коротких процедур и функций ( < 10 байт).

     Так как Inline процедуры и функции - это макро,  они не  могут использоваться как аргументы оператора @ и функций Addr, Ofs и Seg.

[начало] [оглавление]    

 


Предыдущая страница     |     Следующая страница


Добавить в избанное Обсудить в форуме Написать автору сайта Версия для печати

Опрос

Конкурсы
Реклама

 

Web дизайн: Бурлаков Михаил    

Web программирование: Бурлаков Михаил

Используются технологии uCoz