Mach-O


Mach-O is a file format for executables, object code, shared libraries, dynamically loaded code, and core dumps. It was developed to replace the a.out format.
Mach-O is used by some systems based on the Mach kernel. NeXTSTEP, macOS, and iOS are examples of systems that use this format for native executables, libraries and object code.

File layout

Each Mach-O file is made up of one Mach-O header, followed by a series of load commands, followed by one or more segments, each of which contains between 0 and 255 sections. Mach-O uses the REL relocation format to handle references to symbols. When looking up symbols, Mach-O uses a two-level namespace that encodes each symbol into an 'object/symbol name' pair that is then linearly searched for, first by the object and then the symbol name.
The basic structure—a list of variable-length "load commands" that reference pages of data elsewhere in the file—was also used in the executable file format for Accent. The Accent file format was, in turn, based on an idea from Spice Lisp.
All multi-byte values in all data structures are written in the byte order of the host for which the code was produced.

Header

OffsetBytesDescription
04Magic number
44CPU type
84CPU subtype
124File type
164Number of load commands
204Size of load commands
244Flags
284Reserved

For big-endian binaries, magic number for 32-bit code is while the magic number for 64-bit architectures is. For little-endian binaries, it will be for 32-bit and for 64-bit. These latter two are just the former but with inverted endianness.
The reserved value is only present in 64-bit Mach-O files. It is reserved for future use or extension of the 64-bit header.
The CPU type indicates the instruction set architecture for the code. If the file is for the 64-bit version of the instruction set architecture, the CPU type value has the bit set. If the file is for the 64-bit version of the instruction set architecture but with 32-bit pointers, the CPU type value has the bit set.
The CPU type values are as follows:
ValueCPU type
0x00000001VAX
0x00000002ROMP
0x00000004NS32032
0x00000005NS32332
0x00000006MC680x0
0x00000007x86
0x00000008MIPS
0x00000009NS32352
0x0000000BHP-PA
0x0000000CARM
0x0000000DMC88000
0x0000000ESPARC
0x0000000Fi860
0x00000010i860 or maybe DEC Alpha
0x00000011RS/6000
0x00000012PowerPC / MC98000
0x00000018RISC-V

Each CPU type has a set of CPU subtype values, indicating a particular model of that CPU type for which the code is intended. Newer models of a CPU type may support instructions, or other features, not supported by older CPU models, so that code compiled or written for a newer model might contain instructions that are illegal instructions on an older model, causing that code to trap or otherwise fail to operate correctly when run on an older model. Code intended for an older model will run on newer models without problems.
If the CPU type is ARM then the subtypes are as follows:
ValueCPU version
0x00000000All ARM processors
0x00000001Optimized for ARM-A500 ARCH or newer.
0x00000002Optimized for ARM-A500 or newer.
0x00000003Optimized for ARM-A440 or newer.
0x00000004Optimized for ARM-M4 or newer.
0x00000005Optimized for ARM-V4T or newer.
0x00000006Optimized for ARM-V6 or newer.
0x00000007Optimized for ARM-V5TEJ or newer.
0x00000008Optimized for ARM-XSCALE or newer.
0x00000009Optimized for ARM-V7 or newer.
0x0000000AOptimized for ARM-V7F or newer.
0x0000000BOptimized for ARM-V7S or newer.
0x0000000COptimized for ARM-V7K or newer.
0x0000000DOptimized for ARM-V8 or newer.
0x0000000EOptimized for ARM-V6M or newer.
0x0000000FOptimized for ARM-V7M or newer.
0x00000010Optimized for ARM-V7EM or newer.

If the CPU type is x86 then the subtypes are as follows:
ValueCPU version
0x00000003All x86 processors.
0x00000004Optimized for 486 or newer.
0x00000084Optimized for 486SX or newer.
0x00000056Optimized for Pentium M5 or newer.
0x00000067Optimized for Celeron or newer.
0x00000077Optimized for Celeron Mobile.
0x00000008Optimized for Pentium 3 or newer.
0x00000018Optimized for Pentium 3-M or newer.
0x00000028Optimized for Pentium 3-XEON or newer.
0x0000000AOptimized for Pentium-4 or newer.
0x0000000BOptimized for Itanium or newer.
0x0000001BOptimized for Itanium-2 or newer.
0x0000000COptimized for XEON or newer.
0x0000001COptimized for XEON-MP or newer.

