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

 


Найти: на:


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

ЧАСТЬ 4. ИСПОЛЬЗОВАНИЕ TURBO PASCAL С ЯЗЫКОМ АССЕМБЛЕРА 

ГЛАВА 22. ВСТРОЕННЫЙ АССЕМБЛЕР


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

           Встроенный Ассемблер  Turbo  Pascal   позволяет   Вам   писать ассемблерный код   для   8086/8087  и  80286/80287  прямо  в  Ваших программах на  Паскале.  Конечно,  Вы  еще  можете  преобразовывать ассемблерные инструкции  в машинный код вручную для использования в операторах inline  или  подредактироватьь   .OBJ   файлы,   которые содержат external  процедуры  и функции,  когда Вы хотите смешивать Паскаль и Ассемблер.

      Встроенный Ассемблер    реализует     большое     подмножество синтаксиса, поддерживаемого   Turbo  Assembler  и  макроассемблером Microsoft. Встроенный Ассемблер поддерживает все коды операций 8086 /8087 и   80286/80287   и   почти  все  операторы  выражений  Turbo Assembler.

      За исключением DB,  DW,  DD (определить байт,  слово и двойное слово) ни  одна из директив Turbo Assembler,  таких как EQU,  PROC, STRUC, SEGMENT и MACRO не  поддерживается  встроенным  Ассемблером. Однако операции,  поддерживаемые  директивами  Turbo Assembler,  во многом соответствуют  соответствующим  конструкциям  Turbo  Pascal. Например большинство  директив EQU соответствует объявлениям const, var и type в Turbo Pascal, директива PROC соответствует объявлениям procedure и function, а директива STRUC соответсвует типам record в Turbo Pascal.  В  действительности  можно   думать   о   встроенном Ассемблере Turbo  Pascal,  как  о  компиляторе  с  языка Ассемблер, который использует синтаксис Паскаля для всех объявлений.

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

 

Оператор asm.

      К встроенному   Ассемблеру   обращаются  через  оператор  asm. Синтаксис оператора asm:

      asm AsmStatement < Separator AsmStatement > end

где AsmStatement - это ассемблерный оператор,  а  Separator  - это ";",  новая строка или комментарий Паскаля.  Несколько примеров оператора asm:

      if EnableInts then

       asm

         sti

       end

       else

       asm

         cli

       end;

      asm

       mov ax,Left; xchg ax,Right; mov Left,ax;

     end;

     asm

       mov    ah,0

       int    16H

       mov    CharCode,al

       mov    ScanCode,ah

     end;

      asm

       push   ds

       lds    si,Source

       les    di,Dest

       mov    cx,Count

       cld

       rep    movsb

       pop    ds

     end;

      Заметим, что  на  одной  строке  можно   поместить   несколько операторов Ассемблера,  разделенных ";". Заметим так же, что ";" не требуется между двумя ассемблерными операторами, если они на разных строках. Наконец  заметим,  что  ";"  не указывает,  что оставшаяся часть строки - комментарий,  т.к. комментарий должен быть написан в стиле Паскаля, используя {} и (* *).

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

 

Использование регистров.

      Правила использования регистров в операторе asm  в общем такие же, как  и  в  external процедурах и функциях.  Оператор asm должен сохранять регистры BP,  SP,  SS и  DS  и  может  свободно  изменять регистры AX, BX, CX, DX, SI, DI, ES и Flags. На входе оператора asm BP указывает на текущий стек,  SP указывает на  вершину  стека,  SS содержит сегментный адрес сегмента стека,  а DS содержит сегментный адрес сегмента данных. За исключением BP, SP, SS и DS, оператор asm не должен делать предположений о содержимом остальных регистров при входе в оператор.

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

 

Синтаксис ассемблерных операторов.

      Синтаксис ассемблерного оператора:

     [ Label ":" ] < Prefix > [ Opcode [ Operand < "," Operand > ] ]

где Label - идентификатор метки, Prefix - код префикса, Opcode - директива или инструкция  Ассемблера  и  Opеrand  -  ассемблерное выражение.

      Комментарии разрешены  между  ассемблерными операторами, но не внутри их. Например, это разрешено:

      asm

       mov   ax,1        {Initial value}

       mov   cx,100      {Count}

     end;

      а это нет:

      asm

       mov   {Initial value} ax,1

       mov   cx,{Count} 100

     end;

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

   

