Файл: О.А.Калашников. Ассемблер Это Просто. Учимся программировать.pdf
Добавлен: 16.02.2019
Просмотров: 29235
Скачиваний: 1689
Часть 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 была умышленно допущена одна ошибка. Заметить эту ошибку вы
могли только в том случае, если разобрали программу полностью.
С какой целью была допущена ошибка? Дело в том, что для того, чтобы узнать
что-то новое, приходится столкнуться с чем-то сложным. В данном случае слож-
ность заключалась в том, что программа не работала так, как она должна была ра-
Глава 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). Что такое параметр? Например, для вывода
Часть 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).
Глава 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).
Часть III. Файловая оболочка, вирус, резидент
188
Рис. 18.1. Передача параметров в стеке
Рис. 18.2. Возврат из подпрограммы и выравнивание стека
Рис. 18.3. После выполнения подпрограммы стек выровнен