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

ВИДЕОКУРС ВЗЛОМ
выпущен 10 декабря!


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

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

Под редакцией Еpшова В.Г.

               Кодовый сегмент          04AF0
               Смещение в IP             0200
               Действительный адрес     04CF0

     При выходе из вызванной процедуры межсегментная команда возврата  REP
восстанавливает оба адреса в регистрах CS и IP и  таким  образом  передает
управление на следующую после CALL команду.


     АТРИБУТЫ EXTRN и PUBLIC
     ________________________________________________________________

     Рассмотрим  основную  программу    (MAINPROG),    которая    вызывает
подпрограмму (SUBPROG) с помощью  межсегментного  CALL,  как  показано  на
рис.21.2.
     Команда CALL в MAINPROG должна "знать", что  SUBPROG  существует  вне
данного  сегмента  (иначе  ассемблер  выдаст  сообщение   о    том,    что
идентификатор SUBPROG не  определен).  С  помощью  директивы  EXTRN  можно
указать  ассемблеру,  что  ссылка  на  SUBPROG    имеет    атрибут    FAR,
т.е.определена в другом ассемблерном модуле.  Так  как  сам  ассемблер  не
имеет возможности точно определить такие ссылки,  он  генерирует  "пустой"
объектный код для последующего заполнения его при компановке:

               9A 0000 ---- E

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

          Запрос компоновщика LINK:     Ответ:

          Object Modules [.OBJ]:     B:MAINPROG+B:SUBPROG
          Run File [filespec.EXE]:   B:COMBPROG (или другое имя)
          List File [NUL.MAP]:       CON
          Libraries [.LIB]:          [return]

     Компоновщик устанавливает соответствия между адресами EXTRN  в  одном
объектном  модуле  с  адресами  PUBLIC  в  другом  и  заносит  необходимые
относительные адреса.  Затем он объединяет два  объектных  модуля  в  один
выполняемый.  При  невозможности  разрешить  ссылки  компоновщик    выдает
сообщения об ошибках.  Следите за этими сообщениями  прежде  чем  пытаться
выполнить программу.

__________________________________________________________________________

                    ---------------------------------¬
                    ¦            EXTRN   SUBPROG:FAR ¦
                    ¦ MAINPROG:  .                   ¦
                    ¦            .                   ¦
                    ¦            CALL    SUBPROG     ¦
                    ¦            .                   ¦
                    ¦            .                   ¦
                    +--------------------------------+
                    ¦            PUBLIC  SUBPROG     ¦
                    ¦ SUBPROG:   .                   ¦
                    ¦            .                   ¦
                    ¦            .                   ¦
                    ¦            RET                 ¦
                    L---------------------------------
__________________________________________________________________________

     Рис.21.2. Межсегментный вызов.


                             Директива EXTRN
                            -----------------
     Директива EXTRN имеет следующий формат:

               EXTRN     имя:тип [, ... ]

Можно определить более одного имени (до  конца  строки)  или  закодировать
дополнительные  директивы  EXTRN.    В    другом    ассемблерном    модуле
соответствующее имя должно быть определено и идентифицировано как  PUBLIC.
Тип элемента может быть ABS, BYTE, DWORD, FAR, NEAR, WORD.  Имя может быть
определено через EQU и должно удовлетворять реальному определению имени.


                             Директива PUBLIC
                            ------------------
     Директива PUBLIC  указывает  ассемблеру  и  компоновщику,  что  адрес
указанного идентификатора доступен из  других  программ.  Директива  имеет
следующий формат:

               PUBLIC    идентификатор [, ... ]

Можно  определить  более  одного  идентификатора  (до  конца  строки)  или
закодировать дополнительные директивы PUBLIC.  Идентификаторы  могут  быть
метками  (включая  PROC-метки),  переменными  или  числами.  Неправильными
идентификаторами  являются  имена    регистров    и    EQU-идентификаторы,
определяющие значения более двух байт.
     Рассмотрим три различных способа компановки программ.


      ПРОГРАММА: ИСПОЛЬЗОВАНИЕ ДИРЕКТИВ EXTRN и PUBLIC ДЛЯ МЕТОК
      _______________________________________________________________

__________________________________________________________________________
                                page    60,132
                        TITLE   CALLMULL1 (EXE) Вызов подпрограммы умнож.
                                EXTRN   SUBMUL:FAR
                        ;-----------------------------------------------
0000                    STACKSG         SEGMENT PARA STACK 'Stack'
0000    40 [ ???? ]             DW      64 DUP(?)
0080                    STACKSG         ENDS
                        ;-----------------------------------------------
0000                    DATASG  SEGMENT PARA 'Data'
0000  0140              QTY     DW      0140H
0002  2500              PRICE   DW      2500H
0004                    DATASG  ENDS
                        ;-----------------------------------------------
0000                    CODESG  SEGMENT PARA 'Code'
0000                    BEGIN   PROC    FAR
                                ASSUME  CS:CODESG,DS:DATASG,SS:STACKSG
0000  1E                        PUSH    DS
0001  2B C0                     SUB     AX,AX
0003  50                        PUSH    AX
0004  B8 ---- R                 MOV     AX,DATASG
0007  8E D8                     MOV     DS,AX
0009  A1 0002 R                 MOV     AX,PRICE    ;Загрузить стоимость
000C  8B 1E 0000 R              MOV     BX,QTY      ; и количество
0010  9A 0000 ---- E            CALL    SUBMUL      ;Вызвать подпрограмму
0015  CB                        RET
0016                    BEGIN   ENDP
0016                    CODESG  ENDS
                                END     BEGIN

Segments and Groups:
                N a m e         Size    Align   Combine Class
CODESG . . . . . . . . . . . .  0016    PARA    NONE    'CODE'
DATASG . . . . . . . . . . . .  0004    PARA    NONE    'DATA'
STACKSG. . . . . . . . . . . .  0080    PARA    STACK   'STACK'

Symbols:
                N a m e         Type    Value   Attr
BEGIN. . . . . . . . . . . . .  F PROC  0000    CODESG  Length=0016
PRICE. . . . . . . . . . . . .  L WORD  0002    DATASG
QTY. . . . . . . . . . . . . .  L WORD  0000    DATASG
SUBMUL . . . . . . . . . . . .  L FAR   0000            External


                                page    60,132
                        TITLE   SUBMUL  Подпрограмма для умножения
                        ;-----------------------------------------------
0000                    CODESG  SEGMENT PARA 'Code'
0000                    SUBMUL  PROC    FAR
                                ASSUME  CS:CODESG
                                PUBLIC  SUBMUL
0000  F7 E3                     MUL     BX    ;AX-стоимость, BX-количество
0002  CB                        RET           ;Произведение в DX:AX
0003                    SUBMUL  ENDP
0003                    CODESG  ENDS
                                END     SUBMUL

Segments and groups:
                N a m e         Size    Align   Combine Class
CODESG . . . . . . . . . . . .  0003    PARA    NONE    'CODE'

Symbols:
                N a m e         Type    Value   Attr
SUBMUL . . . . . . . . . . . .  F PROC  0000    CODESG  Clobal Length=0003
__________________________________________________________________________

LINK
IBM Personal Computer Linker
Version 2.30 (C) Copyright IBM Corp 1981, 1985
Object Modules: B:CALLMUL1+B:SUBMUL1
Run File: [B:CALLMUL1.EXE]:
List File:[NUL.MAP]: CON
Libraries [.LIB]:

 Start   Stop  Length  Name             Class

 00000H 00015H 0016H  CODESG            CODE  <--Примечание: 2 кодовых
 00020H 00022H 0003H  CODESG            CODE  <--  сегмента
 00030H 00033H 0004H  DATASG            DATA
 00040H 000BFH 0080H  STACKSG           STACK

Program entry point at 0000:0000
__________________________________________________________________________

     Рис. 21.3. Использование директив EXTRN и PUBLIC.


     Программа на  рис.21.3  состоит  из  основной  программы  CALLMUL1  и
подпрограммы SUBMUL1. В основной программе определены сегменты для  стека,
данных и кода.  В сегменте данных определены поля QTY и PRICE.  В  кодовом
сегменте регистр AX загружается значением PRICE, а регистр BX -  значением
QTY, после чего происходит вызов подпрограммы.  Директива EXTRN в основной
программе определяет SUBMUL как точку входа в подпрограмму.
     Подпрограмма  содержит  директиву  PUBLIC  (после  ASSUME),   которая
указывает компоновщику, что точкой входа  для  выполнения  является  метка
SUBMUL. Подпрограмма выполняет умножение содержимого регистра AX (цена) на
содержимое регистра BX (количество).  Результат умножения вырабатывается в
регистровой паре DX:AX в виде шест.002E 4000.
     Так как подпрограмма  не  определяет  каких-либо  данных,  то  ей  не
требуется сегмент данных.  Если бы подпрограмма имела сегмент  данных,  то
только она одна использовала бы свои данные.
     Также в подпрограмме не  определен  стековый  сегмент,  так  как  она
использует те же стековые адреса, что и основная программа. Таким образом,
стек  определенный  в  основной  программе  является   доступным    и    в
подпрограмме.  Для компоновщика необходимо обнаружить по крайней мере один
стек и определение стека в основной программе является достаточным.
     Рассмотрим теперь  таблицы  идентификаторов,   вырабатываемые   после
каждого   ассемблирования.   Обратите   внимание,  что  SUBMUL  в  таблице
идентификаторов для основной  программы  имеет  атрибуты  FAR  и  External
(внешний),  а  для подпрограммы - F (для FAR) и Global (глобальный).  Этот
последний атрибут указывает,  что данное имя доступно из вне подпрограммы,
т.е. глобально.
     Карта компановки (в конце листинга) отражает организацию программы  в
памяти.  Заметьте, что здесь имеются два  кодовых  сегмента  (для  каждого
ассемблирования)  с  разными  стартовыми   адресами.    Последовательность
расположения кодовых сегментов соответствует последовательности  указанных
для компановки объектных модулей (обычно  основная  программа  указывается
первой).  Таким образом, относительный адрес начала основной  программы  -
шест.00000, а подпрограммы - шест.00020.
     При трассировке выполнения программы можно  обнаружить,  что  команда
