4.10. Загрузка и выполнение программ

Как и любая операционная система, DOS загружает и выполняет программы. При загрузке программы в начале отводимого для нее блока памяти (для

это вся на данный момент память) создается структура данных PSP

(префикс программного сегмента) размером 256 байт (100h). Затем DOS создает копию текущего окружения для загружаемой помещает полный путь

и имя программы в конец окружения, заполняет поля PSP следующим образом:

+00h: слово    - OCDh 20h - команда INT 20h. Если СОМ-программа завершает­ся командой управление передается на эту команду. Введено для совместимости с командой СР/М CALL О

+02h: слово - сегментный адрес первого байта после области памяти, выделен­ной для программы +04h: байт    - не используется DOS

+05h: 5 байт  - 9Ah OFOh OFEh OIDh OFOh - команда CALL FAR на абсолютный адрес OOOCOh, записанная так, чтобы второй и третий байты со­ставляли слово, равное размеру первого сегмента для лов (в этом примере OFEFOh). Введено для совместимости с ко­мандой СР/М CALL 5 4 байта адрес обработчика INT 22h (выход из программы)

+ОЕп:4байта - адрес обработчика INT 23h (обработчик нажатия Ctrl-Break) +12h: 4 байта - адрес обработчика INT 24h (обработчик критических ошибок) +16h: слово   - сегментный адрес PSP процесса, из которого был запущен текущий +18h: 20 байт - JFT - список открытых идентификаторов, один байт на иденти­фикатор, OFFh - конец спискаслово     - сегментный адрес копии окружения для процесса +2Eh: 2 слова   - SS:SP процесса при последнем вызове INT 21h +32h: слово      - число элементов JFT (по умолчанию 20)

+34h: 4 байта - дальний адрес JFT (по умолчанию PSP:0018)

+38h: 4 байта  - дальний адрес предыдущего PSP

+3Ch: байт     - флаг, указывающий, что консоль находится в состоянии ввода

символа

+3Dh: байт - флаг, устанавливаемый функцией 0В71 lh прерывания 2Fh (при следующем вызове INT 21h для работы с файлом имя файла бу­дет заменено полным)

не используется в DOS

- версия DOS, которую вернет функция DOS 30h (DOS 5.0+)

- не используется в DOS ■ OCDh 21h - команда INT 21h

- - команда RETF

- не используется в DOS

- область для расширения первого FOB

первый FCB, заполняемый из первого аргумента командной строки

второй FCB, заполняемый из второго аргумента командной строки

- не используется в DOS командная строка и область DTA по умолчанию

и записывает программу в память, начиная с адреса Если ЕХЕ-программа, использующая дальние процедуры или сегменты данных, DOS модифицирует эти команды так, чтобы используемые в них сегментные адреса соответствовали сегментным адресам, которые получили указанные процедуры и сегменты данных при загрузке программы в память. Во время запуска СОМ-регистры устанавливаются следующим образом: AL » OFFh, если первый параметр командной строки содержит непра­вильное имя диска (например, z:\something), иначе - 00h АН = OFFh, если второй параметр содержит неправильное имя диска,

иначе - OOh

CS = DS = ES = SS = сегментный адрес PSP

SP     адрес последнего слова в сегменте (обычно меньше, если

не хватает памяти)

При запуске регистры SS:SP устанавливаются в соответствии

с сегментом стека, определенным в программе, затем в стек помещается слово OOOOh и выполняется переход на начало программы (PSP:0100h для СОМ, соб­ственная точка входа для ЕХЕ).

Все эти действия выполняет одна функция DOS - загрузить и выполнить программу.

+ЗЕп: слово +40п: слово +42п: 12 байт +501і: 2 байта +521і: байт +5З1і: 2 байта +551т: 7 байт +5 СІї: 16 байт +601: 16 байт +701: 4 байта +801: 128 байт

и выполнить программу

Функция DOS 4Bh: Загрузить Вход: АН = 4Bh

AL = OOh - загрузить и выполнить

AL =      - загрузить и не выполнять

DS:DX - адрес ASCIZ-строки с полным именем программы ES.'BX - адрес блока параметров ЕРВ:

слово - сегментный адрес окружения, которое будет скопи­ровано для нового процесса (или 0, если использу­ется текущее окружение) +02h: 4 байта - адрес командной строки для нового процесса

+06h: 4 байта - адрес первого FCB для нового процесса +0Ah: 4 байта - адрес второго FCB для нового процесса +0Eh: 4 байта - здесь будет записан SS:SP нового процесса после

