Оригинальный DVD-ROM: eXeL@B DVD !
eXeL@B ВИДЕОКУРС !

ВИДЕОКУРС ВЗЛОМ
обновлён 2 декабря!


УЗНАТЬ БОЛЬШЕ >>
Домой | Статьи | RAR-cтатьи | FAQ | Форум | Скачать | Видеокурс
Новичку | Ссылки | Программирование | Интервью | Архив | Связь

Ассемблер для крэкера

Под редакцией Еpшова В.Г.
для прогона листа, перевода строки или горизонтальной  табуляции.  В  свою
очередь, процессор должен "понимать" сигналы от принтера,  указывающие  на
конец бумаги или состояние "занято".
     К сожалению многие типы принтеров по  разному  реагируют  на  сигналы
процессора и  одной  из  наиболее  сложных  проблем  для  программистов  -
обеспечить  соответствие  собственных  программ    имеющимся    печатающим
устройством.


     СИМВОЛЫ УПРАВЛЕНИЯ ПЕЧАТЬЮ
     ________________________________________________________________

     Стандартными символами управления печатью являются следующие:

               Десятичн.    Шест.          Назначение

                  08         08       Возврат на шаг
                  09         09       Горизонтальная табуляция
                  10         0A       Перевод строки
                  11         0B       Вертикальная табуляция
                  12         0C       Прогон страницы
                  13         0D       Возврат каретки

     Г о р и з о н т а л ь н а я     т а б у л я ц и я.     Горизонтальная
табуляция (шест. 09) возможна только на принтерах, имеющих соответствующее
обеспечение, иначе символы  табуляции  игнорируются.  В  последнем  случае
можно имитировать табуляцию выводом соответствующего числа пробелов.

     П е р е в о д  с т р о к и.    Символ   перевода   строки   (шест.OA)
используется для прогона листа на один интервал. Соответственно для печати
через два интервала используется два символа перевода строки.

     П р о г о н  с т р а н и ц ы.   Установка  бумаги   после   включения
принтера определяет начальную позицию печати страницы.  Длина страницы  по
умолчанию составляет 11 дюймов.  Ни процессор, ни принтер автоматически не
определяют конец страницы.  Если ваша программа продолжает печатать  после
конца страницы, то произойдет переход через межстраничную перфорацию на на
чало следующей страницы. Для управления страницами необходимо подсчитывать
число   напечатанных   строк   и  при  достижении  максимального  значения
(например, 55 строк) выдать  код  прогона  страницы  (шест.0C)  и,  затем,
сбросить счетчик строк в 0 или 1.
     В  конце  печати  необходимо  выдать  символ  "перевода  строки"  или
"прогона  страницы"  для  вывода  на  печать  данные  последней    строки,
находящиеся в  буфере  печатающего  устройства.  Использование  последнего
символа "прогон страницы" позволяет установить напечатанный последний лист
в положение для отрыва.


     ФУНКЦИИ ПЕЧАТИ В РАСШИРЕННОЙ ВЕРСИИ DOS
     ________________________________________________________________

     В операционной системе DOS 2.0 имеются  файловые  указатели,  которые
были  показаны  в  главах по управлению экраном дисплея и дисковой печати.
Для вывода на печатающее устройство используется  функция  DOS  шест.40  и
стандартный  файловый  номер 04.  Следующий пример демонстрирует печать 25
символов из области HEADG:

               HEADG     DB   'Industrial Bicycle Mfrs', 0DH, 0AH
                         ...
                         MOV  AH,40H    ;Запрос печати
                         MOV  BX,04     ;Файловый номер принтера
                         MOV  CX,25     ;25 символов
                         LEA  DX,HEADG  ;Область вывода
                         INT  21H       ;Вызов DOS

     В случае ошибки операция  устанавливает  флаг  CF  и  возвращает  код
ошибки в регистре AX.


     ПРОГРАММА: ПОСТРАНИЧНАЯ ПЕЧАТЬ С ЗАГОЛОВКАМИ
     ________________________________________________________________

__________________________________________________________________________

TITLE   PRTNAME (COM)   Ввод и печать имен
CODESG  SEGMENT PARA PUBLIC 'CODE'
        ASSUME  CS:CODESG,DS:CODESG,SS:CODESG,ES:CODESG
        ORG     100H
BEGIN:  JMP     SHORT MAIN
; -----------------------------------------------------
NAMEPAR LABEL   BYTE            ;Список параметров
MAXNLEN DB      20              ; максимальная длина имени
NAMELEN DB      ?               ; длина введенного имени
NAMEFLD DB      20 DUP(' ')     ; введенное имя
                                ;Строка заголовка:
HEADG   DB      'List of Employee Names    Page '
PAGECTR DB      '01',0AH,0AH
FFEED   DB      0CH             ;Перевод страницы
LFEED   DB      0AH             ;Перевод строки
LINECTR DB      01
PROMPT  DB      'Name? '
; -----------------------------------------------------
MAIN    PROC    NEAR
        CALL    Q10CLR          ;Очистить экран
        CALL    M10PAGE         ;Установка номера страницы
A2LOOP:
        MOV     DX,0000         ;Установить курсор в 00,00
        CALL    Q20CURS
        CALL    D10INPT         ;Ввести имя
        CALL    Q10CLR
        CMP     NAMELEN,00      ;Имя введено?
        JE      A30             ; если нет - выйти,
        CALL    E10PRNT         ; если да - подготовить
                                ;  печать
        JMP     A20LOOP
A30:
        MOV     CX,01           ;Конец работы:
        LEA     DX,FFEED        ; один символ
        CALL    P10OUT          ; для прогона страницы,
        RET                     ; возврат в DOS
MAIN    ENDP
;               Ввод имени с клавиатуры:
;               -----------------------
D10INPT PROC    NEAR
        MOV     AH,40H          ;Функция
        MOV     BX,01           ; вывода на экран
        MOV     CX,05           ; 5 символов
        LEA     DX,PROMPT
        INT     21H             ;Вызов DOS
        MOV     AH,0AH          ;Функция ввода с клавиатуры
        LEA     DX,NAMEPAR
        INT     21H             ;Вызов DOS
        RET
D10INPT ENDP
;               Подготовка для печати:
;               ----------------------
E10PRNT PROC    NEAR
        CMP     LINECTR,60      ;Конец страницы?
        JB      E20             ; нет - обойти
        CALL    M10PAGE         ; да - печатать заголовок
E20:    MOV     CH,00
        MOV     CL,NAMELEN      ;Число символов в имени
        LEA     DX,NAMEFLD      ;Адрес имени
        CALL    P10OUT          ;Печатать имя
        MOV     CX,01           ;Один
        LEA     DX,LFEED        ; перевод строки
        CALL    P10OUT
        INC     LINECTR         ;Увеличить счетчик строк
E10PRNT ENDP
;               Подпрограмма печати заголовка:
;               -----------------------------
M10PAGE PROC    NEAR
        CMP     WORD PTR PAGECTR,3130H  ;Первая страница?
        JE      M30             ; да - обойти
        MOV     CX,01           ;
        LEA     DX,FFEED        ; нет --
        CALL    P10OUT          ;  перевести страницу,
        MOV     LINECTR,03      ;  установить счетчик строк
M30:
        MOV     CX,36           ;Длина заголовка
        LEA     DX,HEADG        ;Адрес заголовка
M40:
        CALL    P10OUT
        INC     PAGECTR+1       ;Увеличить счетчик страниц
        CMP     PAGECTR+1,3AH   ;Номер страницы = шест.xx3A?
        JNE     M50             ; нет - обойти,
        MOV     PAGECTR+1,30H   ; да - перевести в ASCII
        INC     PAGECTR
M50:    RET
M10PAGE ENDP
;               Подпрограмма печати:
;               -------------------
P10OUT  PROC    NEAR            ;CX и DX установлены
        MOV     AH,40H          ;Функция печати
        MOV     BX,04           ;Номер устройства
        INT     21H             ;Вызов DOS
        RET
P10OUT  ENDP
;               Очистка экрана:
;               --------------
Q10CLR  PROC    NEAR
        MOV     AX,0600H        ;Функция прокрутки
        MOV     BH,60H          ;Цвет (07 для ч/б)
        MOV     CX,0000         ;От 00,00
        MOV     DX,184FH        ; до 24,79
        INT     10H             ;Вызов BIOS
        RET
Q10CLR  ENDP
;               Установка курсора (строка/столбец):
;               ----------------------------------
Q20CURS PROC    NEAR            ;DX уже установлен
        MOV     AH,02           ;Функция установки курсора
        MOV     BH,00           ;Страница № 0
        INT     10H             ;Вызов BIOS
        RET
Q20CURS ENDP
CODESG  ENDS
        END     BEGIN
__________________________________________________________________________

     Рис.19.1. Постраничная печать с заголовком.


     Программа, приведенная на рис.19.1, аналогична программе на  рис.9.1,
за исключением того, что после ввода имен с клавиатуры выводит  их  не  на
экран, а на печатающее устройство.  Каждая напечатанная страница  содержит
заголовок и через двойной интервал список введенных имен в следующем виде:

               List of Employee Names   Page 01
               Clancy Alderson
               Ianet Brown
               David Christie
               ...

     Программа подсчитывает число  напечатанных  строк  и  при  достижении
конца страницы выполняет прогон до начала следующей страницы.  В программе
имеются процедуры:

D10INPT   Выдает на экран запрос и затем вводит имя с клавиатуры.
E10PRNT   Выводит имя  на  печатающее  устройство  (длина имени берется из
          вводного списка параметров); в конце страницы вызывает процедуру
          M10PAGE.
M10PAGE   Выполняет  прогон  на  новую   страницу,   печатает   заголовок,
          сбрасывает счетчик  строк  и  увеличивает  счетчик  страниц   на
          единицу.
P100UT    Общая подпрограмма для непосредственного  вывода  на  печатающее
          устройство.

     В начале выполнения необходимо напечатать  заголовок,  но  не  делать
