;Строка записи времени в формате 00:00:00. Каждый символ строки представляется двумя байтами:

;кодом символа и атрибутом цвета

clock DB 2 DUP(20h,1Fh),":",1Fh ;1Fh - белый по синему

DB 2 DUP(20h,1Fh),":",1Fh,2 DUP(20h,1Fh)

clock_len = $-clock

locate DW 160 ;Позиция точки вывода на экране

Endp new_1Ch

;Основная программа

Proc main

mov ax,@data

mov ds, ax

;Получим и сохраним адрес старого обработчика 1Сh с помощью функции DOS 35h int 21h

mov ax,351Ch;AL=1Ch - номер вектора

int 21h

mov [word old_1Ch],bx ;offset вектора 1Ch

mov [word old_1Ch+2],es ;segment вектора 1Ch

;Установим новый обработчик new_1Ch функцией DOS 25h int 21h. Адрес обработчика должен

;быть в DS:DX

mov ax,251Ch ;AL=1Ch - номер вектора

mov dx, offset new_1Ch

push ds ;Сохраним ds

mov bx, seg new_1Ch

mov ds, bx

int 21h

pop ds ;Восстановим ds

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

mov cx,10 ;Счётчик числа выводов строки

out_str: mov ah,09h

mov dx, offset string

int 21h

;Задержка, включающая два вложенных цикла

push cx ;Сохраним счётчик выводов

mov cx,1000 ;Параметр задержки

L2: push cx

xor cx, cx

L1: loop L1

pop cx

loop L2

pop cx

loop out_str

;Восстановим вектор 1Сh в таблице векторов функцией DOS 25h int 21h

mov ax,251Ch ;AL=1Ch - номер вектора

lds dx,[old_1CH] ;ds:dx - вектор 1Ch

int 21h

Exit: mov ax,4C00h ;Выход в DOS

int 21h

Endp main

END main ;Конец программы/точка входа

Комментарий: Поля данных основной программы находятся, как это и положено, в сегменте данных, а обработчика new_1Ch в сегменте кода обработчика. Это структурирует программу, но обязывает программиста быть внимательным при выполнении ссылок в обработчике к данным, находящимся в памяти. Функция 02h int1Ah выводит время CMOP - часов в двоично-десятичном формате (код BCD), поэтому в состав обработчика new_1Ch включена подпрограмма bcd_ascii перевода в двоичную ASCII - строку. В обработчике применён непосредственный вывод в видеопамять (текстовый режим: 25 строк по 80 символов), когда каждое знакоместо представляется двумя байтами: символом и его атрибутом (атрибут задаёт цвет символа и фон под ним). Начальная позиция курсора задаётся переменной locate и представляет собой смещение знакоместа в диапазоне от B800h:0000h (левый верхний угол) до B800h:0F9Eh (правый нижний).

НЕ нашли? Не то? Что вы ищете?

Здесь не приходится думать о каких-либо способах сцепления обработчиков, поскольку это реализовано естественным путём, а именно включением обработчика 1Ch (а, следовательно, и new_1Ch) в состав системного обработчика 08h.

7. Клавиатура и системный обработчик 09h

Для уверенной работы с компьютером полезно понимать, каким образом вводятся и обрабатываются символы, вводимые с клавиатуры. Процесс взаимодействия системного обработчика прерываний от клавиатуры иллюстрируется схемой рис. 6. Работой клавиатуры управляет контроллер клавиатуры, который в сою очередь обменивается информацией с системным обработчиком с помощью портов 60h и 61h. Эти порты доступны по чтению и записи. При этом порт 60h – по чтению – является портом данных клавиатуры, в который заносится скан-код (номер клавиши) последней нажатой клавиши.


Порт 61h – это регистр, управляющий работой не только клавиатуры, но и другими устройствами (например, динамиком). Если в старший бит порта 61h записать “1”, клавиатура будет заблокирована, “0” – разблокирована. После чтения обработчиком скан-кода из порта 60h необходимо установить бит 7 в порту 61h в “1”, с последующим возвращением его в исходное состояние (данный механизм будет продемонстрирован в программе prg3). Считанный с порта 60h скан-код анализируется системным обработчиком 09h на предмет того, какая клавиша нажата – алфавитно-цифровая, клавиша с расширенным ASCII - кодом или управляющая. Если клавиша алфавитно-цифровая, то её ASCII - код (младший байт) и скан-код (старший байт) помещаются в кольцевой буфер клавиатуры (см. рис. 6). Если клавиша имеет расширенный ASCII-код (все функциональные клавиши F1…F12, клавиши управления курсором Home, End, PgUp, PgDown, ←, ↑, →, ↓ и др.), то этот двухбайтовый код с нулевым младшим байтом также помещается в кольцевой буфер.

