
Include macro.Inc
.MODEL small
.STACK 100h
.DATA
msg DB "Hello!",CRLFT
.CODE
start: mov ax,@data
mov ds,ax
message msg
exit
END start
Она стала компактнее и нагляднее.
Упражнение. Изучите листинг и расширенный листинг для этого файла (для получения расширенного листинга используется ключ не /l, а /la).
11.7. Локальные метки.
Определим макрос
do_dec MACRO
jcxz skip
dec cx
skip:
ENDM
Если использовать такой макрос дважды, то после обработки текста программы препроцессором в программе будет содержаться две метки skip, что вызовет сообщение об ошибке (какое?).
Исправим макрос
do_dec MACRO
LOCAL skip
jcxz skip
dec cx
skip:
ENDM
Метка skipобъявлена локальной. Теперь при каждом использовании макроса будут генерироваться метки вида ??XXXX, гдеXXXX— 16-ричное число от 0 доFFFF.
Упражнение. Убедитесь в этом.
Директива LOCALимеет формат:LOCALсписок_меток.
Задача. Определите макросstarsn, который выводит на экран строкуnзвездочек (и заканчивает ее символами ВК и ПС). Напишите программу с использованием этого макроса. Программа должна выводить строки:
***
Hello
*****
Для вывода символа воспользуйтесь функцией 02 прерывания int21h(в регистреDL— код выводимого символа). Для этой функции создайте макросout_sym(в нем вызовитеDOS02h). Таким образом у вас получится три уровня вложенности макросов. Звездочки выводите в цикле.
11.8. Директива оптимизации переходов
Рассмотрим программный фрагмент:
jz m
REPT 130
inc ax
ENDM
m: dec ax
С помощью директивы REPT(REPeaT — повторять) в текст программы 130 раз помещается командаinc ax. Ее код занимает один байт — всего 130 байтов. Окружите этот фрагмент нужными директивами и командами и воспроизведите этот пример. При трансляции этой программы получим сообщение об ошибке
**Error** Relative jump out of range by 0003h bytes
(переход превышает допустимый диапазон на 3 байта)
Ранее мы познакомились с приемом, позволяющем избежать этой ошибки. Но в большой программе весьма утомительно изменять команды перехода. Хотелось бы поручить это Ассемблеру — и это возможно. Поставьте первой строкой в файле с программой директиву JUMPS. В Turbo Debugger Вы увидите сочетание командjne/jmp. Проблема решена!
Но не до конца. Замените REPT 130наREPT 30. В отладчике Вы увидите
jz m
nop
nop
nop
inc ax
. . .
Так как jz m— переход "вперед", TASM при трансляции этой команды еще не знает, понадобится ли команда ближнего перехода или нет, и на всякий случай резервирует для нее три байта. Потом выясняется, что меткаmв пределах достижимости для командыjz m, и эти три байта заполняются командамиnop. Размер кода программы без нужды увеличен. Чтобы этого избежать, используйте при трансляции ключ/m— многократные проходы по тексту программы. Тогда транслятор уберет паразитные команды.
11.9. Пример. Вычисление по формуле.
Переделаем второе задание — вычисление по формуле. (Интересно посмотреть, какие новые моменты мы можем привнести в программу в свете изученного материала). Во-первых, вычисление по формуле выделим в подпрограмму, во-вторых, вызов программы организуем с использованием макроса. Наконец, будем обрабатывать ошибку переполнения по сложению при вычислении знаменателя и ошибку при делении.
Немного изменим исходные данные для формулы. Теперь x не байт, а слово (чтобы проиллюстрировать переполнение при сложении).
У нас будет несколько тестовых наборов. Исходные данные — x,y,z. Результат — v и err. Если в err записан 0, то в v — правильный ответ. Но если в err помещено 1, то произошло переполнение при делении, а если 2 — переполнение при сложении.
Прерывание при ошибке деления принадлежит к категории исключений, поэтому начиная с 286 процессора в стек записывается не адрес следующей команды, а адрес команды, на которой произошло прерывание, чтобы можно было произвести рестарт ошибочной команды. Мы рестартовать idiv, конечно не будем, а скорректируем в стеке содержимое IP, чтобы перейти к команде, следующей за командой деления.
Так как мы напишем свой обработчик прерывания, освоим две функции DOS для установки вектора прерывания и его запоминания (чтобы восстанавливать его в конце программы).
получить вектор прерывания
вход: AH = 35h
AL – номер прерывания
выход: ES:BX — адрес ISR
установить вектор прерывания
вход: AH = 25h
AL – номер прерывания
DS:DX — адрес ISR
выход: нет
Файл formula.asm
INCLUDE macro.inc
; Макрос для вызова
eval MACRO x,y,z,v,errn
LOCAL c,w
mov ax,x
mov bx,y
mov cl,z
call formula
jnc c ;; нет ошибки - на c
mov errn,al ;; запомнить код ошибки
jmp short w
c: mov v,ax
w:
ENDM
; вычисление по формуле
.MODEL small
.STACK 100h
.DATA
; старый вектор прерывания по ошибке деления
ip_old DW ?
cs_old DW ?
; индикатор ошибки при делении
idiv_err DB ?
; первый набор
x1 DW 7FFFh ; x+3 даст переполнение при сложении
y1 DW 0h
z1 DB 0h
v1 DW 0
err1 DB 0
; второй набор
x2 DW -3h ; Знаменатель x+3 равен нулю
y2 DW 1h
z2 DB 2h
v2 DW 0
err2 DB 0
; третий набор
x3 DW 1h
y3 DW -3h
z3 DB 4h
v3 DW 0
err3 DB 0
; четвёртый набор
x4 DW 7Dh
y4 DW 6DB7h
z4 DB -6h
v4 DW 0
err4 DB 0
.CODE
; Подпрограмма вычисления по формуле
;
; v = (y(z-1)+1)/(x+3)+1
; вход : AX = x
; BX = y
; CL = z
; выход: CF=0 v = AX
; CF=1 AX = 1 - переполнение при делении
; AX = 2 - переполнение при сложении
formula PROC
push dx ; сохранение используемого регистра
push ax
mov idiv_err,0 ; сброс индикатора ошибки
mov al,cl ; z в AL
cbw
dec ax ; z-1
imul bx ; y(z-1)
add ax,1
adc dx,0 ; y(z-1)+1
mov bx,ax ; сохранить младшее слово числителя
pop ax ; восстановить x
add ax,3 ; x+3
jo int_overflow
xchg ax,bx ; восстановить числитель, знаменатель в BX
idiv bx ; (y(z-1)+1)
cmp idiv_err,1 ; индикатор ошибки установлен?
je idiv_overflow
inc ax ; окончательный результат
clc ; нормальное завершение
pop dx ; восстановить используемый регистр
ret
int_overflow:
stc ; ненормальное завершение
mov ax,2 ; признак переполнения при сложении/вычитании
pop dx
ret
idiv_overflow:
stc
mov ax,1; признак переполнения при делении
pop dx
ret
formula ENDP
;
; обработчик прерывания при ошибке деления
divide_overflow PROC
sti ; Разрешить внешние прерывания
mov idiv_err,1 ; Установить индикатор ошибки
mov bp,sp ; В BP - адрес IP, сохраненного в стеке,
; это адрес команды idiv bx (код F7 FB занимает два байта)
add word ptr [bp], 2 ; теперь IP показывает на следующую команду
iret
divide_overflow ENDP
start: mov ax,@data
mov ds,ax
; Получить вектор прерывания для ошибки при делении (тип 0)
mov al,0
DOS 35h
mov ip_old,bx ; Сохранение
mov cs_old,es ; вектора
; Установить новый вектор прерывания
mov bx,ds ; сохранить DS
mov al,0
mov dx, SEG divide_overflow
mov ds,dx
mov dx, OFFSET divide_overflow
DOS 25h
mov ds,bx; Восстановить DS
; Первый набор
eval x1,y1,z1,v1,err1
; Второй набор
eval x2,y2,z2,v2,err2
; Третий набор
eval x3,y3,z3,v3,err3
; Четвёртый набор
eval x4,y4,z4,v4,err4
; Восстановить вектор прерывания
mov al,0
mov dx,ip_old
mov ds,cs_old
DOS 25h
;
exit
END start
Возникает вопрос, как в TD отлаживать процедуру обработки прерывания по ошибке деления? Если выполнять программу пошагово (нажимая F7), то в эту процедуру мы не попадем. В процедуре надо сделать точку останова (Breakpoint). Поставьте курсор на команду sti и нажмите клавишу F2. Строка будет выделена красным цветом. (Повторное нажатие на F2 отменит точку останова.) Запустите программу на выполнение (F9). Произойдет останов на команде sti. Далее можно изучать работу программы с помощью пошагового выполнения. (Впрочем, можно было обойтись и "старыми запасами": поставить курсор на sti и нажать F4.)