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

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

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

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

Добавлен: 16.02.2019

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

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

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

 

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

184 

Листинг 18.2. Оптимизированный вариант (13 строк) 

... 
push ax 
push dx 
xor ax,ax 
xchg dh,al      ;Имеем: dx = dl, а ax = dh 
mov di,ax 
shl ax,6        ;dh * 64 
shl di,4        ;dh * 16 
add di,ax 
add di,dx 
shl di,1        ;di * 2 
pop dx 
pop ax 
ret 
... 

То,  что  второй  вариант  короче  на  1  строку —  это  мелочи.  Не  в  этом  дело.  Он 

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

shl

, а не умножения 

mul

, да и регистров 

задействовано меньше — только 

ax

Далее также приведены формулы вычисления линейного адреса видеобуфера: 

Линейный адрес = (COL   80 + RAW)   2. 

Отсюда: 

Линейный адрес = (COL   64 + COL   16 + RAW)   2. 

Или: 

Линейный адрес = ((COL 

shl 6

) + (COL 

shl 4

) + RAW) 

shl 1

В

А Ж Н О

!  

Команды  div  (деление)  и  mul  (умножение)  выполняются  гораздо  медленнее,  чем 
операции сдвига. 

Данный пример наглядно демонстрирует принцип высокоуровнего оптимизиро-

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

18.2. Ошибка в главе 17 

В  главе  17  была  умышленно  допущена  одна  ошибка.  Заметить  эту  ошибку  вы 

могли только в том случае, если разобрали программу полностью. 

С какой целью была допущена ошибка? Дело в том, что для того, чтобы узнать 

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


background image

Глава 18. Высокоуровневая оптимизация программ 

185 

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

18.3. Оболочка Super Shell 

Прежде всего, запустите полученный файл (sshell8.com) и посмотрите, какие за-

дачи он выполняет, как реагирует на нажатие клавиш <Ctrl>+<F5> и <Esc>. Теперь 
будем разбирать... 

18.3.1. Передача данных процедуре через стек 

Обратите  внимание,  каким  образом  теперь  вызывается  подпрограмма 

Draw_frame

Этот метод называется передачей данных процедуре через стек. При программиро-

вании  под  Windows  вызов  всех  процедур  производится  таким  способом.  В  Windows 
нет понятия "прерывание". Есть понятие "системная функция". Самое главное для 
вас  на  данном  этапе —  понять  принцип  передачи  данных  в  стеке.  Хотя  на  самом 
деле все очень просто. Принцип таков: занося в стек, например, 20 байт, процедура, 
получившая управление с параметрами в стеке, должна самостоятельно их достать, 
чтобы выровнять стек. Каким образом? 

Существует оператор 

ret N

, где 

N

 — количество освобождаемых байтов из сте-

ка.  В  принципе,  оператор 

ret  N

  аналогичен 

ret

,  за  исключением  того,  что  он  не 

только  выходит  из  подпрограммы,  но  перед  этим  достает  из  стека  определенное 
количество байтов. Давайте рассмотрим это на простом примере (листинг 18.3). 

Листинг 18.3. Передача данных подпрограмме в стеке 

... 
(1)    push 123h 
(2)    call Our_pr 
(3)    pop ax 
 
... 
(4) Our_pr proc 
       ... 
(5)    ret 
(6) Our_pr endp 
... 

Здесь мы вызываем процедуру 

Our_pr

 (2), предварительно занеся в стек некото-

рый параметр для данной процедуры (1). Что такое параметр? Например, для вывода 


background image

 

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

186 

строки  на  экран  с  помощью  функции 

09h

  мы  должны  передать  данной  функции 

параметр — адрес строки для вывода.  

Процедура 

Our_pr

  отработает  и  завершится,  при  этом  в  стеке  останется  пере-

данный  ей  параметр  (число 

123h

).  Нам  нужно  вытащить  его  из  стека,  т. к.  стек  

у нас остается не выровненным. Мы это и делаем в строке (3). 

Вроде все понятно. Но что делать, если мы передаем не один, а 20 параметров? 

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

ret N

, где 

N

 — количество высвобождаемых из стека байтов перед возвра-

том из подпрограммы. Вот как будет выглядеть приведенный ранее пример с исполь-
зованием данного оператора (листинг 18.4).  

Листинг 18.4. Использование оператора ret N 

... 
(1)    push 123h 
(2)    call Our_pr 
 
... 
 
(3) Our_pr proc 
       ... 
(4)    ret 2 
(5) Our_pr endp 
... 

Оператор 

ret 2

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

личит 

sp

 на 2, т. е. как бы "искусственно" достанет из стека данные, причем не за-

действуя никаких регистров. 

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

в  том,  что  если  той  или  иной  процедуре  необходимо  передавать  огромное  число 
параметров, то: 

 

во-первых, не хватит регистров; 

 

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

 

в-третьих, это увеличивает размер программы; 

 

в-четвертых, уменьшит скорость работы.  
Конечно, если процедуре нужно передать два-три параметра, то можно (и жела-

тельно) передавать их в регистрах. А если десять, как у нас при вызове 

Draw_frame

В прошлый раз мы заводили три специальные переменные. Но теперь мы сущест-
венно усовершенствовали нашу процедуру. Мы уже передаем 10 параметров. Сле-
довательно, будем передавать их в стеке (листинг 18.5). 


background image

Глава 18. Высокоуровневая оптимизация программ 

187 

Листинг 18.5. Передача параметров в стеке для подпрограммы Draw_frame 

... 

push 23                      ;высота 

push 78                      ;ширина 

push 1F00h                   ;цвет 

push offset Mess_head        ;надпись вверху 

push 1E00h                   ;ее цвет 

push offset Mess_down        ;надпись внизу 

push 1D00h                   ;ее цвет 

push 0                       ;сообщение внутри рамки 

push 0                       ;его цвет 

push 0                       ;копировать ли экран? 

call Draw_frame              ;рисуем рамку 

... 

Сразу  отметим,  что  команды  вида 

push  23

push  0

  толкают  в  стек  2 байта,  

а не 1. Теперь считаем количество занесенных в стек байтов: 

push 23                        - 2 байта 

push 78                        - 2 байта 

push 1F00h                     - 2 байта 

push offset Mess_head          - 2 байта 

...и т. д... 

Итого:                        - 20 байт 

Следовательно,  процедура 

Draw_frame

  должна  доставать  из  стека  столько  же 

байтов. Таким образом, выход из этой процедуры будет следующим: 

ret 20 

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

заносить  в  стек  20 байт  (10 слов),  даже  если  эти  данные  ей  не  нужны.  Иначе  мы 
оставляем стек не выровненным! 

Обратите  внимание,  как  мы  передаем  параметры  процедуре  рисования  окна  

в нашей оболочке (рис. 18.1—18.3). 

Возникает  вопрос:  как  нам  получить  доступ  в  процедуре  к  занесенным  в  стек 

параметрам? 

Для этой цели в ассемблере принято использовать регистр 

bp

, который мы редко 

затрагивали  в  наших  предыдущих  примерах.  Однако  вспомним  еще  раз  принцип 
работы стека. 

Стек растет снизу вверх. Первый параметр, занесенный в стек — последний для 

нашей процедуры. И наоборот. Давайте сперва рассмотрим все на самом простом 
примере (листинг 18.6). 


background image

 

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

188 

 

Рис. 18.1. Передача параметров в стеке 

 

Рис. 18.2. Возврат из подпрограммы и выравнивание стека 

 

Рис. 18.3. После выполнения подпрограммы стек выровнен