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

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

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

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

Добавлен: 16.02.2019

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

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

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

Глава 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

 после всех зане-

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


background image

 

Часть 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 


background image

Глава 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 байт. Ошибки (упущения) 


background image

 

Часть 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 


background image

Глава 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 как счетчик) 
...