datasg ends
codesg segment para ‘Code’ ;сегмент кода
. . . |
|
|
mov |
ax,datasg |
; первый способ |
mov |
es,ax |
|
. . . |
|
|
mov |
ex,[DataSeg] |
; второй способ |
. . .
codesg ends
Чтобы скопировать содержимое одного сегментного регистра (например, cs) в другой сегментный регистр (например, ds), придется передать значение через регистр общего назначения или стек:
mov |
ax,cs |
; способ 1 (передача через регистр общего назначения) |
mov |
ds,ax |
; ds := ax = cs |
или |
|
|
push |
cs |
; способ 2 (передача через стек) |
pop |
ds |
; ds := cs |
копируют содержимое регистра в ds. Первый метод работает быстрее, но при втором методе требуется меньший объем кода.
При работе с инструкцией mov имеются ограничения, когда дело касается сегментных регистров. В операциях сложения, вычитания, логических операциях или сравнениях использовать cегментные регистры нельзя. Но заносить содержимое сегментных регистров в стек и перемещать из стека в сегментные регистры – можно.
4.71. Перемещение данных в стек и из стека
На вершину стека всегда указывает регистр sp. Для обращения к данным в стеке с использованием режимов адресации памяти, при которых указателем базы является регистр bp, можно использовать инструкцию mov. Например, инструкция
mov ax,[bp+4]
загружает регистр ax содержимым слова в сегменте стека со смещением bp+4.
Однако чаще к стеку обращаются с помощью инструкций push и pop. Инструкция push сохраняет операнд в вершине стека, а инструкция pop извлекает значение из вершины стека и сохраняет его в операнде. Например, инструкции:
mov ax,1 push ax pop bx
76
заносят значение (равное 1) из регистра ax в вершину стека, затем извлекают 1 из вершины стека и сохраняют ее в bx.
5.ТИПИЧНЫЕ ОШИБКИ ПРИ ВЫПОЛНЕНИИ РАБОТЫ
5.1.Перечень часто встречающихся ошибок
Вперечень таких ошибок входят:
-открытая процедура или открытый сегмент;
-программист забывает о возврате в DOS;
-программист забывает о стеке или резервирует маленький стек;
-вызывает процедуру, которая портит содержимое нужных регистров;
-неопределенные символические имена;
-повторное определение символического имени;
-неправильный порядок операндов;
-неправильное использование операндов;
-потеря содержимого регистра при умножении;
-не подготовлены регистры при делении;
-неправильное использование регистра после деления;
-потеря содержимого регистра при делении;
-неправильное использование регистров при преобразовании байта в слово и слова в двойное слово в командах cbw и cwd;
-применение команд преобразования cbw и cwd к беззнаковым данным;
-изменение отдельными инструкциями флага переноса;
-программист долго не использует состояние флагов;
-ошибки при использовании регистров:
а) 8- и 16-разрядный регистры для операндов одной команды; б) 8-разрядный регистр указывается для работы со стеком;
в) сегментные регистры прямо используются в арифметических вычислениях, логических операциях или для непосредственной передачи данных;
г) сегментные регистры могут использоваться либо как источники операндов, либо как приемники операндов, но никак не одновременно;
- выход из диапазона адресов.
В каждом языке имеется свое множество ошибок, которые обычно очень легко сделать, но не всегда просто обнаружить. Не является исключением и язык ассемблера. Типичные ошибки, которые допускаются при программировании на ассемблере, и рекомендации, как можно их избежать, приведены ниже. Источники данной информации: [1], [4], [5], [6].
77
5.2. Открытая процедура или открытый сегмент [5, с.120-121]
Для каждой директивы proc должна быть записана соответствующая директива endp. Если директива endp пропущена или имя процедуры в записи endp записано неверно, ассемблер генерирует предупреждающее сообщение, что процедура не закрыта надлежащим образом. Точно так же директива ends должна быть задана для каждой директивы segment. Если утверждение ends пропущено или имя сегмента в нем указано неверно, генерируется предупреждающее сообщение, что сегмент открыт. Наиболее вероятной причиной ошибки является неверная запись имени сегмента или процедуры.
sample segment |
; Неверные записи |
|
proc1proc near |
|
|
and al,00 |
; а) нет соответствующей директивы endp для |
|
ret |
; |
процедуры proc1 |
; |
(имя proc1 в записи endp указано неверно) |
|
proc |
endp |
|
proc2 |
proc near |
; б) нет соответствующей директивы ends |
or |
al,0ffh |
|
ret |
|
|
proc2 endp end
Данный вариант является правильной записью предыдущего примера: sample segment
proc1proc near and al,00 ret
proc1 endp proc2 proc near
or al,0ffh ret
proc2 endp sample ends
end
5.3.Программист забывает о возврате в DOS
ВПаскале, Си и других языках программа завершается и возвращается в операционную систему DOS автоматически, когда нет больше выполняемого кода, даже если в программе отсутствует явная команда ее завершения. В языке ассемблера это не так. Ассемблер выполняет только те действия, которые вы явно указываете. Когда вы
78
запускаете программу, в которой отсутствует команда возврата в DOS, она просто продолжает работать до конца выполняемого кода программы и переходит в код, который находится в примыкающей памяти. Например:
codesg segment para ‘Code’ ;сегмент кода
DoNothing proc near nop
DoNothing endp codesg ends
end DoNothing
Выполняемый код, сгенерированный при ассемблировании и компоновке данной программы, состоит только из отдельной инструкции nop. В ассемблере директива endp (как и все другие директивы) не генерирует кода, она просто уведомляет ассемблер, что код для процедуры DoNothing закончился, директива ends просто уведомляет ассемблер об окончании сегмента codesg, а директива end DoNothing – о том, что код данного модуля закончился и программа должна начать выполнение с метки DoNothing. Нигде в выполняемом коде не содержится инструкции для передачи управления обратно в операционную систему DOS, когда программа закончится. В результате, когда программа будет запущена, после инструкции nop будут выполняться инструкции, которые случайно окажутся в памяти непосредственно за nop. В этой точке управление будет потеряно, и для возврата в операционную систему DOS может потребоваться программная или аппаратная перезагрузка. Имеется несколько вариантов возврата в DOS. Правильно завершать работу будет, например, следующая версия предыдущей программы, использующая
функцию 4Ch: |
|
|
|
codesg segment para |
‘Code’ |
;сегмент кода |
|
DoNothing proc near |
|
|
|
nop |
|
|
|
mov |
ah,4Ch |
; функция DOS завершения процесса |
|
int |
21h |
; вызвать DOS для завершения программы |
|
DoNothing endp |
|
|
|
codesg ends |
|
|
|
end |
DoNothing |
|
|
5.4.Программист забывает о стеке или резервирует маленький стек
Вбольшинстве программ для резервирования пространства для стека должна присутствовать директива определения сегмента стека (стандартная или упрощенная) и должно быть зарезервировано достаточное пространство, чтобы его хватило для максимальных потребностей в программе (например, директивой db).
79
Ошибки, которые возникают, когда увеличивающийся стек переходит в другие части программы и портит их, обычно бывает трудно воспроизводить и отслеживать. Кроме того, многие отладчики для возврата управления из программы используют небольшое дополнительное пространство в стеке. Поэтому не следует скупиться при выделении пространства для стека. Это избавит вас от многих возможных неприятностей. Обычно выделяют стек размером 512 байтов.
Не требуется выделять стек в программах, которые предполагается преобразовать в файлы типа .com. Файлы .com выполняются со стеком, расположенным в самой вершине программного сегмента (который имеет размер 64 Кб или меньше, если доступно меньше 64 Кб), поэтому максимальный размер стека в этом случае просто равен объему памяти, оставшейся в программном сегменте. При написании программ в формате
.com следует иметь это в виду, так как при увеличении программы соответственно уменьшается стек. Нужно также учитывать, что при работе больших программ в формате .com, выполняемых на компьютерах с небольшой доступной памятью или запущенных из операционной среды DOS наряду с другими программами, могут возникнуть проблемы со стеком. Простейший способ избежать этих потенциальных проблем состоит в написании программ не в формате .com, а в формате .exe с резервированием стека большого объема.
5.5. Вызов подпрограммы, которая портит содержимое нужных регистров
При разработке программы на ассемблере нередко подразумевают, что при обращении к другим процедурам регистры остаются неизмененными. На самом деле это не так. Каждая процедура может сохранить или уничтожить содержимое любого из регистров. Рассмотрим следующий пример. Пусть требуется произвести вычисление по формуле
1000 + w1/10. |
|
|
|
datasg segment para |
‘Data’ |
;сегмент данных |
|
w1 dw 200 |
|
|
|
datasg ends |
|
|
|
codesg segment para |
‘Code’ |
;сегмент кода |
|
. . . |
|
|
|
mov |
bx,1000 |
|
; bx :=1000 |
mov |
ax,w1 |
|
; ax:=w1=200 |
call DivideBy10 |
|
; разделить элемент на 10 |
|
add bx,ax |
|
; вычисление суммы |
|
;(в данном случае получится bx:=bx+ax=10+w1/10), т.е. не правильно
;правильный результат должен быть1000 + w1/10.
80