Страница 1| Страница 2




Синтаксис ассемблерных команд

Ассемблерными командами называются команды на языке встроенного ассемблера, вставляемые в тело ассемблерного оператора asm...end.

Структура ассемблерной команды такова:

     [Метка]  [Префикс]  [Код  [Операнд  [,Операнд]]]

В квадратных скобках указываются необязательные элементы структуры.



Метки

Любой команде ассемблерного оператора может предшествовать одна или несколько меток. В ассемблере используется два типа меток: глобальные и локальные. Глобальные метки - это обычные метки Турбо Паскаля. Они объявляются в разделе описаний после зарезервированного слова Label. С помощью глобальной метки можно передать управление в тело ассемблерного оператора оператором GOTO. Например:

Label

    AltEnd;

begin

    ...............

    Goto AltEnd;         {Передаем управление внутрь ассемблерного оператора}

    ...............

asm

    ...............

AltEnd:                         {Сюда можно передать управление извне}

    ...............

end;

Локальные метки объявляются непосредственно в теле ассемблерного оператора. Эти метки обязаны начинаться символом "@". Поскольку этот символ нельзя использовать в именах Турбо Паскаля, он позволяет отличить локальную метку от глобальной. Локальная метка не известна нигде вне ассемблерного оператора, поэтому на нее нельзя передать управление оператором GOTO . Поэтой же причине в разных ассемблерных операторах можно использовать одноименные локальные метки.





Префиксы

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

LOCK                             Захват шины

REP/REPE/REPNE    Повтор строковой команды

REPZ/REPNZ              Синоним REPE/REPNE

SEGCS                          Перекрытие CS

SEGDS                          Перекрытие DS

SEGSS                          Перекрытие SS

SEGES                          Перекрытие ES

Префиксы LOCK/REP/REPE/REPNE описаны в пункте "система команд". Префиксы SEGxx определяют сегментный регистр, который должен использоваться вместо умалчиваемого, и распространяются только на следующие за ними ассемблерные команды.

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

Код инструкции очень редко имеет более одного префикса и никогда - более трех: допускается следующая последовательность.

LOCK SEGxx REPxx

Если при обработке строковой команды произошло аппаратное прерывание, МП 8086/8088 "забывает" префиксы LOCK и SEGxx, которые, возможно, определены в той же команде, так что использовать сложные префексные конструкции не рекомендуется.





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

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





Операнды

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





Регистры

Во встроенном ассемблере используется мнемоника регистров, указанная в пункте "Регистры", а также имя ST для ссылки на регистры арифметического сопроцессора.





Константы

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

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

     'Строковая константа'

     "Это - тоже строковая константа"

     'Символ ' ' не считается ограничителем'

     'внутри строки, обрамленной кавычками "... " '

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

 -2     147     483     648  ...   +   4     294     967     295.

