Загрузка и выполнение программ
Как и любая операционная система, DOS загружает и выполняет программы. При загрузке программы в начале отводимого для нее блока памяти (для СОМ-программ это вся свободная на данный момент память) создается структура данных PSP (префикс программного сегмента) размером 256 байт (100h). Затем DOS создает копию текущего окружения для загружаемой программы, помещает полный путь и имя программы в конец окружения, заполняет поля PSP следующим образом:
+00h: слово — CDh 20h — команда INT 20h. Если СОМ-программа завершается командой RETN, управление передается на эту команду. Введено для совместимости с командой СР/М CALL 0.
+02h: слово — сегментный адрес первого байта после области памяти, выделенной для программы
+04h: байт — не используется DOS
+05h: 5 байт — 9Ah F0h FEh 1Dh F0h — команда CALL FAR на абсолютный адрес 000C0h, записанная так, чтобы второй и третий байты составляли слово, равное размеру первого сегмента для СОМ-файлов (в этом примере FEF0h). Введено для совместимости с командой СР/М CALL 5.
+0Ah: 4 байта — адрес обработчика INT 22h (выход из программы)
+0Eh: 4 байта — адрес обработчика INT 23h (обработчик нажатия Ctrl-Break).
+12h: 4 байта — адрес обработчика INT 24h (обработчик критических ошибок)
+16h: слово — сегментный адрес PSP процесса, из которого был запущен текущий.
+18h: 20 байт — JFT — список открытых идентификаторов, один байт на идентификатор, FFh — конец списка.
+2Ch: слово — сегментный адрес копии окружения для процесса.
+2Eh: 2 слова — SS:SP процесса при последнем вызове INT 21h.
+32h: слово — число элементов JFT (по умолчанию 20).
+34h: 4 байта — дальний адрес JFT (по умолчанию PSP:0018).
+38h: 4 байта — дальний адрес предыдущего PSP.
+3Ch: байт — флаг, указывающий, что консоль находится в состоянии ввода 2-байтного символа.
+3Dh: байт — флаг, устанавливаемый функцией В711h прерывания 2Fh (при следующем вызове INT 21h для работы с файлом имя файла будет замечено на полное).
+3Eh: слово — не используется в DOS.
+40h: слово — версия DOS, которую вернет функция DOS 30h (DOS 5.0+).
+42h: 12 байт — не используется в DOS.
+50h: 2 байта — CDh 21h — команда INT 21h.
+54h: 7 байт — область для расширения первого FCB.
+5Ch: 16 байт — первый FCB, заполняемый из первого аргумента командной строки.
+6Ch: 16 байт — второй FCB, заполняемый из второго аргумента командной строки.
+7Ch: 4 байта — не используется в DOS.
+80h: 128 байт — командная строка и область DTA по умолчанию.
и записывает программу в память, начиная с адреса PSP:0100h. Если загружается ЕХЕ-программа, использующая дальние процедуры или сегменты данных, DOS модифицирует эти команды так, чтобы используемые в них сегментные адреса соответствовали сегментным адресам, которые получили эти процедуры и сегменты данных при загрузке программы в память. При запуске СОМ-программы регистры устанавливаются следующим образом:
AL = FFh, если первый параметр командной строки содержит неправильное имя диска (например, z:/something), иначе — 00h.
АН = FFh, если второй параметр содержит неправильное имя диска, иначе 00h.
CS = DS = ES = SS = сегментный адрес PSP.
SP = адрес последнего слова в сегменте (обычно FFFEh; меньше, если не хватает памяти).
При запуске ЕХЕ-программы регистры SS:SP устанавливаются в соответствии с сегментом стека, определенным в программе, затем в любом случае в стек помещается слово 0000h и выполняется переход на начало программы (PSP:0100h для СОМ, собственная точка входа для ЕХЕ).
Все эти действия выполняет одна функция DOS — загрузить и выполнить программу.
Функция DOS 4Bh — Загрузить и выполнить программу
Ввод: |
АН = 4Bh
AL = 00h — загрузить и выполнить
AL = 01h — загрузить и не выполнять
DS:DX — адрес ASCIZ-строки с полным именем программы
ES:BX — адрес блока параметров ЕРВ:
+00h: слово — сегментный адрес окружения, которое будет скопировано для нового процесса (или 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: слово — сегментный адрес для загрузки оверлея
+02h: слово — число, которое будет использовано в командах, использующих непосредственные сегментные адреса, — обычно то же самое число, что и в предыдущем поле. 0 для СОМ-файлов
AL = 05h — подготовиться к выполнению (DOS 5.0+)
DS:DX — адрес следующей структуры:
+00h: слово — 00h
+02h: слово:
бит 0 — программа — ЕХЕ бит 1 — программа — оверлей
+04h: 4 байта — адрес ASCIZ-строки с именем новой программы
+08h: слово — сегментный адрес PSP новой программы
+0Ah: 4 байта — точка входа новой программы
+0Eh: 4 байта — размер программы, включая PSP |
Вывод: |
CF = 0, если операция выполнена, ВХ и DX модифицируются, CF = 1, если произошла ошибка, АХ = код ошибки (2 — файл не найден, 5 — доступ к файлу запрещен, 8 — не хватает памяти, 0Ah — неправильное окружение, 0Bh — неправильный формат) |
<
/p>
Для подфункций 00 и 01 требуется, чтобы было достаточно свободной памяти для загрузки программы, так что СОМ-программы должны воспользоваться функцией DOS 4Ah для уменьшения отведенного им блока памяти до минимально необходимого. При вызове подфункции 03 DOS загружает оверлей в память, выделенную текущим процессом, так что ЕХЕ-програмаш должны убедиться, что ее достаточно.
Эта функция игнорирует расширение файла и различает ЕХЕ- и СОМ-файлы по первым двум байтам заголовка («MZ» для ЕХЕ-файлов).
Подфункция 05 должна вызываться после загрузки и перед передачей управления на программу, причем никакие прерывания DOS и BIOS нельзя вызывать после возвращения из этой подфункции и до перехода на точку входа новой программы.
Загруженной и вызванной таким образом программе предоставляется несколько способов завершения работы. Способ, который чаще всего применяется для СОМ-файлов, — команда RETN. При этом управление передается на адрес PSP:0000, где располагается код команды INT 20h. Соответственно можно завершить программу сразу, вызвав INT 20h, но оба эти способа требуют, чтобы CS содержал сегментный адрес PSP текущего процесса. Кроме того, они не позволяют вернуть код возврата, который может передать предыдущему процессу информацию о том, как завершилась запущенная программа. Рекомендованный способ завершения программы — функция DOS 4Ch.
Функция DOS 4Ch — Завершить программу
Ввод: |
АН = 4Ch AL = код возврата |
Значение кода возврата можно использовать в пакетных файлах DOS как переменную ERRORLEVEL и определять из программы с помощью функции DOS 4Dh.
Функция DOS 4Dh — Определить код возврата последнего завершившегося процесса
Ввод: |
АН = 4Dh |
Вывод: |
АН = способ завершения:00h — нормальный 01h — Ctrl-Break 02h — критическая ошибка 03h — программа осталась в памяти как резидентная AL = код возврата CF = 0 |
Воспользуемся функциями 4Ah и 4Bh в следующем примере программы, которая ведет себя как командный интерпретатор, хотя на самом деле единственная команда, которую она обрабатывает, — команда exit. Все остальные команды передаются настоящему COMMAND.COM с ключом /С (выполнить команду и вернуться).
; shell.asm ; программа, выполняющая функции командного интерпретатора ; (вызывающая command.com для всех команд, кроме exit). .model tiny .code .186 org 100h ; СОМ-программа prompt_end equ "$" ; последний символ в приглашении ко вводу
start: mov sp,length_of_program+100h+200h ; перемещение стека на 200h ; после конца программы ; (дополнительные 100h - для PSP) mov ah,4Ah
stack_shift = length_of_program + 100h + 200h
mov bx,stack_shift shr 4+1 int 21h ; освободить всю память после конца ; программы и стека
; Заполнить поля ЕРВ, содержащие сегментные адреса mov ax,cs mov word ptr EPB+4,ax ; сегментный адрес командной строки mov word ptr EPB+8,ax ; сегментный адрес первого FCB mov word ptr EPB+0Ch,ax ; сегментный адрес второго FCB
main_loop:
; построение и вывод приглашения для ввода
mov ah,19h ; Функция DOS 19h int 21h ; определить текущий диск add al,'A' ; теперь AL = ASCII-код диска (А, В, С,) mov byte ptr drive_letter,al ; поместить его в строку mov ah,47h ; Функция DOS 47h mov dl,00 mov si,offset pwd_buffer int 21h ; определить текущий каталог mov al,0 ; найти ноль (конец текущего каталога) 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 21h ; вывод строки в файл или устройство mov al,prompt_end int 29h ; вывод последнего символа в приглашении
; получить команду от пользователя
mov ah,0Ah ; Функция DOS 0Ah mov dx,offset command_buffer int 21h ; буферированный ввод mov al,0Dh ; вывод символа CR int 29h mov al,0Ah ; вывод символа LF int 29h ; (CR и LF вместе - перевод строки) 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 ex,cmd_exit_l ; длина эталонной строки repe cmpsb ; сравнить строки jcxz got_exit ; если строки идентичны - выполнить exit
; передать остальные команды интерпретатору DOS (COMMAND.COM)
xor сх,сх mov si,offset command_buffer+2 ; адрес введенной строки mov di,offset command_text ; параметры для command.com mov cl,byte ptr command_buffer+1 ; размер введенной строки inc cl ; учесть 0Dh в конце 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:" ; подсказка для ввода drive_letter db "С:" pwd_buffer db 64 dup (?) ; буфер для текущего каталога prompt_l equ $-prompt_start ; максимальная длина подсказки command_com db "С:\COMMAND.COM",0 ; имя файла EPB dw 0000 ; использовать текущее окружение dw offset commandline,0 ; адрес командной строки dw 005Ch,0,006Ch,0 ; адреса FCB, переданных DOS ; нашей программе при запуске ; (на самом деле они не используются) commandline db 125 ; максимальная длина командной строки db " /С" ; ключ /С для COMMAND.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 A0h.
Содержание раздела