2. Векторная таблица связи MS-DOS

Операционная система 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 , таблицу файлов, дисковые буферы и т. д.

В векторной таблице связи есть и другая полезная информация, открывающая доступ практически ко всем внутренним структурам данных операционной системы. Можно, например, получить доступ ко всем резидентным и загружаемым драйверам операционной системы. Можно узнать, какие в системе установлены дисковые устройства и каковы их характеристики. Зная форматы структур данных операционной системы, можно анализировать ошибочные ситуации при отладке программного обеспечения, разрабатывать программы, отображающие внутреннее состояние системы и конфигурацию устройств.

Если понятия и термины, перечисленные выше, вам незнакомы, не огорчайтесь. Через некоторое время мы их подробно рассмотрим. А пока займемся описанием формата векторной таблицы связи, а также назначением отдельных полей этой таблицы.

2.1. Поля векторной таблицы связи

Приведем список полей векторной таблицы связи 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, нетрудно проследить всю цепочку, организовав поиск нужного драйвера и непосредственное обращение к нему.

2.2. Как получить адрес векторной таблицы связи

Для получения адреса векторной таблицы связи можно воспользоваться недокументированной функцией 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 позволяют выделить из дальнего указателя, соответственно, сегмент и смещение.

2.3. Программа CVTADDR

Программа 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 (&regs, &regs, &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. Эта спецификация удобна для вывода значения дальнего указателя в формате <сегмент:смещение>.

2.4. Блоки управления памятью в MS-DOS

Вся оперативная память в MS-DOS разделена на фрагменты, перед которыми распложены блоки MCB . Как мы уже говорили в первой главе, эти блоки описывают фрагменты памяти.

Поле векторной таблицы связи mcb_seg содержит сегментный адрес первого блока управления памятью MCB . Блок MCB всегда начинается на границе параграфа, поэтому полный адрес первого блока будет равен mcb_seg:0.

В этом разделе мы научим вас просматривать цепочку блоков MCB и определять тип соответствующих этим блокам фрагментов памяти.

Распределение памяти в MS-DOS

Для лучшего понимания механизма управления памятью вспомним распределение памяти в 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 фрагмента памяти. Следующий фрагмент памяти начинается сразу за предыдущим. Все блоки управления памятью связаны в список.

Блоки 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

С помощью программы 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 (&regs, &regs, &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);
}

2.5. Список управляющих блоков устройств

Поле dev_cb векторной таблицы связи содержит дальний адрес списка блоков управления устройствами MS-DOS (MS-DOS Device Control Block), который мы назвали DDCB . Блок 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

Программа 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 (&regs, &regs, &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);
}

Программа DDCBLST1

Приведенный выше способ получения доступа к блокам 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 ( &regs, &regs, &sregs );
  if(regs.h.al == 0xff)
    return(LPDDCB)NULL;

  return((DDCB  far*)MK_FP (sregs.ds, regs.x.bx));
}

2.6. Системная таблица файлов SFT

MS-DOS создает системную таблицу файлов SFT (System File Table ) и помещает ее адрес в поле file_tab векторной таблицы связи. В этой таблице для каждого открытого файла хранится такая информация, как количество файловых идентификаторов, связанных с данным файлом, режим открытия файла (чтение, запись и т. д.), слово информации об устройстве, указатель на заголовок драйвера, обслуживающего данное устройство, элемент дескриптора файла (дата, время, имя файла, номер начального кластера, распределенного файлу), номер последнего прочитанного кластера и т. д.

Эта информация может пригодиться, например, при организации защиты программы от копирования путем ее привязки к номерам занимаемых файлом программы кластеров.

Заметьте, что получить информацию о начальном кластере файла довольно трудно - стандартные средства MS-DOS не предоставляют такой возможности. Приходится работать с диском на уровне секторов, отслеживать списки кластеров в таблице размещения файлов FAT , читать каталоги напрямую по секторам диска и т. д. Таблица файлов содержит этот номер в явном виде.

Строка файла config.sys может содержать оператор files=xx. Этот оператор, в конечном счете, определяет размер таблицы SFT .

Формат таблицы 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

Для подробной распечатки содержимого таблицы файлов можно использовать программу 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 (&regs, &regs, &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" будет записано содержимое таблицы файлов.

2.7. Список загружаемых драйверов устройств

Все драйверы, резидентные или подключенные к операционной системе во время обработки файла 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    

К этому списку мы еще вернемся при обсуждении процесса загрузки драйверов.

2.8. Блок описания дисков DINFO

Очень интересные сведения находятся в структуре, адрес которой находится в поле drv_info векторной таблицы связи. Это массив, описывающий дисковые устройства. Количество элементов в массиве равно количеству дисковых устройств в системе.

Для каждого диска в этом массиве можно найти текущий путь доступа в виде строки ASCIIZ , указатель на блок управления устройствами DDCB и номер начального кластера текущего каталога (кстати, если к какому-либо каталогу ни разу не обращались, в этом поле находится -1).

Формат массива описания дисковых устройств DINFO

Приведем формат элемента массива 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 ;

Программа DINFOLST

Как пример использования информации из массива 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 (&regs, &regs, &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;
  }
}