перед этим перевод страницы.  Поэтому процедура  M10PAGE  обходит  перевод
страницы, если счетчик PAGECTR  содержит  01  (начальное  значение).  Поле
PAGECTR определено как

               PAGECTR  DB  '01'

     В начале выполнения необходимо напечатать  заголовок,  но  не  делать
перед этим перевод страницы.  Поэтому процедура  M10PAGE  обходит  перевод
страницы, если счетчик PAGECTR  содержит  01  (начальное  значение).  Поле
PAGECTR определено как

               PAGECTR   DB   '01'

В результате будет сгенерировано число в ASCII-коде - шест.3031. Процедура
M10PAGE увеличивает счетчик PAGECTR на  1  так,  что  значение  становится
последовательно 3032,  3033 и т.д.  Эти значения корректны до 3039,  далее
следует 303A,  что будет распечатано,  как двоеточие (:).  Поэтому, если в
правом  байте поля PAGECTR появляется шест.3A,  то это значение заменяется
на шест.30, а к левому байту прибавляется единица. Таким образом шест.303A
перекодируется в шест.3130, т.е. в 10 в символьном представлении.
     Проверка на конец страницы до (но не после) печати имени гарантирует,
что на последней странице будет напечатано по крайней мере  одно  имя  под
заголовком.


     ПЕЧАТЬ ASCII-ФАЙЛОВ И ТАБУЛЯЦИЯ
     ________________________________________________________________

     Табуляция, обеспечиваемая, например, видеоадаптерами,  заключается  в
замене одного символа табуляции (код 09) несколькими пробелами при  выводе
так, чтобы следующая позиция была кратна  8.  Таким  образом,  стандартные
позиции табуляции являются 8, 16,  24  и  т.д.  Многие  принтеры,  однако,
игнорируют символы табуляции.  Поэтому, такая программа,  как  DOS  PRINT,
предназначенная для печати ASCII файлов  (например  ассемблерных  исходных
текстов)  проверяет  каждый  символ,  посылаемый  на  принтер.  И,    если
обнаруживается символ табуляции, то программа выдает несколько пробелов до
позиции кратной 8.
     Программа, приведенная на рис.19.2, выводит на экран запрос  на  ввод
имени файла и, затем, печатает содержимое указанного файла.  Эта программа
в отличие от приведенной на рис.17.3 (вывод файлов на экран)  осуществляет
замену выводимых символов табуляции на соответствующее число  пробелов.  В
результате символ табуляции в позициях от 0 до 7 приводит  к  переходу  на
позицию 8, от 8 до 15 - на 16 и т.д.  Команды, реализующие данную  логику,
находятся в процедуре G10XFER после  метки  G60.  Рассмотрим  три  примера
обработки символа табуляции:

          Текущая позиция печати:        1         9       21
          Двоичное значение:          00000001 00001001 00010101
          Очистка трех правых битов:  00000000 00001000 00010000
          Прибавление 8:              00001000 00010000 00011000
          Новая позиция:                 8        16       24

     В программе организованы следующие процедуры:

С10PRMP   Запрашивает ввод имени  файла.  Нажатие  только  клавиши  Return
          приводит к завершению работы программы.
E10OPEN   Открывает дисковый файл по указанному имени.
G10XFER   Контролирует конец сектора,  конец файла,  конец области вывода,
          символы "перевод строки" и табуляции. Пересылает обычные символы
          в область вывода.
P10PRNT   Распечатывает выводную строку и очищает область вывода.
R10READ   Считывает сектор из дискового файла.

     Коды  "возврат  каретки",  "перевод  строки"  и  "прогон    страницы"
действительны для любых  принтеров.  Можно  модифицировать  программу  для
подсчета распечатываемых строк и выполнения прогона страницы (шест.OC) при
достижении, например, строки 62.
     Некоторые пользователи  предпочитают  устанавливать  символы  "прогон
страницы" в ASCII файлах  с  помощью  текстового  редактора  в  конкретных
местах текста, например, в конце ассемблерных процедур.  Кроме того, можно
изменить  программу  для  функции  05  базовой  версии  DOS.  Эта  функция
выполняет вывод каждого символа непосредственно на принтер.  Таким образом
можно исключить определение и использование области вывода.

__________________________________________________________________________

TITLE   PRINASK (COM)   Чтение и печать дисковых записей
CODESG  SEGMENT PARA 'Code'
        ASSUME  CS:CODESG,DS:CODESG,SS:CODESG,ES:CODESG
        ORG     100H
BEGIN   JMP     MAIN
; ---------------------------------------------------------
PATHPAR LABEL   BYTE            ;Список параметров для
MAXLEN  DB      32              ; ввода
NAMELEN DB      ?               ; имени файла
FILENAM DB      32 DUP(' ')
SECTOR  DB      512 DUP(' ')    ;Область ввода для файла
DISAREA DB      120 DUP(' ')    ;Область вывода
COUNT   DW      00
ENDCDE  DW      00
FFEED   DB      0CH
HANDLE  DW      0
OPENMSG DB      '*** Open error ***'
PROMPT  DB      'Name of file? '
; ----------------------------------------------------------
MAIN    PROC    NEAR            ;Основная программа
        CALL    Q10SCR          ;Очистить экран
        CALL    Q20CURS         ;Установить курсор
A10LOOP:
        MOV     ENDCDE,00       ;Начальная установка
        CALL    C10PRMP         ;Получить имя файла
        CMP     NAMELEN,00      ;Есть запрос?
        JE      A90             ; нет - выйти
        CALL    E10OPEN         ;Открыть файл,
                                ; установить DTA
        CMP     ENDCDE,00       ;Ошибка при открытии?
        JNE     A80             ; да - повторить запрос
        CALL    R10READ         ;Прочитать первый сектор
        CMP     ENDCDE,00       ;Конец файла, нет данных?
        JE      A80             ; да - повторить запрос
        CALL    G10XPER         ;Распечатать сектор
A80:
        JMP     A10LOOP
A90:    RET
MAIN    ENDP
;               Подпрограмма запроса имени файла:
;               --------------------------------
C10PRMP PROC    NEAR
        MOV     AH,40H          ;Функция вывода на экран
        MOV     BX,01
        MOV     CX,13
        LEA     DX,PROMPT
        INT     21H
        MOV     AH,0AH          ;Функция ввода с клавиатуры
        LEA     DX,PATHPAR
        INT     21H
        MOV     BL,NAMELEN      ;Записать
        MOV     BH,00           ; 00 в конец
        MOV     FILENAM[BX],0   ; имени файла
C90     RET
C10PRMP ENDP
;               Открытие дискового файла:
;               ------------------------
E10OPEN PROC    NEAR
        MOV     AH,3DH          ;Функция открытия
        MOV     AL,00           ;Только чтение
        LEA     DX,FILENAM
        INT     21H
        JNC     E20             ;Проверить флаг CF
        CALL    X10ERR          ; ошибка, если установлен
        RET
E20:
        MOV     HANDLE,AX       ;Сохранить номер файла
        MOV     AX,2020H
        MOV     CX,256          ;Очистить пробелами
        REP     STOSW           ; область сектора
        RET
E100PEN ENDP
;               Подготовка и печать данных:
;               --------------------------
G10XFER PROC    NEAR
        CLD                     ;Направление слева-направо
        LEA     SI,SECTOR       ;Начальная установка
G20:
        LEA     DI,DISAREA
        MOV     COUNT,00
G30:
        LEA     DX,SECTOR+512
        CMP     SI,DX           ;Конец сектора?
        JNE     G40
        CALL    R10READ         ; да - читать следующий
        CMP     ENDCDE,00       ;Конец файла?
        JE      G80             ; да - выйти
        LEA     SI,SECTOR
G40:
        MOV     BX,COUNT
        CMP     BX,80           ;Конец области вывода?
        JB      G50             ; нет - обойти
        MOV     [DI+BX],0D0AH   ; да - записать CR/LF
        CALL    P10PRNT
        LEA     DI,DISAREA      ;Начало области вывода
G50:
        LODSB                   ;Записать [SI] в AL,
                                ; увеличить SI
        MOV     BX,COUNT
        MOV     [DI+BX],AL      ;Записать символ
        INC     BX
        CMP     AL,1AH          ;Конец файла?
        JE      G80             ; да - выйти
        CMP     AL,0AH          ;Конец строки?
        JNE     G60             ; нет - обойти,
        CALL    P10PRNT         ; да - печатать
        JMP     G20
G60:
        CMP     AL,09H          ;Символ табуляции?
        JNE     G70
        DEC     BX              ; да - установить BX:
        MOV     BYTE PTR [DI+BX],20H  ;Заменит TAB на пробел
        AND     BX,0FFF8H       ;Обнулить правые 8 бит
        ADD     BX,08           ; и прибавить 8
G70:
        MOV     COUNT,BX
        JMP     G30
G80:    MOV     BX,COUNT        ;Конец файла
        MOV     BYTE PTR [DI+BX],0CH    ;Прогон страницы
        CALL    P10PRNT         ;Печатать последнюю строку
G90:    RET
G10XFER ENDP
;               Подпрограммы печати:
;               -------------------
P10PRNT PROC    NEAR
        MOV     AH,40H          ;Функция печати
        MOV     BX,04
        MOV     CX,COUNT        ;Длина
        INC     CX
        LEA     DX,DISAREA
        INT     21H
        MOV     AX,2020H        ;Очистить область вывода
        MOV     CX,60
        LEA     DI,DISAREA
        REP     STOSW
        RET
P10PRNT ENDP
;               Подпрограмма чтения сектора:
;               ---------------------------
R10READ PROC    NEAR
        MOV     AH,3FH          ;Функция чтения
        MOV     BX,HANDLE       ;Номер файла
        MOV     CX,512          ;Длина
        MOV     DX,SECTOR       ;Буфер
        INT     21H
        MOV     ENDCDE,AX
        RET
