Jump to content

Mach-O

From Alfawiki
Adapted from the English Wikipedia article Mach-O, available under CC BY-SA 4.0Updated 8 August 2025
Mach-O
Company typeFile format
IndustryComputing / Software development
ProductsExecutables, object files, shared libraries, dynamically loaded modules, core dump files

Mach-O (short for Mach object) represents a file format designed for executables, object files, shared libraries, dynamically loaded modules, and core dump files. It was created as a successor to the a.out format.

This format is utilized on platforms built around the Mach kernel, such as NeXTSTEP, macOS, and iOS, serving native executables, libraries, and object code.

Mach-O file structure

A Mach-O file is composed of a header, followed by a series of load commands, and one or more segments, each containing up to 255 sections. It employs the REL relocation scheme to handle symbol references. Symbol resolution uses a two-level namespace where symbols are encoded as 'object/symbol name' pairs and are searched sequentially by object first, then symbol.

The design—a sequence of variable-length load commands pointing to data blocks elsewhere in the file—was also implemented in the executable format for the Accent kernel. This Accent approach drew inspiration from a concept originating in Spice Lisp.

All multi-byte data values adhere to the byte order of the target system's architecture.

Mach-O header

Mach-O File Header
Offset Size (bytes) Description
0 4 Magic number
4 4 CPU type
8 4 CPU subtype
12 4 File type
16 4 Number of load commands
20 4 Total size of load commands
24 4 Flags
28 4 Reserved (only for 64-bit)

Magic numbers vary depending on endianness and architecture: big-endian systems use (32-bit) and (64-bit), while little-endian systems use (32-bit) and (64-bit). The reserved field exists solely in 64-bit headers to accommodate future enhancements.

The CPU type field identifies the instruction set architecture, where the highest bit signals a 64-bit architecture. Notable CPU types include:

CPU Types
Value Architecture
0x00000001 VAX
0x00000007 x86
0x0000000C ARM
0x00000012 PowerPC / MC98000

CPU subtypes specify particular processor variants, which is essential for ensuring compatibility and optimizing performance. For ARM processors:

ARM CPU Subtypes
Value Version Description
0x00000000 All ARM processors
0x00000009 Optimized for ARM-V7 or later versions
0x0000000D Optimized for ARM-V8 or newer

For x86 processors:

x86 CPU Subtypes
Value Version Description
0x00000003 All x86 processors
0x00000004 Optimized for i486 or newer models
0x0000000A Optimized for Pentium-4 or later models

The file type field indicates the Mach-O binary’s intended purpose:

File Types
Value Description
0x00000001 Relocatable object file
0x00000002 Demand-paged executable file
0x00000006 Dynamically linked shared library file

Flags describe various properties and behaviors of the binary, including incremental linking, dynamic symbol binding, namespace usage, and security features. Multiple flags can be combined; some significant ones are:

Selected Flag Definitions
Flag Meaning
1<<0 No undefined symbols allowed
1<<3 Undefined symbols resolved by dynamic linker at load time
1<<7 Utilizes two-level namespace bindings
1<<21 Main executable loaded at randomized memory address (ASLR)

Together, these components define the Mach-O file’s organization, compatibility parameters, and execution behavior critical for software development and operating system integration.

Multi-architecture binaries

A multi-architecture binary merges several Mach-O files into one executable, allowing it to operate across diverse Apple processor architectures, including ARM64 and x86-64 CPUs.

The universal header, formatted in big-endian order, is structured as follows:

Mach-O universal header
Offset Bytes Description
0 4 Magic number
4 4 Count of contained binaries

The magic number, represented as (big-endian), identifies the file as a multi-architecture binary. The count indicates how many Mach-O images are included.

Each binary is detailed by an entry with this layout:

Mach-O universal file entries
Offset Bytes Description
0 4 CPU type identifier
4 4 CPU subtype identifier
8 4 Offset within the file
12 4 Size of the binary
16 4 Section alignment (expressed as power of two)

