Книга: О.А.Калашников. Ассемблер Это Просто. Учимся программировать

Внимание! Если размещение файла нарушает Ваши авторские права, то обязательно сообщите нам
background image

Глава 15. Обработка аппаратных прерываний 

149 

будет находиться в памяти и контролировать прерывание 

21h

  еще  до  выхода!  Та-

ким образом, наш резидент активизируется сразу после того, как мы установили на 
него  вектор  прерывания  (а  мы  это  сделали,  используя  функцию 

25h

  прерывания 

21h

) (листинг 15.2). 

Листинг 15.2. Сохранение и установка вектора прерывания 

... 
;Установка обработчика 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 
... 

Еще одним подтверждением того, что резидент активизируется до выполнения 

команды 

int  27h

,  является  тот  факт,  что  даже  в  отладчике  мы  увидим  результат 

его  работы.  Символы  начнут  заменяться  уже  после  установки  процедуры 

Int_21h_proc

 на прерывание 

21h

 (рис. 15.1). 

Рис. 15.1. Активизация резидента в процессе отладки 

background image

Часть III. Файловая оболочка, вирус, резидент 

150 

Для того чтобы резидентная часть программы продолжала корректно работать 

после  выхода,  нам  нужно  оставить ее резидентной в памяти. Если мы после ус- 
тановки  прерываний  выйдем  в  DOS,  используя 

int  20h

,  то  компьютер  просто  

"зависнет"!  Память,  где  находятся  наши  обработчики  (

Int_05h_proc

, 

Int_21h_proc

, 

Int_1Ch_proc

), освободится, и на ее место загрузятся совсем другие программы. 

После чего адрес прерывания 

21h

 будет указывать на место в памяти, где должна 

находиться процедура обработки этого прерывания (

Int_21h_proc

). Но процеду-

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

15.1.1. Сохранение предыдущего  
вектора прерывания 

Для чего следует сохранять старый вектор 

21h

? 

DOS,  при  смене  вектора  прерывания,  нигде  не  запоминает  его  прежний  адрес. 

Если  программист  его  самостоятельно  не  сохранит,  то  наша  процедура,  получив 
управление в первый раз, отработает и не будет "знать", что делать дальше, кроме 
как  просто  вернуться  из  прерывания  и  передать  дальнейшее  управление  той  про-
грамме, которая вызывала это прерывание. 

Например, обработчик перехватывает прерывание 

21h

 и следит за тем, вызыва-

ется  ли  функция 

3Dh

  (открытие  файла)  некоторой  программой.  Если  так,  то  рези-

дент должен сохранить в своей области имя открываемого файла, а затем передать 
управление на  адрес старого обработчика 

21h

, который и откроет файл. В случае, 

если  вызывается  другая  функция,  отличная  от 

3Dh

,  то  следует  также  немедленно 

передать  управление  предыдущему  обработчику,  который  и  выполнит  необходи-
мые действия. Адрес прежнего обработчика может указывать как на ядро DOS, так 
и  на  другой  резидент,  перехвативший  прерывание 

21h

  и  загрузившийся  ранее  на-

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

21h

,  кроме,  разумеется,  самого  ядра  DOS,  являющегося  начальным 

звеном и выполняющим необходимые действия. 

Для  сохранения  предыдущего  адреса  того  или  иного  прерывания  необходимо 

завести  отдельные  32-разрядные  переменные,  способные  хранить  два  слова  (сег-
мент и смещение): 

Int_21h_vect dd ? 

или 

Int_1Ch_vect dd ? 

и т. п. 

Здесь 

dd

  означает  "define  double  word" —  определить  переменную  с  двойным 

словом.  

background image

Глава 15. Обработка аппаратных прерываний 

151 

15.1.2. Способы передачи управления  
на прежний адрес прерывания 

Существуют  2 стандартных  способа  передачи  управления  на  прежний  адрес 

(вектор) прерывания.  

Первый способ 

jmp dword ptr cs:[Int_21h_vect] 

dword  ptr

  означает,  что  надо  "прыгнуть",  используя  не  только  смещение,  но  

и сегмент (сегмент:смещение), которые находятся в переменной 

Int_21h_vect

. 

cs:

 указывает на то, что переменная находится в текущем сегменте. А 

cs:

 все-

гда  указывает  на  текущий  сегмент,  т. е.  на  тот,  в  котором  выполняется  код  про-
граммы  в  настоящий  момент.  Если  мы  опустим 

cs:

,  то  процессор  по  умолчанию 

будет работать так: 

jmp dword ptr ds:[Int_21h_vect] 

А это неверно, т. к. 

ds

 будет содержать отличное от 

cs

 значение (см. в отладчике 

файл-приложение). 

Переход в другой сегмент происходит в тот момент, когда мы, например, вызы-

ваем некоторое прерывание (как в нашем примере resid15.asm). При вызове преры-
вания  из  сегментных  регистров  меняется  только 

cs

,  а  прочие  регистры  остаются 

прежними, сохраняют те значения, которые были перед вызовом прерывания (под-
робнее об этом скажем далее). 

