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

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

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

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

Добавлен: 16.02.2019

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

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

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

 

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

154 

ратном порядке). Если после команды 

call

 следует 

dword ptr

, то в стек заносится 

не только смещение, но и сегмент. 

Возникает  вопрос:  как  компьютер  различает,  когда  нужно  извлечь  из  стека 

только  смещение,  а  когда  смещение  и  сегмент?  Ответ  простой:  существуют  две 
разновидности инструкции 

ret

:

 ret

 и 

retf

. Инструкция 

retf

 (от англ. return far — 

дальний возврат) извлекает из стека не только смещение, но и сегмент (в отличие 
от  команды 

ret

,  достающей  смещение).  Если  мы  вызовем  дальнюю  процедуру 

(

call dword ptr [Far_proc]

), а выйдем из нее, используя 

ret

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

сто "зависнет". В листингах 15.5 и 15.6 приведены примеры.  

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

... 

[1234:0200h]  call Near_proc    ;Вызов ближней процедуры 

... 

;Ближняя процедура Near_proc (находится в том же сегменте, что и 

;и основная программа) 

[1234:4569h]  Near_proc proc 

... 

;Правильный выход из ближней процедуры (процедуры, которая располагается 

;в том же сегменте, что и программа, ее вызывающая) 

[1234:6789h]  ret 

[1234:6789h]  Near_proc endp 

... 

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

... 

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

... 

;Дальняя процедура New_proc (находится в другом сегменте) 

[3456:0300h]  New_proc proc 

... 

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

;в другом сегменте, в отличие от программы, ее вызывающей) 

[3456:0534h]  retf 

[3456:0534h]  New_proc endp 

... 

Одна и та же процедура не может быть вызвана двумя разными способами. Если 

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


background image

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

155 

Возможно, это покажется вам на первый взгляд сложным, но, поверьте, все на 

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

15.3. Механизм работы  
аппаратных прерываний. Оператор iret
 

"Ага! —  скажет  читатель. —  Что-то  мы  тут  упустили!  А  почему  тогда  перед 

call  dword  ptr  [Int_21h_vect]

 мы заносим в стек командой 

pushf

 регистр фла-

гов, но впоследствии его не достаем?" 

... 
pushf                            ;Заносим в стек флаги 
call dword ptr [Int_21h_vect]    ;Вызываем дальнюю процедуру 
... 

Зачем нужно заносить флаги в стек командой 

pushf

 перед вызовом оригинального 

обработчика прерывания? Попробуем дать ответ на этот вопрос. Сперва рассмотрим, 
для чего вообще вызываются прерывания, и что происходит в этот момент. 

Существуют  два  вида  прерываний:  программные и аппаратные. Программные 

прерывания  вызываются  программами  при  помощи  инструкции 

int

,  что  мы  уже 

неоднократно делали. К таким прерываниям можно отнести, в частности, 

21h

16h

10h

 и пр. 

В настоящий момент нас больше интересуют аппаратные прерывания, которые 

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

09h

 

(клавиатура)  и  некоторой  программы,  выполняемой в данный момент (обращайте 
внимание на сегменты).  

Листинг 15.7. Обработчик прерывания 09h 

;Обработчик прерывания 09h, расположенный где-то в памяти, который 
;активизируется при нажатии и отпускании какой-нибудь клавиши: 
... 
[0900:0050h]  mov al,bl 
;Здесь неважно, какой код находится. Главное то, что сегмент другой, 
;отличный от того, в котором находится наша программа (см. ниже) 
[0900:0052h]  ... 
 
[0900:0345h]  iret 
... 
;Это часть нашей программы, которая в данный момент выполняется: 
... 
(1) [1234:0200h]  mov ax,Num_regax 


background image

 

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

156 

(2) [1234:0205h]  cmp ax,17 
(3) [1234:0208h]  jne Not_equal 
... 

Аппаратное  прерывание 

09h

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

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

Допустим,  пользователь  нажал  клавишу  в  тот  момент,  когда  наша  программа 

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

У нас в регистрах находятся определенные числа. Более того, после выполнения 

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

ax

 17. Наша про-

грамма находится в момент вызова прерывания в сегменте 

1234h

, смещение 

0208h

 

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

Как сделать так, чтобы передать управление прерыванию 

09h

, которое выполнит 

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

10h

 прерывания 

16h

), при этом вернуться в то 

место, с которого произошло прерывание, не нарушив работы нашей программы? 

Процессору  нужно  запомнить  номер  текущего  сегмента  (

cs

),  смещение  сле-

дующей команды (оно всегда в 

ip

), а также состояние флагов в стеке. Этого доста-

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

09h

 отработает, 

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

cs:ip

! В случае необходи-

мости  сохранять  регистры  должно  то  прерывание,  которое  получило  управление  
(в случае нажатия клавиши — 

09h

). 

После того как процессор сохранил регистры 