его завершения (только для AL = 01) + 12h: 4 байта - здесь будет записан CS:IP (точка входа) нового

процесса после его завершения (только для AL = 01)

AL = 03h - загрузить как оверлей

DS:DX - адрес ASCIZ-строки с полным именем программы

ES:BX - адрес блока параметров:

+00h: слово - сегментный адрес для загрузки оверлея

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

AL - подготовиться к выполнению (DOS 5.0+)

DS:DX - адрес следующей структуры: +00h: слово - OOh

+02h: слово - бит 0 - программа - ЕXЕ

бит 1 - программа - оверлей +04h: 4 байта - адрес ASCIZ-строки с именем новой программы

+08h: слово - сегментный адрес PSP новой программы +ОАЪ: 4 байта - точка входа новой программы

+OEh: 4 байта - размер программы, включая PSP

Выход:

CF = 0, если операция выполнена, ВX и DX модифицируются, CF = 1, если произошла ошибка, АX = код ошибки (2 - файл не найден, 5 - доступ к файлу запрещен, 8 - не хватает памяти. OAh - непра­вильное окружение, OBh - неправильный формат)

Подфункциям 00 и 01 требуется, чтобы свободная память для загрузки про­граммы была в нужном количестве, так что СОМ-программыдолжны воспользо­ваться функцией DOS 4Ah с целью уменьшения отведенного им блока памяти до

минимально необходимого. При вызове подфункции 03 DOS загружает оверлей

в память, выделенную текущим процессом, поэтому ЕXЕ-программы должны убедиться, что ее достаточно.

Эта функция игнорирует расширение файла и различает ЕXЕ- и СОМ-файлы по первым двум байтам заголовка (MZ для ЕXЕ-файлов).

Подфункция 05 должна вызываться после загрузки и перед передачей управле­ния на программу, причем никакие прерывания DOS и BIOS нельзя вызывать после возвращения из этой подфункции и до перехода на точку входа новой программы.

Загруженной и вызванной таким образом программе предоставляется несколь­ко способов завершения работы. Способ, который чаще всего применяется для СОМ-файлов, - команда RETN. При этом управление передается на адрес PSR0000, где располагается код команды INT 20h. Соответственно программу мож­но завершить сразу, вызвав INT 20h, но оба эти способа требуют, чтобы CS содер­жал сегментный адрес PSP текущего процесса. Кроме того, они не позволяют вер­нуть код возврата, который может передать предыдущему процессу информацию о том, как завершилась запущенная программа. Рекомендованный способ заверше­ния программы - функция DOS 4Ch.

Функция DOS4Ch: Завершить программу Вход:    АН = 4Ch

Значение кода возврата можно использовать в пакетных файлах DOS как пере­менную ERRORLEVELh определять из программы с помощью функции DOS 4Dh.

Функция DOS4Dh: Определить код возврата последнего завершившегося процесса Вход:    АН - 4Dh Выход: АН = способ завершения:

00h - нормальный

Olh - Ctrl-Break

02h - критическая ошибка

03h - программа осталась в памяти как резидентная AL = код возврата

Воспользуемся функциями 4Ah и 4Bh в следующем примере программы, кото­рая ведет себя как командный интерпретатор, хотя на самом деле единственная ко­манда, которую она обрабатывает, - команда exit. Все остальные команды передают­ся настоящему COMMAND.COM с ключом /С (выполнить команду и вернуться).

; shell.asm

;   Программа,   выполняющая функции командного интерпретатора ;  (вызывающая command.com для всех команд,   кроме exit).

.model tiny

. code

.186

org 100h ' ; СОМ-программа

prompt_end equ        "$" ;  Последний символ в приглашении ко вводу.

AL = код возврата

CF

О

start:

mov

 

І Перемещение стека на

І  после конца программы

(дополнительные 100h - для PSP).

mov ah,4Ah stack_shift=length_of_program+100h+200h

mov bx,stack_shift shr 4+1

int 2lh                             ; Освободить всю память после конца

; программы и стека.

; Заполнить поля ЕРВ, содержащие сегментные адреса.

mov ax.cs

mov word ptr EPB+4,ax           ; Сегментный адрес командной строки,

mov word ptr ЕРВ+8,ах           ; Сегментный адрес первого FOB.

mov word ptr EPB+OCh,ax        ; Сегментный адрес второго FOB.

main_loop:

; Построение и вывод приглашения для ввода.

