Файл: О.А.Калашников. Ассемблер Это Просто. Учимся программировать.pdf
Добавлен: 16.02.2019
Просмотров: 29236
Скачиваний: 1689
Часть III. Файловая оболочка, вирус, резидент
194
Необходимая информация заносится в регистр
al
перед вызовом подпрограммы.
Mul bl
умножает содержимое
bl
на
al
, результат помещается в
ax
, который впо-
следствии переносим в
cx
. Сохраним все эти данные в переменных для восстанов-
ления. Копировать будем целыми рядами, т. е. от начала левого угла экрана до
конца (
dl
от 0 до 79; для этого мы и обнуляем
dl
на всякий случай). Копируется
столько рядов, сколько будет занимать выводимое окошко.
Теперь все готово. Переносим данные в область второй видеостраницы (первую
занимает наша оболочка, вторую — пользовательский экран) (листинг 18.10).
Листинг 18.10. Копируем видеостраницу
...
mov si,di ;ds:si — откуда копируем
xor di,di ;es:si — куда копируем
;Сохраним полученные значения для восстановления
mov Num_copysi,si
mov Num_copydi,di
mov Num_copycx,cx
push 0B800h ;Настроим сегментные регистры
pop ds
push 0BA00h
pop es
rep movsb ;Копируем...
...
Восстановление происходит достаточно просто и по тому же принципу, что
и сохранение экрана. (См. описание процедуры
Restore_scr
.)
18.3.5. Оператор scas
Рассмотрим еще один оператор, позволяющий работать со строками (массивами
данных) (табл. 18.1).
Таблица 18.1. Оператор
scas
Команда Перевод
Назначение
Процессор
scas
Scan string
— сканирование строки
Поиск символа в массиве 8086
Данный оператор имеет две разновидности, подобно
movs
и
stos
, а именно:
scasb
и
scasw
. Оператор
scasb
служит для поиска первого попавшегося байта,
а
scasw
— первого попавшегося слова (двух байтов). При этом
es:di
должен со-
держать адрес строки (листинг 18.11).
Глава 18. Высокоуровневая оптимизация программ
195
Листинг 18.11. Пример использования оператора scas
...
;es:di — адрес строки
(1) mov di,offset String
;cx — максимальное количество сканируемых байтов/слов
(2) mov cx,offset String_len
;Символ для поиска (9)
(3) mov al,9
;Ищем первый байт, который находится в al
(4) repne scasb
...
(5) String db 1,2,3,4,5,6,7,8,9,10,11,12
(6) String_len equ $-String
...
В
cx
заносим длину (количество символов/байтов) строки
String
, т. е. 12.
В
al
— символ, который нам нужно найти. После выполнения строки (4)
di
будет
указывать на адрес байта, следующего после найденного символа (т. е. на смеще-
ние числа 10). Вроде, все понятно, но возникают четыре вопроса:
1. Что такое
repne
? Мы до сих пор работали только с
rep
.
Итак, оператор
repne
(о англ. repeat if not equal — повторять, если не равно)
сканирует строку (массив) до тех пор, пока символ, расположенный в
al
/
ax
, не
будет в этой строке (массиве) найден. Данный оператор обычно используется
для поиска первого символа, в отличие от оператора
repe
(от англ. repeat if
equal — повторять, если равно).
2. Существует ли префикс
repe
?
Безусловно, существует и выполняет действия, противоположные действиям
оператора
repne
(листинг 18.12).
Листинг 18.12. Пример использования оператора repne
...
;es:di — адрес строки
(1) mov di,offset String
;cx — максимальное количество сканируемых байтов/слов
(2) mov cx,offset String_len
;Символ для поиска
(3) mov al,1
;Ищем первый байт, отличный от того, который находится в al
(4) repe scasb
Часть III. Файловая оболочка, вирус, резидент
196
...
(5) String db 1,1,1,1,1,6,1,1,1,1,1,1
(6) String_len equ $-String
...
В данном случае после выполнения строки (4) регистр
di
будет указывать на
адрес следующего за цифрой 6 байта (т. е. 1), а флаг нуля будет установлен. Мы
можем проверить, найден ли байт, отличный от загруженного в
al
, или нет, пу-
тем выполнения команды
je Label
(переход на метку
Label
, если байт найден).
Данный префикс (
repe
) обычно используется для поиска первого символа, не
равного тому, который содержится в
al
/
ax
.
3. Что произойдет, если в листинге 18.11 мы загрузим в
al
число 13?
В таком случае, если после числа 12 в массиве
String
не находится байт с чис-
лом 13, будет сброшен флаг нуля, а
di
выйдет за пределы массива
String
.
4. Что произойдет, если в листинге 18.11 мы в
cx
загрузим, например, число 7?
Это будет означать, что
scasb
выполнится 7 раз, при этом не дойдет до числа 9
(которое загружено в
al
). Следовательно,
di
будет указывать на число 8 в мас-
сиве
String
, а флаг нуля будет сброшен. Мы можем элементарно проверить это,
выполнив команду
jnz Label
(перейдем, если флаг нуля сброшен — значит,
символ не найден).
18.3.6. Подсчет длины нефиксированной строки
Мы рассмотрели оператор
scas
, чтобы теперь использовать его в нашей оболоч-
ке для подсчета длины строки (
Count_strmid
, display.asm). Каким образом?
Признаком окончания строки у нас является ASCII-символ 0. Считать же коли-
чество символов в строке необходимо для того, чтобы вывести ее в центре ряда
на экране монитора. В дальнейшем мы будем постоянно использовать данную про-
цедуру, поэтому давайте сейчас ее подробнее рассмотрим (листинг 18.13).
Листинг 18.13. Подсчет длины строки
...
(1) push cs ;es=cs
(2) pop es
(3) mov di,si ;di=si
(4) xor al,al ;al=0
(5) mov cx,0FFFFh ;Сколько символов перебирать (возьмем максимум)
(6) repne scasb ;Ищем 0 в массиве данных...
;0 найден! di указывает на следующий символ за найденным нулем
;si=начало строки
;di=конец строки+1
(7) sub di,si ;di=di-si-1 = длина строки
Глава 18. Высокоуровневая оптимизация программ
197
(8) dec di
(9) shr di,1 ;Делим длину на 2
(10) mov ax,40 ;Делим количество символов в строке на 2 = 40
(11) sub ax,di ;ax=40-половина длины строки = нужная колонка
(12) mov dl,al ;dl=колонка, с которой следует выводить строку.
...
Перед вызовом этой процедуры мы в
si
загружаем адрес строки, в которой сле-
дует посчитать количество символов. Так как оператор
scas
работает с парой реги-
стров
es:di
, то нам нужно сперва в
di
загрузить содержимое регистра
si
(строка
(3)). Далее аннулируем
al
(4), заносим в
cx
максимальное число. Длину строки мы
пока не знаем, поэтому предположим, что строка имеет максимально возможную
длину, которую может содержать регистр
cx
. Начался поиск (6)... Итак, нашли
символ
'0'
(7). Мы его просто обязаны найти.
Теперь мы имеем следующее:
si
содержит начальное смещение строки,
di
—
конец строки + 1. Чтобы получить длину, нужно из
di
вычесть
si
и еще единицу
((7), (8)):
di
=
di
–
si
– 1 = длина строки.
Обратите внимание, как это выглядит в отладчике (рис. 18.5).
Рис. 18.5. Вычислим длину строки и разделим эту длину на 2. di = длина строки / 2
Затем разделим полученную длину на два (9). Так как на экране в одном ряду
80 символов (режим 3), то 80 делим на два, чтобы получить середину ряда. Из се-
редины ряда вычитаем половину длины строки. Полные формулы:
di
= длина строки,
dl
= (80/2) – (
di
/2).
Теперь
dl
содержит колонку, с которой следует начинать выводить строку. Это
и будет центр ряда.
Часть III. Файловая оболочка, вирус, резидент
198
18.3.7. Вывод строки на экран
путем прямого отображения в видеобуфер
Пример вывода строки на экран показан в листинге 18.14.
Листинг 18.14. Вывод строки (Print_string, display.asm)
...
Print_string proc
(1) call Get_linear ;Получаем линейный адрес строки
...
Next_symstr:
(2) lodsb ;Получаем очередной символ строки
(3) or al,al ;Это 0 (конец строки?)
(4) jz Stop_outstr ;Да — выходим...
;Иначе заносим в видеобуфер атрибут (ah) и символ (al)
(5) stows
(6) jmp short Next_Symstr ;Следующий символ...
(7) Stop_outstr:
(8) ret
Print_string endp
...
Перед вызовом данной процедуры мы должны загрузить в пару регистров
ds:si
адрес строки для вывода, в
dx
— координаты для вывода (
dh
— столбец,
dl
—
строка (ряд)), а в
ah
— атрибуты выводимой строки.
В самом начале мы вызываем процедуру перевода
dx
в линейный адрес. Для че-
го, ведь это замедляет работу программы в целом?
Во-первых, это делается для удобства написания программы. Как вы думаете,
было бы удобно постоянно вычислять самим линейный адрес перед тем, как вы-
вести строку, или отдать эту работу отдельной готовой подпрограмме? Безус-
ловно, гораздо проще, если необходимые вычисления произведет компьютер.
Во-вторых, что было бы удобней при написании кода: указать просто стро-
ку/столбец или одно число — линейный адрес, который нужно вычислять про-
граммисту? Конечно же, первый вариант предпочтительней.
В-третьих, скорость работы программы не настолько уж падает. Даже на ком-
пьютерах моделей 8086/8088 это абсолютно незаметно. Так что беспокоиться по
поводу скорости нет необходимости.
Строка, передаваемая подпрограмме, должна заканчиваться символом 0. Конеч-
но, вы можете придумать другое ограничение строки (например, как у функции
09h
—
'$'
). Но вряд ли это будет удобно, т. к. нередко приходится выводить этот
символ на экран, а вот вывод числа 0 вообще не имеет смысла.