Наконец, если нажата управляющая клавиша (Caps Lock, Shift, Ctrl, Alt, Num Lock, Scroll Lock, SysReg, Insert, Pause), то обработчик меняет соответствующий флаг в слове состояния клавиатуры (два байта по адресу 40h:17h и 40h:18h). При этом клавиши (Caps Lock, Num Lock, Scroll Lock) – действуют как триггеры: каждое нажатие переключает соответствующую настройку. Клавиша Insert является единственной, которая при нажатии выдаёт как код символа в буфер клавиатуры, так и меняет статус соответствующего флага клавиатуры. Кроме того, существует несколько комбинаций, которые не генерируют скан-коды, но распознаются системным обработчиком для вызова специальных процедур: Ctrl-Break (препывание 1Bh), Ctrl-Alt, Ctrl-Alt-Del (системный сброс), Shift-PrtScr (печать экрана), Ctrl-Num Lock (приостановка действий любой программы до нажатия какой-либо клавиши).

Слово-состояние клавиатуры и кольцевой буфер клавиатуры опрашиваюся обработчиком прерывания BIOS 16h, при этом функции DOS получают сведения о нажатых клавишах только через это прерывание.

Вернёмся к рассмотрению кольцевого буфера клавиатуры. Сформированный обработчиком двухбайтовый код алфавитно-цифровой или функциональной клавиши, или сочетания управляющих и функциональных клавиш, например, Shift F1, Ctrl-Home, Alt-Insert и т. п., засылаются в кольцевой буфер по адресу, хранящемуся в хвостовом указаh:1Ch). Этот адрес всегда указывает на первую свободную ячейку буфера, увеличиваясь на 2 после выполнения очередной записи в буфер. Каждое последующее нажатие на какую-либо клавишу заносит в буфер очередной код и смещает хвостовой указатель.

Выполняемая на компьютере программа, может получить код нажатой клавиши, если обратится к каким-либо системным средствам DOS (прерывание 21h) или BIOS (прерывание16h), которые считывают двухбайтовый код из кольцевого буфера по адресу, хранящемуся в головном указаh:1Ah) с последующим увеличением адреса на 2. Следовательно, программный запрос на ввод символа с клавиатуры, фактически воспринимает код не с клавиатуры, а с кольцевого буфера. Хвостовой указатель, увеличиваясь в процессе занесения в буфер кодов, доходит до его конца (40h:3Ch), а затем – при записи очередного кода – заново устанавливается на его начало (40h:1Eh) и так далее по кольцу. Аналогичные изменения происходят и с головным указателем. Буфер может хранить 15 нажатий клавиш и считается заполненным, когда разность между указателями становится равным 2 (содержимое 1Сh меньше на 2 содержимого ячейки 1Ah), или когда (частный случай) в 1Ah будет 1Eh, а в 1Ch – 3Ch.

Если на компьютере не выполняется никакая программа, то в активном состоянии находится командный процессор DOS , готовый к вводу команды с помощью функции DOS 0Ah прерывания 21h. Поступающие при этом с клавиатуры символы, заносятся в кольцевой буфер, а оттуда во внутренний буфер данных DOS c отображением вводимых символов на экране в командной строке DOS. При получении кода клавиши Enter, функция 0Ah завершает работу, а процессор , анализируя буфер DOS, приступает к выполнению команды.

8. Особенности обработки прерываний по Ctrl-C и Ctrl-Break

(дополнительно: об особенностях взаимодействия клавиатурного и системного контроллеров прерываний и учёта значений сегментных регистров в обработчиках прерываний)

В данном параграфе рассматри Scroll Lock ваются достаточно сложные для программистов вопросы, связанные с обработкой в компьютере процедур, вызванных известным нажатием клавиш Ctrl-C или Ctrl-Break, а также ряд вопросов по реализации аппаратных прерываний от клавиатуры.

Во многих прикладных программах сочетание клавиш Ctrl-C зарезервировано для принудительного завершения программы (для этого сочетания клавиш формируется код ASCII 03h). Все сервисные функции DOS делятся на две группы – функции ввода/вывода с номерами 01h – 0Ch и все остальные с номерами 00h, 0Dh – 6Ch, которые условно называют “дисковыми” (обслуживают в основном операции с дисками и файлами). Различие двух указанных групп проявляется в том, что каждая из них:

