Форматы исполняемых файлов

Введение

править

Исполни́мый (исполня́емый) мо́дуль, исполнимый файл (англ. executable file) — файл, содержащий программу в виде, в котором она может быть (после загрузки в память и настройки по месту) исполнена компьютером.

Чаще всего он содержит двоичное представление машинных инструкций для определённого процессора (по этой причине на программистском сленге в отношении него используют слово бинарник — кальку с английского binary), но может содержать и инструкции на интерпретируемом языке программирования, для исполнения которых требуется интерпретатор. В отношении последних часто используется термин «скрипт».

Исполнением бинарных файлов занимаются аппаратно- и программно-реализованные машины. К первым относятся процессоры, например, семейств x86 или SPARC. Ко вторым — виртуальные машины, например, виртуальная машина Java или .NET Framework. Формат бинарного файла определяется архитектурой исполняющей его машины. Известны машины, реализованные как аппаратно, так и программно, например, процессоры семейства x86 и виртуальная машина VMware.

Статус исполнимости файла чаще всего определяется принятыми соглашениями. Так, в одних операционных системах исполнимые файлы распознаются благодаря соглашению об именовании файлов (например, путём указания в имени расширения файла — .exe или .bin), тогда как в других исполнимые файлы обладают специфичными метаданными (например, битом разрешения execute в UNIX-подобных операционных системах).

В современных компьютерных архитектурах исполнимые файлы содержат большие объемы данных, не являющихся компьютерной программой: описание программного окружения, в котором программа может быть выполнена, данные для отладки программы, используемые константы, данные, которые могут потребоваться операционной системе для запуска процесса (например, рекомендуемый размер кучи), и даже описания структур окон графической подсистемы, используемых программой.

Зачастую исполнимые файлы содержат вызовы библиотечных функций, например, вызовы функций операционной системы. Таким образом, наряду с процессорозависимостью (машинозависимым является любой бинарный исполнимый файл, содержащий машинный код) исполнимым файлам может быть свойственна зависимость от версии операционной системы и её компонент.

В настоящее время существует большое количество файловых форматов исполняемых файлов для различных операционных систем. При этом документация, хотя и существует, зачастую разрознена или практически недоступна (например, описание формата LX/LE становится всё сложнее и сложнее найти). Плюс к этому различные производители программного обеспечения вводят дополнительные расширения, которые, зачастую, становится трудно сопоставить с оригинальным форматом или они взаимоисключающие.

Данная работа является попыткой составить единый справочник по работе с исполняемыми файлами, их анализа, разбора и создания исполняемого образа, готового для работы в памяти. Скриптовый формат исполняемых файлов оставим на откуп соответствующих руководств по языкам программирования, а рассмотрим именно двоичные форматы.

Исторический экскурс

править

Одним из простейших форматов исполняемых файлов на архитектуре x86 является файл с расширением .COM (здесь и далее при использовании расширений файлов операционных система семейства CP/M будет использоваться запись расширения файла в верхнем регистре, а для операционных систем семейства Unix будет использоваться нижний регистр для суффикса имени файла). Истоки данного формата файла идут из операционной системы CP/M для процессора Intel 8080, известной своей широкой популярностью. Формат файла предполагает использование для создания команд (COM - сокращение от COMMAND), расширяющий базовую функциональность операционной системы.

При разработке MS-DOS формат .COM сохранился практически без изменения и обеспечивает совместимость на уровне исходного кода с CP/M. Двоичный формат специфичен для каждого типа процессора. Формат подразумевает исполнение в пределах одного сегмента (64Кб), что не позволяло использовать возможности оперативной памяти в полной мере.

Формат .EXE появился в QDOS/86-DOS (позднее продаваемой под брендом MS-DOS) в 1981 году и был разработан Марком Збиковски (Mark Zbikowski), о чем свидетельствует сигнатура MZ (или ZM в некоторых старых версиях редакторов обратных связей) в первых двух байтах файла. В отличие от формата .COM, где для кода, данных и стека используется один и тот же сегмент, в формате .EXE были сняты данные ограничения, что делало доступным все адресное пространство компьютера.