. mov ah,l9h                           ; Функция DOS 19h:

int 21h                               ; определить текущий диск,

add al.'A'                          ; Теперь AL = ASCII-код диска (А,  В, С,),

mov byte ptr drive_letter,al ; Поместить его в строку,

mov ah,47h                           ; Функция DOS 47h:

mov dl.OO

mov si,offset pwd_Buffer

int 21h                             ; определить текущую директорию

mov   ■ al.O                            ; Найти ноль (конец текущей директории)

mov di, offset prompt_start    ; в строке с приглашением.

mov cx,prompt_l

repne scasb

dec di                     -        ; DI - адрес байта с нулем.

mov dx, offset prompt_start     ; DS:DX - строка приглашения,

sub di,dx                          ; DI - длина строки приглашения.

mov cx.di

mov bx, 1                             ; stdout

mov ah,40h

int 21 h                             ; Вывод строки в файл или устройство,

mov al,prompt_end

int 29h                             ; Вывод последнего символа в приглашении.

; получить команду от пользователя

mov ah.OAh                            ; Функция DOS OAh:

mov dx, offset command_buffer

int 21b                              ; буферированный ввод.

mov al.ODh                         ;; Вывод символа OR

int 29h

mov al.OAh                         ■■ Вывод символа DF

int 29h                             ;;   (CR и DF вместе - перевод строки).

cmp byte ptr command_buffer+1, 0 ; Если введена пустая строка,

je main_loop ; продолжить основной цикл.

Загрузка и выполнение программ

; Проверить,  является ли

введенная команда командой "exit". '

mov

di,offset

command_buffer+2

; Адрес  введенной строки.

mov

si,offset

cmd_exit

; Адрес  эталонной строки

 

 

 

; "exit",0Dh.

mov

cx,cmd_exit_l

; Длина эталонной строки.

repe

cmpsb

 

; Сравнить строки.

jcxz

got_exit

 

; Если строки идентичны -

 

 

 

выполнить exit.

; Передать остальные команды интерпретатору DOS

(C0MMAND.COM).

xor

cx.cx

 

 

mov

si,offset

command_buffer+2

Адрес введенной строки.

mov

di,offset

command_text

Параметры для command.com.

mov

ptr

; Размер введенной строки.

inc

cl

 

; Учесть ODh в конце.

rep

movsb

 

Скопировать строку. -

mov

ax,4B00h

 

; Функция DOS 4Bh.

mov

dx,offset

command_com

; Адрес ASCIZ-строки с адресом.

mov

bx,offset

EPB

 

int

21h -

 

: Исполнить программу.

jmp

short main_loop

; Продолжить основной цикл.

got exit:

 

 

 

int

20h

 

; Выход из программы (ret нельзя,

 

 

 

; потому что мы перемещали стек).

cmd exit

db

"exit",0Dh

; Команда "exit".

cmd_exit_l

equ

$-cmd_exit   ■'

Ее длина.

prompt start

db

"tinyshell:"

; Подсказка для ввода.

drivejetter

db

"C:"

 

pwd_buffer

db ■

64 dup (?)

; Буфер для текущей директории.

prompt_l

equ

$-prompt_start

; Максимальная длина подсказки.

command_com

db

"C:\C0MMAND.COM",0

Имя файла.

EPB

dw

0000

; Использовать'текущее окружение.

 

dw

offset commandline,0

Адрес командной строки.

 

dw

005Ch,0,006Ch,0

; Адреса FCB, переданных DOS

 

 

 

; нашей программе при запуске

 

 

 

(на самом деле они не

commandline

db

125

Максимальная длина

 

 

 

: командной строки.

 

db

/C "

;  Ключ /С для C0MMAN0.COM.

command_text

db

122 dup (?)

Буфер для командной строки.

command_buffer

db 122

 

; Здесь начинается буфер для ввода

length_of.program equ

124+$-start

; Длина программы + длина

 

 

 

буфера для ввода.

end

start

 

 

Чтобы сократить пример, в нем используются функции для работы с обычны­ми короткими именами файлов. Достаточно заменить строку

mov ah,47h

на

mov ax,7147h

и увеличить размер буфера для текущей директории (pwd_buffer) с 64 до 260 байт, и директории с длинными именами будут отображаться корректно в под­сказке для ввода. Но с целью совместимости следует также добавить проверку на поддержку функции 71h (LFN) и определить размер буфера для директории с по­мощью подфункции LFN OAOh.