R10READ ENDP
;               Прокрутка экрана:
;               ----------------
Q10SCR  PROC    NEAR
        MOV     AX,0600H
        MOV     BH,1EH          ;Установить цвет
        MOV     CX,0000         ;Прокрутка (сскроллинг)
        MOV     DX,184FH
        INT     10H
        RET
Q10SCR  ENDP
;               Подпрограмма установки курсора:
;               ------------------------------
Q20CURS PROC    NEAR
        MOV     AH,02           ;Функция установки
        MOV     BH,00           ; курсора
        MOV     DX,00
        INT     10H
        RET
Q20CURS ENDP
;               Вывод сообщения об ошибке:
;               -------------------------
X10ERR  PROC    NEAR
        MOV     AH,40H          ;Функция вывода на экран
        MOV     BX,01           ;Номер
        MOV     CX,18           ;Длина
        LEA     DX,OPENMSG      ;Адрес сообщения
        INT     1H
        MOV     NDCDE,01        ;Признак ошибки
        RET
X10ERR  ENDP
CODESG  ENDS
        END     BEGIN
__________________________________________________________________________

     Рис.19.2. Печать ASCII файла.


     ПЕЧАТЬ ПОД УПРАВЛЕНИЕМ БАЗОВОЙ DOS
     ________________________________________________________________

     Для печати в базовой версии DOS необходимо установить в  регистре  AH
код функции 05, а в регистр DL поместить распечатываемый символ и,  затем,
выполнить команду INT 21H следующим образом:

               MOV  AH,05     ;Запрос функции печати
               MOV  DL,char   ;Распечатываемый символ
               INT  21H       ;Вызов DOS

С помощью этих команд можно передавать  на  принтер  управляющие  символы.
Однако, печать, обычно, предполагает вывод  полной  или  частичной  строки
текста и пошаговую обработку области данных, отформатированной по строкам.
     Ниже показана  программа  печати полной строки.  Сначала в регистр SI
загружается начальный адрес области HEADG,  а в регистр CX  -  длина  этой
области.  Цикл,  начинающийся  по метке P20,  выделяет очередной символ из
области HEADG и посылает его на принтер.  Так как  первый  символ  области
HEADG  -  "прогон  страницы",  а  последние  два  -  "перевод строки",  то
заголовок печатается в начале новой страницы и после него следует  двойной
интервал.

          HEADG  DB   0CH,'Industrial Bicycle Mfrs',0DH,0AH,0AH
                 LEA  SI,HEADG       ;Установка адреса и
                 MOV  CX,27          ; длины заголовка
          P20:
                 MOV  AH,05          ;Запрос функции печати
                 MOV  DL,[SI]        ;Символ из заголовка
                 INT  21H            ;Вызов DOS
                 INC  SI             ;Следующий символ
                 LOOP P20

     Пока принтер не включен, DOS выдает сообщения "Out of  paper".  После
включения питания программа начинает работать нормально.  Для  прекращения
печати можно нажать клавиши Ctrl/Break.


     СПЕЦИАЛЬНЫЕ КОМАНДЫ ПРИНТЕРА
     ________________________________________________________________

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

               Десятичн.      Шест.

                  15           0F        Включить узкий формат
                  14           0E        Включить широкий формат
                  18           12        Выключить узкий формат
                  20           14        Выключить широкий формат

     Есть команды, которые распознаются  по  предшествующему  символу  Esc
(шест.1B).  Некоторые из  них  в  зависимости  от  печатающего  устройства
представлены ниже:

               1B 30     Установить плотность 8 строк на дюйм
               1B 32     Установить плотность 6 строк на дюйм
               1B 45     Включить жирный формат
               1B 46     Выключить жирный формат

     Коды команд можно посылать на принтер двумя разными способами:

          1.  Определить  команды  в  области  данных.  Следующий   пример
     устанавливает узкий формат, 8 строк на дюйм, затем печатает заголовок
     с завершающими командами "возврат каретки" и "перевод строки":

               HEADG  DB  0FH, 1BH, 30H, 'Title...', 0DH, 0AH

          2. Использовать команды с непосредственными данными:

               MOV  AH,05          ;Запрос функции печати
               MOV  DL,0FH         ;Включить узкий формат
               INT  21H

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


     ПЕЧАТЬ С ПОМОЩЬЮ BIOS INT 17H
     ________________________________________________________________

     Прерывание  BIOS  INT  17H  обеспечивает  три  различные    операции,
специфицированные содержимым регистра AH:

     AH=0: Данная операция выполняет печать одного символа на три принтера
по номерам 0,1 и 2 (стандартное значение - 0).

               MOV  AH,00     ;Запрос функции печати
               MOV  AL,char   ;Символ, выводимый на печать
               MOV  DX,00     ;Выбор принтера № 0
               INT  17H       ;Вызов BIOS

Если  операция  не  может  распечатать  символ,  то    в    регистре    AH
устанавливается значение 01.

     AH=1: Инициализация порта печатающего устройства:

               MOV  AH,01     ;Запрос на инициализацию порта
               MOV  DX,00     ;Выбор порта № 0
               INT  17H       ;Вызов BIOS

     Данная операция посылает на принтер символ "прогон страницы", поэтому
ее можно использовать для установки положения "верх страницы". Большинство
принтеров выполняют данную установку автоматически при включении.

     AH=2: Чтение состояние порта принтера:

               MOV  AH,02     ;Функция чтения состояния порта
               MOV  DX,00     ;Выбор порта № 0
               INT  17H       ;Вызов BIOS
               TEST AH,00101001B;  Принтер готов?
               JNZ  errormsg  ;Нет - выдать сообщение об ошибке

     Назначение функций  AH=1  и  AH=2  состоит  в  определении  состояния
принтера.  В результате выполнения этих функций  биты  регистра  AH  могут
устанавливаться в 1:

               Бит  Причина

               7    Не занято
               6    Подтверждение от принтера
               5    Конец бумаги
               4    Выбран
               3    Ошибка ввода/вывода
               0    Таймаут

     Если принтер  включен,  то  операция  возвращает шест.90 или двоичное
10010000 - принтер "не  занят"  и  "выбран"  -  это  нормальное  состояние
готовности.  В  случае  неготовности принтера устанавливаются бит 5 (конец
бумаги или бит 3 (ошибка  вывода).  Если  принтер  выключен,  то  операция
возвращает шест.B0 или двоичное 10110000, указывая на "конец бумаги".
     Выполняя программу при выключенном принтере, BIOS не выдает сообщение
автоматически, поэтому предполагается, что программа должна сама проверить
и отреагировать на состояние принтера.  Если программа не делает этого, то
единственной индикацией будет мигающий курсор на экране  дисплея.  Если  в
этот момент включить принтер, то  некоторые  выходные  данные  могут  быть
потеряны.  Следовательно, прежде чем использовать функции BIOS для печати,
следует проверить  состояние  порта  принтера  и,  если  будет  обнаружена
ошибка, то выдать соответствующее сообщение. (Функции  DOS  выполняют  эту
проверку автоматически, хотя их  сообщение  "Out  of  paper"  относится  к
различным состояниям). После включения принтера, вывод сообщений об ошибке
прекращается и принтер начинает нормально работать без потери данных.
     В процессе работы принтер может выйти за страницу или  быть  нечаянно
выключен.  Поэтому в  программах  печати  следует  предусмотреть  проверку
состояния принтера перед каждой попыткой печати.


     ОСНОВНЫЕ ПОЛОЖЕНИЯ НА ПАМЯТЬ
     ________________________________________________________________

     - Прежде чем  выводить  данные  на  печатающее  устройство,  включите
принтер и вставьте в него бумагу.

     - Для  завершении  печати  используйте  символы  "перевод  строки"  и
"прогон страницы" для очистки буфера принтера.

     -  Функции  DOS  для  печати  предусматривают  вывод  сообщений   при
возникновении  ошибки  принтера.  Функции  BIOS  возвращают  только    код
состояния.  При использовании BIOS INT 17H проверяйте  состояние  принтера
перед печатью.


     ВОПРОСЫ ДЛЯ САМОПРОВЕРКИ
     ________________________________________________________________

     19.1. Напишите программу в расширенной  версии  DOS  для  а)  прогона
страницы; б) печати вашего имени;  в)  перевода  строки  и  печати  вашего
адреса;  г)  перевода  строки  и  печати  названия  вашего    города/штата
(республики); д) прогона страницы.

     19.2. Переделайте программу из предыдущего вопроса для базовой версии
DOS.

     19.3. Закодируйте строку, в  которой  имеется  следующая  информация:
возврат каретки, прогон страницы, включение узких букв,  заголовок  (любое
имя) и выключение узких букв.

     19.4. Измените программу из вопроса 19.1 для использования  BIOS  INT
17H. Обеспечьте проверку состояния принтера.

     19.5. Измените программу из вопроса 19.1 так, чтобы пункты б), в), г)
выполнялись по 5 раз.

     19.6. Измените программу на рис.19.1 для выполнения в базовой  версии
DOS.

     19.7. Измените  программу  на  рис.19.2  так,  чтобы  распечатываемые
строки также выводились на экран.



ГЛАВА 20                                                     Макросредства
__________________________________________________________________________

     Ц е л ь:   Объяснить   определение   и   использование   ассемблерных
макрокоманд.


     ВВЕДЕНИЕ
     ________________________________________________________________

     Для каждой закодированной команды ассемблер генерирует  одну  команду
на машинном языке. Но для каждого закодированного оператора компиляторного
языка Pascal или  C  генерируется  один  или  более  (чаще  много)  команд
машинного языка.  В этом отношении можно считать, что  компиляторный  язык
состоит из макрооператоров.
     Ассемблер  MASM  также  имеет  макросредства,  но    макросы    здесь
