Калашников.ru - Ассемблер? Это просто!.. (Выпуск № 015)

InterReklama Advertising

Доброе время суток, друзья мои!

Выпуск N 015

А ты опять сегодня не пришла.

А я так ждал, наде-еялся и верил,

Что зазвоня-я-ят опять колокола-а-а-а,

И я тебя увижу на дисплее.

Чего у нас сегодня?

Еще несколько слов о клубе экспертов

Резидент. Механизм работы прерываний

Несколько слов о клубе экспертов

Вот-вот-вот, дорогие мои! Начинает, вроде работать наш клуб экспертов. Только возник парадокс: когда экспертов не было, письма с вопросами приходили тоннами, а как появились эксперты, вопросы приходят граммами... В данный момент экспертов больше, чем людей, задающих вопросы!

___________

Новая система.

Несколько слов о новой системе. Теперь все письма можно направлять напрямую экспертам (без моего участия). Адреса экспертов указаны ниже. Я использую систему eGroup, которую мне посоветовал один подписчик. Зачем это нужно? Все ваши вопросы будут пересылаться экспертам немедленно. До этого письма получал я, а затем перенаправлял их экспертам. Т.о. чтобы эксперт смог получить ваш вопрос, я должен был включить компьютер и получить почту. А если я заболею? Тогда вопросы лежали бы в ящике до тех пор, пока я не пришел бы на работу. Теперь все быстрее и проще.

___________

Нужны еще эксперты!

В данный момент ощущается острая нехватка экспертов по следующим вопросам:

Эксперты по AFD;

Эксперты по CV;

Эксперты по TD.

Т.е. по отладчикам.

"Укомплектована" в данный момент одна группа:

Эксперты по DOS.

Заявки в данную группу пока не принимаются!

___________

Как стать экспертом?

Для этого необходимо направить мне письмо в произвольной форме с просьбой включить вас в ту или иную группу экспертов. В данный момент существуют следующие группы (которые еще "не укомплектованы", помимо вышеперечисленных):

Эксперты по ассемблерам (MASM/TASM) (параметры командной строки, работа под разными ОС и пр.);

Эксперты по общим вопросам программирования на Ассемблере под Win32;

Эксперты по общим вопросам программирования на ассемблере под DOS (то, что рассматривается в рассылке, а также все остальное, связанное с Ассемблером под DOS);

Эксперты по моделям, видам и типам компьютеров и все, что с ними связано (hard);

Эксперты по дизассемблерам (принцип работы, для чего нужны, как пользоваться в общих чертах и т.п.);

Эксперты по оболочкам DOS (Norton Commander, Volcov Commander, DOS Navigator, Far);

Эксперты по работе с Windows (пользовательский уровень).

___________

Как задать вопрос экспертам?

Итак, у вас возник вопрос в той или иной области. Как его задать эксперту? Очень просто: ниже приведен список адресов экспертов, куда необходимо послать ваш вопрос:

Эксперты по ассемблерам (MASM/TASM) - ExpertAssm@egroups.com;

Эксперты по дизассемблерам - ExpertDisassm@egroups.com;

Эксперты по моделям, видам и типам компьютеров и все, что с ними связано (hard) - ExpertComputers@egroups.com;

Эксперты по общим вопросам программирования на Ассемблере под Win32 - ExpertAssmWin32@egroups.com;

Эксперы по общим вопросам программирования на ассемблере под DOS - ExpertGeneralAssm@egroups.com;

Эксперты по работе с Windows (пользовательский уровень) - ExpertWin@egroups.com;

Эксперты по работе с DOS (пользовательский уровень) - ExpertDOS@egroups.com;

Эксперты по оболочкам DOS - ExpertDOSShells@egroups.com;

Эксперты по AFD - ExpertAFD@egroups.com;

Эксперты по CV - ExpertCV@egroups.com;

Эксперты по TD - ExpertTD@egroups.com.

___________

Как отказаться от участия в группе экспертов?

Необходимо написать мне письмо в произвольной форме, в котором указать ваш e-mail и желание выйти из состава экспертов. Все!

___________

Экспертам:

Пожалуйста, не забывайте высылать мне копию письма с ответом.

Подписчикам:

Пожалуйста, не забывайте указывать в теле письма ваш обратный адрес и ставить оценку за полученный ответ от эксперта.

Спасибо всем, кто решил принять участие в этом нелегком деле - отвечать на вопросы подписчиков!

Резидент

Сегодня рассмотрим "многофункциональный" резидент. Будет сложно, но интересно.

Что же делает наш резидент теперь?

1. Записывает в файл содержимое текстового экрана (то, что в момент нажатия определенных клавиш находится в области экрана);

2. Заменяет на экране все символы "A" и "a" на "О" и "о" соответственно;

3. Передает неверные данные о файлах оболочкам NC, VC, DN.

И все это один резидент, который занимает всего 746 байт (вместе с текстом)!

Итак, засучите рукава, вдохните глубже и - вперед!

________

Файл-приложение можно взять здесь: http://www.Kalashnikoff.ru/Assembler/Programs/Lessons/Resid15.rar

Если у вас нет выхода в Сеть, то напишите мне письмо с просьбой выслать данный файл. Я также включу ваш адрес в базу данных. Затем, перед выходом очередной рассылки, вы получите этот файл по почте. На сайте есть также rar.exe (150 Кб), который необходим для распаковки файла-приложения.

Разобраться в инициализации резидента (метка Init) труда не составит. В двух словах мы делаем следующее: проверяем на повторную загрузку в память путем отправления нашего "позывного" (в данном случае - 9889h в AX) и получения (или неполучения) "отклика" (число 8998h тоже в AX). Если после вызова 21h прерывания с числом 9889h мы получаем число 8998h, то наш резидент уже в памяти (он-то и меняет местами AH и AL). См. первые четыре строки процедуры Int_21h_proc, которые как раз это-то и делают (то бишь меняют местами AH/AL)... Но делают только в том случае, если наш резидент уже загружен в память. Проведем эксперимент. Перенесем приведенные ниже строки перед вызовом int 27h, но после установки прерывания 21h (это делать не обязательно!):

mov ax,9889h

int 21h

cmp ax,8998h

jne Set_resident

Что мы увидим? Наша программа выдаст сообщение о том, что она уже загружена в память, хотя и загружается первый раз. Следовательно, наш обработчик (Int_21h_proc) уже в памяти и работает! Т.е. обработчик начинает работать сразу после того, как мы установили на него вектор прерывания (а мы это сделали, используя функцию 25h прерывания 21h):

; Теперь 21h-ое...

mov ax,3521h

int 21h ;получим и сохраним адрес (вектор) 21h прерывания

mov word ptr Int_21h_vect,bx ;вначале младшее слово (смещение)...

mov word ptr Int_21h_vect+2,es ;затем старшее (сегмент)

mov ax,2521h

mov dx,offset Int_21h_proc

int 21h ;"повесим" нашу процедуру на 21h прерывание

После выполнения последней строки (int 21h) наш обработчик (Int_21h_proc) начинает работать. Чтобы он продолжил свою работу и после выхода в DOS, нам нужно оставить программу резидентной в памяти. Если мы после установки прерываний выйдем в DOS, используя int 20h, то компьютер просто "зависнет"! Память-то, где находятся наши обработчики (Int_05h_proc, Int_21h_proc, Int_1Ch_proc) освободится, и на ее место загрузятся совсем другие программы. Тем самым, адрес 21h прерывания будет указывать на процедуру Int_21h_proc, которой больше нет в памяти, и что находится вместо нее - одному богу известно. Компьютер "зависнет" (или перестанет работать должным образом) на 100%, если мы (или кто-то другой) попробуем вызвать одно из перечисленных выше прерываний (кроме 1Ch).

А зачем мы запоминаем старый вектор 21h-ого? Думаю, что многие уже догадались, но я объясню еще раз.

DOS, при смене вектора прерывания, нигде не запоминает его прежний адрес. Т.о., если мы не запомним прежний вектор, то будет работать только наша процедура. Куда мы передадим управление после того, как наша процедура отработала? Вот-вот. Мы и передаем управление по прежнему (сохраненному) адресу 21h-ого (или любого другого) прерывания. Сохраняем же мы его в определенной переменной, которую обзываем по-своему:

Int_21h_vect dd ?

или

Int_1Ch_vect dd ?

и т.п.