After the subtype value is the file type value.
ValueDescription
0x00000001Relocatable object file.
0x00000002Demand paged executable file.
0x00000003Fixed VM shared library file.
0x00000004Core file.
0x00000005Preloaded executable file.
0x00000006Dynamically bound shared library file.
0x00000007Dynamic link editor.
0x00000008Dynamically bound bundle file.
0x00000009Shared library stub for static linking only, no section contents.
0x0000000ACompanion file with only debug sections.
0x0000000Bx86_64 kexts.
0x0000000Ca file composed of other Mach-Os to be run in the same userspace sharing a single linkedit.

After the file type value is the number of load commands and the total number of bytes the load commands are after the Mach-O header, then a 32-bit flag with the following possible settings.
Flag in left shift Flag in binaryDescription
1<<00000_0000_0000_0000_0000_0000_0000_0001The object file has no undefined references.
1<<10000_0000_0000_0000_0000_0000_0000_0010The object file is the output of an incremental link against a base file and can't be link edited again.
1<<20000_0000_0000_0000_0000_0000_0000_0100The object file is input for the dynamic linker and can't be statically link edited again.
1<<30000_0000_0000_0000_0000_0000_0000_1000The object file's undefined references are bound by the dynamic linker when loaded.
1<<40000_0000_0000_0000_0000_0000_0001_0000The file has its dynamic undefined references prebound.
1<<50000_0000_0000_0000_0000_0000_0010_0000The file has its read-only and read-write segments split.
1<<60000_0000_0000_0000_0000_0000_0100_0000The shared library init routine is to be run lazily via catching memory faults to its writeable segments.
1<<70000_0000_0000_0000_0000_0000_1000_0000The image is using two-level name space bindings.
1<<80000_0000_0000_0000_0000_0001_0000_0000The executable is forcing all images to use flat name space bindings.
1<<90000_0000_0000_0000_0000_0010_0000_0000This umbrella guarantees no multiple definitions of symbols in its sub-images so the two-level namespace hints can always be used.
1<<100000_0000_0000_0000_0000_0100_0000_0000Do not have dyld notify the prebinding agent about this executable.
1<<110000_0000_0000_0000_0000_1000_0000_0000The binary is not prebound but can have its prebinding redone. only used when MH_PREBOUND is not set.
1<<120000_0000_0000_0000_0001_0000_0000_0000Indicates that this binary binds to all two-level namespace modules of its dependent libraries.
1<<130000_0000_0000_0000_0010_0000_0000_0000Safe to divide up the sections into sub-sections via symbols for dead code stripping.
1<<140000_0000_0000_0000_0100_0000_0000_0000The binary has been canonicalized via the un-prebind operation.
1<<150000_0000_0000_0000_1000_0000_0000_0000The final linked image contains external weak symbols.
1<<160000_0000_0000_0001_0000_0000_0000_0000The final linked image uses weak symbols.
1<<170000_0000_0000_0010_0000_0000_0000_0000When this bit is set, all stacks in the task will be given stack execution privilege.
1<<180000_0000_0000_0100_0000_0000_0000_0000When this bit is set, the binary declares it is safe for use in processes with uid zero.
1<<190000_0000_0000_1000_0000_0000_0000_0000When this bit is set, the binary declares it is safe for use in processes when UGID is true.
1<<200000_0000_0001_0000_0000_0000_0000_0000When this bit is set on a dylib, the static linker does not need to examine dependent dylibs to see if any are re-exported.
1<<210000_0000_0010_0000_0000_0000_0000_0000When this bit is set, the OS will load the main executable at a random address.
1<<220000_0000_0100_0000_0000_0000_0000_0000Only for use on dylibs. When linking against a dylib that has this bit set, the static linker will automatically not create a load command to the dylib if no symbols are being referenced from the dylib.
1<<230000_0000_1000_0000_0000_0000_0000_0000Contains a section of type S_THREAD_LOCAL_VARIABLES.
1<<240000_0001_0000_0000_0000_0000_0000_0000When this bit is set, the OS will run the main executable with a non-executable heap even on platforms that don't require it.
1<<250000_0010_0000_0000_0000_0000_0000_0000The code was linked for use in an application.
1<<260000_0100_0000_0000_0000_0000_0000_0000The external symbols listed in the nlist symbol table do not include all the symbols listed in the dyld info.
1<<270000_1000_0000_0000_0000_0000_0000_0000Allow LC_MIN_VERSION_MACOS and LC_BUILD_VERSION load commands with the platforms macOS, macCatalyst, iOSSimulator, tvOSSimulator and watchOSSimulator.
1<<311000_0000_0000_0000_0000_0000_0000_0000Only for use on dylibs. When this bit is set, the dylib is part of the dyld shared cache, rather than loose in the filesystem.
----0xxx_0000_0000_0000_0000_0000_0000_0000The digits marked with "x" have no use, and are reserved for future use.

