§ Заголовок файла

Формат хорошо описан в разных источниках, например в Википедии, но я продублирую информацию на сайте.
Структура заголовка EXE-файла (все значения 16 битные):
0x00 Signature        0x5A4D ("MZ")
0x02 Extra bytes      Кол-во байт в последней странице
0x04 Pages            Кол-во полных (или неполных) страниц по 512 байт
0x06 Relocation items Кол-во элементов в таблице релокации
0x08 Header size      Размер заголовка в параграфах (16 байт)
0x0A Minimum alloc    Кол-во параграфов (исключая PSP и программу), минимальное для работы программы
0x0C Maximum alloc    Запрошенное программой количество максимальных параграфов
0x0E Initial SS       Сегмент (перемещаемый) для SS
0x10 Initial SP       Значение SP после загрузки программы
0x12 Checksum         Когда добавляется сумма всех оставшихся слов в файле, результат должен быть 0
0x14 Initial IP       Значение IP после запуска
0x16 Initial CS       Сегмент (перемещаемый) для CS
0x18 Relocation tbl   Абсолютное смещение таблицы релокации
0x1A Overlay word     Значение, которые используется для оверлеев. Если равно 0, это главная программа.
----------------------
0x1C Reseved          Зарезервировано 8 байт
0x24 OemId
0x26 OemInfo
0x28 Reserved         Зарезервировано 20 байт
0x3C LFANew           Адрес Portable Executable заголовка
Начиная с 0x1C, может идти любая информация, которая касается оверлея. Оверлей - это такая программа, которая подгружается по время исполнения основной программы. То есть, как работает оверлей: когда загружается основная программа (у нее overlay word равно 0), то она вызывает особую DOS-функцию, которая может подгружать оверлейные программы.
Таким самым образом, вычисляется CS и SS относительно стартового сегмента (который находится сразу после PSP-блока), а DS и ES устанавливаются на сегмент PSP.
Файл с программой состоит из трех частей:
  • Заголовок (занимает 28 байт)
  • Таблица релокации
  • Образ с программой
Загрузка программы происходит следующим образом:
  • Сначала создается блок PSP (программный сегментный префикс)
  • Последовательно в память загружается весь образ с программой
  • Из таблицы релокации берутся смещения в образе и проставляются в загруженном образе программы

§ Таблица релокации

Эта таблица содержит элементы, которые состоят из двух машинных слов (16 битное смещение и 16 битный сегмент).
Допустим, в заголовке в поле 0x06 содержится число 5. Это значит, что в таблице релокации 5 элементов. Каждый элемент занимает 4 байта (пара смещение и сегмент):
 0 offset:segment
 1 offset:segment
 2 offset:segment
 3 offset:segment
 4 offset:segment
Абсолютный адрес относительно образа программы (а образ программы идет сразу же за заголовком), вычисляется так: segment*16 + offset. Когда этот адрес вычислен, туда добавляется СЛОВО (16 бит) стартового сегмента. В общем-то, и всё.

§ Пример загрузки программы


Разберем все поля:
Размер файла:             81
Длина образа:             49
Длина в блоках (x 512):   1
Длина образа % 512:       81
Старт данных:             0x20 (32)

Min памяти x byte:        512
Max памяти x byte:        1048560

SS:                     + 0x14
CS:                     + 0x2
SP:                       0x200
IP:                       0x0
CRC:                      0x0

Адрес таблицы настройки:  0x1c
Кол-во эл-тов настройки:  1

Сегмент оверлея:          0x0

0000:0021 == 00000041
Каким способом вычисляется длина образа:
  • Берется "длина в блоках" (Pages), если же "длина образа" (Extra bytes) != 0, то тогда "длина в блоках" (Pages) уменьшается на 1
  • Умножается на 512 полученная длина в блоках
  • Добавляется "длина образа" (Extra bytes)
1IF Extra = 0 THEN Size = Pages*512 ELSE Size = (Pages-1)*512 + Extra
В "Старт данных" записан адрес (0x20), откуда начинается образ данных.
Допустим, что хотим загрузить программу, начиная с базового сегмента 0x2335. Загружаем по адресу:
2335:0000 256 байт PSP
2345:0000 - образ программы -
Устанавливается DS = 2335, ES = 2335.
Ставится SP = 200h, IP = 0000h.
Вычисляется SS = 14h + (2335h + 10h) = 2359h
Вычисляется CS = 2h + (2335h + 10h) = 2347h