Операционная система MS-DOS© Александр Фролов, Григорий ФроловТом 1, книги 1-2, М.: Диалог-МИФИ, 1991. 5.5. Примеры резидентных программПриведем несколько примеров 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-программы способ организовать обслуживание нестандартной аппаратуры. |