¨  Имеет свой внутренний стек, который и используется при вызове соответствующей сервисной функции из прикладной программы,

¨  По-разному выполняется проверка на Ctrl-C.

Функции ввода/вывода в своём большинстве проверяют код 03h в кольцевом буфере клавиатуры и при его обнаружении вызывают прерывание Int 23h. Системный обработчик этого прерывания завершает текущею программу вызовом известной нам функции DOS 4Ch. Дисковые функции выполняют проверку на Ctrl-C лишь в том случае, если установлен флаг Break, то есть была выполнена команда DOS Break on.

Поскольку проверка на Ctrl-C осуществляется только при выполнении функции DOS, нажатием на Ctrl-C в системе MS-DOS нельзя завершить чисто вычислительную задачу, - надо ждать вызова какой-нибудь системной функции. Однако завершение задачи нажатием клавиши Ctrl-C является не всегда желательным действием, так как при этом могут остаться не восстановленными изменённые вектора прерываний. Именно, поэтому ряд прикладных программ вводят собственные обработчики прерывания new_Int23h.

Система MS-DOS предоставляет и другую возможность вмешательства в ход исполнения программы – нажатие клавиш Ctrl-Break. Распознавание этой комбинации также осуществляется системным обработчиком 09h. При этом выполняется прерывание BIOS 1Bh, содержащее единственную команду Iret. Однако в процессе начальной загрузки компьютера DOS подменяет вектор BIOS 1Bh своим обработчиком. Обработчик DOS 1Bh выполняет следующие действия:

¨  Очищает кольцевой буфер клавиатуры,

¨  Записывает флаг Ctrl-Break (код 80h) в ячейку области данных BIOS по адресу 40h:71h.

¨  Записывает код 03h в буферный регистр драйвера последовательного порта CON.

Последнее обстоятельство для функций DOS ввода/вывода равносильно записи данного кода в кольцевой буфер клавиатуры. Поэтому, если прикладная программа вызывает какую-либо функцию DOS, выполняющую проверку на Ctrl-С, то управление опять будет передано обработчику Int 23h. Следовательно, системная обработка сочетания клавиш Ctrl-С и Ctrl-Break выполняется фактически одинаково, хотя отличие есть. Дело в том, что драйвер ввода/вывода консоли CON (именно его используют соответствующие функции DOS) анализирует в кольцевом буфере клавиатуры только самый старый из введённых символов, поэтому если перед нажатием Ctrl-С были нажаты какие-либо другие клавиши, то они будут маскировать код 03h (признак этой комбинации). Ввод же Ctrl-Break с обработчиком DOS 1Bh записывает код 03h не в кольцевой буфер, а непосредственно в буферный регистр драйвера CON. Поэтому комбинацию Ctrl-Break нельзя замаскировать.

Программы, желающие исключить прерывание по Ctrl-Break, используют собственный обработчик 1Bh по Ctrl-Break, которые, как правило, содержат единственную команду Iret.

Для исключения же прерывания по Ctrl-С можно вместо замены системного обработчика Int 23h новым, прикладным, ликвидировать саму причину вызова этого обработчика, изменив системный обработчик 09h.[1] Новый обработчик new_09h, считывая данные порта 60h должен сравнивать этот скан-код с кодом клавиши С (2Eh) и при его обнаружении проверять статус клавиши Ctrl в байте флагов клавиатуры. Если она также нажата, то необходимо сбросить второй по номеру бит (см. рис. 5). Двух этих операций вполне достаточно, чтобы программа не реагировала на нажатие комбинаций Ctrl-С и Ctrl-Break. В программе prg3 реализован предложенный способ исключения прерываний. Основной модуль программы осуществляет вывод на экран в бесконечном цикле символы ‘*’ с помощью функции DOS 02h. Выход из программы возможен только при нажатии клавиши ESC.

Prg3. Запрет прерываний по Ctrl-C и Ctrl-Break (первый вариант)

IDEAL

MODEL small

P486N

STACK 256

DATASEG

;Поля данных программы и место для хранения перехватываемых векторов

old_09h DD?

old_1Bh DD?

CODESEG

Proc new_09h

;Новый обработчик вектора 09h

pusha ;Сохраним все регистры, включая сегментные регистры,