CALL SUBMUL имеет объектный код

               9A 0000 D413

Машинный код для межсегментного CALL - шест.9A.  Эта команда  сохраняет  в
стеке регистр IP и загружает в  него  значение  0000,  сохраняет  в  стеке
значение шест.13D2 из регистра CS и загружает в него шест.D413.  Следующая
выполняемая команда находится по адресу  в  регистровой  паре  CS:IP  т.е.
13D40 плюс 0000. Обратите внимание, что основная программа  начинается  по
адресу в регистре CS, содержащему шест.13D2, т.е. адрес  13D20.  Из  карты
компановки видно,  что подпрограмма начинается  по  относительному  адресу
шест.0020.  Складывая  эти  два  значения,  получим  действительный  адрес
кодового сегмента для подпрограммы:

               Адрес в CS               13D20
               Смещение в IP             0020
               Действительный адрес     13D40

Компоновщик определяет это значение точно таким же образом, и  подставляет
его в операнд команды CALL.


     ПРОГРАММА: ИСПОЛЬЗОВАНИЕ ДИРЕКТИВЫ PUBLIC В КОДОВОМ СЕГМЕНТЕ
     ________________________________________________________________

__________________________________________________________________________

                                page    60,132
                        TITLE   CALLMUL2 (EXE)  Вызов подпрограммы умнож.
                                EXTERN  SUBMUL:FAR
                        ;----------------------------------------------
0000                    STACKSG         SEGMENT PARA STACK 'Stack'
0000  40 [????]                         DW      64 DUP(?)
0080                    STACKSG         ENDS
                        ;----------------------------------------------
0000                    DATASG  SEGMENT PARA 'Data'
0000     0140           QTY     DW      0140H
0002    2500            PRICE   DW      2500H
0004                    DATASG  ENDS
                        ;----------------------------------------------
0000                    CODESG  SEGMENT PARA  PUBLIC 'Code'
0000                    BEGIN   PROC    FAR
                                ASSUME  CS:CODESG,DS:DATASG,SS:STACKSG
0000    1E                      PUSH    DS
0001    2B C0                   SUB     AX,AX
0003    50                      PUSH    AX
0004    B8 ---- R               MOV     AX,DATASG
0007    8E D8                   MOV     DS,AX
0009    A1 0002 R               MOV     AX,PRICE   ;Загрузить стоимость
000C    8B 1E 0000 R            MOV     BX,QTY     ; и количество
0010    9A 0000 ---- E          CALL    SUBMUL     ;Вызвать подпрограмму
0015    CB                      RET
0016                    BEGIN   ENDP

0016                    CODESG  ENDS
                                END     BEGIN
________________________________________________________________________

Segments and Group:
                N a m e         Size    Align   Combine Class
CODESG . . . . . . . . . . . . .0016    PARA    PUBLIC  'CODE'
DATASG . . . . . . . . . . . . .0004    PARA    NONE    'DATA'
STACKSG. . . . . . . . . . . . .0080    PARA    STACK   'STACK'

Symbols:
                N a m e         Type    Value   Attr
BEGIN. . . . . . . . . . . . . F PROC   0000    CODESG Lenght=0016
PRICE. . . . . . . . . . . . . L WORD   0002    DATASG
QTY. . . . . . . . . . . . . . L WORD   0000    DATASG
SUBMUL . . . . . . . . . . . . L FAR    0000           External


                                page    60,132
                        TITLE   SUBMUL2 Вызываемая подпрограмма умножения
                        ;----------------------------------------------
0000                    CODESG  SEGMENT PARA    PUBLIC 'CODE'
0000                    SUBMUL  PROC    FAR
                                ASSUME  CS:CODESG
                                PUBLIC  SUBMUL
0000    F7 E3                   MUL     BX      ;AX-стоимость, BX-количество
0002    CB                      RET             ;Произведение в DX:AX
0003                    SUBMUL  ENDP
0003                    CODESG  ENDS
                                END     SUBMUL

Segments and Groups:
                N a m e         Size    Align   Combine Class
CODESG. . . . . . . . . . . . . 0003    PARA    PUBLIC  'CODE'

Symbols:
                N a m e         Type    Value   Attr
SUBMUL. . . . . . . . . . . . .F PROC   0000    CODESG  Global  Length=0003


LINK
IBM Personal Computer Linker
Version 2.30 (C) Copyright IBM Corp 1981, 1985
Object Modules: B:CALLMUL2+B:SUBMUL2
Run File: [B:CALLMUL2.EXE]:
List File: [NUL.MAP]: CON
Libraries [.LIB]:

 Start   Stop  Length  Name             Class

 00000H 00022H 0023H  CODESG            CODE  <-- Примечание: 1 сегмент кода
 00030H 00033H 0004H  DATASG            DATA
 00040H 000BFH 0080H  STACKSG           STACK

Program entry point at 0000:0000
__________________________________________________________________________

     Рис.21.4. Кодовый сегмент, определенный как PUBLIC.


     Следующий пример на рис.21.4 представляет собой вариант программы  на
рис.21.3.  Имеется  одно  изменение  в  основной  программе  и  одно  -  в
подпрограмме.  В обоих случаях в директиве  SEGMENT  используется  атрибут
PUBLIC:

               CODESG    SEGMENT   PARA PUBLIC 'CODE'

     Рассмотрим результирующую  карту  компоновки  и объектный код команды
CALL.
     Из таблицы идентификаторов (в конце каждого листинга ассемблирования)
следует: обобщенный тип кодового сегмента CODESG  -  PUBLIC  (на  рис.21.3
было NONE).  Но более интересным является то, что карта компановки в конце
листинга показывает теперь только один кодовый сегмент! Тот факт, что  оба
сегмента имеют одни и те же имя (CODESG), класс ('CODE') и атрибут PUBLIC,
заставил компоновщика объединить два логических кодовых  сегмента  в  один
физический  кодовый  сегмент.  Кроме  того,  при  трассировке   выполнения
программы можно обнаружить, что теперь команда вызова  подпрограммы  имеет
следующий объектный код:

               9A 2000 D213

Эта команда  заносит шест.2000 в регистр IP и шест.D213 в регистр CS.  Так
как подпрограмма находится в общем с основной программой кодовом сегменте,
то  в  регистре CS устанавливается тот же стартовый адрес - шест.D213.  Но
теперь смещение равно шест.0020:

               Адрес в CS:              13D20
               Смещение в IP:            0020
               Действительный адрес:    13D40

Таким образом,  кодовый  сегмент  подпрограммы  начинается,  очевидно,  по
адресу шест.13D40. Правильно ли это? Карта компановки не  дает  ответа  на
этот вопрос, но можно определить адрес  по  листингу  основной  программы,
которая заканчивается на смещении шест.0016. Так как кодовый  сегмент  для
подпрограммы определен как SEGMENT, то он  должен  начинаться  на  границе
параграфа, т.е. его адрес должен нацело делиться  на  шест.10  или  правая
цифра адреса должна быть равна 0. Компоновщик  размещает  подпрограмму  на
ближайшей границе параграфа непосредственно  после  основной  программы  -
этот  относительный  адрес  равен  шест.00020.  Поэтому  кодовый   сегмент
подпрограммы начинается по адресу 13D20 плюс 0020 или 13D40.

                __________________________________________
               |   Основная программа...   | Подпрограмма |
               | (не используемый участок) |              |
               |___________________________|______________|
                 |            |             |
               13D20        13D30         13D40

Рассмотрим, каким образом компоновщик  согласует  данные,  определенные  в
основной программе и имеющие ссылки из подпрограммы.


     ПРОГРАММА: ОБЩИЕ ДАННЫЕ В ПОДПРОГРАММЕ
     ________________________________________________________________

__________________________________________________________________________

                                page    60,132
                        TITLE   CALLMUL3 (EXE)  Вызов подпрограммы
                        ;                       для умножения
                                EXTRN   SUBMUL:FAR
                                PUBLIC  QTY,PRICE
                        ;-------------------------------------------------
0000                    STACKSG         SEGMENT PARA STACK 'Stack'
0000    40 [????]               DW      64 DUP(?)
0080                    STACKSD         ENDS
                        ;-------------------------------------------------
0000                    DATASG  SEGMENT PARA PUBLIC 'Data'
0000    0140            QTY     DW      0140H
0002    2500            PRICE   DW      2500H
0004                    DATASG  ENDS
                        ;-------------------------------------------------
0000                    CODESG  SEGMENT PARA PUBLIC 'Code'
0000                    BEGIN   PROC    FAR
                                ASSUME  CS:CODESG,DS:DATASG,SS:STACKSG
0000    1E                      PUSH    DS
0001    2B C0                   SUB     AX,AX
0003    50                      PUSH    AX
0004    B8 ---- R               MOV     AX,DATASG
0007    8E D8                   MOV     DS,AX
0009    9A 0000 ---- E          CALL    SUBMUL    ;Вызвать подпрограмму
000E    CB                      RET
000F                    BEGIN   ENDP

000F                    CODESG  ENDS
                                END     BEGIN
_____________________________________________________________________

Segments and Groups:
                N a m e         Size    Align   Combine Class
CODESG . . . . . . . . . . . .  000F    PARA    PUBLIC  'CODE'
DATASG . . . . . . . . . . . .  0004    PARA    PUBLIC  'DATA'
STACKSG. . . . . . . . . . . .  0080    PARA    STACK   'STACK'

Symbols:
                N a m e         Type    Value   Attr
BEGIN. . . . . . . . . . . . .  F PROC  0000    CODESG  Length=000F
PRICE. . . . . . . . . . . . .  L WORD  0002    DATASG  Global
QTY. . . . . . . . . . . . . .  L WORD  0000    DATASG  Global
SUBMUL . . . . . . . . . . . .  L FAR   0000            External


                                page    60,132
                        TITLE   SUBMUL  Подпрограмма для умножения
                                EXTRN   QTY:WORD,PRICE:WORD
                        ;-------------------------------------------------
0000                    CODESG  SEGMENT PARA PUBLIC 'CODE'
0000                    SUBMUL  PROC    FAR
                                ASSUME  CS:CODESG
                                PUBLIC  SUBMUL
