Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых

MS-DOS для программиста

© Александр Фролов, Григорий Фролов
Том 18, М.: Диалог-МИФИ, 1995, 254 стр.

[Назад] [Содеожание] [Дальше]

3.1. Форматы программных файлов

В среде MS-DOS пользователь может запустить два типа программ (если не считать пакетных файлов, которые, вообще говоря, не являются программами, состоящими из машинных кодов). Файлы, содержащие программы этих двух типов, имеют расширение имени .com и .exe (мы будем называть их, соответственно, com-программы и exe-программы).

Указанные программные файлы имеют различный формат и загружаются по-разному, однако, когда загрузка завершена, структура распределенной для них памяти выглядит совершенно одинаково.

Программы, которые хранятся в файлах с расширением имени .com - это двоичный образ программы, состоящий из кода и данных. Образно говоря, com-файл содержит программу в "чистом" виде. Такая программа (как и exe-программа) может загружаться в любое место памяти. MS-DOS выполняет ее привязку к физическим адресам при загрузке с помощью установки сегментных регистров. Существенным ограничением com-программы является то, что она не может занимать больше одного сегмента (соответственно, стандартный com-файл не может иметь размер, превосходящий 64 Кбайта).

Программа второго типа (exe-программа) может иметь любой размер. В самом начале файла программы содержится заголовок (у файла com-программы заголовка нет). Этот заголовок используется операционной системой в процессе загрузки программы в память для правильной установки сегментных регистров. Заголовок exe-файла нужен только при загрузке; когда программа загружена и готова к работе, самого заголовка уже нет в памяти.

Заголовок EXE-файла

Заголовок exe-файла состоит из форматированной зоны и таблицы расположения сегментов (Relocation Table ). Форматированная зона выглядит следующим образом:

Смещение, байт Размер, байт Имя поля Описание
0 2 signature Два байта 'MZ' (4Dh, 5Ah), идентифицирующие exe-файл
2 2 part_pag Размер последней страницы программы в байтах (страница содержит 512 байт)
4 2 file_size Размер программы в страницах по 512 байт
6 2 rel_item Количество элементов в таблице расположения сегментов
8 2 hdr_size Размер заголовка файла в параграфах (длина параграфа - 16 байт)
10 2 min_mem Минимальное количество памяти в параграфах, которое нужно зарезервировать после области загруженной программы
12 2 max_mem Максимальное количество памяти в параграфах, которое нужно зарезервировать после области загруженной программы
14 2 ss_reg Начальное значение для загрузки сегментного регистра SS
16 2 sp_reg Начальное значение для загрузки регистра SP
18 2 chk_summ Контрольная сумма всех слов в файле
20 2 ip_reg Значение, которое будет загружено в регистр IP при запуске программы
22 2 cs_reg Смещение от начала программы для установки сегментного регистра кода CS
24 2 relt_off Смещение таблицы расположения сегментов программы от начала exe-файла
26 2 overlay Номер оверлея, равен 0 для основного модуля программы

Таблица расположения сегментов программы начинается сразу после форматированной области и состоит из четырехбайтовых значений в формате <смещение:сегмент>.

Область файла после таблицы расположения сегментов выравнивается на границу параграфа с помощью байта-заполнителя. Дальше начинается сама программа.

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

typedef struct 
{
  unsigned signature;
  unsigned part_pag;
  unsigned file_size;
  unsigned rel_item;
  unsigned hdr_size;
  unsigned min_mem;
  unsigned max_mem;
  unsigned ss_reg;
  unsigned sp_reg;
  unsigned chk_summ;
  unsigned ip_reg;
  unsigned cs_reg;
  unsigned relt_off;
  unsigned overlay;
} EXE_HDR;

typedef struct 
{
  unsigned offset;
  unsigned segment;
} RELOC_TAB;

Программа EXELIST

Для демонстрации приемов работы с заголовком exe-файла приведем исходный текст программы EXELIST (листинг 3.1).

Эта программа считывает форматированную часть заголовка exe-файла, проверяет наличие в его первых двух байтах признака exe-формата ('MZ'). Если признак имеется, программа выводит на экран расшифрованное содержимое заголовка и таблицу перемещений, если такая таблица присутствует. В качестве параметра при запуске надо передать программе путь к exe-файлу.


Листинг 3.1. Файл exelist\ exelist.cpp


#include <stdio.h>
#include <stdlib.h>

typedef struct
{
  unsigned signature;
  unsigned part_pag;
  unsigned file_size;
  unsigned rel_item;
  unsigned hdr_size;
  unsigned min_mem;
  unsigned max_mem;
  unsigned ss_reg;
  unsigned sp_reg;
  unsigned chk_summ;
  unsigned ip_reg;
  unsigned cs_reg;
  unsigned relt_off;
  unsigned overlay;
} EXE_HDR;