Метки.

      Метки определяются   в  Ассемблере  так  же,  как  в  Паскале, записывая идентификатор метки с двоеточием до оператора;  как  и  в Паскале, метки,  определенные  в  Ассемблере,  должны объявляться в декларативной части label в блоке,  содержащем оператор asm. Однако существует одно исключение из этого правила: локальные метки.

      Локальные метки - это метки, которые начинаются с @. Поскольку @ не может быть частью идентификатора Паскаля,  использование таких локальных меток  допускается  только внутри оператора asm,  который определяет их (т.е.  сфера действия локальной метки расширяется  от ключевого слова  asm  до  ключевого  слова  end для этого оператора asm).

      Примечание: В отличие от обычных  меток,  локальные  метки  не объявляются в разделе объявления label до их использования.

      Идентификатор локальной   метки   состоит   из   символа  @  с последующей одной или более буквой A..Z,  цифр 0..9, "_" или @. Как для всех меток, после идентификатора идет ":".

     Следующий фрагмент   программы   демонстрирует   использование локальных и глобальных меток в операторе asm:

      label Start, Stop;

       ...

     begin

       asm

         Start:

         ...

           jz      Stop

         @1:

           ...

           loop    @1

       end;

       asm

         @1:

           ...

           jc      @2

           ...

           jmp    @1

         @2:

       end;

       goto Start;

       Stop:

     end;

        Заметим, что нормальная метка  может  быть  определена  внутри оператора asm и использована вне оператора asm, и наоборот. Заметим также, что одно и то же имя локальной метки может  использоваться в различных операторах asm.

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

 

Префикс.

      Встроенный ассемблер поддерживает встроенные префиксы:

 ─────────────────────────────────────────────────

     LOCK              Захват шины

     REP               Повтор строковой операции

     REPE/REPZ         Повтор строковой операции пока =/0

     REPNE/REPNZ       Повтор строковой операции пока (не =)/(не 0)

     SEGCS             Перекрытие CS (сегмент кода)

     SEGDS             Перекрытие DS (сегмент данных)

     SEGES             Перекрытие ES (экстра сегмент)

     SEGSS             Перекрытие SS (сегмент стека)

──────────────────────────────────────────────────

      С ассемблерной инструкцией могут  быть  указаны  0  или  более префиксов. Например:

      asm

       rep    movsb

       SEGES  lodsw

       SEGCS  mov ax,[bx]

       SEGES

       mov    WORD PTR [DI],0

     end;

      Заметим, что  префикс  может быть указан без кода инструкции в том же операторе -  в  этом  случае  префикс  воздействует  на  код инструкции в следующем ассемблерном операторе.

      Код инструкции  очень  редко  имеет более одного префикса и не может быть указано более  3  префиксов  (LOC,  затем  SEGxx,  затем REPxx). Будьте внимательны при использовании нескольких префиксов - их порядок важен и некоторые процессоры 80х86 не могут обрабатывать все комбинации  правильно.  Например,  8086  или 8088 помнят только префикс REPxx,  если  в  середине  строковой  инструкции  возникает прерывание. Поэтому  префиксы  LOC  и  SEGxx  не могут быть надежно закодированы до REPxx в строковой инструкции.

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

 

Коды инструкций.

      Встроенный Ассемблер    поддерживает   все   коды   инструкций 8086/8087 и 80286/80287. Коды 8087 допустимы только в состоянии {$N +} (числовой  процессор  разрешен),  коды  80286 допустимы только в состоянии {$G+} ( генерация кода 80286  разрешена),  и  коды  80287 разрешены только в состоянии {$G+,N+}.

      Полное описание этих инструкций см.  в руководствах по 80х86 и 80х87.

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

 

Размер инструкции RET.

      Инструкция RET   генерирует   ближний   или   дальний  возврат в зависимости от модели вызова текущей процедуры или функции.

      procedure NearProc; near;

     begin

       asm

         ret     {генерирует ближний вызов}

       end;

     end;

      procedure FarProc; far;

     begin

       asm

         ret     {генерирует дальний вызов}

       end;

     end;

      С другой стороны,  инструкции RETN и  RETF  всегда  генерируют ближний возврат и дальний возврат, вне зависимости от модели вызова текущей процедуры или функции.

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

 

Автоматический размер перехода.

       Если не   было   указано   противное,   встроенный   Ассемблер оптимизирует инструкции  перехода,  автоматически   выбирая   самую короткую, и,  следовательно, самую эффективную инструкцию перехода. Автоматический выбор инструкции перехода применяется  к  инструкции безусловного перехода JMP и ко всем инструкциям условного перехода, когда назначение - метка (а не процедура или функция).

      Для инструкции безусловного перехода JMP  встроенный Ассемблер генерирует короткий   переход  (1  байт  кода  операции  и  1  байт смещения), если расстояние до  метки  назначения  внутри  диапазона от -128  до  127  байт;  иначе генерируется ближний переход (1 байт кода операции и 2 байта смещения).

      Для инструкции  условного   перехода   генерируется   короткий переход (1  байт кода и 1 байт смещения),  если расстояние до метки назначения от -128 до 127 байт; иначе генерируется короткий переход с обратным  условием,  который  обходит  ближний  переход  на метку назначения (5 байт в итоге). Например оператор

      JC Stop

где Stop не внутри диапазона короткого перехода, преобразуется в машинный код

      jnc Skip

     jmp Stop

     Skip:

      Переходы на   точки  входа  процедур  и  функций  всегда  либо ближние, либо дальние,  и никогда не короткие,  а условные перехода на процедуры и функции не разрешены.  Вы можете указать встроенному Ассемблеру генерировать безусловный ближний или дальний  переход на метку, используя   конструкцию  NEAR  PTR  или  FAR  PTR.  Например операторы

      jmp NEAR PTR Stop

     jmp FAR PTR Stop

