лабораторные на Pasca (Кудрявцев)l
.pdfВстроенный ассемблер |
353 |
|
|
dw X |
{Размещает смещение переменной X} |
dd Proc |
{Размещает FAR-адрес процедуры Proc} |
Данные, определяемые директивами Dx, всегда размещаются в те- кущем кодовом сегменте. Разместить таким образом данные в сег- менте данных (т.е. определить константу или типизированную кон- станту) невозможно – для этого используются стандартные средст- ва Турбо Паскаля. Более того, директивы не могут снабжаться име- нами, а поэтому использовать размещаемые с их помощью данные не так – то просто. В следующем примере на экран выводится тек- стовое сообщение. Для этого используется функция 9 вызова DOS, в соответствии с которой в регистрах DS:DX должен содержаться адрес текстовой строки, а сама строка должна заканчиваться сим- волом «$»:
asm
jmp @NextCode |
{Обходим фрагмент данных} |
@: |
|
db 'Текстовая строка,13,10,'$' |
|
@NextCode: |
{Сохраняем DS} |
push ds |
|
push cs |
{DS = CS} |
pop ds |
|
mov dx, OFFSET @ |
{DS:DX - адрес строки} |
mov ah, 9 |
{АН - код функции вывода} |
int 21h |
{Выводим строку} |
pop ds |
{Восстанавливаем DS} |
end; |
|
Обратите внимание на использование регистра DS. В соответ- ствии с требованиями функции 9, он должен содержать сегмент вы- водимой строки. В нашем случае строка располагается в кодовом сегменте, поэтому мы вынуждены сначала сохранить значение DS в стеке, а затем восстановить его. Если бы мы этого не сделали, по завершении ассемблерного оператора регистр DS указывал бы на сегмент кода и была бы потеряна связь программы Турбо Паскаля с глобальными переменными и константами.
Ассемблерные подпрограммы
Ассемблерные подпрограммы – это процедуры и функции, объявленные с директивой Assembler. В таких подпрограммах ис-
354 |
Приложение |
|
|
полняемая часть не содержит 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 – общая длина в байтах всех объявленных в подпро- грамме локальных переменных, а Params – длина (в байтах) всех формальных параметров. Если Locals и Params равны нулю, вход- ной код не создается, а выходной содержит единственную инструк-
цию RET.
Все локальные переменные Турбо Паскаль размещает в стеке. Это относится как к обычным, так и к ассемблерным подпрограммам.
Для ссылки на локальные переменные используется адресация по базе, задаваемой парой DS:ВР, поэтому при входе в процедуру все- гда создается так называемый локальный стек: в регистр ВР поме-
356 |
Приложение |
|
|
Во-вторых, используя встроенный ассемблер пакета Turbo Pascal, отдельные части текста программы можно написать непосредст- венно на языке ассемблера, заключив их в операторные скобки
asm...end.
В-третьих, ту или иную подпрограмму (процедуру или функцию) можно полностью, за исключением заголовка, написать на языке ассемблера, используя при этом директиву assembler. В этом слу- чае также используется встроенный ассемблер.
В-четвертых, небольшую подпрограмму можно написать непо- средственно в кодах процессора, используя оператор или директиву
inline.
При написании отдельных частей программы на языке ассемблера следует иметь в виду, что необходимосохранить содержимое реги- стров ВР, SP, SS и DS. Если их необходимоизменить, то исходные значения следует запомнить, а затем восстановить. Остальные ре- гистры можно безболезненно изменять.
Основным вопросом стыковки программы с подпрограммой, напи- санной на ассемблере, является передача параметров в подпро- грамму и обратно. Именно этому вопросу и будет здесь уделено основное внимание.
Ниже будут рассмотрены особенности использования этих вариан- тов. В качестве примера их использования будут приведены раз- личные варианты подпрограммы-функции, определяющей макси- мальный элемент из массива целых чисел.
Использование компилятора TASM
Как правило, этот вариант применяется, когда та или иная про- грамма имеет большой размер и ее целесообразно и написать, и скомпилировать отдельно, используя компилятор TASM. В этом
случае можно использовать все возможности языка и компилятора
TASM.
Пример. Программа, использующая подпрограмму-функцию, опре- деляющую максимальный элемент из массива целых чисел и напи- санную на языке ассемблера.
Основная программа, использующая подпрограмму, написанную на языке ассемблера, содержит инициализированный массив, в кото-
Встроенный ассемблер |
357 |
|
|
ром будет определяться максимальное число, а сама программа вы- водит на экран значение максимального числа из этого массива:
Program EXAMPLE1;
Const N=7; |
|
{Размер массива} |
Massiv: Array [1… n] of Integer = (1, 2, 3, 2, 17, 7, 2); |
{Исходный массив} |
|
{$L SUBR} |
{Подключение файла SUBR.OBJ} |
|
Function Max (var Mas; N: Integer): Integer; External; Begin
WriteLn('Максимальное число массива равно: ', Max(Massiv, N)); ReadLn
End.
Используя стандартную модель памяти, подпрограмму, опреде- ляющую максимальное число из массива, можно написать следую- щим образом:
CODE |
SEGMENT |
BYTE |
PUBLIC |
;внешний идентификатор |
|
ASSUME |
CS:CODE |
|
|
|
PUBLIC |
Max |
|
;адрес первого параметра |
AdrMas |
EQU |
DWORD |
PTR[BP+6] |
|
N |
EQU |
WORD |
PTR[BP+4] |
;второй параметр |
Max |
|
PROC |
NEAR |
;сохранение регистра ВР |
|
PUSH |
ВР |
|
|
|
MOV |
BP, SP |
|
;указатель стека |
|
LDS |
SI, AdrMas |
|
;адрес массива |
|
XOR |
AX, AX |
|
;0 - в регистр АХ |
|
MOV |
BX, 8001h |
|
;минимальное целое число |
|
MOV |
CX, N |
|
;число элементов массива |
|
CMP |
CX, AX |
|
;сравнение с 0 |
|
JLE |
M3 |
|
;0 или отрицательное число |
M1: |
LODSW |
|
|
;загрузка элемента массива |
|
CMP |
AX, BX |
;сравнение с текущим максимумом |
|
|
JLE |
M2 |
|
;не больше |
|
MOV |
BX, AX |
|
;новое максимальное число |
M2: |
LOOP |
M1 |
|
;цикл |
M3: |
MOV |
AX, BX |
|
;результат функции |
|
POP |
ВР |
|
;восстановление регистра |
ВР |
RET |
6 |
|
;возврат из подпрограммы |
Max |
|
ENDP |
|
|
CODE |
ENDS |
|
|
|
|
END |
|
|
|
358 |
Приложение |
|
|
По приведенной подпрограмме следует сделать следующие замеча- ния.
Первые две команды – сохранение регистра ВР и загрузка в него указателя стека – являются типичными командами, с помощью ко-
торых можно установить доступ к передаваемым параметрам через регистр ВР.
Параметры передаются в подпрограмму следующим образом. Па- раметры-значения размером в один байт передаются одним 16- разрядным словом, причем информативным является младший байт, параметры-значения в 2 байта передаются одним 16- разрядным словом, в 4 байта – двумя 16-разрядными словами, па- раметры-значения типа Real передаются тремя 16-разрядными сло- вами, все остальные параметры-значения (в том числе и 3- байтовые) передаются своими полными адресами. Из этого правила есть некоторые исключения: параметры-переменные и параметры- константы всегда передаются своими полными адресами.
Т.к. в подпрограмме первый параметр является параметром- переменной, то он передается своим адресом, с помощью которого в дальнейшем и извлекаются элементы массива. Второй параметр подпрограммы – параметр-значение, и он передается своим значе- нием. Первый параметр находится по адресу ВР+6, а второй – ВР+4. Указанные смещения определяются наличием в стеке адреса воз- врата (при ближней адресации - 2 байта), размещенным в стеке зна- чением регистра ВР (2 байта) и для первого параметра - размером второго параметра (2 байта).
Если подпрограмма является подпрограммой-функцией, то возвра-
щаемый параметр передается различным образом в зависимости от своего размера. Параметр размером в байт передается в регистре AL, параметр размером в 2 байта - в регистре АХ, параметр разме- ром в 4 байта - в регистрах DX (старшая часть или адрес сегмента) и АХ (младшая часть или смещение), параметры размером в 6 байтов (типа Real) - в регистрах DX (старшая часть), ВХ (средняя часть) и АХ (младшая часть). Параметры других вещественных типов пере- даются в нулевом элементе стека сопроцессора ST(0). Если функция возвращает значение типа string, то при обращении к функции ре- зервируется память для размещения возвращаемой строки, а адрес
Встроенный ассемблер |
359 |
|
|
этой области размещается в стеке выше всех передаваемых пара- метров.
В рассматриваемом примере возвращаемый параметр – типа Integer, и он возвращается в регистре АХ.
При возвращении из подпрограммы в команде RET записан аргу- мент 6 для удаления из стека передаваемых параметров, которые в данном примере имеют именно этот размер.
Turbo Assembler предполагает и другое оформление подпрограмм, используемых затем в программах, написанных на языке Паскаль. Для этого используется специальная модель памяти Large (боль- шая), задаваемая в виде:
.MODEL Large, PASCAL.
Она позволяет несколько упростить оформление входа в подпро- грамму и выхода из нее. Подпрограмма дополняется необходимы- ми командами на этапе компиляции.
Пример. Вариант предыдущей подпрограммы, использующий спе- циальную модель памяти.
|
.MODEL |
Large, PASCAL |
;специальная модель памяти |
|
.CODE |
|
;внешний идентификатор |
|
PUBLIC |
MAX |
|
Max |
|
PROC |
NEAR mas:DWORD, N:WORD |
|
LDS |
SI, MAS |
;адрес массива |
|
XOR |
AX, AX |
;0 - в регистр АХ |
|
MOV |
BX,8001H |
;минимальное целое число |
|
MOV |
CX, N |
;число элементов массива |
|
CMP |
CX, AX |
;сравнение с 0 |
|
JLE |
@@3 |
;0 или отрицательное число |
@@1 |
LODSW |
|
;загрузка элементов массива |
|
CMP |
AX, BX |
;сравнение с текущим максимумом |
|
JLE |
@@2 |
;не больше |
|
MOV |
BX, AX |
;новое максимальное число |
@@2 LOOP |
@@1 |
;цикл |
|
@@3 |
MOV |
AX, BX |
;результат функции |
MAX |
RET |
ENDP |
;возврат из подпрограммы |
END |
|
||
|
|
|
|
Встроенный ассемблер |
361 |
|
|
WriteLn('Максимальное число массива равно: ', Max(Massiv, N)); ReadLn
end.
Подпрограмма, написанная на языке ассемблера, будет в этом слу- чае иметь следующий вид:
|
.MODEL |
Large, PASCAL |
;специальная модель памяти |
|
.CODE |
|
;внешняя подпрограмма |
|
EXTRN |
ErrorReport: Near |
|
|
PUBLIC |
Max |
;внешний идентификатор |
Max |
|
PROC NEAR Mas: DWORD, N: Word |
|
|
|
|
;передаваемые параметры |
|
LDS |
SI, Mas |
;адрес массива |
|
XOR |
AX, AX |
;0 - в регистр AX |
|
MOV |
BX, 8001h |
;минимальное целое число |
|
MOV |
CX, N |
;число элементов массива |
|
CMP |
CX, AX |
;сравнение с 0 |
|
JG |
@@1 |
;допустимое число |
|
PUSH |
BX |
;сохранение регистра BX |
|
PUSH |
CX |
;передаваемый параметр |
|
CALL |
ErrorReport |
;обращение к подпрограмме |
|
POP |
BX |
;восстановление регистра BX |
|
JMP |
@@3 |
;на завершение |
@@1: |
LODSW |
|
;загрузка элемента массива |
|
CMP |
AX, BX |
;сравнение с текущим максимумом |
|
JLE |
@@2 |
;не больше |
|
MOV |
BX, AX |
;новое максимальное число |
@@2: LOOP |
@@1 |
;цикл |
|
@@3: |
MOV |
AX, BX |
;результат функции |
Max |
RET |
ENDP |
;возврат из подпрограммы |
END |
|
||
|
|
|
|
Перед обращением к подпрограмме, написанной на языке Паскаль, в стек в соответствующем порядке следует поместить передавае- мые параметры. В данном случае такой параметр один - число эле- ментов массива.
Т.к. подпрограмма, написанная на языке Паскаль, не гарантирует сохранение регистров AX, BX, CX и DX, то в случае необходимости сохранения их значений следует перед обращением к подпрограм- ме, написанной на языке Паскаль, сохранить в стеке значения соот- ветствующих регистров, а после возвращения из подпрограммы -
362 |
Приложение |
|
|
восстановить их. В данном примере сохраняется содержимое реги- стра BX, в котором записано минимальное целое число.
При написании программ, содержащих отдельные части, написан- ные на языках ассемблера и Паскаль, следует обращать внимание на способ адресации (дальний – far или ближний – near). Здесь су- ществует следующее правило: если подпрограмма объявляется в интерфейсной части какого-либо модуля, то она должна иметь дальнюю адресацию, в других случаях (подпрограмма объявляется в файле, содержащем основную программу, или в исполнительной части модуля) следует использовать ближнюю адресацию.
И еще одно замечание: внешнюю подпрограмму нельзя объявлять внутри другой подпрограммы.
Использование встроенного ассемблера
Начиная с версии 6.0 Turbo Pascal содержит встроенный ассемблер, позволяющий писать отдельные части программ на языке ассемб- лера. Встроенный ассемблер обладает многими возможностями языка Turbo Assembler, но приспособлен к использованию в про- граммах, написанных на языке Паскаль (позволяет использовать идентификаторы программы, написанные на языке Паскаль, ком- ментарии, имеющие такой же вид, как в языке Паскаль, позволяет воспользоваться встроенным отладчиком для пошагового выполне- ния программы, контроля содержимого регистров и параметров программы и т.д.).
Так же как и Turbo Аssembler, встроенный ассемблер предполагает использование ряда предопределенных стандартных идентифика- торов, имеющих специальное назначение. Если в программе будет введен идентификатор с таким же именем, но имеющий другое на- значение, в частях программы, написанных на встроенном ассемб- лере, будет отдано предпочтение стандартному назначению этого идентификатора. Перечень стандартных идентификаторов встроен- ного ассемблера приведен в приложении В.
Наряду с возможностью использования идентификаторов языка Паскаль встроенный ассемблер использует три дополнительных идентификатора:
− @Code – текущий кодовый сегмент (используется только с опе- ратором SEG);