Multiple binary digits can be set to one in the flags to identify any information or settings that apply to the binary.
Now the load commands are read as one has reached the end of the Mach-O header.

Multi-architecture binaries

Multiple Mach-O files can be combined in a multi-architecture binary. This allows a single binary file to contain code to support multiple instruction set architectures, for example for different generations and types of Apple devices, including different processor architectures such as ARM64 and x86-64.
All fields in the universal header are big-endian.
The universal header is in the following form:
OffsetBytesDescription
04Magic number
44Number of binaries

The magic number in a multi-architecture binary is in big-endian byte order, so the first four bytes of the header will always be, in that order.
The number of binaries is the number of entries that follow the header.
The header is followed by a sequence of entries in the following form:
OffsetBytesDescription
04CPU type
44CPU subtype
84File offset
124Size
164Section alignment

The sequence of entries is followed by a sequence of Mach-O images. Each entry refers to a Mach-O image.
The CPU type and subtype for an entry must be the same as the CPU type and subtype for the Mach-O image to which the entry refers.
The file offset and size are the offset in the file of the beginning of the Mach-O image, and the size of the Mach-O image, to which the entry refers.
The section alignment is the logarithm, base 2, of the byte alignment in the file required for the Mach-O image to which the entry refers; for example, a value of 14 means that the image must be aligned on a 214-byte boundary, i.e. a 16384-byte boundary. This is required by tools that modify the multi-architecture binary, in order for them to keep the image properly aligned.

Load commands

The load commands are read immediately after the Mach-O header.
The Mach-O header specifies how many load commands exist after the Mach-O header and the size in bytes to where the load commands end. The size of load commands is used as a redundancy check.
When the last load command is read and the number of bytes for the load commands do not match, or if we go outside the number of bytes for load commands before reaching the last load command, then the file may be corrupted.
Each load command is a sequence of entries in the following form:
OffsetBytesDescription
04Command type
44Command size

The load command type identifies what the parameters are in the load command. If a load command starts with bit set, the load command is necessary in order to be able to load or run the binary. This allows older Mach-O loaders to skip commands not understood by the loader that are not mandatory for loading the application.

Segment load command

Mach-O binaries that use load command type use the 32-bit version of the segment load command, while is used to specify the 64-bit version of the segment load command.,
The segment load command varies whether the Mach-O header is 32-bit or 64-bit. This is because 64-bit processor architecture uses 64-bit addresses while 32-bit architectures use 32-bit addresses.
All virtual RAM addresses are added to a base address to keep applications spaced apart. Each section in a segment load command has a relocation list offset that specifies the offsets in the section that must be adjusted based on the application's base address. The relocations are unnecessary if the application can be placed at its defined RAM address locations such as a base address of zero.
Offset Bytes Offset Bytes Description
0404
4444Command size
816816Segment name
244248Address
284328Address size
324408File offset
364488Size
404564Maximum virtual memory protections
444604Initial virtual memory protections
484644Number of sections
524684Flag32