всегда генерируют  ближний  и  дальний переходы соответственно, даже если Stop внутри диапазона короткого перехода.

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

 

Директивы Ассемблера.

     Встроенный Ассемблер  Turbo  Pascal  поддерживает  3 директивы Ассемблера:

      DB, DW, DD (определить байт, слово и двойное слово)

      Они генерируют данные,  соответствующие операндам, разделенным запятыми, в этой директиве.

      Директива DB   генерирует   последовательность   байт.  Каждый операнд может быть константным выражением со значением от  -128  до 255 или   строкой   символов  любой  длины.  Константное  выражение генерирует 1 байт кода, а строка генерирует последовательность байт со значениями, соответствующими ASCII кода каждого символа.

      Директива DW   генерирует   последовательность   слов.  Каждый операнд может быть константным выражением со значением от -32768 до 65535 или  адресным выражением.  Для адресного выражения встроенный Ассемблер генерирует ближний указатель,  который содержит  смещение этого адреса.

      Директива DD   генерирует   последовательность  двойных  слов. Каждый операнд может быть константным выражением  со  значением  от -2,147,483,648 до   4,294,967,295   или  адресным  выражением.  Для адресного  выражения  встроенный   Ассемблер   генерирует   дальний указатель, который содержит и смещение и сегментную часть адреса.

      Данные, генерируемые директивами DB,  DW, DD всегда хранятся в кодовом сегменте так же,  как код, генерируемый другими операторами встроенного Ассемблера. Чтобы генерировать неинициализированные или инициализированные данные, в сегменте данных Вы должны использовать обычные объявления Var или Const Паскаля.

     Примеры директив DB, DW, DD:

     asm

       DB   0FFH

       DB   0,99

       DB   'A'

       DB   'Hello world...',0DH,0AH

       DB   12,"Turbo Pascal"

       DW   0FFFFH

       DW   0,9999

       DW   'A'

       DW   'BA'

       DW   MyVar

       DW   MyProc

       DD   0FFFFFFFFH

       DD   0,999999999

       DD   'A'

       DD   'DCBA'

       DD   MyVar

       DD   MyProc

     end;

     Примечание: В Turbo Assembler, когда идентификатор стоит перед директивой DB,  DW и  DD,  это  приводит  к  объявлению  переменной размером в байт,  слово или двойное слово по адресу этой директивы. Например, Turbo Assembler разрешает:

      ByteVar   DB    ?

     WordVar   DW    ?

     ...

               mov   al,ByteVar

               mov   bx,WordVar

      Встроенный Ассемблер   не   поддерживает   такое    объявление переменных. В Turbo Pascal единственный символ,  который может быть определен в операторе  встроенного  Ассемблера  -  это  метка.  Все переменные должны   быть   объявлены  с  использованием  синтаксиса Паскаля и предыдущий пример соответствует:

      var

       ByteVar: Byte;

       WordVar: Word;

       ...

     asm

       mov   al,ByteVar

       mov   bx,WordVar

     end;

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

 

Операнды.

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

      Внутри операндов  следующие  зарезервированные  слова имеют во встроенном Ассемблере предопределенный смысл:  

     AH           CL           FAR           SEG

     AL           CS           HIGH          SHL

     AND          CX           LOW           SHR

     AX           DH           MOD           SI

     BH           DI           NEAR          SP

     BL           DL           NOT           SS

     BP           DS           OFFSET        ST

     BX           DWORD        OR            TBYTE

     BYTE         DX           PTR           TYPE

     CH           ES           QWORD         WORD

                                             XOR

      Зарезервированные слова    всегда    имеют    приоритет    над идентификаторами пользователя. Например фрагмент кода:

      VAR

       Ch: Char;

       ...

     asm

       mov   ch,1

     end;

      загружает 1 в регистр CH,  а не в переменную Ch. Для доступа к символу, определенному пользователем с тем  же  именем,  Вы  должны использовать & для перекрытия оператора:

      asm

       mov   &ch,1

     end;

      Мы настоятельно    рекомендуем    Вам   избегать   определения идентификаторов с теми же именами,  что и  зарезервированные  слова встроенного Ассемблера,    поскольку    это    может   привести   к труднонаходимым ошибкам.

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

 

Выражения.

      Встроенный Ассемблер  вычисляет все выражения,  как 32-битовые целые выражения;  он не поддерживает значения с плавающей точкой  и строковые значения, за исключением строковых констант.  

     Выражения встроенного   Ассемблера   строятся   из   элементов выражения и операторов и каждое выражение связано с классом и типом выражения. Эти концепции объясняются в следующих разделах.

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

Различия между выражениями Паскаля и Ассемблера.

      Наиболее важное   отличие   между   выражениями   Паскаля    и встроенного Ассемблера в том,  что выражения встроенного Ассемблера должны разрешаться в константное  значение,  т.е.  значение  должно быть вычислено во время компиляции. Например, для объявлений

     const

       X = 10;

       Y = 20;

     var

       Z: Integer;

     следующий оператор разрешен:

     asm

       mov   Z,X+Y

     end;

     Поскольку X  и  Y - константы,  выражение X+Y наиболее удобный способ написания константы 30,  а результирующая  инструкция  будет пересылать непосредственное значение 30 в переменную Z,  размером в слово. Но если Вы объявите X и Y как переменные:

      var

       X, Y: Integer;