These entries specify the CPU type and subtype corresponding to each Mach-O image, along with the file offset and size of that image. The section alignment value (for example, a value of 14 corresponds to alignment on a 16,384-byte boundary) guarantees compatibility with tools that manipulate multi-architecture binaries while maintaining accurate placement of each image.

Load commands

Load commands follow immediately after the Mach-O header, which states both their quantity and total byte size to allow verification. Discrepancies in these values or premature reading can indicate file corruption.

Each load command contains:

Load command
Offset Bytes Description
0 4 Type of command
4 4 Size of the command

The command type determines the specific parameters it carries. Commands marked with the bit are essential for loading or executing the binary; this mechanism enables older loaders to bypass unsupported optional commands safely.

Segment load command

The load command type value designates either the 32-bit segment load command () or its 64-bit counterpart (). The difference between these commands arises from varying address sizes between architectures.

Virtual memory addresses are shifted by a base address to isolate application memory spaces. Each section within a segment has relocation offsets adjusted relative to this base address; relocations are unnecessary when loaded precisely at the base address.

Load command (Segment load32/64)
Offset (32-bit) Bytes (32-bit) Offset (64-bit) Bytes (64-bit) Description
0 4 0 4 <syntaxhighlight lang="text" class="" style="" inline="1">0x00000001</syntaxhighlight> (32-bit) / <syntaxhighlight lang="text" class="" style="" inline="1">0x00000019</syntaxhighlight> (64-bit)
4 4 4 4 Command size
8 16 8 16 Segment name
24 4 24 8 Virtual address
28 4 32 8 Address size
32 4 40 8 File offset
36 4 48 8 Size in bytes from file offset
40 4 56 4 Maximum VM protections
44 4 60 4 Initial VM protections
48 4 64 4 Number of sections
52 4 68 4 Flags

Segment names are limited to a maximum of sixteen characters and padded with null bytes if shorter.

This segment load command defines the virtual address plus base, the data size to be written, file offset, and number of bytes to read from the file. If the specified address size exceeds available file data, remaining RAM bytes are zero-initialized.

The __PAGEZERO segment reserves virtual memory without corresponding file data or access permissions to catch NULL pointer dereferences. Its default size varies by architecture: typically 4KiB for 32-bit and up to 4GiB for 64-bit systems.

Initial CPU access rights derive from the initial virtual memory protections; these can be adjusted dynamically during execution within maximum protection constraints.

Permissions
Permission bit (binary) Description
<syntaxhighlight lang="text" class="" style="" inline="1">0001</syntaxhighlight> (bitmask) Read permission for CPU
<syntaxhighlight lang="text" class="" style="" inline="1">0010</syntaxhighlight> (bitmask) Write permission for CPU
<syntaxhighlight lang="text" class="" style="" inline="1">0100</syntaxhighlight> (bitmask) Execute permission for CPU

Bits labeled "x" are reserved for future use.

Segment flags include:

Segment flag settings
Flag32 in Binary Meaning
<syntaxhighlight lang="text" class="" style="" inline="1">0001</syntaxhighlight> High virtual memory space allocated; low space zero-filled (e.g., stacks)
<syntaxhighlight lang="text" class="" style="" inline="1">0010</syntaxhighlight> Fixed virtual memory allocation for libraries to prevent overlaps
<syntaxhighlight lang="text" class="" style="" inline="1">0100</syntaxhighlight> No relocations required in this segment; safe for replacement without relocation adjustments
<syntaxhighlight lang="text" class="" style="" inline="1">1000</syntaxhighlight> Segment protected; first page unprotected if located at zero file offset
<syntaxhighlight lang="text" class="" style="" inline="1">10000</syntaxhighlight> Read-only after any necessary relocations have been applied

Bits marked "x" remain reserved.

Following flags is the count of sections contained; each section entry is formatted as:

Segment section32/64
Offset (32-bit) Bytes (32-bit) Offset (64-bit) Bytes (64-bit) Description
0 16 0 16 Section name
16 16 16 16 Segment name (must correspond with segment command)
32 4 32 8 Section virtual address
36 =4 40 =8 Section size