DD означает то, что данная переменная может хранить два слова (т.е. в данном случае адрес (сегмент и смещение) оригинального (прежнего) обработчика).

Существует два способа передачи управления на старый адрес (вектор) прерывания:

1. jmp dword ptr cs:[Int_21h_vect]

DWORD PTR означает, что надо "прыгнуть" используя не только смещение, но и сегмент (т.е. сегмент:смещение).

CS: указывает на то, что переменная находится в текущем сегменте (а CS всегда указывает на текущий сегмент (т.е. на тот сегмент, где мы сейчас находимся), а вот все остальные сегменты (DS, ES, SS) не меняются при переходе в другой сегмент. Если мы опустим CS:, то процессор по умолчанию будет работать так:

jmp dword ptr DS:[Int_21h_vect], а это неверно, т.к. DS будет содержать совсем другой сегмент (а не тот, в котором мы сейчас находимся (см. файл-приложение)).

Переход в другой сегмент происходит когда мы, например, вызываем какое-нибудь прерывание, что мы и делаем в нашем примере (Resid15.asm)). При вызове прерывания из сегментных регистров меняется только CS (Code Segment - сегмент кода, т.е. кода, который в данный момент выполняется), а остальные остаются прежними (т.е. имеют те значения, которые были перед вызовом прерывания) (подробнее ниже).

Скобки ([ ]) указывают на то, что нужно "прыгнуть" на тот адрес, который находится в переменной Int_21h_vect.

Данная команда (jmp dword ptr) называется дальний jmp, в отличие от короткого (например, jmp Init, т.е. без всяких "прибамбасов" после jmp, только метка). Со временем все станет на свои места.

2. call dword ptr cs:[Int_21h_vect]

Что здесь? Да здесь рассуждаем по аналогии с jmp. Но есть некоторые отличия.

Итак, вы привыкли к тому, что call используется для вызова процедур внутри нашего сегмента (такие процедуры называют ближними). Это же - дальняя процедура. В чем отличие? При вызове ближней процедуры в стеке сохраняется только смещение, куда необходимо вернуться, при вызове же дальней процедуры в стеке сохраняется не только смещение, но и сегмент.

Вот примеры:

;Предположим, что стек пустой (SS=1234h, SP=0FFFFh).

(1) [1234:0100h] mov ax,0A0Bh

(2) [1234:0103] call Our_proc

(3) [1234:0105h] mov dx,123h

...

(4) [1234:0200h] Our_proc proc

(5) [1234:0200h] mov dx,offset Message

...

(6) [1234:0250h] ret

(7) [1234:0250h] Our_proc endp

Итак, что же в данном примере происходит? Давайте предположим, что строка (1) расположилась по адресу 1234:0100h, а процедура Our_proc находится по адресу 1234:0200h. Когда программа дойдет до строки (2), CS (сегмент) будет содержать адрес текущего сегмента (т.е. 1234h), а IP (смещение) - 0105h (т.е. адрес следующей команды). В момент выполнения строки (2) в стек заносится текущее состояние регистра IP (и только!), т.е. число 0105h. Затем процессор переходит на адрес 0200h (т.е. на метку нашей процедуры). Как видите Our_proc proc не занимает памяти; эта команда нужна только ассемблерам (MASM/TASM). Начинает работать процедура Our_proc, при этом адрес возврата находится в стеке. Я уже говорил, что в процедуре нужно очень тщательно следить за стеком, т.к. если мы оставим какое-нибудь число в стеке перед выходом из процедуры, то процессор, дойдя до инструкции ret, вытащит со стека не адрес возврата, а то число, которое находится на вершине стека. Но это мы забегаем немножко вперед...

Итак, процедура отработала. Процессор дошел до команды ret. RET достает из стека последнее число (число, находящееся на вершине стека в данный момент) и переходит по этому адресу. Если мы со стеком ничего не намудрили (ничего в нем не оставили и ничего не оставили записанным), то RET вытащит со стека число 0105h, т.е. то число, которое сохранила инструкция call (адрес возврата), и перейдет по этому адресу. Еще раз: если мы стек оставили выровненным (надеюсь, что вы помните, что это значит), то процедура выйдет корректно.