0000    A1 0000 E               MOV     AX,PRICE
0003    8B 1E 0000 E            MOV     BX,QTY
0007    F7 E3                   MUL     BX         ;Произведение в DX:AX
0009    CB                      RET
000A                    SUBMUL  ENDP
000A                    CODESG  ENDS
                                END     SUBMUL
_____________________________________________________________________

Segments and Groups:
                N a m e         Size    Align  Combine Class
CODESG . . . . . . . . . . . .  000A    PARA   PUBLIC  'CODE'

Symbols:
                N a m e         Type    Value  Attr
PRICE. . . . . . . . . . . . .  V WORD  0000           External
QTY. . . . . . . . . . . . . .  V WORD  0000           External
SUBMUL . . . . . . . . . . . .  F PROC  0000   CODESG  Global  Length=000A
_____________________________________________________________________

LINK
IBM Personal Computer Linker
Version 2.30 (C) Copyright IBM Corp 1981, 1985
Object Modules: B:CALLMUL3+B:SUBMUL3
Run File: [B:CALLMUL3.EXE]:
List File: [NUL.MAP]: CON
Libraries [.LIB]:

 Start   Stop  Length  Name             Class

 00000H 00019H 001AH  CODESG            CODE
 00030H 00033H 0004H  DATASG            DATA
 00040H 000BFH 0080H  STACKSG           STACK

PROGRAM entry point at 0000:0000
__________________________________________________________________________

     Рис.21.5. Общие данные в подпрограмме.


     Наличие общих  данных  предполагает  возможность  обработки  в  одном
ассемблерном модуле  данных,  которые  определены  в  другом  ассемблерном
модуле.  Изменим  предыдущий  пример  так,  чтобы  области  QTY  и   PRICE
по-прежнему определялись в основной программе,  но  загрузка  значений  из
этих областей в  регистры  BX  и  AX  выполнялась  в  подпрограмме.  Такая
программа приведена на рис.21.5. В ней сделаны следующие изменения:

          - В основной программе имена QTY и PRICE определены как  PUBLIC.
     Сегмент данных также определен с атрибутом PUBLIC.  Обратите внимание
     на  атрибут  Global  (глобальный)  для  QTY  и  PRICE    в    таблице
     идентификаторов.

          - В подпрограмме имена QTY и PRICE определены как EXTRN и  WORD.
     Такое определение указывает ассемблеру на длину этих полей в 2 байта.
     Теперь ассемблер сгенерирует правильный код операции для команд  MOV,
     а компоновщик установит значения операндов. Заметьте, что имена QTY и
     PRICE в таблице идентификаторов имеют атрибут External (внешний).

     Команды MOV в листинге подпрограммы имеют следующий вид:

               A1 0000 E      MOV  AX,PRICE
               8B 1E 0000 E   MOV  BX,QTY

В объектном коде шест.A1 обозначает пересылку слова из  памяти  в  регистр
AX, а шест.8B - пересылку слова из памяти в регистр BX (объектный код  для
операций с регистром AX чаще требует меньшее число байтов, чем  с  другими
регистрами).  Трассировка выполнения программы показывает, что компоновщик
установил в объектном коде следующие операнды:

               A1 0200
               8B 1E 0000

Объектный код теперь идентичен коду сгенерированному в предыдущем примере,
где команды MOV находились в вызывающей программе. Это логичный результат,
так как операнды во всех трех программах базировались  по  регистру  DS  и
имели одинаковые относительные адреса.
     Основная программа  и  подпрограмма  могут  определять  любые  другие
элементы данных, но общими являются лишь имеющие атрибуты PUBLIC и EXTRN.
     Следуя основным правилам, рассмотренным в данной главе, можно  теперь
компоновать программы, состоящие более чем из двух ассемблерных модулей  и
обеспечивать доступ к общим данным  из  всех  модулей.  При  этом  следует
предусматривать стек достаточных  размеров  -  в  разумных  пределах,  для
больших программ определение 64 слов для стека бывает достаточным.
     В гл.23 будет рассмотрены дополнительные свойства сегментов,  включая
определение более одного сегмента  данных  и  кодового  сегмента  в  одном
ассемблерном  модуле  и  использование  директивы  GROUP  для  объединения
сегментов в один общий сегмент.


     ПЕРЕДАЧА ПАРАМЕТРОВ
     ________________________________________________________________

__________________________________________________________________________

                                page    60,132
                        TITLE   CALLMULL4 (EXE) Передача параметров
                        ;                       в подпрограмму
                                EXTRN   SUBMUL:FAR
                        ;-------------------------------------------------
0000                    STACKSG         SEGMENT PARA STACK 'Stack'
0000    40 [ ???? ]             DW      64 DUP(?)
0080                    STACKSG         ENDS
                        ;-------------------------------------------------
0000                    DATASG  SEGMENT PARA 'Data'
0000  0140              QTY     DW      0140H
0002  2500              PRICE   DW      2500H
0004                    DATASG  ENDS
                        ;-------------------------------------------------
0000                    CODESG  SEGMENT PARA PUBLIC 'Code'
0000                    BEGIN   PROC    FAR
                                ASSUME  CS:CODESG,DS:DATASG,SS:STACKSG
0000  1E                        PUSH    DS
0001  2B C0                     SUB     AX,AX
0003  50                        PUSH    AX
0004  B8 ---- R                 MOV     A,DATASG
0007  8E D8                     MOV     DS,AX
0009  FF 36 0002 R              PUSH    PRICE
000D  FF 36 0000 R              PUSH    QTY
0011  9A 0000 ---- E            CALL    SUBMUL      ;Вызвать подпрограмму
0016  CB                        RET
0017                    BEGIN   ENDP
0017                    CODESG  ENDS
                                END     BEGIN
_____________________________________________________________________

Segments and Groups:
                N a m e         Sise    Align   Combine Class
CODESG . . . . . . . . . . . .  0017    PARA    NONE    'CODE'
DATASG . . . . . . . . . . . .  0004    PARA    NONE    'DATA'
STACKSG. . . . . . . . . . . .  0080    PARA    STACK   'STACK'

Symbols:
                N a m e         Type    Value   Attr
BEGIN. . . . . . . . . . . . .  F PROC  0000    CODESG  Length=0017
PRICE. . . . . . . . . . . . .  L WORD  0002    DATASG
QTY. . . . . . . . . . . . . .  L WORD  0000    DATASG
SUBMUL . . . . . . . . . . . .  L FAR   0000            External
_____________________________________________________________________

                                page    60,132
                        TITLE   SUBMUL  Вызываемая подпрограмма умножения
0000                    CODESG  SEGMENT PARA PUBLIC 'Code'
0000                    SUBMUL  PROC    FAR
                                ASSUME  CS:CODESG
                                PUBLIC  SUMBUL
0000    55                      PUSH    BP
0001    8P EC                   MOV     BP,SP
0003    8B 46 08                MOV     AX,[BP+8]    ;Стоимость
0006    8B 5E 06                MOV     BX,[BP+6]    ;Количество
0009    F7 E3                   MUL     BX           ;Произведение в DX:AX
000B    5D                      POP     BP
000F                    SUMBUL  ENDP
000F                    CODESG  ENDS
                                END
_____________________________________________________________________

Segments and Groups:
                N a m e         Size    Align   Combine Class
CODESG . . . . . . . . . . . .  000F    PARA    PUBLIC  'CODE'

Symbols:
                N a m e         Type    Value   Attr
SUBMUL . . . . . . . . . . . .  F PROC  0000    CODESG  Global Length=000F
_____________________________________________________________________

LINK
IBM Personal Computer Linker
Version 2.30 (C) Copyright IBM Corp 1981, 1985
Object Modules: B:CALLMUL4+B:SUBMUL4
Run File: [B:CALLMUL4.EXE]:
List File: [NUL.MAP]: CON
Libraries [.LIB]:

 Start   Stop  Length  Name             Class

 00000H 00019H 001AH  CODESG            CODE
 00030H 00033H 0004H  DATASG            DATA
 00040H 000BFH 0080H  STACKSG           STACK

PROGRAM entry point at 0000:0000
__________________________________________________________________________

     Рис.21.6. Передача параметров.


     Другим  способом  обеспечения  доступа  к  данным    из    вызываемой
подпрограммы  является  передача  параметров.  В  этом  случае  вызывающая
программа физически передает данные через стек. Каждая команда PUSH должна
записывать в стек данные размером в одно слово из памяти или из регистра.
     Программа, приведенная на рис.21.6, прежде чем  вызвать  подпрограмму
SUBMUL заносит в стек значения из полей PRICE и QTY.  После  команды  CALL
стек выглядит следующим образом:

               ... | 1600 | D213 | 4001 | 0025 | 0000 | C213 |
                      6      5      4      3      2      1

1.   Инициализирующая команда PUSH DS заносит адрес сегмента в стек.  Этот
     адрес может отличаться в разных версиях DOS.
2.   Команда PUSH AX заносит в стек нулевой адрес.
3.   Команда PUSH PRICE заносит в стек слово (2500).
4.   Команда PUSH QTY заносит в стек слово (0140).
5.   Команда CALL заносит в стек содержимое регистра CS (D213)
6.   Так как команда CALL представляет здесь межсегментный вызов,то в стек
     заносится также содержимое регистра IP (1600).

     Вызываемая программа использует регистр BP для доступа к параметрам в
стеке, но прежде она запоминает содержимое регистра BP,  записывая  его  в
стек.  В данном случае, предположим, что регистр BP содержит  нуль,  тогда
нулевое слово будет записано в вершине стека (слева).
     Затем программа помещает в регистр BP содержимое из регистра SP,  так
как в качестве индексного регистра может использоваться регистр BP, но  не
SP. Команда загружает в регистр BP значение 0072. Первоначально регистр SP
содержал размер пустого стека, т.е. шест.80. Запись каждого слова  в  стек
уменьшает содержимое SP на 2:

               | 0000 | 1600 | D213 | 4001 | 0025 | 0000 | C213 |
                  |      |      |      |      |      |      |
          SP:     72     74     76     78     7A     7C     7E

     Так как BP теперь также содержит 0072, то параметр цены (PRICE) будет