push es ;т. к. не знаем их значения на момент прерывания

push ds

mov ax,40h ;настроим предварительно ES на начало области

mov es, ax ;данных BIOS

in al,60h ;Введём скан-код из порта данных контроллера клавиатуры

jmp short $+2 ;Застрахуемся задержкой на ввод с периферии

cmp al,2Eh ;Скан-код клавиши "С"?

jnz handler09 ;Нет. Перейдём в системный обработчик 09h

;Отметим, что если будет нажата клавиша Ctrl вне комбинации Ctrl-C, то мы перейдём также в

;системный обработчик

test [byte es:17h],b ;Да, нажата клавиша "С". Проверим статус

;клавиши Ctrl (2-й бит 1-го байта флагов клавиатуры)

jz handler09 ;Переход, если клавиша Ctrl не нажата

;Замечание. Мы не стали сбрасывать соответствующий бит в байте флагов, т. к. всё равно эту

;операцию надо было бы повторить и для второго байта флагов. Итак, продолжается участок

;программы, когда оказалось нажатой комбинация Ctrl-C. Разрешим дальнейшую работу контроллеру

;клавиатуры (стандартная процедура), подтвердив контроллеру клавиатуры, что скан-код принят

in al,61h ;Введём содержимое порта 61h

or al,80h ;Подтвердим приём кода, записав 1 в старший

out 61h, al ;бит порта 61h

and al,7fh ;Снова разрешим работу контроллера клавиатуры,

out 61h, al ;сбросив старший бит порта 61h

;Завершим работу системного контроллера обработки аппаратных прерываний

mov al,20h ;20h - команда EOI

out 20h, al ;20h - порт контроллера

pop ds ;Восстановим регистры

pop es

popa

iret

;Переход на системный обработчик. Ещё раз заметим, что клавиша Ctrl может быть нажата вне

;комбинации Ctrl-C

handler09: mov ax,@data ;Восстановим значение регистра ds,

mov ds, ax ;соответствующее сегменту данных) *См. комментарий

pushf

call [dword ds:old_09h] ;В системный обработчик с возвратом. Возврат нужен

;для выполнения команд типа “pop”

pop ds ;Восстановим регистры

pop es

popa

iret

Endp new_09h

Proc new_1Bh

;Новый обработчик прерывания по Ctrl-Break

iret

Endp new_1Bh

Proc main

mov ax,@data ;Установка в ds адреса

mov ds, ax ;сегмента данных

;Сохраним системные вектора 09h и 1Bh с помощью функции 35h

mov ax,3509h ;al=09h - номер вектора

int 21h

mov [word old_09h],bx ;offset 09h

mov [word old_09h+2],es ;segment 09h

mov ax,351Bh ;al=1Bh - номер вектора

int 21h

mov [word old_1Bh],bx ;offset 1Bh

mov [word old_1Bh+2],es ;segment 1Bh

;Установим новые обработчики векторов 09h и 1Bh функцией 25h. Адрес обработчиков должен

;быть в DS:DX

mov ax,2509h ;al=09h - номер вектора

mov dx, offset new_09h

push ds ;Сохраним ds

mov bx, seg new_09h

mov ds, bx

int 21h

pop ds ;Восстановим ds

mov ax,251Bh ;al=1Bh - номер вектора

mov dx, offset new_1Bh

push ds ;Сохраним ds

mov bx, seg new_1Bh

mov ds, bx

int 21h

pop ds ;Восстановим ds

;Собственно программа пользователя

;Бесконечный цикл вывода на экран "*", выход из которого осуществляется нажатием

;клавиши ESC. В циклический процесс вывода введена небольшая задержка с целью

;обеспечения «мгновенной» реакции на нажимаемые клавиши. Нажатие комбинаций клавиш Ctrl-C и

;Ctrl-Break не прерывает цикл вывода "*" на консоль

loop1: mov dl,'*'

mov ah,02h

int 21h

mov cx,0

M1: loop M1 ;Небольшая задержка

mov ah,06h ;Функция ввода/вывода. Режим ввода задаётся

mov dl,0ffh ;значением dl=0ffh.

int 21h ;Возвращает: al=код символа и zf=0.

;При отсутствии символа в кольцевом буфере не ждёт его ввода, а устанавливает флаг zf=1 и

;передаёт управление программе

jz loop1

cmp al,27 ;ASCII - код клавиши ESC?

jnz loop1 ;Нет, цикл продолжится