Что нужно уяснить: при вызове ближней процедуры (т.е. call Our_proc, например), в стек кладется только смещение следующей за процедурой инструкции (в данном случае - только 0105h).

При вызове же дальней процедуры в стек кладется не только смещение, но и сегмент для возврата.

Например, мы точно знаем, что по адресу 3456:0400h находится процедура вывода строки на экран. Проблема в том, что эта процедура находится в другом сегменте (не в 1234h, как наша программа, а в 3456h). Для этого занесем в некую переменную (пусть это будет New_proc) два слова (четыре байта): 3456h и 0400h (адрес процедуры New_proc, включая сегмент и смещение). Затем передаем управление этой процедуре:

mov word ptr [New_proc],0400h

mov word ptr [New_proc+2],3456h

call dword ptr [New_proc]

...

New_proc dd ?

Здесь мы вначале заносим в переменную New_proc смещение, а затем сегмент процедуры (вспоминаем о том, что в компьютере данные хранятся "задом наперед").

К чему это? К тому, что если после команды (инструкции) call следует dword ptr, то в стек заносится не только смещение, но и сегмент. Возникает второй вопрос: как компьютер различает, когда нужно достать из стека только смещение, а когда смещение и сегмент? Ответ простой: существует две разновидности инструкции ret:

ret и retf.

RETF (RETurn Far - дальний возврат) - достает из стека не только смещение, но и сегмент (в отличие от RET, который достает только смещение). Если мы вызовем дальнюю процедуру (call dword ptr [Far_proc]), а выйдем из нее, используя RET, то компьютер просто "зависнет". Надеюсь, вы поняли почему...

Вот примеры:

[1234:0200h] call Near_proc

...

[1234:4569h] Near_proc proc

...

[1234:6789h] ret ;Правильный выход из ближней процедуры (процедуры, которая располагается в том же сегменте, что и программа, ее вызывающая)

[1234:6789h] Near_proc endp

...

____________

...

[1234:0200h] call dword ptr [New_proc]

...

[3456:0300h] New_proc proc

...

[3456:0534h] retf ;Правильный выход из дальней процедуры (процедуры, которая находится в другом сегменте, в отличие от программы ее вызывающей)

[3456:0534h] New_proc endp

Иначе говоря, процессор, дойдя до оператора call имя процедуры, сохранит в стеке только смещение следующей за call команды (возврат - RET), а call dword ptr имя процедуры сохранит в стеке сегмент и смещение следующей за call dword ptr (возврат - RETF) команды.

Возможно, это покажется вам на первый взгляд сложно, но, поверьте, все на самом деле очень просто. Более того, путаницы не возникает с вызовами и возвратами. Нужен прежде всего опыт работы с Ассемблером. Остальное придет само собой.

"Ага! - скажает читатель. - Что-то ты, Калаш, "лапшу гонишь". А почему тогда перед call dword ptr [Int_21h_vect] мы заносим в стек регистр флагов (pushf), и впоследствии его не достаем? Куда он девается? А?!"

Верно замечено! Этому есть объяснение. Смотрим:

...

pushf

call dword ptr [Int_21h_vect]

...

Зачем нужно заносить флаги в стек (pushf) перед вызовом оригинального обработчика прерываний? Хороший вопрос... Длинный ответ... Но попробуем затронуть эту тему, раз уж вы настаиваете... Если что-то непонятно будет - не плачьте! Впоследствии все рассмотрим подробнее.

Зачем вообще вызываются прерывания? Вот кусок кода:

______________

;Это обработчик прерывания 09h где-то в памяти, который "срабатывает" при нажатии и отпускании какой-нибудь клавиши:

[0900:0050h] mov al,bl ;Здесь неважно, какой код находится. Главное то, что сегмент другой, отличный от того, в котором находится наша программа (см. ниже).

[0900:0052h] ...

[0900:0345h] iret

=============

;Это кусок нашей программы, которая в данный момент выполняется:

...

(1) [1234:0200h] mov ax,Num_regAX

(2) [1234:0205h] cmp ax,17

(3) [1234:0208h] jne Not_equal

...

_____________

