Mach-O (Mach Object) — это формат исполняемых файлов, библиотек и объектного кода, который используется в macOS, iOS и других операционных системах Apple. Любое приложение, которое вы запускаете на Mac, любая системная библиотека и даже ядро системы существуют именно в этом формате. Если сравнивать с миром Windows и Linux, то Mach-O занимает то же место, что PE (EXE-файлы) у Microsoft и ELF у Linux: это «контейнер», внутри которого лежит скомпилированный машинный код и всё необходимое для его загрузки в память.
Понимание устройства Mach-O нужно не только разработчикам компиляторов. Оно помогает разбираться, почему приложение не запускается с сообщением о повреждённом файле, что такое «универсальный бинарник», зачем нужна подпись кода, как проверить, под какую архитектуру собрана программа, и что происходит между двойным щелчком по иконке и работающим процессом.
- Зачем вообще нужен отдельный формат файлов
- Структура файла Mach-O
- Заголовок (header)
- Команды загрузки (load commands)
- Сегменты и секции
- Универсальные бинарники: несколько программ в одном файле
- Как проходит запуск Mach-O приложения
- Подпись кода и защита целостности
- Mach-O рядом с приложением .app
- Чем Mach-O отличается от ELF и PE
- Практические сценарии: когда знание формата пригождается
- Проверка, под какую архитектуру собрана программа
- Диагностика проблем с зависимостями
- Проверка подозрительных файлов
- Разработка и сборка
- Типичные заблуждения
- Что делать дальше
Зачем вообще нужен отдельный формат файлов
Когда компилятор превращает исходный код в машинные инструкции, результат — это просто последовательность байтов, понятных процессору. Но чтобы операционная система могла этот код запустить, ей нужно знать гораздо больше:
- какие части файла являются кодом, а какие — данными;
- по какому адресу в памяти загружать каждую часть;
- какие внешние функции и библиотеки требуются программе;
- где находится точка входа, с которой начинается выполнение;
- какие разрешения нужны этому коду при работе.
Всю эту метаинформацию и описывает формат исполняемого файла. Mach-O решает те же задачи, что ELF или PE, но исторически вырос из ядра Mach, разработанного в Университете Карнеги — Меллона в 1980-х годах. Это ядро легло в основу NeXTSTEP, а затем macOS, поэтому формат «перекочевал» вместе с системой и используется в ней до сих пор.
Структура файла Mach-O
Каждый файл Mach-O состоит из трёх логических частей: заголовка, команд загрузки и самих данных.
Заголовок (header)
Первые байты файла — это заголовок, в котором система узнаёт базовые сведения о бинарнике:
- магическое число — специальная сигнатура, по которой система распознаёт формат; разные значения соответствуют 32-битной, 64-битной версии и порядку байтов;
- тип файла — исполняемый файл, динамическая библиотека, объектный модуль после компиляции, набор статических библиотек или файл дампа памяти;
- архитектура — например, ARM64 для процессоров Apple Silicon или x86_64 для Intel;
- количество команд загрузки, которые следуют далее.
Именно по магическому числу macOS мгновенно понимает, что перед ней Mach-O, ещё до какого-либо анализа содержимого.
Команды загрузки (load commands)
Сразу после заголовка идёт таблица команд загрузки — своего рода «инструкция по сборке» процесса. Каждая команда говорит загрузчику, что сделать с той или иной частью файла. Наиболее важные типы команд:
- сегменты — указывают, какие области файла и в какие адреса виртуальной памяти отображать;
- подключение динамических библиотек — список зависимостей вида «загрузить такую-то библиотеку»;
- точка входа — адрес, с которого начинается выполнение программы;
- подпись кода — ссылка на блок криптографической подписи в конце файла;
- минимальная версия ОС — ограничение снизу на версию системы;
- символы и таблицы экспорта — информация о функциях, которые библиотека предоставляет наружу.
Если упростить: заголовок отвечает на вопрос «что это», а команды загрузки — «что с этим делать при запуске».
Сегменты и секции
Основное содержимое файла организовано в сегменты, а внутри сегментов — в секции. Сегмент — это область, которая целиком отображается в память с определёнными правами доступа; секция — более мелкая единица внутри него. Типичное устройство выглядит так:
| Сегмент | Что содержит | Права доступа |
|---|---|---|
| __TEXT | Машинный код, константы, заголовочные структуры | Чтение и исполнение |
| __DATA / __DATA_CONST | Глобальные переменные, таблицы указателей | Чтение и запись (или только чтение) |
| __LINKEDIT | Метаданные: символы, relocation-записи, подпись | Чтение |
Такое разделение не случайно. Код помещается в память с запретом записи — это усложняет эксплуатацию многих уязвимостей: злоумышленник не может одновременно записать вредоносные инструкции и заставить процессор их выполнить. Данные, наоборот, доступны для изменения во время работы, но не исполняются как код. Современные версии macOS дополнительно защищают неизменяемые части __DATA_CONST, делая их доступными только для чтения после начальной настройки.
Универсальные бинарники: несколько программ в одном файле
Особенность экосистемы Apple — fat binary (универсальный бинарник). Это файл, который содержит несколько независимых образов Mach-O подряд: например, один для Intel x86_64 и один для ARM64. В начале такого файла стоит отдельный «внешний» заголовок со списком вложенных образов и смещениями, где каждый из них находится.
При запуске система читает список, выбирает образ, подходящий под текущий процессор, и работает уже с ним. Благодаря этому одно приложение можно распространять для Mac на Intel и на Apple Silicon без двух отдельных дистрибутивов. Когда переход на собственные процессоры только начинался, тот же механизм обеспечивал совместимость через трансляцию Rosetta: она получала x86_64-образ из универсального файла и выполняла его на ARM-процессоре.
Проверить состав любого бинарника на Mac можно прямо в терминале командой file (например, file /bin/ls) — она покажет перечень архитектур внутри. Команда lipo -info даёт ту же информацию, а lipo также умеет извлекать или удалять отдельные архитектуры из файла.
Как проходит запуск Mach-O приложения
Последовательность от запуска до работающего процесса примерно такая:
- Система читает заголовок и убеждается, что формат распознан, а архитектура подходит текущему процессору.
- Проверяется подпись кода: целостность файла и его происхождение. Нарушенная подпись — частая причина отказа в запуске.
- Загрузчик (динамический линковщик) читает команды загрузки и отображает сегменты файла в виртуальную память процесса.
- По списку зависимостей подгружаются динамические библиотеки; если какой-то библиотеки нет или её версия несовместима, запуск прерывается с ошибкой.
- Выполняется привязка символов: вызовы функций из библиотек связываются с реальными адресами.
- Управление передаётся точке входа, и программа начинает работу.
Отсюда становятся понятны многие практические ошибки. Сообщение о том, что файл «повреждён и не может быть открыт», часто означает не физическую порчу, а проблему с подписью или карантинным атрибутом. Ошибка вида «библиотека не найдена» — это сбой на четвёртом шаге. А «Bad CPU type in executable» означает, что подходящей архитектуры в файле нет.
Подпись кода и защита целостности
В macOS подпись — не необязательное украшение, а часть формата. Блок подписи хранится в сегменте __LINKEDIT и охватывает все страницы файла: каждая страница кода получает свой хеш, а хеши защищены общей подписью разработчика. При загрузке система может проверять страницы по мере обращения к ним.
Это даёт несколько эффектов. Во-первых, система знает, кто выпустил программу и не менялся ли файл после подписания. Во-вторых, становится возможным жёсткое разграничение прав доступа: приложение получает доступ к камере, микрофону, ключам и другим защищённым ресурсам только если подпись совпадает с той, под которой эти права были выданы. Изменённый файл теряет права и, как правило, возможность запуска.
Для проверки подписи из терминала используются команды codesign -dv (сведения о подписи) и codesign —verify —deep (проверка целостности). Для просмотра внутренних структур удобна утилита otool: например, otool -L показывает список подключённых библиотек, а otool -l — все команды загрузки. Более детальный разбор дают otool -ov и сторонние графические анализаторы.
Mach-O рядом с приложением .app
Приложение macOS визуально выглядит как единый файл, но на самом деле это папка-пакет (bundle). Исполняемый Mach-O лежит внутри, обычно в подпапке Contents/MacOS, рядом с ресурсами, настройками и метаданными. Посмотреть содержимое пакета можно через контекстное меню «Показать содержимое пакета».
Динамические библиотеки, которые поставляются вместе с приложением, тоже представляют собой Mach-O, но другого типа — они помечены как dylib, а не как исполняемый файл. Фреймворки — это, по сути, пакеты, внутри которых такая библиотека и сопутствующие ресурсы объединены вместе.
Чем Mach-O отличается от ELF и PE
Все три формата решают одну задачу, но различаются деталями, которые заметны на практике:
- Организация. В ELF и PE разделы (sections) первичны, а сегменты собираются из них при загрузке. В Mach-O наоборот: сегмент — основная единица, а секции живут внутри него.
- Подпись кода. В Mach-O она встроена в сам формат и активно используется системой для безопасности и управления правами. В Windows и Linux механизмы проверки целостности реализованы иначе и менее тесно связаны с форматом файла.
- Универсальные бинарники. Fat-обёртка с несколькими архитектурами в одном файле — характерная черта платформы Apple; в других экосистемах мультиархитектурность обычно решается раздельной поставкой пакетов.
- Связь с системой. Mach-O рассчитан на конкретную модель загрузки и защиты памяти macOS/iOS, поэтому некоторые оптимизации (например, работа с разделяемым кэшем системных библиотек) встроены глубже, чем в более универсальные форматы.
Для обычного пользователя эти различия малозаметны, но они объясняют, почему нельзя просто «переупаковать» Linux-программу в Mach-O: требуется перекомпиляция под другой формат, набор системных библиотек и ABI.
Практические сценарии: когда знание формата пригождается
Проверка, под какую архитектуру собрана программа
Перед покупкой или установкой инструмента, распространяемого вручную, полезно убедиться, что в бинарнике есть поддержка вашего процессора. Команда file путь/к/файлу покажет это за секунду. Отсутствие нужной архитектуры означает либо ошибку «Bad CPU type», либо необходимость Rosetta (если речь о запуске x86_64-кода на Apple Silicon).
Диагностика проблем с зависимостями
Если программа падает при старте с жалобой на отсутствующую библиотеку, otool -L покажет полный список ожидаемых зависимостей и пути, по которым они ищутся. Часто проблема в том, что библиотека установлена в нестандартное место, либо её версия несовместима с тем, чего требует бинарник.
Проверка подозрительных файлов
Файл, который «должен быть приложением», но не открывается, легко проверить: file покажет его настоящий тип, а codesign — есть ли подпись и кем она выдана. Отсутствие подписи у программы из сомнительного источника — веский повод насторожиться.
Разработка и сборка
Разработчикам формат важен при кросс-компиляции, сборке универсальных бинарников (компилятор Apple умеет создавать fat-файлы сразу) и при работе с инструментами вроде статических анализаторов, которым нужно понимать структуру объекта.
Типичные заблуждения
- «Mach-O — это только EXE-аналог». Нет: в этом формате существуют и библиотеки, и объектные файлы, и даже дампы ядра.
- «Переименование файла изменит его тип». Тип определяется содержимым, прежде всего магическим числом, а не расширением.
- «Универсальный бинарник работает быстрее». Он лишь содержит несколько вариантов; скорость определяет тот образ, который выбран под ваш процессор.
- «Подпись нужна только для App Store». Подпись влияет на запуск, права доступа и доверие системы независимо от канала распространения.
Что делать дальше
Если вы хотите разобраться с Mach-O на практике, начните с безопасных наблюдений на собственной системе: выполните file для нескольких системных утилит, посмотрите otool -L у любимого приложения и codesign -dv для любого бинарника. Эти три команды дают наглядное представление об архитектурах, зависимостях и подписи без каких-либо изменений в системе.
Главный принцип, который стоит удержать в голове: Mach-O — это не просто «код в файле», а структурированный договор между программой и операционной системой. Заголовок говорит, что перед нами, команды загрузки — как это выполнить, сегменты — где код, где данные и какие у них права, а подпись подтверждает, что содержимое не менялось с момента выпуска. Понимание этой логики позволяет самостоятельно диагностировать большинство проблем запуска и осознанно оценивать любые исполняемые файлы на Mac.
