Операционная система MS-DOS содержит векторную таблицу связи основных управляющих блоков. Зная адрес этой таблицы можно получить доступ к внутренним структурам данных операционной системы. К сожалению, в руководстве программиста ничего не говорится о векторной таблице связи .
Мы попытаемся в некоторой степени восполнить указанный пробел, так как изучение векторной таблицы связи позволит глубже осознать принципы работы операционной системы. Информация из векторной таблицы связи будет полезной для составления программ отображения распределения памяти, вывода списка загруженных драйверов, списка устройств прямого доступа и т. д.
Вся информация, приведенная в данной главе, отсутствует в документации на операционную систему MS-DOS. Мы собирали ее из разных доступных источников, таких, например, как список прерываний Ральфа Брауна, и проверили для MS-DOS версии 6.22.
Не будет лишним напомнить еще раз, что использование недокументированных возможностей всегда сопряжено с риском. Программа, активно использующая недокументированные прерывания и структуры данных, возможно, не будет работать в следующей версии MS-DOS. Так как многие пользователи запускают программы MS-DOS в среде виртуальной машины операционных систем Microsoft Windows или OS/2, могут возникнуть проблемы совместимости - такие виртуальные машины часто способны предоставить в распоряжение программ только документированные прерывания.
Однако популярность некоторых недокументированных прерываний (в частности, необходимых для разработки резидентных программ) перевела их в разряд полудокументированных. Он стали стандартом де-факто. Кроме того, ряд недокументированных возможностей ранних версий MS-DOS стал документированным и, следовательно, безопасным для использования.
Итак, что же представляет собой векторная таблица связи?
Вслед за областью данных BIOS , расположенной в начале оперативной памяти, расположена область данных MS-DOS. Здесь находятся внутренние переменные и структуры данных MS-DOS.
Основные структуры данных организованы в виде дерева. Корнем является векторная таблица связи, которая содержит адреса всех остальных структур: список блоков управления памятью (MCB ), список блоков управления устройствами MS-DOS , таблицу файлов, дисковые буферы и т. д.
В векторной таблице связи есть и другая полезная информация, открывающая доступ практически ко всем внутренним структурам данных операционной системы. Можно, например, получить доступ ко всем резидентным и загружаемым драйверам операционной системы. Можно узнать, какие в системе установлены дисковые устройства и каковы их характеристики. Зная форматы структур данных операционной системы, можно анализировать ошибочные ситуации при отладке программного обеспечения, разрабатывать программы, отображающие внутреннее состояние системы и конфигурацию устройств.
Если понятия и термины, перечисленные выше, вам незнакомы, не огорчайтесь. Через некоторое время мы их подробно рассмотрим. А пока займемся описанием формата векторной таблицы связи, а также назначением отдельных полей этой таблицы.
Приведем список полей векторной таблицы связи
MS-DOS. Для каждого поля мы укажем его смещение
относительно адреса, полученного при помощи
недокументированной функции 52h, размер в байтах,
имя и краткое описание.
Смещение, байт |
Размер, байт |
Имя поля |
Описание |
-2 |
2 |
mcb_seg |
Сегмент первого блока памяти MCB |
0 |
4 |
dev_cb |
Указатель на первый блок управления
устройствами MS-DOS (MS-DOS Device Control Block) |
4 |
4 |
file_tab |
Указатель на таблицу файлов MS-DOS |
8 |
4 |
clock_dr |
Указатель на драйвер CLOCK$ , установленный
в файле config.sys или резидентный |
12 |
4 |
con_dr |
Указатель на драйвер CON , установленный в
файле config.sys или резидентный |
16 |
2 |
max_btbl |
Максимальный размер блока (в байтах) для
устройства, выполняющего передачу данных
отдельными блоками |
18 |
4 |
disk_buf |
Указатель на структуру, описывающую
дисковые буферы |
22 |
4 |
drv_info |
Указатель на массив информации об
устройствах |
26 |
4 |
fcb_tabl |
Указатель на таблицу FCB |
30 |
2 |
fcb_size |
Размер таблицы FCB |
32 |
1 |
num_bdev |
Число устройств, выполняющих передачу
данных отдельными блоками |
33 |
1 |
lastdriv |
Значение LASTDRIVE в файле config.sys (по
умолчанию равно 5) |
34 |
? |
null_dr |
Начало драйвера NUL. Этот драйвер всегда
первый в списке драйверов MS-DOS |
Заметим, что функция 52h возвращает адрес поля dev_cb. Для того чтобы получить адрес слова, содержащего сегмент первого блока памяти MCB , необходимо уменьшить значение смещения, полученное от функции 52h, на 2 байта.
Для удобства работы с векторной таблицей связи определим тип CVT следующим образом (имена полей соответствуют приведенному выше списку):
typedef struct { unsigned mcb_seg; void far *dev_cb; void far *file_tab; void far *clock_dr; void far *con_dr; unsigned max_btbl; void far *disk_buf; void far *drv_info; void far *fcb_tabl; unsigned fcb_size; unsigned char num_bdev; unsigned char lastdriv; } CVT ;
Определим также тип LPCVT как дальний указатель на векторную таблицу связи MS-DOS:
typedef CVT far* LPCVT ;
Поле mcb_seg содержит сегментную компоненту адреса первого блока MCB . Зная это значение, нетрудно проследить и при необходимости даже изменить структуру блоков памяти.
Например, некоторые вирусы искусственно уменьшают размер самого последнего свободного блока памяти, записывая на освободившееся место свое тело. Зная структуру блоков MCB , можно удалить из памяти ненужные резидентные программы .
В поле dev_cb векторной таблицы связи хранится указатель на список блоков управления дисковыми устройствами MS-DOS. Каждый такой блок содержит описание характеристик устройства, а также указатель на драйвер, обслуживающий устройство.
Ваша программа может использовать блок управления дисковым устройством для доступа к диску на низком уровне или для получения справочной информации об устройстве.
Поле file_tab содержит адрес таблицы файлов MS-DOS. В этой таблице для каждого открытого файла хранится разнообразная информация, такая как количество назначенных файлу идентификаторов (т. е. сколько раз файл был открыт), режим использования файла, номер первого кластера диска, распределенного файлу и т. д.
Вся эта информация может потребоваться при организации доступа к файлу на уровне кластеров, например, в системах защиты файлов от несанкционированного копирования. Заметим, что с помощью только стандартных средств MS-DOS программа не сможет узнать номер первого кластера диска, распределенного файлу.
С помощью полей clock_dr и con_dr программа может получить доступ, соответственно, к драйверу CLOCK$ и драйверу консоли CON . Это может понадобиться для организации вызова драйвера непосредственно из программы.
Поле max_btbl содержит размер блока устройств, которые выполняют обмен данными отдельными блоками (пример - драйвер диска). Для MS-DOS версии 6.22 размер блока равен 512 байт.
Поле drv_info содержит указатель на массив, в котором хранится информация о дисковых устройствах. В этом массиве вы сможете найти текущий путь доступа для каждого диска, номер первого кластера диска, распределенного каталогу и адрес соответствующего блока управления дискового устройства.
Анализируя этот массив, программа может определить количество установленных в системе дисковых устройств и их параметры.
В поле fcb_tabl находится указатель на таблицу блоков FCB . Размер этой таблицы записан в поле fcb_size и определяется оператором fcbs=хх, расположенном в файле config.sys .
Поле lastdriv содержит значение параметра оператора lastdive, расположенном в файле config.sys (или значение, принятое по умолчанию). Его можно использовать для определения максимального количества дисковых устройств в системе.
В поле num_bdev хранится количество действительно используемых дисковых устройств.
Поле nul_dr само по себе не содержит никакой полезной информации. Имеет значение лишь его адрес - в этом месте расположен самый первый в цепочке драйвер MS-DOS. Зная адрес поля nul_dr, нетрудно проследить всю цепочку, организовав поиск нужного драйвера и непосредственное обращение к нему.
Для получения адреса векторной таблицы связи можно воспользоваться недокументированной функцией 52h прерывания INT 21h . После вызова этой функции регистры ES:BX будут содержать искомый адрес. Так как описание этой функции отсутствует в руководстве по MS-DOS версии 6.22, в следующих версиях операционной системы, возможно, придется искать другой способ получения адреса векторной таблицы связи . Может также измениться формат этой таблицы.
Программа, составленная на языке ассемблера, может получить адрес векторной функции в регистрах ES:BX следующим образом:
mov ax, 5200h int 21h
Приведем исходный текст функции, составленной на языке программирования С, которая возвращает тот же самый адрес:
void far *get_cvt(void) { union REGS inregs, outregs; struct SREGS segregs; inregs.h.ah = 0x52; intdosx (&inregs, &outregs, &segregs); return(MK_FP (segregs.es,outregs.x.bx)); }
Функция get_cvt вызывает прерывание MS-DOS, пользуясь для этого функцией intdosx . В качестве параметров ей передаются адреса структур и объединения inregs, outregs и segregs.
Перед вызовом функции intdosx необходимо записать в поля объединения inregs нужные значения регистров. После того как эта функция вернет управление, в поля объединения outregs и структуры segregs будут записаны значения регистров общего назначения и сегментных регистров, полученные после возврата из обработчика прерывания.
В файле dos.h описаны макрокоманды MK_FP , FP_SEG и FP_OFF :
#define MK_FP (seg,ofs)((void _seg*)(seg)+(void near*)(ofs)) #define FP_SEG (fp)((unsigned)(void _seg*)(void far*)(fp)) #define FP_OFF (fp)((unsigned)(fp))
С помощью макрокоманды MK_FP можно сконструировать дальний указатель из значений сегмента и смещения. Макрокоманды FP_SEG и FP_OFF позволяют выделить из дальнего указателя, соответственно, сегмент и смещение.
Программа CVTADDR (листинг 2.1) выводит на консоль адрес векторной таблицы связи.
Листинг 2.1. Файл cvtaddr\cvtaddr.cpp
#include <stdio.h> #include <conio.h> #include <dos.h> typedef struct { unsigned mcb_seg; void far *dev_cb; void far *file_tab; void far *clock_dr; void far *con_dr; unsigned max_btbl; void far *disk_buf; void far *drv_info; void far *fcb_tabl; unsigned fcb_size; unsigned char num_bdev; unsigned char lastdriv; } CVT ; typedef CVT far* LPCVT ; void main(void) { union REGS regs; struct SREGS sregs; LPCVT lpCVT; // Получаем адрес векторной таблицы связи regs.h.ah = 0x52; intdosx (®s, ®s, &sregs); // Передвигаем указатель на поле msb_seg lpCVT = (LPCVT )MK_FP (sregs.es, regs.x.bx - 2); // Выводим адрес векторной таблицы связи, // вычисленный с учетом смещения printf("\nАдрес CVT : %Fp", (LPCVT )lpCVT); printf("\n\nНажмите любую клавишу..."); getch(); }
Обратите внимание, что указатель lpCVT устанавливается на поле msb_seg, для чего значение компоненты смещения адреса, полученного от функции 52h, уменьшается на 2 байта. Это сделано для удобства использования структуры CVT .
При выводе адреса векторной таблицы связи мы использовали спецификацию формата вывода %Fp. Эта спецификация удобна для вывода значения дальнего указателя в формате <сегмент:смещение>.
Вся оперативная память в MS-DOS разделена на фрагменты, перед которыми распложены блоки MCB . Как мы уже говорили в первой главе, эти блоки описывают фрагменты памяти.
Поле векторной таблицы связи mcb_seg содержит сегментный адрес первого блока управления памятью MCB . Блок MCB всегда начинается на границе параграфа, поэтому полный адрес первого блока будет равен mcb_seg:0.
В этом разделе мы научим вас просматривать цепочку блоков MCB и определять тип соответствующих этим блокам фрагментов памяти.
Для лучшего понимания механизма управления памятью вспомним распределение памяти в MS-DOS:
Диапазон адресов |
Содержимое |
0000:0000 |
Векторы прерываний |
0000:0400 |
Область данных BIOS |
0000:0500 |
Область данных MS-DOS |
xxxx:0000 |
Область программ MS-DOS.В ней находится
расширение BIOS , обработчики прерываний MS-DOS,
буферы, внутренние структуры данных MS-DOS,
загружаемые драйверы устройств |
xxxx:0000 |
Резидентная порция командного
процессора command.com |
xxxx:0000 |
Резидентные программы |
xxxx:0000 |
Запущенные прикладные программы типа COM
или exe |
xxxx:0000 |
Транзитная порция command.com |
A000:0000 |
Память EGA , используемая в некоторых
видеорежимах |
B000:0000 |
Память монохромного видеоконтроллера |
B800:0000 |
Память видеоконтроллера CGA |
C800:0000 |
Внешнее ПЗУ |
F600:0000 |
ПЗУ интерпретатора BASIC |
FE00:0000 |
ПЗУ BIOS |
Первый килобайт памяти занимает таблица векторов прерываний . Она содержит 256 элементов, имеющих размер 4 байта. Это дальние адреса обработчиков прерываний, состоящие из компонент сегмента и смещения. Подробно формат и использование этой таблицы будет обсуждаться в главе, посвященной прерываниям .
Адреса 0000:0400 - 0000:04FF (или от 0040:0000 до 0050:0000) занимает область данных BIOS . Это так называемые внутренние переменные BIOS . К ним можно обращаться для получения различной информации. При этом необходимо только помнить, что формат этой области может быть различным для различных версий BIOS.
Начиная с адреса 0000:0500 (или с адреса 0050:0000, что одно и то же) следует область данных MS-DOS. Здесь MS-DOS хранит свои внутренние таблицы, переменные и структуры данных. Формат этой области (и ее размер) зависит от версии операционной системы.
Далее следует большая область памяти, используемая MS-DOS. Здесь находятся:
После драйверов располагается резидентная порция командного процессора command.com . Она, в частности, обрабатывает прерывания INT 22h, INT 23h и INT 24h .
Следующая область памяти занимается резидентными программами.
После резидентных программ находится выполняющаяся в настоящий момент программа (запущенная из файла с расширением имени .com или .exe). Она может занимать всю оставшуюся память до адреса A000:0000 или только ее некоторую часть.
Нижнюю часть адресного пространства (до адреса A000:0000) занимает транзитная часть command.com . Она может перекрываться выполняющейся программой. Если программа перекроет транзитную часть command.com, то после завершения выполнения программы эта часть командного интерпретатора будет загружена заново.
Область адресов от A000:0000 до C800:0000 используется видеоконтроллерами. Каждый тип видеоконтроллера использует эту часть памяти по-своему.
Далее и до конца границы первого мегабайта оперативной памяти идет область ПЗУ. Там расположено ПЗУ базовой системы ввода/вывода BIOS , ПЗУ интерпретатора BASIC (если такое ПЗУ есть, что совсем не обязательно), расширение BIOS (например, расширение для видеоконтроллера или контроллера диска). Кроме того, в этой области могут находиться порты ввода/вывода некоторых устройств, обращение к которым выполняется аналогично обращению к памяти (устройства, имеющие ввод/вывод, отображенный на память).
Диапазон адресов свыше первого мегабайта используется для машин класса не ниже AT. Это так называемая расширенная память (Extended Memory). Она используется операционной системой MS-DOS для организации "электронного" диска, кэш -памяти для дисков, для загрузки резидентных программ и драйверов (совместно с драйвером emm386.exe ).
Некоторые прикладные программы хранят в этой области свои данные.
Непосредственная адресация расширенной памяти возможна только в защищенном режиме работы процессора. Этот режим используется в операционных системах Windows, Windows NT , OS/2, UNIX и т. п.
С расширенной памятью не следует путать дополнительную память (Expanded Memory ). Эта память отображается с помощью специальной аппаратуры и драйверов в область адресного пространства, лежащую ниже границы первого мегабайта основной оперативной памяти. MS-DOS может использовать эту память аналогично расширенной памяти.
Зона памяти, начиная с области программ MS-DOS и до видеопамяти разбита на фрагменты. Перед каждым таким фрагментом находится блок управления памятью MCB .
Сегментный адрес первого блока MCB , как мы уже говорили, находится в векторной таблице связи в поле mcb_seg.
Внутри блока MCB хранится длина описываемого данным MCB фрагмента памяти. Следующий фрагмент памяти начинается сразу за предыдущим. Все блоки управления памятью связаны в список.
Блоки MCB бывают двух типов - M и Z. M-блоки - это промежуточные блоки. Блок типа Z является последним блоком в списке и может быть только один.
Приведем формат блока MCB :
Смещение, байт |
Размер, байт |
Имя поля |
Описание |
0 |
1 |
type |
Тип блока MCB (M или Z) |
1 |
2 |
owner |
Сегментная компонента адреса владельца
блока; этот блок всегда выровнен на границу
параграфа (если 0, то блок описывает сам себя) |
3 |
2 |
size |
Число параграфов в этом блоке |
5 |
11 |
reserve |
Зарезервировано |
Напомним, что параграф имеет размер 16 байт.
Для удобства работы с блоком MCB определим тип MCB:
typedef struct { unsigned char type; unsigned owner; unsigned size; char reserve[11]; } MCB ;
Вы можете получить обширную информацию о распределении фрагментов памяти с помощью внешней команды MS-DOS с именем mem. Запустите ее с параметрами /d и /p. На экране вы увидите примерно следующее:
Conventional Memory Detail: Segment Total Name Type ------- ---------------- ----------- -------- 00000 1 039 (1K) Interrupt Vector 00040 271 (0K) ROM Communication Area 00050 527 (1K) MS-DOS Communication Area 00070 2 752 (3K) IO System Data CON System Device Driver AUX System Device Driver PRN System Device Driver CLOCK$ System Device Driver A:-D: System Device Driver COM1 System Device Driver LPT1 System Device Driver LPT2 System Device Driver LPT3 System Device Driver COM2 System Device Driver COM3 System Device Driver COM4 System Device Driver 0011C 104 (5K) MSDOS System Data 0025B 16 496 (16K) IO System Data 576 (1K) SETVERXX Installed Device=SETVER 1 152 (1K) XMSXXXX0 Installed Device=HIMEM 4 128 (4K) EMMQXXX0 Installed Device=EMM386 4 432 (4K) FILES=80 256 (0K) FCBS=4 512 (1K) BUFFERS=15 2 288 (2K) LASTDRIVE =Z 3 008 (3K) STACKS=9,256 00662 80 (0K) MSDOS System Program 00667 48 (0K) COMMAND Data 0066A 2 656 (3K) COMMAND Program 00710 80 (0K) MSDOS -- Free -- 00715 1 040 (1K) COMMAND Environment 00756 224 (0K) NC Environment 00764 28 288 (28K) MSCDEX Program 00E4C 30 368 (30K) SMARTDRV Program 015B6 272 (0K) MOUSE Program 015C7 12 912 (13K) NC Program 018EE 224 (0K) COMMAND Data 018FC 2 656 (3K) COMMAND Program 019A2 272 (0K) COMMAND Environment 019B3 224 (0K) MEM Environment 019C1 88 992 (87K) MEM Program 02F7B 460 864 (450K) MSDOS -- Free -- Upper Memory Detail: Segment Region Total Name Type ------- ------ ------------- ----------- -------- 0C94A 1 82 256 (80K) IO System Data 82 224 (80K) MITSUMI Inst. Device=SGCDM 0DD5F 1 8 320 (8K) IO System Data 8 288 (8K) CON Inst. Device=DISPLAY 0DF67 1 224 (0K) SWAKEYB Environment 0DF75 1 848 (1K) SWAKEYB Program 0DFAA 1 24 336 (24K) MOUSE Data 0E59B 1 9 776 (10K) MSDOS -- Free -- 0EC01 2 3 888 (4K) IO System Data 3 856 (4K) IFS$HLP$ Inst. Device=IFSHLP 0ECF4 2 4 288 (4K) MSDOS -- Free -- Memory Summary: Type of Memory Total = Used + Free ---------------- ---------- ---------- ---------- Conventional 655 360 105 424 549 936 Upper 133 984 119 920 14 064 Reserved 393 216 393 216 0 Extended (XMS) 15 594 656 2 335 904 13 258 752 ---------------- ---------- ---------- ---------- Total memory 16 777 216 2 954 464 13 822 752 Total under 1 MB 789 344 225 344 564 000 Memory accessible using Int 15h 0 (0K) Largest executable program size 549 840 (537K) Largest free upper memory block 9 776 (10K) MS-DOS is resident in the high memory area. XMS version 3.00; driver version 3.16
Программа подробно расписывает распределение фрагментов памяти с указанием их начального сегментного адреса и размера. С помощью программы mem.exe вы можете определить назначение каждого фрагмента памяти.
Например, в начале памяти по адресу 0000 располагается таблица векторов прерываний (Interrupt Vector). Она занимает 1 Кбайт памяти.
Начиная с адреса 0011Ch находится область системных данных MS-DOS. В ней, в частности, находится векторная таблица связи.
С помощью программы mem.exe вы можете определить расположение в памяти резидентных программ и драйверов, а также размер занимаемый ими памяти. Например, программе SMARTDRV отведен фрагмент памяти с начальным адресом 00E4Ch, причем размер этого фрагмента равен 30 Кбайт.
Мы уже отмечали, что при использовании драйвера emm386.exe пользователь может переместить часть резидентных программ и драйверов в верхнюю область памяти, освободив место в пределах первых 640 Кбайт адресного пространства. В приведенном выше примере драйвер устройства чтения компакт-дисков MITSUMI загружен в верхнюю область памяти начиная с адреса 0C94Ah.
В разделе Memory Summary отображается общий, использованный и свободный размер стандартной памяти (Conventional), верхней области памяти (Upper), зарезервированной, например, для организации "теневого" ПЗУ в оперативной памяти (Reserved), а также расширенной памяти (Extended).
Существует несколько других удобных программ для просмотра списка блоков MCB . Вот какую информацию выдает известная программа mi.com из пакета PCSHELL при запуске с параметром /A:
Memory Info v5.8 Copyright 1989 Central Point Software, Inc. All rights reserved. Conventional memory. Total: 640k Largest executable program: 485k Type Paragraphs Bytes Owner ---- ---------- ----- ------------- Sys 0BA4-18C5h 53792 0008h < MS-DOS > Free 18C7-18CFh 144 0000h < MS-DOS > Env 18D1-18D2h 32 18D4h JYRKEYB Prog 18D4-1904h 784 18D4h JYRKEYB C:\MS-DOS\JYRKEYB.COM C Prog 1906-1A69h 5696 1906h COMMAND Env 1A6B-1A7Dh 304 1906h COMMAND 1A7F-1A82h 64 1906h COMMAND Free 1A84-1A93h 256 0000h < MS-DOS > Prog 1A95-1DD8h 13376 1A95h MOUSE Env 1DDA-1DEDh 320 1ED8h NS Prog 1DEF-1ED6h 3712 1DEFh SHELLB DOSSHELL Prog 1ED8-21EBh 12608 1ED8h NS f:\norton\NS.EXE Env 21ED-2200h 320 2202h NC Prog 2202-2527h 12896 2202h NC f:\norton\NC.EXESocha 2529-253Ch 320 253Eh COMMAND Prog 253E-26A1h 5696 253Eh COMMAND /a Env 26A3-26B5h 304 253Eh COMMAND Env 26B7-26CAh 320 26CCh MI Prog 26CC-9FFFh 485k 26CCh MI c:\dos\MI.COM /a
Программа сообщает размер стандартной оперативной памяти (640 Кбайт), максимальный размер области памяти, доступной для запускаемой программы (485 Кбайт). Затем она выводит на экран список блоков памяти с указанием типа, сегментных адресов занимаемых параграфов памяти, размера в байтах и имени владельца блока памяти. Программа mi.com различает четыре типа блоков памяти:
Откуда программа mi.com берет информацию о типе блока памяти и имени программы? Системный блок распознается по занимаемым адресам, программный - по наличию правильного префикса программного сегмента (будет описан ниже).
Блок переменных среды находится перед программным блоком и содержит кроме собственно переменных среды еще и полный путь к файлу запущенной программы. Если блок не системный, программный, или не является блоком среды и если в поле владельца этого блока записан нуль, программа отмечает такой блок памяти как свободный.
Видно, что для каждой запущенной программы создается два блока памяти - блок среды и программный блок. Среда формируется при загрузке операционной системы с помощью команд SET и содержит строки вида:
SET LIB=D:\C600\LIB
Строки хранятся в формате ASCIIZ , т. е. закрыты двоичным нулем. Вся таблица переменных среды также закрыта двоичным нулем. После переменных среды в блоке памяти, отведенном для среды, содержится путь к файлу программы в виде строки ASCIIZ.
Блок памяти типа Prog (программный) независимо от формата загрузочного модуля (загруженного из файла с расширением имени .com или .exe) начинается с префикса программного сегмента PSP , за которым следует сама программа.
Таким образом, при загрузке для программы выделяются блоки памяти, располагающиеся в следующей последовательности:
Приведем фрагмент дампа оперативной памяти, полученные при отладке программы mi.com с помощью отладчика Microsoft CodeView. Дамп памяти начинается с адреса 6D47:0000.
Первая строка дампа (смещение 000-00F) - это MCB блока памяти переменных среды. Это не последний блок памяти, поэтому MCB имеет тип M. В поле владельца блока памяти находятся нули, следовательно, MCB принадлежит сам себе. Поле длины содержит значение 0014 - это количество параграфов в блоке памяти переменных среды.
Со смещением 010 начинается блок переменных среды. Из дампа видно, что строки определения переменных среды закрыты двоичным нулем. Таблица переменных среды также закрыта нулем (смещение 13B). Со смещением 13C записано слово 0001 (количество слов в последующей строке), после которого расположен полный путь к файлу запущенной программы.
000 4D00 0014 0000 0000 0000 0000 0000 0000 M............... 010 434F 4D53 5045 433D 433A 5C43 4F4D 4D41 COMSPEC=C:\COMMA 020 4E44 2E43 4F4D 0054 4D50 3D67 3A5C 7465 ND.COM.TMP=g:\te 030 6D70 0050 4154 483D 673A 5C3B 643A 5C71 mp.PATH=g:\;d:\q 040 6332 5C62 696E 3B65 3A5C 6336 3030 5C62 c2\bin;e:\c600\b 050 696E 623B 653A 5C63 3630 305C 6269 6E3B inb;e:\c600\bin; 060 633A 5C64 6F73 3B63 3A5C 6172 633B 663A c:\dos;c:\arc;f: 070 5C6E 6F72 746F 6E3B 653A 5C77 6F72 643B \norton;e:\word; 080 004C 4942 3D65 3A5C 7163 325C 4C49 4200 .LIB=e:\qc2\LIB. 090 494E 434C 5544 453D 673A 5C69 6E63 6C75 INCLUDE=g:\inclu 0A0 6465 3B65 3A5C 7163 325C 494E 434C 5544 de;e:\qc2\INCLUD 0B0 453B 653A 5C63 746F 6F6C 735C 696E 636C E;e:\ctools\incl 0C0 7564 653B 0048 454C 5046 494C 4553 3D65 ude;.HELPFILES=e 0D0 3A5C 4336 3030 5C48 454C 505C 2A2E 484C :\C600\HELP\*.HL 0E0 503B 0049 4E49 543D 653A 5C43 3630 305C P;.INIT=e:\C600\ 0F0 494E 4954 0044 4D41 4B45 3D67 3A5C 646D INIT.DMAKE=g:\dm 100 616B 653B 0056 4547 413D 653A 5C76 6567 ake;.VEGA=e:\veg 110 613B 004E 4152 4348 454C 503D 663A 5C61 a;.NARCHELP=f:\a 120 7263 5C6E 6172 633B 0048 454C 5050 4154 rc\narc;.HELPPAT 130 483D 643A 5C68 656C 703B 0000 0100 433A H=d:\help;....C: 140 5C44 4F53 5C6D 692E 636F 6D00 6D00 7016 \MS-DOS\mi.com.m.p.
Далее начинается MCB программного блока памяти (смещение 150). Это последний блок памяти, поэтому MCB имеет тип Z. Непосредственно за MCB располагается префикс программного сегмента PSP (смещение 160). Размер PSP - 256 байт, его формат будет описан в третьей главе.
И, наконец, со смещением 260 расположена сама программа mi.com.
150 5A00 00A3 3216 0000 6D69 0000 0000 0000 Z...2...mi...... 160 CD20 00A0 009A F0FE 1DF0 3D09 B22E 340A . ........=...4. 170 B22E 850E B22E CD26 FFFF FFFF FFFF FFFF .......&........ 180 FFFF FFFF FFFF FFFF FFFF FFFF 486D 92B3 ............Hm.. 190 FD4B 1400 1800 5D6D FFFF FFFF 0000 0000 .K....]m........ 1A0 0000 0000 0000 0000 0000 0000 0000 0000 ................ 1B0 CD21 CB00 0000 0000 0000 0000 0020 2020 .!........... 1C0 2020 2020 2020 2020 0000 0000 0020 2020 ..... 1D0 2020 2020 2020 2020 0000 0000 0000 0000 ........ 1E0 0320 2F61 0038 0F00 2C09 0001 0000 4005 . /a.8..,.....@. 1F0 7808 0000 9000 0100 04B3 0D07 ED2F 14B3 x............/.. 200 7501 4A36 0000 0000 0000 4003 A308 3F00 u.J6......@...?. 210 FD4B FD4B 0100 2ABA 0000 0000 026F 5718 .K.K..*......oW. 220 3800 0500 3800 CF08 DD26 2D09 FD4B FD4B 8...8....&-..K.K 230 E408 0000 4200 A308 3F00 FD4B FD4B E408 ....B...?..K.K.. 240 1304 0000 5718 C000 0500 C000 CE02 CE03 ....W........... 250 5718 8400 0500 8400 CE02 CE03 973A 92B3 W............:.. 260 E9BB 000D 0A4D 656D 6F72 7920 496E 666F .....Memory Info 270 2076 352E 380D 0A43 6F70 7972 6967 6874 v5.8..Copyright 280 2031 3938 3920 4365 6E74 7261 6C20 506F 1989 Central Po 290 696E 7420 536F 6674 7761 7265 2C20 496E int Software, In 2A0 632E 2020 416C 6C20 7269 6768 7473 2072 c. All rights r 2B0 6573 6572 7665 642E 0D0A 0A00 4279 2047 eserved.....By G 2C0 5744 2030 312F 3036 2F38 391A 2D2D 2D2D WD 01/06/89.---- 2D0 5041 5443 4820 4152 4541 2D2D 2D2D 2D2D PATCH AREA------ 2E0 2D2D FF90 5601 4102 0000 0290 5D6D 5C6D --..V.A.....]m\m
С помощью программы MCBLIST (листинг 2.2) вы сможете просмотреть весь список блоков MCB . Для каждого блока программа выводит его адрес, тип, адрес владельца и размер. Все перечисленные выше параметры считываются непосредственно из блоков MCB.
Листинг 2.2. Файл mcblist\mcblist.cpp
#include <dos.h> #include <stdio.h> #include <conio.h> typedef struct { unsigned char type; unsigned owner; unsigned size; char reserve[11]; } MCB ; typedef MCB far* LPMCB; typedef struct { unsigned mcb_seg; void far *dev_cb; void far *file_tab; void far *clock_dr; void far *con_dr; unsigned max_btbl; void far *disk_buf; void far *drv_info; void far *fcb_tabl; unsigned fcb_size; unsigned char num_bdev; unsigned char lastdriv; } CVT ; typedef CVT far* LPCVT ; LPMCB get_nmcb(LPMCB); void main(void) { union REGS regs; struct SREGS sregs; LPCVT lpCVT; LPMCB lpMCB; // Получаем адрес векторной таблицы связи regs.h.ah = 0x52; intdosx (®s, ®s, &sregs); // Передвигаем указатель на поле msb_seg lpCVT = (LPCVT )MK_FP (sregs.es, regs.x.bx - 2); // Получаем указатель на первый блок MCB lpMCB = (LPMCB)MK_FP (lpCVT->mcb_seg, 0); printf("\nБлоки управления памятью MCB " "\n(C) Фролов А.В., 1995\n" "\nАдрес MCB Тип Владелец Размер" "\n--------- --- -------- ------" "\n"); for(;;) { // Если последний блок MCB , выходим // из цикла if(lpMCB == NULL) break; // Выводим информацию о блоке MCB printf("%Fp %c %04X %04X\n", lpMCB, lpMCB->type, lpMCB->owner, lpMCB->size); // Получаем адрес следующего блока MCB lpMCB=get_nmcb(lpMCB); } printf("\nНажмите любую клавишу..."); getch(); } // ------------------------------------------ // get_nmcb // Функция возвращает адрес следующего блока // MCB или NULL, если это последний блок // ------------------------------------------ LPMCB get_nmcb(LPMCB mcb) { unsigned seg, off; // Проверяем тип блока if(mcb->type == 'M') { // Вычисляем адрес следующего MCB seg = FP_SEG (mcb) + mcb->size + 1; off = FP_OFF (mcb); return((MCB far *) MK_FP (seg,off)); } else return((LPMCB)NULL); }
Поле dev_cb векторной таблицы связи содержит дальний адрес списка блоков управления устройствами MS-DOS (MS-DOS Device Control Block), который мы назвали DDCB . Блок DDCB строится операционной системой для каждого дискового устройства и содержит информацию о характеристиках этого устройства, указатель на заголовок драйвера, обслуживающего данное устройство и многое другое.
Как мы уже говорили, блок DDCB может быть использован программами, которые выполняют доступ к диску на уровне секторов. Подробнее назначение и использование полей блока DDCB будет описано в разделе, посвященном файловой системе, так как эта информация требуется в основном для организации работы с диском на низком уровне.
Ниже мы привели формат блока управления устройствами DDCB , адрес которого доступен через векторную таблицу связи MS-DOS.
Смещение, байт |
Размер, байт |
Имя поля |
Описание |
|
0 |
1 |
drv_num |
Номер устройства (0 соответствует
устройству А:, 1 - В: и т. д.) |
|
1 |
1 |
drv_numd |
Дополнительный номер устройства
(используется, если драйвер обслуживает
несколько устройств) |
|
2 |
2 |
sec_size |
Размер сектора в байтах |
|
4 |
1 |
clu_size |
Число, на единицу меньшее количества
секторов в кластере |
|
5 |
1 |
clu_base |
Если содержимое этого поля не равно
нулю, то для получения общего числа секторов в
кластере надо возвести 2 в степень clu_base и
получившееся число прибавить к clu_size |
|
6 |
2 |
boot_siz |
Количество зарезервированных секторов
(загрузочные секторы, начало корневого каталога) |
|
8 |
1 |
fat_num |
Количество копий таблицы размещения
файлов FAT |
|
9 |
2 |
max_dir |
Максимальное количество дескрипторов
файлов в корневом каталоге (т. е. максимальное
количество файлов, которое может содержать
корневой каталог на этом устройстве) |
|
11 |
2 |
data_sec |
Номер первого сектора данных на диске
(номер сектора, соответствующего кластеру с
номером 2) |
|
13 |
2 |
hi_clust |
Максимальное количество кластеров
(равно количеству кластеров данных, увеличенному
на 1) |
|
15 |
1 |
fat_size |
Количество секторов, занимаемых одной
копией FAT |
|
16 |
1 |
reserv1 |
Зарезервировано |
|
17 |
2 |
root_sec |
Номер первого сектора корневого
каталога |
|
19 |
4 |
drv_addr |
Дальний адрес заголовка драйвера,
обслуживающего данное устройство |
|
23 |
1 |
media |
Байт описания среды носителя данных |
|
24 |
1 |
acc_flag |
Флаг доступа, 0 означает, что к
устройству был доступ |
|
25 |
4 |
next |
Адрес следующего блока DDCB .Для
последнего блока в поле смещения находится число
FFFFh |
|
29 |
2 |
reserv2 |
Зарезервировано |
|
31 |
2 |
built |
Значение 52EEh в этом поле означает, что
блок DDCB был сформирован |
Мы приведем также описание формата блока DDCB в следующем виде:
typedef struct { unsigned char drv_num; unsigned char drv_numd; unsigned sec_size; unsigned char clu_size; unsigned char clu_base; unsigned boot_siz; unsigned char fat_num; unsigned max_dir; unsigned data_sec; unsigned hi_clust; unsigned char fat_size; char reserv1; unsigned root_sec; void far *drv_addr; unsigned char media; unsigned char acc_flag; struct _DDCB _ far *next; unsigned reserv2; unsigned built; } DDCB ;
Еще раз уместно заметить, что формат этого блока не описан в документации по MS-DOS, поэтому он может отличаться в различных версиях операционных систем.
Программа DDCBLIST (листинг 2.3) выводит на консоль содержимое всех блоков DDCB в расшифрованном виде. Так как при большом количестве дисков выводится очень много информации, следует использовать средство переназначения стандартного устройства вывода MS-DOS, запустив программу следующим образом:
ddcblist > ddcb.txt
Затем вы сможете просмотреть полученный файл при помощи любого текстового редактора.
Листинг 2.3. Файл ddcblist\ddcblist.cpp
#include <dos.h> #include <stdio.h> #include <stdlib.h> typedef struct { unsigned mcb_seg; void far *dev_cb; void far *file_tab; void far *clock_dr; void far *con_dr; unsigned max_btbl; void far *disk_buf; void far *drv_info; void far *fcb_tabl; unsigned fcb_size; unsigned char num_bdev; unsigned char lastdriv; } CVT ; typedef CVT far* LPCVT ; typedef struct _DDCB _ { unsigned char drv_num; unsigned char drv_numd; unsigned sec_size; unsigned char clu_size; unsigned char clu_base; unsigned boot_siz; unsigned char fat_num; unsigned max_dir; unsigned data_sec; unsigned hi_clust; unsigned char fat_size; char reserv1; unsigned root_sec; void far *drv_addr; unsigned char media; unsigned char acc_flag; struct _DDCB _ far *next; unsigned reserv2; unsigned built; } DDCB ; typedef DDCB far* LPDDCB; void main(void); LPDDCB get_fddcb(LPCVT cvt); LPDDCB get_nddcb(LPDDCB ddcb); void main(void) { union REGS regs; struct SREGS sregs; LPCVT lpCVT; LPDDCB lpDDCB; printf("\nБлоки управления дисковыми устройствами DDCB " "\n(C) Фролов А.В., 1995\n\n"); // Получаем адрес векторной таблицы связи regs.h.ah = 0x52; intdosx (®s, ®s, &sregs); // Передвигаем указатель на поле msb_seg lpCVT = (LPCVT )MK_FP (sregs.es, regs.x.bx - 2); // Получаем адрес первого блока DDCB lpDDCB = get_fddcb(lpCVT); for(;;) { // Если это последний блок, завершаем цикл if(lpDDCB == NULL) break; printf("Адрес DDCB : %Fp\n" "Номер устройства: %d\n" "Дополнительный номер: %d\n" "Размер сектора: %d\n" "Размер кластера в секторах: %d\n" "База размера кластера: %d\n" "Зарезервировано секторов: %d\n" "Число копий FAT : %d\n" "Макс. файлов в корневом каталоге : %d\n" "Первый кластер данных: %d\n" "Всего кластеров: %ld\n" "Размер FAT в секторах: %d\n" "Первый сектор корневого каталога: %d\n" "Поле reserv1: %01X\n" "Адрес драйвера: %Fp\n" "Байт описателя среды носителя: %01X\n" "Флаг доступа: %01X\n" "Адрес следующего DDCB : %Fp\n" "Поле reserv2: %04X\n" "Блок заполнен: %04X\n" "-------------------------------------\n\n", lpDDCB, lpDDCB->drv_num, lpDDCB->drv_numd, lpDDCB->sec_size, lpDDCB->clu_size, lpDDCB->clu_base, lpDDCB->boot_siz, lpDDCB->fat_num, lpDDCB->max_dir, lpDDCB->data_sec, lpDDCB->hi_clust, lpDDCB->fat_size, lpDDCB->root_sec, lpDDCB->reserv1, lpDDCB->drv_addr, lpDDCB->media, lpDDCB->acc_flag, lpDDCB->next, lpDDCB->reserv2, lpDDCB->built); // Получаем адрес следующего блока DDCB lpDDCB = get_nddcb(lpDDCB); } } // --------------------------------------------- // get_fddcb // Функция возвращает адрес первого блока DDCB . // В качестве параметра ей следует передать // адрес векторной таблицы связи // --------------------------------------------- LPDDCB get_fddcb(LPCVT cvt) { LPDDCB ddcb; ddcb = (LPDDCB)cvt->dev_cb; return(ddcb); } // --------------------------------------------- // get_nddcb // Функция возвращает адрес следующего блока DDCB // --------------------------------------------- LPDDCB get_nddcb(LPDDCB ddcb) { LPDDCB ddcb_n; ddcb_n = (LPDDCB)ddcb->next; if(FP_OFF (ddcb_n) == 0xffff) return((LPDDCB)NULL); return(ddcb_n); }
Приведенный выше способ получения доступа к блокам DDCB больше всего подходит для просмотра блоков управления всеми дисковыми устройствами. Если вам требуется получить DDCB для какого-нибудь конкретного устройства, можно воспользоваться недокументированной функцией 32h прерывания INT 21h (со всеми ограничениями, связанными с использованием недокументированных возможностей).
Функция 32h получает в регистре DL номер устройства (0 - текущий диск , 1 - А: и т. д.) и возвращает в регистрах DS:BX адрес соответствующего DDCB . Если номер устройства был задан неправильно, после выполнения функции регистр AL будет содержать значение FFh.
Если требуется получить адрес DDCB для НГМД , необходимо установить дискету в приемный карман накопителя.
Программа, исходный текст который приведен в листинге 2.4, выводит адреса всех DDCB в виде списка. Можете запустить ее (она есть на дискете, которая продается вместе с книгой) и посмотреть, что получится. Перед запуском не забудьте вставить дискеты во все НГМД .
Листинг 2.4. Файл ddcblst1\ddcblst1.cpp
#include <dos.h> #include <stdio.h> #include <stdlib.h> typedef struct _DDCB _ { unsigned char drv_num; unsigned char drv_numd; unsigned sec_size; unsigned char clu_size; unsigned char clu_base; unsigned boot_siz; unsigned char fat_num; unsigned max_dir; unsigned data_sec; unsigned hi_clust; unsigned char fat_size; char reserv1; unsigned root_sec; void far *drv_addr; unsigned char media; unsigned char acc_flag; struct _DDCB _ far *next; unsigned reserv2; unsigned built; } DDCB ; typedef DDCB far* LPDDCB; void main(void); LPDDCB get_ddcb(unsigned char device_number); void main(void) { LPDDCB lpDDCB; unsigned char dr; for(dr=1;; dr++) { lpDDCB = get_ddcb(dr); if(lpDDCB == NULL) break; printf("%Fp\n", lpDDCB); } } LPDDCB get_ddcb(unsigned char device_number) { union REGS regs; struct SREGS sregs; regs.h.ah = 0x32; regs.h.al = 0; regs.h.dl = device_number; intdosx ( ®s, ®s, &sregs ); if(regs.h.al == 0xff) return(LPDDCB)NULL; return((DDCB far*)MK_FP (sregs.ds, regs.x.bx)); }
MS-DOS создает системную таблицу файлов SFT (System File Table ) и помещает ее адрес в поле file_tab векторной таблицы связи. В этой таблице для каждого открытого файла хранится такая информация, как количество файловых идентификаторов, связанных с данным файлом, режим открытия файла (чтение, запись и т. д.), слово информации об устройстве, указатель на заголовок драйвера, обслуживающего данное устройство, элемент дескриптора файла (дата, время, имя файла, номер начального кластера, распределенного файлу), номер последнего прочитанного кластера и т. д.
Эта информация может пригодиться, например, при организации защиты программы от копирования путем ее привязки к номерам занимаемых файлом программы кластеров.
Заметьте, что получить информацию о начальном кластере файла довольно трудно - стандартные средства MS-DOS не предоставляют такой возможности. Приходится работать с диском на уровне секторов, отслеживать списки кластеров в таблице размещения файлов FAT , читать каталоги напрямую по секторам диска и т. д. Таблица файлов содержит этот номер в явном виде.
Строка файла config.sys может содержать оператор files=xx. Этот оператор, в конечном счете, определяет размер таблицы SFT .
Каждая таблица SFT содержит указатель на следующую таблицу, а также количество управляющих блоков файлов DFCB .
Приведем формат таблицы SFT :
Смещение, байт |
Размер, байт |
Имя поля |
Описание |
0 |
4 |
next |
Указатель на следующую таблицу файлов SFT
|
4 |
2 |
file_count |
Количество файлов, описанных в этой
таблице с помощью блоков DFCB |
Блоки DFCB (по одному для каждого файла) расположены в конце таблицы SFT и имеют следующий формат:
Смещение, байт |
Размер, байт |
Имя поля |
Описание |
0 |
2 |
handl_num |
Количество идентификаторов, связанных с
данным файлом |
2 |
1 |
access_mode |
Режим доступа к файлу, заданный при
открытии файла |
3 |
2 |
reserv1 |
Зарезервировано |
5 |
2 |
dev_info |
Информация IOCTL , полученная для
устройства, на котором расположен этот файл
(подробно формат и назначение этого поля будут
рассмотрены в главе, посвященной драйверам) |
7 |
4 |
driver |
Указатель на драйвер, обслуживающий
устройство, содержащее файл |
11 |
2 |
first_clu |
Номер первого кластера, распределенного
файлу |
13 |
2 |
time |
Время последнего изменения файла в
упакованном формате |
15 |
2 |
date |
Дата последнего изменения файла в
упакованном формате |
17 |
4 |
fl_size |
Размер файла в байтах |
21 |
4 |
offset |
Текущее смещение внутри файла в байтах |
25 |
2 |
reserv2 |
Зарезервировано |
27 |
2 |
reserv7 |
Зарезервировано |
29 |
3 |
reserv3 |
Зарезервировано |
32 |
1 |
reserv4 |
Зарезервировано |
33 |
11 |
filename |
Имя файла в формате FCB (имя выровнено на
левую границу поля, дополнено пробелами до 8
символов, справа к нему прилегает 3 символа
расширения без точки) |
44 |
2 |
reserv5 |
Зарезервировано |
46 |
2 |
ownr_psp |
Адрес блока PSP программы, открывшей файл |
48 |
2 |
reserv6 |
Зарезервировано |
50 |
2 |
last_clu |
Номер только что прочитанного кластера |
52 |
4 |
reserv8 |
Зарезервировано |
Приведем соответствующие типы данных:
typedef struct _DFCB _ { unsigned handl_num; unsigned char access_mode; unsigned reserv1; unsigned dev_info; void far *driver; unsigned first_clu; unsigned time; unsigned date; unsigned long fl_size; unsigned long offset; unsigned reserv2; unsigned reserv7; unsigned reserv3; char reserv4; char filename[11]; char reserv5[6]; unsigned ownr_psp; unsigned reserv6; unsigned last_clu; char reserv8[4]; } DFCB ; typedef struct _SFT _ { struct _SFT _ far *next; unsigned file_count; DFCB dfcb; } SFT ;
Для подробной распечатки содержимого таблицы файлов можно использовать программу SFTLIST (листинг 2.5), которая была проверена в MS-DOS версии 6.22.
Листинг 2.5. Файл sftlist\sftlist.cpp
#include <dos.h> #include <stdio.h> #include <stdlib.h> typedef struct _DFCB _ { unsigned handl_num; unsigned char access_mode; unsigned reserv1; unsigned dev_info; void far *driver; unsigned first_clu; unsigned time; unsigned date; unsigned long fl_size; unsigned long offset; unsigned reserv2; unsigned reserv7; unsigned reserv3; char reserv4; char filename[11]; char reserv5[6]; unsigned ownr_psp; unsigned reserv6; unsigned last_clu; char reserv8[4]; } DFCB ; typedef DFCB far* LPDFCB; typedef struct _DFT_ { struct _DFT_ far *next; unsigned file_count; DFCB dfcb; } SFT ; typedef SFT far* LPSFT; typedef struct { unsigned mcb_seg; void far *dev_cb; void far *file_tab; void far *clock_dr; void far *con_dr; unsigned max_btbl; void far *disk_buf; void far *drv_info; void far *fcb_tabl; unsigned fcb_size; unsigned char num_bdev; unsigned char lastdriv; } CVT ; typedef CVT far* LPCVT ; void main(void); LPSFT get_fsft(LPCVT cvt); LPSFT get_nsft(LPSFT sft); void main(void) { union REGS regs; struct SREGS sregs; LPCVT lpCVT; LPSFT lpSFT; unsigned i,j,k; LPDFCB lpDFCB; FILE * list; printf("Информация об открытых файлах MS-DOS\n" "(C) Фролов А.В., 1995\n"); // Открываем файл для вывода информации о файлах list = fopen("!sft.lst","w+"); fprintf(list,"Информация об открытых файлах MS-DOS\n" "(C) Фролов А.В., 1995\n\n"); // Получаем адрес векторной таблицы связи regs.h.ah = 0x52; intdosx (®s, ®s, &sregs); // Передвигаем указатель на поле msb_seg lpCVT = (LPCVT )MK_FP (sregs.es, regs.x.bx - 2); lpSFT = get_fsft(lpCVT); for(;;) { if(lpSFT == NULL) break; i = lpSFT->file_count; fprintf(list,"Таблица файлов SFT : %Fp, в ней %d файлов\n" "===========================================\n", lpSFT, i); for(j=0; j<i; j++) { lpDFCB = (&(lpSFT->dfcb)) + j; // Адрес DFCB файла fprintf(list,"\nDFCB файла: %Fp\n\n", lpDFCB); fprintf(list,"Имя файла: "); for(k=0; k<11; k++) fputc(lpDFCB->filename[k], list); fprintf(list, "\nКоличество идентификаторов: %d\n" "Режим доступа: %d\n" "Поле reserv1: %04X\n" "Информация об устройстве: %04X\n" "Адрес драйвера: %Fp\n" "Начальный кластер: %d\n" "Время: %04X\n" "Дата: %04X\n" "Размер файла в байтах: %ld\n" "Текущее смещение в файле: %ld\n" "Поле reserv2: %04X\n" "Последний прочитанный кластер: %d\n" "Сегмент PSP владельца файла: %04X\n" "Поле reserv7: %d\n" "-------------------------------\n\n", lpDFCB->handl_num, lpDFCB->access_mode, lpDFCB->reserv1, lpDFCB->dev_info, lpDFCB->driver, lpDFCB->first_clu, lpDFCB->time, lpDFCB->date, lpDFCB->fl_size, lpDFCB->offset, lpDFCB->reserv2, lpDFCB->last_clu, lpDFCB->ownr_psp, lpDFCB->reserv7); } lpSFT = get_nsft(lpSFT); } fclose(list); } LPSFT get_nsft(LPSFT sft) { LPSFT sft_next; sft_next = sft->next; if(FP_OFF (sft_next) == 0xffff) return((LPSFT)NULL); return(sft_next); } LPSFT get_fsft(LPCVT cvt) { LPSFT sft; sft = (LPSFT)cvt->file_tab; return(sft); }
После запуска этой программы в файл с именем "!dfcb.lst" будет записано содержимое таблицы файлов.
Все драйверы, резидентные или подключенные к операционной системе во время обработки файла config.sys , связаны в список. Первый драйвер - это так называемый NUL-драйвер . Он располагается в памяти всегда непосредственно после векторной таблицы связи. Подробно о драйверах будет рассказано отдельной главе, а сейчас мы кратко опишем, как проследить цепочку загруженных драйверов и получить некоторые сведения об этих драйверах.
Драйвер - это программа, которая занимает только один сегмент (64 Кбайт) и имеет в самом начале специальный заголовок. Заголовок драйвера имеет следующий формат:
Смещение, байт |
Размер, байт |
Имя поля |
Описание |
0 |
4 |
next |
Указатель на заголовок следующего
драйвера. Если смещение адреса следующего
драйвера равно FFFFh, это последний драйвер в
цепочке |
4 |
2 |
attrib |
Атрибуты драйвера |
6 |
2 |
strateg |
Смещение программы стратегии драйвера |
8 |
2 |
interrupt |
Смещение программы обработки
прерывания для драйвера |
10 |
8 |
dev_name |
Имя устройства для символьных устройств
или количество обслуживаемых устройств (только
для устройств, выполняющих обмен данными
отдельными блоками) |
Мы приведем программу, которая сканирует список драйверов и выводит на стандартное устройство вывода адрес драйвера, его атрибуты, имя устройства для символьных устройств и количество обслуживаемых устройств (только для блочных драйверов).
Эта программа написана на языке ассемблера (листинг 2.6).
Листинг 2.6. Файл drvlist\drvlist.asm
; Программа выводит информацию о загруженных драйверах ; ; Эта макрокоманда выводит символы на экран ; @@out_ch MACRO c1,c2,c3,c4,c5 mov ah,02h IRP chr,<c1,c2,c3,c4,c5> IFB <chr> EXITM ENDIF mov dl,chr int 21h ENDM ENDM .MODEL tiny DOSSEG .DATA msg DB 13,10,"Device Drivers Information V1.1", 13, 10 DB "(C) Фролов А.В., 1995",13,10,13,10 DB "Address Attr Device Name",13,10 DB "------- ---- -----------",13,10 DB "$" bl_msg DB "--------> Block Device, Number of Units: ","$" .CODE .STARTUP mov ah, 9h ; Выводим заголовок mov dx, OFFSET msg int 21h mov ah,52h ; Получаем адрес первого int 21h ; драйвера в цепочке add bx,22h ; es:bx - адрес первого драйвера dr_loop: call show_driver_info ; выводим параметры cmp WORD PTR es:[bx+2], 0ffffh ; последний ? jz end_of_driver_list cmp WORD PTR es:[bx], 0ffffh jz end_of_driver_list mov ax,es:[bx] ; получаем адрес следующего mov cx,es:[bx+2] ; драйвера mov bx,ax mov es,cx jmp dr_loop end_of_driver_list: .EXIT 0 show_driver_info proc near ;es:bx - адрес драйвера push es push bx mov ax,es ; выводим адрес драйвера call Print_word @@out_ch ':' mov ax,bx call Print_word @@out_ch ' ',' ' mov ax,es:[bx+4] ; выводим атрибут драйвера call Print_word @@out_ch ' ',' ' test WORD PTR es:[bx+4],8000h ; проверяем, это символьный jz is_block ; драйвер или блочный mov cx,8 ; для символьного выводим mov si,bx ; имя драйвера pr_name: mov al,BYTE PTR es:[si+10] @@out_ch al inc si loop pr_name jmp nxt is_block: mov ah, 9h ; для блочного драйвера mov dx, OFFSET bl_msg ; выводим количество int 21h ; логических устройств, mov al,BYTE PTR es:[bx+10] ; которые обслуживает mov ah,0 ; этот драйвер call Print_word nxt: @@out_ch 13,10 pop bx pop es ret show_driver_info endp ; Вывод на экран содержимого регистра AX Print_word proc near ;-------------------- push ax push bx push dx ; push ax mov cl,8 rol ax,cl call Byte_to_hex mov bx,dx @@out_ch bh @@out_ch bl ; pop ax call Byte_to_hex mov bx,dx @@out_ch bh @@out_ch bl ; pop dx pop bx pop ax ret Print_word endp ; Byte_to_hex proc near ;-------------------- ; al - input byte ; dx - output hex ;-------------------- push ds push cx push bx ; lea bx,tabl mov dx,cs mov ds,dx ; push ax and al,0fh xlat mov dl,al ; pop ax mov cl,4 shr al,cl xlat mov dh,al ; pop bx pop cx pop ds ret ; tabl db '0123456789ABCDEF' Byte_to_hex endp END
Для трансляции приведенного в листинге 2.5 исходного текста мы использовали пакетный файл, запускающий Turbo Assembler и Turbo Linker, которые входят в состав Borland C++ версий 3.0 и 3.1 (листинг 2.7).
Листинг 2.7. Файл drvlist\mk.bat
tasm drvlist tlink drvlist /t
Если запустить программу DRVLIST, она выведет на экран список всех загруженных драйверов и сведения о них:
Device Drivers Information V1.1 (C) Фролов А.В., 1995 Address Attr Device Name ------- ---- ----------- 011C:0048 8004 NUL 0E5D:2192 08C2 --------> Block Device, Number of Units: 0004 DD61:0000 C053 CON EC03:0000 D000 IFS$HLP$ C94C:0000 C800 MITSUMI 02CB:003A C000 $MMXXXX0 02CB:0000 C000 EMMQXXX0 0282:0000 A000 XMSXXXX0 025D:0000 8000 SETVERXX 0070:0023 8013 CON 0070:0035 8000 AUX 0070:0047 A0C0 PRN 0070:0059 8008 CLOCK$ 0070:006B 08C2 --------> Block Device, Number of Units: 0004 0070:007B 8000 COM1 0070:008D A0C0 LPT1 0070:009F A0C0 LPT2 0070:00B8 A0C0 LPT3 0070:00CA 8000 COM2 0070:00DC 8000 COM3 0070:00EE 8000 COM4
К этому списку мы еще вернемся при обсуждении процесса загрузки драйверов.
Очень интересные сведения находятся в структуре, адрес которой находится в поле drv_info векторной таблицы связи. Это массив, описывающий дисковые устройства. Количество элементов в массиве равно количеству дисковых устройств в системе.
Для каждого диска в этом массиве можно найти текущий путь доступа в виде строки ASCIIZ , указатель на блок управления устройствами DDCB и номер начального кластера текущего каталога (кстати, если к какому-либо каталогу ни разу не обращались, в этом поле находится -1).
Приведем формат элемента массива DINFO :
Смещение, байт |
Размер, байт |
Имя поля |
Описание |
|
0 |
64 |
path |
Текущий путь доступа для диска |
|
64 |
2 |
reserv1 |
Зарезервировано |
|
66 |
2 |
reserv2 |
Зарезервировано |
|
68 |
1 |
reserv3 |
Зарезервировано |
|
69 |
4 |
ddcb |
Адрес блока DDCB , соответствующего
данному устройству |
|
73 |
2 |
cdir_clu |
Первый кластер текущего каталога на
диске. 0 соответствует корневому каталогу, -1 -
если к диску еще не обращались |
|
75 |
2 |
reserv4 |
Зарезервировано |
|
77 |
2 |
reserv5 |
Зарезервировано |
|
79 |
2 |
reserv6 |
Зарезервировано |
|
81 |
7 |
reserv7 |
Зарезервировано |
Для доступа к массиву из программ, составленных на языке программирования С, мы определили тип данных DINFO :
typedef struct { char path[64]; unsigned reserv1; unsigned reserv2; unsigned char reserv3; DDCB far *ddcb; unsigned cdir_clu; unsigned reserv4; unsigned reserv5; unsigned reserv6; unsigned char reserv7[7]; } DINFO ;
Как пример использования информации из массива DINFO приведем исходный текст программы DINFOLST, которая выводит содержимое массива на экран (листинг 2.8).
Листинг 2.8. Файл dinfolst\ dinfolst.cpp
#include <dos.h> #include <stdio.h> #include <stdlib.h> typedef struct { unsigned mcb_seg; void far *dev_cb; void far *file_tab; void far *clock_dr; void far *con_dr; unsigned max_btbl; void far *disk_buf; void far *drv_info; void far *fcb_tabl; unsigned fcb_size; unsigned char num_bdev; unsigned char lastdriv; } CVT ; typedef CVT far* LPCVT ; typedef struct _DDCB _ { unsigned char drv_num; unsigned char drv_numd; unsigned sec_size; unsigned char clu_size; unsigned char clu_base; unsigned boot_siz; unsigned char fat_num; unsigned max_dir; unsigned data_sec; unsigned hi_clust; unsigned char fat_size; char reserv1; unsigned root_sec; void far *drv_addr; unsigned char media; unsigned char acc_flag; struct _DDCB _ far *next; unsigned reserv2; unsigned built; } DDCB ; typedef DDCB far* LPDDCB; typedef struct { char path[64]; unsigned reserv1; unsigned reserv2; unsigned char reserv3; LPDDCB ddcb; unsigned cdir_clu; unsigned reserv4; unsigned reserv5; unsigned reserv6; unsigned char reserv7[7]; } DINFO ; typedef DINFO far* LPDINFO; void main(void); void main(void) { union REGS regs; struct SREGS sregs; LPCVT lpCVT; LPDINFO lpDINFO; unsigned i,j,k; printf("Информация о дисковых устройствах\n" "(C) Фролов А.В., 1995\n" "---------------------------------\n"); // Получаем адрес векторной таблицы связи regs.h.ah = 0x52; intdosx (®s, ®s, &sregs); // Передвигаем указатель на поле msb_seg lpCVT = (LPCVT )MK_FP (sregs.es, regs.x.bx - 2); // Адрес таблицы дисковых устройств lpDINFO = (LPDINFO)lpCVT->drv_info; // Количество дисковых устройств i = lpCVT->num_bdev; for(j=0; j<i; j++) { printf("Адрес: %Fp, путь: %Fs\n" "Первый кластер каталога: %d\n\n", lpDINFO, lpDINFO->path, lpDINFO->cdir_clu); lpDINFO = lpDINFO + 1; } }