Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

2591

.pdf
Скачиваний:
2
Добавлен:
07.01.2021
Размер:
25.15 Mб
Скачать

5.14. Неправильное использование регистров при преобразовании байта в слово и слова в двойное слово в командах cbw и cwd

Неправильное использование регистров при преобразованиях заключается либо в ошибочном размещении исходного значения, либо в использовании в качестве результата не регистров ax либо (dx:ax). Для cbw и cwd исходные значения должны быть размещены соответственно в регистрах al и ax. Программисты же иногда ошибочно помещают исходное значение в какой-нибудь другой регистр (например, в bl или bx).

5.15.Применение команд преобразования cbw и cwd

кбеззнаковым данным

При применении команд cbw и cwd к беззнаковым данным может возникнуть ошибка. Когда знаковый бит равен единице, старший байт или слово, соответственно, заполняется единицами, а не нулями, как требуется в этом случае. Это надо учитывать при выполнении логических операций и операций сдвига и вместо инструкций cbw и cwd применять зануление старшего байта или слова командой xor. Например, если al= 128 (т.е. 100000002), то после выполнения команды cbw в регистре ax будет число 65408 (т.е. 11111111100000002), а должно остаться равным 128, но расширенным на 16 битов (т.е.

00000000100000002).

5.16. Изменение отдельными инструкциями флага переноса

Некоторые инструкции не влияют на те флаги, изменения которых вы ожидаете. Например, инструкция add в случае слишком большого для операнда-приемника результата устанавливает флаг переноса, а инструкция inc на флаг cf не влияет.

То же самое имеет место для инструкции dec и инструкций loop, loopz и loopnz (не влияют на состояние флагов; см. уч. пос., ч. 6 и 7). На практике это можно использовать в тех случаях, когда нужно выполнить одну из этих инструкций без нарушения установки флага переноса.

5.17. Программист долго не использует состояние флагов

Состояние флагов сохраняется до тех пор, пока следующая инструкция их не изменит. Обычно это не слишком долгий интервал времени. Хорошей практикой программирования является возможно скорейшее использование флагов их установки, что позволяет избежать многих потенциальных ошибок.

51

5.18. Ошибки при использовании регистров

При использовании регистров для записи операндов в ассемблерной программе необходимо соблюдать следующие правила:

1.Нельзя задавать 8- и 16-разрядные (а для процессора 80386 также 8-

и32-разрядные или 16- и 32-разрядные) регистры для записи двух операндов одной команды (это правило не распространяется на команды movsx и movzx, см. уч. пос., ч. 7).

2.Операции со стеком должны осуществляться только в 16-битовых кодах. Если 8-разрядный регистр указывают для работы со стеком (push/pop), то выдается сообщение об ошибке.

3.Сегментные регистры не могут прямо использоваться в арифметических вычислениях, логических операциях или для непосредственной передачи данных. Кроме того, сегментные регистры могут быть использованы только либо как источники операндов, либо как приемники операндов, но никак не одновременно (см. уч. пос., ч. 1).

5.19. Выход из диапазона адресов [5, с.124–125]

Все команды условного перехода используют режим относительной адресации с 8-битовым смещением. Если относительный адрес символа, используемого в качестве операнда-адреса перехода, выходит за диапазон +127 байтов -128 байтов от конца команды условного перехода, выдается сообщение об ошибке(см. уч. пос., ч. 1).

5.20. Программист забывает об инструкции ret

Правильный вызов подпрограммы состоит из вызова подпрограммы из другой части кода, выполнения подпрограммы и возврата из подпрограммы в вызывающую программу. Не забудьте включать в каждую подпрограмму инструкцию ret, по которой управление будет передаваться в вызывающий код. При наборе программы эту директиву легко пропустить и закончить код следующим образом:

;

;Подпрограмма для умножения значения на 80

;Ввод: ax — значение, которое нужно умножить на 80

;Вывод: dx:ax — произведение

MultiplyBy80 proc near mov dx,80

mul dx MultiplyBy80 endp

;Подпрограмма для получения следующей нажатой клавиши

;Вывод: al — следующая нажатая клавиша

52

;Содержимое регистра ah теряется

GetKey proc near mov ah,1 int 21h

ret GetKey endp