typedef struct 
{
  unsigned offset;
  unsigned segment;
} RELOC_TAB;

void main(int, char *[]);
int get_exeh(EXE_HDR *exeh, RELOC_TAB **rtb,
             FILE *exe_file);
int listhdr(char *path);

void main(int argc, char *argv[])
{
  printf("Просмотр заголовка exe-файла\n"
    "(C) Фролов А.В., 1995\n\n");

  if(argc != 2)
  {
    printf("  Укажите путь к exe-файлу в качестве"
           "параметра\n" );
    return;
  }

  if(listhdr(argv[1]) != 0)
  {
    printf("Ошибка в формате файла или нет такого"
      "файла\n");
    return;
  }
}

// -------------------------------------------
// listhdr
// Отображение заголовка exe-файла
// -------------------------------------------

int listhdr(char *path)
{
  EXE_HDR     header;
  RELOC_TAB   *reloc;
  FILE        *inpfile;
  int         i;

  if((inpfile = fopen(path,"rb")) == 0)
    return(-1);

  if(get_exeh(&header,&reloc,inpfile) != 0)
  {
    fclose(inpfile);
    return(-1);
  }
  printf("Магическое число:                   %04X\n"
	 "Длина последней страницы файла:      %d\n"
	 "Количество страниц в файле:          %d\n"
	 "Кол. элементов табл. перемещений:    %d\n"
	 "Размер заголовка в параграфах:       %d\n"
	 "Минимальная память для программы:    %04X\n"
	 "Максимальная память для программы:   %04X\n"
	 "Значение адреса стека SS:SP:         %04X:%04X\n"
	 "Контрольная сумма:                   %04X\n"
	 "Значения для регистров CS:IP:        %04X:%04X\n"
	 "Смещение табл. перемещений:          %02X\n"
	 "Номер оверлея:                       %d\n",
	 header.signature, header.part_pag,
	 header.file_size, header.rel_item,
	 header.hdr_size,  header.min_mem,
	 header.max_mem,   header.ss_reg,
	 header.sp_reg,    header.chk_summ,
	 header.cs_reg,    header.ip_reg,
	 header.relt_off,  header.overlay);

  if(reloc != 0)
  {
    printf("\nСодержимое таблицы перемещений:\n\n");

      for(i=0; i < header.rel_item; i++)
      {
        printf("%04X:%04X\n",
          (reloc+i)->segment, (reloc+i)->offset);
      }
      free(reloc);
   }

   fclose(inpfile);
   return(0);
}

// -------------------------------------------
// get_exeh
// Прочитать заголовок exe-файла
//
// Функция читает заголовок exe-файла в структуру
// типа exe_HDR, заказывает память для таблицы
// размещений сегментов и считывает таблицу
// в эту область. Адрес заказанной области
// помещается по адресу, указанному через
// параметр rtb.
// Если таблица размещений отсутствует, память для
// нее не заказывается.
//
// Параметры:
//   exeh -  указатель на структуру, которая
//           должна быть заполнена информацией
//           из заголовка exe-файла
//
//   rtb  -  указатель на слово памяти, в котором
//           хранится указатель на таблицу
//           размещений сегментов программы
//
//   exe_file - указатель на открытый exe-файл
//
// Возвращаемое значение:
//   0 при успешном считывании заголовка;
//  -1 в случае неправильного формата заголовка
// -------------------------------------------

int get_exeh(EXE_HDR *exeh, RELOC_TAB **rtb,
             FILE *exe_file)
{
  int i, j, k;

  // Считываем форматированную часть заголовка
  for(i=0; i < sizeof(EXE_HDR); i++)
  {
    *(((char*)exeh) + i) = fgetc(exe_file);
    if(feof(exe_file)) break;
  }

  // Убеждаемся, что это EXE-файл
  if(exeh->signature != 0x5a4d) return(-1);

  // Eсли есть таблица перемещений,
  // заказываем для нее память
  if((i = exeh->rel_item) != 0)
  {
    *rtb =
      (RELOC_TAB*)malloc(i * sizeof(RELOC_TAB) + 16);

    // Считываем таблицу перемещений
    for(k=0; k<i; k++)
    {
      for(j=0; j < sizeof(RELOC_TAB); j++)
      {
        *((char*)(*rtb)+j+k*sizeof(RELOC_TAB)) =
           fgetc(exe_file);
        if(feof(exe_file)) break;
      }
    }
  }
  else
    *rtb = (RELOC_TAB *)NULL;
  return(0);
}

[Назад] [Содеожание] [Дальше]