Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Уроки ассемблеру. Быстро и просто. Урок 13


Сегодня мы завершаем нашу подготовительную часть самым сложным, на мой взгляд, для новичка вопросом. Мы научимся оставлять нашу программу в памяти и перехватывать прерывания из других программ.

Допустим, у нас есть программа из второго урока, выводящая на экран строку "Hello, world!". Нам нужно написать свою программу, которая бы осталась резидентной в памяти, но при этом перехватывала прерывания 21h таким образом, что при запуске первой программы (откомпилируем её как hello.com) выводилась бы строка не "Hello, world!", а "Goodbye, world!".

Рассмотрим текст такой программы.

;Всё, что следует за значком ";" - это комментарий.

.286 ;Разрешает ассемблирование непривилегированных инструкций
;процессора 80286 (реальный режим) и инструкций арифметического
;сопроцессора 80287.

CSEG segment ;Даём имя сегменту, а точнее определяем абсолютный
;сегмент в памяти программ по определённому адресу.
;Имя нашего сегмента будет CSEG.

assume cs:CSEG, ds:CSEG, es:CSEG, ss:CSEG ;Задаём сегментные регистры, которые будем использовать для
;вычисления действующего адреса для всех меток и переменных, опре-
;делённых для сегмента или группы сегментов с указанным именем.

org 100h ;Программа типа .com

begin: ;Метка начала программы.


jmp Nerezident ;Прыгаем сразу за кодом резидента - продолжаем программу.

Rezident proc ;Начало нашей подпрограммы, которая останется в памяти.

pushf ;Сохраняем все флаги в стеке. Это нужно для того,
;чтобы не нарушить работу программы. В частности,
;следующие 2 строчки значение одного флага изменят (je).

cmp ah,9 ;Проверяем: это функция 09h?
je metka ;Если да, то переходим на метку

popf ;Если нет, восстановим регистр флагов и
jmp dword ptr cs:[Peremennaja_21h] ;перейдём на оригинальный обработчик прерывания 21h.
;А именно он находится в сегменте cs по адресу,
;содержащемуся в регистре (см.квадратные скобки).

metka:

push ds ;Сохраним регистр ds.
push dx ;Сохраним регистр dx.

push cs ;Сохраним регистр cs.
pop ds ;"Вытолкнем" последнее значение cs в ds.

mov dx,offset Goodbye_world ;Занесём в dx адрес смещения нашей строки.

pushf ;Сохраним значение всех флагов в стеке.
call dword ptr cs:[Peremennaja_21h] ;Вывели нашу строку вместо той, которую надо было.

pop dx ;Восстановим регистр dx из стека.
pop ds ;Восстановим регистр ds из стека.

popf ;Восстановим флаги.

iret ;Выйдем из прерывания и продолжим работу.

Peremennaja_21h dd ? ;Определяем переменную для хранения оригинального адреса
;обработчика 21h.

Goodbye_world db 'Goodbye, world!$' ;Определяем переменную Goodbye_world, доступную побайтно, с фразой
;"Goodbye, world". В одинарных кавычках, после знака "!" ставим
;знак "$".

Rezident endp ;Конец подпрограммы.

Nerezident: ;Здесь закончился резидент.

mov ah,35h ;AH содержит номер функции 35h - получение адреса прерывания.
mov al,21h ;AL указывает номер прерывания, которое перехватываем.

int 21h ;Выполняем функцию.
;в ES:BX получаем адрес 21h прерывания (ES - сегмент, BX - смещение).

mov word ptr Peremennaja_21h,bx ;Загружаем в Peremennaja_21h слово (2 байта) из BX,
;где находится смещение.

mov word ptr Peremennaja_21h+2,es ;А следующим словом - из ES (смещение).

mov ax,2521h ;Перехватываем прерывание.

mov dx,offset Rezident ;DX должен указывать на наш обработчик (смотри HELP.EXE).

int 21h ;Осуществляем прерывание. Мы не вызываем резидент командой
;call Rezident, а используем эти три строчки, начиная с mov ax,2521h

mov dx,offset Nerezident ;Оставляем программу резидентной: в dx - смещение по адресу
;метки Nerezident - указываем последний байт, остающийся в памяти.
int 27h ;Осуществляем прерывание и выходим в DOS (но оставляем резидента).

CSEG ends
end begin


Сохраним её как test.com (ml test.asm /AT). Запускаем сначала её, а затем hello.com (при использовании Far поместите hello.com в каталог с нашей программой. Дело в том, что при запуске test.com мы в оболочку не вернёмся и просто будем вводить в командной строке hello.com).

Мы видим, что на экран выводится фраза "Goodbye, world!", при этом текст программы hello.com остался прежним. Что же произошло?

Рассмотрим нашу подпрограмму, которая будет резидентной. Она начинается словами Rezident proc и заканчивается Rezident endp. Первоначально мы сохраняем все флаги в стеке (новая для нас функция pushf), затем проверяем, используется ли какой-либо программой функция 9h (вывод строки на экран). Если нет, переходим на "настоящий" обработчик функции 9h прерывания 21h (перед этим восстановив флаги, "испорченные" je metka. Если же да, то сохраняем значение в регистров ds и dx, и заносим в ds уже другое число - из cs. Что в нём было? Посмотрите в отладчике.

Затем добавляем в регистр dx адрес смещения, где располагается наша новая фраза. Значение ds и dx полностью изменились с прошлого раза. И сохраняем флаги. Строчка call dword ptr cs:[Peremennaja_21h] служит нам для вывода нашей новой строки.

Далее мы "вынимаем" старые значения ds и dx из стека, а затем восстанавливаем флаги. Обратите внимание на порядок. Потом идёт команда выхода из прерывания (опять новый оператор, здесь прослеживание аналогия с командой ret.

Теперь рассмотрим остальную (нерезидентную) часть программы, которую не будет больше использовать процессор.

HELP.EXE -> Указатель функций DOS/BIOS -> Функции DOS -> 35H GetVector

Читаем: DOS Fn 35H: Дать вектор прерывания. Вход: AH — 35h. AL — номер прерывания (00H до 0ffH).
Выход: ES:BX — адрес обработчика прерывания.

Описание: Возвращает значение вектора прерывания для INT (AL); то есть, загружает в BX 0000:[AL*4], а в ES — 0000:[(AL*4)+2].
Предупр: Эта функция изменяет сегментный регистр ES.

Благодаря этой функции мы получаем в BX адрес обработчика прерывания, который и используем в следующих командах. Определяя переменную Peremennaja_21h dd ? и зарезервировав под неё двойное слово, мы можем занести в неё данные из BX и ES.

Следующие три строчки (начиная с mov ax,2521h):

HELP.EXE -> Указатель функций DOS/BIOS -> Функции DOS -> 25H Set Vector

Вход: AH - 25H
AL - номер прерывания
DS:DX - вектор прерывания: адрес программы обработки прерывания.

Следующие две строчки (начиная с mov dx,offset Nerezident):

HELP.EXE -> Указатель функций DOS/BIOS -> Функции DOS по группам-> INT 27H: Завершиться, но остаться резидентным
Обращаем внимание на запись: "Вход: DX - адрес первого байта за резидентным участком программы. (DX интерпретируется как смещение от PSP (DS/ES при запуске)". Мы используем запись mov dx,offset Nerezident.

Немного сложновато. Но я уверен, всё получится! Чаще используйте отладчик: afdpro test.com

В избранное