Скачанная программа для Windows не запустится на Linux «двойным щелчком», а приложение, собранное для Linux, macOS откажется открывать вовсе. Причина не в капризах разработчиков: каждая операционная система исторически и технически опирается на свой формат исполняемого файла — жёстко описанную структуру, по которой ядро системы понимает, как загрузить программу в память и передать ей управление. В этой статье объясняем, что именно описывает такой формат, чем различаются EXE, ELF и Mach-O, почему унификация не состоялась и в каких случаях программа всё-таки может работать на нескольких системах.
- Что происходит, когда вы запускаете программу
- Что описывает формат исполняемого файла
- Основные форматы и где они используются
- PE: формат Windows
- ELF: формат Unix-мира
- Mach-O: формат Apple
- Почему форматы не унифицировали
- Формат файла — не единственная причина несовместимости
- Как программы всё-таки работают на нескольких ОС
- Компиляция под каждую платформу
- Виртуальные машины и эмуляция
- Слои совместимости
- Переносимые среды выполнения
- Статическая сборка
- Что это значит на практике
- Частые вопросы
- Можно ли изменить расширение файла, чтобы он запустился в другой системе?
- Почему Android-приложения устанавливаются как APK, а программы Linux — без расширения?
- Правда ли, что Linux-программы можно запускать на Windows?
- Появится ли когда-нибудь единый формат для всех ОС?
- Главное
Что происходит, когда вы запускаете программу
Исполняемый файл — это не просто «набор команд процессора». Это контейнер со строгой внутренней разметкой, который операционная система читает перед запуском. Когда вы запускаете приложение, происходит примерно следующая последовательность:
- Система распознаёт тип файла — по расширению, метаданным или по сигнатуре в первых байтах.
- Загрузчик (часть ядра или системная утилита) проверяет, что формат ему знаком и структура файла корректна.
- Загрузчик выделяет программе виртуальную память и размещает в ней секции файла: машинный код, данные, таблицы.
- Подключаются библиотеки, от которых зависит программа, — динамически, если они не встроены в файл.
- Управление передаётся на точку входа — конкретный адрес, с которого начинается выполнение кода.
Если на втором шаге загрузчик не узнаёт структуру файла, запуск прекращается. Именно поэтому Windows сообщает, что «приложение не может быть запущено», а Linux в терминале отвечает «cannot execute binary file». Файл физически присутствует на диске, но система не понимает, как с ним работать.
Что описывает формат исполняемого файла
Формат — это, по сути, договор между компилятором, который собирает программу, и загрузчиком, который её запускает. В этом договоре зафиксировано несколько принципиальных вещей:
- Архитектура процессора. Формат указывает, под какой набор инструкций собран код: x86-64, ARM, RISC-V и так далее. Код для одной архитектуры другой процессор просто не поймёт.
- Разметка памяти. Какие секции есть в файле (код, данные, ресурсы), где они должны располагаться в адресном пространстве процесса и с какими правами — исполнение, чтение, запись.
- Точка входа. Адрес, с которого начинается выполнение.
- Зависимости. Список динамических библиотек, которые нужно подключить до старта, и правила поиска этих библиотек.
- Таблицы символов и релокаций. Информация, позволяющая связать обращения программы к функциям библиотек с их реальными адресами в памяти.
- Метаданные. Версия формата, требования к версии системы, информация для отладчика, подписи и сертификаты — в тех форматах, где это предусмотрено.
Поскольку каждая ОС разрабатывала свой загрузчик под свои задачи, «договоры» получились разными — и по структуре, и по возможностям.
Основные форматы и где они используются
Три формата покрывают подавляющее большинство настольных и серверных систем.
| Формат | Основные системы | Типичное расширение | Ключевая особенность |
|---|---|---|---|
| PE (Portable Executable) | Windows, ReactOS | .exe, .dll, .sys | Развитая система метаданных, поддержка подписи кода и версионирования |
| ELF (Executable and Linkable Format) | Linux, большинство BSD, Android (для приложений на нативном коде) | обычно без расширения, .so для библиотек | Гибкость: один формат для исполняемых файлов, библиотек и объектов |
| Mach-O | macOS, iOS | без расширения, .dylib для библиотек | Проектировался под ядро Mach, поддерживает «универсальные» файлы с кодом для нескольких архитектур |
За пределами этой тройки остаются форматы с долгой историей: a.out — ранний формат Unix-систем, COFF — предок PE, использовавшийся в старых версиях Windows и некоторых Unix, а также проприетарные форматы встраиваемых систем и мейнфреймов. Их объединяет то же свойство: они понятны конкретному загрузчику конкретной системы.
PE: формат Windows
Portable Executable появился в Windows NT как развитие формата COFF. Название «переносимый» относится не к переносимости между операционными системами, а к переносимости между архитектурами процессоров: формат позволяет описывать код и для x86, и для ARM. PE подробно описывает секции файла, импортируемые и экспортируемые функции, ресурсы (иконки, строки, диалоги), информацию о версии и цифровую подпись. Последнее важно для экосистемы Windows: подпись кода — часть механизмов безопасности, и её отсутствие или повреждение влияет на то, как система и антивирусы относятся к файлу.
ELF: формат Unix-мира
ELF пришёл на смену a.out в начале 1990-х и стал стандартом де-факто для свободных Unix-подобных систем. Его сильная сторона — универсальность: один и тот же формат описывает и готовые программы, и промежуточные объектные файлы, и разделяемые библиотеки. ELF хорошо приспособлен к динамической компоновке: программа может объявлять зависимости от библиотек, а загрузчик разрешает их при запуске. Именно поэтому в Linux исполняемые файлы обычно не имеют расширения — система ориентируется на внутреннюю структуру, а не на имя.
Mach-O: формат Apple
Mach-O вырос из ядра Mach, на котором построены операционные системы Apple. Формат исторически тесно связан с инструментами разработки NeXTSTEP, наследницей которой стала macOS. Отличительная черта — поддержка универсальных бинарных файлов (fat binaries): один файл может содержать код сразу для нескольких архитектур, например для Intel и Apple Silicon. Это позволило относительно безболезненно пережить переходы между платформами: система сама выбирает подходящую часть файла.
Почему форматы не унифицировали
Технически создать общий формат возможно — и такие попытки были. Но у унификации есть фундаментальные препятствия.
Формат привязан к загрузчику, а загрузчик — к архитектуре системы. Формат исполняемого файла не существует сам по себе: он имеет смысл только вместе с кодом, который умеет его читать. Переделать загрузчик ядра — значит затронуть самый чувствительный слой операционной системы, где цена ошибки максимальна.
Разные модели безопасности. Windows встраивает в PE подписи и механизмы контроля целостности, macOS связывает запуск с проверкой нотаризации и карантинными атрибутами, Linux традиционно оставляет больше свободы на уровне дистрибутива. Общий формат потребовал бы либо общего механизма безопасности, либо его отсутствия — оба варианта кого-то не устраивают.
Разные модели библиотек и ABI. Даже при одинаковом формате файла программа зависит от системного интерфейса: как она обращается к ядру, какие библиотеки ожидает найти, как устроены системные вызовы. Этот слой называется ABI (Application Binary Interface), и он у систем различается не меньше, чем форматы файлов. Совместимый формат без совместимого ABI ничего бы не дал.
История и обратная совместимость. Миллионы работающих программ опираются на существующие форматы. Любая смена формата — это годы поддержки двух режимов параллельно. Windows, например, до сих пор способна запускать программы, написанные десятилетия назад, именно потому, что не ломает старые механизмы без крайней необходимости.
Формат файла — не единственная причина несовместимости
Распространённое заблуждение: «если бы форматы совпадали, программы запускались бы везде». На практике формат — лишь первый барьер. Полный список препятствий выглядит так:
- Архитектура процессора. Код для ARM не выполнится на x86-64 без эмуляции или трансляции инструкций.
- Системные вызовы. Программа общается с ядром через строго определённый интерфейс, и он у Windows, Linux и macOS разный.
- Системные библиотеки. Программа для Linux ожидает найти libc и другие библиотеки в определённых местах; на macOS их нет или они устроены иначе.
- Разметка экрана, файловой системы, реестра. Пути вроде «C:\Program Files» или «/usr/lib» не существуют в чужой системе.
- Графические и оконные подсистемы. Приложения с интерфейсом зависят от оконного API, который у каждой ОС свой.
Поэтому даже «правильный» файл без поддержки окружения бесполезен. Совместимость достигается не совпадением форматов, а эмуляцией целого слоя системы.
Как программы всё-таки работают на нескольких ОС
Кроссплатформенность существует, но достигается обходными путями, каждый из которых имеет цену.
Компиляция под каждую платформу
Самый надёжный способ: исходный код один, а компилятор собирает отдельную версию для каждой системы. Так выпускаются большинство кроссплатформенных программ — браузеры, редакторы, игры. Цена — необходимость поддерживать сборку и тестирование для каждой платформы, а также учитывать различия в поведении систем.
Виртуальные машины и эмуляция
Виртуальная машина запускает внутри себя полноценную гостевую операционную систему: программа для Windows работает внутри Windows, установленной на виртуальной машине поверх macOS или Linux. Эмуляция процессора (например, трансляция инструкций одной архитектуры в другую) позволяет запускать программы даже для чужой архитектуры, но с заметной потерей производительности — объём работы зависит от того, насколько различаются системы команд.
Слои совместимости
Слой совместимости — это программа, которая «притворяется» другой ОС: принимает системные вызовы чужой программы и переводит их в вызовы родной системы. Классический пример — Wine, позволяющий запускать Windows-приложения на Linux и macOS без самой Windows. Такой подход легче виртуальной машины, но требует огромной работы по воспроизведению поведения чужой системы, и часть программ всё равно работает с ошибками или не работает вовсе.
Переносимые среды выполнения
Программа пишется не под операционную систему, а под промежуточную среду: виртуальную машину Java, среду .NET, интерпретатор Python или браузер как платформу для веб-приложений. Среда выполнения устанавливается на любую ОС и обеспечивает программе единое окружение. Ограничение — доступ только к тем возможностям, которые среда предоставляет, и зависимость от её производительности.
Статическая сборка
Можно собрать программу так, чтобы все библиотеки были встроены в сам файл и не требовались от системы. Это снимает проблему зависимостей, но не проблему формата, системных вызовов и архитектуры: статически собранный Linux-бинарник всё равно не запустится на Windows. Зато он часто работает на разных дистрибутивах Linux, где версии библиотек различаются, — поэтому такой подход популярен для утилит командной строки.
Что это значит на практике
Понимание форматов помогает в повседневных ситуациях:
- Выбор загрузок. Если на сайте разработчика есть несколько версий файла, выбирайте ту, что соответствует вашей системе и архитектуре процессора. Файл «для Windows» на Mac не заработает, даже если переименовать расширение.
- Диагностика ошибок запуска. Сообщение «cannot execute binary file» в Linux чаще всего означает, что файл собран для другой архитектуры или это вообще исполняемый файл чужой системы, а не повреждение самого файла.
- Оценка предложений. Если разработчик обещает «один файл для всех систем» без упоминания среды выполнения или эмуляции, это повод уточнить, о чём именно идёт речь.
- Понимание размеров файлов. Универсальные бинарные файлы macOS и программы, собранные статически, весят больше, потому что содержат несколько вариантов кода или встроенные библиотеки, — это нормально.
Частые вопросы
Можно ли изменить расширение файла, чтобы он запустился в другой системе?
Нет. Расширение — лишь подсказка для пользователя и проводника. Загрузчик операционной системы читает внутреннюю структуру файла, и чужой формат он не распознает независимо от имени. Переименование в лучшем случае приведёт к другой ошибке, в худшем — к попытке выполнить бессмысленные данные.
Почему Android-приложения устанавливаются как APK, а программы Linux — без расширения?
Это разные уровни упаковки. APK — установочный пакет, внутри которого лежат ресурсы и, среди прочего, нативные библиотеки в формате ELF. Сам исполняемый код при этом по-прежнему описывается ELF. Формат пакета отвечает за установку и обновление, а формат исполняемого файла — за запуск.
Правда ли, что Linux-программы можно запускать на Windows?
Напрямую — нет, но есть несколько путей: подсистема Windows для Linux (WSL) запускает настоящую Linux-среду внутри Windows, виртуальная машина даёт полную Linux-систему, а отдельные утилиты пересобираются под Windows изначально. Выбор зависит от задачи: для работы в терминале обычно достаточно WSL, для графических приложений чаще нужна виртуальная машина.
Появится ли когда-нибудь единый формат для всех ОС?
Полная унификация маловероятна: форматы слишком глубоко связаны с загрузчиками, моделями безопасности и системными интерфейсами. Практическая альтернатива уже сложилась — переносимые среды выполнения и веб-платформа, где «совместимость» обеспечивается не общим бинарным форматом, а общим слоем поверх операционных систем.
Главное
Разные операционные системы используют разные форматы программ потому, что формат — это не ярлык, а часть механизма запуска: он описывает, как загрузчик размещает код в памяти, подключает библиотеки и передаёт управление. Windows опирается на PE, Linux и большинство Unix-систем — на ELF, Apple — на Mach-O, и каждый формат вырос из архитектуры своей системы, её модели безопасности и истории развития.
Если перед вами стоит задача запустить программу на «чужой» системе, действуйте в таком порядке:
- Убедитесь, что версия программы соответствует вашей ОС и архитектуре процессора.
- Если версии для вашей системы нет, проверьте, существует ли сборка под переносимую среду (Java, .NET, Python, браузер).
- Для Windows-приложений на Linux или macOS рассмотрите слой совместимости или виртуальную машину, понимая, что результат не гарантирован для каждой программы.
- Для серверных задач оцените контейнеризацию: она решает проблему зависимостей внутри одной системы, но не совместимость между разными ОС.
Формат файла — первый фильтр совместимости, но не единственный. Программа работает там, где для неё воссоздано всё окружение: архитектура, системные вызовы и библиотеки. Поэтому универсальность достигается не общим форматом, а общим слоем поверх систем — и выбор этого слоя определяет и удобство, и производительность, и надёжность результата.