определяются программистом.  Для этого  задается  имя  макроса,  директива
MACRO, различные ассемблерные команды, которые должен генерировать  данный
макрос и для завершения макроопределения - директива MEND.  Затем в  любом
месте  программы,  где  необходимо  выполнение определенных в макрокоманде
команд,  достаточно  закодировать  имя  макроса.  В  результате  ассемблер
сгенерирует необходимые команды.
     Использование макрокоманд позволяет:

          - упростить и сократить исходный текст программы;
          - сделать программу более понятной;
          - уменьшить число возможных ошибок кодирования.

     Примерами макрокоманд могут быть операции ввода-вывода,  связанные  с
инициализацией регистров и выполнения прерываний  преобразования  ASCII  и
двоичного форматов данных, арифметические операции  над  длинными  полями,
обработка строковых данных, деление с помощью вычитания.
     В данной главе  рассмотрены  особенности  макросредств,  включая  те,
которые не достаточно ясно даны в руководстве по ассемблеру.  Тем не менее
пояснения  для  некоторых  малоиспользуемых  операций  следует  искать   в
руководстве по ассемблеру.


     ПРОСТОЕ МАКРООПРЕДЕЛЕНИЕ
     ________________________________________________________________

     Макроопределение  должно  находиться   до    определения    сегмента.
Рассмотрим  пример  простого  макроопределения  по  имени  INIT1,  которое
инициализирует сегментные регистры для EXE-программы:

     INIT1  MACRO                                   ;Начало
            ASSUME CS:CSEG,DS:DSEG,SS:STACK;ES:DSEG ;________________
            PUSH DS                                 ;                |
            SUB  AX,AX                              ;                |
            PUSH AX                                 ;Тело            |
            MOV  AX,DSEG                            ;макроопpеделения|
            MOV  DS,AX                              ;                |
            MOV  ES,AX                              ;________________|
            ENDM                                    ;Конец

     Директива  MACRO  указывает  ассемблеру,  что  следующие  команды  до
директивы ENDM являются частью макроопределения. Имя макрокоманды - INIT1,
хотя  здесь  возможны  другие  правильные  уникальные  ассемблерные имена.
Директива ENDM завершает макроопределение.  Семь команд между  директивами
MACRO и ENDM составляют тело макроопределения.
     Имена, на которые имеются ссылки в  макроопределении,  CSEG,  DSEG  и
STACK  должны  быть  определены  где-нибудь  в  другом  месте   программы.
Макрокоманда INIT1  может  использоваться  в  кодовом  сегменте  там,  где
необходимо инициализировать регистры.  Когда ассемблер анализирует команду
INIT1, он сначала просматривает таблицу мнемокодов  и,  не  обнаружив  там
соответствующего  элемента,  проверяет  макрокоманды.  Так  как  программа
содержит  определение  макрокоманды  INIT1  ассемблер  подставляет    тело
макроопределения,  генерируя  необходимые  команды   -    макрорасширение.
Программа использует рассматриваемую макрокоманду только  один  раз,  хотя
имеются другие макрокоманды, предназначенные на любое число  применений  и
для таких макрокоманд ассемблер генерирует одинаковые макрорасширения.
     На  рис.20.1  показана  ассемблированная  программа.    В    листинге
макрорасширения каждая команда, помеченная слева знаком плюс (+), является
результатом  генерации  макрокоманды.  Кроме  того,   в    макрорасширении
отсутствует директива ASSUME, так как она не генерирует объектный код.
     В  последующем  разделе  "Включение  из  библиотеки  макроопределений
показана  возможность  каталогизации   макрокоманд    в    библиотеке    и
автоматическое включение их в любые программы.

__________________________________________________________________________

                          TITLE   MACRO1  (EXE) Макрос для инициализации
                          ; --------------------------------------------
                          INIT1   MACRO
                                  ASSUME  CS:CSEG,DS:DSEG,SS:STACK,ES:DSEG
                          PUSH    DS
                          SUB     AX,AX
                          PUSH    AX
                          MOV     AX,DSEG
                          MOV     DS,AX
                          MOV     ES,AX
                          ENDM                       ;Конец макрокоманды
                          ; --------------------------------------------
0000                      STACK   SEGMENT PARA STACK 'Stack'
0000     20 [ ???? ]              DW      32 DUP(?)
0040                      STACK   ENDS
                          ; --------------------------------------------
0000                      DSEG    SEGMENT PARA 'Data'
0000  54 65 73 74 20 6F   MESSGE  DB      'Test of macro-instruction', 13
      66 20 6D 61 63 72
      6F 2D 69 6E 73 74
      72 65 73 74 69 6F
      6E 0D
001A                      DSEG    ENDS
                          ; --------------------------------------------
0000                      CSEG    SEGMENT PARA 'Code'
0000                      BEGIN   PROC    FAR
                                  INIT1              ;Макрокоманда
0000  1E                 +        PUSH    DS
0001  2B C0              +        SUB     AX,AX
0003  50                 +        PUSH    AX
0004  B8 ---- R          +        MOV     AX,DSEG
0007  8E D8              +        MOV     DS,AX
0009  8E C0              +        MOV     ES,AX
000B  B4 40                       MOV     AH,40H     ;Вывод на экран
000D  BB 0001                     MOV     BX,01      ;Номер
0010  B9 001A                     MOV     CX,26      ;Длина
0013  8D 16 0000 R                LEA     DX,MESSGE  ;Сообщение
0017  CD 21                       INT     21H
0019  CB                          RET
001A                      BEGIN   ENDP
001A                      CSEG    ENDS
                                  END     BEGIN

Macros:
                   N a m e          Length
INIT1. . . . . . . . . . . . . . . . 0004

Segments and Groups:
                   N a m e           Size    Align   Combine  Class
CSEG . . . . . . . . . . . . . . . . 001A    PARA    NONE    'CODE'
DSEG . . . . . . . . . . . . . . . . 001A    PARA    NONE    'DATA'
STACK. . . . . . . . . . . . . . . . 0040    PARA    STACK   'STACK'

Symbols:
                   N a m e           Type    Value   Attr
BEGIN. . . . . . . . . . . . . . . . F PROC  0000    CSEG    Length=001A
MESSAGE. . . . . . . . . . . . . . . L BYTE  0000    DSEG
__________________________________________________________________________

     Рис.20.1. Пример ассемблирования макрокоманды.


     ИСПОЛЬЗОВАНИЕ ПАРАМЕТРОВ В МАКРОКОМАНДАХ
     ________________________________________________________________

     В  предыдущем  макроопределении  требовались   фиксированные    имена
сегментов: CSEG, DSEG и STACK.  Для того, чтобы  макрокоманда  была  более
гибкой и могла принимать любые имена сегментов, определим эти  имена,  как
формальные параметры:

          INIT2  MACRO CSNAME,DSNAME,SSNAME   ;Формальные параметры
                 ASSUME CS:CSNAME,DS:DSNAME,SC:SSNAME,ES:DSNAME
                 PUSH DS
                 SUB  AX,AX
                 PUSH AX
                 MOV  AX,DSNAME
                 MOV  DS,AX
                 MOV  ES,AX
                 ENDM                         ;Конец макроопределения

     Формальные  параметры  в  макроопределении  указывают  ассемблеру  на
соответствие их имен любым аналогичным именам в теле макроопределения. Все
три формальных параметра CSNAME, DSNAME и SSNAME встречаются  в  директиве
ASSUME, а параметр DSNAME еще и  в  последующей  команде  MOV.  Формальные
параметры могут иметь любые правильные ассемблерные имена, не  обязательно
совпадающими именами в сегменте данных.
     Теперь при использовании  макрокоманды  INIT2  необходимо  указать  в
качестве параметров действительные имена трех сегментов в  соответствующей
последовательности.  Например,  следующая  макрокоманда    содержит    три
параметра,  которые  соответствуют  формальным  параметрам   в    исходном
макроопределении:

    Макроопределение: INIT2  MACRO  CSNAME,DSNAME,SSNAME (форм. параметры)
                                      |     |     |
    Макрокоманда:            INIT2  CSEG,DSEG,STACK (параметры)

Так как ассемблер уже определил соответствие между формальными параметрами
и операторами  в  макроопределении,  то  теперь  ему  остается  подставить
параметры макрокоманды в макрорасширении:

          -  Параметр  1:  CSEG  ставится  в  соответствие  с  CSNAME    в
     макроопределении.  Ассемблер  подставляет  CSEG  вместо   CSNAME    в
     директиве ASSUME.
          -  Параметр  2:  DSEG  ставится  в  соответствие  с  DSNAME    в
     макроопределении.  Ассемблер подставляет DSEG вместо двух  DSNAME:  в
     директиве ASSUME и в команде MOV.
          -  Параметр  3:  STACK  ставится  в  соответствие  с  SSNAME   в
     макроопределении.  Ассемблер  подставляет  STACK  вместо  SSNAME    в
     директиве ASSUME.

     Макроопределение  с  формальными  параметрами    и    соответствующее
макрорасширение приведены на рис.20.2.
     Формальный параметр может иметь  любое  правильное  ассемблерное  имя
(включая имя регистра, например, CX), которое в  процессе  ассемблирования
будет заменено на параметр макрокоманды.  Отсюда следует, что ассемблер не
распознает регистровые имена и имена, определенные в области  данных,  как
таковые. В одной макрокоманде может быть определено любое число формальных
параметров, разделенных запятыми, вплоть до 120 колонки в строке.

__________________________________________________________________________

                         TITLE   MACRO2  (EXE) Использование параметров
                         ; ----------------------------------------------
                         INIT2   MACRO   CSNAME,DSNAME,SSNAME
                                 ASSUME  CS:CSNAME,DS:DSNAME
                                 ASSUME  SS:SSNAME,ES:DSNAME
                                 PUSH    DS
                                 SUB     AX,AX
                                 PUSH    AX
                                 MOV     AX,DSNAME
                                 MOV     DS,AX
                                 MOV     ES,AX
                                 ENDM                 ;Конец макрокоманды
                         ; ----------------------------------------------
0000                     STACK   SEGMENT PARA STACK 'Stack'
0000     20 [ ???? ]             DW      32 DUP(?)
0040                     STACK   ENDS
                         ; ----------------------------------------------