Директива MultipleBy80 endp может ввести в заблуждение: подпрограмма MultipleBy80 не завершена, т.е. после того, как содержимое ax будет умножено на 80, продолжается выполнение кода (в данном случае подпрограммы GetKey, и в регистре al будет возвращаться код следующей нажатой клавиши. Корректной эта подпрограмма будет в следующем виде:

;Подпрограмма для умножения значения на 80

;Ввод: ax — значение, которое нужно умножить на 80

;Вывод: dx:ax — произведение

MultiplyBy80 proc near mov dx,80

mul dx

 

ret

; добавлена эта инструкция!

MultiplyBy80

endp

;Подпрограмма для получения следующей нажатой клавиши

;Вывод: al — следующая нажатая клавиша

;Содержимое регистра ah теряется

GetKey proc near mov ah,1 int 21h

ret GetKey endp

5.21. Генерация неверного типа возврата

Директива proc определяет имя, по которому будет вызываться процедура, и управляет типом (ближним или дальним) процедуры.

Тип процедуры используется ассемблером для определения того, какой тип вызовов нужно генерировать при вызове процедуры из того же исходного файла, и для определения типа инструкции ret, которая выполняется, когда процедура возвращает управление. Например:

;

; Подпрограмма ближнего типа для сдвига dx:ax вправо на 2 байта

LongShiftRight2 proc near shr dx,1

rcr ax,1 ; сдвиг dx:ax вправо на 1 бит

53

shr dx,1

rcr ax,1 ; сдвиг dx:ax вправо еще на 1 бит ret

LongShiftRight2 endp

Ассемблер обеспечивает, что инструкция ret будет ближнего типа, так как LongShiftRight2 — это процедура ближнего типа (near). Однако, если директиву proc изменить следующим образом:

LongShiftRight2 proc far

то будет генерироваться инструкция ret дальнего типа (far).

Часто группируют в одной и той же процедуре несколько процедур. Поскольку в этих процедурах отсутствует соответствующая директива proc, их инструкции ret будут иметь тип той процедуры, в которую они заключены, а для конкретных подпрограмм этот тип не всегда может оказаться корректным. Например:

; Подпрограмма дальнего типа для сдвига dx:ax на 2 бита.

;

LongShiftRight2 proc far

 

call

LongShiftRight

; сдвиг dx:ax вправо на 1 бит

call

LongShiftRight

; сдвиг dx:ax вправо еще на 1 бит

ret

 

 

LongShiftRight:

 

shr

dx,1

 

rcr

ax,1

; сдвиг dx:ax вправо на 1 бит

ret

 

 

LongShiftRight2 endp

работает неправильно. Процедура LongShiftRight2 обращается с вызовом ближнего типа к LongShiftRight (так как они находятся в одном сегменте кода). Однако, так как LongShiftRight встроена в процедуру

LongShiftRight2, то возврат в конце подпрограммы LongShiftRight

становится возвратом дальнего типа, а когда вызову ближнего типа соответствует возврат дальнего типа, это с большой вероятностью может привести к сбою (аварийному завершению программы).

Хорошим решением здесь будет наличие в каждой подпрограмме директивы proc. Вложенные директивы proc прекрасно работают:

; Подпрограмма дальнего типа для сдвига dx:ax на 2 бита.

;

LongShiftRight2 proc far

 

call

LongShiftRight

; сдвиг dx:ax вправо на 1 бит

call

LongShiftRight

; сдвиг dx:ax вправо еще на 1 бит

ret

 

 

 

 

54

LongShiftRight

proc near

shr

dx,1

 

rcr

ax,1

; сдвиг dx:ax вправо на 1 бит

ret

 

 

LongShiftRight2

endp

LongShiftRight

endp

Так же, как и последовательные процедуры:

; Подпрограмма дальнего типа для сдвига dx:ax на 2 бита.

;

 

 

LongShiftRight2

proc far

call

LongShiftRight; сдвиг dx:ax вправо на 1 бит

call

LongShiftRight; сдвиг dx:ax вправо еще на 1 бит

ret

 

 

LongShiftRight2

endp

LongShiftRight

proc near

shr

dx,1

 

rcr

ax,1

; сдвиг dx:ax вправо на 1 бит

ret

 

 

LongShiftRight

endp

Для явной генерации ближнего или дальнего возврата можно использовать, соответственно, инструкции retn и retf. Вы можете обеспечить с их помощью корректную генерацию инструкций возврата.

5.22. Ошибки при использовании условных переходов

Использование в языке ассемблера инструкций условных переходов (je, jne, jc, jnc, ja, jb, jg и т.д.) обеспечивает большую гибкость в программировании, но при этом также очень просто ошибиться, выбрав неверный переход. Типичные ошибки:

1.Использование инструкций ja, jb, jae или jbe для сравнения значений со знаком или, соответственно, инструкций jg, jl, jge или jle для сравнения беззнаковых значений.

2.Использование инструкции ja там, где нужно использовать jae. Без буквы e в конце инструкции в сравнении не учитывается случай, когда два операнда равны.

3.Использование инвертированной логики, например, применение инструкции js там, где нужно использовать jns.

55

5.23. Перепутаны операнды в памяти и непосредственные операнды

Программа на ассемблере может ссылаться либо на переменную памяти по смещению, либо на значение, хранящееся в этой переменной.

Например, смещение переменной в памяти размером в слово MemLoc представляет собой константу 5002, которую можно получить с помощью оператора offset. Например, инструкция:

mov bx, offset MemLoc

загружает значение 5002h в регистр bx. Значение 5002h представляет собой непосредственный операнд. Другими словами, оно встроено непосредственно в инструкцию и не изменяется.

Допустим, значением MemLoc является 1234h. Оно считывается из памяти со смещением 5002h в сегменте данных. Один из способов считывания данного значения состоит в загрузке в регистр bx, si, di или bp смещения MemLoc и использования данного регистра для адресации к памяти. Инструкции:

mov bx, offset MemLoc mov ax,[bx]

загружают значение MemLoc в регистр ax. Значение MemLoc можно также загрузить непосредственно в ax с помощью инструкции:

mov ax,MemLoc

или

mov ax,[MemLoc]

Здесь значение 1234h получается как прямой, а не как непосредственный операнд: инструкция MOV использует встроенное в нее смещение 5002h и загружает в ax значение по смещению 5002h, которое в данном случае равно 1234h.

Часто встречающейся ошибкой является то, что, увлекшись написанием программы, часто забывают использовать операцию offset, например:

mov si,MemLoc

где нужно использовать смещение MemLoc. На первый взгляд данная строка не выглядит неправильной, и так как MemLoc — это переменная размером в слово, то эта строка не приведет к ошибке ассемблирования. Однако при выполнении в si будут загружены содержащиеся в переменной MemLoc данные (1234h), а не ее смещение (5002h), и результаты будут непредсказуемы.

Надежного способа избежать этой проблемы нет, но можно принять за правило заключать все ссылки на память в квадратные скобки. Когда перед ссылками на адресные константы будет указываться префикс offset, а ссылки на память — заключаться в квадратные скобки, это устранит

56

двусмысленность и неопределенность при использовании имен переменных памяти. При таком соглашении работа инструкций:

mov si, offset MemLoc

и

mov si,[MemLoc]

становится совершенно понятной, в то время как инструкция mov si,MemLoc

будет настораживать.

5.23. Границы сегментов

Один из наиболее трудных моментов при программировании для процессоров 8086 заключается в том, что к памяти нельзя обращаться, как к одному большому массиву байт. Она доступна только по частям, каждая из которых равна 64Кб и связана с сегментным регистром. Трудноуловимой ошибкой является обращение к адресу за концом сегмента, т.к. в действительности будет обращение к началу сегмента.

В качестве примера предположим, что память, начинающаяся с адреса 10000h, содержит некоторые данные. Когда регистр ds устанавливается в значение 1000h, программа, которая обращается к некоторой строке "Testing" по адресу 1000:FFF9, после обработки символа g по адресу 1000:FFFF, возвращается назад к байту по адресу 1000:0000, так как смещение не может превышать 0FFFFh (максимальное 16-битовое значение).

Предположим теперь, что при ds:si, равном 1000:FFF9, вызывается подпрограмма для преобразования строки "Testing" в верхний регистр:

;Подпрограмма для преобразования завершающейся нулевым символом

;строки в верхний регистр.

;Ввод: ds:si — указатель на строку.

ToUpper

PROC NEAR

 

mov

ax,[si]

; получить следующий символ

cmp

al,0

; если 0, то

jz ToUpperDone ;

преобразовать строку

cmp

al,'a'

; это строчная буква?

jb

ToUpperCase ; не строчная буква

cmp

al,'z'

 

 

ja

ToUpperNext ; не строчная буква

and al,NOT 20h ; строчная буква, преобразовать ее в верхний регистр

mov

[si],al

; сохранить прописную букву

ToUpperNext:

 

 

 

 

 

57

inc

si

; ссылка на следующий символ

jmp

ToUpper

 

ToUpperDone: ret

ToUpper endp

После того, как процедура ToUpper обработает первые семь символов строки, si изменит значение с 0FFFFh на 0. (si — это 16-разрядный регистр, поэтому отсчет с превышением значения 0FFFFh выполнить нельзя.) Завершающий строку нулевой байт, записанный по адресу 20000h, достигнут не будет. Вместо этого процедура начнет преобразовывать не относящиеся к делу байты по адресу 10000h и не остановится, пока не встретит нулевой байт. На более позднем этапе эти измененные байты могут вызвать некорректную работу программы. Часто такие ошибки, вызванные случайным изменением байтов при достижении программы конца сегмента, бывает очень трудно отследить, поскольку эти ошибки могут проявляться совсем в другом месте программы и в другое время. Рекомендация: нужно обеспечивать, чтобы программа непреднамеренно не выходила за конец сегмента, и не следует обращаться к слову с адресом 0FFFFh. Машина может "зависнуть".

Библиографический список

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 с.

58

Вопросы для самопроверки

1.Какие шестнадцатеричные значения строки и столбца соответствуют нижнему правому углу экрана 25х40?

2.Напишите команды для установки курсора по координатам: строка 12, столбец 8.

3.Напишите команды для очистки экрана, начиная с 0-го столбца 12-й строки до 79-го столбца 22-й строки.

4.Составьте необходимые элементы данных и команды для вывода запроса 'Введите дату (дд/мм/гг)'. За сообщением должен следовать звуковой сигнал. Используйте для вывода функцию базовой версии DOS.

5.Составьте необходимые элементы данных и команды для ввода с клавиатуры в формате дд/мм/гг. Используйте для ввода функцию базовой версии DOS.

6.Укажите стандартные файловые номера для ввода с клавиатуры, обычного вывода на экран и вывода на принтер.

7.Определите атрибуты экрана:

а) для мигания с подчеркиванием; б) нормальной яркости;

