Portable Executable

Материал из Википедии — свободной энциклопедии
Перейти к навигации Перейти к поиску
Переносимый исполняемый файл
Расширение .exe, .dll, .sys, .scr, .drv, .efi, .acm, .ax, .mui или .tsp
MIME-тип application/vnd.microsoft.portable-executable[1] и application/efi[2]
Тип формата двоичный, исполняемый, объектный, динамическая библиотека
Логотип Викисклада Медиафайлы на Викискладе

Portable Executable (PE, «переносимый исполняемый») — формат исполняемых файлов, объектного кода и динамических библиотек (DLL), используемый в 32- и 64-разрядных версиях операционной системы Microsoft Windows. Формат PE представляет собой структуру данных, содержащую всю информацию, необходимую PE-загрузчику для отображения файла в память. Исполняемый код включает в себя ссылки для связывания динамически загружаемых библиотек, таблицы экспорта и импорта API-функций, данные для управления ресурсами и данные локальной памяти потока (TLS). В операционных системах семейства Windows NT формат PE используется для EXE, DLL, SYS (драйверов устройств) и других типов исполняемых файлов.

PE представляет собой модифицированную версию COFF-формата файла для Unix. PE/COFF — альтернативный термин при разработке Windows.

На операционных системах семейства Windows NT формат PE в настоящее время поддерживает следующие архитектуры наборов команд: IA-32, IA-64, ARM64 и x86-64 (AMD64/Intel64). До Windows 2000 Windows NT (таким образом, и PE) поддерживал MIPS, Alpha и PowerPC. Поскольку PE используется на Windows CE, он продолжает поддерживать несколько разновидностей MIPS, ARM (включая Thumb) и SuperH.

Основные «конкуренты» PE — ELF (используемый в Linux и большинстве других версий Unix) и Mach-O (используемый в Mac OS X).

Краткая история

[править | править код]

С появлением операционной системы Windows NT 3.1 Microsoft перешла на формат PE. Все более поздние версии Windows, включая Windows 95/98/ME, поддерживают этот формат. Формат сохранил ограниченную поддержку существующего (MZ) для преодоления разрыва между системами, основанными на DOS, и системами NT. Например, заголовки PE/COFF всё ещё включают исполняемую программу MS-DOS, которая по умолчанию является заглушкой, выводящей на экран простое сообщение "This program cannot be run in DOS mode" — «Эта программа не может быть выполнена в режиме DOS» (или подобное). PE продолжает служить изменяющейся платформе Windows. Некоторые расширения включают формат PE.NET (см. ниже), 64-разрядную версию под названием PE32 (иногда PE ) и спецификацию для Windows CE.

Технические детали

[править | править код]
Структура 32-разрядного PE-файла

Первые 2 байта PE-файла содержат сигнатуру 0x4D 0x5A — «MZ» (как наследник MZ-формата). Далее — двойное слово по смещению 0x3C содержит адрес PE-заголовка. Последний начинается с сигнатуры 0x50 0x45 — «PE».

Файл PE состоит из нескольких заголовков и секций, которые указывают динамическому компоновщику, как отображать файл в память. Исполняемый образ состоит из нескольких различных областей (секций), каждая из которых требует различных прав доступа к памяти; таким образом, начало каждой секции должно быть выровнено по границе страницы. Например, обычно секция .text, которая содержит код программы, отображена как исполняемая/доступная только для чтения, а секция .data, содержащая глобальные переменные, отображена как неисполняемая/доступная для чтения и записи. Однако, чтобы не тратить впустую пространство на жёстком диске, различные секции на нём на границу страницы не выровнены. Часть работы динамического компоновщика состоит в том, чтобы отобразить каждую секцию в память отдельно и присвоить корректные права доступа получившимся областям согласно указаниям, содержащимся в заголовках.

Таблица импорта

[править | править код]

Одна из известных секций — таблица адресов импорта (IAT — Import Address Table), которая используется в качестве таблицы поиска, когда приложение вызывает функцию из другого модуля. Это может быть сделано и в форме импорта по порядковому номеру функции (ordinal), и импорта по её имени. Поскольку скомпилированной программе неизвестно расположение библиотек, от которых она зависит, то требуется производить косвенный переход всякий раз, когда происходит вызов API-функции. Когда динамический компоновщик загружает модули и объединяет их, он записывает действительные адреса в область IAT так, чтобы они указали на ячейки памяти соответствующих библиотечных функций. Хотя это добавляет дополнительный переход внутри модуля, приводящий к потере производительности, это предоставляет ключевое преимущество: количество страниц памяти, которые должны быть скопированы загрузчиком при записи, минимизировано, что приводит к экономии памяти и дискового времени ввода-вывода. Если компилятору будет известно заранее, что вызов будет межмодульным (через атрибут dllimport), то он сможет произвести более оптимизированный код, который просто приводит к коду операции косвенного вызова.

