Операционная система MS DOS

       

Примеры резидентных программ


Приведем несколько примеров TSR-программ.

Первая программа перехватывает прерывание 9 (аппаратное прерывание клавиатуры). Запустив эту программу из приглашения DOS, вы сможете убедиться в том, что прерывание от клавиатуры возникает не только тогда, когда вы нажимаете на клавишу, но и когда ее отпускаете.

#include <dos.h> #include <stdio.h> #include <stdlib.h>

// Выключаем проверку стека и указателей #pragma check_stack( off ) #pragma check_pointer( off )

// Макро для подачи звукового сигнала #define BEEP() _asm { \ _asm xor bx, bx \ _asm mov ax, 0E07h \ _asm int 10h \ }

// Указатель на старую функцию обработки // 9-го прерывания void (_interrupt _far *oldkey)( void );

// Объявление новой функции обработки // 9-го прерывания void _interrupt _far newkey( void );

void main(void); void main(void) {

unsigned size; // Размер резидентной части // TSR-программы

char _far *newstack; // Указатель на новый стек, // который будет использовать // TSR-программа

char _far *tsrbottm; // Указатель на конец // TSR-программы, используется // для определения размера // резидентной части

// Записываем адрес стека TSR-программы _asm mov WORD PTR newstack[0], sp _asm mov WORD PTR newstack[2], ss

FP_SEG(tsrbottm) = _psp; // Указатель конца FP_OFF(tsrbottm) = 0; // программы устанавливаем // на начало PSP



// Вычисляем размер программы в параграфах // Добавляем 1 параграф на случай // некратной параграфу длины size = ((newstack - tsrbottm) >> 4) + 1;

// Встраиваем свой обработчик прерывания 9, // запоминаем старый вектор прерывания 9 oldkey = _dos_getvect(0x9); _dos_setvect(0x9, newkey);

// Завершаем программу и остаемся в памяти _dos_keep(0, size); }

// Новый обработчик клавиатурного прерывания void _interrupt _far newkey() { BEEP(); // Выдаем звуковой сигнал

// Вызываем стандартный обработчик прерывания 9 _chain_intr( oldkey ); }

Следующая программа GRAB демонстрирует использование функций DOS в TSR-программах. Она содержит все элементы, необходимые "безопасным" резидентным программам.


Программа предназначена для копирования содержимого видеобуфера в файл. Запись в файл активизируется при нажатии комбинации клавиш Ctrl+PrtSc. После каждой записи имя файла изменяется.
В самом начале своей работы программа проверяет наличие своей копии в памяти, так как повторное переназначение векторов прерываний приведет систему к краху. Некоторые тонкости, связанные с программированием клавиатуры и видеоадаптера, с получением адреса видеобуфера, используются в программе без объяснения. Вся необходимая информация будет приведена позже, в главах, посвященных программированию клавиатуры и видеоадаптеров.
Итак, текст программы:
#include <dos.h> #include <stdio.h> #include <stdlib.h> #include "sysp.h"
// Выключаем проверку стека и указателей #pragma check_stack( off ) #pragma check_pointer( off )
// Макро для подачи звукового сигнала #define BEEP() _asm { \ _asm xor bx, bx \ _asm mov ax, 0E07h \ _asm int 10h \ } // Указатели на старые обработчики прерываний void (_interrupt _far *old8)(void); // Таймер void (_interrupt _far *old9)(void); // Клавиатура void (_interrupt _far *old28)(void); // Занятость DOS void (_interrupt _far *old2f)(void); // Мультиплексор
// Новые обработчики прерываний void _interrupt _far new8(void); void _interrupt _far new9(void); void _interrupt _far new28(void); void _interrupt _far new2f(unsigned _es, unsigned _ds, unsigned _di, unsigned _si, unsigned _bp, unsigned _sp, unsigned _bx, unsigned _dx, unsigned _cx, unsigned _ax, unsigned _ip, unsigned _cs, unsigned flags);
int iniflag; // Флаг запроса на вывод экрана в файл int outflag; // Флаг начала вывода в файл int name_counter; // Номер текущего выводимого файла char _far *crit; // Адрес флага критической секции DOS
// ======================================= void main(void); void main(void) { union REGS inregs, outregs; struct SREGS segregs;
unsigned size; // Размер резидентной части // TSR-программы
// Вызываем прерывание мультиплексора с AX = FF00 // Если программа GRAB уже запускалась, то новый // обработчик прерывания мультиплексора вернет // в регистре AX значение 00FF. // Таким способом мы избегаем повторного изменения // содержимого векторной таблицы прерываний. inregs.x.ax = 0xff00; int86(0x2f, &inregs, &outregs);


if(outregs.x.ax == 0x00ff) { printf("\nПрограмма GRAB уже загружена\n"); hello(); exit(-1); }
// Выдаем инструкцию по работе с программой GRAB hello();
// Вычисляем размер программы в параграфах // Добавляем 1 параграф на случай // некратной параграфу длины size = (12000 >> 4) + 1;
// Устанавливаем начальные значения флагов outflag=iniflag=0;
// Сбрасываем счетчик файлов. Первый файл будет // иметь имя GRAB0.DOC. В дальнейшем этот счетчик // будет увеличивать свое значение на 1. name_counter=0;
// Получаем указатель на флаг критической секции DOS. // Когда этот флаг равен 0, TSR-программа может // пользоваться функциями DOS inregs.h.ah = 0x34; intdosx( &inregs, &outregs, &segregs ); crit=(char _far *)FP_MAKE(segregs.es,outregs.x.bx);
// Устанавливаем собственные обработчики прерываний. old9 = _dos_getvect(0x9); _dos_setvect(0x9, new9);
old8 = _dos_getvect(0x8); _dos_setvect(0x8, new8);
old28 = _dos_getvect(0x28); _dos_setvect(0x28, new28);
old2f = _dos_getvect(0x2f); _dos_setvect(0x2f, new2f);
// Завершаем программу и остаемся в памяти _dos_keep(0, size); }
// ======================================= // Новый обработчик прерывания мультиплексора. // Используется для предохранения программы // от повторного встраивания в систему как резидентной.
void _interrupt _far new2f(unsigned _es, unsigned _ds, unsigned _di, unsigned _si, unsigned _bp, unsigned _sp, unsigned _bx, unsigned _dx, unsigned _cx, unsigned _ax, unsigned _ip, unsigned _cs, unsigned flags) { // Если прерывание вызвано с содержимым // регистра AX, равным FF00, возвращаем // в регистре AX значение 00FF, // в противном случае передаем управление // старому обработчику прерывания
if(_ax != 0xff00) _chain_intr(old2f); else _ax = 0x00ff; }
// ======================================= // Новый обработчик аппаратного прерывания таймера
void _interrupt _far new8(void) {
// Вызываем старый обработчик (*old8)();
// Если была нажата комбинация клавиш Ctrl+PrtSc // (iniflag при этом устанавливается в 1 // новым обработчиком прерывания 9) и // если запись в файл уже не началась, // то при значении флага критической секции // DOS, равном 0, выводим содержимое экрана // в файл


if((iniflag != 0) && (outflag == 0) && *crit == 0) {
outflag=1; // Устанавливаем флаг начала вывода _enable(); // Разрешаем прерывания
write_buf(); // Записываем содержимое // буфера экрана в файл
outflag=0; // Сбрасываем флаги в исходное iniflag=0; // состояние } }
// ======================================= // Новый обработчик прерывания 28h, которое вызывает // DOS, если она ожидает ввода от клавиатуры. // В этот момент TSR-программа может пользоваться // функциями DOS.
void _interrupt _far new28(void) {
// Если была нажата комбинация клавиш Ctrl+PrtSc // (iniflag при этом устанавливается в 1 // новым обработчиком прерывания 9) и // если уже не началась запись в файл, // то выводим содержимое экрана в файл
if((iniflag != 0) && (outflag == 0)) {
outflag=1; // Устанавливаем флаг начала вывода _enable(); // Разрешаем прерывания
write_buf(); // Записываем содержимое видеобуфера // в файл
outflag=0; // Сбрасываем флаги в исходное iniflag=0; // состояние }
// Передаем управление старому обработчику // прерывания 28 _chain_intr(old28); }
// ======================================= // Новый обработчик клавиатурного прерывания. // Он фиксирует нажатие комбинации клавиш Ctrl+PrtSc // и устанавливает флаг iniflag, который сигнализирует // о необходимости выбрать подходящий момент и // записать содержимое видеобуфера в файл
void _interrupt _far new9(void) {
// Если SCAN-код равен 0x37 (клавиша PrtSc), // нажата клавиша Ctrl (бит 4 байта состояния // клавиатуры, находящийся в области данных // BIOS по адресу 0040:0017 установлен в 1) // и если не установлен флаг iniflag, // то устанавливаем флаг iniflag в 1.
if((inp(0x60) == 0x37) && (iniflag == 0) && (*(char _far *)FP_MAKE(0x40,0x17) & 4) != 0) {
// Выдаем звуковой сигнал BEEP(); BEEP(); BEEP();
_disable(); // Запрещаем прерывания
// Разблокируем клавиатуру // и разрешим прерывания _asm { in al,61h mov ah,al or al,80h out 61h,al xchg ah,al out 61h,al
mov al,20h out 20h,al }


// Устанавливаем флаг запроса // на запись содержимого видеобуфера // в файл iniflag = 1; _enable(); // Разрешаем прерывания }
// Если нажали не Ctrl+PrtSc, то // передаем управление старому // обработчику прерывания 9 else _chain_intr(old9);
}
// ======================================= // Функция возвращает номер // текущего видеорежима
int get_vmode(void) { char _far *ptr; ptr = FP_MAKE(0x40,0x49); // Указатель на байт // текущего видеорежима return(*ptr); }
// ======================================= // Функция возвращает сегментный адрес // видеобуфера. Учитывается содержимое // регистров смещения адреса видеобуфера.
int get_vbuf(int vmode) { unsigned vbase; unsigned adr_6845; unsigned high; unsigned low; unsigned offs;
// В зависимости от видеорежима базовый адрес // видеобуфера может быть 0xb000 или 0xb800 vbase = (vmode == 7) ? 0xb000 : 0xb800;
// получаем адрес порта видеоконтроллера 6845 adr_6845 = *(unsigned _far *)(FP_MAKE(0x40,0x63));
// Считываем содержимое регистров 12 и 13 // видеоконтроллера outp(adr_6845,0xc); high = inp(adr_6845+1);
outp(adr_6845,0xd); low = inp(adr_6845+1);
offs = ((high << 8) + low) >> 4;
// Добавляем к базовому адресу видеобуфера // смещение, взятое из регистров видеоконтроллера vbase += offs;
return(vbase); }
// ======================================= // Функция возвращает количество символов в строке // для текущего видеорежима
int get_column(void) { return(*(int _far *)(FP_MAKE(0x40,0x4a))); }
// ======================================= // Функция возвращает количество строк // для текущего видеорежима
int get_row(void) { unsigned char ega_info; ega_info = *(unsigned char _far *)(FP_MAKE(0x40,0x87));
// Если нет EGA, то используется 25 строк, // если EGA присутствует, считываем число // строк. Это число находится в области данных // BIOS по адресу 0040:0084.
if(ega_info == 0 ( (ega_info & 8) != 0) ) { return(25); } else { return(*(unsigned char _far *) (FP_MAKE(0x40,0x84)) + 1); } }
// ======================================= // Функция записи содержимого видеобуфера в // файл


int write_buf(void) {
// Видеопамять состоит из байтов символов и байтов // атрибутов. Нам нужны байты символов chr. typedef struct _VIDEOBUF_ { unsigned char chr; unsigned char attr; } VIDEOBUF;
VIDEOBUF _far *vbuf; int i, j, k, max_col, max_row; FILE *out_file; char fname[20],ext[8];
i=get_vmode(); // Получаем номер текущего // видеорежима
// Для графического режима ничего не записываем if(i > 3 && i != 7) return(-1);
// Устанавливаем указатель vbuf на видеобуфер vbuf=(VIDEOBUF _far *)FP_MAKE(get_vbuf(i),0);
// Определяем размеры экрана max_col = get_column(); max_row = get_row();
// Формируем имя файла для записи образа экрана itoa(name_counter++,ext,10); strcpy(fname,"!grab"); strcat(fname,ext); strcat(fname,".doc");
out_file=fopen(fname,"wb+");
// Записываем содержимое видеобуфера в файл for(i=0; i<max_row; i++) { for(j=0; j<max_col; j++) {
fputc(vbuf->chr,out_file); vbuf++;
}
// В конце каждой строки добавляем // символы перевода строки и // возврата каретки fputc(0xd,out_file); fputc(0xa,out_file); } fclose(out_file); return(0); }
// ======================================= // Функция выводит на экран инструкцию по // использованию программы GRAB
int hello(void) { printf("\nУтилита копирования содержимого" "\nэкрана в файл GRAB<n>.DOC" "\nCopyright (C)Frolov A.,1990" "\n" "\nДля копирования нажмите Ctrl+PrtSc" "\n"); }
Приведем пример TSR-программы, написанной на языке ассемблера. Эта программа переназначает прерывание 13h, которое используется для работы с дисками. Она позволяет организовать защиту диска от записи. При первом запуске программа включает защиту, при втором выключает, потом опять включает и так далее. В качестве флага - признака включения или выключения защиты, используется компонента смещения вектора прерывания F0h, зарезервированного для интерпретатора BASIC.
.MODEL tiny .CODE .STARTUP
jmp begin
old_int13h_off dw 0 ; Адрес старого обработчика old_int13h_seg dw 0 ; прерывания 13h


old_int2Fh_off dw 0 ; Адрес старого обработчика old_int2Fh_seg dw 0 ; прерывания 2Fh
; Новый обработчик прерывания 2Fh нужен ; для проверки наличия программы в памяти ; при ее запуске для предохранения ; от повторного запуска
new_int2Fh proc far cmp ax,0FF00h jz installed
jmp dword ptr cs:old_int2Fh_off
; Если код функции 0FF00h, то возвращаем ; в регистре AX значение 00FFh. Это признак ; того, что программа уже загружена в память
installed: mov ax,00FFh iret
new_int2Fh endp
; Новый обработчик прерывания 13h. Для команд записи ; на жесткий диск выполняет проверку содержимого ; компоненты смещения вектора прерывания FFh. ; Эта ячейка служит для триггерного переключения ; режима работы прерывания 13h - включения/выключения ; защиты записи.
new_int13h proc far cmp ah,3 ; запись сектора je protect cmp ah,5 ; форматирование трека je protect jmp dword ptr cs:old_int13h_off
old_int13h: pop es pop bx pop ax
jmp dword ptr cs:old_int13h_off
protect: push ax push bx push es
; Проверяем значение триггерного флага защиты xor ax,ax mov es,ax mov bx,0F0h*4 mov ax,WORD PTR es:[bx] cmp ax,0FFFFh
jne old_int13h
; Для флоппи-дисков защиту не включаем
cmp dl,0 je old_int13h cmp dl,1 je old_int13h
pop es pop bx pop ax
; Имитируем ошибку записи при попытке ; записать данные на защищенный от записи диск
mov ah,3 stc ret 2
new_int13h endp
;============================== ; Точка входа в программу
begin proc far
; Проверяем, не загружена ли уже программа ; в память
mov ax,0FF00h int 2Fh
cmp ax,00FFh jne first_start
jmp invert_protect_flag
; Первоначальный запуск программы
first_start:
; Устанавливаем триггерный флаг защиты записи ; в состояние, соответствующее включенной защите
xor ax,ax mov es,ax mov bx,0F0h*4 mov WORD PTR es:[bx],0FFFFh
; Запоминаем адрес старого обработчика прерывания 13h
mov ax,3513h int 21h mov cs:old_int13h_off,bx mov cs:old_int13h_seg,es
; Запоминаем адрес старого обработчика прерывания 2Fh
mov ax,352Fh int 21h mov cs:old_int2Fh_off,bx mov cs:old_int2Fh_seg,es


push cs pop ds
; Выводим сообщение о включении защиты
mov dx,offset msg_on mov ah,9 int 21h
; Устанавливаем новые обработчики прерываний 13h и 2Fh
mov dx,OFFSET new_int13h mov ax,2513h int 21h
mov dx,OFFSET new_int2Fh mov ax,252Fh int 21h
; Завершаем программу и оставляем резидентно ; в памяти часть программы, содержащую новые ; обработчики прерываний
mov dx,OFFSET begin int 27h
; Если это не первый запуск программы, ; инвертируем содержимое триггерного флага защиты
invert_protect_flag:
xor ax,ax mov es,ax mov bx,0F0h*4
mov ax,WORD PTR es:[bx] not ax mov WORD PTR es:[bx],ax mov cx,ax
cmp cx,0FFFFh je prot_on
; Выводим сообщение о выключении защиты
mov dx,OFFSET msg_off jmp short send_msg prot_on: ; Выводим сообщение о включении защиты mov dx,OFFSET msg_on send_msg: mov ah,9 push cs pop ds int 21h
.EXIT begin endp
msg_on db 'Защита диска включена$' msg_off db 'Защита диска ВЫКЛЮЧЕНА$' end
Этим примером мы завершим обзор TSR-программ. В следующей главе будет описан другой вид резидентных программ - драйверы. Использование драйвера - более предпочтительный, чем TSR-программы способ организовать обслуживание нестандартной аппаратуры.

Содержание раздела