Операционная система MS DOS

       

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


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

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

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

Программа в формате EXE может иметь любой размер. В самом начале файла программы содержится заголовок (у COM-файла заголовка нет). Этот заголовок используется операционной системой в процессе загрузки программы в память для правильной установки сегментных регистров. Заголовок 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 смещение от начала файла таблицы расположения сегментов программы
(+26) 2 overlay номер оверлея, равен 0 для основного модуля
<
Таблица расположения сегментов программы начинается сразу после форматированной области и состоит из четырехбайтовых значений в формате "смещение:сегмент".

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

В файле sysp.h есть описание заголовка файла и таблицы расположения сегментов, которые вы можете использовать при обработке заголовка EXE-файла:

typedef struct _EXE_HDR_ { 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 _RELOC_TAB_ { unsigned offset; unsigned segment; } RELOC_TAB;

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

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

void main(int, char *[]);

void main(int argc, char *argv[]) {

printf("Распечатка заголовка EXE-файла\n" "Copyright (C)Frolov A., 1990\n\n");

if( argc != 2 ) { printf( " Задайте путь EXE-файла в качестве" "параметра\n" ); exit(0); }

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

int gethdr( 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); }

Приведенная выше программа для чтения заголовка EXE-файла пользуется функцией get-exeh:

/** *.Name get_exeh * *.Title Прочитать заголовок EXE-файла * *.Descr Функция читает заголовок EXE-файла в * структуру типа EXE_HDR, заказывает память * для таблицы размещений сегментов и считывает * таблицу в эту область. Адрес заказанной области * помещается по адресу, передаваемому в rtb. * Если таблица размещений отсутствует, память для * нее не заказывается. * *.Params int get_exeh(EXE_HDR *exeh,RELOC_TAB **rtb, * FILE *exe_file) * * exeh - указатель на структуру, которая * должна быть заполнена информацией * из заголовка EXE-файла * * rtb - указатель на указатель на таблицу * размещений сегментов программы * * exe_file - указатель на открытый EXE-файл * (до вызова функции нельзя обращаться * к этому файлу, т.к. считается, что * указатель текущего смещения * установлен на начало файла) * *.Return 0 при успешном считывании заголовка; * -1 в случае неправильного формата заголовка **/

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

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);

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 *)0;

return(0);}


Содержание раздела