;Выход из цикла, т. к. нажата клавиша ESC

;Восстановление старых (системных) обработчиков

mov ax, 2509h ;al=09h - номер вектора

lds dx,[old_09h] ;ds:dx - адрес вектора 09h

int 21h

mov ax, 251Bh ;al=1Bh - номер вектора

lds dx,[old_1Bh] ;ds:dx - адрес вектора 1Bh

int 21h

Exit: mov ax,4C00h ;Выходим в DOS

int 21h

Endp main

End main ;Конец программы/точка входа

Комментарий. Обратите внимание на окончание прикладного обработчика new_09h, где выбрана первая форма сцепления обработчиков

pushf

call [dword ds:old_09h] ;В системный обработчик с возвратом

Так как ячейка old_09h для хранения системного вектора хранится в сегменте данных программы, нам потребовалось восстановить то значения сегментного регистра DS, которое он имел после выполнения первой пары команд основной процедуры main, чтобы правильно сделать ссылку к памяти (ds:old_09h). Никаких других причин для использования сегментного регистра DS в данном обработчике нет. Ясно, что после этого нам нужно вернуться из системного обработчика, чтобы восстановить status quo.

Ниже представлены программа Prg4, в которой ячейки для хранения старых векторов расположены в сегментном коде основной программы (можно хранить и в программном коде обработчиков, главное не допустить исполнение кода данных в качестве команды процессора). В тексте программ Prg4 приведены полностью лишь те модули, в которых произошли изменения. Сравнительный анализ программ Prg3 и Prg4 позволит вам в дальнейшим избежать многих неприятностей с производством ссылок к памяти.

Prg4. Запрет прерываний по Ctrl-C и Ctrl-Break (второй вариант)

IDEAL

MODEL small

P486N

STACK 256

DATASEG

;Сегмент данных для основной программы (в данной задаче он пуст в силу особенностей основной

;программы, а ячейки памяти для хранения векторов системных обработчиков мы разместили в

;программном коде основной программы)

CODESEG

;Новый обработчик вектора 09h

Proc new_09h

pusha ;Сохраним все регистры

push es ;Сохраним сегментный регистр ES

mov ax,40h ;Настроим предварительно ES на начало области

;Дальше, так же как в программе Prg3

handler09: pop es ;Восстановим регистры

popa

jmp [dword cs:old_09h] ;В системный обработчик без возврата

;old_09h DD? ;одно из возможных мест хранения

Endp new_09h

;Новая процедура прерывания по Ctrl-Break

Proc new_1B

Iret

;old_1Bh DD? ;одно из возможных мест хранения

Endp new_1Bh

Proc main

mov ax,@data ;Установка в ds адреса

mov ds, ax ;сегмента данных

;Сохраним системные вектора 09h и 1Bh с помощью функции 35h

mov ax,3509h ;al=09h - номер вектора

int 21h

mov [word cs:old_09h],bx ;offset 09h

mov [word cs:old_09h+2],es ;segment 09h

mov ax,351Bh ;al=1Bh - номер вектора

int 21h

mov [word cs:old_1Bh],bx ;offset 1Bh

mov [word cs:old_1Bh+2],es ;segment 1Bh

;Установим новые обработчики векторов 09h и 1Bh функцией 25h. Адрес обработчиков должен

;быть в DS:DX

;Дальше, так же как в программе Prg3

;Собственно программа пользователя

;Бесконечный цикл вывода на экран "*", выход из которого возможен нажатием клавиши ESC.

;Нажатие комбинаций клавиш Ctrl-C и Ctrl-Break не прерывает цикл вывода "*" на консоль

;Дальше, так же как в программе Prg3

;Выход из цикла, т. к. нажата клавиша ESC

;Восстановление старых (системных) обработчиков

mov ax, 2509h ;al=09h - номер вектора

lds dx,[cs:old_09h] ;ds:dx - адрес вектора 09h

int 21h

mov ax, 251Bh ;al=1Bh - номер вектора

lds dx,[cs:old_1Bh] ;ds:dx - адрес вектора 1Bh

int 21h

Exit: mov ax,4C00h ;Выходим в DOS

int 21h

;Поля данных системных векторов

old_09h DD ?

old_1Bh DD ?

Endp main

End main

9. Резидентные программы и их организация

