Введение

Формат файла для исполняемых файлов, объектного кода, общих библиотек и дампов памяти.

Mach O, сокращение от Mach object file format (формат файла объекта Mach), — это формат файла для исполняемых файлов, объектного кода, общих библиотек, динамически загружаемого кода и дампов памяти. Он был разработан для замены формата a.out. Mach O используется в некоторых системах на базе ядра Mach. NeXTSTEP, macOS и iOS — примеры систем, использующих этот формат для нативных исполняемых файлов, библиотек и объектного кода.

Макет файла Mach-O

Каждый файл Mach-O состоит из одного заголовка Mach-O, за которым следует последовательность команд загрузки, а затем один или несколько сегментов, каждый из которых содержит от 0 до 255 секций. Mach-O использует формат релокации REL для обработки ссылок на символы. При поиске символов Mach-O использует двухуровневое пространство имен, которое кодирует каждый символ в пару "объект/имя символа", которая затем линейно просматривается, сначала по объекту, а затем по имени символа. Базовая структура — список команд загрузки переменной длины, ссылающихся на страницы данных в других частях файла — также использовалась в формате исполняемых файлов Accent. Формат файла Accent, в свою очередь, был основан на идее, заимствованной из Spice Lisp. Все многобайтовые значения во всех структурах данных записываются в порядке байтов хост-системы, для которой был скомпилирован код.

Номер сегмента и номера секций

Сегменты и разделы идентифицируются по номеру сегмента и номеру раздела в сжатых и несжатых разделах информации о редактировании связей. Значение сегмента 3 означает смещение к данным четвертой команды загрузки сегмента в файле Mach-O, отсчитываемое от нуля (0, 1, 2, 3 = 4-й сегмент). Разделы также нумеруются, начиная с 1. Значение нуля для раздела используется в таблице символов для символов, которые не определены ни в одном разделе (неопределенные), например, для методов или данных, существующих в таблице символов другой двоичной программы. Сегмент, содержащий 7 разделов, означает, что последний раздел имеет номер 8. Если следующая команда загрузки сегмента содержит 3 раздела, они будут пронумерованы как 9, 10 и 11. Номер раздела 10 означает второй сегмент, раздел 2. Мы не сможем корректно прочитать таблицу символов и информацию о связях, если не сохраним порядок чтения разделов и их позицию в файле (смещение/адрес). Для создания читателя символов, чтения разделов редактирования связей и даже сопоставления вызовов методов или разработки дизассемблера можно легко использовать смещения файлов без обращения к адресам оперативной памяти и переносам. При создании загрузчика Mach-O необходимо выгрузить разделы по определенным адресам оперативной памяти, добавив базовый адрес для разделения приложений и предотвращения перезаписи друг друга. Имена сегментов и разделов можно переименовывать по своему усмотрению, и это не вызовет проблем с поиском соответствующих разделов по номеру раздела или сегмента, если не изменять порядок команд сегментов.

Порядковые номера библиотеки ссылок

Библиотека располагается по порядковому номеру в сжатых и несжатых разделах информации о редактировании связей. Библиотеки связей нумеруются, начиная с порядкового номера 1. Порядковый номер ноль в таблице символов указывает, что символ отсутствует как внешний символ в другом исполняемом файле Mach-O. Информация о редактировании связей без проблем сможет найти нужную библиотеку для чтения по порядковому номеру, если не изменять порядок следования команд библиотек связей. Команды библиотек связей следует избегать из соображений производительности, поскольку в случае отсутствия библиотеки потребуется поиск по всем загруженным библиотекам связей.

Организация таблицы символов

