Выгрузка резидентной программы из памяти
Чтобы выгрузить резидентную программу из памяти, необходимо сделать три вещи: закрыть открытые программой файлы и устройства, восстановить все перехваченные векторы прерываний, и наконец, освободить всю занятую программой память. Трудность может вызвать второй шаг, так как после нашего резидента могли быть загружены другие программы, перехватившие те же прерывания. Если в такой ситуации восстановить вектор прерывания в значение, которое он имел до загрузки нашего резидента, программы, загруженные позже, не будут получать управление. Более того, они не будут получать управление только по тем прерываниям, которые у них совпали с прерываниями, перехваченными нашей программой, в то время как другие векторы прерываний будут все еще указывать на их обработчики, что почти наверняка приведет к ошибкам. Поэтому, если хоть один вектор прерывания не указывает на наш обработчик, выгружать резидентную программу нельзя. Это всегда было главным вопросом, и спецификации 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.
Содержание раздела