Резидентная программа, это программа, остающаяся в памяти после её завершения. Их ещё кратко называют TSR – программами, что определяется английской фразой Terminate State Resident – завершить и остаться резидентным. Никогда не известно, по каким адресам в памяти оказываются загруженными в разное время TSR – программы, поэтому единственным способом получить управление над ними является механизм программных (пассивный резидент) или аппаратных (активный резидент) прерываний. Наиболее часто используются резидентные драйверы, обеспечивающие русифицированную работу экрана, клавиатуры или поддержку мыши. Общеупотребительны резидентные переводчики и детекторы, позволяющие на ранней стадии обнаружить появление вируса. MS-DOS рассматривает TSR – программы как часть операционной системы, защищая их адресное пространство от других систем.

TSR – программы обычно пишутся в формате. com, так как их проще оставить в памяти резидентными, в сравнении с exe-программами. Существует два стандартных способа оставить программу резидентной в памяти. Это прерывание Int 27h для COM – программ и функция DOS 31h, которая может использоваться как для Com –, так и exe - программ. Рассмотрим оба способа на примере шаблона типичной TSR – программы (правда, позже выяснится, что такая структура резидентной программы не вполне удовлетворяет предъявляемым к ним требованиям).

%Title “Шаблон COM - резидента”

Ideal

Model tiny

Codeseg

Org 100h ;Установим IP на адрес после PSP

Proc resident

jmp init ;Переход на секцию инициализации

;Данные резидентной секции программы

Entry: ;Начало программного кода резидента (точка входа после активизации резидента)

Endp resident

;Секция инициализации резидента

Proc init

mov ah,27h ;Можно опустить

lea dx,[cs:init] ;DX - адрес первого байта за резидентной частью программы,

;CS – на начало PSP

Int 27h ;Оставить резидентной

Endp init

End resident ;Конец программы/точка входа

При запуске программы управление передаётся (в соответствии с параметром директивы End) на начало процедуры resident. Командой jmp сразу же осуществляется переход на секцию инициализации, которая обычно оформляется в виде отдельной процедуры. В секции инициализации подготавливаются условия для работы программы (её вызова) в резидентном состоянии. В конце секции инициализации вызывается прерывание int 27h с параметрами, указанными в комментарии к шаблону. Секция инициализации располагается в конце программы и отбрасывается при её завершении. Прерывание 27h, закрепив за резидентной частью программы необходимую для её функционирования память, передаёт управление командному процессору DOS. Процесс первичного запуска резидентной программы, приводящего к её загрузке в память, называют её установкой. Наличие программы, резидентной в памяти, никак не отражается на ходе исполнения других программ, хотя уменьшает объём свободной памяти компьютера.

Шаблон резидентной программы (только секция инициализации) с использованием функции DOS 31h.

……………….

;Секция инициализации

Proc init

mov dx,(init-resident+10Fh/16) ;Размер резидентной части программы,

;включая PSP, в параграфах

mov ax,3100h ;Функция завершить и оставить в памяти

int 21h

Endp init

End resident

В данном случае в регистр DX записывается объём резидентной части программы с учётом PSP (+100h) и ещё добавляется число 0Fh, чтобы после целочисленного деления на 16, результат мог быть округлён в большую сторону.

Как уже отмечалось ранее, передать управление резиденту можно с помощью механизма аппаратных или программных прерываний. В этом случае, в процессе инициализации должен быть осуществлён перехват какого-либо системного прерывания или произведена установка одного из свободных векторов прерывания для пользователей, реализующих вход в резидентную часть на метку entry. Такой процесс активизации резидентной части программы обуславливает её оформление по типу обработчика прерывания, заканчивающегося командой iret, а метка entry – имя обработчика прерывания.

В резидентной программе Prg5_res, реализован принцип передачи управления (активизации) через посредство обработчика одного из свободных векторов прерывания для пользователей Int 61h. Для проверки работы резидента предлагается использовать тестовую программу Prg5_test. Особенности работы программ и их функциональное назначение разъяснено в подробных комментариях[2] (естественно, тестовая программа запускается после успешной загрузки резидентной).

Prg5_res. Пассивный резидент с использованием прикладного программного обработчика прерывания с номером 61h

;Взаимодействие тестовой и резидентной задач выполняется через пользовательский вектор 61h.

;Тестовая программа передаёт управление резиденту командой int 61h, предварительно загрузив в

;регистр BP 16-разрядное двоичное число для преобразования его в десятичное, а в регистры AX:BX -

;полный адрес (seg:offset) буфера результата преобразования тестовой задачи. Резидент выполняет