Программа работает, работает. И вдруг пользователь с перепугу нажимает какую-нибудь клавишу, причем нажимает ее в тот момент, когда наша программа только что выполнила строку (2). Что происходит? На первый взгляд кажется, что ничего особенного не произойдет. Процессор занесет клавишу в буфер, которая будет храниться там до того момента, пока DOS ее от туда не запросит и, по желанию, не выведет на экран или выполнит определенные действия (например, клавиша F9 в NC). Но это только кажется и на словах звучит просто. На самом деле процессор выполнит уйму работы за считанные доли миллисекунд, а именно:

У нас в регистрах находятся определенные числа. Более того, после выполнения строки (2) изменится регистр флагов. В строке (3) мы ведь проверяем состояние флага нуля, который сигнализирует нам в данном случае о том, равен ли AX 17.

Мы (наша программа) находимся в момент вызова прерывания в сегменте 1234h, смещение 0208h (адрес следующей команды). Как сделать так, чтобы передать управление 09h прерыванию, которое выполнит свою работу (занесет в буфер клавиатуры код клавиши, которую мы затем проверяем при помощи функции 10h прерывания 21h), при этом вернуться в то место, с которого произошло прерывание, не нарушив работы нашей программы? Длинный вопрос...

К слову. 09h прерывание вызывается всегда, когда пользователь нажимает какую-нибудь клавишу, даже тогда, когда процессор чем-то занят (не ждет от пользователя клавишу; например, копирует файл или форматирует диск).

Вернемся к нашему длинному вопросу. Итак, пользователь нажал на клавишу после выполнения строки (2) нашей программы. Процессору нужно запомнить текущий сегмент (CS), смещение следующей команды (оно всегда в IP), а также флаги в стеке. Этого достаточно для того, чтобы вернуться назад после того, как прерывание 09h отработало. Заметьте, что при вызове прерывания регистры (кроме CS:IP и флаги) НЕ сохраняются. Их должно сохранять то прерывание, которое получило управление (в случае нажатия клавиши - 09h). После того, как процессор сохранил регистры CS:IP и флаги в стеке, он передает управление обработчику прерывания (в нашем случае - 09h), адрес которого находится в определенном месте в памяти (где именно находятся адреса прерываний, мы рассмотрим позже). Что значит "передает управление"? Да просто "прыгает" на определенный адрес. Это будет называться что-то вроде "дальний безусловный переход", т.к. мы прыгаем не только на смещение внутри сегмента (как в случаях, которые мы рассматривали уже; например, jmp Init в нашем резиденте в начале), а на сегмент и смещение. Улавливаете разницу?

Итак, управление получило прерывание 09h. Помним, что процессор хранит в стеке сегмент (CS):смещение (IP) команды на которую нужно вернуться (в нашем примере - строка (3)), а также регистр флагов. Прерывание 09h работает. Если в процессе работы меняются какие-то регистры, то оно должно их предварительно сохранить, иначе наша программа (после отработки 09h прерывания) получит совсем другие значения в регистрах (например, если 09h изменяет AX, не сохраняя его, а затем, соответственно не восстанавливая, то при выходе из данного прерывания мы получаем то, что AX не равен Num_RegAX (1)). Отсюда жесткое правило: если вы пишите свой обработчик, то обязательно сохраняйте все регистры, которые он меняет. Иначе может произойти непредвиденное... Если вы написали обработчик 21h-ого прерывания, и после загрузки его в память компьютер "зависает" или ведет себя не так, как хотелось бы, то ищите ошибку в вашем резиденте. Возможно, вы забыли сохранить тот или иной регистр в стеке. Хотя причин по которым "виснет" компьютер может быть очень много...

Что-то мы отвлекаемся постоянно... Информации просто много...

Итак, прерывание 09h (правильнее: обработчик 09h прерывания) отработало: процессор дошел до инструкции iret (Interrupt RETurn). Эта инструкция отличается от ret тем, что при ее выполнении процессор достанет со стека сегмент (CS), смещение (IP) и регистр флагов, вместо смещения (IP) (как ret). Вот и вся разница между ret и iret... Пожалуйста, не путайте их!

Резюмируем:

RET достает из стека только смещение для возврата; процедура должна находится в том же сегменте, из которого ее вызывают (ближняя процедура - NEAR (по умолчанию));

RETF достает из стека сегмент и смещение; процедура может находится в любом сегменте, независимо от того, откуда ее вызывают (дальняя процедура - FAR или DWORD PTR);

