| [Назад] [Далее] | ![]() |
Чтобы выгрузить резидентную программу из памяти, необходимо сделать три вещи: закрыть открытые программой файлы и устройства, восстановить все перехваченные векторы прерываний, и наконец, освободить всю занятую программой память. Трудность может вызвать второй шаг, так как после нашего резидента могли быть загружены другие программы, перехватившие те же прерывания. Если в такой ситуации восстановить вектор прерывания в значение, которое он имел до загрузки нашего резидента, программы, загруженные позже, не будут получать управление. Более того, они не будут получать управление только по тем прерываниям, которые у них совпали с прерываниями, перехваченными нашей программой, в то время как другие векторы прерываний будут все еще указывать на их обработчики, что почти наверняка приведет к ошибкам. Поэтому, если хоть один вектор прерывания не указывает на наш обработчик, выгружать резидентную программу нельзя. Это всегда было главным вопросом, и спецификации AMIS и IBM ISP (см. предыдущую главу) являются возможным решением этой проблемы. Если вектор прерывания не указывает на нас, имеет смысл проверить, не указывает ли он на ISP-блок (первые два байта должны быть EBh 10h, а байты 6 и 7 — «K» и «B»), и, если это так, взять в качестве вектора значение из этого блока и т.д. Кроме того, программы могут изменять порядок, в котором обработчики одного и того же прерывания вызывают друг друга.
Последний шаг в выгрузке программы — освобождение памяти — можно выполнить вручную, вызывая функцию DOS 49h на каждый блок памяти, который программа выделяла через функцию 48h, на блок с окружением DOS, если он не освобождался при загрузке, и наконец, на саму программу. Однако есть способ заставить DOS сделать все это (а также закрыть открытые файлы и вернуть код возврата) автоматически, вызвав функцию 4Ch, объявив резидент текущим процессом. Посмотрим, как это делается на примере резидентной программы, занимающей много места в памяти. Кроме того, этот пример реализует все приемы, использующиеся для вызова функций DOS из обработчиков аппаратных прерываний, о которых рассказано в главе 5.8.3.
; scrgrb.asm
; Резидентная программа, сохраняющая изображение с экрана в файл.
; Поддерживается только видеорежим 13h (320x200x256) и только один файл.
; HCI:
; Нажатие Alt-G создает файл scrgrb.bmp в текущем каталоге с изображением,
; находившимся на экране в момент нажатия клавиши.
; Запуск с командной строкой /u выгружает программу из памяти
; API:
; Программа занимает первую свободную функцию прерывания 2Dh (кроме нуля)
; в соответствии со спецификацией AMIS 3.6
; Поддерживаемые подфункции AMIS: 00h, 02h, 03h, 04h, 05h
; Все обработчики прерываний построены в соответствии с IBM ISP
; Резидентная часть занимает в памяти 1056 байт, если присутствует EMS,
; и 66 160 байт, если EMS не обнаружен
.model tiny
.code
.186 ; для сдвигов и команд pusha/popa
org 2Ch
envseg dw ? ; сегментный адрес окружения
org 80h
cmd_len db ? ; длина командной строки
cmd_line db ? ; командная строка
org 100h ; COM-программа
start:
jmp initialize ; переход на инициализирующую часть
; Обработчик прерывания 09h (IRQ1)
int09h_handler proc far
jmp short actual_int09h_handler ; пропустить ISP
old_int09h dd ?
dw 424Bh
db 00h
jmp short hw_reset
db 7 dup (0)
actual_int09h_handler: ; начало собственно обработчика INT 09h
pushf
call dword ptr cs:old_int09h ; сначала вызвать старый
; обработчик, чтобы он завершил аппаратное
; прерывание и передал код в буфер
pusha ; это аппаратное прерывание - надо
push ds ; сохранить все регистры
push es
push 0040h
pop ds ; DS = сегментный адрес области данных BIOS
mov di,word ptr ds:001Ah ; адрес головы буфера
; клавиатуры,
cmp di,word ptr ds:001Ch ; если он равен адресу
; хвоста,
je exit_09h_handler ; буфер пуст, и нам делать нечего,
mov ax,word ptr [di] ; иначе: считать символ,
cmp ah,22h ; если это не G (скан-код 22h),
jne exit_09h_handler ; выйти
mov al,byte ptr ds:0017h ; байт состояния клавиатуры,
test al,08h ; если Alt не нажата,
jz exit_09h_handler ; выйти,
mov word ptr ds:001Ch,di ; иначе: установить адреса головы
; и хвоста буфера равными, то есть
; опустошить его
call do_grab ; подготовить BMP-файл с изображением
mov byte ptr cs:io_needed, 1 ; установить флаг
; требующейся записи на диск
cli
call safe_check ; проверить, можно ли вызвать DOS,
jc exit_09h_handler
sti
call do_io ; если да - записать файл на диск
exit_09h_handler:
pop es
pop ds ; восстановить регистры
рора
iret ; и вернуться в прерванную программу
int09h_handler endp
hw_reset: retf
; Обработчик INT 08h (IRQ0)
int08h_handler proc far
jmp short actual_int08h_handler ; пропустить ISP
old_int08h dd ?
dw 424Bh
db 00h
jmp short hw_reset
db 7 dup (0)
actual_int08h_handler: ; собственно обработчик
pushf
call dword ptr cs:old_int08h ; сначала вызвать стандартный
; обработчик, чтобы он завершил
; аппаратное прерывание (пока оно
; не завершено, запись на диске невозможна)
pusha
push ds
cli ; между любой проверкой глобальной переменной
; и принятием решения по ее значению -
; не повторно входимая область, прерывания
; должны быть запрещены
cmp byte ptr cs:io_needed,0 ; проверить,
je no_io_needed ; нужно ли писать на диск
call safe_check ; проверить,
jc no_io_needed ; можно ли писать на диск
sti ; разрешить прерывания на время записи
call do_io ; запись на диск
no_io_needed:
pop ds
рора
iret
int08h_handler endp
; Обработчик INT 13h
; поддерживает флаг занятости INT 13h, который тоже надо проверять перед
; записью на диск
int13h_handler proc far
jmp short actual_int13h_handler ; пропустить ISP
old_int13h dd ?
dw 424Bh
db 00h
jmp short hw_reset
db 7 dup (0)
actual_int13h_handler: ; собственно обработчик
pushf
inc byte ptr cs:bios_busy ; увеличить счетчик занятости INT 13h
cli
call dword ptr cs:old_int13h
pushf
dec byte ptr cs:bios_busy ; уменьшить счетчик
popf
ret 2 ; имитация команды IRET, не восстанавливающая
; флаги из стека, так как обработчик INT 13h возвращает некоторые
; результаты в регистре флагов, а не в его копии, хранящейся
; в стеке. Он тоже завершается командой ret 2
int13h_handler endp
; Обработчик INT 28h
; вызывается DOS, когда она ожидает ввода с клавиатуры и функциями DOS можно
; пользоваться
int28h_handler proc far
jmp short actual_int28h_handler ; пропустить ISP
old_int28h dd ?
dw 424Вh
db 00h
jmp short hw_reset
db 7 dup (0)
actual_int28h_handler:
pushf
push di
push ds
push cs
pop ds
cli
cmp byte ptr io_needed,0 ; проверить,
je no_io_needed2 ; нужно ли писать на диск
lds di,dword ptr in_dos_addr
cmp byte ptr [di+1],1 ; проверить,
ja no_io_needed2 ; можно ли писать на диск (флаг
; занятости DOS не должен быть больше 1)
sti
call do_io ; запись на диск
no_io_needed2:
pop ds
pop di
popf
jmp dword ptr cs:old_int28h ; переход на старый
; обработчик INT 28h
int28h_handler endp
; Процедура do_grab
; помещает в буфер палитру и содержимое видеопамяти, формируя BMP-файл.
; Считает, что текущий видеорежим - 13h
do_grab proc near
push cs
pop ds
call ems_init ; отобразить наш буфер в окно EMS
mov dx,word ptr cs:buffer_seg
mov es,dx ; поместить сегмент с буфером в ES и DS
mov ds,dx ; для следующих шагов процедуры
mov ax,1017h ; Функция 1017h - чтение палитры VGA
mov bx,0 ; начиная с регистра палитры 0,
mov сх,256 ; все 256 регистров
mov dx,BMP_header_length ; начало палитры в BMP
int 10h ; видеосервис BIOS
; перевести палитру из формата, в котором ее показывает функция 1017h
; (три байта на цвет, в каждом байте 6 значимых бит),
; в формат, используемый в BMP-файлах
; (4 байта на цвет, в каждом байте 8 значимых бит)
std ; движение от конца к началу
mov si,BMP_header_length+256*3-1 ; SI- конец 3-байтной палитры
mov di,BMP_header_length+256*4-1 ; DI - конец 4-байтной палитры
mov сх,256 ; СХ - число цветов
adj_pal:
mov al,0
stosb ; записать четвертый байт (0)
lodsb ; прочитать третий байт
shl al,2 ; масштабировать до 8 бит
push ax
lodsb ; прочитать второй байт
shl al,2 ; масштабировать до 8 бит
push ax
lodsb ; прочитать третий байт
shl al,2 ; масштабировать до 8 бит
stosb ; и записать эти три байта
pop ax ; в обратном порядке
stosb
pop ax
stosb
loop adj_pal
; Копирование видеопамяти в BMP.
; В формате BMP строки изображения записываются от последней к первой, так что
; первый байт соответствует нижнему левому пикселю
cld ; движение от начала к концу (по строке)
push 0A000h
pop ds
mov si,320*200 ; DS:SI - начало последней строки на экране
mov di,bfoffbits ; ES:DI - начало данных в BMP
mov dx,200 ; счетчик строк
bmp_write_loop:
mov cx,320/2 ; счетчик символов в строке
rep movsw ; копировать целыми словами, так быстрее
sub si,320*2 ; перевести SI на начало предыдущей строки
dec dx ; уменьшить счетчик строк,
jnz bmp_write_loop ; если 0 - выйти из цикла
call ems_reset ; восстановить состояние EMS
; до вызова do_grab
ret
do_grab endp
; Процедура do_io
; создает файл и записывает в него содержимое буфера
do_io proc near
push cs
pop ds
mov byte ptr io_needed,0 ; сбросить флаг требующейся
; записи на диск
call ems_init ; отобразить в окно EMS наш буфер
mov ah,6Ch ; Функция DOS 6Ch
mov bx,2 ; доступ - на чтение/запись
mov cx,0 ; атрибуты - обычный файл
mov dx,12h ; заменять файл, если он существует,
; создавать, если нет
mov si,offset filespec ; DS:SI - имя файла
int 21h ; создать/открыть файл
mov bx,ax ; идентификатор файла - в ВХ
mov ah,40h ; Функция DOS 40h
mov cx,bfsize ; размер BMP-файла
mov ds,word ptr buffer_seg
mov dx,0 ; DS:DX - буфер для файла
int 21h ; запись в файл или устройство
mov ah,68h ; сбросить буфера на диск
int 21h
mov ah,3Eh ; закрыть файл
int 21h
call ems_reset
ret
do_io endp
; Процедура ems_init,
; если буфер расположен в EMS, подготавливает его для чтения/записи
ems_init proc near
cmp dx,word ptr ems_handle ; если не используется EMS
cmp dx,0 ; (EMS-идентификаторы начинаются с 1),
je ems_init_exit ; ничего не делать
mov ax,4700h ; Функция EMS 47h
int 67h ; сохранить EMS-контекст
mov ax,4100h ; Функция EMS 41h
int 67h ; определить адрес окна EMS
mov word ptr buffer_seg,bx ; сохранить его
mov ax,4400h ; Функция EMS 44h
mov bx,0 ; начиная со страницы 0,
int 67h ; отобразить страницы EMS в окно
mov ax,4401h
inc bx
int 67h ; страница 1
mov ax,4402h
inc bx
int 67h ; страница 2
mov ax,4403h
inc bx
int 67h ; страница 3
ems_init_exit:
ret
ems_init endp
; Процедура ems_reset
; восстанавливает состояние EMS
ems_reset proc near
mov dx,word ptr cs:ems_handle
cmp dx,0
je ems_reset_exit
mov ax,4800h ; Функция EMS 48h
int 67h ; восстановить EMS-контекст
ems_reset_exit:
ret
ems_reset endp
; Процедура safe_check
; возвращает CF = 0, если в данный момент можно пользоваться функциями DOS,
; и CF = 1, если нельзя
safe_check proc near
push es
push cs
pop ds
les di,dword ptr in_dos_addr ; адрес флагов занятости DOS,
cmp word ptr es:[di],0 ; если один из них не 0,
pop es
jne safe_check_failed ; пользоваться DOS нельзя,
cmp byte ptr bios_busy,0 ; если выполняется прерывание 13h,
jne safe_check_failed ; тоже нельзя
clc ; CF = 0
ret
safe_check_failed:
stc ; CF = 1
ret
safe_check endp
in_dos_addr dd ? ; адрес флагов занятости DOS
io_needed db 0 ; 1, если надо записать файл на диск
bios_busy db 0 ; 1, если выполняется прерывание INT 13h
buffer_seg dw 0 ; сегментный адрес буфера для файла
ems_handle dw 0 ; идентификатор EMS
filespec db 'scrgrb.bmp',0 ; имя файла
; Обработчик INT 2Dh
hw_reset2D: retf
int2Dh_handler proc far
jmp short actual_int2Dh_handler ; пропустить ISP
old_int2Dh dd ?
dw 424Bh
db 00h
jmp short hw_reset2D
db 7 dup (0)
actual_int2Dh_handler: ; собственно обработчик
db 80h,0FCh ; начало команды CMP АН,число
mux_id db ? ; идентификатор программы,
je its_us ; если вызывают с чужим АН - это не нас
jmp dword ptr cs:old_int2Dh
its_us:
cmp al,06 ; функции AMIS 06h и выше
jae int2D_no ; не поддерживаются
cbw ; AX = номер функции
mov di,ax ; DI = номер функции
shl di,1 ; * 2, так как jumptable - таблица слов
jmp word ptr cs:jumptable[di] ; переход на обработчик функции
jumptable dw offset int2D_00,offset int2D_no
dw offset int2D_02,offset int2D_no
dw offset int2D_04,offset int2D_05
int2D_00: ; проверка наличия
mov al,0FFh ; этот номер занят
mov cx,0100h ; номер версии программы 1.0
push cs
pop dx ; DX:DI - адрес AMIS-сигнатуры
mov di,offset amis_sign
iret
int2D_no: ; неподдерживаемая функция
mov al,00h ; функция не поддерживается
iret
unload_failed: ; сюда передается управление, если хоть один из векторов
; прерываний был перехвачен кем-то после нас
mov al,01h ; выгрузка программы не удалась
iret
int2D_02: ; выгрузка программы из памяти
cli ; критический участок
push 0
pop ds ; DS - сегментный адрес таблицы векторов прерываний
mov ax,cs ; наш сегментный адрес
; проверить, все ли перехваченные прерывания по-прежнему указывают на нас,
; обычно достаточно проверить только сегментные адреса (DOS не загрузит другую
; программу с нашим сегментным адресом)
cmp ax,word ptr ds:[09h*4+2]
jne unload_failed
cmp ax,word ptr ds:[13h*4+2]
jne unload_failed
cmp ax,word ptr ds:[08h*4+2]
jne unload_failed
cmp ax,word ptr ds:[28h*4+2]
jne unload_failed
cmp ax,word ptr ds:[2Dh*4+2]
jne unload_failed
push bx ; адрес возврата - в стек
push dx
; восстановить старые обработчики прерываний
mov ax,2509h
lds dx,dword ptr cs:old_int09h
int 21h
mov ax,2513h
lds dx,dword ptr cs:old_int13h
int 21h
mov ax,2508h
lds dx,dword ptr cs:old_int08h
int 21h
mov ax,2528h
lds dx,dword ptr cs:old_int28h
int 21h
mov ax,252Dh
lds dx,dword ptr cs:old_int2Dh
int 21h
mov dx,word ptr cs:ems_handle ; если используется EMS
cmp dx,0
je no_ems_to_unhook
mov ax,4500h ; функция EMS 45h
int 67h ; освободить выделенную память
jmp short ems_unhooked
no_ems_to_unhook:
ems_unhooked:
; собственно выгрузка резидента
mov ah,51h ; Функция DOS 51h
int 21h ; получить сегментный адрес PSP
; прерванного процесса (в данном случае
; PSP - копии нашей программы,
; запущенной с ключом /u)
mov word ptr cs:[16h],bx ; поместить его в поле
; "сегментный адрес предка" в нашем PSP
pop dx ; восстановить адрес возврата из стека
pop bx
mov word ptr cs:[0Ch],dx ; и поместить его в поле
mov word ptr cs:[0Ah],bx ; "адрес перехода при
; завершении программы" в нашем PSP
pop bx ; BX = наш сегментный адрес PSP
mov ah,50h ; Функция DOS 50h
int 21h ; установить текущий PSP
; теперь DOS считает наш резидент текущей программой, а scrgrb.com /u -
; вызвавшим его процессом, которому и передаст управление после вызова
; следующей функции
mov ax,4CFFh ; Функция DOS 4Ch
int 21h ; завершить программу
int2D_04: ; получить список перехваченных прерываний
mov dx,cs ; список в DX:BX
mov bx,offset amis_hooklist
iret
int2D_05: ; получить список "горячих" клавиш
mov al,0FFh ; функция поддерживается
mov dx,cs ; список в DX:BX
mov bx,offset amis_hotkeys
iret
int2Dh_handler endp
; AMIS: сигнатура для резидентной программы
amis_sign db "Cubbi..." ; 8 байт
db "ScrnGrab" ; 8 байт
db "Simple screen grabber using EMS",0
; AMIS: список перехваченных прерываний
amis_hooklist db 09h
dw offset int09h_handler
db 08h
dw offset int08h_handler
db 28h
dw offset int28h_handler
db 2Dh
dw offset int2Dh_handler
; AMIS: список "горячих" клавиш
amis_hotkeys db 1
db 1
db 22h ; скан-код клавиши (G)
dw 08h ; требуемые флаги клавиатуры
dw 0
db 1
; конец резидентной части
; начало процедуры инициализации
initialize proc near
jmp short initialize_entry_point
; пропустить различные варианты выхода без установки резидента,
; помещенные здесь потому, что на них передают управление
; команды условного перехода, имеющие короткий радиус действия
exit_with_message:
mov ah,9 ; функция вывода строки на экран
int 21h
ret ; выход из программы
already_loaded: ; если программа уже загружена в память
cmp byte ptr unloading,1 ; если мы не были вызваны с /u
je do_unload
mov dx,offset already_msg
jmp short exit_with_message
no_more_mux: ; если свободный идентификатор INT 2Dh не найден
mov dx,offset no_more_mux_msg
jmp short exit_with_message
cant_unload1: ; если нельзя выгрузить программу
mov dx,offset cant_unload1_msg
jmp short exit_with_message
do_unload: ; выгрузка резидента: при передаче управления сюда АН содержит
; идентификатор программы - 1
inc ah
mov al,02h ; AMIS-функция выгрузки резидента
mov dx,es ; адрес возврата
mov bx,offset exit_point ; в DX:BX
int 2Dh ; вызов нашего резидента через мультиплексор
push cs ; если управление пришло сюда -
; выгрузка не произошла
pop ds
mov dx,offset cant_unload2_msg
jmp short exit_with_message
exit_point: ; если управление пришло сюда -
push cs ; выгрузка произошла
pop ds
mov dx,offset unloaded_msg
push 0 ; чтобы сработала команда RET для выхода
jmp short exit_with_message
initialize_entry_point: ; сюда передается управление в самом начале
cld
cmp byte ptr cmd_line[1],'/'
jne not_unload
cmp byte ptr cmd_line[2],'u' ; если нас вызвали с /u
jne not_unload
mov byte ptr unloading,1 ; выгрузить резидент
not_unload:
mov ah, 9
mov dx,offset usage ; вывод строки с информацией о программе
int 21h
mov ah,-1 ; сканирование от FFh до 01h
more_mux:
mov al,00h ; функция AMIS 00h -
; проверка наличия резидента
int 2Dh ; мультиплексорное прерывание
cmp al,00h ; если идентификатор свободен,
jne not_free
mov byte ptr mux_id,ah ; вписать его сразу в код обработчика,
jmp short next_mux
not_free:
mov es,dx ; иначе - ES:DI = адрес AMIS-сигнатуры
; вызвавшей программы
mov si,offset amis_sign ; DS:SI = адрес нашей сигнатуры
mov cx,16 ; сравнить первые 16 байт,
repe cmpsb
jcxz already_loaded ; если они не совпадают,
next_mux:
dec ah ; перейти к следующему идентификатору,
jnz more_mux ; если это 0
free_mux_found:
cmp byte ptr unloading, 1 ; и если нас вызвали для выгрузки,
je cant_unload1 ; а мы пришли сюда - программы нет в
; памяти,
cmp byte ptr mux_id,0 ; если при этом mux_id все еще 0,
je no_more_mux ; идентификаторы кончились
; проверка наличия устройства ЕММХХХХ0
mov dx,offset ems_driver
mov ax,3D00h
int 21h ; открыть файл/устройство
jc no_emmx
mov bx,ax
mov ax,4400h
int 21h ; IOCTL: получить состояние файла/устройства
jc no_ems
test dx,80h ; если старший бит DX = 0, ЕММХХХХ0 - файл
jz no_ems
; выделить память под буфер в EMS
mov ax,4100h ; функция EMS 41h
int 67h ; получить адрес окна EMS
mov bp,bx ; сохранить его пока в ВР
mov ax,4300h ; Функция EMS 43h
mov bx,4 ; нам надо 4 * 16 Кб
int 67h ; выделить EMS-память (идентификатор в DХ),
cmp ah,0 ; если произошла ошибка (нехватка памяти?),
jnz ems_failed ; не будем пользоваться EMS,
mov word ptr ems_handle,dx ; иначе: сохранить идентификатор
; для резидента
mov ax,4400h ; Функция 44h - отобразить
mov bx,0 ; EMS-страницы в окно
int 67h ; страница 0
mov ax,4401h
inc bx
int 67h ; страница 1
mov ax,4402h
inc bx
int 67h ; страница 2
mov ax,4403h
inc bx
int 67h ; страница 3
mov dx,offset ems_msg ; вывести сообщение об установке в EMS
jmp short ems_used
ems_failed:
no_ems: ; если EMS нет или он не работает,
mov ah,3Eh
int 21h ; закрыть файл/устройство ЕММХХХХ0,
no_emmx:
; занять общую память
mov ah,9
mov dx,offset conv_msg ; вывод сообщения об этом
int 21h
mov sp,length_of_program+100h+200h ; перенести стек
mov ah,4Ah ; Функция DOS 4Ah
next_segment = length_of_program+100h+200h+0Fh
next_segment = next_segment/16 ; такая запись нужна только для
; WASM, остальным ассемблерам это
; можно было записать в одну строчку
mov bx,next_segment ; уменьшить занятую память, оставив
; текущую длину нашей программы + 100h
; на PSP +200h на стек
int 21h
mov ah,48h ; Функция 48h - выделить память
bfsize_p = bfsize+0Fh
bfsize_p = bfsize_p/16
mov bx,bfsize_p ; размер BMP-файла 320x200x256 в 16-байтных
int 21h ; параграфах
ems_used:
mov word ptr buffer_seg,ax ; сохранить адрес буфера для резидента
; скопировать заголовок BMP-файла в начало буфера
mov cx,BMP_header_length
mov si,offset BMP_header
mov di,0
mov es,ax
rep movsb
; получить адреса флага занятости DOS и флага критической ошибки (считая, что
; версия DOS старше 3.0)
mov ah,34л ; Функция 34h - получить флаг занятости
int 21h
dec bx ; уменьшить адрес на 1, чтобы он указывал
; на флаг критической ошибки,
mov word ptr in_dos_addr,bx
mov word ptr in_dos_addr+2,es ; и сохранить его для резидента
; перехват прерываний
mov ax,352Dh ; АН = 35h, AL = номер прерывания
int 21h ; получить адрес обработчика INT 2Dh
mov word ptr old_int2Dh,bx ; и поместить его в old_int2Dh
mov word ptr old_int2Dh+2,es
mov ax,3528h ; AH = 35h, AL = номер прерывания
int 21h ; получить адрес обработчика INT 28h
mov word ptr old_int28h,bx ; и поместить его в old_int28h
mov word ptr old_int28h+2,es
mov ax,3508h ; AH = 35h, AL = номер прерывания
int 21h ; получить адрес обработчика INT 08h
mov word ptr old_int08h,bx ; и поместить его в old_int08h
mov word ptr old_int08h+2,es
mov ax,3513h ; AH = 35h, AL = номер прерывания
int 21h ; получить адрес обработчика INT 13h
mov word ptr old_int13h,bx ; и поместить его в old_int13h
mov word ptr old_int13h+2,es
mov ax,3509h ; AH = 35h, AL = номер прерывания
int 21h ; получить адрес обработчика INT 09h
mov word ptr old_int09h,bx ; и поместить его в old_int09h
mov word ptr old_int09h+2,es
mov ax,252Dh ; AH = 25h, AL = номер прерывания
mov dx,offset int2Dh_handler ; DS:DX - адрес обработчика
int 21h ; установить новый обработчик INT 2Dh
mov ax,2528h ; AH = 25h, AL = номер прерывания
mov dx,offset int28h_handler ; DS:DX - адрес обработчика
int 21h ; установить новый обработчик INT 28h
mov ax,2508h ; AH = 25h, AL = номер прерывания
mov dx,offset int08h_handler ; DS:DX - адрес обработчика
int 21h ; установить новый обработчик INT 08h
mov ax,2513h ; AH = 25h, AL = номер прерывания
mov dx,offset int13h_handler ; DS:DX - адрес обработчика
int 21h ; установить новый обработчик INT 13h
mov ax,2509h ; AH = 25h, AL = номер прерывания
mov dx,offset int09h_handler ; DS:DX - адрес обработчика
int 21h ; установить новый обработчик INT 09h
; освободить память из-под окружения DOS
mov ah,49h ; Функция DOS 49h
mov es,word ptr envseg ; ES = сегментный адрес окружения DOS
int 21h ; освободить память
; оставить программу резидентной
mov dx,offset initialize ; DX - адрес первого байта за концом
; резидентной части
int 27h ; завершить выполнение, оставшись
; резидентом
initialize endp
ems_driver db 'EMMXXXX0',0 ; имя EMS-драйвера для проверки
; текст, который выдает программа при запуске:
usage db 'Простая программа для копирования экрана только из'
db ' видеорежима 13h',0Dh,0Ah
db ' Alt-G - записать копию экрана в scrgrb.bmp'
db 0Dh,0Ah
db ' scrgrb.com /u - выгрузиться из памяти',0Dh,0Ah
db '$'
; тексты, которые выдает программа при успешном выполнении:
ems_msg db 'Загружена в EMS',0Dh,0Ah,'$'
conv_msg db 'He загружена в EMS',0Dh,0Ah,'$'
unloaded_msg db 'Программа успешно выгружена из памяти',0Dh,0Ah,'$'
; тексты, которые выдает программа при ошибках:
already_msg db 'Ошибка: Программа уже загружена',0Dh,0Ah,'$'
no_more_mux_msg db 'Ошибка: Слишком много резидентных программ'
db 0Dh,0Ah,'$'
cant_unload1_msg db 'Ошибка: Программа не обнаружена в памяти',0Dh,0Ah,'$'
cant_unload2_msg db 'Ошибка: Другая программа перехватила прерывания'
db 0Dh,0Ah,'$'
unloading db 0 ; 1, если нас запустили с ключом /u
; BMP-файл (для изображения 320x200x256)
BMP_header label byte
; файловый заголовок
BMP_file_header db "BM" ; сигнатура
dd bfsize ; размер файла
dw 0,0 ; 0
dd bfoffbits ; адрес начала BMP_data
; информационный заголовок
BMP_info_header dd bi_size ; размер BMP_info_header
dd 320 ; ширина
dd 200 ; высота
dw 1 ; число цветовых плоскостей
dw 8 ; число бит на пиксель
dd 0 ; метод сжатия данных
dd 320*200 ; размер данных
dd 0B13h ; разрешение по X (пиксель на метр)
dd 0B13h ; разрешение по Y (пиксель на метр)
dd 0 ; число используемых цветов (0 - все)
dd 0 ; число важных цветов (0 - все)
bi_size = $-BMP_info_header ; размер BMP_info_header
BMP_header_length = $-BMP_header ; размер обоих заголовков
bfoffbits = $-BMP_file_header+256*4 ; размер заголовков + размер палитры
bfsize = $-BMP_file_header+256*4+320*200 ; размер заголовков +
; размер палитры + размер данных
length_of_program = $-start
end start
В этом примере, достаточно сложном из-за необходимости избегать всех возможностей повторного вызова прерываний DOS и BIOS, добавилась еще одна мера предосторожности — сохранение состояния EMS-памяти перед работой с ней и восстановление в исходное состояние. Действительно, если наш резидент активируется в тот момент, когда какая-то программа работает с EMS, и не выполнит это требование, программа будет читать/писать уже не в свои EMS-страницы, а в наши. Аналогичные предосторожности следует предпринимать всякий раз, когда вызываются функции, затрагивающие какие-нибудь глобальные структуры данных. Например: функции поиска файлов используют буфер DTA, адрес которого надо сохранить (функция DOS 2Fh), затем создать собственный (функция DOS 1Ah) и в конце восстановить DTA прерванного процесса по сохраненному адресу (функция 1Ah). Таким образом надо сохранять/восстанавливать состояние адресной линии А20 (функции XMS 07h и 03h), если резидентная программа хранит часть своих данных или кода в области HMA, сохранять состояние драйвера мыши (INT 33h, функции 17h и 18h), сохранять информацию о последней ошибке DOS (функции DOS 59h и 5D0Ah), и так с каждым ресурсом, который затрагивает резидентная программа. Писать полноценные резидентные программы в DOS сложнее всего, но, если не выходить за рамки реального режима, это — самое эффективное средство управления системой и реализации всего, что только можно реализовать в DOS.