Способ создания TSR программ без PSP
Оформил: DeeCo
В данной статье рассматривается способ построения резидентных программ - TSR (Terminate and Stay Resident), позволяющий использовать область PSP (Program Segment Prefix) для размещения резидентного кода программы.
Несколько слов о TSR.
При разработке TSR - программ стандартными средствами DOS в памяти после завершения программы остается PSP размером 256 байт или по крайней мере его часть, если программа использует область FCB (File Control Block) и параметров для собственных нужд. Мы расскажем Вам о способе, позволяющем полностью использовать область PSP в интересах резидентной программы и приведем пример такой программы. Данный способ был разработан авторами в начале 1989 года, когда потребовалось загрузить много крохотных TSR - программ, размер которых не превышал PSP.
Кое-что о недокументированных функциях DOS.
Для понимания механизма разработки TSR без PSP необходимо ознакомиться с некоторыми недокументированными функциями DOS и с форматами PSP и MCB - Memory Control Block.
PSP - program segment prefix.
PSP всегда строится DOS при запуске любой программы и непосредственно предшествует началу программы. Рассмотрим формат PSP.
Смещение |
Размер |
Описание |
00h |
2 БАЙТА |
содержит команду INT 20h, которая используется для
завершения программы |
02h |
СЛОВО |
сегментный адрес свободной памяти, следующей, за
памятью, выделенной программе. Это может быть либо адрес за памятью
DOS (например, A000h), либо адрес следующего доступного MCB. |
04h |
БАЙТ |
Резерв |
05h |
5 БАЙТ |
длинный вызов диспетчера функций DOS. Содержит
команду длинного перехода к диспетчеру функций DOS. Используется в
программах, ориентированных на CP/M. Смещение в команде длинного
перехода содержит количество байтов, доступных в сегменте кода
программы. |
0Ah |
ДВ.СЛОВО |
копия вектора прерывания 22h, по которому управление
передается для завершения программы. |
0Eh |
ДВ.СЛОВО |
копия вектора прерывания 23h, по которому управление
передается при нажатии CONTROL-BREAK или CONTROL-C. |
12h |
ДВ.СЛОВО |
копия вектора прерывания 24h, по которому управление
передается при обнаружении критической ошибки. |
16h |
СЛОВО |
сегментный адрес PSP родительского процесса (адрес
текущего PSP для процесса, у которого нет родителя). |
18h |
20 БАЙТ |
FILE HANDLE TABLE. Содержит 20 однобайтовых индексов
для системной таблицы файлов. Первые пять входов предназначены для
STDIN, STDOUT,STDERR, AUXIO и LSTOUT. |
2Ch |
СЛОВО |
сегментный адрес блока среды для процесса. |
2Eh |
ДВ.СЛОВО |
область сохранения указателя стека процесса, когда
процесс использует стек DOS (т.е. содержимое SS:SP перед последним
подключением функции DOS с помощью INT 21h). |
32h |
СЛОВО |
максимальное количество входов в FILE HANDLE TABLE
(по умолчанию 20). |
34h |
ДВ.СЛОВО |
адрес FILE HANDLE TABLE (стандартно указывает на
таблицу в текущем PSP). |
38h |
24 БАЙТА |
Резерв |
50h |
3 БАЙТА |
команда INT 21h, за которой следует команда far RET.
Используется для вызова диспетчера функций DOS. |
53h |
2 БАЙТА |
Резерв |
55h |
7 БАЙТ |
расширение первого FCB. |
5Ch |
16 БАЙТ |
начальные байты первого неоткрытого FCB. Открытие
данного FCB приведет к разрушению второго FCB и байта с длиной
командной строки. |
6Ch |
16 БАЙТ |
начальные байты второго неоткрытого FCB. Открытие
данного FCB приведет к разрушению командной строки. |
7Сh |
ДВ.СЛОВО |
Резерв |
80h |
128 БАЙТ |
область DTA (Data Transfer Area) по умолчанию.
Перекрывает байт с длиной командной строки и буфер командной строки
(127 байтов). |
Размер блока - 256 байт.
MCB - memory control block.
MCB является блоком DOS описывающим каждый распределенный участок памяти. Как правило MCB всегда строится перед PSP исполняемой программы. Рассмотрим формат MCB.
Смещение |
Размер |
Описание |
00h |
БАЙТ |
тип блока: 'M' (4Dh) - промежуточный блок; 'Z'
(5Ah) - последний блок. |
01h |
СЛОВО |
сегмент владельца блока, 0 - свободный блок. |
03h |
СЛОВО |
количество параграфов в блоке. |
05h |
11 БАЙТ |
Резерв |
Размер блока - 16 байт.
Недокументированные функции DOS.
Рассмотрим теперь недокументированные функции DOS, которые используются для построения TSR без PSP.
установить текущий PSP.
Данная функция указывает DOS, что в качестве текущего следует использовать указанный PSP.
Входные параметры:
AH = 50h
BX = сегментный адрес нового PSP.
создать подчиненный PSP.
Данная функция требует от DOS создать подчиненный PSP. В отличии от функции 26h данные не копируются из текущего PSP, а строятся заново.
Входные параметры:
AH = 55h
BX = сегментный адрес для построения нового PSP.
SI = значение, которое требуется установить в поле со смещением 2 в новом PSP.
Процесс завершения резидентной программы.
Сущность завершения резидентной программы без PSP состоит в создании нового PSP и указании DOS использовать этот PSP в качестве активного. При этом сам текст резидентной части перемещается на начало старого PSP.
Рассмотрим последовательность действий при завершении программы.
Освободить ENVIRONMENT, адрес которого находится в PSP по смещению 2Ch. Вообще говоря, это действие не связано с собственно процессом завершения резидентной программы без PSP и рекомендуется для любого способа завершения резидентных программ.
Изменить размер памяти, используемый программой, указав в качестве нового размера длину резидентной части программы в параграфах, а в качестве адреса модифицируемой памяти - сегментный адрес PSP. Для выполнения используется функция DOS 4Ah. Если резидентная часть начинается с начала программы, то ее следует сначала сохранить, т.к. при освобождении памяти в теле программы будет построен новый MCB. Также целесообразно зарезервировать после резидентной части участок длиной 16 байт для MCB, чтобы последующие действия не уничтожили дальнейший код программы.
Вычисляется сегментный адрес для построения нового PSP по формуле <длина резидентной части в параграфах>+<сегментный адрес старого PSP>+1 (размер MCB в параграфах).
Строится новый PSP по вычисленному сегментному адресу с использованием функции 55h. В качестве значения SI берется поле со смещением 2 в старом PSP.
Корректируется адрес родительского PSP в новом PSP по смещению 16h. Значение выбирается из поля с таким же смещением из старого PSP.
Новый PSP указывается DOS в качестве текущего с использованием функции 50h.
Корректируется указатель на собственника памяти в новом MCB по смещению 01h. MCB расположен по сегментному адресу равному сегменту нового PSP минус 1. Значение
указателя устанавливается равным сегментному адресу нового PSP.
Резидентная часть программы пересылается по адресу сегмента старого PSP со смещением 0.
Выполняются необходимые действия для настройки резидентной части. Их можно также выполнить заранее.
Программа завершается обычным образом по функции DOS 04Ch.
Совместимость.
Данный метод опробован в различных совместимых операционных средах:
- MS/PC DOS 3.30;
- MS/PC DOS 4;
- MS DOS 5 beta release;
- DR DOS 3.41;
- DR DOS 5;
- с использованием загрузчика LOADHI от системы QEMM 5.0;
- с использованием загрузки в старшую память HILOAD DR DOS 5.
Пример резидентной программы без PSP.
Для лучшего понимания порядка завершения резидентной программы без PSP приведем пример.
page 60, 132
title NONPSP - Резидентная программа без PSP
;
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
= = = = = = = = = = = = = = = = =
;
;
Пример построения резидентной программы без PSP
;
;
Авторские права ГРУППЫ ПРОГРАММИСТОВ 2 B
;
;
Язык программирования: Ассемблер
;
Транслятор: MASM
;
;
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
= = = = = = = = = = = = = = = = = =
;
;
сообщить транслятору адресацию
;
assume cs: code, ds: code, es: nothing
;
;
определить сегмент кода
;
code segment para
;
subttl Резидентная часть программы
;
ResStart label
byte;
определим начало программы
;
Текст резидентной части.
ResEnd label
byte;
конец резидентной части
ResLen equ ResEnd - ResStart;
размер резидентной части в
;
байтах
ResSize equ(ResEnd - ResStart + 15) / 16;
размер резидентной
;
части в параграфах
org ResStart + ResSize * 16;
для выравнивания на
;
границу параграфа
;
page
subttl Инициализация резидентной программы без PSP
;
MCBLen equ 10 h;
размер MCB
;
PSPMCB db MCBlen dup(0);
резервная область для MCB
SavRes db ResLen dup(0);
область сохранения
;
резидентной части
;
PspOld dw 0;
для адреса старого PSP
PspNew dw 0;
для адреса нового PSP
;
;
сообщения
;
MsgInst label
byte
db 0 ah, 0 dh
db 'Программа установлена'
db 0 ah, 0 dh
db '$'
MsgNoInst label
byte
db 0 ah, 0 dh
db 'Ошибка. Программа не установлена'
db 0 ah, 0 dh
db '$'
;
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
= = = = = = = = = = = = = = = = =
;
;
процедура инициализации резидентной программы без PSP
;
;
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
= = = = = = = = = = = = = = = = =
ini proc far
;
cld;
для операций
пересылки
mov cs: PspOld, ds;
сохраним старый PSP
;
;
Освобождение environment
;
mov ax, ds: [2 ch];
адрес сегмента
mov es, ax;
environment
cmp ax, 0;
есть environment ?
je Go1;
нет
mov ah, 49 h;
код функции
int 21 h;
освободить
;
память
jnc Go1;
успешно
jmp ErrorRet;
неуспешно
Go1:
mov word ptr ds: [2 ch], 0;
укажем, что нет
;
environment
push cs;
установим
pop es;
ES
push cs;
установим
pop ds;
DS
;
;
запомним резидентную часть
;
т.к.она будет разрушена при построении новых PSP и MCB
;
mov si, Offset ResStart;
откуда
mov di, Offset SavRes;
куда
mov cx, ResLen;
сколько
rep movsb;
перешлем
;
;
модифицируем размер памяти текущей программы от сегмента
;
PSP на длину резидентной части
;
это приведет к построению нового MCB в теле программы
;
mov ah, 4 ah;
модифицируем память
mov bx, ResSize;
размер резидентной
;
части
mov es, PspOld;
сегмент старого PSP
int 21 h;
выполним
jnc Go2;
успешно
jmp ErrorRet;
неуспешно
Go2:
;
;
подсчитаем сегмент памяти для нового PSP
;
mov ax, ResSize;
длина резидентной
;
части в параграфах
add ax, PspOld;
плюс сегмент
;
старого PSP
add ax, MCBLen / 16;
плюс длина MCB
mov PspNew, ax;
запомним сегмент
;
для нового PSP
;
;
создадим новый PSP
;
mov es, PspOld;
адрес старого PSP
mov si, word ptr es: [02 h];
конец памяти
mov ah, 55 h;
построим
;
подчиненный PSP
mov dx, PspNew;
сегмент нового PSP
int 21 h;
выполним
;
;
скорректируем указатель на родительский PSP в новом PSP
;
mov es, PspOld;
сегмент старого PSP
mov ax, word ptr es: [16 h];
адрес родительского
;
PSP
mov es, PspNew;
сегмент нового PSP
mov word ptr es: [16 h], ax;
скорректируем
;
;
сообщим DOS о новом активном PSP
;
mov bx, PspNew;
сегмент нового PSP
mov ah, 50 h;
функция указания нового PSP
int 21 h;
выполним
;
;
укажем принадлежность полученной памяти для нового PSP
;
mov ax, bx;
сегмент нового PSP
sub ax, 1;
сегмент
;
построенного MCB
mov es, ax
;
= = = = = = = = = = = = = = = = = =
mov word ptr es: [01 h], bx;
скорректируем адрес
;
владельца памяти
;
;
переместим резидентную часть на старый PSP
;
mov es, PspOld;
адрес старого PSP
mov cx, ResLen;
размер резидентной
;
части
mov di, 0;
куда
mov si, Offset SavRes;
откуда
rep movsb;
переместим
push cs;
восстановим
pop es;
ES
;
;
Успешный выход из программы
;
PrgEnd:
mov ah, 09 h;
выведем сообщение
mov dx, offset MsgInst;
об успешной
;
установке
int 21 h;
mov ax, 4 c00h;
завершим программу
int 21 h;
обычным
;
образом с кодом 0
;
;
Выход из программы по ошибке
;
ErrorRet:
push cs;
установим адресацию
pop ds;
данных
mov ah, 09 h;
выведем сообщение
mov dx, offset MsgNoInst;
о неуспешной
;
установке
int 21 h;
mov ax, 4 c04h;
завершим программу
int 21 h;
обычным
;
образом с кодом 4
ini endp
;
code ends
;
;
СТЕК
;
stack segment stack
dw 512 dup(0)
stack ends
end ini
|