Резидентный обработчик клавиатуры (перехват нажатий клавиш и запись в файл) 
	
	
прерывания, заставляющий процессор прервать выполняемую программу и  перейти 
на программу системного обработчика прерываний от  клавиатуры,  входящего  в 
систему BIOS. Поскольку обработчик вызывается через вектор 09h,  его  иногда 
называют программой int09h. 
   Программа int09h, помимо  порта  60h,  работает  еще  с  двумя  областями 
оперативной памяти: кольцевым буфером ввода,  располагаемым  по  адресам  от 
40h:lEh до 40h:3Dh, куда  в  конце  концов  помещаются  коды  ASCII  нажатых 
клавиш, и битом  флагов  клавиатуры,  находящимся  по  адресу  40h:17h,  где 
фиксируется состояние управляющих клавиш (, ,   
и др.). 
   Программа  int09h,  получив  управление  в   результате   прерывания   от 
клавиатуры, считывает из порта 60h  скэн-код  и  анализирует  его  значение. 
Если скэн-код принадлежит  одной  из  управляющих  клавиш,  и,  к  тому  же, 
представляет собой код нажатия, в байте  флагов  клавиатуры  устанавливается 
бит (флаг), соответствующий нажатой клавише. Например,  при  нажатии  правой 
клавиши  в байте флагов устанавливается  бит  0,  при  нажатии  левой 
клавиши  - бит 1, при нажатии любой клавиши  -  бит  2  и  т.д. 
Биты флагов сохраняют свое состояние, пока клавиши (по одиночке или в  любых 
комбинациях)  остаются  нажатыми.  Если  управляющая  клавиша   отпускается, 
программа int09h получает скэн-код отпускания и  сбрасывает  соответствующий 
бит в байте  флагов.  Кроме  состояния  указанных  клавиш,  в  байте  флагов 
фиксируются еще режимы , ,    и   
(см. рис. 2.3). 
   Компьютеры PC/AT имеют второй  байт  флагов  клавиатуры,  находящийся  по 
адресу 40h:18h, и отражающий состояние  управляющих  клавиш  на  расширенной 
(101-клавишной) клавиатуре. 
   При нажатии обычной, не управляющей клавиши, программа  int09h  считывает 
из порта 60h ее скэн-код нажатия и по таблице трансляции скэн-кодов  в  коды 
ASCII формирует двухбайтовый код, старший байт которого  содержит  скэн-код, 
а младший код ASCII. При этом если скэн-код характеризует  клавишу,  то  код 
ASCII определяет закрепленный за ней символ. 
Поскольку  за  каждой  клавишей  закреплено,  как  правило,  не  менее  двух 
символов ("а" и "А", "1" и "!", "2" и "@"  и  т.д.),  то  каждому  скэн-коду 
соответствуют, как минимум, два кода ASCII. В процессе трансляции  программа 
int09h анализирует состояние флагов, так что если нажата, например,  клавиша 
Q (скэн-код 10h, код ASCII буквы Q - 51h, а буквы q - 7lh),  то  формируется 
двухбайтовый код 1071h,  но  если  клавиша  Q  нажата  при  нажатой  клавише 
 (смена регистра), то результат трансляции  составит  1051h.  Тот  же 
код 1051h получится, если при нажатии клавиши  Q  был  включен  режим   (заглавные буквы), однако при включенном режиме  и  нажатой 
клавише  образуется код 1071h, поскольку  в  такой  ситуации  клавиша 
 на время нажатия  переводит  клавиатуру  в  режим  нижнего  регистре 
(строчные буквы). 
   Полученный в результате трансляции двухбайтовый код засылается программой 
int09h в кольцевой буфер ввода, который служит для  синхронизации  процессов 
ввода данных с клавиатуры и приема их  выполняемой  компьютером  программой. 
Объем буфера составляет 16 слов, при этом коды символов извлекаются из  него 
в том же порядке, в каком они в него поступали. За состоянием буфера  следят 
два указателя. В хвостовом  указателе  (слово  по  адресу  40:lCh)  хранится 
адрес первой свободной ячейки,  в  головном  указателе  (40:  1Ah)  -  адрес 
самого старого  кода,  принятого  с  клавиатуры  и  еще  не  востребованного 
программой. Оба  адреса  представляют  собой  смещения  относительно  начала 
области данных BIOS, т.е. числа от 1Eh до 3Ch. В начале работы, когда  буфер 
пуст, оба указателя - и хвостовой, и головной, указывают  на  первую  ячейку 
буфера. 
   Программа int09h, сформировав двухбайтовый код, помещает его в  буфер  по 