встроенный Ассемблер не сможет вычислить значение X+Y во время компиляции. Встроенный    Ассемблер   для   пересылки   суммы   X+Y генерирует:

      asm

       mov   ax,X

       add   ax,Y

       mov   Z,ax

     end;

      Другое важное отличие между выражениями Паскаля  и встроенного Ассемблера в способе интерпретации переменных.  В выражении Паскаля ссылка на переменную интерпретируется как содержимое  переменной, а в выражении  встроенного  Ассемблера  ссылка на переменную означает адрес этой переменной.  Например,  в Паскале выражение X+4, где Х - переменная, означает содержимое X+4, а во встроенном Ассемблере это означает содержимое слова с адресом на 4 байта выше,  чем адрес  Х. Так даже если Вы можете написать

      asm

       mov   ax,X+4

     end;

этот код не загружает значение Х+4 в AX,  а загружает значение слова, хранящегося по адресу на 4 байта выше Х.  Корректный  способ добавить 4 к содержимому Х:

      asm

       MOV   AX,X

       ADD   AX,4

     end;

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

Элементы выражения.

      Основные элементы  выражения  -  это  константы,  регистры   и символы.

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

Константы.

      Встроенный Ассемблер поддерживает 2  типа  констант:  числовые константы и строковые константы.

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

Числовые константы.

      Числовые константы должны быть целыми  и  их  значения  должны быть в диапазоне от -2,147,483,648 до 4,294,967,295.

      Числовые константы по умолчанию используют десятичную нотацию, но встроенный Ассемблер так же поддерживает двоичную, 8-ричную и 16 -ричную нотации.  Двоичная  нотация  выбирается  написанием В после числа, 8-ричная нотация выбирается написанием буквы О после числа и 16-ричная нотация  выбирается  написанием  Н  после  числа или $ до числа.

      Примечание: Суффиксы B,  O,  H не поддерживаются в  выражениях Паскаля. Выражения  Паскаля допускают только десятичную нотацию (по умолчанию) и 16-ричную нотацию (используя префикс $).

      Числовые константы должны начинаться с одной из цифр  0..9 или символа $;  так,  когда  Вы  пишите 16-ричную константу,  используя суффикс Н,  требуется дополнительный 0 в начале числа,  если первая значащая цифра  одна  из  16-ричных цифр А..F.  Например,  0BAD4H и $BAD4 - 16-ричные константы,  а BAD4H  -  идентификатор,  поскольку начинается с буквы, а не с цифры.

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

Строковые константы.

      Строковые константы  должны  быть  заключены  в  кавычки   или апострофы. Две  последовательные  "" или '' внутри строки считаются как один символ. Примеры строковых констант:

      'Z'

     'Turbo Pascal'

     "That's all folks"

     '"That"s all folks,"he said.'

     '100'

     '"'

     "'"

      Заметим, что  в  4  строке  использовались  2  апострофа   для указания одиночного символа "апостроф".

      В директивах DB разрешены строковые константы любой длины. Это приводит к распределению последовательности байт,  содержащей ASCII значения символов   строки.   Во   всех  других  случаях  строковая константа не может быть длиннее 4  символов  и  означает,  числовое значение, которое   может   использоваться  в  выражении.  Числовое значение строковой константы вычисляется как

      Ord(Ch1) + Ord(Ch2) shl 8 + Ord(Ch3) shl 16 + Ord(Ch4) shl 24

где Ch1 наиболее правый (последний) символ,  а Ch4 -  наиболее левый (первый) символ.  Если строка короче 4 символов,  самые левые символы устанавливаются в 0.  Несколько примеров строковых констант и соответствующих числовых значений:

     'a'       00000061H

     'ba'      00006261H

     'cba'     00636261H

     'dcba'    64636261H

     'a '      00006120H

     '   a'    20202061H

     'a'*2     000000E2H

     'a'-'A'   00000020H

     not'a'    FFFFFF9EH

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

 

Регистры.

      Следующие зарезервированные символы означают регистры 8086:

      16-битный общего назначения     AX  BX  CX  DX

     8-битный младший                AL  BL  CL  DL

     8-битный старший                AH  BH  CH  DH

     16-битный указатель или индекс  SP  BP  SI  DI

     16-битный сегментный регистр    CS  DS  SS  ES

     регистр стека 8087              ST

      Когда операнд состоит только из имени регистра,  он называется регистровым операндом.  Все регистры могут  быть  использованы  как регистровые операнды.   Кроме   того   некоторые   регистры   могут использоваться в других констекстах.

      Базовые регистры (BX,  BP) и индексные регистры (SI, DI) могут быть написаны   внутри   []  для  указания  индексации.  Допустимые комбинации базовых/индексных регистров: [BX], [BP], [SI], [DI], [BX +SI], [BX+DI], [BP+SI], [BP+DI].

     Сегментные регистры  (ES,  CS,  SS,  DS)  могут использоваться вместе с  ":"  как  перекрытие  сегмента  для  указания   сегмента, отличного от того, который процессор выбирает по умолчанию.

      Символ S  означает  самый  верхний  регистр из регистров стека 8087. Каждый  из  8  регистров  с  плавающей  точкой   может   быть использован с  помощью  ST(x),  где  х  -  константа  от  1  до  7, указывающая на смещение от вершины стека.

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

 