A segment name cannot be larger than 16 text characters in bytes. The unused characters are in value.
The segment command contains the address to write the section in virtual address space plus the application's base address. The number of bytes to write to the address location.
After the address information is the file offset the segment data is located in the Mach-O binary, and the number of bytes to read from the file.
When the address size is larger than the number of bytes to read from the file, the rest of the bytes in RAM space are set.
There is a segment that is called __PAGEZERO, which has a file offset of zero and a size of zero in the file. It has a defined virtual memory address and size. Its access permissions are set to zero as well, meaning it cannot be used at all. The purpose of this segment is to catch invalid NULL pointers. On 32-bit environments, the default size of this segment is 4 KiB, while on 64-bit environments it is 4 GiB The size of this segment is configurable through the -pagezero_size compiler/linker flag.
When a segment is initially placed in the virtual address space, it is given the CPU access permissions specified by the initial virtual memory protections value. The permissions on a region of the virtual address space may be changed by application or library code with calls to routines such as ; the maximum virtual memory protections limit what permissions may be granted for access to the segment.
Permission bit in binaryDescription
The section allows the CPU to read data from this section.
The section allows the CPU to write data to this section.
The section allows the CPU to execute code in this section.
The digits marked with "x" have no use, and are reserved for future use.

Then after the CPU address protection settings is the number of sections that are within this segment that are read after the segments flag settings.
The segment flag settings are as follows:
Flag32 in binaryDescription
The file contents for this segment is for the high part of the VM space, the low part is zero filled.
This segment is the VM that is allocated by a fixed VM library, for overlap checking in the link editor.
This segment has nothing that was relocated in it and nothing relocated to it, that is it maybe safely replaced without relocation.
This segment is protected. If the segment starts at file offset 0, the first page of the segment is not protected. All other pages of the segment are protected.
This segment is made read-only after relocations are applied if needed.
The digits marked with "x" have no use, and are reserved for future use.

The number of sections in the segment is a set of entries that are read as follows:
Offset Bytes Offset Bytes Description
016016Section name
16161616Segment name
324328Section address
364408Section size
404484Section file offset
444524Alignment
484564Relocations file offset
524604Number of relocations
564644Flag/Type
604684Reserved1
644724Reserved2
N/AN/A764Reserved3

The section's segment name must match the segment's load command name. The sections entries locate to data in the segment. Each section locates to the relocation entries for adjusting addresses in the section if the application base address is added to anything other than zero.
The section size applies to both the size of the section at its address location and size in the file at its offset location.
The section Flag/Type value is read as follows:
Flag in binaryDescription
Section contains only true machine instructions
Section contains coalesced symbols that are not to be in a ranlib table of contents
Ok to strip static symbols in this section in files with the MH_DYLDLINK flag
No dead stripping
Blocks are live if they reference live blocks
Used with i386 code stubs written on by dyld
A debug section
Section contains some machine instructions
Section has external relocation entries
Section has local relocation entries

Any of the settings that apply to the section have a binary digit set one. The last eight binary digits is the section type value.
Flag in binaryDescription
Section with only non-lazy symbol pointers
Section with only lazy symbol pointers
Section with only symbol stubs
Zero fill on demand section
Section with only lazy symbol pointers to lazy loaded dylibs

The Mach-O loader records the symbol pointer sections and symbol stub sections. They are sequentially used by the indirect symbol table to load in method calls.
The size of each symbol stub is stored in reserved2 value. Each pointer is 32-bit address locations in 32-bit Mach-O and 64-bit address locations in 64-bit Mach-O. Once the section end is reached, we move to the next section while reading the indirect symbol table.

Segment number and section numbers

