
;            RESID27.ASM - программа к Главе № 27

; (С) Авторские права на файлы-приложения принадлежат автору книги
; "Ассемблер? Это просто! Учимся программировать под MS-DOS"
; Автор: Калашников Олег Александрович (e-mail: Assembler@Kalashnikoff.ru)
;	 http://www.Kalashnikoff.ru

; --- Ассемблирование (получение *.com файла) ---
;При использовании MASM 6.11 - 6.13:
;ML.EXE resid27.asm /AT

;При использовании TASM:
;TASM.EXE resid27.asm
;TLINK.EXE resid27.obj /t/x


;   === Начало программы: ===
cseg segment
assume cs:cseg, ds:cseg, ss:cseg, es:cseg
org 100h

Begin:
    jmp Init ; На метку инициализации 


; === Обработчик 21h-ого прерывания ===
Int_21h_proc proc
; ---
    cmp ax,9988h  ;Проверка на повторную загрузку?
    jne No_test
    xchg ah,al    ;Даем наш "отклик".
    iret          ;И моментально выходим из прерывания...

; ---
No_test:
    cmp ax,9999h  ;Получаем информацию о нашем резиденте?
    jne No_remove
    mov ax,9998h  ;Даем отклик и...

    push cs       ;...передаем в ES сегмент резидента...
    pop es
    mov dx,offset Int_21h_proc ;...в DX смещение...

;...BX - смещение оригинального обработчика...
    mov bx,word ptr cs:[Int_21h_vect]
;...CX - сегмент оригинального обработчика...
    mov cx,word ptr cs:[Int_21h_vect+2]
    iret ;...и выходим из резидента.

; ---
No_remove:
;Ну что сделает наш резидент? Да, собственное, суть не в том, что он будет
;делать, а в том, что мы сможем его выгружать из памяти.
;Поэтому давайте просто будем заменять символы 'A' на 'Z', если они
;выводятся при помощи функции 02 прерывания 21h. Ниже все видно:
    cmp ah,2
    jne Go_21h

    cmp dl,'A'
    jne Go_21h

    mov dl,'Z'

Go_21h:
;Передаем управление предыдущему (оригинальному) обработчику
;21h-ого прерывания без условия того, что потом опять вернемся сюда (jmp...).
;А вот если бы мы использовали call..., то ... (дополните сами!).

    jmp dword ptr cs:[Int_21h_vect]

Int_21h_vect dd ?
Int_21h_proc endp




;   === Инициализация (подготовка и настройка резидента) ===
Init:
       mov es,word ptr cs:[2Ch] ;Получим сегмент окружения DOS.
       mov ah,49h               ;Функция освобождения памяти.
       int 21h                  ;Освобождаем память...

       call Get_cmd  ;Проверим командную строку

;Итак,
;Если ничего в командной строке не введено, тогда пробуем установить резидент.
       or al,al
       jz Ok_cmd  ;

;Если в командной строке введено '/u', то пробуем удалить программу из памяти.
       cmp al,1
       je Remove  ;

;В противном случае выведем сообщение о неверной командной строке
;и завершимся...
Bad_cmd:
       mov dx,offset Mess_badcmd
       call Out_mess
       ret


;На процедуру удаления программы из памяти...
Remove:
       jmp Remove_prog   ;


;Устанавливаем резидент.
Ok_cmd:
       mov ax,9988h ;Проверка на повторную загрузку.
       int 21h
       cmp ax,8899h ;Получили наш отклик?
       jne Next_step2  ;Нет. Тогда 

;Мы уже в памяти! Выведем соответствующую строку.
       mov dx,offset Mess_memory
       call Out_mess   ;Универсальная процедура вывода строки.
       ret   ;Выйдем в DOS...

Next_step2:
; === 21h ===
;Все готово для перехвата прерывания и установки резидента.
       mov ax,3521h
       int 21h ;Получим и сохраним адрес (вектор) 21h прерывания
       mov word ptr cs:[Int_21h_vect],bx ;Смещение...
       mov word ptr cs:[Int_21h_vect+2],es ;Сегмент...

       mov ax,2521h
       mov dx,offset Int_21h_proc
       int 21h  ;"Повесим" нашу процедуру на 21h прерывание

;Выведем сообщение, что, мол, все в порядке!!! Программа загружена в память!
       mov dx,offset Mess_hello
       call Out_mess

;Оставляем резидентную часть в памяти и выходим в DOS.
       mov dx,offset Init
       int 27h


; ======= Пошли подпрограммы =======

; --- Получим параметры в командной строке ---
Get_cmd proc
       mov si,80h   ;SI=смещение командной строки.
       lodsb        ;Получим кол-во символов.
       or al,al     ;Если 0 символов введено,
       jz Got_cmd   ;то все в порядке. 
       cmp al,3     ;Иначе ввели не 3 символа? (пробел + /u)
       jne No_string ;Да - на метку No_string 

       inc si       ;Теперь SI указывает на первый символ строки.