Символы.

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

      @Code             @Data              @Result

      Символы @Code и @Data представляют  текущие  сегменты  кода  и данных. Они  могут быть использованы только совместно с операторами SEG:

      asm

       mov   ax,SEG @Data

       mov   ds,ax

     end;

      Символ @Result представляет переменную с  результатом  функции внутри операторной части функции. Например, в функции

      function Sum(X, Y: Integer): Integer;

     begin

       Sum := X + Y;

     end;

оператор, который  назначает результат значения функции в Sum, будет использовать переменную @Result:

     function Sum(X, Y: Integer): Integer;

     begin

       asm

         mov   ax,X

         add   ax,Y

         mov   @Result,AX

       end;

     end;

     Следующие символы не  могут  быть  использованы  в  выражениях встроенного Ассемблера:

      - Стандартные процедуры и функции (например Writeln, Chr).

      - Специальные массивы Mem, MemW, MemL, Port, PortW.

      - Константы строковые, с плавающей точкой и типа множество.

      - Процедуры и функции, объявленные с директивой inline.

      - Метки, которые не объявлены в текущем блоке.

      - Символ @Result вне функции.

      Таблица 22.1  суммирует значение,  класс и тип различных видов символов, которые могут  использоваться  в  выражениях  встроенного Ассемблера (типы и классы выражений описаны в следующем разделе).

           Таблица 22.1. Значения, классы и типы символов.

 ──────────────────────────────────────────────────

   Символ         Значение           Класс            Тип

──────────────────────────────────────────────────

   Метка        Адрес метки         Память            SHORT

   Константа    Значение            Непосредственный  0

                константы

   Тип          0                   Память            Размер типа

   Поле         Смещение поля       Память            Размер типа

   Переменная   Адрес               Память            Размер типа

                переменной

   Процедура    Адрес процедуры     Память            NEAR или FAR

   Функция      Адрес функции       Память            NEAR или FAR

   Модуль       0                   Непосредственный  0

   @Code        Адрес сегмента      Память            0FFF0H

                кода

   @Data        Адрес сегмента      Память            0FFF0H

                данных

   @Result      Смещение переменной Память            Размер типа

                результата

──────────────────────────────────────────────────

     Локальные переменные (переменные,  объявленные в процедурах  и функциях) всегда распределяются в стеке и используются относительно SS:BP, а значение локальной переменной - это его смещение со знаком от SS:BP.  Ассемблер  автоматически  добавляет  [BP]  в  ссылки  на локальные переменные. Например, объявление

      procedure Test;

     var

       Count: Integer;

      и инструкция

      asm

       mov   ax,Count

     end;

      ассемблируется в MOV AX, [BP-2]

      Встроенный Ассемблер всегда интерпретирует var параметр как 32 -битный указатель  и  размер  var  параметра   всегда   4   (размер 32-битного указателя).  В  Паскале  синтаксис  для  доступа  к  var параметру и параметру  значению  одинаков.  Не  так  во  встроенном Ассемблере. Поскольку  var  параметры в действительности указатели, Вы должны интерпретировать их так во  встроенном  Ассемблере.  Так, чтобы обратиться к содержимому var параметра, Вы вначале загружаете 32-битный указатель,  а затем обращаетесь к памяти,  на которую  он указывает. Например, если X и Y - var параметры функции Sum, то:

      function Sum(var X, Y: Integer): Integer;

     begin

       asm

         les   bx,X

         mov   ax,es:[bx]

         les   bx,Y

         add   ax,es:[bx]

         mov   @Result,ax

       end;

     end;

      Некоторые символы,   типы   и  переменные  записей,  позволяют обращаться к элементам структуры с  использованием  селектора  ".". Например, для объявлений:

      type

       Point = record

         X, Y: Integer;

       end;

       Rect = record

         A, B: Point;

       end;

     var

       P: Point;

       R: Rect;

следующие конструкции можно использовать для доступа к полям в переменных P и R:

      asm

       mov   ax,P.X

       mov   dx,P.Y

       mov   cx,R.A.X

       mov   bx,R.B.Y

     end;

      Идентификатор типа можно использовать для  создания переменных "на лету".   Каждая   инструкция   ниже  генерирует  машинный  код, загружающий содержимое ES:[DI+4] в AX.

      asm

       mov   ax,(Rect PTR es:[di]).B.X

       mov   ax,Rect(es:[di]).B.X

       mov   ax,es:Rect[di].B.X

       mov   ax,Rect[es:di].B.X

       mov   ax,es:[di].Rect.B.X

     end;

      Сфера действия символа  типа  поля  или  записи  -  это  сфера действия записи или объекта этого типа.  Кроме того,  идентификатор модуля открывает сферу действия определенного модуля  так  же,  как полный квалифицированный идентификатор в Паскале.

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

 

