[an error occurred while processing this directive]

.сс

9 Использование машинного кода из программ на Паскале

_______________________________________________________________

В этой главе приводятся примеры использования машинных подпрограмм в программах на Паскале. Проще, если объектный код программы на Паскале (и, следовательно, машинные подпрограммы) грузится всегда в одно и то-же место, но возможно, с небольшими усилиями, написать полностью перемещаемую программу (объектный код полностью написанной на Паскале программы всегда перемещаемый). Машинные подпрограммы вызываются функциями 'code0' и 'code1', которые описывались в разделе 5.8.

Основная идея использования машинных подпрограмм заключается в помещении их перед объектным кодом программы на Паскале, и добавлении инструкции для интерпретатора Паскаля о пропуске блока памяти с машинными подпрограммами. Это выглядит так:

.ао0

.лв

Конец кода+-------------------------------------------+
||
|Объектный код программы на Паскале|
||
Конец машинного+-------------------------------------------+
кода||
|Машинный код|
||
OSHWM+3+-------------------------------------------+
|Инструкция пропуска|
OSHWM+-------------------------------------------+

.нф

.ов

В Паскале объектная программа всегда грузится с OSHWM. Так как значение OSHWM может меняться (в зависимости от модели машины, файловой системы, наличия сопроцессора и т.д.), важно учесть это при использовании подпрограмм на ассемблере. Инструкция пропуска в начале объектного кода - это команда, говорящая интерпретатору о переходе по заданному адресу. Она имеет длину три байта. Первый байт (&F1) - это код инструкции, а два оставшихся - адрес перехода.

9.1 Пример подпрограммы

Как простой пример, мы приводим небольшую программу, которая сдвигает содержимое целой переменной Паскаля на заданное число бит влево. Сдвигание на n бит эквивалентно умножению на 2 в степени n. Это можно было бы сделать в цикле 'for', но это было бы слишком медленно.

Функция 'code1' использована для вызова машинной подпрограммы. Вспомните, что при этом требуется три параметра: адрес подпрограммы, содержимое аккумулятора и переменная Паскаля. Следовательно, подпрограмма вызывается инструкцией вида:

dummy:=code1(shiftAddr, count, int);

Необходимо отметить, что здесь машинной подпрограмме не сообщается тип переменной, так что при некорректном вызове, подпрограмма сдвинет, например, первые четыре байта переменной-множества.

Машинные подпрограммы могут использовать адреса нулевой страницы от &70 до &8F, также как и в Бейсике.

9.1.1

Подпрограмма сдвига, приведенная ниже написана на ассемблере BBC Бейсика II.

.ао0

.лв

1000 REM Пример машиннойй подпрограммы для Паскаля
1010
1020 REM Инструкция перехода интерпретатора Паскаль
1030 jump_code_base = &F1
1040 
1050 oshwm= &E00
1060 REM Указатель переменной
1070 var_ptr= &70
1080 REM Счетчик байт
1090 count= &72
1100 REM Для машинной подпрограммы
1110 DIM code 40
1120
1130 FOR opt=4 to 6 STEP 2
1140 P%=oshwm
1150 O%=code
1160 [ opt opt
1170 \ Помещает переход перед машинным кодом
1180 EQUB jump_code_base
1190 \ Переход к обаектному коду программы на Паскале
1200 EQUW end_of_mc - oshwm
1210 .shift
1220 \ Сохраняем адрес переменной
1230 stx var_ptr
1240 \ Счетчик должен быть в пределе 0..31
1250 sty var_ptr + 1
1260 and #&1F
1270 \ Количество сдвигов
1280 sta count
1290 \ Цикл сдвига
1300 .shift_loop_count
1310 dec count
1320 bmi end_shift
1330 \ Сдвиг четырех байт
1340 ldy #0
1350 \ X - счетчик байт
1360 ldx #4
1370 \ В первый байт вдвигаем ноль
1380 clc
1390 \ Берем байт
1400 .shift_loop_y
1410 lda (var_ptr),y
1420 \ Сдвиг на один бит влево
1430 rol a
1440 \ Сохранение сдвинутого байта
1450 sta (var_ptr),y
1460 \ Следующий байт
1470 iny
1480 \ Следующий бит
1490 dex
1500 bne shift_loop_y
1510 beq shift_loop_count
1520 \ Все биты обработаны
1530 .end_shift
1540 \ Для вычисления адреса перехода
1550 rts
1560 .end_of_mc
1570 ]
1580 NEXT opt
1590
1600 PRINT " *SAVE SHIFTMC ";~code" ";
1610 PRINT ;~O%" ";~shift" ";~oshwm
1620 END

.нф

.ов

Ключевые строки здесь следующие:

1030

Определяет код команды перехода для интерпретатора Паскаля.

1050

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

1070, 1090

Здесь задаются ячейки нулевой страницы, используемые в программе. &70-&8F доступны для использования.