Все записи таблицы символов хранятся в порядке, определяемом типом. Первыми считываются локальные отладочные символы (если они есть), затем – приватные символы, затем – внешние символы и, наконец, неопределенные символы, которые ссылаются на другую бинарную таблицу символов, содержащую адрес внешнего символа в другом Mach-O бинарном файле. Команда загрузки информации таблицы символов всегда присутствует, если в Mach-O бинарном файле есть раздел таблицы символов. Эта команда сообщает линковщику, сколько в таблице локальных, приватных, внешних и неопределенных символов, а также указывает номер первого символа каждого типа. Информация таблицы символов используется динамическим линковщиком перед чтением записей символов, поскольку она сообщает ему, с какого символа начинать чтение для загрузки неопределенных символов и с какого символа начинать поиск соответствующих внешних символов, не просматривая все записи. Порядок символов в разделе таблицы символов никогда не должен изменяться, так как каждому символу присваивается номер, начиная с нуля. Команда информации таблицы символов использует номера символов для определения порядка загрузки неопределенных символов в разделы "stub" и "pointer". Изменение порядка приведет к вызову неверного метода при выполнении машинного кода.

Косвенная таблица

Загрузчик Mach O записывает секции указателей символов и секции заглушек символов во время выполнения команд загрузки сегментов. Они последовательно используются косвенной таблицей символов для загрузки вызовов методов. Как только достигается конец секции, происходит переход к следующей. Смещение в косвенной таблице символов указывает на набор 32-битных (4-байтовых) значений, используемых в качестве индекса номера символа. Порядок номеров индексов символов соответствует порядку, в котором адреса каждого символа записываются последовательно в секциях указателей и заглушек. Секция заглушек символов содержит инструкции машинного кода с инструкциями JUMP к адресу косвенного символа для вызова метода/функции из другого Mach O-бинарника. Размер каждой инструкции JUMP зависит от типа процессора и хранится в значении reserved2 в секции 32/64 команды загрузки сегмента. Секции указателей содержат 32-битные (4-байтовые) адреса для 32-битных Mach O-бинарников и 64-битные (8-байтовые) адреса для 64-битных Mach O-бинарников. Указатели считываются машинным кодом, а прочитанное значение используется как местоположение для вызова метода/функции, а не содержит инструкции машинного кода. Установленный бит индекса символа указывает на абсолютный метод, что означает, что указатель указывает на точный адрес метода. Установленный бит индекса символа указывает на локальный метод, что означает, что сам указатель указывает на метод, и имя метода отсутствует (локальный метод). Если вы разрабатываете дизассемблер, вы можете легко сопоставить только имя символа со смещением каждого заглушки и указателя, чтобы отобразить вызов метода или функции, не выполняя поиск неопределенного адреса символа в других Mach O-файлах.

Другие варианты реализации

Приложения Mach O могут работать на различных операционных системах, если существует двоичный образ Mach O, соответствующий типу ядра в вашем компьютере. Большинство настольных компьютеров используют архитектуру x86, поэтому Mach O с x86-бинарным кодом будет работать без проблем, если его секции будут загружены в память. Если Mach O предназначен для iPhone, который имеет ядро ARM, для его запуска потребуется компьютер с ядром ARM (не обязательно Apple Silicon ARM); в противном случае потребуется преобразовать ARM-инструкции в эквивалентные x86-инструкции. Основная проблема при загрузке и непосредственном выполнении Mach O – это неопределенные символы, вызывающие функции или методы из других Mach O-бинарных файлов, которых нет в целевой операционной системе. Некоторые символы могут вызывать эквивалентные функции в других операционных системах или использовать адаптерные функции для обеспечения аналогичного поведения двоичных вызовов, как в macOS. Файлы Mach O, хранящиеся на устройстве, могут различаться для iPhone (iOS), macOS, watchOS и tvOS, что приводит к различиям в вызовах функций, связанных с неопределенными символами. В некоторых версиях NetBSD добавлена поддержка Mach O в рамках реализации бинарной совместимости, что позволило запускать некоторые бинарные файлы Mac OS 10.3. Для Linux Синичиро Хамаджи разработал загрузчик Mach O, способный загружать бинарные файлы 10.6. Проект Darling, основанный на этом загрузчике, представляет собой более комплексное решение, направленное на создание полноценной среды для запуска приложений macOS на Linux. Для языка программирования Ruby библиотека ruby macho предоставляет реализацию парсера и редактора двоичных файлов Mach O.