Классы выражений.

      Встроенный Ассемблер делит выражения на  3  класса:  регистры, ссылки на память и непосредственные значения.

      Выражение, которое  состоит  только  из  имени  регистра - это регистровое выражение.  Пример регистрового выражения - это AX, CL, DI и  ES.  Используемые  как операнды,  регистровые выражения прямо ассемблируются в  инструкции,  которые  воздействуют  на   регистры процессора.

      Выражения, которые означают положение памяти,  - это ссылки на память; метки, переменные, типированные константы и функции Паскаля относятся к этой категории.

      Выражения, которые  не  относятся  к  регистрам и не связаны с положением в памяти,  - это непосредственные значения;  эта  группа включает нетипированные константы и типы Паскаля.

      Непосредственные значения   и   ссылки  к  памяти  приводят  к генерации различного  кода,  когда   используются   как   операнды. Например:

      const

       Start = 10;

     var

       Count: Integer;

     ...

     asm

       mov   ax,Start                 { MOV AX,xxxx }

       mov   bx,Count                 { MOV BX,[xxxx] }

       mov   cx,[Start]               { MOV CX,[xxxx] }

       mov   dx,OFFSET Count          { MOV DX,xxxx }

     end;

      Поскольку Start  -  это непосредственное значение,  первая MOV ассемблируется в инструкцию пересылки  непосредственного  значения. Вторая MOV транслируется в инструкцию пересылки памяти,  т.к. Count - это  ссылка  на  память.  В  третий  MOV  []   используются   для преобразования Start  в  ссылку  на  память (в этом случае слово со смещением 10 в сегменте данных) и в четвертой MOV  оператор  OFFSET используется для  преобразования  Count в непосредственное значение (смещение Count в сегменте данных).

      Как Вы видите,  [] и OFFSET дополняют друг друга.  В  терминах результирующего машинного  кода  следующий  оператор  asm идентичен двум первым строкам предыдущего оператора asm:

      asm

       mov   ax,OFFSET [Start]

       mov   bx,[OFFSET Count]

     end;

      Ссылки на память и непосредственные  значения классифицируются как перемещаемые  и  абсолютные  выражения.  Перемещаемое выражение означает значение,   которое   требует   перемещения    во    время редактирования, а абсолютное значение означает значение, которое не требует такого перемещения.  Обычно выражения, которые ссылаются на метки, переменные, процедуры или функции, являются переместимыми, а выражения, которые ссылаются только на константы - абсолютные.

      Перемещение -  это  процесс,  при  котором   редактор   связей назначает символам   абсолютные   адреса.   Во   время   компиляции компилятор не  знает  окончательных  адресов   меток,   переменных, процедур или  функций;  они  неизвестны  до времени редактирования, когда редактор назначает абсолютные адреса символам.

      Встроенный Ассемблер позволяет Вам  выполнить  любую  операцию над    абсолютным   значением,   но   ограничивает   операции   над перемещаемыми объектами до сложения и вычитания констант.

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

 

Типы выражений.

      Каждое выражение  встроенного ассемблера имеет тип - или более точно размер,  поскольку встроенный  асссемблер  рассматривает  тип выражения просто как размер его положения в памяти.  Например,  тип (размер) переменной Integer - 2, поскольку она занимает 2 байта.

     Встроенный асссемблер   выполняет  проверку  типа,  когда  это возможно, так в инструкциях

      var

       QuitFlag: Boolean;

       OutBufPtr: Word;

     ...

     asm

       mov   al,QuitFlag

       mov bx,OutBufPtr

     end;

встроенный ассемблер проверяет что размер QuitFlag - 1  байт, а размер OutBufPtr  -  2 байта.  Если тип неправильный,  то возникает ошибка; например, неверно:

      asm

       mov   dl,OutBufPtr

     end;

поскольку DL - регистр байтового размера, а OutBufPtr - слово. Тип ссылки  на память может быть изменен с помощью приведения типа; корректный способ написания предыдущей инструкции:

     asm

       mov   dl,BYTE PTR OutBufPtr

       mov   dl,Byte(OutBufPtr)

       mov   dl,OutBufPtr.Byte

     end;