Развитие машин класса IBM PC привело к появлению новых режимов адресации, появлению виртуальной памяти и прочих механизмов, что делало формат .EXE MZ недостаточно гибким и не приспособленным к реалиям. В частности, многие производители реализовывали так называемые оверлеи, что позволяло осуществлять простейшую подкачку нужных функций в ОЗУ. Плюс, неэффективное использование повторяющегося исполняемого кода привело к реализации библиотек динамического связывания, код которых мог использоваться различными процессами без его дублирования в физической памяти. В итоге формат .EXE был расширен и были реализованы его более современные версии LX/LE и NE.

Формат .EXE NE был разработан Microsoft для применения в операционной системе OS/2 и оболочки Windows. Данный формат ориентирован, как и .EXE MZ, на 16-разрядные среды исполнения. Основная дополнительная функциональность, обеспечиваемая данным форматом - это поддержка .DLL.

С приходом на рынок микропроцессоров 386+ возникла необходимость поддержки 32-разрядного кода, что, естественно, привело к появлению такого формата, как LE (использовался в оболочках семейства Windows 3.x и операционных систем семейства Windows 9x) и LX (использовался в операционной системе OS/2). Данные форматы позволили смешивать как 16-битный код, так и 32-битный. Кроме смешанного кода переходной период также потребовал реализации такого механизма, как thunking, что также наложило отпечаток на данные форматы файлов.

При разработке OS/2 NT 3.0 (в последующем - Windows NT 3.51) был разработан формат .EXE PE, который был предназначен для хранения кода ориентированного на различные аппаратные платформы. В настоящее время данный формат является основным для семейства операционных систем Windows.

В кросс платформенной OS/2 (OS/2 PPC, Workstation OS) конечным форматом файлы был принят формат ELF, о котором будет сказано несколько дальше.

В мире операционных систем семейства Unix также наблюдалось развитие форматов исполняемых файлов. Первый формат, a.out, появился с первой версией UNIX. На наименование повлияла специфика процесса получения двоичного файла. В отличие от систем семейства CP/M, для которых всегда была характерна нехватка памяти, Unix системы позволяли осуществлять полный цикл: компиляция, компиляция, компоновка. a.out - это сокращение от assembler output. Формат a.out по структуре похож на форматы .EXE. За свое существование претерпел ряд модификаций.

С появлением разделяемых библиотек формат a.out из-за ряда ограничений был заменен на формат COFF. Из основных нововведений - это добавление отладочной информации и относительного виртуального адреса, что позволило загружать его по произвольному фактическому адресу. Использование формата COFF в Unix системах в настоящее время ограничено, однако формат .EXE PE является наиболее известным вариантом формата COFF.

Формат COFF был принят не всеми производителями семейства Unix и многие все еще продолжали использовать a.out. Ситуация изменилась с появлением формата ELF. Достаточно удачное решение и учет того, что формат не был ориентирован под специфические особенности определенной архитектуры, он получил широкое распространение. Большинство современных реализаций Unix и ряд других операционных систем используют именно его. Существует также "универсальный" формат ELF, содержащий двоичные образы для большинства различных платформ. Сложно сказать, будет ли формат FatELF широко использован, но если оглянуться на историю подобный "многосистемных" форматов, то, скорее всего, FatELF не получит широкого распространения.

Наличие большого числа форматов, накопившихся со временем, привело к тому, что в конце девяностых годов появилось ряд исследовательских проектов по разработке некоторого "абстрактного" формата файлов а также библиотек, предоставляющих единый интерфейс по доступу к данным любого формата. Причины появление таких инструментов было несколько. Одна из причин - инструментарий для сред разработки. Например, поддержка компоновщиком наибольшего числа форматов. Другая причина - необходимость переноса и запуска двоичных программ на другой аппаратной платформе.

Структура форматов файлов операционных систем семейства CP/M

править

Структура форматов файлов операционных систем семейства Unix

править

Библиотеки чтения форматов файлов

править

Исследования в области языков описания форматов файлов

править