1180

Помещает команду перехода перед началом машинной подпрограммы, так как отсюда Паскаль начинает выполнение программы, например после команды RUN.

1200

Это двухбайтный адрес перехода.

1210-1500

Программа, выполняющая сдвиг

1560

Метка конца машинной подпрограммы (и начала объектного кода программы на Паскале), использованная в 1200-й строке.

1600, 1610

Печатает строку, копирование которой курсором вызывает запись подпрограммы на диск. Третье число - адрес вызова подпрограммы (по которому происходит переход из функции code1), а последнее - значение OSHWM.

9.1.2

Следующим шагом будет написание на Паскале программы, использующей машинную подпрограмму. Один из возможных вариантов приведен ниже. Здесь вначале производится присваивание переменной единицы, с последующим ее сдвигом от 0 до 31 бита в цикле. При этом производится печать нового значения переменной после каждого сдвига.

.ао0

.лв

program shift(input,output)

{ This demonstrates how the machine code routine
at the start of the file is called. It uses
OSBYTE to deduce its address (by finding OSHWM)
and calls the code three bytes above that. If the
machine code were not relocatable, its address
might as well be declared in a constant
declaration, as the program would not work at any
other location. }

const
osbyte= &FFF4;{ Operating system
call }
findOshwm= 131;{ Code to find OSHWM }
mcOffset= 3;{ Offset from OSHWM to
code }

var
count: integer;{ Looping variable }
mcAddr: integer;{ Address of machine
code }
oshwm: integer;{ Current OSHWM value }
anInteger: integer;{ A variable accessed by
'shift' }
dummy: integer;{ A dummy variable for
code1 }

begin
{ Find OSHWM - Returned in XY hence the mod and
div }
oshwm :=
code0(osbyte,findOshwm,0,0) mod &1000000 div &100;

mcAddr := oshwm + mcOffset;
{ Simply print the integer and shift it left, 32
times }
for count := 0 to 31 do
begin
anInteger := 1;
dummy := code1(mcAddr,count,anInteger);
write(~anInteger : 10)
end;
writeln
end.

.нф

.ов

Единственная сложность заключается в определении значения OSHWM. Вспомним, что code0 и code1 возвращают результат в виде &PYXA. Нужные нам байты (X и Y), следовательно, находятся в середине целочисленной переменной. Выражение:

.лв

code0(osbyte,findOshwm,0,0) mod &1000000 div &100

.ов

сначала обнуляет первый байт (P), а затем сдвигает остаток на один байт вправо, так, что в результате мы получаем YX.

Если программа была записана под именем 'code1', ее надо откомпилировать:

COMPILE code1 O.code1

Пока мы не можем запустить объектный файл из-за отсутствия машинной подпрограммы в начале объектного файла. Для этого нужно воспользоваться сервисной программой на Паскале. Она запрашивает имена объектного файла программы на Паскале, файла с машинной подпрограммой и получаемого в результате выполнения этой программы, комбинированного объектного файла. Она создает комбинированный файл путем слияния двух заданных файлов. Имена файлов могут быть введены сразу после команды запуска прогаммы или по запросу программы во время ее выполнения.

.лв

.ао0

{¤T+}
program merge(input,output,codeFile,blFile,pascalFile);

{ This asks for the filenames of a machine code
file, a bl-code (Pascal object) file and a final
file. It merges the first two files to create the
last. }

const
nameLen = 10;
type
byte = 0..255;
name = packed array[1..nameLen] of char;
binary = packed file of byte;

var
codeFile : binary;
blFile : binary;
pascalFile : binary;
codeName : name;
blName : name;
pascalName : name;
prompt : boolean;

procedure readName(var fileName : name);
var
i : 1..nameLen;

begin
for i:=1 to nameLen do
fileName[i] := ' ';
while input^ = ' ' do
get(input);
i := 1;
repeat
read(fileName[i]);
i := i+1
until eoln or (fileName[i-1}=' ');
if eoln then
readln
end;

procedure copy(var sourceFile, destFile : binary);
var
b : byte;
begin
while not eof(sourceFile) do
begin
read(sourceFile, b);
write(destFile, b)
end
end;

begin { Main Program }

prompt := eoln;

if prompt then
write('Name of machine code file: ');
readName(codeName);

if prompt then
write('Name of object file: ');
readName(blName);

if prompt then
write('Name of final object file: ');
readName(pascalName);

reset(codeFile, codeName);
reset(blFile, blName);
rewrite(pascalFile, pascalName);

copy(codeFile, pascalFile);
copy(blFile, pascalFile);

write('Finished')

end.

.нф

.ов

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

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

SHIFTMC<ВВОД>
O.code1<ВВОД>
fin<ВВОД>

В результате мы получим выполняемый объектный файл 'fin'.

.ст pascal.a

[an error occurred while processing this directive]