Reserved3 field (only in the 64-bit format) appears at offset seventy-six and occupies four bytes.

Sections define data locations within segments; their relocation entries adjust addresses if the base address differs from zero. Section sizes apply both in-memory and on-disk representations.

Section Flag/Type values encompass:

Flag (binary) Explanation

- Sections containing executable machine instructions have their highest bit set. - Other flags indicate symbol coalescing behavior, stripping permissions, debug information presence, and relocation entry status. - The least significant eight bits specify section types such as non-lazy or lazy symbol pointers, stub functions, or zero-fill-on-demand sections.

Mach-O loaders keep track of symbol pointer and stub sections utilized by the indirect symbol table for method dispatch. Stub sizes are stored in reserved2 fields; symbol pointers use architecture-dependent address sizes. Sections are processed sequentially according to this table during loading.

Numbering of Segments and Sections

Within the compressed and uncompressed link edit data, segments and sections are distinguished by their respective segment and section numbers. A segment number of 3 corresponds to the fourth segment load command in the Mach-O file, as counting starts from zero. Section numbering begins at 1; notably, section zero in the symbol table represents undefined symbols, such as methods or data referenced externally from other binaries.

For instance, a segment containing 7 sections concludes at section number 8. The subsequent segment with 3 sections would then be assigned section numbers 9, 10, and 11 respectively. Consequently, section number 10 refers to the second section of the second segment.

Ensuring the correct sequence and file offset locations of segments and sections is vital for precise interpretation of the symbol table and linking details. File offsets allow for symbol readers to be constructed, link edit sections to be accessed, or method calls to be mapped without depending on RAM addresses or relocations—an essential aspect when developing disassemblers.

When implementing a Mach-O loader, it is important to output sections at their specified RAM addresses plus a base address to avoid conflicts between applications. While renaming segment and section identifiers does not affect linkability, maintaining the original order of segment commands is crucial.

Libraries Linked

Link libraries in Mach-O binaries operate similarly to those in other formats but do not include a load command defining the program’s entry point. There are three types of load commands utilized for loading linked libraries:

  • Full file path to a dynamically linked shared library
  • Dynamically linked shared libraries located relative to the application’s current directory
  • Optional dynamically linked shared libraries that may be absent; symbols from these are weakly imported if not present

The structure of a link library load command comprises:

Load command (Link library)
Offset Bytes Description
0 4 Command type (values: 0x0000000C, 0x0000000D, or 0x00000018)
4 4 Size of command
8 4 String offset (fixed at 24)
12 4 Timestamp
16 4 Current version
20 4 Compatible version
24 Command size minus 24 File path string

The file path string begins at offset 24 and extends for the remaining bytes defined by command size minus 24. It terminates with a null character (), with any extra null bytes serving as padding.

Ordinal Numbers for Link Libraries

Link libraries are assigned ordinal numbers starting from 1 upwards; ordinal zero signifies that a symbol does not exist as an external reference in another Mach-O binary.

The link edit information uses these ordinal numbers to identify the corresponding library, assuming the order of link library commands remains consistent.

It is recommended to avoid using link library command type for performance considerations because if the specified library is missing, it triggers a search through all loaded link libraries.

__LINKEDIT Symbol Table

Both Mach-O executables and their linked libraries include a symbol table load command structured as follows:

Load command (Symbol table)
Offset Bytes Description
0 4 Command type (0x00000002)
4 4 Command size (fixed at 24)
8 4 File offset where symbols start (relative to Mach-O header)
12 4 Total number of symbols
16 4 File offset where string table begins (relative to Mach-O header)
20 4 Size of string table

The symbol offset points to where symbol entries commence relative to the Mach-O header. The number of symbols indicates where the symbol table ends.

Each symbol's name is stored as an offset within the string table and concludes with a null byte. Symbol addresses use either 32-bit or 64-bit values depending on whether the file architecture is for a respective bitness.

Symbol entries are interpreted as follows:

