5.9.1. Пассивная резидентная программа

В качестве первой резидентной программы рассмотрим именно пассивный

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

; tsr.asm

; Пример пассивной резидентной программы.

; Запрещает удаление файлов на диске, указанном в командной строке, всем

; программам, использующим средства DOS.

.model

tiny

 

 

 

. code

 

 

 

 

org

2Ch

 

 

 

envseg

dw

?

;  Сегментный адрес

копии окружения

org

80h

 

 

 

cmd_len.

db

?

;  Длина командной

строки.

cmd_line

db

?

;  Начало командной

строки.

org

100h .

 

; СОМ-программа.

 

start:

old_int21h: jmp

int21h_handler pushf cmp

je

cmp

je jmp

fn41h:

push push mov

cmp

je mov int add

jmp

full_spec:

compare:

mov and

cmp

je pop pop not_fn41h:

popf

jmp

access_denied:

pop pop

popf

push

mov or

pop

short dw

proc

initialize

0

far

ah,41h

fn41h

ax,7141h

fn41h ,

short not fn4lh

ax bx

bx.dx

byte ptr ds:[bx+1],

full_spec ah,l9h 21h al, 'A'

short compare

al.byte ptr [bx] : al,11011111b ;

al.byte ptr cs:crad_line[i:

access_denied ; bx ; ax ;

dword ptr cs:old_int21h

bx ax

bp

bp, sp

word ptr

bp

Эта команда занимает 2 байта,  так что

; вместе с ними получим old_int21h dd ?. Обработчик прерывания 21h. Сохранить флаги.

Если вызвали функцию 41h (удалить файл)

или 7141п (удалить файл с длинным именем), начать наш обработчик. Иначе - передать управление предыдущему обработчику.

Сохранить модифицируемые

регистры.

Если второй символ

переданной INT 21h, двоеточие - первый

символ должен быть именем диска. Иначе -

функция DOS 19h - определить текущий диск. Преобразовать номер диска

к заглавной букве.

Перейти  к сравнению.

AL = имя диска

Преобразовать к

из ASCIZ-строки.

заглавной букве.

; Если диски

совпадают - запретить доступ. Иначе - восстановить регистры

и флаги

и передать управление

предыдущему обработчику INT 21h.

Восстановить регистры.

Установить флаг переноса

(бит 0) в регистре флагов,

который поместила команда INT в стек перед адресом возврата.

mov       ax,5 ; Возвратить код ошибки "доступ запрещен".

; Вернуться в программу.

int21h_handler endp

proc near

cmp       byte ptr cmd_len,3 ; Проверить размер командной строки

jne       not.install ; (должно быть 3 - пробел, диск, двоеточие)..

cmp       byte ptr cmd_line[2], ' :' ; Проверитв третий символ

jne       not.install ; командной строки   (должно бытв двоеточие).

mov       al.byte ptr cmd_line[1]

and       al. 11011111b ; Преобразоватв второй

символ к заглавной букве.

cmp       al, 'A' ; Проверитв,  что это не

■ jb        not.install ; менвше "А" и не болвще

cmp       al,1Z* ; "Z".

ja         not.install ; Если хотв одно из этих условий

не выполняется - выдать информацию

о программе и выйти.

Иначе - начать процедуру инициализации.

mov ax,3521h ; АН = 35h, AL = номер прерывания,

int 21h ; Получитв адрес обработчика INT 21h

mov word ptr old_int21h, bx ; и поместитв его в old_int21h.

mov word ptr old_int21h+2,es

mov ax,2521h ; AH = 25h, AL = номер прерывания,

mov dx,offset int21h_handler ; DS:DX - адрес нашего обработчика,

int 21b - ; Установитв обработчик INT 21 п.

mov        ah,49h ; AH = 49h.

mov       es.word ptr envseg ; ES = сегментный адрес блока с нашей

; копией окружения DOS.

int       21 п ; Освободить памятв из-под окружения.

mov       dx, offset  initialize       ; DX - адрес первого байта за концом

; резидентной части программы, int       27b ; Завершитв' выполнение,  оставшисв ■

резидентом.

not_install:

mov        ah, 9 ; АН = 09h

mov       dx,offset usage ; DS:DX = адрес строки с информацией об

использовании программы. int       21b ; Вывод строки на экран,

ret ' ; Нормальное завершение программы.

; Текст,  который выдает программа при запуске с неправилвной командной строкой: usage db "Исполвзование:  tsr.com D:",ODh,OAh

db "Запрещает удаление на диске D:",ODh,OAh

db "$"

initialize endp end start

Резидентные

Если запустить эту программу с командной строкой D:, никакой файл на дис­ке D нельзя будет удалить командой Del, средствами оболочек типа Norton Commander и большинством программ для DOS. Действие этого запрета, однако,

не будет распространяться на оболочку Far, которая использует системные функ­ции Windows API, и на программы типа Disk Editor, обращающиеся с дисками при помощи функций BIOS (INT 13h). Несмотря на то что мы освободили память, занимаемую окружением DOS (а это могло быть лишних 512 или даже 1024 бай­та), наша программа все равно занимает в памяти 352 байта потому, что первые 256 байт отводятся для блока PSP. Существует возможность оставить программу резидентной без PSP - для этого инсталляционная часть программы должна ско­пировать резидентную часть с помощью, например, movs в начало PSP. Но при этом возникает сразу несколько проблем: во-первых, команда INT 27h, так же как и функция DOS 31h, использует данные из PSP для своей работы; во-вторых, код резидентной части должен быть написан для работы с нулевого смещения, а не со как обычно; и, в-третьих, некоторые программы, исследующие выделенные блоки памяти, определяют конец блока по адресу, находящемуся в PSP програм­мы - владельца блока со смещением 2. С первой проблемой можно справиться

вручную, создав отдельные блоки памяти для резидентной и инсталляционной частей программы, новый PSP для инсталляционной части и завершив програм­му обычной функцией 4Ch или INT 20h. Реальные программы, делающие это, су­ществуют (например, программа поддержки нестандартных форматов дискет PU_1700), но мы не будем чрезмерно усложнять наш первый пример и скопиру­ем резидентную часть не в позицию 0, а в позицию        то есть, начиная

с середины PSP, оставив в нем все значения, необходимые для нормальной рабо­ты функций DOS.

Прежде чем это сделать, заметим, что и номер диска, и адрес предыдущего обработчика INT 21h изменяются только при установке резидента и являются

константами во время всей его работы. Более того, каждое из этих чисел исполь­зуется только по одному разу. В таких условиях оказывается, что можно вписать номер диска и адрес перехода на старый обработчик прямо в код программы. Кроме того, после этого наш резидент не будет больше ссылаться ни на какие переменные с конкретными адресами, а значит, его код становится перемещаемым, то есть его можно выполнять, скопировав в любую область памяти.

; tsrpsp.asm

; Пример пассивной резидентной программы с переносом кода в PSP. ; Запрещает удаление файлов на диске, указанном в командной строке,

;  всем программам,  использующим средства DOS.

 

. model

tiny

 

 

.code

 

 

 

org

2Ch

 

envseg

dw

і

; Сегментный адрес копии окружения

 

org

80h

 

cmd len

db

?

; Длина командной строки.

line

db

і

; Начало командной строки. ■

 

org

100h

; СОМ-программа.

IP

Сложные приемы программирования

start: old_int21h:

jmp '

'int21h_handler

pushf cmp

' je

cmp

je

.jmp

fn41h:

push push mov

cmp

je

mov int

add

jmp full_spec:

mov and

compare:

drive_letter:

pop pop je

not_fn41h:

popf

old int21h

short initialize, proc far

ah,41h

fn41h ax,7141h

fn41h

short not fn41h

ax bx

bx, dx

byte ptr [bx+1],'

full_spec ah,I9h 21h

al, 'A'

short compare

al.byte ptr [bx] al,11011111b

db db

3Ch

bx

ax

access denied

db dd

OEah 0

Переход на инициализирующую часть.

Обработчик прерывания 21h. Сохранить флаги. Если вызвали функцию 41 h (удалитв файл)

или 7141п (удалить файл с длинным именем), начать наш обработчик. Иначе - передать

управление предыдущему обработчику.

Сохранить модифицируемые

регистры.

Можно было бы исполвзоватв адресацию но в старшем

слове EDX совсем необязательно 0. '   ;  Если второй символ ASCIZ-строки,

переданной INT 21h, двоеточие, первый символ должен быть именем диска.

Иначе:

функция DOS 19h - определить текущий диск. Преобразовать номер диска к заглавной букве.

Перейти  к сравнению.

AL = имя диска из ASCIZ-строки.

Преобразовать к заглавной букве.

Начало кода команды CMP AL-, число/

Сюда процедура инициализации впишет нужную букву. Эти регистры больше не понадобятся.   Если диски совпадают -запретить доступ.

Восстановить флаги и передать управление предыдущему обработчику  INT 21h:. Начало кода команды JHP, число FAR. Сюда процедура инициализации запишет адрес предыдущего обработчика INT 21h. '

access_denied:

popf

push bp

mov bp.sp

tsr_length

initialize cmp

jne

cmp

jne

mov

and

cmp

jb

cmp

mov

push

mov

int

mov mov pop

eld

mov mov rep

word ptr [bp+6],1

pop bp

mov * ax, 5 і ret

int21h_handler endp

equ

$-int21h_handler

proc near

byte ptr cmd_len,3

not_install

byte ptr :'

not_install

al,byte ptr cmd_line[1] al,11011111b

al, 'A'

not_install al, 'Z'

not install

byte ptr

es

ax,3521h

21h

word ptr bx

word ptr old_int21h+2,es

es

si,offset int21h_handler di,80b

movsb

Чтобы адресоваться в стек

в реальном режиме,

установить флаг

переноса  (бит 0)  в регистре

флагов,  который поместила команда INT

в стек перед адресом возврата.

Возвратить код ошибки "доступ запрещен". Вернуться в программу.

Проверить размер командной строки (должно быть 3 -пробел,  диск, двоеточие).

Проверить

третий символ командной

строки (должно быть двоеточие).

Преобразовать второй

символ к заглавной букве. Проверить, что это не меньше "А" и не больше

Если хоть одно из этих условий не выполняется - выдать информацию о программе и выйти. Иначе - начать процедуру инициализации. Вписать имя

диска в код резидента. АН = 35п,

АР = номер прерывания.

Получить адрес

обработчика ШТ 21 п.

и вписать его в код резидента.

Перенос кода резидента, начиная с этого адреса, в PSP:0080h.

or

int

mov mov

int

int

not_install:

mov mov

int

ret ax,2521h

dx,0080h 21ґі

ah,49h

ptr envseg

21h

dx,80h+tsr_length

27h

ah,9

usage

21h

AH

AL = номер прерывания.

DS:DX - адрес нашего обработчика.

Установить обработчик INT- 21 п. АН = 49h

ES = сегментный адрес блока

с нашей копией окружения DOS. Освободить память из-под

окружения.

DX - адрес первого байта

за концом резидентной части

программы.

Завершить выполнение,

оставшись резидентом. АН = 09h.

DS;DX = адрес строки

с информацией об использовании программы. Вывод строки на экран.

Нормальное завершение

программы.

; Текст,  который выдает программа при запуске

; с неправильной командной строкой:

usage db "Usage:  tsr.com D:",ODh,OAh

db "Denies delete on drive D:",ODh,OAh

initialize

db

endp

end

start

Теперь эта резидентная программа занимает в памяти только 208 байт. 5.9.2. Мультиплексорноепрерывание

Если вы запустите предьщущий пример несколько раз, с разными или даже оди­наковыми именами дисков в командной строке, объем свободной памяти DOS вся­кий раз будет уменьшаться на 208 байт, то есть каждый новый запуск устанавлива­ет дополнительную копию резидента, даже если она идентична уже установленной. Разумеется, это неправильно - инсталляционная часть обязательно должна уметь определять, загружен ли уже резидент в памяти перед его установкой. В нашем случае это не приводит ни к каким последствиям, кроме незначительного умень­шения объема свободной памяти, но во многих чуть более сложных случаях мо­гут возникать различные проблемы, например многократное срабатывание актив­ного резидента по каждому аппаратному прерыванию, которое он перехватывает.

Для того чтобы идентифицировать себя в памяти, резидентные программы обычно или устанавливали обработчики для неиспользуемых или

mov

mov

вводили дополнительную функцию в используемое прерывание. Например: наш резидент мог бы проверять в обработчике INT 21h АН на равенство какому-ни­будь числу, не соответствующему функции DOS, и возвращать в, например, AL код, означающий, что резидент присутствует. Очевидная проблема, связанная с таким подходом, - вероятность того, что кто-то другой выберет то же неисполь­зуемое прерывание или что будущая версия DOS станет использовать ту же фун­кцию. Именно для решения этой проблемы, начиная с версии DOS 3.3, был пре­дусмотрен специальный механизм, позволяющий разместить до 64 резидентных программ в памяти одновременно, - мулътиплексорное прерывание.

INT2FH: Мультиплексорное прерывание . Вход:   АН = идентификатор программы

-       зарезервировано для . OB8h - OBFh зарезервировано для сетевых функций OCOh - OFFh отводится для программ . AL = код функции

OOh - проверка наличия программы остальные функции - свои для каждой программы ВХ, СХ, DX = 0 (так как некоторые программы выполняют те или иные действия в зависимости от значений этих регистров)

Выход:

Для подфункции AL = OOh, если установлен резидент с номером АН, он дол­жен вернуть OFFh в AL и какой-либо идентифицирующий код в других регист­рах, например адрес строки с названием и номером версии. Оказалось, что такого уровня спецификации совершенно недостаточно и резидентные программы по-

прежнему работали по-разному, находя немало способов конфликтовать между

собой. Поэтому появилась новая спецификация - AMIS (альтернативная специ­фикация мультиплексорного прерывания). Все резидентные программы, следую­щие этой спецификации, обязаны поддерживать базовый набор функций AMIS,

а их обработчики прерываний должны быть написаны в соответствии со стандар­том IBM ISP, который делает возможным выгрузку резидентных программ из памяти в любом порядке.

Начало обработчика прерывания должно выглядеть следующим образом:

+00h: 2 байта - ОЕВЬДОЬ (команда jmp short на первый байт после этого блока) +02h: 4 байта — адрес предыдущего обработчика: именно по адресу, хранящемуся

здесь, обработчик должен выполнять call или jmp +06h: 2 байта - 424Bh - сигнатура ISP-блока

+08h: байт    - 80h, если это первичный обработчик аппаратного прерывания (то

есть он посылает контроллеру прерываний сигнал EOI) OOh, если это обработчик программного или дополнительный об­работчик аппаратного прерывания

+09h: 2 байта - команд-ajmp short на начало подпрограммы аппаратного сброса -обычно состоит из одной команды IRET

+0Bh: 7 байт  - зарезервировано

Все стандартное общение с резидентной программой по спецификации AMIS-происходит через прерывание 2Dh. При установке инсталляционная часть рези­дентной программы должна проверить, нет ли ее копии, просканировав все иден­тификаторы от 00 до OFFh, и, если нет, установить обработчик на первый свобод­ный идентификатор.

INT2Dh: Мультиплексорное прерывание AMIS

Вход: АН

=

идентификатор программы

AL

=

00: проверка наличия

AL

=

получить адрес точки входа

AL

=

02: деинсталляция

AL

=

03: запрос на активизацию (для «всплывающих» программ)

AL

=

04: получить список перехваченных прерываний

AL

=

05: получить список перехваченных клавиш

 

=

06: получить информацию о драйвере (для драйверов устройств)

AL

=

0У -       - зарезервировано для AMIS

AL

=

- OFFh - свои для каждой программы

Выход: AL = OOh, если функция не поддерживается

Рассмотрим функции, описанные в спецификации AMIS как обязательные.

INT2DhAL = OOh: Функция AMIS - проверка наличия резидентной программы Вход:    АН = идентификатор программы AL - OOh

Выход: AL = OOh, если идентификатор не занят AL = OFFh, если идентификатор занят

СН = старший номер версии программы CL = младший номер версии программы

DX:DI = адрес AMIS-сигнатуры, по первым 16 байтам которой и про­исходит идентификация.

Первые 8 байт - имя производителя программы; следующие 8 байт -имя программы; затем или 0 или ASCIZ-строка С опи­санием программы, не больше 64 байт.

AL =     Функция AMIS - выгрузка резидентной программы из памяти

Вход:    АН = идентификатор программы AL = 02h

DX:BX = адрес, на который нужно передать управление после выгрузки

Выход:

AL =      - выгрузка не удалась

AL = 02h - выгрузка сейчас невозможна, но произойдет чуть позже AL =      - резидент не умеет выгружаться сам, но его можно выгрузить,

все еще активен ВХ = сегментный адрес резидента

AL = 04h - резидент не умеет выгружаться сам, но его можно выгрузить, резидент больше неактивен

BX = сегментный адрес резидента

AL = 05h - сейчас выгружаться небезопасно - повторить запрос позже AL = 06h - резидент был загружен из CONFIG.SYS и выгрузиться не­может, резидент больше неактивен AL = 07h - это драйвер устройства, который не умеет выгружаться сам BX = сегментный адрес

AL = OFFh с передачей управления на DX:BX - успешная выгрузка

AL =    Функция AMIS - запрос на активизацию Вход:  АН = идентификатор программы

AL = 03с

Выход: AL = 00h - резидент - «невсплывающая» программа

AL =      - сейчас «всплывать» нельзя - повторить запрос позже AL = 02h - сейчас «всплыть» не могу, но «всплыву» при первой возмож­ности

AL = 03h - уже «всплыл»

AL = 04h - «всплыть» невозможно

BX, CX - коды ошибки

AL = OFFh - программа «всплыла», отработала и завершилась BX - код завершения

AL = Вход: АН

Функция AMIS - получить список перехваченных прерываний

= идентификатор программы AL - 04h Выход: AL = 04h

DX:BX = адрес списка прерываний, состоящего из 3-байтных структур: байт 1: номер прерывания (2 Dh должен быть последним) байты 2, 3: смещение относительно сегмента, возвращенного в DX обработчика прерывания (по этому смещению должен

Находиться стандартный заголовок ISP)

INT2DhAL = 05h: Функция AMIS - получить список перехваченных клавиш Вход:    АН = идентификатор программы AL = 05h

Выход:

AL = OFFh - функция поддерживается

DX:BX = адрес списка клавиш:

+00h: 1 байт: тип проверки клавиши:

бит 0: проверка до обработчика INT 9

бит 1: проверка после обработчика INT 9

бит 2: проверка до обработчика INT 15h/AH= 4Fh

бит 3: проверка после обработчика INT 15h/AH = 4Fh

бит 4: проверка при вызове INT 16h/AH = 0, 1,2

бит 5: проверка при вызове INT 16h/AH = 10h, llh, 12h

бит 6: проверка при вызове INT 16h/AH= 20h, 21h, 22h

бит 7: О

І байт: количество перехваченных клавиш +02h: массив структур по 6 байт: байт 1: скан-код клавиши (старший бит - отпускание клавиши, 00/80h -

если срабатывание только по состоянию Shift-Ctrl-Alt и т, д.) байты 2,3: необходимое состояние клавиатуры (формат тот же, что и в сло­ве состояния клавиатуры, только бит У соответствует нажатию

любой клавиши Shift)

байты 4, 5: запрещенное состояние' клавиатуры (формат тот же)

байт 6: способ обработки клавиши

бит 0: клавиша перехватывается до обработчиков. бит    клавиша перехватывается после обработчиков бит 2: другие обработчики не должны «проглатывать» клавишу бит 3: клавиша не сработает, если, пока она была нажата, нажима­ли или отпускали другие клавиши

бит 4: клавиша преобразовывается в другую

бит 5: клавиша иногда «проглатывается», а иногда передается

дальше биты 6, У: О

Теперь можно написать резидентную программу, и она не загрузится дважды

в память. В этой программе установим дополнительный обработчик на аппарат­ное прерывание от клавиатуры IRQ1 (INT 9) для отслеживания комбинации кла­виш Alt-A; после их нажатия программа перейдет в активное состояние, выведет на экран свое окно и среагирует уже на большее количество клавиш. Такие про­граммы, активизирующиеся при нажатии какой-либо клавиши, часто называют «всплывающими» программами, но наша программа на самом деле будет только казаться «всплывающей». Настоящая «всплывающая» программа после активи­зации в обработчике INT 9h не возвращает управление до окончания работы пользователя. В нашем случае управление возобновится после каждого нажатия клавиши, хотя сами клавиши будут поглощаться программой, так что ей можно пользоваться одновременно с работающими программами, причем на скорости их работы активный ascii.com никак не скажется.

Так же как и с предыдущим примером, не использующие средства

DOS/BIOS для работы с клавиатурой, например файловый менеджер FAR, будут получать все нажатые клавиши параллельно с нашей программой, что приведет к нежелательным эффектам на экране. Кроме того, в этом упрощённом примере

отсутствуют некоторые необходимые проверки (например, текущий видеорежим)

и функции (например, выгрузка программы из памяти), но тем не менее это ре­ально используемая программа. С ее помощью легко посмотреть, какой символ соответствует какому ASCII-коду, и ввести любой символ, которого нет на клави­атуре, в частности псевдографику.

; ascii.asm

; Резидентная программа для просмотра и ввода ASCII-символов. ; HCI:

API:

Alt-A - активизация программы. Клавиши управления курсором - выбор символа. Enter - выход из программы с вводом символа. Esc - выход из программы без ввода символа.

Программа занимает первую свободную функцию прерывания 2Dh в соответствии со спецификацией AMIS 3.6. Поддерживаются функции AMIS OOh,   02h,   03h,  04h и 05h. Обработчики прерываний построены в соответствии с 1MB ISP.

; Адрес верхнего левого угла окна (23-я позиция START_P0SITI0N

в третьей строке).

envseg

start;

.model . code .186

org

org

jmp

equ <80*2+23)*2