The segments and sections are located by segment number and section number in the compressed and uncompressed link edit information sections.
A segment value of 3 would mean the offset to the data of the fourth segment load command in the Mach-O file starting from zero up.
Sections are also numbered from sections 1 and up. Section value zero is used in the symbol table for symbols that are not defined in any section. Such as a method, or data that exist within another binaries symbol table section.
A segment that has seven sections would mean the last section is 8. Then if the following segment load command has three sections they are labelled as sections 9, 10, and 11. A section number of 10 would mean the second segment, section 2.
One would not be able to properly read the symbol table and linking information if we do not store the order the sections are read in and their address/file offset position.
One can easily use file offset without using the RAM addresses and relocations to build a symbol reader and to read the link edit sections and even map method calls or design a disassembler.
If building a Mach-O loader, then you want to dump the sections to the defined RAM addresses plus a base address to keep applications spaced apart so they do not write over one another.
The segment names and section names can be renamed to anything you like and there link will be no problems locating the appropriate sections by section number, or segment number as long as you do not alter the order the segment commands go in.

Link libraries

Link libraries are the same as any other Mach-O binary, just that there is no command that specifies the main entry point at which the program begins.
There are three load commands for loading a link library file.
Load command type are for the full file path to the dynamically linked shared library.
Load command type are for dynamically linked shared locations from the application's current path.
Load command type is for a dynamically linked shared library that is allowed to be missing. The symbol names exist in other link libraries and are used if the library is missing meaning all symbols are weak imported.
The link library command is read as follows:
OffsetBytesDescription
04

44Command size
84String offset
124Time date stamp
164Current version
204Compatible version
24Command size - 24File path string

The file path name begins at the string offset, which is always 24. The number of bytes per text character is the remaining bytes in command size. The end of the library file path is identified by a character that is. The remaining values are used as padding, if any.

Link library ordinal numbers

The library is located by ordinal number in the compressed and uncompressed link edit information sections.
Link libraries are numbered from ordinal 1 and up. The ordinal value zero is used in the symbol table to specify the symbol does not exist as an external symbol in another Mach-O binary.
The link edit information will have no problem locating the appropriate library to read by ordinal number as long as one does not alter the order in which the link library commands go in.
Link library command should be avoided for performance reasons, as in the case the library is missing, then a search must be performed through all loaded link libraries.

__LINKEDIT symbol table

Mach-O application files and link libraries both have a symbol table command.
The command is read as follows:
OffsetBytesDescription
04
44Command size
84Symbols
124Number of symbols
164String table
204String table size

The symbol file offset is the offset relative to the start of the Mach-O header to where the symbol entries begins in the file. The number of symbol entries marks the end of the symbol table.
A symbol has a name offset that should never exceed the string table size. Each symbol name offset is added to the string table file offset which in turn is relative to the start of the Mach-O header. Each symbol name ends with a byte value.
The symbol address uses a 32-bit address for 32-bit Mach-O files and a 64-bit address for 64-bit Mach-O files.
Each symbol entry is read as follows:
Offset Bytes Offset Bytes Description
0404Name offset
4141Symbol type
5151Section number 0 to 255
6262Data info
8488Symbol address

The symbol name offset is added to the string table offset. The last text character byte is read as.
The symbol type value has multiple adjustable sections in binary. The symbol type is read as follows:
Binary digitsDescription
Local debugging symbols
Symbol address type
Symbol visibility setting flags

The digits marked are used for the specified purpose; the digits marked are used for other purposes.
The three first binary digits are symbols that locate to function names relative to compiled machine code instructions and line numbers by address location. This information allows us to generate line numbers to the location your code crashed. Local debugging symbols are only useful when designing the application, but are not needed to run the application.
Binary valueDescription
Symbol undefined
Symbol absolute
Symbol indirect
Symbol prebound undefined
Symbol defined in section number

The following flag settings:
Binary valueDescription
Private symbol
External symbol

External symbols are symbols that have a defined address in the link library and can be copied to an undefined symbol in a Mach-O application. The address location is added to the link library base address.
A private symbol is skipped even if it matches the name of an undefined symbol. A private and external symbol can only be set to an undefined symbol if it is in the same file.
After the symbol type is the section number the symbol exists in. The section number is a byte value. You can add more sections than 255 using segment load commands, but the section numbers are then outside the byte value range used in the symbol entries.
A section number of zero means the symbol is not in any section of the application, the address location of the symbol is zero, and is set as Undefined. A matching External symbol name has to be found in a link library that has the symbol address.
The data info field contains the link library ordinal number that the external symbol can be found in with the matching symbol name. The data info bit field breaks down as follows:
Binary digitsDescription
Library ordinal number 0 to 255
Dynamic loader flag options
Address type option