По умолчанию при записи числовых констант используется десятичная нотация, но ассемблер поддерживает также двоичные, восьмиричные и шестнадцатиричные константы. Двоичная константа составляется как комбинация единиц и нулей, заканчивающаяся символом В (от Binary - двоичный); при записи восьмиричной константы используются символы 0..7, а в ее конце ставится символ О (Octal - восьмиричный); шестнадцатиричная константа записывается по правилам Турбо Паскаля (начинается с символа #) либо по правилам Турбо Ассемблера: начинается с цифры, а в конце ставится символ Н (от Hexadecimal - шестнадцатиричный).





Имена

Локальные метки - это единственные имена, которые разрешается определять внутри ассемблерного оператора. Имена остальных объектов программы - констант, переменных, подпрограмм - должны определяться только с помощью средств Турбо Паскаля.

Область определения имен подчиняется тем же правилам, что и в Турбо Паскале -имена должны быть <видны> в том месте, где они используются, и они локализуются в пределах блока, в котором описаны.

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

    @@Code - текущий сегмент кода

    @Data - начальный сегмент данных

    @Result - ссылка внутри функции на ее результат

Имена @Code и @Data могут использоваться только в сочетании с директивой SEG для ссылки на нужный сегмент. Например:

asm

    mov ax, SEG cData

    mov ds,ax

end;

Имя @Result используется для присвоения результата функции. Например:

  Function Min(X,Y: Integer): Integer;

  {Эта функция сравнивает два целых числа и возвращает наименьшее из них)

  begin

    asm

       mov ax,X          {Помещаем Х в АХ}

       cmp ax,Y          {X < Y ?}

       jl @                    {Да - на выход}

       mov ax,Y          {Нет - помещаем Y в АХ}

    @: mov cResult,ax {АХ содержит результат}

    end

  end;

Для доступа к полям записей разрешается использование составных имен. Например:

type

   Point = record

      X,Y: Integer

   end;

   Rect = record

      A,B: Point

   end;

var

   P: Point;

   R: Rect;

begin

  asm

      mov ax,P.X

      add ax,P.Y

      mov R.A.X,ax

  end

end.

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

  mov ax,(Rect PTR es:[di]).В.Х

  mov ax,Rect(es:[di]).В.Х

  mov ax,esrRect[di].B.X

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

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

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

<    стандартные процедуры и функции (например, WriteLn, Chr);

<   предопределенные массивы Mem, MemW, MemL, Port, PortW;

<   константы с плавающей точкой, строковые и множественного типа;

<   макросы (процедуры и функции, полностью реализуемые одним InLine-оператором);

<   символ

<   @Result вне функции.





Выражения

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

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

  push ds

  pop es

  mov ah,bl

  add ex,ax

Непосредственные выражения - это нетипизированные константы и имена типов. Примеры непосредственных выражений:

const

    dec =10;

.....

asm

    mov ax, dec

    mov bx,0

    add cx,2*dec+l

    sub dh,- 5

end;

Все остальные выражения относятся к ссылкам на память. Например:

const

    dec: Word = 10;

    Step =12;

var

    Х,Y: Byte;

asm

    mov ax, dec

    mov ex, [Step]

    add ah,X mov Y,bl

    mov ax,[bx]

end;

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

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

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

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

         х+10

в Паскале означает: <к содержимому переменной X прибавить 10>. В ассемблерной команде это означает: .<к адресу (смещению) переменной X прибавить 10>. Однако команда

        mov ах,X

означает: <поместить в регистр АХ первые два байта переменной X>. Если бы нам понадобилось загрузить в АХ адрес переменной X, мы должны были бы написать

        mov ax,OFFSET X

Замечу, что попытка <перехитрить> ассемблер командами типа

        mov ax,X+0 mov ax,X+1-1

и т.п. не дает желаемого результата: ассемблер просто загружает в АХ содержимое переменной X.

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

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

Тип Длина в памяти
BYTE 1
WORD 2
DWORD 4
QWORD 8
TBYTE 10
NEAR -
FAR -

Имена предопределенных типов можно использовать для приведения типов выражений. Например, если определены переменные

var

    Flag: Boolean;

    X : Word;

то такие ассемблерные выражения недопустимы:

    mov Flag,bx

    mov ah,X

Для корректного задания последней команды можно использовать следующие варианты:

    mov ah,BYTE PTR X

    mov ah,Byte(X)

    mov ah,X.Byte

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





Операции

Операция Комментарий
& Перекрытие идентификатора.
( ) Подвыражение.
[ ] Ссылка на память
. (точка) Селектор структуры.
HIGH LOW Доступ к байту в слове.
+ - Унарные операции задания знака.
: Перекрытие сегмента.
OFFSET SEG TYPE PTR *  
/ MOD SHL SHR  
+ - Бинарные операции
NOT AND OR XOR Операции над битами



Операция Описание
& Осуществляет перекрытие идентификатора: следующий за знаком & идентификатор считается определенным в программе, даже если он совпадает с зарезервированным словом. Например:

var

    Ch: Byte

   ........

   mov ch, 0      {Посылаем 0 в регистр CH}

   mov &Ch, 0   {Посылаем 0 в переменную Ch}

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

mov  ax, ((1+2)*3+4)*5    {ax = 65}

mov bx, 1+2*3+4*5         {bx = 27}

[ ] Определяет ссылку на память. Выражение внутри скобок вычисляется в первую очередь Обычно оно связывается с регистрами BX, BP, SI, DI и может использовать операции + и - для указания индекации. Например:

mov  ah, 100     {AH =100}

mov  ah, [100]   {Загружаем в AH содержимое байта по адресу DS: 100}

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

var

   R: record

       X: Word; Y: Byte

  end;

..............

   mov ax, R.X

   mov R. Y, al

HIGH и LOW HIGH возвращает старший, а LOW - младший байт выражения типа слова, следующего за символами операции. Выражение должно иметь абсолютное непосредственное значение. Например:

mov al, High $1000  {AL = $10}

: (двоеточие) Указывает ассемблеру, что выражение после операции должно относится к сегменту, указонному до операции. Результат - ссылка на память со значением второго выражения. Например:

mov ax,[10]           {AX = слово по адресу DS: 10}

mov ax,BS : [10]   {AX = слово по адресу BS: 10}

OFFSET Возвращает смещение выражения, следующего за операцией. Результат имеет непосре дственное значение. Например:

mov ax,X              {AX = слово по адресу переменной X}

mov ax,offset X    {AX = смещение адреса X}

SEG Возвращает смещение выражения, следующего за операцией. Результат имеет непосредственное значение.
PTR Осуществляетприведение типа. Результат - ссылка на память со значением выражения после операции и типом выражения до операции.
* и / * - умножение, / - целочисленное деление. Оба выражения должны иметь непосредственные абсолютные значения, такое же значение имеет и результат операции. Например:

mov ax, 2*2   {AX = 4}

mov ax, 17/3  {AX = 5}

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

mov ax, 17 mod 3   {AX = 2}

SHL и SHR Осуществляют логический сдвиг влево (SHL) или вправо (SHR) выражения, стоящего до операции, на количество разрядов, определяемое выражением после операции. Оба выражения должны иметь непосредственные абсолютные значения, такое же значение имеет и результат операции. Например:

mov ax, 1 shl 7       {Ah = $80 = 128}

+ Осуществляет сложение двух выражений. Выражения могут быть непосредственными значениями или ссылками на память, но только одно из них может быть перемещаемым. Если одно из выражений - ссылка на память, результат также определяет ссылку на память, а если одно из выражений - перемещаемое, результат будет перемещаемым.
- Вычитание двух выражений. Первое выражение может быть любого класса, а второе должно быть абсолютным непосредственным значением. Результат относится к тому же классу, что и первое выражение.
NOT, AND, OR, XOR Имеют такой же смысл, что и одноименные операции Турбо Паскаля над целыми числами. Оба выражения должны иметь непосредственные абсолютные значения, такое же значение имеет и результат операции.





Оператор ASM

Зарезервированное слово ASM открывает доступ к средствам встроенного ассемблера. Этот оператор может располагаться только внутриисполняемой части программы(подпрограммы). Область действия оператора ASM ограничивается ближайшим по тексту зарезервированным словом END. Таким образом, структура любого ассемблерного оператора такова:

  ASM

        <Одна или несколько команд встроенного ассемблера>

  END

С точки зрения Турбо Паскаля пара ASM...END считается операторными скобками, ограничивающими единственный оператор Паскаля, например:

if x>10 then

  ASM

   .....................

  END

else

   .................;

for k :=1 to 5 do

  ASM

   .....................

  END

Тело ассемблерного оператора asm...end может быть пустым или содержать несколько ассемблерных команд. Каждая ассемблерная команда должна располагаться на отдельной строке или отделяться от следующей за ней команды символом ";". Ниже приводятся два разных способа написания одной и той же последовательности ассемблерных команд:

ASM

  mov ah,0; int 16; mov ChCode, al; mov ScCode, ah

END;

ASM

  mov ah,0

  int $ 16

  mov ChCode, al

  mov ChCode, ah

END;

В конце строки, содержащей единую ассемблерную команду, или между двумя командами, распологающимися на одной строке, разрешается вставлять комментарий, который должен оформляться по обычным правилам Турбо Паскаля, т.е. ограничиваться символами "{" "}" или "(*", "*)". Таким образом, комментарии разрешены между ассемблерными командами, но не внутри них. Например, такой оператор будет правильным:

ASM

    {Инициируем регистры}

    lea si, X; push ds;

    pop es;

    lea di, Y; mov cx,100

    cld    {Перенос - вперед}

rep     {Выполняем Y:= X}  movsw

END;

а такой - неправильным:

ASM

    {Готовим регистры}

    lea si,X; push ds;

    pop {ES:=DS} es;

      {Ошибка! Комментарий разорвал мнемонику команды и ее операнд}

    lea di, Y; mov cx,100 {и направление} cld

      {Комментарий является разделителем команд, поэтому перед ним можно не ставить ";"}

    rep movsw

ASM

В пределах ассемблерного оператора допускаются любые команды, но Турбо Паскаль требует выполнения следующего соглашения:

В начале ассемблерного оператора регистр DS содержит сегмент кода, SS- сегмент стека, BP- текущий стек, SP указывает на вершину стека. Все эти регистры должны иметь точно такие же значения к моменту завершения работы ассемблерного оператора.





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

Встроенный ассемблер не поддерживает никакие директивы, обычно используемые в других ассемблерах, за исключением DB, DW, DD. Структура директив такова:

Dx <константа> [,<константа>,...,<константа>]

Здесь Dx - DB, DW или DD; <константа> - ассемблерная константа или константное выражение.

DB определяет цепочку байт, DW- слов, DD - двойных слов. Например:

      db 'Турбо Паскаль',13,10

      dw 0,$ FFFF, NearProc

      dd 'ABCD1,999999999, FarProc

В качестве константных выражений разрешается использовать любые ассемблерные константы со значением, не выходящим из диапазона байта (DB), слова (DW) или двойного слова (DD). В любой директиве можно определять строковую константу, которая приводит к побайтовому заполнению памяти ASCII-кодами символов. Поскольку слово (двойное слово) размещается в памяти, начиная со своего младшего байта, старший (старшие) байт в директивах DW и DD при размещении строкой константы может остаться неопределенным и заполняется нулем. Например, два следующих объявления эквивалентны:

      dw '5'

      dw $35 {$35 - ASCII-код символа '5'}

В директивах DW и DD разрешается также указывать имена, которые в этом случае интерпретируются как адреса соответствующих объектов, причем для DW это - ближний адрес (смещение), а для DD - дальний. Например:

      dw X           {Размещает смещение переменной X}

      dd Proc      {Размещает FAR-адрес процедуры Рrос}

Данные, определяемые директивами Dx, всегда размещаются в текущем кодовом сегменте. Разместить таким образом данные в сегменте данных (т.е. определить константу или типизированную константу) невозможно - для этого используются стандартные средства Турбо Паскаля. Более того, директивы не могут снабжаться именами, а поэтому использовать размещаемые с их помощью данные не так-то просто. В следующем примере на экран выводится текстовое сообщение. Для этого используется функция 9 вызова ДОС, в соответствии с которой в регистрах DS:DX должен содержаться адрес текстовой строки, а сама строка должна заканчиваться символом <$>:

asm

   jmp cNextCode           {Обходим фрагмент данных}

@:

   db 'Текстовая строка,13,10,'$'

@NextCode:

   push ds                         {Сохраняем DS}

   push cs

   pop ds                           {DS = CS}

   mov dx,OFFSET @     {DS:DX - адрес строки}

   mov ah,9                        {AH - код функции вывода}

   int 21h                             {Выводим строку}

   pop ds                             {Восстанавливаем DS}

end;

Обратите внимание на использование регистра DS. В соответствии с требованиями функции 9, он должен содержать сегмент выводимой строки. В нашем случае строка располагается в кодовом сегменте, поэтому мы вынуждены сначала сохранить значение DS в стеке, а затем восстановить его. Если бы мы этого не сделали, по завершении ассемблерного оператора регистр DS указывал бы на сегмент кода и была бы потеряна связь программы Турбо Паскаля с глобальными переменными и константами.





Подпрограммы

Ассемблерные подпрограммы - это процедуры и функции, объявленные с директивой Assembler. В таких подпрограммах исполняемая часть не содержит begin... end и состоит из единственного ассемблерного оператора asm... end. Например:

Function LongMul(X,Y:Integer):LongInt; Assembler;

asm

    mov ax, X

    imul Y         {DX/AX содержат "длинный" результат}

end;

При компиляции ассемблерных подпрограмм выполняется ряд оптимизаций кода, в том числе:

<   параметры-значения строкового типа, а также длиной в 1, 2 и 4 байта не копируются во временную память, т.е. внутри подпрограммы они считаются параметрами-переменными ;

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

<   генерируются следующие команды на входе в подпрограмму и на ее выходе:

      push bp                  {Сохраняется ВР}

      mov bp,sp              {ВР содержит текущую границу стека}

      sub sp,Locals         {Резервируется часть стека для размещения локальных переменных}

      .......

      mov sp,bp               {Восстанавливается граница стека}

      pop bp                    {Восстанавливается ВР}

      ret Params             {Из стека удаляются параметры подпрограммы и осуществляется выход из нее}

Здесь Locals - общая длина в байтах всех объявленных в подпрограмме локальных переменных, a Params - длина (в байтах) всех формальных параметров. Если Locals и Params равны нулю, входной код не создается, а выходной содержит единственную инструкцию RET.

Все локальные переменные Турбо Паскаль размещает в стеке. Это относится как к обычным, так и к ассемблерным подпрограммам. Для ссылки на локальные переменные используется адресация по базе, задаваемой парой DS: ВР, поэтому при входе в процедуру всегда создается так называемый локальный стек: в регистр ВР помещается текущая граница стека, а сама эта граница смещается вверх на суммарную длину всех локальных переменных, чтобы работа со стеком внутри подпрограммы не разрушила локальные переменные. Например:

      Procedure ...; Assembler;

      var

            X: Word;

            Y: Byte;

      asm

            mov X, ax       {Компилируется в mov [BP-2], ax}

            mov ah,Y         {Компилируется в mov ah,[BP-3]}

      end;

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

      <  длиной 1 байт (Byte, Char и т.п.) - в регистре AL;

      <  длиной 2 байта (Integer, Word) - в регистре АХ;

      <  длиной 4 байта (Pointer, LongInt) - в регистрах DX (старшее слово) и АХ (младшее слово);

      <  типа Real - в регистрах DX, BX, АХ (старшее слово - в DX, младшее в АХ);

      <  вещественных типов Single, Double, Extended, Comp - в регистре ST (0) сопроцессора;

      <  строкового типа - во временной области памяти, на которую ссылается @Result.





Страница 1| Страница 2