epd627
.pdf. . .
;Подпрограмма для деления значения на 10.
;Ввод: ax – значение, которое требуется разделить на 10
;Вывод: ax – значение, разделенное на 10
;dx – остаток значения, деленного на 10
DivideBy10 proc near
mov |
dx,0 |
; подготовить dx:ax как 32-битовое делимое |
mov |
bx,10 |
; bx – 16-битовый делитель (именно здесь bx:=10)! |
div bx ret
DivideBy10 endp codesg ends
В вызывающей программе подразумевается, что bx в процедуре DivideBy10 сохраняется, хотя фактически он устанавливается процедурой DivideBy10 в значение 10. В этом конкретном случае существует несколько возможных решений.
Например, способ 1 – сохранение регистра bx в вызывающей программе до вызова процедуры DivideBy10 и восстановление его тоже в вызывающей программе после вызова процедуры DivideBy10:
mov bx,1000 |
; bx :=1000 |
|
mov |
ax,w1 |
; ax:=w1=200 |
push bx |
; сохранить bx |
|
call |
DivideBy10 |
; разделить элемент на 10 |
pop |
bx |
; восстановить bx |
add |
bx,ax |
; вычисление суммы bx:=bx+ax=1000+w1/10 |
. . . |
; (в данном случае получится правильно) |
либо регистр bx можно загрузить после вызова процедуры (способ 2):
mov |
ax,w1 |
; ax:=w1=200 |
call |
DivideBy10 |
; разделить элемент на 10 |
mov bx,1000 |
; bx :=1000 |
|
add |
bx,ax |
; вычисление суммы |
или в начале процедуры DivideBy10 bx заносить содержимое регистра в стек, а при выходе из процедуры извлекать из стека (способ 3):
mov bx,1000 |
; bx :=1000 |
|
mov |
ax,w1 |
; ax:=w1=200 |
call |
DivideBy10 |
; разделить элемент на 10 |
add |
bx,ax |
; вычисление суммы |
. . .
;Подпрограмма для деления значения на 10.
;Ввод: ax – значение, которое требуется разделить на 10
;Вывод: ax – значение, разделенное на 10
81
; |
dx – остаток значения, деленного на 10 |
|
DivideBy10 proc near |
||
push bx |
; сохранить bx в стеке |
|
mov |
dx,0 |
; подготовить dx: ax как 32-битовое делимое |
mov |
bx,10 |
; bx –- 16-битовый делитель |
div |
bx |
|
pop |
bx |
; восстановить bx (извлечь из стека |
ret DivideBy10 endp
Первый подход к решению проблемы: все подпрограммы, которые могут изменять содержимое регистров, сохраняют их, а затем восстанавливают (требует дополнительного времени и увеличивает объем программы). Другой подход: каждая подпрограмма сопровождается комментарием, в котором указывается, какие регистры сохраняются, а какие разрушаются. При написании программы эта информация строго учитывается и тщательно проверяется, действительно ли в подпрограмме регистр не изменяется. Третий подход заключается в явном сохранении нужных регистров при вызове подпрограмм (см. уч. пос., ч.6).
5.6. Неопределенные символические имена [5, с.121-122]
Ассемблер генерирует сообщение о неопределенном символическом имени, если имя не определено в поле метки какой-либо строки ассемблерной программы. Если имя определено в другой программе (см. уч. пос., ч.2), то его следует определить как внешнее имя директивой extrn (external — внешний). Неправильная запись имени является наиболее вероятной причиной такой ошибки, особенно в тех случаях, когда используемые шестнадцатеричные константы записываются без начальной цифры.
secondproc near mov ax,cs mov ds,ax mov es,ax
;Неверные записи
;а) неопределенное имя value
mov |
ax,value |
; б) некорректная шестнадцатеричная запись |
|
mov |
bx,ffe2h; число ffe2h воспринимается как имя: нет нуля впереди |
;в) неопределенное имя code mov cx,code1
mov dx,code2 mov si,code
82
ret
code1 dw 100 code2 dw 200 code3 dw 300 secondendp
Ниже приведен образец правильной записи предыдущего примера. Символическое имя value представлено 16-битовым внешним именем. Шестнадцатеричная запись числа начинается с цифры, и неправильно записанное имя code заменено на правильное code3.
secondproc near extrn value:word mov ax,cs
mov ds,ax mov es,ax
mov ax,value |
|
mov bx,0ffe2h |
; т.к. первый символ– цифра, |
; |
воспринимается как число |
mov cx,code1 mov dx,code2 mov si,code3 ret
code1 dw 100 code2 dw 200 code3 dw 300 secondendp
5.7.Повторное определение символического имени [5, с.122-123]
Ассемблер формирует сообщение об ошибке, если одно и то же имя или метка два и более раз повторяется в поле метки ассемблерных строк программы.
case1:
decax ; ax:=ax-1 loop case1
and dx,bx cmp dx,0 jz case3
; Неверные записи
case1: ;имя case1 уже было определено
. . .
jz case3
83
Правильная запись предыдущего примера:
case1: |
|
|
dec ax |
; ax:=ax-1 |
|
; Неверные записи |
|
|
loop |
case1 |
|
and dx,bx |
|
|
cmp |
dx,0 |
|
jz case3 |
|
|
case2: |
|
|
. . .
jz case3
5.8. Неправильный порядок операндов
Многие программисты ошибаются и изменяют порядок операндов в инструкциях процессора 8086 на обратный. Это, вероятно, связано с тем, что строка
mov ax,bx
которая означает "поместить bx в ax", читается слева направо, и это иногда приводит к путанице. Чтобы запомнить порядок операндов в языке ассемблера процессора 8086, нужно строку
mov ax,bx
рассматривать как ax := bx
5.9.Неправильное использование операндов
Вязыке ассемблера первый операнд всегда используется в качестве приемника (кроме синтаксиса АТ&T для UNIX и Linux, см. уч. пос., ч.12). Операнд может быть записан в регистре общего назначения, регистре сегмента или ячейке памяти. При использовании непосредственных данных в качестве операнда-приемника генерируется сообщение об ошибке:
1) неправильная запись |
2) правильная запись |
cmp З,al |
cmp al,3 |
5.10. Потеря содержимого регистра при умножении
Большинство способов, с помощью которых программа на ассемблере может изменить состояние процессора, достаточно непосредственны и очевидны. Например, инструкция:
add bx,w1 |
значение по адресу w1 к bx и, чтобы отразить |
|
прибавляет |
16-битовое |
|
результат |
сложения, |
изменяет флаги переполнения, знака, нуля, |
|
|
84 |
дополнительного переноса, четности и переноса. Однако некоторые инструкции изменяют состояние процессора менее очевидным образом, и если программист забывает о необычных побочных эффектах, то возникает ошибка. Одной из таких ошибок является потеря содержимого регистра при умножении. Другие – такие как использование регистров ah, dx и edx (в МП 80386) после операции div и idiv в качестве старших разрядов частного, хотя содержат остаток и т.п., рассмотрены ниже (п.5.12-5.13).
В синтаксисе инструкций mul и imul явно указываются только один из операндов и размер, а регистры, используемые в качестве операндаприемника, просто подразумеваются. Это приводит к тому, что легко можно упустить из виду использование какого-либо неявного регистра. Есть много случаев, в которых, скажем, программист знает, что результат перемножения 16-разрядного значения на 16-разрядную величину, поместится в регистр ax. При этом часто забывают, что теряется содержимое регистра dx. Поэтому всегда нужно помнить о том, что при использовании инструкций mul и imul уничтожается содержимое не только регистров al, ax или eax (в МП 80386), но также и ah, dx или edx (в МП
80386).
5.11. Не подготовлены регистры при делении
Распространенной ошибкой для случаев, когда делимое по размеру совпадает с делителем и размещено в al или ax, является то, что программист не обнуляет регистры ah, dx.
Пример 1. Не очищается ah при делении байта на байт:
; нет обнуления ah или ax, т.е. пропущена команда xor ah,ah или xor ax,ax
mov al,b1 |
; al:=b1 |
div b2 |
; ax используется как делимое (но ah не подготовлен) |
mov byte ptr rezult,al
Пример 2. Не очищается dx при делении слова на слово. ; пропущена команда зануления dx, т.е. команда xor dx,dx
mov ax,w1 ;ax:=w1
div w2 ; dx:ax используется как делимое (но dx не подготовлен) mov word ptr rezult,ax
5.12. Неправильное использование регистра после деления
Не учитывается, что регистры ah и dx содержат остаток и используют в качестве старших разрядов частного.
Пример 1. Ошибочно используется ah после деления байта на байт: mov al,b1 ; b1/b2
div b2
mov word ptr rezult,ax ;ошибка: в ah содержится остаток,
85
; а не старшие разряды частного (правильно будет mov byte ptr result,al). Пример 2. Ошибочно используется регистр dx после деления слова на
слово:
|
xor dx,dx |
;\ |
|
|
mov ax,w1 |
; |w1/w2 |
|
|
div w2 ;/ |
|
|
|
mov word ptr rezult,ax |
|
|
|
mov word ptr result+2,dx; ошибка: в dx содержится остаток, а не |
||
; |
старшие разряды частного. Правильно будет mov word ptr result+2,0 |
||
|
Пример 3. Ошибочно используется регистр dx после деления двойно- |
||
го слова на слово: |
|
||
|
mov ax,word ptr d1 |
|
|
|
mov dx,word ptr d1+2 |
|
|
|
div w1 |
|
|
|
mov word ptr rezult,ax |
; результат деления d1/w1 |
|
|
mov word ptr rezult+2,dx |
;ошибка: в регистре dx содержится |
|
|
; |
|
остаток, а не старшие разряды частного |
|
; |
|
(правильно будет mov word ptr result+2,0) |
Пример ошибочного использования регистра edx после деления учетверенного слова на двойное слово приведен в уч. пос., ч.7.
5.13. Потеря содержимого регистра при делении
Потеря содержимого регистра при делении заключается в том, что теряется значение, помещенное в регистр до операции деления (значение регистров al, ah, ax, dx теряется, т.к. туда заносится частное (в регистры al, ax) или остаток от деления (в регистры ah , dx).
Пример 1. Теряется содержимое ah после деления байта на байт:
mov ah,b3 |
; ah:=b3 |
mov al,b1 |
; b1/b2 |
div b2 |
|
mov byte ptr rezult,al
add al,ah ; ошибка: в регистре ah содержится остаток, а не b3 ; (правильно будет команду mov ah,b3 выполнять не до деления, а после).
Пример 2. Теряется содержимое dx после деления слова на слово:
mov dx,w3 |
|
mov ax,w1 |
; w1/w2 |
div w2 |
|
mov word ptr rezult,ax
mov word ptr result+2,0 ; ошибка: в регистре dx содержится
;остаток, а не w3 (правильно будет команду mov dx,w3
; |
выполнять после деления, а не до него). |
86
5.14. Неправильное использование регистров в командах cbw и cwd
Неправильное использование регистров при преобразовании байта в слово и слова в двойное слово заключается либо в ошибочном размещении исходного значения, либо в использовании в качестве результата не регистров ax (dx:ax). Для cbw и cwd исходные значения должны быть размещены в регистрах al и ax, соответственно. Программисты же иногда ошибочно помещают исходное значение в какой-нибудь другой регистр (например, в bl или bx).
Пример 1.
mov bl,b3 |
; bl := b3 |
cbw |
; ошибка: результат, получившийся в ax, не имеет никакого |
; |
отношения к регистру bl (а значит, и к b3) |
Пример 2. |
|
mov al,b3 |
; al := b3 |
cbw |
; результат получился в регистре ax |
mov rez_w,bx ; ошибка: в bx не содержится результат выполнения
;команды cbw
5.15.Применение команд преобразования cbw и cwd
кбеззнаковым данным
При применении команд cbw и cwd к беззнаковым данным может возникнуть ошибка. Когда знаковый бит равен единице, старший байт или слово соответственно заполняется единицами, а не нулями, как это требуется в данном случае. Это надо учитывать при выполнении логических операций и операций сдвига и вместо инструкций cbw и cwd применять зануление старшего байта или слова командой xor. Например, если al= 128 (т.е. 100000002), то после выполнения команды cbw в регистре ax будет число 65408 (т.е. 11111111100000002), а должно остаться равным
128, но расширенным на 16 битов (т.е. 00000000100000002).
5.16.Изменение отдельными инструкциями флага переноса
Вто время как некоторые инструкции "непредвиденным" образом изменяют содержимое регистров или флагов, другие инструкции не влияют на все те флаги, изменения которых вы ожидаете. Например, инструкция
inc ah
выглядит логически эквивалентной инструкции
add ah,1
и это действительно так, но инструкция add в случае слишком большого для операнда-приемника результата устанавливает флаг переноса, а инструкция inc на флаг cf не влияет. В результате инструкции
add ax,1 |
; ax := ax+1 (и cf:=1, если возник перенос) |
|
87 |
adc dx,0 ; dx := dx+0+<значение cf>
можно использовать для увеличения 32-битового значения, хранящегося в регистрах dx: ax, а инструкции
inc ax ; ax:=ax+1 (cf не меняется, даже если возник перенос) adc dx,0
нельзя. То же самое имеет место для инструкции dec. Инструкции loop, loopz и loopnz также не влияют на состояние флагов (см. уч. пос., ч.6 и 7). Это можно использовать, например, в тех случаях, когда нужно организовать цикл и при этом не изменять значения флагов, в т.ч. и флага переноса.
5.17. Программист долго не использует состояние флагов
Состояние флагов сохраняется до тех пор, пока следующая инструкция их не изменит. Обычно это не слишком долгий интервал времени. Хорошей практикой программирования является возможно скорейшее использование флагов их установки, что позволяет избежать многих потенциальных ошибок. Например, часто хочется проверить условие, задать содержимое одного-двух регистров и только после этого в соответствии с результатом проверки выполнить переход. Инструкции:
cmp |
ax,1 |
;вычисление (ax-1) без присвоения полученного значения |
|
; |
ах и присвоение значений (0 или 1 – зависит от результата) флагам: |
||
; |
нуля (zf), знака (sf), переноса (cf) и других (of , af и pf) |
||
mov |
ax,0 |
;ax:=0 значения флагов не изменяет |
|
jg |
|
Positive |
; если больше (т.е если zf=0 и sf=of), переход на Positive |
представляют собой допустимый способ проверки состояния регистра ax, установки его в значение 0 и обработки результата. А инструкции:
cmp |
ax,1 |
sub |
ax,ax ; присвоение значений (0 или 1 – зависит от результата) |
; |
флагам:нуля (zf), знака (sf), переноса (cf) и других (of , af и pf) |
jg |
Positive |
которые выглядят более привлекательно, поскольку такой код короче и выполняется быстрее, правильно работать не будут, так как при вычитании теряется содержимое всех флагов, установленных при сравнении. Это типичная проблема, возникающая, когда не торопятся проверить флаг.
5.18. Ошибки при использовании регистров
При использовании регистров для записи операндов в ассемблерной программе необходимо соблюдать следующие правила:
1.Нельзя задавать 8- и 16-разрядный (а для процессора 80386 также 8-
и32-разрядный или 16- и 32-разрядный) регистры для записи двух
88
операндов одной команды (это правило не распространяется на команды movsx и movzx, см. уч. пос., ч.7).
2.Операции со стеком должны осуществляться только в 16-битовых кодах. Если 8-разрядный регистр указывают для работы со стеком (push/pop), то выдается сообщение об ошибке.
3.Сегментные регистры не могут прямо использоваться в арифметических вычислениях, логических операциях или для непосредственной передачи данных. Кроме того, сегментные регистры могут быть использованы только либо как источники операндов, либо только как приемники операндов, но никак не одновременно.
Неверные записи
add ax,dl ; а) одновременное использование
;8-битового регистра (dl) и 16-битового регистра (ax) push bl ; б) 8-разрядный регистр указывают для работы со стеком
add ds,100 ; в) сегментный регистр ds используется при сложении
mov es,200 ; г) непосредственный операнд передается в регистр es
mov ss,cs |
|
;д)оба операнда являются сегментными регистрами |
||||
Ниже приведена правильная запись предыдущих примеров |
||||||
xor dh,dh |
|
|
|
|||
add ax,dx |
; а) |
|||||
|
|
|
|
|
|
|
xor bh,bh |
|
|
|
|||
push bx |
; б) |
|||||
|
|
|
|
|
|
|
addcx,100 |
; в) |
|||||
|
|
|
|
|
|
|
movcx,200 |
; г) |
|||||
moves,cx |
|
|
|
|||
|
|
; д) |
||||
movcx,cs |
||||||
movss,cx |
|
|
|
89
5.19. Выход из диапазона адресов [5, с.124-125]
Все команды условного перехода используют режим относительной адресации с 8-битовым смещением. Если относительный адрес символа, используемого в качестве операнда-адреса перехода, выходит за диапазон + 127 байтов -128 байтов от конца команды условного перехода, выдается сообщение об ошибке.
Неверные записи
and al,7fh |
|
je next_proc |
;между символическим адресом end_proc и next_proc |
; |
более 127 байтов (это важно, т .к. при выполнении |
end_proc: |
; команды je указатель команды (ip) показывает |
; |
на end_proc, а перейти надо на next_proc) |
ret
data dw 300 dup(?) next_proc:
clc
Ниже приведена правильная запись предыдущего примера: and al,7fh
jne end_proc jmp next_proc
end_proc: ret
data dw 300 dup(?) next_proc:
clc
Библиографический список
1.Зубков С.В. Assembler для DOS, Windows и UNIX.–3-е изд., стер.–
М.: ДМК Пресс; СПб.: Питер, 2005.– 608 с.
2.Бурдаев О.В., Иванов М.А., Тетерин И.И. Ассемблер в задачах защиты информации/ Под ред. И.Ю.Жукова. – М.: КУДИЦ-ОБРАЗ, 2002. – 320 с.
3.Абель Питер. Ассемблер и программирование для IBM PC. Британская Колумбия. Технологический институт.
4.Олейник Л.Е. Язык ассемблера для микропроцессора 8086: Курс лекций. – Омск: Сибирская региональная школа бизнеса, 2000.
5.Дао. Л. Программирование микропроцессора 8088. – М.: Мир, 1988.
–356 с.
6.Абель Питер. Язык и программирование для IBM PC: Пер. с англ.– Киев: Век; М.: Энтроп; Киев: НТИ, 2003. – 736 с.
90