;преобразование двоичного кода в десятичный (ASCII - формат) и помещает его в заданный буфер

;для тестовой программы.

Ideal

Model Tiny ;COM - программа

P386N

Codeseg

Org 100h ;Установим IP на адрес после PSP

Proc resident

jmp init

;Поле данных резидентной части

old_61h dd 0 ;Двухсловная ячейка для хранения адреса старого вектора.

;Для данного вектора это скорее дань принятой форме построения программы, так как данный адрес

;вектора является свободным

Proc new_61h

pusha ;Сохраним общие регистры и сегментный регистр DS

push ds ;в том числе

mov ds, ax ;Восстановим значения DS из тестовой программы

mov ax, bp ;Двоичный код для преобразования поместим в AX

Call bin_decASCII ;Процедура преобразования двоичного кода из регистра AX в

;десятичный ASCII - формат с записью в буфер по адресу DS:BX

pop ds ;Восстановим регистры

popa

iret

Endp new_61h

;Процедура bin_decASCII для преобразования «двоичный код -> десятичное ASCII-число»

Proc bin_decASCII

push ax ;Сохраним знак преобразуемого числа

mov cx,6 ;Число байт буфера

L1: mov [byte bx],' ' ;Заполним буфер пробелами

inc bx

loop L1

dec bx ;Установим адрес последнего элемента буфера

mov si,10 ;Основание системы счисления

or ax, ax ;Определим знак числа

jns convert ;Перейдём к преобразованию, если SF=0

neg ax ;Иначе изменим знак числа

convert: xor dx, dx

div si ;ax=quot(dx:ax/10), dx=rem(dx:ax/10)

add dl,'0' ;Сформируем ASCII-цифру

mov [byte bx],dl ;Занесём в буфер

dec bx ;Движение назад

or ax, ax ;Преобразование закончено

jnz convert ;Нет, так как АХ≠0

pop ax ;Да

or ax, ax ;Проверим знак числа

jns end_convert ;Конец преобразованию, если число положительное

dec bx

mov [byte bx],'-' ;Запись знака

end_convert: ret

Endp bin_decASCII

Endp resident

;Секция инициализации резидента.

Proc init

mov ax,3561h ;Чтение и сохранение вектора 61h. AL=номер вектора

int 21h

mov [word cs:old_61h],bx ;bx=offset вектора 61h

mov [word cs:old_61h+2],es ;es=segment вектора 61h

;Установка обработчика 61h функцией DOS 25h. Адрес обработчика должен быть в DS:DX.

;Изначально в COM-программах выполняется CS=DS

mov ax,2561h ;Al=номер вектора

mov dx, offset new_61h

int 21h

mov ah,09h

mov dx, offset msg

int 21h

;Завершим программу, оставив её резидентной в памяти прерыванием Int 27h.

mov ah,27h

lea dx,[cs:init] ;DX - адрес первого байта за резидентной частью программы,

;включая PSP, в параграфах

int 27h ;Оставить резидентной

Endp init

;Поле данных нерезидентной части

msg DB 'Резидент загружен',13,10,'$'

End resident ;Конец программы/точка входа

Prg5_test. Тестовая задача для проверки работоспособности резидентной программы

Prg5_res

;Тестовая программа передаёт управление резиденту командой int 61h, предварительно загрузив в

;регистр BP 16-разр. двоичное число для преобразования его в десятичное, а в регистры AX:BX -

;полный адрес (seg:offset) буфера результата преобразования. Вывод на экран строки string с

;десятичным числом, находящимся в буфере buff, осуществляется функцией DOS 09h. В строку

;вывода string для большей наглядности введены управляющие коды ANSI - драйвера для управления

;курсором и цветом символов

Ideal

Model small

stack 256

Dataseg

;Поле данных программы с применением EXC - последовательности

string DB 27,'[2J',27,'[31;47m' ;Очистка экрана и задание цвета (красные

;символы на белом фоне)

DB 27,'[12;30H',201,8 DUP(205),187 ;Позиционирование (12 - я строка,

;30-й столбец) и символы

DB 27,'[13;30H',186,32 ;Позиционирование и символы

buff DB 6 DUP(' '),32,186 ;Буфер, заполненный пробелами и символы

DB 27,'[14;30H',200,8 DUP(205),188 ;Позиционирование и символы

DB 27,'[0m',27,'[15;30H','$' ;Отмена цвета и позиционирование

Codeseg

start: mov ax,@data

