[Назад] [Далее]

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

Как и любая операционная система, DOS загружает и выполняет программы. При загрузке программы в начале отводимого для нее блока памяти (для СОМ-программ это вся свободная на данный момент память) создается структура данных PSP (префикс программного сегмента) размером 256 байт (100h). Затем DOS создает копию текущего окружения для загружаемой программы, помещает полный путь и имя программы в конец окружения, заполняет поля PSP следующим образом:

и записывает программу в память, начиная с адреса PSP:0100h. Если загружается ЕХЕ-программа, использующая дальние процедуры или сегменты данных, DOS модифицирует эти команды так, чтобы используемые в них сегментные адреса соответствовали сегментным адресам, которые получили эти процедуры и сегменты данных при загрузке программы в память. При запуске СОМ-программы регистры устанавливаются следующим образом:

При запуске ЕХЕ-программы регистры 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 — неправильный формат)

Для подфункций 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.


п»ї
"target=_blank><\/a>") //-->