по адресу BP+8, а параметр количества (QTY) - по  адресу  BP+6.  Программа
пересылает эти величины из стека в  регистры  AX  и  BX  соответственно  и
выполняет умножение.
     Перед  возвратом  в    вызывающую    программу    в    регистре    BP
восстанавливается первоначальное значение,  а  содержимое  в  регистре  SP
увеличивается на 2, с 72 до 74.
     Последняя  команда  RET  представляет  собой  "длинный"  возврат    в
вызывающую программу. По этой команде выполняются следующие действия:

          - Из вершины стека восстанавливается значение регистра IP (1600)
          - Содержимое регистра SP увеличивается на 2, от 74 до 76.
          - Из новой вершины стека восстанавливается значение  регистра  CS
     (D213).
          - Содержимое регистра SP увеличивается на 2 от 76 до 78.

     Таким  образом  осуществляется  корректный  возврат   в    вызывающую
программу. Осталось одно небольшое пояснение. Команда RET закодирована как

               RET  4

     Параметр 4 представляет собой число байт в стеке  использованных  при
передаче параметров (два слова в данном случае). Команда RET прибавит этот
параметр к содержимому регистра SP, получив значение 7C. Таким образом, из
стека исключаются ненужные больше параметры.  Будьте особенно  внимательны
при восстановлении регистра SP - ошибки могут привести  к  непредсказуемым
результатам.


     КОМПОНОВКА ПРОГРАММ НА BASIC-ИНТЕРПРЕТАТОРЕ И АССЕМБЛЕРЕ
     ________________________________________________________________

     В руководстве по языку BASIC для IBM PC приводятся  различные  методы
связи BASIC-интерпретатора и программ на ассемблере. Для этого имеются две
причины:  сделать   возможным    использование    BIOS-прерываний    через
ассемблерные модули и создать более эффективные  программы.  Цель  данного
раздела - дать общий обзор по данному вопросу; повторять здесь технические
подробности из руководства по языку BASIC нет необходимости.
     Для связи с BASIC ассемблерные программы кодируются, транслируются  и
компонуются отдельно.  Выделение памяти для подпрограмм на машинном  языке
может быть либо внутри, либо вне  64  Кбайтовой  области  памяти,  которой
ограничен BASIC. Выбор лежит на программисте.
     Существует  два  способа  загрузки  машинного    кода    в    память:
использование оператора языка BASIC - POKE или объединение скомпонованного
модуля с BASIC-программой.


                   Использование BASIC-оператора POKE
                  ------------------------------------
     Хотя это и самый простой  способ,  но  он  удобен  только  для  очень
коротких подпрограмм.  Способ заключается в том, что сначала  определяется
объектный код ассемблерной программы по LST-файлу или с помощью  отладчика
DEBUG. Затем   шестнадцатиричные  значения  кодируются  непосредственно  в
BASIC-программе в операторах DATA.  После этого с помощью  BASIC-оператора
READ  считывается  каждый  байт  и  оператором POKE заносится в память для
выполнения.


                     Компановка ассемблерных модулей
                    ---------------------------------
     С большими ассемблерными подпрограммами обычно проще иметь дело, если
они оттранслированы и скомпонованы как выполнимые (EXE) модули. Необходимо
организовать BASIC-программу и выполнимый модуль в рабочую программу.  При
работе с BASIC-программой не забывайте пользоваться командой BSAVE  (BASIC
save)   для   сохранения  программы  и  BLOAD  -  для  загрузки  ее  перед
выполнением.
     Прежде чем кодировать BASIC-  и  ассемблерную  программы,  необходимо
решить, каким из двух способов они будут связаны.  В языке BASIC  возможны
два способа: функция USR и оператор CALL. В обоих способах регистры DS, ES
и SS на входе содержат указатель на  адресное  пространство  среды  BASIC.
Регистр CS содержит текущее значение,  определенное  последним  оператором
DEF SEG (если он  имеется).  Стековый  указатель  SP  указывает  на  стек,
состоящий только из восьми слов, так  что  может  потребоваться  установка
другого стеке в подпрограмме.  В  последнем  случае  необходимо  на  входе
сохранить значение указателя текущего стека,  а  при  выходе  восстановить
его.  В  обоих  случаях  при  выходе  необходимо  восстановить    значение
сегментных  регистров  и  SP  и  обеспечить  возврат  в  BASIC  с  помощью
межсегментного возврата RET.
     Скомпонуйте ваш  ассемблированный  объектный  файл  так,  что  бы  он
находился в старших адресах памяти.  Для этого используется параметр  HIGH
при ответе на второй запрос компоновщика, например,  B:имя/HIGH.  Затем  с
помощью отладчика DEBUG необходимо загрузить EXE-подпрограмму и по команде
R определить значения в регистрах CS и IP:  они  показывают  на  стартовый
адрес подпрограммы.  Находясь в отладчике укажите имя (команда N) BASIC  и
загрузите его командой L.
     Два способа связи BASIC-программы и EXE-подпрограммы -  использование
операторов USR  или  CALL.  Работая  в  отладчике,  необходимо  определить
стартовый адрес EXE-подпрограммы  и,  затем,  указать  этот  адрес  или  в
операторе USRn или в CALL.  В  руководстве  по  языку  BASIC  для  IBM  PC
детально представлено описание функции USRn и оператора CALL с  различными
примерами.


                Программа: Компоновка BASIC и ассемблера
               ------------------------------------------
__________________________________________________________________________

LOAD"D:BASTEST.BAS

LIST
010     CLEAR ,32768!
020     ' для BLOAD
030     ' для DEFSEG
040     ' для точки входа в CALL
050     ' для вызова ASM-модуля
060     FOR N = 1 TO 5
070     INPUT "Hours "; H
080     INPUT "Rate "; R
090     W = H * R
100     PRINT "Wage = " W
110     NEXT N
120     END
_____________________________________________________________________

TITLE   LINKBAS Ассемблерная подпрограмма, вызываемая из BASIC
CODESG  SEGMENT PARA 'CODE'
        ASSUME  CS:CODESG
CLRSCRN PROC    FAR
        PUSH    BP              ;Сохранить BP
        MOV     BP,SP           ;База списка параметров
        MOV     AX,0600H        ;Функция прокрутки
        MOV     BH,07           ; всего
        MOV     CX,0000         ; экрана
        MOV     DX,184FH
        INT     10H
        POP     BP
        RET                     ;Завершить подпрограмму
CLRSCRN ENDP
CODESG  ENDS
        END
__________________________________________________________________________

     Рис.21.7. Основная программа на языке BASIC и подпрограмма на
               ассемблере.

     Рассмотрим  теперь  простой  пример    компановки    программы    для
BASIC-интерпретатора  и  подпрограммы  на  ассемблере.  В  этом    примере
BASIC-программа запрашивает ввод значений времени и расценки и выводит  на
экран их  произведение  -  размер  зарплаты.  Цикл  FOR-NEXT  обеспечивает
пятикратное выполнение ввода и затем программа завершается.  Пусть  BASIC-
программа вызывает ассемблерный модуль, который очищает экран.
     На  рис.  21.7  приведена  исходная  BASIC-программа  и  ассемблерная
подпрограмма.  Обратите внимание на следующие особенности BASIC-программы:
оператор 10 очищает 32К байт памяти; операторы 20, 30, 40  и  50  временно
содержат комментарии.
     Позже мы вставим BASIC-операторы для связи  с  ассемблерным  модулем.
BASIC-программу можно сразу  проверить.  Введите  команду  BASIC  и  затем
наберите все пронумерованные операторы так, как они  показаны  в  примере.
Для выполнения программы нажмите F2. Не забудьте сохранить текст программы
с помощью команды

               SAVE "B:BASTEST.BAS"

     Обратите внимание на следующие особенности ассемблерной подпрограммы:

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

     Все что осталось  -  это  связать  эти  программы  вместе.  Следующие
действия предполагают, что системная дискета (DOS) находится на  дисководе
A, а рабочие программы - на дисководе B:

1.   Наберите  ассемблерную  подпрограмму,   сохраните   ее   под   именем
     B:LINKBAS.ASM и оттранслируйте ее.
2.   Используя компоновщик LINK,  сгенерируйте объектный  модуль,  который
     будет загружаться в старшие адреса памяти:

               LINK B:LINKBAS,B:LINKBAS/HIGH,CON;

3.   С помощью отладчика DEBUG загрузите BASIC - компилятор: DEBUG
     BASIC.COM.
4.   По  команде  отладчика R  выведите  на  экран  содержимое  регистров.
     Запишите значения в регистрах SS, CS и IP.
5.   Теперь установите имя и загрузите скомпонованный ассемблерный  модуль
     следующими командами:
                             N B:LINKBAS.EXE
                             L

6.   По команде R  выведите  на  экран  содержимое  регистров  и  запишите
     значения в CX, CS и IP.
7.   Замените содержимое регистров SS, CS и IP значениями из шага 4.  (Для
     этого служат команды R SS, R CS и R IP).
8.   Введите команду отладчика G (go) для передачи управления  в BASIC. На
     экране должен появиться запрос из BASIC-программы.
9.   Для того,  чтобы  сохранить  ассемблерный  модуль,  введите следующие
     команды (без номеров операторов):

               DEF SEG = &Hxxxx (значение в CS из шага 6)
               BSAVE "B:CLRSCRN.MOD",0,&Hxx (значение в CX из шага 6)

          Первая команда обеспечивает адрес загрузки модуля в  память  для
     выполнения.  Вторая команда идентифицирует имя модуля,  относительную
     точку входа и размер модуля. По второй команде система запишет модуль
     на дисковод B.
10.  Теперь  необходимо  модифицировать  BASIC-программу  для  компоновки.
     Можно загрузить ее сразу,  находясь  в  отладчике,  но  вместо  этого
     наберите команду SYSTEM для выхода из BASIC и,  затем,  введите Q для
     выхода из отладчика DEBUG. На экране должно появиться приглашение DOS
11.  Введите команду BASIC,  загрузите BASIC-программу и  выведите  ее  на
     экран:
               BASIC
               LOAD "B:BASTEST.BAS"
               LIST