cs:ip

 и флаги в стеке, он передает 

управление  обработчику  аппаратного  прерывания  (в  нашем  случае — 

09h

),  адрес 

которого  находится  в  определенном  месте  памяти  (где  именно  находятся  адреса 
прерываний, мы рассмотрим позже). Что значит "передает управление"? Да просто 
"прыгает"  на  определенный  адрес.  Это  будет  называться  "дальним  безусловным 
переходом", т. к. мы прыгаем не только на смещение внутри сегмента (как в случа-
ях,  которые  мы  рассматривали  уже,  например, 

jmp  Init

  в  разд. 15.1.2),  а  на  сег-

мент и смещение.  

Итак, управление получило прерывание 

09h

. Если в процессе его работы меня-

ются какие-то регистры, то оно должно их предварительно сохранить, иначе наша 
программа,  после  отработки  прерывания 

09h

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

в регистрах. 

Например, если 

09h

 изменит 

ax

, предварительно не сохранив его, а затем, соот-

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

ax

 не равен 

Num_Regax

 (1). 


background image

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

157 

Отсюда жесткое правило: если вы пишете свой обработчик, то обязательно со-

храняйте все регистры, которые он меняет. Если вы написали обработчик, напри-
мер, прерывания 

21h

, и после загрузки его в память компьютер "зависает" или ве-

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

Итак, прерывание 

09h

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

09h

) отработало: процессор 

дошел  до  инструкции 

iret

  (от  англ.  interrupt  return —  возврат  прерывания).  Эта 

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

ret

  тем,  что  при  ее  выполнении  процессор  извлечет  из 

стека сегмент (

cs

), смещение (

ip

) и регистр флагов, вместо одного смещения (

ip

(как 

ret

). Вот и вся разница между 

ret

 и 

iret

Резюмируем: 

  ret

  достает  из  стека  только  смещение  для  возврата;  процедура  должна  нахо-

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

near

 

(по умолчанию)); 

  retf

  достает  из  стека  сегмент  и  смещение;  процедура  может  находиться  в  лю-

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

far

 или 

dword ptr

); 

  iret

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

хода из прерываний.  
Команда 

iret

  вытаскивает  из  стека  адрес  возврата  (

cs:ip

).  В  примере  из  лис-

тинга 15.7 

cs=1234h

ip=0208h

, а в регистре флагов установлен флаг нуля, т. е. его 

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

Теперь ответ на главный вопрос: почему перед вызовом прерывания командой 

вида 

call  dword  ptr  cs:[Int_21h_vect]

  мы  заносим  в  стек  регистр  флагов  ко-

мандой 

pushf

 и впоследствии его не достаем? 

Ответ  прост.  Стоит  только  посмотреть  на  отличие  оператора 

retf

  от 

iret

 

(см. ранее).  При  передаче  управления  (вызове)  прерывания  командой 

call  dword 

ptr ...

 процессор заносит в стек только сегмент (

cs

) и смещение (

ip

) следующей 

за командой 

call

 инструкции. А 

iret

 извлечет из стека сегмент, смещение и фла-

ги.  Но  флаги  ведь  не  заносятся  командой 

call  dword  ptr

!  Мы  их  заносим  сами, 

"вручную".  Иначе  произойдет  нарушение  работы  стека,  и  компьютер  "зависнет". 
Как мы уже убедились, за стеком нужно следить очень внимательно!  

15.4. Практика 

Что делает наш обработчик прерывания 

21h

Он  умышленно  передает  неверные  данные  программе,  которая  ищет  файлы  

в каталоге, используя функции 

4Eh

 и 

4Fh

. Подробнее эти функции мы рассмотрим, 

когда будем писать оболочку. Здесь все вкратце. 


background image

 

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

158 

Функция 

4Eh

  ищет  первый  файл  или  каталог  на  диске,  и,  если  какой-нибудь 

файл найден, заносит в DTA информацию о нем, которая содержит, в частности: 

 

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

 

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

 

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

 

атрибуты файла. 
То же самое делает функция 

4Fh

. Ее отличие только в том, что она ищет второй 

и последующие файлы. Так устроена MS-DOS. 

Наш резидент контролирует прерывание 

21h

. Если вызывается одна из упомяну-

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

21h

 (листинг 15.8). 

Листинг 15.8. Проверка вызовов прерывания 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

  самим,  для  того  чтобы  оно  занесло  необходимые  данные  

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

Обратите  внимание  на  две  команды,  следующие  за  меткой 

Do_not

  в  листин-

ге 15.8.  Здесь  мы  вызываем  прерывание 

21h

.  Но  каким  образом!  Ведь  данный 

фрагмент кода — это и есть наш обработчик прерывания 

21h

. Следовательно, если 

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

int 21h

, то она передаст управле-

ние на наш обработчик, что зациклит процедуру и "повесит" компьютер. Нам это