- •Глава 3. Директивы и операторы ассемблера
- •3.1. Структура программы
- •3.2. Директивы распределения памяти
- •3.2.1. Псевдокоманды определения переменных
- •3.2. Директивы распределения памяти
- •3.2.1. Псевдокоманды определения переменных
- •3.2.2. Структуры
- •3.3. Организация программы
- •3.3.1. Сегменты
- •3.2. Директивы распределения памяти
- •3.2.1. Псевдокоманды определения переменных
- •3.2.2. Структуры
- •3.3. Организация программы
- •3.3.1. Сегменты
- •3.3.2. Модели памяти и упрощенные директивы определения сегментов
- •3.3.3. Порядок загрузки сегментов
- •3.3.4. Процедуры
- •3.3.5. Конец программы
- •3.3.6. Директивы задания набора допустимых команд
- •3.3.7. Директивы управления программным счетчиком
- •3.3.8. Глобальные объявления
- •3.3.9. Условное ассемблирование
- •3.4. Выражения
- •3.5. Макроопределения
- •3.6. Другие директивы
- •3.6.1. Управление файлами
- •4. Основы программирования для ms-dos
- •4.1. Программа типа сом
- •4.2. Программа типа ехе
- •4.3. Вывод на экран в текстовом режиме
- •4.3.1. Средства dos
- •4.4. Ввод с клавиатуры
- •4.4.1. Средства dos
- •4.5. Графические видеорежимы
- •4.5.1. Работа с vga-режимами
- •4.6. Работа с мышью
- •4.7. Другие устройства
- •4.7.1. Системный таймер
- •4.8. Работа с файлами
- •4.9. Управление памятью
- •4.9.1. Обычная память
- •4.10. Загрузка и выполнение программ
- •4.11. Командные параметры и переменные среды
4.11. Командные параметры и переменные среды
В случае если команда не передавалась бы интерпретатору DOS, а выполнялась нами самостоятельно, то оказалось бы: чтобы запустить любую программу из-под shell.com, потребовалось бы предварительно переходить в каталог с этой программой или вводить ее с полным путем. Дело в том, что COMMAND.COM при запуске файла ищет его по очереди в каждом из каталогов, указанных в переменной среды PATH. DOS создает копию всех переменных среды (так называемое окружение DOS) для каждого запускаемого процесса. Сегментный адрес копии окружения для текущего процесса располагается в PSP по смещению 2Ch. В этом сегменте записаны все переменные подряд в форме ASCIZ-строк вида "COMSPEC=C:/WINDOWS/COMMAND.COM",0. По окончании последней строки стоит дополнительный нулевой байт, затем слово (обычно 1) — количество дополнительных строк окружения, а потом — дополнительные строки. Первая дополнительная строка — всегда полный путь и имя текущей программы — также в форме ASCIZ-строки. При запуске новой программы с помощью функции 4Bh можно создать полностью новое окружение и передать его сегментный адрес запускаемой программе в блоке ЕРВ или просто указать 0, позволив DOS скопировать окружение текущей программы.
Кроме того, в предыдущем примере мы передавали запускаемой программе (command.com) параметры (/с команда), но пока не объяснили, как программа может определить, что за параметры были переданы ей при старте. При запуске программы DOS помещает всю командную строку (включая последний символ 0Dh) в блок PSP запущенной программы по смещению 81h и ее длину в байт 80h (таким образом, длина командной строки не может быть больше 7Eh (126) символов). Под Windows 95 и 4DOS, если командная строка превышает эти размеры, байт PSP:0080h (длина) устанавливается в 7Fh, в последний байт PSP (PSP:00FFh) записывается 0Dh, первые 126 байт командной строки размещаются в PSP, а вся строка целиком — в переменной среды CMDLINE.
; cat.asm
; копирует объединенное содержимое всех файлов, указанных в командной строке,
; в стандартный вывод. Можно как указывать список файлов, так и использовать
; маски (символы "*" и "?") в одном или нескольких параметрах,
; например:
; cat header *.txt footer > all-texts помещает содержимое файла
; header, всех файлов с расширением .txt в текущем каталоге и файла
; footer - в файл all-texts
; длинные имена файлов не используются, ошибки игнорируются
;
.model tiny
.code
org 80h ; по смещению 80h от начала PSP находятся:
cmd_length db ? ; длина командной строки
cmd_line db ? ; и сама командная строка
org 100h ; начало СОМ-программы - 100h от начала PSP
start:
cld ; для команд строковой обработки
mov bp,sp ; сохранить текущую вершину стека в ВР
mov cl,cmd_length
cmp cl,1 ; если командная строка пуста -
jle show_usage ; вывести информацию о программе и выйти
mov ah,1Ah ; функция DOS 1Ah
mov dx,offset DTA
int 21h ; переместить DTA (по умолчанию она совпадает
; с командной строкой PSP)
; преобразовать список параметров в PSP:81h следующим образом:
; каждый параметр заканчивается нулем (ASCIZ-строка)
; адреса всех параметров помещаются в стек в порядке обнаружения
; в переменную argc записывается число параметров
mov cx,-1 ; для команд работы со строками
mov di,offset cmd_line ; начало командной строки в ES:DI
find_param:
mov al,' ' ; искать первый символ,
repz scasb ; не являющийся пробелом
dec di ; DI - адрес начала очередного параметра
push di ; поместить его в стек
inc word ptr argc ; и увеличить argc на один
mov si,di ; SI = DI для следующей команды lodsb
scan_params:
lodsb ; прочитать следующий символ из параметра,
cmp al,0Dh ; если это 0Dh - это был последний параметр
je params_ended ; и он кончился,
cmp al,20h ; если это 20h - этот параметр кончился,
jne scan_params ; но могут быть еще
dec si ; SI - первый байт после конца параметра
mov byte ptr [si],0 ; записать в него 0
mov di,si ; DI = SI для команды scasb
inc di ; DI - следующий после нуля символ
jmp short find_param ; продолжить разбор командной строки
params_ended:
dec si ; SI - первый байт после конца последнего
mov byte ptr [si],0 ; параметра - записать в него 0
; каждый параметр воспринимается как файл или маска для поиска файлов,
; все найденные файлы выводятся на stdout. Если параметр - не имя файла,
; то ошибка игнорируется
mov si,word ptr argc ; SI - число оставшихся параметров
next_file_from_param:
dec bp
dec bp ; BP - адрес следующего адреса параметра
dec si ; уменьшить число оставшихся параметров,
js no_more_params ; если оно стало отрицательным - все
mov dx,word ptr [bp] ; DS:DX - адрес очередного параметра
mov ah,4Eh ; Функция DOS 4Eh
mov cx,0100111b ; искать все файлы, кроме каталогов
; и меток тома
int 21h ; найти первый файл,
jc next_file_from_param ; если произошла ошибка - файла нет
call output_found ; вывести найденный файл на stdout
find_next:
mov ah,4Fh ; Функция DOS 4Fh
mov dx,offset DTA ; адрес нашей области DTA
int 21h ; найти следующий файл,
jc next_file_from_param ; если ошибка - файлы кончились
call output_found ; вывести найденный файл на stdout
jmp short find_next ; продолжить поиск файлов
no_more_params:
mov ax,word ptr argc
shl ax,1
add sp,ax ; удалить из стека 2 * argc байт
; (то есть весь список адресов
; параметров командной строки)
ret ; конец программы
; процедура show_usage
; выводит информацию о программе
show_usage:
mov ah,9 ; Функция DOS 09h
mov dx,offset usage
int 21h ; вывести строку на экран
ret ; выход из процедуры
; процедура output_found
; выводит в stdout файл, имя которого находится в области DTA
output_found:
mov dx,offset DTA+1Eh ; адрес ASCIZ-строки с именем файла
mov ax,3D00h ; Функция DOS 3Dh
int 21h ; открыть файл (al = 0 - только на чтение),
jc skip_file ; если ошибка - не трогать этот файл
mov bx,ax ; идентификатор файла - в ВХ
mov di,1 ; DI будет хранить идентификатор STDOUT
do_output:
mov cx,1024 ; размер блока для чтения файла
mov dx,offset DTA+45 ; буфер для чтения/записи располагается
; за концом DTA
mov ah,3Fh ; Функция DOS 3Fh
int 21h ; прочитать 1024 из файла,
jc file_done ; если ошибка - закрыть файл
mov cx,ax ; число реально прочитанных байт в СХ,
jcxz file_done ; если это не ноль - закрыть файл
mov ah,40h ; Функция DOS 40h
xchg bx,di ; BX = 1 - устройство STDOUT
int 21h ; вывод прочитанного числа байт в STDOUT
xchg di,bx ; вернуть идентификатор файла в ВХ,
jc file_done ; если ошибка - закрыть файл
jmp short do_output ; продолжить вывод файла
file_done:
mov ah,3Eh ; Функция DOS 3Eh
int 21h ; закрыть файл
skip_file:
ret ; конец процедуры output_found
usage db "cat.com v1.0",0Dh,0Ah
db "объединяет и выводит файлы на stdout",0Dh,0Ah
db "использование: cat имя_файла, ...",0Dh,0Ah
db "(имя файла может содержать маски * и ?)",0Dh,0Ah,'$'
argc dw 0 ; число параметров (должен быть 0 при старте программы!)
DTA: ; область DTA начинается сразу за концом файла,
; а сразу за областью DTA начинается
; 1024-байтный буфер для чтения файла
end start
Размер блока для чтения файла можно значительно увеличить, но в этом случае почти наверняка потребуется проследить за объемом памяти, доступным для программы.