Таблица экспорта

[править | править код]

Таблица адресов экспорта (EAT — Export Address Table) нужна для того, чтобы один модуль (обычно это динамически загружаемая библиотека) мог указать другим модулям, какие функции они могут из него импортировать, и по каким адресам последние расположены.

Таблица перемещений

[править | править код]

Файлы PE не содержат позиционно-независимого кода. Вместо этого они скомпилированы для предпочтительного базового адреса, и все адреса, генерируемые компилятором/компоновщиком, заранее фиксированы. Если PE-файл не может быть загружен по своему предпочтительному адресу (потому что он уже занят чем-то ещё), операционная система будет перебазировать его. Это включает в себя перевычисление каждого абсолютного адреса и изменение кода для того, чтобы использовать новые значения. Загрузчик делает это, сравнивая предпочтительный и фактический адреса загрузки, и вычисляя значение разности. Тогда для получения нового адреса ячейки памяти эта разность складывается с предпочтительным адресом. Базовые адреса перемещений хранятся в списке и при необходимости добавляются к существующей ячейке памяти. Полученный код является теперь отдельным по отношению к процессу и не является больше разделяемым, так что при таком способе теряются многие из преимуществ экономии памяти динамически загружаемых библиотек. Такой способ также значительно замедляет загрузку модуля. По этой причине следует избегать перебазирования везде, где это возможно; например, библиотеки, поставляемые Microsoft, имеют предварительно вычисленные неперекрывающиеся базовые адреса. В случае отсутствия необходимости перебазировании PE-файлы имеют преимущество очень эффективного кода, но при наличии перебазирования издержки в использовании памяти могут быть значительными. Это отличает формат PE от ELF, который использует полностью позиционно-независимый код и глобальную таблицу смещений, которая жертвует временем выполнения в пользу расходования памяти.

.NET, метаданные и PE-формат

[править | править код]

Платформа .NET корпорации Microsoft расширила формат PE с помощью функций, которые поддерживают общеязыковую среду исполнения (Common Language Runtime — CLR). Среди дополнений — заголовок CLR и секция данных CLR. После загрузки двоичного файла загрузчик ОС приводит к выполнению CLR через ссылку в таблице импорта PE/COFF. Затем CLR загружает заголовок CLR и секции данных.

Секция данных CLR содержит два важных сегмента: сегмент метаданных и сегмент кода промежуточного языка (IL):

  • Метаданные содержат информацию, относящуюся к сборке, включая манифест сборки. Манифест подробно описывает сборку, включая уникальный идентификатор (с помощью хеша, номера версии и т. д.), данные об экспортируемых компонентах, расширенную информацию о типе (поддерживаемую общей системой типов (Common Type System — CTS)), внешние ссылки и список файлов в сборке. Среда CLR широко использует метаданные.
  • Код промежуточного языка (Intermediate Language — IL) — абстрактный, независимый от языка код, который удовлетворяет требованиям общего промежуточного языка (Common Intermediate Language — CIL) .NET CLR. Термин «промежуточный» относится к природе кода IL, обладающего межъязыковой и кроссплатформенной совместимостью. Этот промежуточный язык, подобный байт-коду Java, позволяет платформам и языкам поддерживать общую среду .NET CLR. IL поддерживает объектно-ориентированное программирование (полиморфизм, наследование, абстрактные типы и т. д.), исключения, события и различные структуры данных.

Использование в других операционных системах

[править | править код]

Формат PE также используется ReactOS, поскольку ReactOS предназначена для того, чтобы быть двоично совместимой с Windows на уровне кода. Кроме того, он исторически использовался многими другими операционными системами, включая SkyOS и BeOS R3. Однако и SkyOS, и BeOS в конечном счёте перешли на формат ELF.

Поскольку платформа разработки Mono намеревается быть двоично совместимой с Microsoft .NET, она использует тот же формат PE, что и в реализации Microsoft.

На платформе x86 в Unix-подобных операционных системах некоторые двоичные файлы Windows (в формате PE) могут быть исполнены с помощью Wine. HX DOS Extender также использует формат PE для собственных 32-разрядных двоичных файлов DOS, кроме того, может в некоторой степени выполнить существующие двоичные файлы Windows в DOS, действуя, таким образом, как Wine для DOS.

Mac OS X 10.5 имеет возможность загружать и интерпретировать PE-файлы, однако они не являются двоично совместимыми с Windows.

Примечания

[править | править код]