The library ordinal number is set zero if the symbol is an external symbol, or exists in the current file. Only undefined symbols use the data info section to specify a library ordinal number and linker options.
The dynamic loader flag options are as follows:
Binary digitsDescription
Must be set for any defined symbol that is referenced by dynamic-loader.
Used by the dynamic linker at runtime.
If the dynamic linker cannot find a definition for this symbol, it sets the address of this symbol to 0.
If the static linker or the dynamic linker finds another definition for this symbol, the definition is ignored.

Any of the four options that apply can be set.
The address type option values are as follows:
Binary digitsDescription
Non Lazy loaded pointer method call
Lazy loaded pointer method call
Method call defined in this library/program
Private Method call defined in this library/program
Private Non Lazy loaded pointer method call
Private Lazy loaded pointer method call

Only one address type value can be set by value. A pointer is a value that is read by the program machine code to call a method from another binary file. Private means other programs are not intended to be able to read or call the function/methods other than the binary itself. Lazy means the pointer locates to the dyld_stub_binder which looks for the symbol then calls the method, then replaces the dyld_stub_binder location with the location to the symbol. Any more calls done from machine code in the binary will now locate to the address of the symbol and will not call the dyld_stub_binder.

Symbol table organization

The symbol table entries are all stored in order by type. The first symbols that are read are local debug symbols if any, then private symbols, then external symbols, and finally the undefined symbols that link to another binary symbol table containing the external symbol address in another Mach-O binary.
The symbol table information load command always exists if there is a symbol table section in the Mach-O binary. The command tells the linker how many local symbols there are, how many private, how many external, and how many undefined. It also identifies the symbol number they start at. The symbol table information is used before reading the symbol entries by the dynamic linker as it tells the dynamic linker where to start reading the symbols to load in undefined symbols and where to start reading to look for matching external symbols without having to read all the symbol entries.
The order the symbols go in the symbol section should never be altered as each symbol is numbered from zero up. The symbol table information command uses the symbol numbers for the order to load the undefined symbols into the stubs and pointer sections. Altering the order would cause the wrong method to be called during machine code execution.

__LINKEDIT Symbol table information

The symbol table information command is used by the dynamic linker to know where to read the symbol table entries under symbol table command, for fast lookup of undefined symbols and external symbols while linking.
The command is read as follows:
OffsetBytesDescription
04
44Command size
84Local symbol index
124Number of local symbols
164External symbols index
204Number of external symbols
244Undefined symbols index
284Number of undefined symbols
324Content table offset
364Number of content table entries
404Module table offset
444Number of module table entries
484Offset to referenced symbol table
524Number of referenced symbol table entries
564Indirect symbol table offset
604Indirect symbol table entries
644External relocation offset
684Number of external relocation entries
724Local relocation offset
764Number of Local relocation entries

The symbol index is multiplied by 12 for Mach-O 32-bit, or 16 for Mach-O 64-bit plus the symbol table entries offset to find the offset to read the symbol entries by symbol number index.
The local symbol index is zero as it is at the start of the symbol entries. The local symbols are used for debugging information.
Number of local symbols is how many exist after the symbol index.
The same two properties are repeated for external symbols and undefined symbols for fast reading of the symbol table entries.
There is a small index/size gap between local symbols and external symbols if there are private symbols.
Any file offsets that are zero are unused.

Indirect table

The Mach-O loader records the symbol pointer sections and symbol stub sections during the segment load commands. They are sequentially used by the indirect symbol table to load in method calls. Once the section end is reached, we move to the next.
The Indirect symbol table offset locates to a set of 32-bit values that are used as a symbol number index.
The order the symbol index numbers go is the order we write each symbol address one after another in the pointer and stub sections.
The symbol stub section contains machine code instructions with JUMP instructions to the indirect symbol address to call a method/function from another Mach-O binary. The size of each JUMP instruction is based on processor type and is stored in the reserved2 value under the section32/64 of a segment load command.
The pointer sections are 32-bit address values for 32-bit Mach-O binaries and 64-bit address values for 64-bit Mach-O binaries. Pointers are read by machine code and the read value is used as the location to call the method/function rather than containing machine code instructions.
A symbol index number bit set are absolute methods meaning the pointer locates to the exact address of a method.
A symbol index number bit set are local methods meaning the pointer itself located to the method and that there is no method name.
If you are designing a disassembler you can easily map just the symbol name to the offset address of each stub and pointer to show the method or function call taking place without looking for the undefined symbol address location in other Mach-O files.