Symbol32/64 Entry Format
Offset (32-bit) Bytes (32-bit) Offset (64-bit) Bytes (64-bit) Description
0 4 0 4 Name offset within string table
4 1 4 1 Symbol type field
5 1 5 1 Section number (range: 0–255)
6 2 6 2 Additional data (library ordinal number)
8 4 8 8 Symbol address value

The symbol type field contains multiple bit segments:

Symbol Type Bit Fields
Bit Pattern Meaning
???xxxxx Local debugging symbols
xxxx???x Symbol address classification
xxx?xxx? Symbol visibility indicators

Local debugging symbols facilitate software development but are unnecessary during runtime.

Symbol address classifications include:

Symbol Address Classification
Bit Pattern Meaning
xxxx000x Undefined symbols
xxxx001x Absolute symbols
xxxx101x Indirect symbols
xxxx110x Prebound undefined symbols
xxxx111x Defined within specific section number

Visibility flags denote:

Symbol Visibility Flags
Bit Pattern Meaning
xxx1xxx0 Private symbol scope
xxx0xxx1 External symbol scope

External symbols possess defined addresses within linked libraries and can resolve undefined references in Mach-O executables. Private symbols are disregarded when resolving undefined references unless they reside in the same file.

Section numbers indicate where a symbol is located; zero implies undefined with no associated section or address.

The data info field specifies both the link library ordinal number and linker-related options:

Symbol Data Info Bit Fields
Bit Pattern Description
????????xxxxxxxx Library ordinal number (values from 0 to 255)
xxxxxxxx????xxxx Flags related to dynamic loader behavior
xxxxxxxxxxxx???? Address type options for dynamic linking

An ordinal value of zero denotes either an external or current-file symbol. Undefined symbols use this field to indicate their associated library ordinal and linker options.

Dynamic loader flags include:

xxxxxxxx0001xxxx | Required reference by dynamic loader
Dynamic Loader Flag Options
Bit Pattern Description

xxxxxxxx0010xxxx | Utilized at runtime by dynamic linker

xxxxxxxx0100xxxx | Sets symbol address to zero if undefined during runtime

xxxxxxxx1000xxxx | Ignores duplicate definitions detected by static or dynamic linker

Multiple flags may be applied simultaneously.

Address type options define how method calls behave:

xxxxxxxxxxxx0000 | Non-lazy pointer call
Bit Pattern Description

xxxxxxxxxxxx0001 | Lazy pointer call

xxxxxxxxxxxx0010 | Method call defined within this library or program

xxxxxxxxxxxx0011 | Private method call defined within this library or program

xxxxxxxxxxxx0100 | Private non-lazy pointer call

xxxxxxxxxxxx0101 | Private lazy pointer call

Only one address type flag applies per symbol. Lazy pointers initially point to dyld_stub_binder, which resolves the actual address upon first invocation and replaces the stub thereafter, enhancing performance efficiency.

Organization of the Symbol Table

Entries within the symbol table are categorized by type in a specific sequence: starting with local debug symbols, followed by private symbols, then external symbols, and concluding with undefined symbols that link to external Mach-O binaries.

Whenever a symbol table is present, the corresponding load command for symbol table information is included. This command details the counts and starting indices for local, private, external, and undefined symbols. Such organization enables the dynamic linker to efficiently locate undefined and external symbols without scanning the entire symbol list.

Maintaining the original order of symbols is crucial because symbols receive sequential numbering beginning at zero. The load command depends on these numbers to accurately associate undefined symbols with their respective stubs and pointers. Any change in this sequence could result in erroneous method invocations during runtime.

__LINKEDIT Symbol Table Details

This load command facilitates rapid identification of undefined and external symbols during linking by specifying their locations as referenced by the command .