IRET достает из стека сегмент, смещение и адрес флагов. Используется для выхода из прерываний.

Вернемся. Команда iret вытаскивает со стека адрес возврата (CS:IP). В нашем случае: CS = 1234h, IP = 0208h, а в регистре флагов установлен флаг нуля, т.е. равен 1. Затем просто передает управление на этот адрес. Наша программа продолжает работать, не догадываясь даже о том, что кто-то ее прервал. Естественно, все эти процедуры происходят мгновенно.

Теперь ответ на вопрос: почему перед вызовом прерывания командой вида call dword ptr cs:Int_21h_vect] мы заносим в стек регистр флагов командой pushf?

Все просто. Стоит только посмотреть на отличие оператора RETF от IRET (см. выше). При передаче управления (вызове) прерывания командой call dowrd ptr... процессор заносит в стек только сегмент (CS) и смещение (IP) следующей за командой call инструкции. А IRET (который выполняется при возврате из прерываний) достанет со стека сегмент:смещение и флаги. Но флаги-то не заносятся командой call dword ptr...! Мы их заносим сами, "вручную". Иначе произойдет нарушение работы стека, и компьютер "зависнет". Как я уже говорил, за стеком нужно следить очень внимательно!

Что мы там начали рассматривать? Забыл уже... Ща гляну...

Что делает наш обработчик 21h-ого прерывания? Он передает программе, которая ищет файлы в каталоге, используя функции 4Eh и 4Fh. Подробнее эти функции мы рассмотрим, когда будем писать оболочку. Здесь все вкратце.

Функция 4Eh ищет первый файл или каталог на диске, и, если какой-нибудь файл найден, заносит в DTA информацию о данном файле. Программа, вызывающая эту функцию использует эту информацию из DTA для анализа файлов. В DTA заносится в частности:

имя и расширение файла;

размер файла;

дата и время создания файла;

атрибуты файла.

Тоже самое делает функция 4Fh. Ее отличие только в том, что она ищет второй и последующие файлы. Ну вот так устроена MS-DOS!

Наш резидент контролирует 21h прерывание. Если вызывается одна из упомянутых выше функций, то резидент подменяет информацию, которая находится в DTA после вызова 21h прерывания.

Вот пример:

______________

;Проверяем: вызывает ли какая-то программа функцию 4Eh или 4Fh (поиск файлов)

cmp ah,4Eh

je Do_not

cmp ah,4Fh

je Do_not

;Если вызывается другая функция, то просто передадим управление оригинальному обработчку 21h

;Передаем дальним jmp'ом. Здесь заносить в стек флаги не нужно, т.к. мы больше не вернемся в наш обработчик. Уходим навсегда...

Go_21h:

jmp dword ptr cs:[Int_21h_vect]

;Итак, кто-то вызывает функцию 4Fh или 4Eh...

Do_not:

pushf

call dword ptr cs:[Int_21h_vect]

______________

Прежде, чем менять информацию о найденных файлах в DTA, нам нужно вызвать 21h прерывание самим для того, чтобы оно занесло эти данные в DTA. Мы, конечно, можем сами записать туда чего угодно, но это будет неинтересно. Обратите внимание на две команды, следующие за меткой Do_not. Здесь мы вызываем 21h прерывание. Но как! Ведь данный кусок кода - это и есть наш обработчик 21h прерывания. Следовательно, если мы вызовем прерывание стандартной командой int 21h, то она нас приведет на наш же обработчик. Нам это надо? Нет. Поэтому мы вызываем прежний обработчик 21h-ого прерывания, т.е. тот обработчик, который работал до того момента, как мы "повисли" в памяти (перехватили его и загрузились в память). Получается своего рода "фильтр" 21h-ого. Мы можем "фильтровать" те или иные функции прерывания и делать с ними все, что захотим. Вот он, контроль над программами! Не то, что Windows!