__LINKEDIT Compressed table

If the compressed link edit table command exists, then the undefined/external symbols in the symbol table are no longer needed. The indirect symbol table and location of the stubs and pointer sections are no longer required.
The indirect symbol table still exists in the case of building backwards compatible Mach-O files that load on newer and older OS versions.
OffsetBytesDescription
04
44Command size
84Rebase file offset
124Rebase size
164Bind file offset
204Bind size
244Weak bind file offset
284Weak bind size
324Lazy bind file offset
364Lazy bind size
404Export file offset
444Export size

Any file offsets that are zero are sections that are unused.

Binding information

The bind, weak bind, and lazy bind sections are read using the same operation code format.
Originally the symbol table would define the address type in the data info field in the symbol table as lazy, weak, or non-lazy.
Weak binding means that if the set library to look in by library ordinal number, and the set symbol name does not exist but exists under a different previously loaded Mach-O file then the symbol location is used from the other Mach-O file.
Lazy means the address that is written located to the dyld_stub_binder, which looks for the symbol then calls the method, then replaces the dyld_stub_binder location with the location to the symbol. Any more calls done from machine code in the binary will now locate to the address of the symbol and will not call the dyld_stub_binder.
The plain old bind section does not do any fancy loading or address tricks. The symbol must exist in the set library ordinal.
A byte value that is sets the link library ordinal number. The hex digit that is X is a 0 to 15 library ordinal number.
A byte value that is to sets the link library ordinal number to the value that is read after the operation code.
The byte sequence set ordinal number 132.
The number value after the operation code is encoded as a LEB128 number. The last 7 binary digits are added together to form a larger number as long as the last binary digit is set one in value. This allows us to encode variable length number values.
A byte value that is sets the symbol name. The hex digit marked X sets the flag setting.
Flag setting 8 means the method is weak imported. Flag setting 1 means the method is non weak imported.
The byte sequence sets the symbol name Example. The last text character byte is. It is also weak imported, meaning it can be replaced if another exportable symbol is found with the same name.
A byte value sets the current location. The hex digit marked X is the selected segment 0 to 15. After the operation code is the added offset as a LEB128 number to the segment offset.
The byte sequence sets the location to the third segment load command address and adds 140 to the address.
Operation code to binds the current set location to the set symbol name and library ordinal. Increments the current set location by the size 4 bytes for a 32-bit Mach-O binary or increments the set address by 8 for a 64-bit Mach-O binary.
The byte sequence
Sets link library ordinal 1. Set location to segment number 2, and adds 140 to the current location. Looks for a symbol named Example in the selected library ordinal number. Operation code writes the symbol address and increments the current set address. The operation code after that sets the next symbol name to look for a symbol named Example2. Operation code writes the symbol address and increments the current set address.
The new format removes the repeated fields in the symbol table and makes the indirect symbol table obsolete.

Application main entry point

A load command starting with type is used to specify the address location the application begins at.
OffsetBytesDescription
04
44Command size
88Address location
168Stack memory size

If the segments/sections of the program do not have to be relocated to run, then the main entry point is the exact address location. This is only if the application segment addresses are added to an application base address of zero and the sections did not need any relocations.
The main entry point in a Mach-O loader is the program's base address plus the Address location. This is the address at which the CPU is set to begin running machine code instructions.
This replaced the old load command which varied by CPU type as it stored the state that all the registers should be at before the program starts.

Application UUID number

A load command starting with type is used to specify the universally unique identifier of the application.
OffsetBytesDescription
04
44Command size
816128-bit UUID