Load Command: Symbol Table Information
Offset Size (Bytes) Description
0 4 <syntaxhighlight lang="text" class="" style="" inline="1">0x0000000B</syntaxhighlight> (Command identifier)
4 4 Size of command (fixed at 80 bytes)
8 4 Starting index for local symbols
12 4 Count of local symbols
16 4 Starting index for external symbols
20 4 Count of external symbols
24 4 Starting index for undefined symbols
28 4 Count of undefined symbols
32 4 Offset to content table
36 4 Number of entries in content table
40 4 Offset to module table
44 4 Number of entries in module table
48 4 Offset to referenced symbol table
52 4 Number of entries in referenced symbol table
56 4 Offset to indirect symbol table
60 4 Number of entries in indirect symbol table
64 4 Offset to external relocation entries
68 4 Count of external relocation entries
72 4 Offset to local relocation entries
76 4 Count of local relocation entries

To locate individual entries, symbol indices are multiplied by either 12 bytes (for 32-bit architectures) or 16 bytes (for 64-bit architectures), then added to the base offset of the symbol table. Local symbols begin at index zero and mainly support debugging processes.

Similar indexing and counting apply to external and undefined symbols to streamline their retrieval. When private symbols exist, a gap may appear between local and external symbol ranges.

Offsets with a value of zero indicate sections that are not utilized.

Indirect Symbol Table

During segment load commands, the Mach-O loader records sections containing symbol pointers and stubs. These sections are accessed in sequence by the indirect symbol table to resolve method invocations. The offset for the indirect symbol table points to an array of 32-bit values representing symbol indices, which correspond sequentially to addresses stored within pointer and stub sections.

The symbol stub section includes machine-level JUMP instructions that route calls to functions or methods located in other Mach-O binaries. The size of these instructions varies depending on processor architecture and is specified in the reserved2 field within the segment load command.

Pointer sections store address values sized at either 32 bits or 64 bits based on the binary's architecture. These pointers are directly utilized by machine code for method/function calls rather than containing executable instructions themselves.

Symbol index values with a particular bit set identify absolute methods—pointers referencing exact addresses—whereas those with another bit set denote local methods lacking explicit method names.

In designing disassemblers, associating symbol names with offsets in stub and pointer tables allows clear visualization of function or method calls without needing to cross-reference undefined symbols from other Mach-O files.

__LINKEDIT Compressed table

When a compressed link edit table command is present, the need for undefined or external symbols along with the indirect symbol table is eliminated. This enhancement improves loading efficiency by consolidating all binding data.

Nonetheless, the indirect symbol table might still be included to maintain compatibility with older operating system versions.

Load command (Compressed link edit table)
Offset Bytes Description
0 4 <syntaxhighlight lang="text" class="" style="" inline="1">0x00000022</syntaxhighlight> (Command type)
4 4 Size of command (48 bytes)
8 4 Offset for rebase information
12 4 Size of rebase data
16 4 Offset for bind information
20 4 Size of bind data
24 4 Offset for weak bind data
28 4 Size of weak bind data
32 4 Offset for lazy bind data
36 4 Size of lazy bind data
40 4 Offset for export information
44 4 Size of export data

File offsets that are unused are set to zero.

Binding information

The bind, weak bind, and lazy bind sections share a single opcode format to resolve symbols.

Weak binding permits fallback to symbols from previously loaded Mach-O files if the specified library ordinal does not provide them. Lazy binding initially routes calls through a dyld_stub_binder that resolves the symbol dynamically at runtime, then patches the call site for direct future access. Plain binding mandates the symbol’s presence in the specified library ordinal.

Library ordinals are encoded as byte values: for ordinals between 0 and 15, or as – followed by a LEB128-encoded number for larger ordinals (for example, ordinal 132 is encoded as ).

Symbol names are introduced with byte values , where flag bits indicate whether an import is weak (flag 8) or non-weak (flag 1). For instance, specifies a weakly imported symbol named "Example."

Location-setting opcodes select segments and add LEB128 offsets to establish the current binding address.

Binding opcodes associate the current location with symbols and advance addresses by the pointer size (4 bytes on 32-bit platforms, 8 bytes on 64-bit).

An example sequence sets the library ordinal, specifies segment location plus offset, and binds symbols "Example" and "Example2," writing their addresses consecutively.