Квадратные скобки (

[  ]

) указывают на то, что нужно "прыгнуть" на тот адрес, 

который находится в переменной 

Int_21h_vect

, хотя в данном случае квадратные 

скобки можно было и опустить. 

Команды вида 

jmp dword ptr ...

 называются "дальним 

jmp

 с переходом в дру-

гой сегмент". 

Второй способ 

call dword ptr cs:[Int_21h_vect] 

Здесь рассуждаем, как и в первом случае. Но есть некоторые отличия. 
Вы привыкли к тому, что 

call

 используется для вызова процедур внутри нашего 

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

 

вызов ближней (

near

) процедуры. В данном случае в стек автоматически поме-

щается  смещение  следующей  за  командой 

call

  инструкции.  После  того  как 

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

call

,  ее  вызывающая.  Пример  вызова  ближней  

процедуры: 

call Near_proc 

background image

Часть III. Файловая оболочка, вирус, резидент 

152 

 

вызов дальней (

far

) процедуры. В этом случае процессор автоматически поме-

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

call dword ptr cs:[Far_proc] 

В  данном  случае  сегмент  и  смещение  вызываемой  процедуры  находятся  

в  переменной 

Far_proc

,  которая  расположена  в  текущем  сегменте,  т. е.  в  том,  

в котором выполняется в настоящий момент указанная выше команда 

call

.  

15.2. Инструкции ret и retf 

15.2.1. Оператор ret 

Пример работы оператора 

ret

 приведен в листинге 15.3. 

Листинг 15.3. Вызов ближней процедуры 

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

                    ... 

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

(2) [1234:0103h]    call Our_proc    ;Вызываем процедуру Our_proc 

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

... 

(4) [1234:0200h]    Our_proc proc 

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

... 

;Выходим из процедуры (переходим на адрес 1234:0105h) 

(6) [1234:0250h]   ret 

(7) [1234:0250h]    Our_proc endp 

... 

Давайте  предположим,  что  строка  (1)  расположилась  по  адресу 

1234:0100h

,  

а процедура 

Our_proc

 находится по адресу 

1234:0200h

 (см. адреса в скобках). Ко-

гда  программа  дойдет  до строки (2), сегмент

  cs

  будет  содержать  адрес  текущего 

сегмента (

1234h

), а смещение

 ip

 — адрес следующей команды (

0105h

). 

background image

Глава 15. Обработка аппаратных прерываний 

153 

В момент выполнения строки (2) в стек заносится текущее состояние регистра 

ip

  (и  только!),  т. е.  число 

0105h

.  Затем  процессор  переходит  на  адрес 

0200h

  (на 

метку нашей процедуры). Как видите, директива 

Our_proc proc

 не занимает памя-

ти: она нужна только ассемблер-программам.  

Начинает работать процедура 

Our_proc

, при этом адрес возврата (в нашем при-

мере — 

0105h

)  находится  в  стеке.  Мы  уже  обращали  ваше  внимание,  что  в  про- 

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

ret

, вытащит из стека 

не адрес возврата, а то число, которое находится на вершине стека. 

Итак,  процедура  отработала.  Процессор  дошел  до  команды 

ret

.  Команда 

ret

достает  последнее  число,  находящееся  на  вершине  стека  в  данный  момент,  и  пе-
рейдет по этому адресу. Если мы со стеком ничего не намудрили (ничего в нем не 
оставили и ничего "лишнего" не вытащили), то 

ret

 достанет именно число 

0105h

, 

т. е. то, что сохранила инструкция 

call

, и перейдет по этому адресу. 

В

А Ж Н О

!  

При вызове ближней процедуры в стеке сохраняется только смещение следующей за 
процедурой инструкции (в примере из листинга 15.3 — только 0105h).  

15.2.2. Оператор retf 

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

и сегмент для возврата. Например, мы точно знаем, что по адресу 

3456:0400h

  на-

ходится  процедура  вывода  строки  на  экран.  Проблема  в  том,  как  определить  сег-
мент этой процедуры, отличный от того, в котором находится наша программа (не 

1234h

, а 

3456h

). Для этого занесем в некую переменную (пусть это будет 

New_proc

) 

два слова — 

3456h

 и 

0400h

 — адрес процедуры 

New_proc

, включая сегмент и сме-

щение. Затем передаем управление этой процедуре (листинг 15.4). 

Листинг 15.4. Вызов дальней подпрограммы 

... 
;Заносим сперва смещение 
mov word ptr [New_proc],0400h 
;Затем сегмент 
mov word ptr [New_proc+2],3456h 
;Вызываем процедуру 
call dword ptr [New_proc] 
... 
New_proc dd ?    ;Переменная для хранения адреса дальней процедуры (2 байта) 
...
 

В листинге 15.4 мы вначале заносим в переменную 

New_proc

 смещение, а затем 

сегмент  процедуры  (вспоминаем  о  том,  что  в  компьютере  данные  хранятся  в  об-

Источник: https://files.student-it.ru/previewfile/2839