Delphi World - это проект, являющийся сборником статей и малодокументированных возможностей  по программированию в среде Delphi. Здесь вы найдёте работы по следующим категориям: delphi, delfi, borland, bds, дельфи, делфи, дэльфи, дэлфи, programming, example, программирование, исходные коды, code, исходники, source, sources, сорцы, сорсы, soft, programs, программы, and, how, delphiworld, базы данных, графика, игры, интернет, сети, компоненты, классы, мультимедиа, ос, железо, программа, интерфейс, рабочий стол, синтаксис, технологии, файловая система...
Способ создания 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
Проект Delphi World © Выпуск 2002 - 2004
Автор проекта: ___Nikolay