0000                     DSEG    SEGMENT PARA 'Data'
0000  54 65 73 74 20 6F  MESSAGE DB      'Test of macro', '$'
      66 20 6D 61 63 72
      6F 24
000E                     DSEG    ENDS
                         ; ----------------------------------------------
0000                     CSEG    SEGMENT PARA 'Code'
0000                     BEGIN   PROC    FAR
                                 INIT2   CSEG,DSEG,STACK
0000  1E                +        PUSH    DS
0001  2B C0             +        SUB     AX,AX
0003  50                +        PUSH    AX
0004  B8 ---- R         +        MOV     AX,DSEG
0007  8E D8             +        MOV     DS,AX
0009  8E C0             +        MOV     ES,AX
000B  B4 09                      MOV     AH,09          ;Вывод на экран
000D  8D 16 0000 R               LEA     DX,MESSGE      ;Сообщение
0011  CD 21                      INT     21H
0013  CB                         RET
0014                     BEGIN   ENDP
0014                     CSEG    ENDS
                                 END     BEGIN
__________________________________________________________________________

     Рис.20.2. Использование параметров в макрокомандах.


     КОММЕНТАРИИ
     ________________________________________________________________

     Для пояснений назначения  макроопределения  в  нем  могут  находиться
комментарии.  Директива COMMENT или символ точка с  запятой  указывают  на
строку комментария, как это показано в следующем макроопределении PROMPT:

               PROMPT    MACRO     MESSGE
               ;Эта макрокоманда выводит сообщения на экран
                         MOV       AH,09H
                         LEA       DX,MESSGE
                         INT       21H
                         ENDM

     Так как по умолчанию в листинг попадают только  команды  генерирующие
объектный код, то ассемблер не будет автоматически выдавать и комментарии,
имеющиеся  в  макроопределении.  Если  необходимо,  чтобы  в    расширении
появлялись комментарии, следует использовать перед макрокомандой директиву
.LALL ("list all" - выводить все), которая кодируется вместе с  лидирующей
точкой:
               .LALL
               PROMPT    MESSAG1

     Макроопределение  может  содержать  несколько  комментариев,   причем
некоторые из них могут выдаваться в листинге, а другие  -  нет.  В  первом
случае необходимо использовать директиву .LALL.  Во  втором  -  кодировать
перед комментарием два символа точка с запятой (;;) -  признак  подавления
вывода  комментария  в  листинг.  По  умолчанию  в  ассемблере   действует
директива .XALL, которая выводит в листинг  только  команды,  генерирующие
объектный  код.  И,  наконец,  можно  запретить  появление   в    листинге
ассемблерного  кода  в  макрорасширениях,  особенно   при    использовании
макрокоманды в одной программе несколько раз.  Для этого служит  директива
.SALL ("suppress all" - подавить весь  вывод),  которая  уменьшает  размер
выводимого листинга, но не оказывает никакого влияния на размер объектного
модуля.
     Директивы управления листингом .LALL,  .XALL,  .SALL  сохраняют  свое
действие по всему тексту программы,  пока  другая  директива  листинга  не
изменит его.  Эти директивы можно размещать в программе так, чтобы в одних
макрокомандах распечатывались комментарии, в других - макрорасширения, а в
третьих подавлялся вывод в листинг.
     Программа на рис.20.3 демонстрирует описанное выше свойство  директив
листинга. В  программе  определено  два  макроопределения  INIT2 и PROMPT,
рассмотренные ранее.  Кодовый  сегмент  содержит   директиву   .SALL   для
подавления  распечатки  INIT2  и  первого  расширения PROMPT.  Для второго
расширения PROMPT директива .LALL указывает ассемблеру на вывод в  листинг
комментария   и   макрорасширения.   Заметим,   однако,  что  комментарий,
отмеченный двумя символами точка с запятой (;;) в макроопределении PROMPT,
не  распечатывается  в  макрорасширениях  независимо  от действия директив
управления листингом.

__________________________________________________________________________

                         TITLE   MACRO3  (EXE) Директивы .LALL и .SALL
                         ; -----------------------------------------------
                         INIT2   MACRO   CSNAME,DSNAME,SSNAME
                                 ASSUME  CS:CSNAME,DS:DSNAME
                                 ASSUME  SS:SSNAME,ES:DSNAME
                                 PUSH    DS
                                 SUB     AX,AX
                                 PUSH    AX
                                 MOV     AX,DSNAME
                                 MOV     DS,AX
                                 MOV     ES,AX
                                 ENDM
                         ; -----------------------------------------------
                         PROMPT  MACRO   MESSAGE
                         ;   Макрокоманда выводит на экран любые сообщения
                         ;;  Генерирует команды вызова DOS
                                 MOV     AH,09          ;Вывод на экран
                                 LEA     DX,MESSAGE
                                 INT     21H
                                 ENDM
                         ; -----------------------------------------------
0000                     STACK   SEGMENT PARA STACK 'Stack'
0000     20 [ ???? ]             DW      32 DUP (?)
0040                     STACK   ENDS
                         ; -----------------------------------------------
0000                     DATA    SEGMENT PARA 'Data'
0000  43 75 73 74 6F 6D  MESSG1  DB      'Customer name?', '$'
      65 72 20 6E 61 6D
      65 3F 24
000F  43 75 73 74 6F 6D  MESSG2  DB      'Customer address?', '$'
      65 72 20 61 64 64
      72 65 73 73 3F 24
0021                     DATA    ENDS
                         ; -----------------------------------------------
0000                     CSEG    SEGMENT PARA 'Code'
0000                     BEGIN   PROC    FAR
                                 .SALL
                                 INIT2   CSEG,DATA,STACK
                                 PROMPT  MESSG1
                                 .LALL
                                 PROMPT  MESSG2
                       + ;   Макрокоманда выводит на экран любые сообщения
0013  B4 09            +         MOV     AH,09          ;Вывод на экран
0015  8D 16 000F R     +         LEA     DX,MESSG2
0019  CD 21            +         INT     21H
001B  CB                         RET
001C                     BEGIN   ENDP
001C                     CSEG    ENDS
                                 END     BEGIN
__________________________________________________________________________

     Рис.20.3. Распечатка и подавление макрорасширений в листинге.


     ИСПОЛЬЗОВАНИЕ МАКРОКОМАНД В МАКРООПРЕДЕЛЕНИЯХ
     ________________________________________________________________

     Макроопределение может содержать ссылку на  другое  макроопределение.
Рассмотрим простое макроопределение DOS21, которое заносит  в  регистр  AH
номер функции DOS и выполняет INT 21H:

               DOS21     MACRO     DOSFUNC
                         MOV       AH,DOSFUNC
                         INT       21H
                         ENDM

Для использования данной макрокоманды при вводе  с  клавиатуры  необходимо
закодировать:

               LEA       DX,NAMEPAR
               DOS21     0AH

Предположим, что имеется другое макроопределение, использующее функцию  02
в регистре AH для вывода символа:

               DISP      MACRO     CHAR
                         MOV       AH,02
                         MOV       DL,CHAR
                         INT       21H
                         ENDM

Для  вывода  на  экран,  например,  звездочки   достаточно    закодировать
макрокоманду  DISP  '*'.    Можно    изменить    макроопределение    DISP,
воспользовавшись макрокомандой DOC21:

               DISP      MACRO     CHAR
                         MOV       DL,CHAR
                         DOS21     02
                         ENDM

Теперь, если закодировать макрокоманду DISP в виде DISP '*', то  ассемблер
сгенерирует следующие команды:

                         MOV       DL,'*'
                         MOV       AH,02
                         INT       21H


     ДИРЕКТИВА LOCAL
     ________________________________________________________________

     В некоторых макрокомандах требуется определять  элементы  данных  или
метки команд.  При использовании  такой  макрокоманды  в  программе  более
одного раза происходит также неоднократное  определение  одинаковых  полей
данных или меток.  В результате ассемблер выдаст сообщения об ошибке из-за
дублирования имен.  Для обеспечения  уникальности  генерируемых  в  каждом
макрорасширении имен  используется  директива  LOCAL,  которая  кодируется
непосредственно после директивы MACRO,  даже  перед  комментариями.  Общий
формат имеет следующий вид:

               LOCAL dummy-1,dummy-2,...     ;Формальные параметры

     Рис.20.4 иллюстрирует использование директивы LOCAL. В приведенной на
этом рисунке программе выполняется деление вычитанием; делитель вычитается
из  делимого и частное увеличивается на 1 до тех пор,  пока делимое больше
делителя.  Для данного алгоритма необходимы две метки: COMP - адрес цикла,
OUT - адрес выхода из цикла по завершению. Обе метки COMP и OUT определены
как LOCAL и могут иметь любые правильные ассемблерные имена.
     В макрорасширении для COMP генерируется метка ??0000,  а  для  OUT  -
??0001. Если макрокоманда DIVIDE будет использована в этой  программе  еще
один раз, то в следующем макрорасширении будут сгенерированы метки  ??0002
и  ??0003  соответственно.  Таким  образом,  с  помощью  директивы   LOCAL
обеспечивается уникальность меток в макрорасширениях в одной программе.

__________________________________________________________________________

                      TITLE   MACRO4  (COM) Использование директивы LOCAL
                      ; -------------------------------------------------
                      DIVIDE  MACRO   DIVIDEND,DIVISOR,QUOTIENT
                              LOCAL   COMP
                              LOCAL   OUT
                      ;       AX=делимое, BX=делитель, CX=частное
                              MOV     AX,DIVIDEND    ;Загрузить делимое
                              MOV     BX,DIVISOR     ;Загрузить делитель
                              SUB     CX,CX          ;Регистр для частного
                      COMP:
                              CMP     AX,BX          ;Делимое < делителя?
                              JB      OUT            ; да - выйти
                              SUB     AX,BX          ;Делимое - делитель
                              INC     CX             ;Частное + 1
                              JMP     COMP
                      OUT:
                              MOV     QUOTIENT,CX    ;Записать результат
                              ENDM
                      ; ------------------------------------------------