12.  Измените операторы 20, 30, 40 и 50 следующим образом:

               20   BLOAD "B:CLRSCRN.MOD"
               30   DEF SEG = &Hxxxx (значение в CS из шага 6)
               40   CLRSCRN = 0      (точка входа в подпрограмму)
               50   CALL CLRSCRN     (вызов подпрограммы)

13.  Просмотрите, выполните и сохраните измененную BASIC-программу.


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

               CALL подпрограмма (параметр-1,параметр-2,...)

     Ассемблерная подпрограмма может получить доступ  к  этим  параметрам,
используя регистр BP в виде [BP], как это делалось ранее  на  рис.21.3.  В
этом случае необходимо определить операнд в команде  RET,  соответствующий
длине адресов параметров в стеке.  Например, если оператор  CALL  передает
три параметра то возврат должен быть закодирован в виде RET 6.

__________________________________________________________________________

D>LINK

IBM Personal Computer Linker
Version 2.30 (C) Copyright IBM Corp. 1981, 1985

Object Modules [.OBJ]: LINKBAS
Run File [LINKBAS.EXE]: LINKBAS/HIGH
List File [NUL.MAP]: CON
Libraries [.LIB]:
Warning: no stack segment

 Start  Stop   Length Name              Class
 00000H 00011H 00012H CODESG            CODE
D>DEBUG BASIC.COM
-R
AX=0000  BX=0000  CX=0012  DX=0000  SP=FFFF  BP=0000  SI=0000  DI=0000
DS=1410  ES=1410  SS=1410  CS=1410  IP=0100   NV UP EI PL NZ NA PO NC
1410:0100 E9E03E        JMP     3FE3
-N D:LINKBAS.EXE
-L
-R
AX=FFA3  BX=0000  CX=0012  DX=0000  SP=0000  BP=0000  SI=0000  DI=0000
DS=1410  ES=1410  SS=9FE0  CS=9FE0  IP=0000   NV UP EI PL NZ NA PO NC
9FE0:0000  55
-R SS
SS 9FE0
:1410
-R CS
CS 9FE0
:1410
-R IP
IP 0000
:0100
-G
Ok
DEF SEG = &H9EF0
Ok
BSAVE "D:CLRSCREEN.MOD",0,&H12
Ok
SYSTEM
Program terminated normally
-Q
D>BASIC
IBM Personal Computer Basic
Ver4sion D3.10 Copyright IBM Corp. 1981, 1985
61310 Bytes free
Ok
LOAD"D:BASTEST.BAS
Ok
20      BLOAD "D:CLRSCREEN.MOD"
30      DEF SEG = &H9FE0
40      CLRSCRN = 0
50      CALL CLRSCRN
LIST

10      CLEAR ,32768!
20      BLOAD "D:CLRSCRN.MOD"
30      DEF SEG = &H9FE0
40      CLRSCRN = 0
50      CALL CLRSCRN
60      FOR N = 1 TO 5
70      INPUT "HOURS"; H
80      INPUT "rATE"; R
90      W = H * R
100     PRINT "WAGE = " W
110     NEXT N
120     END
Ok
__________________________________________________________________________

     Рис.21.8. Этапы связи BASIC и ассемблера.


     КОМПОНОВКА ПРОГРАММ НА ЯЗЫКЕ PASCAL И АССЕМБЛЕРЕ
     ________________________________________________________________

__________________________________________________________________________

program pascall ( input, output );

    procedure move_cursor( const row: integer;
                           const col: integer ); extern;
    var
        temp_row:       integer;
        temp_col:       integer;

    begin
        write( 'Enter cursor row: ' );
        readln( temp_row );

        write( 'Enter cursor column:' );
        readln( temp_col );

        move_cursor( temprow, temp_col );
        write( 'New cursor location' );
      end.
_____________________________________________________________________

TITLE   MOVCUR  Подпрограмма на ассемблере,
;               вызываемая из программы на Паскале
        PUBLIC  MOVE_CURSOR
;----------------------------------------------------------
;    MOVE_CURSOR:       Устанавливает курсор
;                       по переданным параметрам
;    Параметры: const row       Строка и столбец
;               const col       для установки курсора
;    Возвращаемое значение:     Отсутствует
;----------------------------------------------------------
CODESEG SEGMENT PARA PUBLIC 'CODE'

MOVE_CURSOR PROC FAR
        ASSUME  CS:CODESEG
ROWWPAR EQU     8               ;Параметр "строка"
COLPAR  EQU     6               ;Параметр "столбец"

        PUSH    BP              ;Сохранить регистр BP
        MOV     BP,SP           ;Установить BP на параметры

        MOV     SI,[BP+ROWPAR]  ;SI указывает на строку
        MOV     DH,[SI]         ;Поместить столбец в DL

        MOV     AH,02           ;Функция установки курсора
        SUB     BH,BH           ;Страница #0
        INT     10H

        POP     BP              ;Вернуться
        RET     4               ; в вызывающую программу
MOVE_CURSOR       ENDP
CODESEG ENDS
        END
__________________________________________________________________________

     Рис.21.9. Компановка PASCAL-ассемблер.


     В  данном  разделе  показано,  как  можно  установить  связь    между
программами на  языке  PASCAL  фирм  IBM  и  MicroSoft  с  программами  на
ассемблере.  На рис.21.9 приведен пример связи простой PASCAL-программы  с
ассемблерной подпрограммой.  PASCAL-программа скомпилирована для получения
OBJ-модуля, а ассемблерная программа оттранслирована также  для  получения
OBJ-модуля.  Программа LINK затем компонует вместе эти  два  OBJ-модуля  в
один выполнимый EXE-модуль.
     В PASCAL-программе определены две переменные:  temp_row  и  temp_col,
которые  содержат  введенные  с  клавиатуры  значения  строки  и   колонки
соответственно. Программа передает адреса переменных temp_row и temp_col в
виде параметров в ассемблерную подпрограмму для установки курсора по  этим
координатам.  PASCAL-программа  определяет    также    имя    ассемблерной
подпрограммы в  операторе  procedure  как  move_cursor  и  определяет  два
параметра, как extern  (внешние).  Оператор  в  PASCAL-программе,  который
вызывает ассемблерную программу  по  имени  и  передает  параметры,  имеет
следующий вид:
               move_cursor (temp_row, temp_col);

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

               00   Указатель блока вызывающей программы
               02   Указатель сегмента возврата
               04   Указатель смещения возврата
               06   Адрес второго параметра
               08   Адрес первого параметра

     Так как ассемблерная подпрограмма будет использовать регистр  BP,  то
его необходимо сохранить  в  стеке  для  последующего  восстановления  при
возврате  в  вызывающую  PASCAL-программу.  Заметьте,  что  этот  шаг    в
вызываемой подпрограмме аналогичен предыдущему примеру на рис.21.6.
     Регистр SP обычно адресует элементы стека.  Но так как  этот  регистр
нельзя использовать в качестве индексного регистра,  то  после  сохранения
старого значения регистра BP необходимо переслать адрес из регистра  SP  в
BP.  Этот  шаг  дает  возможность  использовать  регистр  BP  в   качестве
индексного регистра для доступа к элементам в стеке.
     Следующий шаг - получить доступ к адресам двух  параметров  в  стеке.
Первый переданный параметр (адрес строки) находится в  стеке  по  смещению
08, и может быть адресован по BP+08.  Второй  переданный  параметр  (адрес
столбца) находится в стеке по смещению 06 и может быть адресован по BP+06.
     Два адреса  из  стека  должны  быть  переданы  в  один  из  индексных
регистров BX, DI или SI.  В данном примере адрес  строки  пересылается  из
[BP+08] в регистр  SI,  а  затем  содержимое  из  [SI]  (значение  строки)
пересылается в регистр DH.
     Значение столбца пересылается  аналогичным  способом  в  регистр  DL.
Затем подпрограмма использует значения строки и столбца в регистре DX  при
вызове BIOS для установки курсора. При выходе подпрограмма восстанавливает
регистр BP. Команда RET имеет операнд, значение которого в два раза больше
числа параметров, в данном случае  2х2,  или  4.  Параметры  автоматически
выводятся из стека и управление переходит в вызывающую программу.
     Если  в  подпрограмме  предстоит  изменить  сегментный  регистр    то
необходимо сохранить его значение командой PUSH на  входе  и  восстановить
командой POP на выходе. Можно также использовать стек для передачи величин
из подпрограммы в вызывающую программу. Хотя рассмотренная подпрограмма не
возвращает  каких-либо  значений,  в  языке  PASCAL  предполагается,   что
подпрограмма возвращает  одно  слово  в  регистре  AX  или двойное слово в
регистровой паре DX:AX.
     В  результате  компановки  двух  программ  будет   построена    карта
компановки,   в    которой    первый    элемент    PASCALL    представляет
PASCALL-программу, второй элемент CODESEG (имя сегмента кода) представляет
ассемблерную  подпрограмму.  Далее  следует  несколько  подпрограмм    для
PASCALL-программы.  Эта  довольно  тривиальная  программа    занимает    в
результате шест.5720 байт памяти - более 20К.  Компилирующие языки  обычно
генерируют  объектные  коды  значительно  превышающие  по  объему  размеры
компилируемой программы.


     КОМПОНОВКА ПРОГРАММ НА ЯЗЫКЕ CИ И АССЕМБЛЕРЕ
     ________________________________________________________________

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

          - Большинство версий языка C  обеспечивают  передачу  параметров
     через  стек  в  обратной  (по   сравнению    с    другими    языками)
     последовательности.  Обычно  доступ,  например,  к  двум  параметрам,
     передаваемым через стек, осуществляется следующим образом:

               MOV  ES,BP
               MOV  BP,SP
               MOV  DH,[BP+4]
               MOV  DL,[BP+6]
               ...
               POP  BP
               RET

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

          - В некоторых версиях  языка  C  требуется,  чтобы  ассемблерные
     программы, изменяющие регистры DI и SI, записывали  их  содержимое  в
     стек при входе и восстанавливали эти значения из стека при выходе.

          - Ассемблерные программы должны возвращать  значения,  если  это
     необходимо, в регистре AX (одно слово) или в регистровой  паре  DX:AX
     (два слова).

          - Для некоторых версий  языка  C,  если  ассемблерная  программа
     устанавливает флаг DF, то она должна сбросить его командой CLD  перед
     возвратом.


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

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

     - Будьте внимательны при использовании рекурсий, когда подпрограмма 1
