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