mov ds, ax

mov bx, offset buff ;Смещение буфера результата

mov bp,8000h ;Исходное число (максимальное отрицательное) для

;преобразования резидентом

int 61h ;Вызов резидента, с параметрами в регистрах ax=ds,

;bx=смещение буфера, bp=число для преобразования.

;Вывод на экран результата преобразования, сформированного резидентом

mov ah,09h

mov dx, offset string

int 21h

mov ax,4C00h ;Завершим тестовую программу

int 21h

End start ;Конец программы/точка входа

10. Недостатки рассмотренного подхода построения резидентных программ и возможные пути их преодоления

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

Вспомним, что при загрузке резидентной программы в память, последняя перехватывает векторы прерываний, через которые она в дальнейшем будет активизирована. Если после этого резидентную программу запустить с клавиатуры повторно, в память будет загружена её вторая копия. Помимо дополнительных расходов памяти при этом осуществиться вторичный перехват (что ещё хуже) тех же векторов. Дело в том, что если резидентная программа в процессе исполнения передаёт управление старому (системному) обработчику перехваченного ею прерывания, то новая копия резидентной программы будет при каждой активизации вызывать первую, что приводит часто к нарушению работу системы. Ниже рассматривается два наиболее распространённых способа защиты резидентной программы от повторной установки:

·  защита с помощью сигнатуры или ключевого слова, которое помещается непосредственно в резидентную часть загрузочного модуля;

·  использование специально введённого мультиплексного прерывания 2Fh.

10.1. Организация резидентных программ, использующих сигнатуру

В литературе [Л.7] данный способ назван как внешняя привязка к перехваченному вектору прерывания. Сигнатура или ключевое слово помещается непосредственно перед прикладным обработчиком прерывания в резидентной части. Предполагается, что активизация резидента как раз и происходит при вызове (программном или аппаратном) данного обработчика. В этом случае инициализационная часть резидентных программ (установщика) должна начинаться с проверки наличия этого слова в памяти по адресу, предшествующему адресу расположения обработчика.

При рассмотрении структуры резидентной программы Prg6 для определённости принят прикладной обработчик клавиатурного прерывания new_09h, содержательная часть которого опущена. В программе рассматривается также способ выгрузки резидентной программы с помощью её второй копии, запускающейся с дополнительной опцией в командной строке.

В DOS отсутствуют средства выгрузки резидентных программ. Единственный способ – перезагрузка компьютера. Выгрузку резидентной программы из памяти можно осуществить разными способами, и сводятся они, в любом случае, к освобождению блоков памяти, занимаемых программой (например, с помощью функции DOS 49h, int 21h). Однако перед освобождением памяти необходимо восстановить векторы прерываний, перехваченные программой. Это очень сложная задача, если действовать с помощью некоторой внешней программы (по отношению к резидентной), т. к. неизвестны адреса ячеек памяти, куда спрятала их резидентная программа в процессе её инициализации. Поэтому возможность выгрузки резидентной программы обычно предусматривается из неё самой на этапе её разработки. С другой стороны, если выгрузка резидентной программы осуществляется из неё самой, то восстановление старых векторов возможно лишь в том случае, когда не произошло вторичного перехвата векторов другой резидентной программой. В силу этого, надёжно можно выгрузить лишь последнюю из загруженных резидентных программ.

Будем использовать в качестве выгружающей программы её вторую копию, которая будет запускаться командой с клавиатуры, содержащей дополнительную опцию, означающую приказ о выгрузке из памяти первой копии. Однако для этого необходимо изменить инсталляционную часть программы. Эта часть программы должна будет анализировать наличие введённой опции и при её обнаружении осуществить её выгрузку. Выгрузку резидентной программы из памяти будем осуществлять путём освобождения блоков памяти, занимаемых программой, с помощью функции DOS 49h.

Prg6. Шаблон резидента с сигнатурой.

Ideal

Model Tiny

Codeseg

Org 100h

Proc resident

jmp init

;Данные резидентной части программы и место для перехватываемых векторов

old_09h dd 0 ;Ячейка для хранения системного вектора 09h

............

mark dw 12F3h ;Сигнатура или ключевое слово

;

Proc new_09h ;Точка входа в программу при активизации резидента

............

Endp new_09h

Endp resident

;Секция инициализации

;– Выполняется проверка на наличие в памяти первого экземпляра резидента.

Из за большого объема этот материал размещен на нескольких страницах:
1 2 3 4