ссылаются на  первый  байт  (наименее   значащий)   переменной OutBufPtr.

     В некоторых случаях ссылки на память нетипированные,  т.е.  не имеют типа. Пример с непосредственным значением, заключенным в []:

      asm

       mov   al,[100H]

       mov   bx,[100H]

     end;

      Встроенный асссемблер  разрешает  обе  инструкции,   поскольку выражение в  [100H]  не  имеет  типа  - это означает "содержимое по адресу 100H  в  сегменте  данных",  и  тип  может быть определен из первого операнда (байт для AL,  слово для BX). В случае если тип не может  быть  определен  из  другого операнда,  встроенный Ассемблер требует явного приведения типов:

      asm

       inc   BYTE PTR [100H]

       imul  WORD PTR [100H]

     end;

      Таблица 22.2 суммирует предопределенные типы символов, которые встроенный Ассемблер предоставляет в дополнение к типа Паскаля.

            Таблица 22.2 Предопределенные типы символов.

                    ────────────────────────────────

                        Символ           Тип

                   ────────────────────────────────

                        BYTE             1

                        WORD             2

                        DWORD            4

                        QWORD            8

                        TBYTE            10

                        NEAR             0FFFEH

                        FAR              0FFFFH

                   ────────────────────────────────

      Заметим, что  псевдотипы  NEAR и FAR используются для указания модели вызова процедур и функций. Вы можете использовать NEAR и FAR в приведении типов точно так же, как другие символы. Например, если FarProc - FAR процедура

      procedure FarProc; far;

и, если Вы пишете ассемблерный код в  том  же  модуле,  что  и FarProc, Вы можете использовать более эффективный NEAR вызов:

      asm

       push   cs

       call   NEAR PTR FarProc

     end;

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

   

Операторы выражений.

      Встроенный Ассемблер предоставляет операторы,  разделенные  на 12 классов   по   приоритетам.   Таблица  22.3  приводит  операторы встроенного Ассемблера в порядке уменьшения их приоритета.

      Таблица 22.3. Операторы выражений встроенного Ассемблера.

 ─────────────────────────────────────────────────

            Оператор                   Комментарий

─────────────────────────────────────────────────

     &                         Оператор перекрытия идентификатора

     ()                        Селектор элемента структуры

     []

     .

     HIGH LOW

     + -                       Унарные операторы

     :                         Оператор перекрытия сегмента

     OFFSET SEG TYPE PTR *

     / MOD SHL SHR

     + -                       Бинарные операторы

     NOT AND OR XOR            Побитовые операторы

─────────────────────────────────────────────────

 

     Примечание: Приоритет   операторов   встроенного    Ассемблера отличается от Паскаля.  Например, в ассемблерном выражении оператор AND имеет меньший приоритет,  чем операторы +  и  -,  а  в  Паскале наоборот.

      &

     Перекрытие идентификатора.  Идентификатор,  следующий   за   & интерпретируется как символ,  определенный пользователем, даже если он совпадает с зарезервированным словом встроенного Ассемблера.

      (...)

     Подвыражение. Выражения внутри () вычисляются до интерпретации как элемент  выражения.  Другое  выражение   может   предшествовать выражению внутри  ();  результат  в  этом  случае становится суммой значений двух выражений с типом первого выражения.

      [...]

     Ссылка на память. Выражение внутри [] полностью вычисляется до интерпретации, как один  элемент  выражения.  Выражение  внутри  [] может быть комбинировано с регистрами BX,  BP,  SI,  DI,  используя оператор +  для  указания  индексации.   Другое   выражение   может предшествовать выражению в []; результат становится суммой значений двух выражений с типом первого выражения.  Результат всегда  ссылка на память.

      "."

     Селектор элемента структуры.  Результат - сумма  выражения  до точки и  выражения  после  точки  с  типом  выражения  после точки. Символы, лежащие  в  сфере  действия,  определяемой  выражением  до точки, могут использоваться в выражении после точки.

      HIGH

     Возвращает старшие 8 бит выражения типа слово,  следующего  за оператором. Выражение   должно   быть  абсолютным  непосредственным значением.

     LOW

     Возвращает младшие  8 бит выражения типа слово,  следующего за оператором.  Выражение  должно  быть  абсолютным   непосредственным значением.

     "+"

     Унарный плюс.  Возвращает  выражение,  следующее  за  +,  без изменений.   Выражение   должно  быть  абсолютным  непосредственным значением.

      "-"

     Унарный минус.   Возвращает   выражение,  следующее  за  -,  с отрицательным значением.   Выражение   должно    быть    абсолютным непосредственным значением.

     ":"

     Перекрытие сегмента. Указывает Ассемблеру, что выражение после ":" относится к сегменту с именем сегментного регистра (CS, DS, SS, ES) до ":".  Результат - ссылка на память  со  значением  выражения после ":".   Когда  перекрытие  сегмента  используется  в  операнде инструкции, инструкция будет предварена  соответствующим  префиксом перекрытия сегмента,   гарантируя,   что   будет  выбран  указанный сегмент.

      OFFSET

     Возвращает смещение  (младшее слово) выражения,  следующего за оператором. Результат - непосредственное значение.

     SEG

     Возвращает сегментную   часть   (старшее   слово)   выражения, следующего за оператором. Результат - непосредственное значение.

      TYPE

     Возвращает тип  (размер  в  байтах)  выражения,  следующего за оператором. Тип непосредственного значения - 0.

      PTR

     Оператор приведения  типа.  Результат  -  ссылка  на память со значением выражения,  следующего за оператором и  типом  выражения, стоящего перед оператором.

      "*"

     Умножение. Оба    выражения    должны     быть     абсолютными непосредственными значениями     и     результат    -    абсолютное непосредственное значение.

     /

     Целое деление.   Оба   выражения   должны   быть   абсолютными непосредственными   значениями    и    результат    -    абсолютное непосредственное значение.

     MOD

     Остаток от  целого  деления.   Оба   выражения   должны   быть абсолютными  непосредственными  значениями и результат - абсолютное непосредственное значение.

     SHL

     Логический сдвиг влево.  Оба выражения должны быть абсолютными непосредственными   значениями    и    результат    -    абсолютное непосредственное значение.

     SHR

     Логический сдвиг вправо. Оба выражения должны быть абсолютными непосредственными    значениями    и    результат    -   абсолютное непосредственное значение.

     "+"

     Сложение. Выражения  могут  быть  непосредственными значениями или ссылками к памяти,  но только  одно  из  выражений  может  быть перемещаемым значением.  Если  одно  из  выражений  -  перемещаемое значение, то результат так же перемещаемое значение.  Если одно  из выражений - ссылка на память, то результат так же ссылка на память.

     "-"

     Вычитание. Первое выражение может быть любого класса, а второе выражение должно   быть   абсолютным   непосредственным  значением. Результат того же класса, что и первое значение.

     NOT

     Побитовое отрицание.    Выражение   должно   быть   абсолютным непосредственным    значением    и    результат    -     абсолютное непосредственное значение.

     AND

     Побитовое "и".   Оба   выражения   должны   быть   абсолютными непосредственными    значениями    и    результат    -   абсолютное непосредственное значение.

     OR

     Побитовое "или".   Оба   выражения   должны  быть  абсолютными непосредственными   значениями    и    результат    -    абсолютное непосредственное значение.

     XOR

     Побитовое исключающее  "или".  Оба   выражения   должны   быть абсолютными  непосредственными  значениями и результат - абсолютное непосредственное значение.

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

 

