Файл: О.А.Калашников. Ассемблер Это Просто. Учимся программировать.pdf
Добавлен: 16.02.2019
Просмотров: 29237
Скачиваний: 1689
Глава 18. Высокоуровневая оптимизация программ
189
Листинг 18.6. Использование регистра bp
...
(1) push offset Message1
(2) push offset Message2
(3) call Print_string
...
(4) Print_string proc
(5) mov bp,sp
(6) add bp,2
(7) mov ah,9
(8) mov dx,[bp]
(9) int 21h
(10) mov dx,[bp+2]
(11) int 21h
(12) ret 4
(13) Print_string endp
...
(14) Message1 db 'Привет!$'
(15) Message2 db 'Это я!$'
...
Здесь мы вывели две строки на экран, используя дважды функцию
09h
прерыва-
ния
21h
. Это, конечно, ужасно, но для теоретического исследования пойдет. Как вы
думаете, что произойдет?
Сперва выведется строка "Это я!", а затем — "Привет". Если у вас остался во-
прос, почему именно в таком порядке, то еще обратим внимание на следующее:
стек растет снизу вверх. Следовательно, первый параметр, занесенный в стек, будет
последним. Строка (8) получает адрес
Message2
, а (10) —
Message1
. Очень важно
помнить это при использовании данного метода! Получение параметров в стеке
показано в отладчике на рис. 18.4.
Обратите внимание, что в самом начале процедуры (строки (5), (6)) мы заносим
в
bp
текущее состояние стека, а затем увеличиваем
bp
на 2. Это необходимо для
того, чтобы "перепрыгнуть" адрес возврата, который занесла инструкция в строке
(3). Адрес возврата из процедуры сохраняет в стеке команда
call
после всех зане-
сенных в него параметров. Следовательно, первый параметр и будет адресом воз-
врата. Но он нашей процедуре вовсе не нужен в качестве параметра. Поэтому мы
Часть III. Файловая оболочка, вирус, резидент
190
просто увеличим
bp
на 2, установив его сразу на переданные параметры. Так даже
удобней будет считать. И еще: не забывайте выходить из процедуры соответст-
вующим образом, как в строке (12). Мы занесли 4 байта, нам и выйти нужно, ис-
пользуя
ret 4
.
Рис. 18.4. Получение смещения строки из переданных в стеке параметров
"Ну, нет! Это ж как неудобно! Лучше я заведу 10, 50, 100 переменных и буду
работать с ними, чем через стек. Пусть это и займет много байтов, зато удобно!" —
скажете вы. Однако это далеко не так! Давайте попробуем разобраться дальше.
Для удобства работы со стеком (и не только с ним) используется директива
equ
(от англ. equivalent — эквивалент). (Помните
Finish EQU $
?) Вот именно с помо-
щью данной директивы мы можем очень просто получить доступ к параметрам,
занесенным в стек. Теперь внимательно смотрите, как в листинге 18.7 используется
директива
equ
.
Листинг 18.7. Использование директивы equ
...
(1) push offset Message1
(2) push offset Message2
(3) call Print_string
...
(4) Print_string proc
(5) mov bp,sp
(6) add bp,2
Глава 18. Высокоуровневая оптимизация программ
191
(7) mov ah,9
(8) mov dx,Mess1
(9) int 21h
(10) mov dx,Mess2
(11) int 21h
(12) ret 4
(13) Print_string endp
...
(14) Message1 db 'Привет!$'
(15) Message2 db 'Это я!$'
(16) Mess1 equ [bp+2]
(17) Mess2 equ [bp]
...
В данном случае строки будут выведены на экран в следующем порядке:
"Привет", а затем "Это я". Строки (16), (17) места в памяти не занимают. При ас-
семблировании MASM/TASM заменит строки
mov dx,Mess1
mov dx,Mess2
на
mov dx,[bp+2]
mov dx,[bp]
соответственно. То есть стоит один раз позаботиться, а затем все будет понятно
и просто. Конечно, вам нужно будет еще немного "руку набить": написать три-
четыре подобные программы. Смотрите, как мы делаем подобные вещи в нашей
оболочке (data.asm):
Height_X equ [bp+18] ;высота рамки
Width_Y equ [bp+16] ;ширина рамки
Attr equ [bp+14] ;атрибуты рамки
И т. д.
Получаем же доступ к параметрам следующим образом (
DRAW_FRAME
,
display.asm):
mov ax,Height_X
mov ax,Attr
И т. п.
Один раз посчитали, записали и пользуемся!
Так как у программистов часто возникают ошибки при использовании данного
метода, то мы очередной раз обращаем ваше внимание на следующее: переменная
Heigt_X
заносится в стек первой, а достается из стека последней —
Height_X equ
[bp+18]
. Возврат из процедуры:
ret 20
, т. к. заносим 20 байт. Ошибки (упущения)
Часть III. Файловая оболочка, вирус, резидент
192
у вас, конечно, будут. Но сразу проверяйте: а правильно ли вы передали и получи-
ли параметры в/из стека, а также верно ли вы выходите из процедуры. Поверьте,
это очень удобно. Более того, при программировании под Windows мы будем по-
стоянно передавать параметры в стеке, причем очень много! Так что, готовьтесь
и привыкайте!
18.3.2. Передача параметров в стеке
Еще несколько слов о данной процедуре. Обратите внимание на параметр
Other
.
В нем мы будем передавать параметры, которые требуют проверки на "да" или
"нет". Для этого используется всего один бит данной переменной. В нашем приме-
ре мы, допустим, договариваемся о том, что нулевой бит (крайний справа) будет
указывать на то, стоит ли перед выводом рамки копировать часть экрана или нет
(см. далее). В дальнейшем, когда мы добавим функции, будем проверять уже не
байты, а биты.
Предположим, что третий бит параметра
Other
указывает на то, выводить под-
черкнутую линию или нет. Если третий бит установлен, то выводим. Та-
ким образом, один байт в ассемблере может нести 8 различных параметров (в бай-
те, как мы помним, 8 бит). Для этого и изучали мы в самом начале двоичную
систему счисления. Удобно ли использовать 8 бит одного байта для передачи сразу
восьми параметров? Не только удобно, но и компактно!
Обратите внимание, как мы заносим в стек смещение строки:
push offset Mess_head
Очень просто. Еще обращаем ваше внимание на следующие команды:
;надпись вверху рамки (если 0, то не выводить)
push offset Mess_head
push 1E00h ;цвет надписи вверху рамки
Если в качестве надписи заносим просто ноль, то строка выводиться не будет.
Может ли быть такое, что смещение некоторой строки будет нулевым? Вряд ли...
Тем не менее, если нам не нужно выводить строку, то ее атрибут все равно надо
заносить (просто любое число). Если мы этого не сделаем, то стек останется не вы-
ровненным. Так как занесли мы 16 байт, а процедура вытащит 20. Получается на-
рушение работы стека.
18.3.3. Вычисление длины строки
на стадии ассемблирования
Обратите внимание, как просто вычисляется длина строки, в случае, если ее
длина известна еще до ассемблирования программы:
Mess_quit db 'Ассемблер',0
Mess_quitl equ $-Mess_quit
Причем последняя строка занимать памяти не будет! В отладчике строка
mov dx,offset Mess_quitl
Глава 18. Высокоуровневая оптимизация программ
193
будет выглядеть как
mov dx,0009
Зачем это нужно? Вспомните ситуацию со строками вида
mov ax,23+5*2
. Это
удобно. Более того, добавив что-либо в строку
Mess_quit
или удалив из нее, ас-
семблер-программа (MASM/TASM) автоматически посчитает ее размер при обра-
ботке файла. А иначе нам придется делать это самостоятельно...
18.3.4. Процедуры Copy_scr / Restore_scr (display.asm)
Прежде чем нарисовать на экране рамку (окно), нам нужно сохранить ту ин-
формацию на экране, которая будет потеряна (затерта выводимыми поверх нее
символами). Попробуйте нажать в Far Manager клавишу <F5>, а затем <Esc>. Вы
задумывались, каким образом окно появляется на экране, а затем исчезает, восста-
новив затертые символы. Создается ощущение того, что окно просто располагается
поверх чего-то другого. Кажется, мелочь. А это все, уважаемые читатели, надо соб-
ственными руками делать. И еще пример: восстановление пользовательского экра-
на при нажатии комбинации клавиш <Ctrl>+<O>.
Для сохранения содержимого экрана перед рисованием окна и для восстановле-
ния затираемых данных напишем две процедуры:
Copy_scr
(копирование в буфер)
и
Restore_scr
(восстановление) (display.asm).
Принцип работы этих подпрограмм таков: получаем
dh
(ряд), с которого следует
начать сохранение, причем
dl
(колонка) будет всегда нулевым (так удобнее для
программиста). В
ax
заносится количество рядов, которое нужно будет сохранить
относительно
dh
. Сперва надо получить линейный адрес (просто вызовем извест-
ную нам процедуру
Get_linear
) (листинг 18.8).
Листинг 18.8. Получение линейного адреса
...
xor dl,dl ;Обнулим dl. Теперь dh = ряд, dl = 0
call Get_linear ;Получим линейный адрес
...
После этого необходимо получить количество байтов для сохранения (вспоми-
наем, как расположены символы в видеокарте). Для этого количество рядов нужно
умножить на 160 (листинг 18.9).
Листинг 18.9. Вычисление количества копируемых байтов
...
mov bl,160 ;Получим количество байтов, которые нужно копировать
mul bl
mov cx,ax ;переносим их в cx (будем использовать cx как счетчик)
...