вызывает подпрограмму 2, которая в свою очередь вызывает подпрограмму 1.

     - Если кодовые сегменты необходимо скомпоновать в  один  сегмент,  то
необходимо определить их с одинаковыми  именами,  одинаковыми  классами  и
атрибутом PUBLIC.

     - Для  простоты  программирования  начинайте  выполнение  с  основной
программы.

     - Определение общих данных в основной программе обычно проще  (но  не
обязательно).  Основная программа определяет общие данные  как  PUBLIC,  а
подпрограмма (или подпрограммы) - как EXTRN.


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

     21.1. Предположим, что программа MAINPRO должна вызвать под программу
SUBPRO.  а) Какая директива в программе MAINPRO указывает ассемблеру,  что
имя SUBPRO определено вне ее  собственного  кода?  б)  Какая  директива  в
подпрограмме  SUBPRO  необходима  для  того,  чтобы  имя  точки входа было
доступно в основной программе MAINPRO?

     21.2. Предположим,  что в программе MAINPRO определены переменные QTY
как DB,  VALUE как DW и PRICE как DW. Подпрограмма SUBPRO должна разделить
VALUE на QTY и записать  частное  в  PRICE.  а)  Каким  образом  программа
MAINPRO  указывает  ассемблеру,  что  три переменные должны быть доступный
извне основной программы?  б) Каким образом подпрограмма SUBPRO  указывает
ассемблеру, что три переменные определены в другом модуле?

     21.3. На   основании   вопросов  21.2  и  21.3  постройте  работающую
программу и проверьте ее.

     21.4. Измените программу из предыдущего вопроса так,  чтобы программа
MAINPRO передавала все три переменные,  как параметры. Подпрограмма SUBPRO
должна возвращать результат через параметр.

     21.5. Теперь предлагаем упражнение,  на  которое  потребуется  больше
времени.   Требуется  расширить  программу  из  вопроса  21.4  так,  чтобы
программа MAINPRO позволяла вводить количество  (QTY)  и  общую  стоимость
(VALVE) с клавиатуры,  подпрограмма SUBCONV преобразовывала ASCII-величины
в двоичное представление;  подпрограмма SUBCALC вычисляла цену (PRICE);  и
подпрограмма SUBDISP преобразовывала двоичную цену в ASCII-представление и
выводила результат на экран.



ГЛАВА 22                                             Программный загрузчик
__________________________________________________________________________

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


     ВВЕДЕНИЕ
     ________________________________________________________________

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

          1. Блок начальной загрузки находится на первом  секторе  нулевой
     дорожки дискеты DOS, а также на любом диске, форматированном командой
     FORMAT /S.  Когда вы инициируете  систему  (предполагается,  что  DOS
     расположен на дисководе A или C) происходит автоматическая загрузка с
     диска в память блока начальной загрузки. Этот блок представляет собой
     программу, которая затем загружает с диска в  память  три  программы,
     описанные ниже.
          2. Программа IBMBIO.COM обеспечивает интерфейс низкого уровня  с
     программами BIOS в ROM; она загружается в память,  начиная  с  адреса
     шест.00600.  При  инициализации  программа   IBMBIO.COM    определяет
     состояние всех устройств и оборудования, а затем загружает  программу
     COMMAND.COM.  Программа IBMBIO.COM управляет операциями  ввода-вывода
     между памятью и внешними  устройствами,  такими  как  видеомонитор  и
     диск.
          3. Программа IBMDOS.COM обеспечивает интерфейс высокого уровня с
     программами и загружается в память, начиная с адреса шест.00B00.  Эта
     программа управляет оглавлениями и файлами на диске, блокированием  и
     деблокированием дисковых записей, функциями INT 21H, а также содержит
     ряд других сервисных функций.
          4. Программа COMMAND.COM выполняет различные команды DOS,  такие
     как DIR или CHKDSK, а также выполняет COM, EXE и  BAT-программы.  Она
     состоит  из  трех  частей:  небольшая  резидентная    часть,    часть
     инициализации и транзитная  часть.  Программа  COMMAND.COM,  подробно
     рассмотренная в следующем разделе,  отвечает за загрузку  выполняемых
     программ с диска в память.

     На рис.22.1 показана карта распределения памяти.  Некоторые  элементы
могут отличаться в зависимости от модели компьютера.

__________________________________________________________________________

     Начальный                  Программа
     адрес

     00000  Векторная таблица прерываний (см.гл.23)
     00400  Область связи с ROM (ПЗУ)
     00500  Область связи с DOS
     00600  IBMBIO.COM
     XXXX0  IBMDOS.COM
            Буфер каталога
            Дисковый буфер
            Таблица параметров дисковода или таблица распределения файлов
            (FAT, по одной для каждого дисковода)
     XXXX0  Резидентная часть COMMAND.COM
     XXXX0  Внешние команды или утилиты (COM или EXE-файлы)
     XXXX0  Пользовательский стек для COM-файлов (256 байтов)
     XXXX0  Транзитная часть COMMAND.COM, записывается в самые старшие
            адреса памяти.
__________________________________________________________________________

     Рис.22.1. Карта распределения DOS в памяти.


     КОМАНДНЫЙ ПРОЦЕССОР COMMAND.COM
     ________________________________________________________________

     Система загружает три части программы COMMAND.COM в память  во  время
сеанса работы постоянно или временно.  Ниже описано назначение  каждой  из
трех частей COMMAND.COM:

          1.  Резидентная  часть  непосредственно  следует  за  программой
     IBMDOS.COM (и ее области данных), где  она  находится  на  протяжении
     всего  сеанса  работы.  Резидентная  часть  обрабатывает  все  ошибки
     дисковых операций ввода-вывода и управляет следующими прерываниями:

          INT 22H   Адрес программы обработки завершения задачи.
          INT 23H   Адрес программы реакции на Ctrl/Break.
          INT 24H   Адрес программы реакции на  ошибки  дисковых  операций
                    чтения/записи или сбойный  участок  памяти  в  таблице
                    распределения файлов (FAT).
          INT 27H   Завершение работы,  после которого программа  остается
                    резидентной.

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

          3. Транзитная часть загружается в самые старшие  адреса  памяти.
     "Транзит" обозначает, что DOS может перекрыть данную область  другими
     программами, если потребуется. Транзитная часть программы COMMAND.COM
     выводит на экран приглашение  DOS  A>  или  C>,  вводит  и  выполняет
     запросы.  Она содержит настраивающий загрузчик  и  предназначена  для
     загрузки COM- или EXE-файлов с диска в память  для  выполнения.  Если
     поступил запрос на выполнение  какой-либо  программы,  то  транзитная
     часть строит  префикс  программного  сегмента  (PSP)  непосредственно
     вслед  за  резидентной  частью  COMMAND.COM.  Затем  она    загружает
     запрошенную программу с диска в память по смещению шест.100 от начала
     программного  сегмента,  устанавливает  адреса  выхода  и    передает
     управление  в  загруженную  программу.    Ниже    приведена    данная
     последовательность:

               IBMBIO.COM
               IBMDOS.COM
               COMMAND.COM (резидент)
               Префикс программного сегмента
               Выполняемая программа
               ...
               COMMAND.COM (транзитная часть, может быть перекрыта).

     Выполнение команды RET или INT  20H  в  конце  программы  приводит  к
возврату в резидентную  часть  COMMAND.COM.  Если  транзитная  часть  была
перекрыта, то резидентная часть перезагружает транзитную часть с  диска  в
память.


     ПРЕФИКС ПРОГРАММНОГО СЕГМЕНТА
     ________________________________________________________________

     Префикс программного сегмента (PSP) занимает 256 (100H) байт и всегда
предшествует  в памяти каждой COM- или EXE-программе,  которая должна быть
выполнена. PSP содержит следующие поля:

00        Команда INT 20H (CD20).
02        Общий размер доступной памяти в формате  хххх0.  Напримеp,  512K
          указывается как 8000H вместо 80000H.