адресу,  находящемуся  в  хвостовом  указателе,  после   чего   этот   адрес 
увеличивается на 2,  указывая  опять  на  первую  свободную  ячейку.  Каждое 
последующее нажатие  на  какую-либо  клавишу  добавляет  в  буфер  очередной 
двухбайтовый код и смещает хвостовой указатель. 
   Выполняемая  программа,  желая  получить  код  нажатой  клавиши,   должна 
обратиться для этого к каким-либо системным средствам  -  функциям  ввода  с 
клавиатуры  DOS  (прерывание  21h)  или  BIOS  (прерывание  16h).  Системные 
программы  с  помощью  драйвера  клавиатуры  (точнее  говоря,  объединенного 
драйвера клавиатуры и экрана, так  называемого  драйвера  консоли  с  именем 
CON)  считывают  из  кольцевого  буфера  содержимое  ячейки,  адрес  которой 
находится в головном  указателе,  и  увеличивает  этот  адрес  на  2.  Таким 
образом, программный запрос на ввод с клавиатуры фактически выполняет  прием 
кода не с клавиатуры, а из кольцевого буфера. 
   Хвостовой указатель, перемещаясь по буферу в процессе  занесения  в  него 
кодов, доходит, наконец, до конца буфера (адрес 40h:3Ch). В этом случае  при 
поступлении  очередного  кода  адрес  в  указателе  не   увеличивается,   а, 
наоборот, уменьшается на длину буфера. Тем самым  указатель  возвращается  в 
начало буфера, затем продолжает перемещаться по буферу до его  конца,  опять 
возвращается в  начало  и  так  далее  по  кольцу.  Аналогичные  манипуляции 
выполняются и с головным указателем. 
   Равенство адресов в обоих указателях свидетельствует  о  том,  что  буфер 
пуст.  Если  при  этом  программа  поставила  запрос  на  ввод   символа   с 
клавиатуры, то драйвер консоли будет ждать поступления кода в  буфер,  после 
чего этот код будет  передан  в  программу.  Если  же  хвостовой  указатель, 
перемещаясь по  буферу  в  процессе  его  заполнения,  подошел  к  головному 
указателю "с обратной стороны" (это произойдет, если  оператор  нажимает  на 
клавиши,  а  выполняемая  в  настоящий  момент  программа  не  обращается  к 
клавиатуре), прием новых кодов блокируется, а нажатие на клавиши  возбуждает 
предупреждающие звуковые сигналы. 
   Если компьютер не  выполняет  никакой  программы,  то  активной  является 
программа  командного   процессора   COMMAND.COM.   Активность   COMMAND.COM 
заключается в том, что он, поставив запрос к DOS на  ввод  с  клавиатуры  (с 
помощью функции 0Ah прерывания 21h) ожидает  ввода  с  клавиатуры  очередной 
команды пользователя. Как только в кольцевом  буфере  ввода  появляется  код 
символа, функция 0Ah переносит его во внутренний буфер DOS, очищая при  этом 
кольцевой буфер ввода, а также выводит символ на экран. При  получении  кода 
клавиши   (0Dh)  функция  0Ah  завершает  свою  работу,  а  командный 
процессор предполагает, что ввод команды  закончен,  анализирует  содержимое 
буфера DOS и приступает к выполнению введенной команды. При  этом  командный 
процессор работает  практически  лишь  с  младшими  половинами  двухбайтовых 
кодов символов, именно, с кодами ASCII. 
   Если  компьютер  выполняет  какую-либо  программу,   ведущую   диалог   с 
оператором, то, как ухе отмечалось, ввод данных с клавиатуры  (а  точнее  из 
кольцевого  буфера  ввода)  и  вывод  их  на  экран  с  целью  эхо  контроля 
организует эта программа, обращаясь непосредственно  к  драйверу  BIOS  (int 
16h) или к соответствующей функции DOS (int 21h). Может  случиться,  однако, 
что выполняемой программе не требуется ввод с клавиатуры, а  оператор  нажал 
какие-то клавиши. В этом случае вводимые символы  накапливаются  (с  помощью 
программы int09h) в кольцевом буфере ввода и, естественно,  не  отображаются 
на экране. Так можно ввести до  15  символов.  Когда  программа  завершится, 
управление будет передано COMMAND. СОМ, который сразу же  обнаружит  наличие 
символов в кольцевом буфере, извлечет  их  оттуда  и  отобразит  на  экране. 
Такой ввод с клавиатуры называют вводом с упреждением. 
   До сих пор речь шла о  символах  и  кодах  ASCII,  которым  соответствуют 
