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

ВУЗ: Не указан

Категория: Книга

Дисциплина: Программирование

Добавлен: 16.02.2019

Просмотров: 29229

Скачиваний: 1689

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
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

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

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