- •16. Строковые команды
- •16.1. Команда пересылки строк.
- •Xor ax, ax
- •Inc_bxDb43h; код командыincbx,
- •Xorbx,bx; вbx- длина очереди
- •Xorax,ax; Счетчик несовпадающих элементов
- •Incax; Нашли совпадение — увеличить счетчик
- •Int 21h
- •Incdi; Вернуть указатель на найденный символ
- •Int 21h
- •16.7. Команды загрузки адресов
Xor ax, ax
rep stosw
Этот фрагмент выполняется вдвое быстрее, чем если бы мы повторили 200 раз команду stosb.
16.5. Неожиданное применение строковых команд — определение типа процессора.
Бывают случаи, когда программа должна определить тип процессора, на котором она работает (ну, например, программы диагностики оборудования). Тип процессора можно определить только по косвенным признакам. Серия программ, различающих процессоры (от 8086 до Pentium) приведена в книге Правиков "Ключевые дискеты". Эти программы, как правило, анализируют новые флаги, появляющиеся в регистре флагов с увеличением номера процессора. ВPentiumвоявилась командаCPUID, с возможностями которой мы ознакомимся позже, и необходимость в специальных программах отпала.
Рассмотрим задачу различения процессоров 8086 и 8088 (хотя сейчас это имеет лишь исторический интерес). Система команд у процессоров одна и та же. Одинаков формат регистра Flags(на различиях в этом регистре основаны программы диагностики для старших моделей процессоров). Измерять быстродействие обращения к внешней шине (у 8086 она 16-разрядная, у 8088 — 8-разрядная) — дело безнадежное. Но можно воспользоваться косвенным признаком — длиной очереди команд. Очередь команд в шинном интерфейсе занимает у 8086 — 6 байтов, а у 8088 — 4 байта. Идея алгоритма: программа изменяет свой код одной командойrep stosb. При этом код команд, находящихся в очереди, изменить невозможно.
...
.DATA
lenDB?, '$' ; строка для вывода длины конвейера
Inc_bxDb43h; код командыincbx,
; получен с помощью debug
.CODE
start: mov ax, @data
movds,ax
Xorbx,bx; вbx- длина очереди
cld
mov di, OFFSET cs:_nop
movax,cs
moves,ax; НастроимESна кодовый сегмент
mov al, inc_bx
movcx,6 ; попытаемся изменить 6 байтов
cli; запрет внешних прерываний
repstosb
_nop: ; Здесь поместим 6 командnop
REPT6
nop
ENDM
sti; разрешить прерывания
sub6,bx; Получить длину очереди
addbx, '0' ; Сформировать код цифры
mov len, bl
message len
...
При запуске программы на 8088 на экран будет выведено число 4 — длина очереди, т.к. команды после метки _nopприняли вид
|
nop |
nop |
nop |
nop |
inc bx |
inc bx |
|
|
|
|
|
|
|
|
очередь |
|
| |||
Команды, находящиеся в очереди, не претерпели изменений, а команды, оставшиеся в ОЗУ, — изменились. Если выполнить эту программу на 8086 (и выше), то будет выведена длина очереди 6 (хотя на 80386 длина очереди составляет 15 байт).
Любопытно, что если запустить эту программу под управлением отладчика, то программа выведет на экран длину очереди 0! Дело в том, что в очереди теперь не команды программы, а команды отладчика.
Это наводит на мысль, что можно создавать программы, защищенные от трассировки отладчиком.
Пример. Фроловы БСП т.1 кн. 3
.DATA
sDB"Программа работает под управлением отладчика!",CRLFT
...
cli
call Test
sti
...
Test PROC near
mov byte ptr next, 90h ; код команды nop
next: ret
message s
ret
TestENDP
16.6. Команды сравнения
Эти две команды осуществляют сравнение элементов цепочек, не меняя операндов. Удобство их применения — в автоматическом переходе к следующему элементу цепочки
|
Сравнить элементы строк |
cmps src,dst (cmpsb, cmpsw) |
(SI)–(DI) модифицировать SIиDI |
|
(CoMPareStrings) |
флаги сост. изменяются | |
|
Сканировать элемент строки |
scas dst (scasb, scasw) |
ac–(DI) модифицировать DI |
|
(SCAnString) |
флаги сост. изменяются | |
По результатам этих операций выставляются флаги, которые анализируются последующими командами. (Для процессора 386 добавляются команды cmpsdиscasd).
Пример. Заменить каждый элемент байтового массива его абсолютной величиной (в предположении, что в массиве нет элемента 80h).
.DATA
m DB 1,-2,3,2,1,-3
len_m = $ - m
.CODE
start:
mov ax, @data
mov ds, ax
mov es, ax
cld
mov al,0
mov cx, len_m
mov di, OFFSET m
p:scasb; из 0 вычитаем элемент массива
jln; если результат отрицательный
; то исходный элемент положительный
negbyteptr[di-1] ; меняем знак у отрицательного
; с учетом того, что указатель
; уже перемещен на следующий
; элемент
n:loopp
…
Применение префикса повторения repдля таких команд бессмысленно, так как можно будет воспользоваться результатом только самого последнего сравнения. Но есть еще два префикса повторения, специально предназначенные для этих команд.
|
Повторять примитив пока равно (нуль) |
repe/repz примитив |
CXCX-1 пока (CX0 иZF= 1) |
|
(REPetition if Equal/Zero) |
| |
|
Повторять примитив пока не равно (не нуль) |
repne/repnz примитив |
CXCX-1 пока (CX0 иZF= 0) |
|
(REPetition if Not Equl or Zero) |
| |
Пример. Определение длины строки.
string DB "abc", 0
…
mov ax, @data
mov es, ax
cld
mov al,0
mov cx, -1
mov di, OFFSET string
repne scasb
not cx
deccx
Проследим выполнение этого фрагмента, начиная с команды repne scasb.
|
a |
b |
c |
0 |
|
|
CX = FFFFh |
CX = FFFEh |
CX = FFFDh |
CX = FFFCh |
CX=FFFBh |
|
DIв начале |
|
|
|
DIв конце |
На схеме показано начальное и конечное положение указателя DI(в конечном положенииDIсодержит адрес байта, следующего за нулевым). Показано также текущее значение счетчикаCX. Командаnot cxинвертирует битыCX. В результатеCX= 4. Командаdec cxкорректирует это значение. В результате вCXдлина строки (без учета терминатора строки — нулевого байта).
Пример. В двух строках одинаковой длины сосчитать количество несовпадающих элементов в позициях с одинаковыми номерами.
.MODEL small
.STACK 100h
.DATA
s1 DB "abcdesss"
len_s1 = $ - s1
s2 DB "abcdfsss"
.CODE
start:
mov ax,@data
mov ds,ax
mov es,ax
cld
mov cx, len_s1