определенные клавиши терминала и которые можно  отобразить  на  экране.  Это 
буквы (прописные и строчные), цифры, знаки препинания и  специальные  знаки, 
используемые в программах и командных строках,  например,  |,  $,  *  и  др. 
Однако имеется ряд клавиш,  которым  не  назначены  отображаемые  на  экране 
символы. Это, например, функциональные клавиши ,  ...;  клавиши 
управления  курсором  ,  ,  ,  ,  , 
 и др.  При  нажатии  этих  клавиш  в  кольцевой  буфер  ввода 
засылается расширенный код ASCII, в  котором  младший  байт  равен  нулю,  а 
старший  является  скэн-кодом  нажатой  клавиши.  Расширенный   коды   ASCII 
поступают в  буфер  ввода  и  в  случае  нажатия  комбинаций  управляющих  и 
функциональных   клавиш,   например,   /,   /     (на 
дополнительной    цифровой    клавиатуре),  /  и  др.  В   этом 
случае, однако, в старший байт расширенного кода  ASCII  помещается  уже  не 
скэн-код клавиши, а некоторый код, специально  назначенный  этой  комбинации 
клавиш. Естественно, этого кода нет среди  "обычных"  скэн-кодов.  Например, 
клавиша , скэн-код  которой  равен  3Bh,  может  генерировать  следующие 
расширенные коды ASCII: 
       ЗB00h     /  5E00h  /  6800h      / 
5400h 
       Итак,  прерывание,  возникающее  при  нажатии  или  отпускании  любой 
клавиши, обрабатывается  по  относительно  сложному  алгоритму  с  системным 
обработчиком, содержащимся в BIOS. Рассмотрим примеры вмешательства  в  этот 
процесс. Ниже приведен пример прикладной  программы,  выполняющей  некоторую 
обработку поступающих с клавиатуры  данных  еще  до  активизации  системного 
обработчика. 
                            3. Описание программы 
                       3.1. Описание для пользователя 
  Приведённая  ниже   программа   осуществляет   перехват   прерывания   от 
клавиатуры, и производит запись скэн-кодов клавиш и байта флагов  клавиатуры 
в файл с именем «  s_code&f.txt  ».  При  этом  фиксируются  только  нажатия 
клавиш. Запись происходит  при  каждом  шестнадцатом  нажатии  клавиши.  Это 
сделано, во-первых, для уменьшения вероятности потери «ценных»  нажатий  при 
экстренном  выключении  компьютера,  во-вторых,  для  экономии   оперативной 
памяти,  в-третьих, для сохранения нормальной работоспособности  компьютера. 
Файл                   « s_code&f.txt » создаётся  в  родительском  каталоге 
программы.  Если  при  инсталляции  файл  уже  существует,   то   программа, 
автоматически, запишет в конец текущую  дату  и  время,  после  этого  будет 
осуществляться запись скэн-кодов и флагов в  обычном  режиме  после  даты  и 
времени. Программа является резидентной. После того как  она  будет  успешно 
инсталлирована,  на  экране  появится   соответствующая   надпись   “Program 
installed”.  В  ней  предусмотрена  защита  от  повторной  установки.  Таким 
образом одновременно в оперативной  памяти  компьютера  не  может  находится 
больше одной копии  программы,  что  практически  сводит  к  нулю  шансы  не 
корректной работы. При попытке запустить программу после того  как  она  уже 
была инсталлирована, на экране  появится  соответствующая  надпись  “Program 
already installed”.  Также эту программу   можно  выгрузить  из  оперативной 
памяти  после  того  как  потребность  в  ней  отпадёт.  Для  этого  следует 
запустить программу с ключом “off”, т.е. в командной строке  написать     off  .  После  этого  вы  увидите  строку   “Program  is   DIE”, 
сигнализирующую об успешной выгрузке программы. При этом  содержимое  буфера 
будет записано в файл. Таким образом, в  файл  будут  записаны  все  нажатия 
клавиш  вплоть  до  выгрузки  программы.  Если  данную  программу  записать, 
например, в autoexec.bat, то можно  будет  проследить  время  начала  работы 
пользователя и какие кнопки он после этого нажимал. 
  Данная программа работает только в среде MS-DOS. 
                        3.2 Описание для программиста 
  Программа пишется в формате СОМ, поэтому в ней  предусматривается  только 