Итак, вызвали прежний обработчик прерывания. Он нам вернул информацию о найденном файле в DTA. Что дальше? А что дальше?! "Где же этот DTA в памяти мы-то не знаем!" - воскликните вы. Верно, не знаем. Он может быть где угодно. Получить адрес DTA позволяет функция 2Fh, прерывания 21h. Но обратите внимание, как мы теперь вызываем 21h... Почему так? Вызвав 21h командой int 21h процессор попадет на начало нашего обработчика. Т.е. в самое начало процедуры Int_21h_proc. Теперь думаем: в AH у нас число 2Fh. Теперь смотрите, что произойдет, если наша процедура (Int_21h_proc) получит управление с находящимся в AH числом 2Fh? Проследите... А-а-а! Понятно? А если она же получит управление с числом 4Fh в AH? То-то... Поэтому мы в случае вызова прерывания 21h с числом 4Eh или 4Fh вызываем напрямую прежний обработчик (call dword ptr cs:[Int_21h_vect]), а с любым другим числом - наш обработчик (int 21h). Второй вариант занимает меньше байт, да и работает быстрее...

Адрес DTA получили. Теперь получим случайное число. Это можно сделать, вызвав функцию 2Ch прерывания 21h, которая вернет в определенных регистрах текущее время. В файле-приложении все написано подробно.

Мы пока не будем углубляться в поля DTA (где что находится). Давайте лучше рассмотрим логические команды.

Логических команд всего несколько:

and

or

xor

Проще всего объяснить принципы работы на примерах. Главное - поймите принцип. Мы еще на раз будем возвращаться и использовать логические операторы. Это одна из быстрых и простых вещей Ассемблера в отличие от языков высокого уровня.

Пример N 1. Оператор OR (ИЛИ).

Оператор OR служит для включения определенных битов (не байтов!) в регистре, переменной или в памяти.

mov ax,1010b

or ax,1111b

Теперь AX=1111b. Т.о. мы включили (установили) первые четыре бита.

1 OR 1 = 1

1 OR 0 = 1

0 OR 0 = 0

mov ah,1001b

or ah,1000b

Теперь AX=1001b. Т.о. мы установили нулевой бит, а третий был уже установлен до нас, но он не тронут!

Пример N 2. Оператор AND (И).

Оператор AND служит для выключения битов.

mov ax,1010b

and ax,0101b

Теперь AX=0000b. Т.о. мы выключили первый и третий биты (отсчет справа налево, начиная с нуля).

1 AND 1 = 1

1 AND 0 = 0

0 AND 0 = 0

mov ah,1001b

and ah,1

Теперь AH=0001b. Т.о. мы выключили третий бит.

Пример N 3. Оператор XOR (исключающее ИЛИ).

Опертор XOR используется в основном для кодирования данных. Вот, что он делает:

mov ah,1010b

xor ah,1100b

Теперь AH=0110b.

1 XOR 1 = 0

1 XOR 0 = 1

0 XOR 0 = 0

Очень удобен также для вкл/выкл одного бита:

mov al,0 ;AL=0

xor al,1 ;AL=1

xor al,1 ;AL=0

xor al,1 ;AL=1 и т.д.

Теперь вы поняли, почему команда xor ax,ax обнуляет регистр AX?

Больше в процедуре обработки 21h прерывания мы пока рассматривать не будем. В принципе, в файле-приложении есть описания к командам, которых вполне достаточно для понимания программы. Но даже если вы не поняли - не отчаивайтесь! Придет время, мы все рассмотрим.

Прерывание 05h.

Что делает прерывание 05h? Оно активизируется (т.е. процессор передает управление процедуре обработки 05h прерывания) при нажатии на клавиши Shift+Print Screen. В этот момент содержимое текстового экрана выводится на принтер. Наверное, вам уже приходилось это делать. Теперь вы знаете, какая процедура печатает экран на принтер.

Ничего не мешает программисту установить свой обработчик на 05h прерывание. Мы это и делаем в нашем резиденте (процедура Int_05h_proc).

После загрузки резидента в память, пользователь нажимает клавиши Shift+Print Screen и... экран выводится не на принтер, а сохраняется в текстовый файл в текущем каталоге. Имя файла Screen.txt. Это работает наша процедура! Ура!

Абсолютно ничего сложного в ней нет. Почитайте примечания в файле-приложении. Все будет понятно. В двух словах:

Как вы уже знаете, один символ на экране занимает 2 байта: символ и его атрибут. Атрибут-то нам не надо сохранять! Иначе ерунда получится. Поэтому нужно атрибуты "отсеять", оставив только символы. Для временного хранения символов мы используем память первой видеостраницы.