This compact format removes redundant fields from symbol tables and makes the indirect symbol table unnecessary.

Application main entry point

A load command of type specifies where the application begins execution.

Load command (Main entry point)
Offset Bytes Description
0 4 <syntaxhighlight lang="text" class="" style="" inline="1">0x00000028</syntaxhighlight> (Command type)
4 4 Command size (24 bytes)
8 8 Starting address location
16 8 Stack size in memory

If no relocation occurs, the main entry point equals the address location plus a zero base address. This marks the CPU’s initial instruction execution point.

This supersedes an older load command , which varied depending on CPU architecture and stored initial register states.

Application UUID number

A load command with type designates the application's universally unique identifier (UUID).

Load command (UUID number)
Offset Bytes Description
0 4 <syntaxhighlight lang="text" class="" style="" inline="1">0x0000001B</syntaxhighlight> (Command type)
4 4 Command size (24 bytes)
8 16 A unique, compile-time assigned, 128-bit UUID

The UUID functions as a distinct identifier assigned during compilation to differentiate the application file in app distribution platforms or online environments.

Minimum OS Version Requirement

A load command identified by the type indicates the minimum operating system version necessary.

Load Command (Minimum OS Version)
Offset Bytes Description
0 4 <syntaxhighlight lang="text" class="" style="" inline="1">0x00000032</syntaxhighlight> (Command identifier)
4 4 Size of the command
8 4 Platform identifier
12 4 Minimum required OS version
16 4 SDK version used
20 4 Count of tools utilized

The following platform identifiers are defined:

Platform Identifier
Value Platform
0x00000001 macOS
0x00000002 iOS
0x00000003 tvOS
0x00000004 watchOS
0x00000005 bridgeOS
0x00000006 Mac Catalyst
Additional values represent simulators and DriverKit platforms.

Version numbers are encoded as a combination of major, minor, and patch levels. For instance, corresponds to version 13.2.0. The SDK version utilizes the same encoding scheme.

The tools employed during binary compilation are itemized as follows:

Tool Information
Offset Bytes Description
0 4 Tool identifier
4 4 Version number of the tool

Tool identifiers include:

Tool Identifier Values
Value Tool Name
0x00000001 CLANG
0x00000002 SWIFT
0x00000003 LD (Linker)

Version numbers for tools adhere to the same format as OS versions.

Starting with Mac OS X 10.6, Mach-O binaries default to execution only on systems running version 10.6 or newer due to introduction of load commands incompatible with earlier releases. Furthermore, Link Edit tables within the __LINKEDIT segment were compressed to optimize space but cannot be processed by versions preceding Mac OS X 10.6. To preserve compatibility with older versions, developers may apply the linker option "-mmacosx-version-min=".

Alternative Implementations

Mach-O applications can operate on various operating systems provided that a Mach-O binary compatible with the device's CPU architecture is available. Most desktop systems utilize x86 processors, enabling x86 Mach-O binaries to execute when mapped into memory. For ARM-based iPhone CPUs, running Mach-O binaries requires either ARM-based hardware (not limited to Apple silicon) or dynamic translation of ARM instructions into x86 code. A significant obstacle is the presence of undefined symbols referencing functions from other Mach-O binaries that may not exist on different operating systems. Some symbols might be resolved by linking to equivalent functions or through adapter routines that emulate macOS behavior. Variations in Mach-O files across iPhone (iOS), macOS, watchOS, and tvOS platforms influence compatibility of function calls.

Certain releases of NetBSD incorporated Mach-O support to enable binary compatibility, allowing execution of select Mac OS X 10.3 applications.

On Linux systems, Shinichiro Hamaji developed a Mach-O loader capable of handling binaries built for version 10.6 and later. Building upon this foundation, the Darling Project provides a comprehensive environment for running macOS software on Linux.

For Ruby developers, the ruby-macho gem offers a pure Ruby implementation for parsing and modifying Mach-O binaries.

See also

Bibliography

External links