Ассемблерные процедуры и функции.

      До сих  пор  каждая  конструкция  asm...end  была  заключена в операторную часть begin...end.  Ассемблерная директива Turbo Pascal позволяет Вам   написать   процедуру   или   функцию  полностью  на встроенном Ассемблере,  не требуя  операторной  части  begin...end. Пример ассемблерной функции:

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

     asm

       mov   ax,X

       imul  Y

     end;

      Директива Ассемблера  заставляет  Turbo  Pascal  выполнить ряд оптимизаций при генерации кода:

     - Компилятор  не  генерирует  код  для  копирования   значения параметров в  локальные  переменные.  Это действует на все значения параметров строкового типа и другие значения параметров  с размером не 1,  2  или 4 байт.  Внутри процедуры или функции такие параметры должны интерпретироваться, как если бы они были var параметрами.

      - Компилятор не распределяет переменную результата  функции  и ссылка на   символ  @Result  является  ошибкой.  Строковые  функции являются исключением  из  этого  правила.  Они  всегда   используют указатель @Result, который распределяется вызывающей программой.

      - Компилятор  не генерирует кадра стека для процедур и функций без параметров и локальных переменных.

      - Автоматически  генерируется   код   входа   и   выхода   для ассемблерных процедур и функций, выглядящих как:

      push   bp

     mov    bp,sp

     sub    sp,Locals

     ...

     mov    sp,bp

     pop    bp

     ret    Params

где Locals  -  размер  локальных  переменных,  Params - размер параметров. Если Locals и Params ноль,  то входного кода нет, а код выхода состоит из инструкции RET.

      Функции, использующие директиву Ассемблера,  должны возвращать результаты:

     - Функции  порядкового  типа   (Integer,   Char,   Boolean   и перечислимые типы  возвращают  результаты в AL (8-битное значение), AX (16-битное значение), или DX:AX (32-битное значение).

      - Функции типа Real возвращают результат в DX:BX:AX

      - Функции с результатами типа 8087 (Single, Double, Extended и Comp) возвращают его в регистре ST(0) сопроцессора 8087.

      - Результат типа указатель возвращается в DX:AX.

      - Результат  типа  строка возвращается через временную память, на которую указывает @Result.

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

      function UpperCase(Str: String): String;

     begin

       asm

         cld

         lea    si,Str

         les    di,@Result

         SEGSS  lodsb

         stosb

         xor    ah,ah

         xchg   ax,cx

         jcxz   @3

       @1:

         SEGSS  lodsb

         cmp    al,'a'

         ja     @2

         cmp    a1,'z'

         jb     @2

         sub    a1,20H

       @2:

         stosb

         loop

       @3:

       end;

     end;

      Второй пример  - это ассемблерная версия функции UpperCase.  В этом случае Str не копируется в локальную память,  и функция должна интерпретировать Str как var параметр.

     function UpperCase(S: String): String; assembler;

     asm

       push   ds

       cld

       lds    si,Str

       les    di,@Result

       lodsb

       stosb

       xor    ah,ah

       xchg   ax,cx

       jcxz   @3

     @1:

       lodsb

       cmp    a1,'a'

       ja     @2

       cmp    a1,'z'

       jb     @2

       sub    a1,20H

     @2:

       stosb

       loop   @1

     @3:

       pop    ds

     end;

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

 


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


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

Опрос

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

 

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

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

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