Next_char:
       lodsw       ;Получаем два символа
       cmp ax,'u/' ;Это /u? Помним, что данные будут наоборот!!!
       jne No_string ;Да - на выход... 

       mov al,1    ;Сигнал того, что пора удалять программу из памяти
       ret

Got_cmd:
       xor al,al ;Сигнал того, что ничего не ввели в командной строке
       ret  ;Выходим из процедуры

No_string:
       mov al,3 ;Сигнал неверного ввода командной строки
       ret  ;Выходим из процедуры
Get_cmd endp


; === Удаляем программу из памяти ===
Remove_prog:
;Прежде посылаем сигнал 21h-ому прерыванию, т.е. 9999h.
       mov ax,9999h
       int 21h

;Если в ответ получаем 9998h, то наш резидент "сидит" в памяти.
       cmp ax,9998h
       je In_mem     ;Перейдем на соответствующую метку.

;Если мы не получили отклик (9998h), то наш резидент не загружен.
;Сообщим об этом пользователю и выйдем в DOS.
       mov dx,offset Mess_badmem
       call Out_mess
       ret


;Итак, наш резидент сидит в памяти.

;Помимо отклика от нашего резидента мы также получаем (см. процедуру
;обрабоки прерывания 21h выше):
;* ES = сегмент, в который загрузился резидент;
;* DX = смещение резидента в данном сегменте;
;* CX = сегмент оринигального (прежнего) обработчика прерывания 21h;
;* BX = смещение оринигального (прежнего) обработчика прерывания 21h.

In_mem:
       push es    ;Сохраним некоторые регистры в стеке,..
       push bx

       mov Seg_21h,es ;...а также в переменных.
       mov Off_21h,dx

       push bx
       push cx

       mov ax,3521h
       int 21h     ;Получим адрес обработчика 21h-прерывания.

;Равен ли он тому, куда загружен наш обработчик?
;Если так, то никто не "повис" над нами. Т.е. можно смело удалять нашу
;программу из памяти.
       mov ax,es
       cmp ax,Seg_21h
       jne Cannot_remove

       cmp bx,Off_21h
       jne Cannot_remove

;Вот и удаляем. Внимательно проследите, что мы загружаем в регистры!
       cli
       mov ax,2521h
       pop ds
       pop dx
       int 21h

       push cs
       pop ds

       mov ah,49h
       int 21h
       sti

;Программа удалена! Выведем сообщение об успешном удалении и вернемся в DOS.
       mov dx,offset Remove_okmess

Exit_prog:
       call Out_mess
       int 20h


;Невозможно удалить программу, т.к. кто-то "повис" над нами.
Cannot_remove:
;Сообщим о случившейся беде пользователю и выйдем в DOS...
       mov dx,offset Mess_cantremove
       jmp short Exit_prog

Seg_21h dw ?
Off_21h dw ?


; === Вывод строки на экран ===
Out_mess proc
       mov ah,9 ;Выводим строку. DX уже должен содержать ее адрес!
       int 21h

       mov ah,9 ;Выводим сообщение типа "нажимте любую клавишу".
       mov dx,offset Any_key
       int 21h

       xor ah,ah ;Ждем нажатия на клавишу...
       int 16h

       ret
Out_mess endp


;  === Сообщения ===
Mess_hello db 0Ah,0Dh,'Резидент к книге "Ассемблер? Это просто! Учимся программировать", Глава № 27.',0Ah,0Dh,0Ah
           db 'Автор: Калашников Олег Александрович (Assembler@Kalashnikoff.ru),',0Ah,0Dh
           db 'http://www.Kalashnikoff.ru, Россия, Москва, 2001 год.',0Ah,0Dh,0Ah
           db '!!! Для проверки работы программы читайте Главу 27 !!!',0Ah,0Dh,'$'

Mess_memory db 0Ah,0Dh,'!!! Программа уже загружена в память !!!',0Ah,0Dh
            db 'Для ее удаления из памяти укажите /u в командной строке!',0Ah,0Dh,'$'

Mess_badcmd db 0Ah,0Dh,'Неверно указан параметр в командной строке!!!',0Ah,0Dh
            db 'Укажите /u, если хотите удалить программу из памяти!',0Ah,0Dh,'$'

Mess_badmem db 0Ah,0Dh,'Хм... Программы ведь нет в памяти!!! Как я могу ее удалить???',0Ah,0Dh,'$'

Remove_okmess db 0Ah,0Dh,'Программа успешно удалена из памяти!!! Ура!!!',0Ah,0Dh,'$'

Mess_cantremove db 0Ah,0Dh,'Не могу удалить резидент из памяти!!!',0Ah,0Dh,0Ah
                db 'Дело в том, что какая-то программа перехватила 21h-ое прерывание после того,',0Ah,0Dh
                db 'как загружен был RESID27.COM. Прежде необходимо удалить ее из памяти,',0Ah,0Dh
                db 'а потом уже удалять RESID27.COM!',0Ah,0Dh,'$'

Any_key db 0Ah,'Для продолжения нажмите любую клавишу...$'

cseg ends
end Begin
