Исполня́емый файл — набор инструкций, который заставляет компьютер выполнить определённую задачу[1]. В отличие от текстового файла, который рассчитан на чтение человеком, исполняемый файл рассчитан на чтение (и выполнение) процессором.
Под «инструкциями» традиционно понимается машинный код, который выполняется напрямую физическим процессором[2]. В некоторых случаях файл, содержащий инструкции сценария промежуточного языка программирования (например, байт-код), также может считаться исполняемым.
Исполняемые файлы могут быть созданы вручную на машинном языке, но этот подход обычно не используется из-за отсутствия как такового синтаксиса и удобочитаемости кода, поэтому гораздо удобнее разрабатывать исполняемые программы на языке программирования высокого уровня, который доступен для понимания. В некоторых случаях исходный код может быть на языке ассемблера, который остается удобочитаемым, но при этом предназначен для работы с инструкциями машинного кода.
Код на языке высокого уровня компилируется в объектные файлы машинного кода, которые не являются исполняемыми. После код может быть скомпонован в исполняемый файл. Этот процесс на языке ассемблера называется линковкой. Объектные файлы в зависимости от операционной системы обычно хранятся в формате контейнера (при котором различные данные содержатся в одном файле), таком как Executable and Linkable Format (ELF) для Unix-подобных систем или Portable Executable (PE) для Windows[3]. Это придает структуру машинному коду, разделяя его на секции, такие как .text (выполняемый код), .data (инициализированные глобальные и статические переменные) и .rodata (данные только для чтения, такие как постоянные и строки).
Исполняемые файлы обычно включают в себя среду выполнения, которая реализует функции языка программирования и компилятора среды выполнения (такие как планирование, обработка исключений, вызов статических конструкторов и деструкторов и т. д.) и взаимодействие с операционной системой, в частности, передача аргументов, окружения и кода возврата, вместе с другими функциями при запуске и завершении программы, которые не были указаны программистом, но представляющие ценность для последующей работы, такие как исполнение ресурсов. В Си это делается путем линковки компоновщиком объектного файла crt0 в исполняемый файл, который содержит точку исполнения, выполняет настройку и завершает работу с помощью вызова библиотеки среды выполнения[4].
Таким образом, исполняемые файлы обычно содержат дополнительный машинный код, который генерируется компилятором определённым образом из исходного кода. Это в некоторых случаях желательно пропустить, например, для разработки встроенных систем или просто для понимания того, как работают компиляция, линковка и загрузка. В Си пропустить стандартную среду выполнения можно с помощью указания сценария компоновщика напрямую, к примеру, вызвать main функцию для запуска программы и возвратить статус выхода ядру[5].
Исполнение
Для выполнения операционной системой, прошивкой или загрузчиком, исполняемый файл должен соответствовать бинарному интерфейсу приложений (ABI)[6]. В простых интерфейсах файл выполняется путем загрузки в память, перехода (прыжка) к началу адресного пространства и выполнения оттуда. В более сложных интерфейсах исполняемые файлы имеют дополнительные данные, определяющие отдельную точку входа. Например, в ELF точка входа указывается в заголовке e_entry, который указывает (виртуальный) адрес памяти, с которого следует начать выполнение. В GCC вход устанавливается компоновщиком с помощью символа _start.