Ассемблерными командами называются команды на языке встроенного ассемблера, вставляемые в тело ассемблерного оператора 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 - шестнадцатиричный).
Тип | Длина в памяти |
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 ограничивается ближайшим по тексту зарезервированным словом
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.