один сегмент, с котором связываются сегментные регистры CS и  DS;  в  начале 
сегмента резервируется 256 байт дня PSP. 
  Инициализация. 
  При запуске программы с клавиатуры управление передается (в  соответствии 
с параметром директивы end) на начало процедуры main. Командой jmp сразу  же 
осуществляется переход на секцию инициализации,  которая  оформлена  в  виде 
отдельной процедуры. В секции  инициализации  подготавливаются  условия  для 
работы программы уже в резидентном состоянии. 
  В начальной части инициализации мы проверяем наличие хвоста в  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  результат  был  округлен  в 
большую сторону. 
  mov ax,3100h 
  mov dx,(end_res-main+10fh)/16 
  int 21h 
   С целью экономии памяти секция инициализации располагается я конце 
программы и отбрасывается при ее завершении. 
   Функция 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: 
                           3.3. Листинг программы 
  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         ; Функция освобождения блока памяти 
         int 21h 
  ; Восстановим использовавшиеся регистры 
         pop dx 
         pop es 
         pop ds 
         iret               ; Возврат в вызвавшую программу 
  new_2fh endp              ; Конец процедуры обработки прерывания 2Fh 
  end_res=$            ; Смещение конца резидентной части программы 
  main endp 
  tail db 'off'             ; Ожидаемый хвост команды 
  flag db 0            ; Флаг требования выгрузки 
  tabl db '0123456789'      ; Таблица для перевода BCD кода в ASCII 
  time db 25 dup(?)         ; Ячейка для сохранения текущей даты и времени 
  ; Процедура создания файла 
  div_f proc 
         mov ah,3ch         ; Функция создания файла 
         mov cx,0                 ; Без атрибутов 
         lea dx,filename          ; DS:DX ( ASCIIZ имени файла 
         int 21h 
         mov bx,ax          ; Дескриптор в ВХ 
         mov ah,40h         ; Функция записи в файл 
         mov cx,buflen      ; CХ ( количество байт 
         lea dx,buf               ; DS:DX ( адрес строки 
         int 21h 
         mov ah,3eh         ; Функция закрытия файла 
         int 21h 
         ret                ; Выход из процедуры 
  div_f endp                ; Конец процедуры создания файла 
  ; Процедура открытия файла и записи в него текущей даты и времени 
  div2_f proc 
         mov [time],0ah           ; Запись в переменную time маркеров 
         mov [time+1],0dh         ; перехода на следующую строку 
         mov ah,3dh         ; Функция открытия файла 
         mov al,1                 ; для записи 
         mov dx,offset filename   ; DS:DX ( ASCIIZ имени файла 
         int 21h 
         mov bx,ax          ; Дескриптор в ВХ 
         push bx            ; Сохраним дескриптор 
         xor cx,cx                ; Отчистим СХ 
         xor dx,dx                ; и DX 
         mov ax,4202h       ; Функция установки указателя в конец файла 
         int 21h 
         mov ah,02h         ; Функция чтения времени из «постоянных» «CMOS» 
часов реального времени 
         int 1ah            ; Прерывание ввода – вывода для времени 
         mov bx,offset tabl       ; DS:DX ( адрес таблицы 
         mov si,2                 ; Установим смещение для переменной time 
         mov ax,cx          ; Часы и минуты сохраним в AX 
         mov cx,12          ; Установим счётчик сдвига 
  next:  push ax             ; Сохраним AX 
         shr ax,cl                ; Сдвинем AX на CL 
         and al,0fh               ; Получим номер ячейки в таблице прибавив 
маску 
         xlat               ; Получим ASCII код числа 
         mov [time+si],al         ; Занесём его в переменную time 
         inc si             ; Увеличим на 1 смещение 
         cmp si,4                 ; Смещение = 4 ? 
         je ras             ; Да, переход на метку ras 
  vw:    sub cl,4                 ; Нет, уменьшим CL на 4 
         pop ax             ; Восстановим AX 
         cmp cl,-4                ; Сравним CL с -4 
         jne next                 ; Не равно – выполним ещё раз 
         jmp ent1                 ; Равно – переход на ent1 
   ras:   mov [time+si],':'       ; Запишем в переменную time – «:» 
         inc si             ; Увеличим на 1 смещение 
         jmp vw             ; Перейдём на метку vw 
  ent1:  mov [time+si],' '        ; Запишем в переменную time – « » 
         inc si             ; Увеличим на 1 смещение 
         mov ah,04h         ; Функция чтения даты из «постоянных» «CMOS» 
часов реального времени 
         int 1ah            ; Прерывание ввода – вывода для времени 
         mov ax,dx          ; Дату сохраним в AX 
         mov cx,12          ; Установим счётчик сдвига 
  next1: push ax             ; Сохраним AX 
         shr ax,cl                ; Сдвинем AX на CL 
         and al,0fh               ; Получим номер ячейки в таблице прибавив 
маску 
         xlat               ; Получим ASCII код числа 
         mov [time+si],al         ; Занесём его в переменную time 
         inc si             ; Увеличим на 1 смещение 
         cmp si,10                ; Смещение = 10 ? 
         je ras1            ; Да, переход на метку ras1 
 vw1:   sub cl,4             ; Нет, уменьшим CL на 4 
         pop ax             ; Восстановим AX 
         cmp cl,-4                ; Сравним CL с -4 
         jne next1                ; Не равно – выполним ещё раз 
         jmp ent2                 ; Равно – переход на ent2 
  ras1:  mov [time+si],'.'        ; Запишем в переменную time – «.» 
         inc si             ; Увеличим на 1 смещение 
         jmp vw1            ; Перейдём на метку vw1 
  ent2:  mov [time+si],0ah        ; Запись в переменную time маркеров 
         mov [time+si+1],0dh      ; перехода на следующую строку 
         pop bx             ; Восстановим дескриптор 
         mov ah,40h         ; Функция записи в файл 
         mov cx,20          ; CХ ( количество байт 
         mov dx,offset time       ; DS:DX ( адрес строки 
         int 21h 
         mov ah,3eh         ; Функция закрытия файла 
         int 21h 
         ret                ; Выход из процедуры 
div2_f endp            ; Конец процедуры подготовки файла 
  ; Процедура инициализации 
  init proc 
         mov cl,es:80h      ; Получим длину хвоста PSP 
         cmp cl,0                 ; Длина хвоста = 0 ? 
         je live            ; Да программа запущена без параметров 
         xor ch,ch                ; Теперь CX=CL=длина хвоста 
         mov di,81h         ; DS:SI ( хвост в PSP 
         lea si,tail              ; DS:SI ( поле tail 
         mov al,' '               ; Уберём пробелы из начала хвоста 
  repe   scasb              ; Сканируем хвост, пока пробелы 
         dec di             ; DI ( первый символ после пробелов 
         mov cx,3                 ; Ожидаемая длина параметра 
  repe   cmpsb              ; Сравниваем введённый хвост с ожидаемым 
         jne live                 ; Введена неизвестная команда 
         inc flag                 ; Введено «off», установим флаг запроса 
на выгрузку 
  ; Проверим, не установлена ли уже данная программа 
  live:  mov ah,0f1h        ; Установим нашу функцию 
         mov al,0                 ; и подфункцию на наличие нашей программы 
в оперативной памяти 
         int 2fh 
         cmp al,0ffh        ; Программа установлена? 
         je installed       ; Да, при наличии запроса на выгрузку её можно 
выгрузить 
  ; Сохраним вектор 2fh 
         mov ax,352fh       ; Функция получения вектора 2fh 
         int 21h 
         mov word ptr cs:old_2fh,bx     ; Сохраним смещение системного 
обработчика 
         mov word ptr cs:old_2fh+2,es   ; Сохраним сегмент системного 
обработчика 
  ; Заполним вектор 2fh 
         mov ax,252fh       ; Функция установления вектора прерывания 2fh 
         mov dx,offset new_2fh    ; Смещение нашего обработчика 
         int 21h 
  ; Сохраним вектор 09h 
         mov ax,3509h       ; Функция получения вектора 09h 
         int 21h 
         mov word ptr cs:old_09h,bx     ; Сохраним смещение системного 
обработчика 
         mov word ptr cs:old_09h+2,es   ; Сохраним сегмент системного 
обработчика 
  ; Заполним вектор 09h 
         mov ax,2509h       ; Функция установления вектора прерывания 09h 
         mov dx,offset new_09h    ; Смещение нашего обработчика 
         int 21h 
         mov ah,4eh         ; Функция поиска файла 
         lea dx,filename          ; DS:DX ( ASCIIZ имени файла 
         int 21h 
         cmp ax,12h         ; Файл не найден? 
         je creat                 ; Да, создадим файл 
         call div2_f        ; Нет, вызов процедуры открытия файла и записи 
в него текущей даты и времени 
         jmp by             ; Переход на метку by 
  creat: call div_f         ; Вызов процедуры создания файла 
  ; Выведем на экран информационное сообщение 
  by:    mov ah,09h         ; Функция вывода на экран 
         lea dx,mes         ; DS:DX ( адрес строки 
         int 21h 
         mov ax,3100h       ; Функция «завершиться и остаться резидентным» 
         mov dx,(end_res-main+10fh)/16  ; Размер в параграфах 
         int 21h 
  installed: 
         cmp flag,1         ; Запрос на выгрузку установлен? 
         je unins                 ; Да, на выгрузку 
  ; Выведем на экран информационное сообщение 
         mov ah,09h         ; Функция вывода на экран 
         lea dx,mes1        ; DS:DX ( адрес строки 
         int 21h 
  ; Выведем предупреждающий звуковой сигнал 
         mov cx,5                 ; Количество гудков 
         mov ah,02h         ; Функция вывода на экран 
  l:     mov dl,07h         ; ASCII код зуммера 
         int 21h 
         loop l             ; Повторим CX раз 
         mov ax,4c01h       ; Функция завершения с кодом возврата 
         int 21h 
  unins: 
  ; Перешлём в первую (резидентную) копию программы запрос на выгрузку 
         mov ax,0f101h      ; Наша функция с подфункцией выгрузки 
         int 2fh            ; Мультиплексное прерывание 
  ; Выведем на экран информационное сообщение 
         mov ah,09h         ; Функция вывода на экран 
         lea dx,mes2        ; DS:DX ( адрес строки 
         int 21h 
         mov ax,4c00h       ; Функция завершения программы 
         int 21h 
         buf  db 'Skencode&Klav_flag file',0ah,0dh 
         buflen equ $-buf 
         mes  db 'Program installed$' 
         mes1 db 'Program already installed$' 
         mes2 db 'Program is DIE$' 
  init endp 
  text ends 
  end main 
                       3.4. Рекомендации по улучшению 
 -  Главным  недостатком  этой  программы  является   неудобное   визуальное 
   восприятие  записей   в  файле.  Т.е.  мы  видим  не  ASCII-код   который 
   образовался в результате  нажатия  клавиши,  а  так  называемый  скэн-код 
   (номер клавиши)  и  состояние  байта  флагов  клавиатуры,  в  котором  он 
   находился при этом нажатии. При необходимости можно написать процедуру  в 
   нашем обработчике либо в виде отдельной программы, которая  анализировала 
   бы  байт  флагов  и  в  зависимости  от   этого   подставляла   ASCII-код 
   соответствующий скэн-коду нажатой клавиши. 
 - Вторым недостатком нашей программы является не  всегда  удобный  механизм 
   выгрузки программы из оперативной памяти.  Можно  предусмотреть  выгрузку 
   нашей программы специальной не стандартной комбинацией клавиш. 
 -  Третий  существенный  недостаток  программы  состоит  в  том,  что   наш 
   обработчик не реагирует на сочетание клавиш  Clrl+Alt+Del.  Так  как  наш 
   обработчик перехватывает прерывания от клавиатуры  раньше  чем  системный 
   обработчик  “int09h”,  то  было  бы  целесообразно  при  этом   сочетании 
   сбрасывать содержимое  буфера  в  файл,  а  затем  передавать  управление 
   системному обработчику. 
 - Можно предусмотреть запись в файл autoexec.bat либо config.sys  строки  с 
   путём к нашему файлу, при  запуске  программы  с  параметром  вводимым  с 
   командной строки. 
 - Можно предусмотреть коррекцию  размеров  буфера,  а  также  задавать  имя 
   рабочего файла с помощью всё  тех  же  параметров  вводимых  с  командной 
   строки. 
 - В зависимости от того в каких целях применяется данный обработчик,  можно 
   запретить   нажатие   какой   либо   клавиши,   комбинации   клавиш   или 
   последовательности. 
  Данная  программа  является   шаблоном   для   резидентных   обработчиков 
прерываний, в частности обработчиков прерываний от  клавиатуры,  и  является 
огромным полем для творчества. 
                      4. Список используемой литературы 
1. П.И.Рудаков, К.Г.Финогенов «Программируем на языке ассемблера IBM PC», 
   Обнинск 1997г. 
2. Зубков С.В. «Assembler для DOS, Windows и UNIX», Москва 2000г. 
3. Богумирский Б.С. «Руководство пользователя ПЭВМ», Санкт–Петербург 1994г. 
Страницы: 1, 2 
	
	
					
							 |