0000                  CSEG    SEGMENT PARA 'Code'
                              ASSUME  CS:CSEG,DS:CSEG,SS:CSEG,ES:CSEG
0100                          ORG     100H
0100  EB 06           BEGIN:  JMP     SHORT MAIN
                      ; ------------------------------------------------
0102  0096            DIVDND  DW      150            ;Делимое
0104  001B            DIVSOR  DW      27             ;Делитель
0106  ????            QUOTNT  DW      ?              ;Частное
                      ; ------------------------------------------------
0108                  MAIN    PROC    NEAR
                              .LALL
                              DIVIDE  DIVDND,DIVSOR,QUOTNT
                    + ;       AX=делимое, BX=делитель, CX=частное
0108  A1 0102 R     +         MOV     AX,DIVDND      ;Загрузить делимое
010B  8B 1E 0104 R  +         MOV     BX,DIVSOR      ;Загрузить делитель
010F  2B C9         +         SUB     CX,CX          ;Регистр для частного
0111                + ??0000:
0111  3B C3         +         CMP     AX,BX          ;Делимое < делителя?
0113  72 05         +         JB      ??0001         ; да - выйти
0115  2B C3         +         SUB     AX,BX          ;Делимое - делитель
0117  41            +         INC     CX             ;Частное + 1
0118  EB F7         +         JMP     ??0000
011A                + ??0001:
011A  89 0E 0106 R  +         MOV     QUOTNT,CX      ;Записать результат
011E  C3                      RET
011F                  MAIN    ENDP
011F                  CSEG    ENDS
                              END     BEGIN
__________________________________________________________________________

     Рис.20.4. Использование директивы LOCAL.


     ИСПОЛЬЗОВАНИЕ БИБЛИОТЕК МАКРООПРЕДЕЛЕНИЙ
     ________________________________________________________________

     Определение таких макрокоманд, как INIT1 и  INIT2  и  одноразовое  их
использование в программе кажется  бессмысленным.  Лучшим  подходом  здесь
является каталогизация собственных макрокоманд в библиотеке  на  магнитном
диске, используя любое описательное имя, например, MACRO.LIB:

               INIT      MACRO     CSNAME,DSNAME,SSNAME
                         .
                         .
                         ENDM
               PROMPT    MACRO     MESSGE
                         .
                         .
                         ENDM

Теперь для использования любой из  каталогизированных  макрокоманд  вместо
MACRO определения в начале программы следует применять директиву INCLUDE:

                         INCLUDE   C:MACRO.LIB
                         .
                         .
                         INIT      CSEG,DATA,STACK

В этом случае ассемблер обращается к файлу MACRO.LIB (в нашем примере)  на
дисководе C и включает в программу оба  макроопределения  INIT  и  PROMPT.
Хотя в нашем примере требуется только  INIT.  Ассемблерный  листинг  будет
содержать копию макроопределения, отмеченного  символом  C  в  30  колонке
LST-файла.  Следом за макрокомандой идет ее расширение с объектным кодом и
с символом плюс (+) в 31 колонке.
     Так как транслятор  с  ассемблера  является  двухпроходовым,  то  для
обеспечения обработки директивы INCLUDE только в первом проходе  (а  не  в
обоих) можно использовать следующую конструкцию:

               IF1
                         INCLUDE   C:MACRO.LIB
               ENDIF

     IF1 и ENDIF являются условными директивами.  Директива IF1  указывает
ассемблеру на необходимость доступа к библиотеке только в  первом  проходе
трансляции. Директива  ENDIF  завершает  IF-логику.  Таким образом,  копия
макроопределений не появится в листинге -  будет  сэкономлено  и  время  и
память.
     Программа на рис.20.5  содержит  рассмотренные  выше  директивы  IF1,
INCLUDE и ENDIF, хотя в LST-файл ассемблер выводит только директиву ENDIF.
Обе макрокоманды в кодовом сегменте INIT  и  PROMPT  закаталогизированы  в
файле MACRO.LIB, т.е. просто записаны друг за другом на дисковый  файл  по
имени MACRO.LIB с помощью текстового редактора.
     Расположение директивы INCLUDE не критично, но она  должна  появиться
ранее любой макрокоманды из включаемой библиотеки.

__________________________________________________________________________

                         TITLE   MACRO5  (EXE) Проверка директивы INCLUDE
                         EDIF
                         ; -----------------------------------------------
0000                     STACK   SEGMENT PARA STACK 'Stack'
0000     20 [????]               DW      32 DUP(?)
0040                     STACK   ENDS
                         ; -----------------------------------------------
0000                     DATA    SEGMENT PARA 'Data'
0000  54 65 73 74 20 6F  MESSGE  DB      'Test of macro','$'
      66 20 6D 61 63 72
      6F 24
000E                     DATA    ENDS
                         ; -----------------------------------------------
0000                     CSEG    SEGMENT PARA 'Code'
0000                     BEGIN   PROC    FAR
                                 INIT    CSEG,DATA,STACK
0000  1E               +         PUSH    DS
0001  3B  C0           +         SUB     AX,AX
0003  50               +         PUSH    AX
0004  B8 ---- R        +         MOV     AX,DATA
0007  8E D8            +         MOV     DS,AX
0009  8E C0            +         MOV     ES,AX
                                 PROMPT  MESSGE
000B  B4 09            +         MOV     AH,09          ;Вывод на экран
000D  8D 16 0000 R     +         LEA     DX,MESSGE
0011  CD 21            +         INT     21H
0013  CB                         RET
0014                     BEGIN   ENDP
0014                     CSEG    ENDS
                                 END     BEGIN
__________________________________________________________________________

     Рис.20.5. Использование библиотеки макроопределений.


                            Директива очистки
                           -------------------
     Директива  INCLUDE  указывает   ассемблеру    на    включение    всех
макроопределений из  специфицированной  библиотеки.  Например,  библиотека
содержит макросы INIT, PROMPT и DIVIDE, хотя  программе  требуется  только
INIT.  Директива PURGE позволяет "удалить" нежелательные макросы PROMPT  и
DIVIDE в текущем ассемблировании:

     IF1
               INCLUDE MACRO.LIB   ;Включить всю библиотеку
     ENDIF
     PURGE     PROMRT,DIYIDE       ;Удалить ненужные макросы
     ...
     INIT      CSEG,DATA,STACK     ;Использование оставшейся макрокоманды

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


     КОНКАТЕНАЦИЯ (&)
     ________________________________________________________________

     Символ амперсанд (&) указывает ассемблеру на сцепление (конкатенацию)
текста или символов.  Следующая макрокоманда MOVE генерирует команду MOVSB
или   MOVSW  :

               MOVE      MACRO     TAG
                           REP         MOVS&TAG
                         ENDM

Теперь можно  кодировать  макрокоманду  в  виде  MOVE  B  или  MOVE  W.  В
результате макрорасширения ассемблер сцепит параметр  с  командой  MOVS  и
получит REP MOVSB или REP MOVSW.  Данный пример весьма тривиален и  служит
лишь для иллюстрации.


     ДИРЕКТИВЫ ПОВТОРЕНИЯ: REPT, IRP, IRPC
     ________________________________________________________________

     Директивы повторения заставляют ассемблер повторить блок  операторов,
завершаемых директивой ENDM. Эти директивы не обязательно должны находится
в макроопределении, но если они там  находятся,  то  одна  директива  ENDM
требуется для  завершения  повторяющегося  блока,  а  вторая  ENDM  -  для
завершения макроопределения.


                             REPT: Повторение
                            ------------------
     Операция REPT приводит к повторению  блока  операторов  до  директивы
ENDM в соответствии с числом повторений, указанным в выражении:

               REPT выражение

     В следующем примере происходит начальная инициализация значения N=0 и
затем повторяется генерация DB N пять раз:

               N =  0
               REPT 5
               N =  N + 1
               DB   N
               ENDM

В результате будут сгенерированы пять операторов DB  от  DB  1  до  DB  5.
Директива REPT может использоваться таким образом для определения  таблицы
или части таблицы.  Другим примером может служить  генерация  пяти  команд
MOVSB, что эквивалентно REP MOVSB при содержимом CX равном 05:

               REPT 5
               MOVSB
               ENDM


                      IRP: Неопределенное повторение
                     --------------------------------
     Операция IRP приводит к повторению блока команд  до  директивы  ENDM.
Основной формат:

               IRP  dummy,

Аргументы, содержащиеся в угловых скобках, представляют собой любое  число
правильных  символов,  строк,  числовых  или   арифметических    констант.
Ассемблер генерирует блок кода для каждого аргумента.  В следующем примере
ассемблер генерирует DB 3, DB 9, DB 17, DB 25 и DB 28:

               IRP  N,<3, 9, 17, 25, 28>
               DB   N
               ENDM


                 IRPC: Неопределенное повторение символа
                -----------------------------------------
     Операция IRPC приводит к повторению  блока  операторов  до  директивы
ENDM. Основной формат:

               IRPC dummy,string

Ассемблер генерирует блок кода для каждого символа в  строке  "string".  В
следующем примере ассемблер генерирует DW 3, DW 4 ... DW 8:

               IRPC N,345678
               DW   N
               ENDM


     УСЛОВНЫЕ ДИРЕКТИВЫ
     ________________________________________________________________

     Ассемблер  поддерживает  ряд  условных  директив.  Ранее   нам    уже
приходилось использовать директиву IF1 для включения библиотеки  только  в
первом проходе ассемблирования. Условные директивы наиболее полезны внутри
макроопределений,  но  не  ограничены  только  этим  применением.   Каждая
директива IF должна иметь спаренную с ней директиву ENDIF  для  завершения
IF-логики и возможную директиву ELSE для альтернативного действия:

               IFxx      (условие)
               .                                __________
               .                                  Условный|
               ELSE (не обязательное действие)            |
               .                                    блок  |
               .                                __________|
               ENDIF     (конец IF-логики)

     Отсутствие директивы    ENDIF    вызывает    сообщение   об   ошибке:
"Undeterminated   conditional"   (незавершенный   условный   блок).   Если
проверяемое условие  истинно,  то  ассемблер  выполняет  условный  блок до
директивы ELSE или при отсутствии ELSE - до директивы ENDIF.  Если условие
ложно,  то  ассемблер выполняет условный блок после директивы ELSE,  а при
отсутствии ELSE вообще обходит условный блок.
     Ниже перечислены различные условные директивы:

IF выражение             Если   выражение   не   равно   нулю,   ассемблер
                         обрабатывает операторы в условном блоке.
IFE выражение            Если выражение равно нулю, ассемблер обрабатывает
                         операторы в условном блоке.
IF1 (нет выражения)      Если осуществляется первый проход ассемблирования
                         то обрабатываются операторы в условном блоке.
IF2 (нет выражения)      Если  осуществляется  второй   проход   операторы
                         ассемблирования, то  обрабатываются  в   условном
                         блоке.
IFDEF идентификатор      Если  идентификатор  определен  в  программе  или
                         объявлен как  EXTRN,  то  ассемблер  обрабатывает
                         операторы в условном блоке.
IFNDEF идентификатор     Если идентификатор не определен в  программе  или
                         не объявлен как EXTRN,  то ассемблер обрабатывает
                         операторы в условном блоке.
IFB <аргумент>           Если  аргументом   является   пробел,   ассемблер
                         обрабатывает операторы в условном блоке. Аргумент
                         должен быть в угловых скобках.
IFNB <аргумент>          Если аргументом является не пробел,  то ассемблер
                         обрабатывает операторы в условном блоке. Аргумент
                         должен быть в угловых скобках.
IFIDN <арг-1>,<арг-2>    Если строка  первого  аргумента  идентична строке
                         второго аргумента,   то   ассемблер  обрабатывает
                         операторы в условном блоке. Аргументы должны быть
                         в угловых скобках.
IFDIF<арг-1>,<арг-2>     Если  строка  первого  аргумента  отличается   от
                         строки второго     аргумента,     то    ассемблер
                         обрабатывает   операторы   в   условном    блоке.
                         Аргументы должны быть в угловых скобках.

     Ниже приведен простой пример директивы IFNB (если не пробел). Для DOS
INT 21H все запросы требуют занесения номера функции в регистр  AH,  в  то
время как лишь  некоторые  из  них  используют  значение  в  регистре  DX.
Следующее макроопределение учитывает эту особенность:

               DOS21     MACRO     DOSFUNC,DXADDRES
                         MOV       AN,DOSFUNC
                         IFNB
                         MOV       DX,OFFSET DXADDRES
                         ENDIF
                         INT       21H
                         ENDM

     Использование DOS21 для простого ввода с клавиатуры требует установки
значения 01 в регистр AH:

                         DOS21     01

Ассемблер генерирует в результате команды MOV AH,01 и INT 21H.  Для  ввода
символьной строки требуется занести в регистр AH значение 0AH, а в регистр
DX - адрес области ввода:

                         DOS21     0AH,IPFIELD

Ассемблер генерирует в результате обе команды MOV и INT 21H.


     ДИРЕКТИВА ВЫХОДА ИЗ МАКРОСА EXITM.
     ________________________________________________________________

     Макроопределение  может  содержать  условные    директивы,    которые
проверяют важные  условия.  Если  условие  истинно,  то  ассемблер  должен
прекратить дальнейшее макрорасширение.  Для  этой  цели  служит  директива
EXITM:

               IFxx      [условие]
               .
               .         (неправильное условие)
               .
               EXITM
               .
               .
               ENDIF

     Как только ассемблер попадает в процессе генерации макрорасширения на
директиву  EXITM,  дальнейшее  расширение   прекращается    и    обработка
продолжается  после  директивы  ENDM.  Можно  использовать    EXITM    для
прекращения повторений по директивам  REPT,  IRP  и  IRPC  даже  если  они
находятся внутри макроопределения.


     МАКРОКОМАНДЫ, ИСПОЛЬЗУЮЩИЕ IF И IFNDEF УСЛОВИЯ
     ________________________________________________________________

     Программа  на  рис.20.6  содержит  макроопределение  DIVIDE,  которая
генерирует подпрограмму для выполнения  деления  вычитанием.  Макрокоманда
должна кодироваться с параметрами в следующей последовательности: делимое,
делитель, частное.  Макрокоманда содержит директиву  IFNDEF  для  проверки
наличия  параметров.  Для  любого  неопределенного  элемента  макрокоманда
увеличивает счетчик CNTR.  Этот счетчик может иметь любое корректное имя и
предназначен  для  временного  использования  в  макроопределении.   После
проверки всех трех параметров, макрокоманда проверяет CNTR:

                    IF   CNTR
                    ;Макрорасширение прекращено
                    EXITM

     Если  счетчик  CNTR  содержит  ненулевое  значение,   то    ассемблер
генерирует  комментарий  и  прекращает  по  директиве  EXITM    дальнейшее
макрорасширение.  Заметим, что начальная команда устанавливает в  счетчике
CNTR нулевое значение и, кроме того, блоки IFNDEF  могут  устанавливать  в
CNTR единичное значение, а не увеличивать его на 1.
     Если ассемблер  успешно  проходит  все  проверки,  то  он  генерирует
макрорасширение.  В кодовом сегменте первая макрокоманда  DIVIDE  содержит
правильные делимое и частное и,  поэтому  генерирует  только  комментарии.
Один из  способов  улучшения  рассматриваемой  макрокоманды  -  обеспечить
проверку на ненулевой делитель и на одинаковый знак делимого  и  делителя;
для этих целей лучше использовать коды ассемблера, чем условные директивы.

__________________________________________________________________________

                         TITLE   MACRO6 (COM) Проверка директ. IF и IFNDEF
                         ; -----------------------------------------------
                         DIVIDE  MACRO   DIVIDEND,DIVISOR,QUOTIENT
                                 LOCAL   COMP
                                 LOCAL   OUT
                                 CNTR    = 0
                         ;       AX-делимое, BX-делитель, CX-частное
                                 IFNDEF  DIVIDEND
                         ;               Делитель не определен
                                 CNTR    = CNTR +1
                                 ENDIF
                                 IFNDEF  DIVISOR
                         ;               Делимое не определено
                                 CNTR    = CNTR +1
                                 ENDIF
                                 IFNDEF  QUOTIENT
                         ;               Частное не определено
                                 CNTR    = CNTR +1
                                 ENDIF
                                 IF      CNTR
                         ;               Макрорасширение отменено
                                 EXITM
                                 ENDIF
                                 MOV     AX,DIVIDEND   ;Загрузка делимого
                                 MOV     BX,DIVISOR    ;Загрузка делителя
                                 SUB     CX,CX       ;Регистр для частного
                         COMP:
                                 CMP      AX,BX      ;Делимое < делителя?
                                 JB       OUT        ; да - выйти
                                 SUB      AX,BX        ;Делимое - делитель
                                 INC      CX           ;Частное + 1
                                 JMP      COMP
                         OUT:
                                 MOV      QUOTIENT,CX  ;Запись результата
                                 ENDM
                         ; -----------------------------------------------
0000                     CSEG    SEGMENT  PARA 'Code'
                                 ASSUME   CS:CSEG,DS:CSEG,SS:CSEG,ES:CSEG
0100                             ORG      100H
0100  EB 06              BEGIN:  JMP      SHORT MAIN
                         ; -----------------------------------------------
0102  0096               DIVDND  DW       150
0104  001B               DIVSOR  DW       27
0106  ????               QUOTNT  DW       ?
                         ; -----------------------------------------------
0108                     MAIN    PROC     NEAR
                                 .LALL
                                 DIVIDE   DIVDND,DIVSOR,QUOTNT
= 0000                 +         CNTR    = 0
                       + ;       AX-делимое, BX-делитель, CX-частное
                       +         ENDIF
                       +         ENDIF
                       +         ENDIF
                       +         ENDIF
0108  A1 0102 R        +         MOV     AX,DIVDND     ;Загрузка делимого
0108  8B 1E 0104 R     +         MOV     BX,DIVSOR     ;Загрузка делителя
010F  2B C9            +         SUB     CX,CX       ;Регистр для частного
0111                   + ??0000:
0111  3B C3            +         CMP     AX,BX       ;Делимое < делителя?
0113  72 05            +         JB      ??0001      ; да - выйти
0115  2B C3            +         SUB     AX,BX         ;Делимое - делитель
0117  41               +         INC     CX
0118  EB F7            +         JMP     ??0000
011A                   + ??0001:
011A  89 0E 0106 R     +         MOV     QUOTNT,CX     ;Запись результата
                                 DIVIDE  DIDND,DIVSOR,QUOT
= 0000                 +         CNTR    = 0
                       + ;       AX-делимое, BX-делитель, CX-частное
                       +         IFNDEF  DIDND
                       + ;               Делитель не определен
= 0001                 +         CNTR    = CNTR +1
                       +         ENDIF
                       +         ENDIF
                       +         IFNDEF  QUOT
                       + ;               Частное не определено
= 0002                 +         CNTR    = CNTR +1
                       +         ENDIF
                       +         IF      CNTR
                       + ;               Макрорасширение отменено
                       +         EXITM
011E  C3                         RET
011F                     MAIN    ENDP
011F                     CSEG    ENDS
                                 END    BEGIN
__________________________________________________________________________

     Рис.20.6. Использование директив IF и IFNDEF.


     МАКРОС, ИСПОЛЬЗУЮЩИЙ IFIDN-УСЛОВИЕ
     ________________________________________________________________

     Программа на  рис.20.7  содержит  макроопределение  по  имени  MOVIF,
