Д. Валдин Резидентные программы на языке С Часть 1 Пример 1. Код функции _interrupt, генерируемый компиляторами Microsoft void _interrupt _far newint( void ) { } ; Compiler: Microsoft C 6.0 _newint PROC FAR push ax push cx push dx push bx push sp push bp push si push di push ds push es mov bp,sp mov ax,DGROUP mov ds,ax cld ; pop es pop ds pop di pop si pop bp pop bx pop bx pop dx pop cx pop ax iret _newint ENDP Пример 2. Код функции _interrupt, генерируемый компилятором фирмы Borland ; Compiler: Borland C++ 2.0 _new_int proc far push ax push bx push cx push dx push es push ds push si push di push bp mov bp,DGROUP mov ds,bp mov bp,sp ; pop bp pop di pop si pop ds pop es pop dx pop cx pop bx pop ax iret _new_int endp Пример. 3. Определение функции обработки прерывания с формальными параметрами void _interrupt _far newvideo( 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 ); Пример 4. Макроопределение для облегчения формирования списка формальных параметров #define ARGS 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 Пример 5. Макроопределение формальных аргументов для компиляторов Microsoft и Borland #if defined _MSC_VER #define ARGS 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 #elif defined __TURBOC__ #define ARGS unsigned _bp, unsigned _di, unsigned _si, \ unsigned __ds, unsigned __es, unsigned _dx, \ unsigned _cx, unsigned _bx, unsigned _ax, \ unsigned _ip, unsigned __cs, unsigned _flags #endif Пример 6. Вызов функции _chain_intr() void _interrupt _far newint() { ... _chain_intr( oldint ); /* Этот код никогда не выполняется. */ ... } Пример 7. Реализация функции обработки прерываний в демонстрационном примере фирмы Microsoft /* Функция предотвращает повторный вызов прерывания 10H, если * оно уже выполняется. Вероятность такого события мала, но оно * может привести к непредсказуемым последствиям, если обработ- * чик был прерван в момент доступа к аппаратным регистрам. */ void _interrupt _far newvideo( 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 ) { static unsigned save_bp; /* Если мы не находимся в прерывании, то передадим * управление старому обработчику. */ if( !finvideo ) _chain_intr( oldvideo ); else { /* Установим флаг занятости и восстановим значения * всех регистров, какими они были в точке прерывания. * Некоторые из них могли быть изменены предшествующим * кодом. Регистр BP должен быть сохранен в статической * переменной, для того чтобы он мог быть восстановлен * без модификации стека. */ ++finvideo; _asm { mov ax, _ax mov bx, _bx mov cx, _cx mov dx, _dx mov es, _es mov di, _di mov save_bp, bp mov bp, _bp } /* Вызовем старый обработчик прерывания. */ (*oldvideo)(); /* Запишем в псевдорегистры значения соответствующих * регистров. Сбросим флаг занятости. */ _asm { mov bp, save_bp mov _bp, bp mov _di, di mov _es, es mov _dx, dx mov _cx, cx mov _bx, bx mov _ax, ax } --finvideo; } } Пример 8. Новый вариант обработчика прерывания вектора 10h void _interrupt _far newvideo( void ) { ++finvideo; _call_intr( oldvideo ); --finvideo; } Пример 9. Обработчик прерывания Int 21h #define highbyte(a) (((a) >> 8) & 0x00ff) void _interrupt _far newdos( ARGS ) { switch ( highbyte( _ax )) { case 0x26: case 0x4b: case 0x00: case 0x31: case 0x4c: _chain_intr( olddos ); } _call_intr( olddos ); ... } Листинг 1 /* Исходный файл: mtsrsize.c * Компилятор : Microsoft C 6.0, Quick C 2.51 * Модель данных: Small */ #include #include enum { ERROR = -1, OK }; /* размер выделенной динамической памяти в параграфах */ #define HEAP_SIZE 64 /* дальние указатели заставляют компилятор выполнять разделение на сегмент/смещение автоматически */ char _huge *tsrstack; char _huge *tsrbottom; int main (void) { unsigned tsrsize; /* Инициализация стека и определение адреса конца программы */ _asm mov WORD PTR tsrstack[0], sp _asm mov WORD PTR tsrstack[2], ss FP_SEG( tsrbottom) = _psp; FP_OFF(tsrbottom ) = 0; /* Размер программы определяется следующими параметрами : * верхушка стека * - нижняя граница программы (преобразованная в параграфы ) * + размер выделенной динамической памяти * + один лишний параграф для учета остатка при целочисленном делении */ tsrsize = ((tsrstack-tsrbottom) >> 4) + HEAP_SIZE + 1; /* Конец работы с сохранением в памяти резидентной части программы */ _dos_keep( OK, tsrsize); /* Сюда мы попасть не должны! */ return ERROR; } Листинг 2 /* Исходный файл: btsrsize.c * Компилятор : Turbo C++ 1.0, Borland C 2.0 * Модель данных: Small */ #include #include enum { ERROR = -1, OK }; /* размер выделенной динамической памяти и стека в параграфах */ unsigned _stklen = 256; unsigned _heaplen = 1024; /* дальние указатели заставляют компилятор выполнять разделение на сегмент/смещение автоматически */ char _huge *tsrstack; char _huge *tsrbottom; int main (void) { unsigned tsrsize; /* Инициализация стека и определение адреса конца программы */ tsrstack = MK_FP( _SS, _SP); tsrbottom = MK_FP( _psp, 0); /* Размер программы определяется следующими параметрами : * верхушка стека * - нижняя граница программы (преобразованная в параграфы ) * + один лишний параграф для учета остатка при целочисленном делении */ tsrsize = ((tsrstack - tsrbottom) >> 4) + 1; /* Конец работы с сохранением в памяти резидентной части программы */ keep( OK, tsrsize); /* Сюда мы попасть не должны! */ return ERROR; } Листинг 3 ; CALLINTR.ASM ; Автор: Дмитрий Валдин. .model small, c ; Последующие определения используются для ; обеспечения доступа к формальным параметрам ; пользовательской процедуры обработки прерываний IFDEF MSC _flags equ Word Ptr [bp+24] _ax equ Word Ptr [bp+18] _cx equ Word Ptr [bp+16] _dx equ Word Ptr [bp+14] _bx equ Word Ptr [bp+12] _bp equ Word Ptr [bp+8] _si equ Word Ptr [bp+6] _di equ Word Ptr [bp+4] _ds equ Word Ptr [bp+2] _es equ Word Ptr [bp+0] ELSEIFDEF BC _flags equ Word Ptr [bp+22] _ax equ Word Ptr [bp+16] _bx equ Word Ptr [bp+14] _cx equ Word Ptr [bp+12] _dx equ Word Ptr [bp+10] _es equ Word Ptr [bp+8] _ds equ Word Ptr [bp+6] _si equ Word Ptr [bp+4] _di equ Word Ptr [bp+2] _bp equ Word Ptr [bp+0] ENDIF .CODE IFDEF BC PUBLIC _call_intr, _chain_intr ENDIF _call_intr PROC NEAR push bp ; Шаг 1: формирование стекового кадра. mov bp, sp ; Регистр BP будет восстановлен на шаге 3. mov cx, Word Ptr [bp+6] ; Шаг 2: сохранить вектор в паре CX:DX. mov dx, Word Ptr [bp+4] ; Эти значения будут использованы на шаге 7. pop bp ; Шаг 3: восстанавливаем BP. push si ; Шаг 4: сохраняем регистры, важные push di ; для среды языка C. push ds push es push bp ; Шаг 5: сохраняем также BP и флаги. pushf mov ax, _flags ; Подготовка к вызову обработчика push ax push cs ; Шаг 6: поместить в стек все объекты, mov bx, offset ret_addr ; которые необходимы для работы обра- push bx ; ботчика, а также регистр флагов и ; адрес возврата (CS:ret_addr). and ah, 0fch ; Шаг 7: сбросить флаги TF и IF push ax ; и поместить в стек регистр флагов push cx ; и адрес первоначального обработчика. push dx mov ax, _ax ; Шаг 8: загрузка регистров. mov cx, _cx ; BP загружается в последнюю очередь. mov dx, _dx mov bx, _bx mov si, _si mov di, _di mov ds, _ds mov es, _es mov bp, _bp iret ; Шаг 9: загрузить флаги и передать ; управление обработчику. Команда ; IRET использует результаты усилий, ; приложенных на шаге 7. ret_addr: ; В эту точку мы попадаем по команде ; IRET, и в стеке остается то, ; что было загружено на шаге 6. ; (Конечно, если обработчик прерываний ; не внес в стек никаких изменений!) push bp ; Шаг 10: временное сохранение pushf ; возвращаемых значений BP и флагов. ; Шаг 11: загрузка BP для обновления ; значений формальных параметров ; процедуры обработки прерываний mov bp, sp mov bp, Word Ptr [bp+6] mov _ax, ax ; Шаг 12: собственное обновление mov _cx, cx ; формальных параметров. mov _dx, dx mov _bx, bx mov _si, si mov _di, di mov _ds, ds mov _es, es pop _flags ; Шаг 13: восстановление значений, pop _bp ; помещенных в стек на шаге 10. popf ; Шаг 14: восстановление флагов и BP, pop bp ; сохраненных на шаге 5. pop es ; Шаг 15: восстановление регистров, pop ds ; используемых средой C pop di ; (смотри шаг 4). pop si ret ; Шаг 16: возврат управления вызывающей ; процедуре. _call_intr ENDP _chain_intr PROC NEAR ; Эта процедура является, по существу, программным трюком. ; Но она работоспособна, поскольку мы знаем, как Borland и Microsoft ; осуществляют сохранение регистров при вызове функции _interrupt. push bp ; Шаг 1: установка стекового кадра. mov bp, sp ; Значение BP будет восстановлено на шаге 3. IFDEF MSC ; Шаг 2: загрузка вектора прерываний в mov cx, Word Ptr [bp+6] ; cx:dx или bx:cx пару. Выбор определяется mov dx, Word Ptr [bp+4] ; используемым компилятором. Эти данные ELSEIFDEF BC ; будут использованы на шаге 8. mov bx, Word Ptr [bp+6] mov cx, Word Ptr [bp+4] ENDIF pop bp ; Шаг 3: восстановление стекового кадра. mov sp, bp ; Так мы можем получить доступ к сохра- ; ненным значениям регистров. ; Шаг 4: загрузка в AX регистра флагов, ; сброс IF и TF. mov ax, _flags and ah, 0fch ; Шаг 5: восстановление сохраненных xchg ax, _ax ; регистров. xchg cx, _cx IFDEF MSC xchg dx, _dx pop es pop ds pop di pop si pop bp pop bx pop bx ELSEIFDEF BC xchg bx, _bx pop bp pop di pop si pop ds pop es pop dx ENDIF ; Шаг 6: передача управления первоначальному ; обработчику. iret _chain_intr ENDP END