Форматы программных файлов
Теперь, когда мы знаем структуру памяти на момент завершения загрузки операционной системы, можно посмотреть, а что же происходит дальше, когда оператор запускает какую-нибудь программу.
Оператор может запустить два типа программ (если не считать командных файлов, которые, вообще говоря, не являются программами, состоящими из машинных кодов) - программы, имеющие расширение имени .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);}