которая генерирует команды MOVSB или MOVSW  в  зависимости  от  указанного
параметра.  Макрокоманду можно кодировать с параметром B (для байта) или W
(для слова) для генерации команд MOVSB или MOVSW из MOVS.
     Обратите внимание на первые два оператора в макроопределении:

               MOVIF     MACRO     TAG
                         IFIDN     <&TAG>,

Условная директива IFIDN сравнивает заданный параметр (предположительно  B
или W) со строкой B.  Если значения идентичны, то ассемблер генерирует REP
MOVSB.  Обычное использование амперсанда (&)  -  для  конкатенации,  но  в
данном примере операнд  без амперсанда  не  будет  работать.  Если  в
макрокоманде не будет указан параметр B или W,  то  ассемблер  сгенерирует
предупреждающий комментарий и команду MOVSB (по умолчанию).
     Примеры в кодовом сегменте трижды проверяют макрокоманду  MOVIF:  для
параметра B, для параметра W и для  неправильного  параметра.  Не  следует
делать попыток выполнения данной программы в том виде, как  она  приведена
на рисунке, так как регистры CX и DX не обеспечены правильными значениями.
     Предполагается, что рассматриваемая макрокоманда  не  является  очень
полезной и ее назначение здесь - проиллюстрировать  условные  директивы  в
простой форме.  К данному моменту, однако, вы имеете достаточно информации
для составления больших полезных макроопределений.

__________________________________________________________________________

                             TITLE   MACRO7 (COM) Проверка директивы IFIDN
                             ; -------------------------------------------
                             MOVIF   MACRO   TAG
                                     IFIDN   <&TAG>,
                                     REP MOVSB
                                     EXITM
                                     ENDIF
                                     IFIDN   <&TAG>,
                                     REP MOVSW
                                     ELSE
                             ;       Не указан параметр B или W,
                             ;           по умолчанию принято B
                                     REP MOVSB
                                     ENDIF
                                     ENDM
                             ; -------------------------------------------
0000                         CSIG    SEGMENT PARA 'Code'
                                     ASSUME  CS:CSEG,DS:CSEG
                                     ASSUME  SS:CSEG,ES:CSEG
0100                                 ORG     100H
0100  EB 00                  BEGIN:  JMP     SHORT MAIN
                             ;       ...
0102                         MAIN    PROC    NEAR
                                     .LALL
                                     MOVIF   B
                           +         IFIDN   ,
0102  F3/A4                +         REP MOVSB
                           +         EXITM
                                     MOVIF   W
                           +         ENDIF
                           +         IFIDN   ,
0104  F3/A5                +         REP MOVSW
                           +         ENDIF
                                     MOVIF
                           +         ENDIF
                           +         ELSE
                           + ;Не указан парам. B или W, по умолч.принято B
                           + ;--------------------------------------------
0106  F3/A4                +         REP MOVSB
                           +         ENDIF
0108 C3                              RET
0109                         MAIN    ENDP
0109                         CSEG    ENDS
                                     END     BEGIN
__________________________________________________________________________

     Рис.20.7. Использование директивы IFIDN


     ОСНОВНЫЕ ПОЛОЖЕНИЯ НА ПАМЯТЬ
     ________________________________________________________________

     - Макросредства возможны только для полной версии ассемблера (MASM).

     - Использование   макрокоманд  в  программах  на  ассемблере  дает  в
результате более удобочитаемые программы и более производительный код.

     - Макроопределение состоит из директивы MACRO,  блока из  одного  или
нескольких   операторов,   которые  генерируются  при  макрорасширениях  и
директивы ENDM для завершения определения.

     - Код, который генерируется в программе по макрокоманде, представляет
собой макрорасширение.

     - Директивы .SALL,  .LALL и  .XALL  позволяют  управлять  распечаткой
комментариев и генерируемого объектного кода в макрорасширении.

     - Директива    LOCAL    позволяет    использовать    имена     внутри
макроопределений.   Директива   LOCAL   кодируется  непосредственно  после
директивы MACRO.

     - Использование формальных параметров  в  макроопределении  позволяет
кодировать параметры, обеспечивающие большую гибкость макросредств.

     - Библиотека    макроопределений    дает   возможность   использовать
макрокоманды для различных ассемблерных программ.

     - Условные директивы позволяют контролировать параметры макрокоманд.


     ВОПРОСЫ ДЛЯ САМОПРОВЕРКИ
     ________________________________________________________________

     20.1. Напишите необходимые директивы: а) для подавления всех  команд,
которые  генерирует  макрокоманда  и  б)  для  распечатки  только  команд,
генерирующих объектный код.

     20.2. Закодируйте  два  макроопределения  для  умножения:  а)  MULTBY
должна генерировать код для умножения байта  на  байт;  б)  MULTWD  должна
генерировать код для умножения слова на слово.  Для множителя и  множимого
используйте в макроопределении формальные параметры.  Проверьте выполнение
макрокоманд на небольшой программе, в которой также определены необходимые
области данных.

     20.3. Запишите макроопределения из вопроса 20.2 в  "макробиблиотеку".
Исправьте  программу  для  включения  элементов  библиотеки  по  директиве
INCLUDE в первом проходе ассемблирования.

     20.4. Напишите макроопределение BIPRINT, использующей  BIOS  INT  17H
для печати. Макроопределение должно включать проверку состояния принтера и
обеспечивать печать любых строк любой длины.

     20.5. Измените макроопределение на рис.20.6 для проверки делителя  на
ноль (для обхода деления).



ГЛАВА 21                                               Компоновка программ
__________________________________________________________________________

     Ц е л ь: Раскрыть технологию программирования, включающую  компоновку
и выполнение ассемблерных программ.


     ВВЕДЕНИЕ
     ________________________________________________________________

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

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

     Каждая программа ассемблируется  отдельно  и  генерирует  собственный
уникальный объектный (OBJ)  модуль.  Программа  компоновщик  (LINK)  затем
компонует объектные модули в один объединенный выполняемый  (EXE)  модуль.
Обычно выполнение начинается с основной программы, которая  вызывает  одну
или более подпрограмм. Подпрограммы, в свою очередь, могут вызывать другие
подпрограммы.
     На рис.21.1  показаны  два  примера  иерархической структуры основной
подпрограммы  и  трех  подпрограмм.  На  рис.21.1.(а)  основная  программы
вызывает  подпрограммы  1,  2  и  3.  На  рис.21.1.(б)  основная программа
вызывает подпрограммы 1 и 2, а подпрограмма 1 вызывает подпрограмму 3.
     Существует много разновидностей  организации  подпрограмм,  но  любая
организация должна быть "понятна" и ассемблеру, и  компоновщику,  и  этапу
выполнения.  Следует быть внимательным к ситуациям, когда,  например,  под
программа 1 вызывает подпрограмму 2, которая вызывает  подпрограмму  3  и,
которая в свою очередь вызывает подпрограмму 1. Такой  процесс,  известный
как рекурсия,  может  использоваться  на  практике,  но  при  неаккуратном
обращении может вызвать любопытные ошибки при выполнении.

__________________________________________________________________________

          a)      ------------¬             б) ------------¬
                  ¦ Основная  ¦                ¦ Основная  ¦
                  ¦ программа ¦                ¦ программа ¦
                  L-----T------                L-----T------
              ----------+---------¬             -----+----¬
          ----.---¬ ----.---¬ ----.---¬     ----.---¬ ----.---¬
          ¦ П/П 1 ¦ ¦ П/П 2 ¦ ¦ П/П 3 ¦     ¦ П/П 1 ¦ ¦ П/П 2 ¦
          L-------- L-------- L--------     L---T---- L--------
                                            ----.---¬
                                            ¦ П/П 3 ¦
                                            L--------
__________________________________________________________________________

     Рис.21.1. Иерархия программ.


     МЕЖСЕГМЕНТНЫЕ ВЫЗОВЫ
     ________________________________________________________________

     Команды CALL в предыдущих главах использовались для  внутрисегментных
вызовов, т.е. для вызовов внутри одного  сегмента.  Внутрисегментный  CALL
может быть короткий (в пределах от  +127  до  -128  байт)  или  длинный  (
превышающий указанные  границы).  В  результате  такой  операции  "старое"
значение в регистре IP запоминается в  стеке,  а  "новый"  адрес  перехода
загружается в этот регистр.
     Например, внутрисегментный CALL может иметь следующий объектный  код:
E82000. Шест.E8 представляет собой код операции, которая  заносит  2000  в
виде относительного адреса 0020 в регистр IP.  Затем процессор  объединяет
текущий адрес в регистре CS  и  относительный  адрес  в  регистре  IP  для
получения адреса следующей выполняемой команды.  При возврате из процедуры
команда RET восстанавливает из стека  старое  значение  в  регистре  IP  и
передает управление таким образом на следующую после CALL команду.
     Вызов в  другой  кодовый  сегмент  представляет  собой  межсегментный
(длинный) вызов.  Данная операция сначала  записывает  в  стек  содержимое
регистра CS и  заносит  в  этот  регистр  адрес  другого  сегмента,  затем
записывает в стек значение регистра IP и заносит новый относительный адрес
в этот регистр.
     Таким образом в  стеке  запоминаются  и  адрес  кодового  сегмента  и
смещение для последующего возврата из подпрограммы.
     Например, межсегментный CALL может состоять из следующего  объектного
кода:

               9A 0002 AF04

Шест.9A представляет  собой  код  команды  межсегментного  вызова  которая
записывает значение 0002 в виде 0200 в регистр IP, а значение AF04 в  виде
04AF в регистр CS. Комбинация этих адресов указывает на первую выполняемую
команду в вызываемой подпрограмме:


Страницы книги разрезаны по 70Кб, чтобы они не были гигантскими и недолго грузились, так что нажмите "Далее" чтобы читать дальше.
Назад  Далее

Материалы находятся на сайте https://exelab.ru/pro/



Оригинальный DVD-ROM: eXeL@B DVD !


Вы находитесь на EXELAB.rU
Проект ReactOS