в) инвертирования с выделением яркостью.

8.Составьте процедуру для установки режима экрана BW (черно-белый) на 80 столбцов

9.Составьте процедуру для прокрутки экрана на 10 строк.

10.Понятие «модуль программы».

11.Назначение директив extrn, public и global.

12.Назначение команды and.

13.Назначение команды or.

14.Назначение команды xor.

15.Назначение команды not.

16.Операция «логическое И», электрическая схема, реализующая эту операцию, таблица истинности.

17.Операция «логическое ИЛИ», электрическая схема, реализующая эту операцию, таблица истинности.

18.Операция «исключающее ИЛИ», таблица истинности.

19.Операция «логическое НЕ», таблица истинности.

59

6. ПРИМЕРЫ (ТЕКСТЫ) ПРОГРАММ

Пример 2.1

;l_2a#.asm EXE-программа. Модуль 1-й "Вызов процедуры умножения" ; ред. 05_01_06

extrn submul:far

;-----------------------------------------------

stacksg segment para Stack 'Stack' dw 64 dup (?)

stacksg ends

datasg segment para 'Data'

qty

dw 0002h

;количество

price

dw 0004h

;стоимость

msg

db '0',0dh,0ah,'$'

datasg ends

codesg segment para 'Code'

assume cs:codesg,ds:datasg,ss:stacksg

begin proc far

 

push ds

 

xor ax,ax

 

push ax

 

mov ax,datasg

 

mov ds,ax

 

mov ax,price

;ax:= стоимость (price)

mov bx,qty

;bx:= количество (qty)

call submul

;вызвать подпрограмму submul (внешнюю)

;результат возвращается в регистрах (dx:ax)

;в частном случае (для заданных данных), т.е. когда получается

;одноразрядное десятичное число, предусмотрим вывод результата на экран

or al,30h

; преобразование в код ASCII

mov msg,al

 

mov ah,09h

; вывод на экран полученного значения

lea dx,msg

 

int 21h

 

ret

 

begin endp

 

codesg ends

 

end begin

 

;-----------------------------------------------

 

;l_2b#.asm EXE-программа. Модуль 2-й "Процедура умножения" codesg segment para 'Code'

submul proc far assume cs:codesg public submul

;параметры процедуре передаются в регистрах ax и bx

– первая цифра в номере примера (до точки) обозначает номер части учебного пособия «Языки программирования»

60

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]