Затем, используя функцию 40h прерывания 21h, мы сохраняем отделенные от атрибутов символы в текстовый файл Screen.txt. Все! Можно смотреть его в любой оболочке...

Прерывание 09h.

Как я уже говорил, это прерывание вызывается в случае, если пользователь нажал какую-нибудь клавишу.

В компьютере есть такое понятие, как порты ввода-вывода. Углубляться сейчас в это мы не будем. Нажав клавишу, в порт 60h заносится скан-код этой клавиши. Чтобы получить из порта число, нужно пользоваться оператором in.

Следует отличать ASCII код от скан-кода. Одна из функций прерывания 09h - преобразование скан-кода в ASCII код. Для чего это нужно - рассмотрим позже. Сейчас главное уловить принцип работы разных прерываний.

______________

(1) Int_09h_proc proc

(2) pusha

(3) in al,60h

(4) cmp al,58h

(5) jne No_F12

(6) xor cs:Num_status,1

(7) No_F12:

(8) popa

(9) jmp dword ptr cs:[Int_09h_vect]

(10) Int_09h_vect dd ?

(11) Int_09h_proc endp

______________

Вот и вся процедурка. Что она делает?

Скан-код клавиши F12 - 58h. Мы вначале получаем из порта 60h скан-код нажатой клавиши. Если это F12, то инвертируем нулевой бит некой переменной Num_status и передаем управление оригинальному обработчику 09h. Если мы этого не сделаем, то пользователь не сможет работать с клавиатурой, т.к. прерывание 09h заносит в буфер клавиатуры ASCII код нажатой клавиши. Но мы-то этого не делаем! Можно, конечно, самому написать подобную процедуру. Если у вас есть время - пожалуйста... Что же касается "таинственной" переменной Num_status, мы рассмотрим в следующем разделе.

Прерывание 1Ch.

Это прерывание примечательно тем, что оно само по себе вызывается примерно 18 раз в секунду. Если мы напишем какую-нибудь процедуру и установим на нее вектор прерывания 1Ch, то она будет вызываться 18 раз в секунду.

В нашем случае процедура Int_1Ch_proc "просматривает" содержимое нулевой страницы и заменяет символы "A" и "a" на "O" и "o". Причем, все это происходит с частотой 18 раз в секунду.

Начало данной процедуры такое:

cmp cs:Num_status,0

jnz Go_1Ch

Вот она, эта таинственная переменная Num_status. Как видите, процедура Int_1Ch_proc проверяет, равна ли она нулю или нет. И если не равна, то переходит на метку Go_1Ch. А там:

Go_1Ch:

jmp dword ptr cs:[Int_1Ch_vect]

Просто передача на оригинальный обработчик 1Ch. Т.о. если переменная Num_status равна единице, то наш обработчик работать не будет. Т.е. символы на экране заменяться не будут.

Теперь вспомним про прерывание 09h, которое меняло переменную Num_status при нажатии на клавишу F12. Причем меняло командой XOR. А что делает XOR? См. выше. Понятно, что происходит?

Повторю еще раз: ваша задача сейчас не полностью разобрать программу, а понять принцип работы прерываний, причем на довольно-таки сложном примере. Зачем я это сделал? Во-первых, вы хотите быстрее перейти к Win? Во-вторых, попробуйте разобрать ее сами. Вдруг получится? Зато сколько удовольствия!!!

Удачного Вам программирования и не забывайте про наших экспертов!

С уважением,

Автор рассылки:

Калашников Олег

www.Kalashnikoff.ru

E-mail:

assembler@beep.ru

UIN (Тетя Ася):

68951340

(С) Авторское право. Запрещается использование материала из рассылки в коммерческих целях без письменного согласия автора. [Следующий выпуск] [На главную страницу]

u="u496.71.spylog.com";d=document;nv=navigator;na=nv.appName;p=1; bv=Math.round(parseFloat(nv.appVersion)*100); n=(na.substring(0,2)=="Mi")?0:1;rn=Math.random();z="p="+p+"&rn="+rn;y=""; y+=""; y+=""; y+=""; d.write(y);if(!n) { d.write("

Соседние файлы в папке Выпуски