Структура PE модуля
В начале PE модуля перед заголовком находится заглушка DOS, которая представляет собой минимальную DOS-программу («this program cannot run in DOS mode»).
Заголовок PE файла содержится после DOS заглушки и начинается с сигнатуры PE00.
Кроме сигнатуры заголовок содержит такую информацию как расположение и размер областей кода программ и данных, указание на то, с какой ОС предполагается использовать данный файл, а также начальный размер стека.
За сигнатурой PE00 следует сигнатура типа Image File Header. Поля этой сигнатуры содержат только самую общую информацию о файле.
После заголовка PE файла находится таблица секций. Эта таблица содержит инфу о каждой секции отображения.
Секции в отображении упорядочены по их стартовому адресу, а не в алфавитном порядке.
Таблица секций PE файла не хранит значение селектора для каждой части программного кода или данных. Вместо этого каждый элемент таблицы секций хранит адрес, по которому исходные данный файла были отображены в память.
Несмотря на то, что секции аналогичны 32-разрядным сегментам, они не являются индивидуальными сегментами. Секция просто отвечает диапазону памяти виртуального адресного пространства процессора. Особенностью PE файла является то, что информация об импортируемых функциях храниться в своей собственной секции, так же как и таблица экспортируемых модулем функций.
Любая программа или данные, которые могут понадобиться программе или операционной системе, получают свою собственную секцию.
За таблицей секций располагаются сами секции. В первой располагается секция .text. В ней собран весь программный код общего назначения, генерируемый компилятором и ассемблером.
Поскольку PE файлы работают в 32-разрядном режиме, компоновщик объединяет все секции .text из различных объектных файлов в одну большую секцию .text в exe-файле. Кроме кода собственно программы в файле будет присутствовать дополнительный программный код в секции .text помимо того, который создается компилятором или используется из библиотек поддержки выполнения программы.
В PE файле в случае вызова функции из другого модуля инструкция CALL сгенерированная компилятором не передает управление непосредственно данной функции в DLL. Вместо этого инструкция CALL передает управление команде JMP DWORD PTR [xxxxxxxx] также находящейся в секции .text.
Команда JMP передает управление по адресу, хранящемся в двойном слове в секции .idata. Это двойное слово содержит настоящий адрес точки входа функции ОС.
Организовав таким образом вызовы dll в PE файлах, все вызовы данной функции dll находятся в одном месте. И загрузчик не будет настраивать каждую инструкцию, вызывающую dll.
Все, что остается загрузчику – это поместить правильный адрес целевой функции в двойное слово в секции .idata и не нужно настраивать никаких инструкций CALL.
.idata содержит информацию о функциях и данных, которые модуль импортирует из других dll. Каждая функция, импортируемая PE файлом, перечислена в этой секции.
Вызовы функций из внешних dll не обращаются к этим dll непосредственно. Перед загрузкой в память информация, хранящаяся в секции .idata, содержит информацию, необходимую для того, чтобы загрузчик мог определить адреса целевых функций и пристыковать их к отображению исполняемого файла.
После загрузки, секция .idata содержит указатели функций, импортируемых exe-файлом или dll.
Секция .idata, называемая таблицей импорта, начинается с массива, состоящего из Image_Import_descriptor, каждый элемент которого соответствует одной из dll, с которой неявно связан данный PE файл.
Количество элементов в массиве нигде не учитывается, но последняя структура массива имеет поля, содержащие null.