04        Зарезервировано.
05        Длинный вызов диспетчера функций DOS.
0A        Адрес подпрограммы завершения.
0E        Адрес подпрограммы реакции на Ctrl/Break.
12        Адрес подпрограммы реакции на фатальную ошибку.
16        Зарезервировано.
2C        Сегментный адрес среды для хранения ASCIIZ строк.
50        Вызов функций DOS (INT 21H и RETF).
5C        Параметрическая  область  1,   форматированная  как  стандартный
          неоткрытый блок управления файлов (FCB#1).
6C        Параметрическая  область  2,   форматированная  как  стандартный
          неоткрытый блок управления файлом (FCB#2);  перекрывается,  если
          блок FCB#1 открыт.
80-FF     Буфер передачи данных (DTA).


                        Буфер передачи данных DTA
                       ---------------------------
     Данная часть  PSP  начинается  по  адресу  80H  и  представляет собой
буферную область ввода-вывода  для  текущего  дисковода.  Она  содержит  в
первом  байте  число,  указывающее  сколько  раз  были  нажаты  клавиши на
клавиатуре непосредственно после ввода имени программы. Начиная со второго
байта,  находятся введенные символы (если таковые имеются).  Далее следует
всевозможный  "мусор",  оставшийся  в  памяти  после   работы   предыдущей
программы. Следующие примеры демонстрируют назначение буфера DTA:

     П р и м е р 1. Команда без операндов.  Предположим,  что  вы  вызвали
программу CALCIT.EXE для выполнения с  помощью  команды  CALCIT  [return].
После того, как DOS построит PSP для этой программы, он установит в буфере
по адресу шест.80 значение шест.000D. Первый байт содержит число символов,
введенных с  клавиатуры  после  имени  CALCIT,  исключая  символ  "возврат
каретки".  Так как кроме клавиши Return не было нажато ни одной, то  число
символов  равно  нулю.  Второй  байт  содержит  символ  возврата  каретки,
шест.0D. Таким образом, по адресам шест.80 и 81 находятся 000D.

     П р и м е р 2. Команда с текстовым операндом.  Предположим, что после
команды  был  указан  текст  (но  не  имя  файла),  например,  COLOR   BY,
обозначающий вызов программы COLOR и  передачу  этой  программе  параметра
"BY" для установки голубого цвета на желтом фоне. В этом случае, начиная с
адреса шест.80, DOS установит следующие значения байт:

               80:  03 20 42 59 0D

Эти байты обозначают длину 3, пробел, "BY" и возврат каретки.

     П р и м е р 3. Команда с именем файла в операнде.  Программы типа DEL
(удаление файла) предполагают после имени программы  ввод  имени  файла  в
качестве  параметра.  Если  будет  введено,  например,  DEL   B:CALCIT.OBJ
[return], то PSP, начиная с адресов шест.5C и шест.80, будет содержать:

               5C:  02 43 41 4C 43 49 54 20 20 4F 42 4A
                       C  A  L  C  I  T        O  B  J
               80:  0D 20 42 3A 43 41 4C 43 49 54 2E 4F 42 4A 0D
                          B  :  C  A  L  C  I  T  .  0   B  J

     Начиная с адреса шест.5C, находится неоткрытый блок  FCB,  содержащий
имя  файла,  который  был  указан  в  параметре,  CALCIT.OBJ,  но  не  имя
выполняемой программы.  Первый символ указывает номер  дисковода  (02=B  в
данном случае).  Следом за CALCIT находятся два пробела, которые дополняют
имя файла до восьми символов, и тип файла, OBJ. Если ввести два параметра,
например:

               progname A:FILEA,B:FILEB

тогда DOS построит FCB для FILEA по смещению шест.5C и FCB  для  FILEB  по
смещению шест.6C.
     Начиная с адреса шест.80 в этом  случае  содержится  число  введенных
символов (длина параметров)  -  16,  пробел  (шест.20)  A:FILEA,B:FILEB  и
символ возврат каретки (OD).
     Так как PSP непосредственно предшествует вашей программе, то возможен
доступ к области PSP для обработки указанных файлов или  для  предпринятия
определенных действий.  Для локализации  буфера  DTA  COM-программа  может
просто поместить шест.80 в регистр SI и получить доступ следующим образом:

               MOV  SI,80H              ;Адрес DTA
               CMP  BYTE PTR [SI],0     ;В буфере нуль?
               JE   EXIT

     Для EXE-программы  нельзя  с  уверенностью  утверждать,  что  кодовый
сегмент  непосредственно  располагается  после  PSP.  Однако,  здесь   при
инициализации регистры DS и ES содержат адрес PSP, так что можно сохранить
содержимое регистра ES после загрузки регистра DS:

               MOV  AX,DSEG
               MOV  DS,AX
               MOV  SAVEPSP,ES

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

               MOV  SI,SAVEPSP
               CMP  BYTE PTR [SI+ 80H],0     ;В буфере нуль?
               JE   EXIT

DOS версии 3.0 и старше содержит команду INT 62H, загружающую в регистр BX
адрес текущего PSP, который можно использовать для доступа к данным в PSP.


     ВЫПОЛНЕНИЕ COM-ПРОГРАММЫ
     ________________________________________________________________

     В отличие от EXE-файла, COM-файл не содержит заголовок на диске.  Так
как организация COM-файла намного проще, то  для  DOS  необходимо  "знать"
только то, что тип файла - COM.
     Как описано выше, загруженным в память COM- и EXE-файлам предшествует
префикс программного сегмента.  Первые два байта этого  префикса  содержат
команду  INT  20H  (возврат  в  DOS).  При  загрузке  COM-программы    DOS
устанавливает в четырех сегментных  регистрах  адрес  первого  байта  PSP.
Затем устанавливается указатель стека  на  конец  64  Кбайтового  сегмента
(шест.FFFE) или на конец памяти, если сегмент  не  достаточно  большой.  В
вершину стека заносится нулевое слово.  В командный  указатель  помещается
шест.100  (размер  PSP).  После  этого  управление  передается  по  адресу
регистровой пары CS:IP, т.е. на  адрес  непосредственно  после  PSP.  Этот
адрес  является  началом  выполняемой  COM-программы  и  должен  содержать
выполнимую команду.
     При выходе из программы команда RET  заносит  в  регистр  IP  нулевое
слово, которое было записано в вершину стека  при  инициализации.  В  этом
случае в регистровой паре CS:IP получается адрес первого  байта  PSP,  где
находится  команда  INT  20H.  При  выполнении  этой  команды   управление
передается в резидентную часть COMMAND.COM. (Если программа завершается по
команде INT 20H вместо RET, то  управление  непосредственно  передается  в
COMMAND.COM).


     ВЫПОЛНЕНИЕ EXE-ПРОГРАММЫ
     ________________________________________________________________

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

00   Шест.4D5A.  Компоновщик  устанавливает  этот  код  для  идентификации
     правильного EXE-файла.
02   Число байтов в последнем блоке EXE-файла.
04   Число 512 байтовых блоков EXE-файла, включая заголовок.
06   Число настраиваемых элементов.
08   Число 16-тибайтовых блоков (параграфов) в заголовке,  (необходимо для
     локализации начала выполняемого модуля, следующего после заголовка).
0A   Минимальное  число  параграфов,   которые  должны   находится   после
     загруженной программы.
0C   Переключатель загрузки в младшие или старшие  адреса.  При компановке
     программист должен решить,  должна ли его программа  загружаться  для
     выполнения  в  младшие  адреса  памяти или в старшие Обычным является
     загрузка в младшие адреса. Значение шест.0000 указывает на загрузку в
     старшие  адреса,  а  шест.FFFF - в младшие.  Иные значения определяют
     максимальное  число  параграфов,  которые  должны  находиться   после
     загруженной программы.
0E   Относительный адрес сегмента стека в выполняемом модуле.
10   Адрес,который загрузчик должен поместить в регистр SP перед передачей
     управления в выполнимый модуль.
12   Контрольная сумма - сумма всех слов в файле (без  учета переполнений)
     используется для проверки потери данных.
14   Относительный адрес,  который загрузчик должен поместить в регистр IP
     до передачи управления в выполняемый модуль.
16   Относительный адрес кодового  сегмента  в  выполняемом  модуле.  Этот
     адрес загрузчик заносит в регистр CS.
18   Смещение первого настраиваемого элемента в файле.
1A   Номер оверлейного фрагмента: нуль обозначает, что заголовок относится
     к резидентной части EXE-файла.
1C   Таблица  настройки,   содержащая   переменное   число   настраиваемых
     элементов, соответствующее значению по смещению 06.

     Заголовок имеет минимальный размер 512 байтов и  может  быть  больше,
если программа содержит большое число настраиваемых элементов.  Позиция 06
в заголовке указывает число элементов в выполняемом модуле, нуждающихся  в
настройке.  Каждый элемент настройки в таблице, начинающейся в позиции  1C
заголовка,  состоит  из  двухбайтовых  величин  смещений  и   двухбайтовых
сегментных значений.
     Система строит префикс программного сегмента  следом  за  резидентной
частью COMMAND.COM, которая выполняет операцию загрузки. Затем COMMAND.COM
выполняет следующие действия:

          - Считывает форматированную часть заголовка в память.
          - Вычисляет размер выполнимого  модуля  (общий  размер  файла  в
     позиции 04 минус размер заголовка в позиции 08) и загружает модуль  в
     память с начала сегмента.
          - Считывает элементы  таблицы  настройки  в  рабочую  область  и
     прибавляет  значения  каждого  элемента  таблицы  к  началу  сегмента
     (позиция OE).
          - Устанавливает в регистрах SS и  SP  значения  из  заголовка  и
     прибавляет адрес начала сегмента.
          - Устанавливает в регистрах DS и ES  сегментный  адрес  префикса
     программного сегмента.
          - Устанавливает в регистре CS адрес PSP  и  прибавляет  величину
     смещения в заголовке (позиция 16) к регистру CS.  Если  сегмент  кода
     непосредственно следует за PSP, то смещение  в  заголовке  равно  256
     (шест.100). Регистровая пара CS:IP содержит стартовый адрес в кодовом
     сегменте, т.е. начальный адрес программы.

     После инициализации регистры CS и SS содержат  правильные  адреса,  а
регистр DS (и ES) должны быть установлены в программе для  их  собственных
сегментов данных:

          1.  PUSH  DS               ;Занести адрес PSP в стек
          2.  SUB   AX,AX            ;Занести нулевое значение в стек
          3.  PUSH  AX               ; для обеспечения выхода из программы
          4.  MOV   AX,datasegname   ;Установка в регистре DX
          5.  MOV   DS,AX            ; адреса сегмента данных

     При завершении программы команда RET заносит  в  регистр  IP  нулевое
значение, которое было помещено в стек в начале  выполнения  программы.  В
регистровой паре CS:IP в этом случае получается  адрес,  который  является
адресом первого байта PSP, где расположена  команда  INT  20H.  Когда  эта
команда будет выполнена, управление перейдет в DOS.


     ПРИМЕР EXE-ПРОГРАММЫ
     ________________________________________________________________

     Рассмотрим следующую таблицу компановки (MAP) программы:

                    Start     Stop      Length    Name      Class
                    00000H    0003AH    003BH     CSEG      CODE
                    00040H    0005AH    001BH     DSEG      DATA
                    00060H    0007FH    0020H     STACK     STACK
                    Program  entry point at 0000:0000

     Таблица MAP содержит относительные (не действительные) адреса каждого
из  трех  сегментов.  Символ  H  после  каждого  значения  указывает    на
шестнадцатиричный формат.  Заметим, что компоновщик может организовать эти
сегменты в последовательности отличного от того, как они были закодированы
в программе.
     В соответствии с таблицей  MAP  кодовый  сегмент  CSEG  находится  по
адресу 00000 - этот  относительный  адрес  является  началом  выполняемого
модуля.  Длина кодового сегмента составляет  шест.003B  байтов.  Следующий
сегмент по имени DSEG  начинается  по  адресу  шест.00040  и  имеет  длину
шест.001B.  Адрес  шест.00040  является  первым  после    CSEG    адресом,
выровненным на границу  параграфа  (т.е.  это  значение  кратно  шест.10).
Последний сегмент, STACK, начинается по адресу шест.00060 - первому  после
DSEG, адресу выровненному на границу параграфа.
     С помощью отладчика DEBUG нельзя проверить содержимое заголовка,  так
как при загрузке программы для выполнения DOS замещает заголовок префиксом
программного сегмента.  Однако, на рынке программного обеспечения  имеются
различные сервисные утилиты  (или  можно  написать  собственную),  которые
позволяют    просматривать   содержимое   любого   дискового   сектора   в
шестнадцатиричном  формате.   Заголовок   для   рассматриваемого   примера
программы  содержит  следующую  информацию (содержимое слов представлено в
обратной последовательности байтов):

00   Шест.4D5A.
02   Число байтов в последнем блоке: 5B00.
04   Число 512 байтовых блоков в файле, включая заголовок: 0200 (шест.0002
     х 512 = 1024).
06   Число   элементов   в   таблице    настройки,    находящейся    после
     форматированной части заголовка: 0100, т.е. 0001.
08   Число  16  байтовых  элементов  в  заголовке:  2000  (шест.0020=32  и
     32х16=512).
0C   Загрузка в младшие адреса: шест.FFFF.
0E   Относительный адрес стекового сегмента: 6000 или шест.60.
10   Адрес для загрузки в SP: 2000 или шест.20.
14   Смещение для IP: 0000.
16   Cмещение для CS: 0000.
18   Cмещение для первого настраиваемого элемента: 1E00 или шест.1E.


     После загрузки программы под  управлением  отладчика  DEBUG  регистры
получают следующие значения:

               SP = 0020      DS = 138F      ES = 138F
               SS = 13A5      CS = 139F      IP = 0000

     Для EXE-модулей загрузчик устанавливает в регистрах  DS  и  ES  адрес
префикса программного сегмента, помещенного в доступной области памяти,  а
в регистрах IP, SS и SP - значения из заголовка программы.


                                Регистр SP
                               ------------
     Загрузчик использует шест.20 из заголовка для инициализации указателя
стека значением длины стека.  В данном примере стек был определен, как  16
DUP (?), т.е. 16 двухбайтовых  полей  общей  длиной  32  (шест.20)  байта.
Регистр SP указывает на текущую вершину стека.


                                Регистр CS
                               ------------
     В соответствии со значением в регистре DS после  загрузки  программы,
адрес PSP равен шест.138F(0).  Так как PSP имеет длину шест.100 байтов, то
выполняемый модуль, следующий  непосредственно  после  PSP,  находится  по
адресу шест.138F0+100=139F0. Это значение  устанавливается  загрузчиком  в
регистре CS.  Таким образом, регистр CS определяет начальный адрес кодовой
части программы (CSEG).  С помощью команды D  CS:0000  в  отладчике  DEBUG
можно просмотреть в режиме дампа машинный код в памяти.  Обратите внимание
на идентичность дампа и шестнадцатиричной части  ассемблерного  LST  файла
кроме операндов, отмеченных символом R.


                                Регистр SS
                               ------------
     Для установки значения  в  регистре  SS  загрузчик  также  использует
информацию из заголовка:

               Начальный адрес PSP (см.DS)   138F0
               Длина PSP                       100
               Относительный адрес стека        60
                                             -----
               Адрес стека                   13A50


                                Регистр DS
                               ------------
     Загрузчик использует регистр DS для установки начального адреса  PSP.
Так как заголовок не содержит стартового адреса, то регистр DS  необходимо
инициализировать в программе следующим образом:

               0004 B8 ---- R      MOV  AX,DSEG
               0007 8E D8          MOV  DS,AX

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

               B8 A313

Значение A313 загружается в регистр DS в виде 13A3. В результате имеем

               Регистр   Адрес     Смещение

                 CS      139F0        00
                 DS      13A30        40
                 SS      13A50        60

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

               Команда        Изменяющиеся регистры

               PUSH DS        IP и SP
               SUB  AX,AX     IP и AX (если был не нуль)
               PUSH AX        IP и SP
               MOV  AX,DSEG   IP и AX
               MOV  DS,AX     IP и DS

Регистр  DS  содержит  теперь  правильный  адрес  сегмента  данных.  Можно
использовать теперь команду D DS:00  для  просмотра  содержимого  сегмента
данных DSEG и команду D SS:00 для просмотра содержимого стека.


     ФУНКЦИИ ЗАГРУЗКИ И ВЫПОЛНЕНИЯ ПРОГРАММЫ
     ________________________________________________________________

     Рассмотрим теперь, как  можно  загрузить  и  выполнить  программу  из
другой  программы.  Функция  шест.4B  дает  возможность  одной   программе
загрузить другую программу в память и  при  необходимости  выполнить.  Для
этой функции необходимо загрузить адрес  ASCIIZ-строки  в  регистр  DX,  а
адрес блока параметров в регистр BX (в действительности в регистровую пару
ES:BX). В регистре AL устанавливается номер функции 0 или 3:

     AL=0.  З а г р у з к а   и   в ы п о л н е н и е.   Данная   операция
устанавливает префикс программного сегмента для новой программы,  а  также
адрес подпрограммы реакции на Cntrl/Break и адрес передачи  управления  на
следующую команду после завершения новой программы.  Так как все регистры,
включая SP, изменяют свои значения, то данная операция  не  для  новичков.
Блок параметров, адресуемый по ES:BX, имеет следующий формат:

     Смещение                 Назначение

        0    Двухбайтовый сегментный адрес строки параметров для передачи.
        2    Четырехбайтовый указатель на командную строку в PSP+80H.
        6    Четырехбайтовый указатель на блок FCB в PSP+5CH.
       10    Четырехбайтовый указатель на блок FCB в PSP+6CH.

     AL=3. О в е р л е й н а я  з а г р у з к а. Данная операция загружает
программу или блок кодов, но не создает  PSP  и  не  начинает  выполнение.
Таким  образом  можно  создавать  оверлейные  программы.  Блок  параметров
адресуется по регистровой паре ES:BX и имеет следующий формат:

     Смещение                 Назначение

        0      Двухбайтовый адрес сегмента для загрузки файла.
        2      Двухбайтовый фактор настройки загрузочного модуля.

     Возможные коды ошибок, возвращаемые в регистре AX: 01, 02, 05, 08, 10
и 11. Программа на рис.22.2 запрашивает  DOS  выполнить  команду  DIR  для
дисковода D.  Выполните эту программу, как EXE-модуль.  (Автор  благодарен
журналу PC Magazine за эту идею).

__________________________________________________________________________

TITLE   EXDOS   (EXE) Функция DOS 4BH для выполнения DIR
CSEG    GMENT   PARA 'Code'
        ASSUME  CS:CSEG,DS:CSEG,ES:CSEG
BEGIN:  JMP     SHORT MAIN
;----------------------------------------------------------
PARAREA DW      ?               ;Адрес строки вызова
        DW      OFFSET DIRCOM   ;Указатель
                                ; на командную строку
        DW      CSEG
        DW      OFFSET FCB1     ;Указатель на FCB2
        DW      CSEG
DIRCOM  DB      17,'/C DIR D:',13,0
FCB1    DB      16 DUP(0)
FCB2    DB      16 DUP(0)
PROGNAM DB      'D:COMMAND.COM',0
; ---------------------------------------------------------
MAIN    PROC    FAR
        MOV     AH,4AH          ;Получить 64K памяти
        MOV     BH,100H         ; в параграфах
        INT     21H
        JC      E10ERR          ;Нет памяти?
        MOV     DI,2CH          ;Получить сегментный адрес
        MOV     AX,[DI]         ; строки вызова
        LEA     SI,PARAREA      ; и записать его в
        MOV     [SI],AX         ; 1 слово блока параметров
        MOV     AX,CS           ;Загрузить в DS и ES
        MOV     DS,AX           ; адрес CSEG
        MOV     ES,AX
        MOV     AH,4BH          ;Функция загрузки
        MOV     AL,00           ; и выполнения
        LEA     BX,PARAREA      ; COMMAND.COM
        LEA     DX,PROGNAM
        INT     21H             ;Вызвать DOS
        JC      E20ERR          ;Ошибка выполнения?
        MOV     AL,00           ;Нет кода ошибки
        JMP     X10XIT
E10ERR:
        MOV     AL,01           ;Код ошибки 1
        JMP     X10XIT
E20ERR:
        MOV     AL,02           ;Код ошибки 2
        JMP     X10XIT
E10XIT:
        MOV     AH,4CH          ;Функция завершения
        INT     21H             ;Вызвать DOS
MAIN    ENDP
CSEG    ENDS
        END
__________________________________________________________________________

     Рис.22.2. Выполнение команды DIR из программы.



ГЛАВА 23                                             Прерывания BIOS и DOS
__________________________________________________________________________

     Ц е л ь: Описать функции, доступные через прерывания BIOS и DOS.


     ВВЕДЕНИЕ
     ________________________________________________________________

     Прерывание представляет  собой  операцию,  которая   приостанавливает
выполнение  программ  для  специальных  системных действий.  Необходимость
прерываний обусловлено двумя основными  причинами:  преднамеренный  запрос
таких  действий,  как  операции  ввода-вывода  на  различные  устройства и
непредвиденные программные ошибки (например, переполнение при делении).
     Система BIOS (Basic Input/Output System) находится в ROM и  управляет
всеми прерываниями в  системе.  В  предыдущих  главах  уже  использовались
некоторые прерывания для вывода на экран дисковых операций ввода-вывода  и
печати. В этой главе описаны различные BIOS- и DOS-прерывания, резидентные
программы и команды IN и OUT.


     ОБСЛУЖИВАНИЕ ПРЕРЫВАНИЙ
     ________________________________________________________________

     В компьютерах IBM PC ROM находится по адресу  FFFF0H.  При  включении
компьютера процессор устанавливает состояние  сброса,  выполняет  контроль
четности, устанавливает в регистре CS значение FFFFH, а в  регистре  IP  -
нуль.  Первая выполняемая команда поэтому находится по адресу  FFFF:0  или


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

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



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


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