tiny 2Ch

dw ? lOOh

initialize

hw resets):

retf

Для сдвигов и команд pusha/popa. ■

Сегментный адрес окружения DOS. Начало СОМ-программы.

Переход на инициализирующую часть.

ISP: минимальный hw_ reset.

Обработчик прерывания 09h (IRQ1)

int09h_handler proc

jmp short old_int09h dd

dw db

jmp short db

actual int09h handler:

far

actual_int09h_handler

7                        ; ISP:

ISP:

OOh                      ; ISP:

hw_reset9                ; ISP:

7 dup  (0)           ; ISP:

; ISP: протгустить блок, старый обработчик.

сигнатура.

вторичный обработчик, ближний jmp на hw„reset.

зарезервировано.

Начало обработчика INT 09h.

:   Сначала вызовем предыдущий обработчик,   чтобы дать BIOS возможность

;  обработать прерывание и,  если это было нажатие клавиши,  поместить код

;   в клавиатурный буфер,  так как мы пока не умеем работать с портами клавиатуры

; и контроллера прерываний.

pushf

call      dword ptr cs:old_int09h

; По этому адресу обработчик INT 2Dh

; для программы. disable_point       label byte

pusha

push push

eld

ds

запишет код команды IRET

Это аппаратное прерывание сохранить все регистры.

надо

;  Флаг для команд строковой обработки.

es

push

pop

push

pop mov cmp

je

cmp

jne

cmp

jne

test

jz

call

push

pop

call

mov jmp