The UUID contains a 128-bit unique random number when the application is compiled that can be used to identify the application file on the internet or in app stores.

Minimum OS version

A load command starting with type is used to specify the minimum OS version information.
OffsetBytesDescription
04
44Command size
84Platform type
124Minimum OS version
164SDK version
204Number of tools used

The Platform type the binary is intended to run on are as follows:
ValuePlatform
0x00000001macOS
0x00000002iOS
0x00000003tvOS
0x00000004watchOS
0x00000005bridgeOS
0x00000006Mac Catalyst
0x00000007iOS simulator
0x00000008tvOS simulator
0x00000009watchOS simulator
0x0000000ADriverKit
0x0000000BvisionOS
0x0000000CvisionOS simulator

The 32-bit version value is read as a 16-bit value and two 8-bit values. A 32-bit version value of breaks down as which is 13 in value, then the next 8-bits is which is 2 in value, then the last 8-bits is which is zero in value giving a version number of 13.2.0v. The SDK version value is read the same way.
The number of tools to create the binary is a set of entries that are read as follows:
OffsetBytesDescription
04Tool type
44Tool version

The tool type values are as follows:
ValueTool type used
0x00000001CLANG
0x00000002SWIFT
0x00000003LD

The version number is read the same as OS version and SDK version.
With the introduction of Mac OS X 10.6 platform the Mach-O file underwent a significant modification that causes binaries compiled on a computer running 10.6 or later to be executable only on computers running Mac OS X 10.6 or later. The difference stems from load commands that the dynamic linker, in previous Mac OS X versions, does not understand. Another significant change to the Mach-O format is the change in how the Link Edit tables function. In 10.6 these new Link Edit tables are compressed by removing unused and unneeded bits of information; however, Mac OS X 10.5 and earlier cannot read this new Link Edit table format. To make backwards-compatible executables, the linker flag "-mmacosx-version-min=" can be used.

Other implementations

Mach-O parsers and editors

It is not uncommon for security researchers and others to work with Mach-O files from places other than a Mac computer. Programs that allow parsing or even editing the data structure of Mach-O are commonplace.
For the Ruby programming language, the ruby-macho library provides an implementation of a Mach-O binary parser and editor.

Mach-O runners

In theory, a program in Mach-O format could be run, by code that can load Mach-O images into memory, on operating systems other than the one for which the program was built, as long as a Mach-O binary image exists that matches the CPU type in the computer being used. Most desktop and laptop computers have x86 processors, meaning that a Mach-O with an x86 binary will be able to run the sections are loaded into memory. If the Mach-O has only ARM binaries, such as programs for iPhones or iPads, then it could only be run on a computer with a compatible ARM core ; otherwise, an emulation tool such as QEMU would have to be used you would have to change ARM instructions to equivalent x86 instructions using CPU emulation tools such as QEMU.
A practical problem with loading and directly executing a Mach-O is "undefined symbols": binaries generally do not exist in a vacuum and they call functions/methods from Mach-O binaries to work; a failure to find a symbol manifests in this error. Mach-O files for iPhone, macOS, watchOS, and tvOS each assume a different collection of libraries, causing incompatibility from this issue. These libraries need to either be present on the machine trying to execute the program, or be replaced by the Mach-O loader using either its own adapter functions or existing functions of the host operating systems. The library routines being called either would also have to provide the same application binary interface as the routines from the OS for which the binary was intended, or an adapter routine would have to be provided.
  • Some versions of NetBSD have had Mach-O support added as part of an implementation of binary compatibility, which allowed some Mac OS 10.3 binaries to be executed.
  • For Linux, a Mach-O loader was written by Shinichiro Hamaji that can load 10.6 binaries. As a more extensive solution based on this loader, the Darling Project aims at providing a complete environment allowing macOS applications to run on Linux.
  • ravynOS is an effort to bring a macOS-like userland to FreeBSD. The goal is to first provide source code compatibility, then binary compatibility.
See also Darwin operating system#Derived projects, which includes a few other efforts to achieve macOS/iOS binary compatibility. Even the efforts directly based on the same Darwin kernel required additional code to replace libraries and other components not open-sourced by Apple.