Mach-O executable file format
A macOS/iOS Mach-O executable, dylib or bundle.
- MIME type
- application/x-mach-binary
- Extensions
- .dylib, .bundle, .o
- Category
- Executables & bytecode
Magic-number signature
| 0x00000000 | CFFAEDFE | ···· | Mach-O 64-bit LE magic |
About the Mach-O executable format
Mach-O is the native binary format of macOS and iOS, and unlike ELF it puts nothing in fixed-size tables: after a seven- or eight-field header comes a stream of variable-length load commands, each announcing its own type and size, and the loader walks them in order. That design is why one file can carry segment maps, the dynamic linker's path, the minimum OS version, the entry point and a code signature without any of them having a reserved slot. The magic doubles as an endianness and width marker: CF FA ED FE is a 64-bit little-endian image, CE FA ED FE the 32-bit one, and the byte-reversed forms belong to the big-endian PowerPC era.
Header layout, byte by byte
| Offset | Length | Field | Meaning |
|---|---|---|---|
| 0 | 4 | magic | CF FA ED FE on disk for a 64-bit little-endian image — the value 0xFEEDFACF read little-endian. CE FA ED FE is the 32-bit form (0xFEEDFACE), and FE ED FA CF / FE ED FA CE are the big-endian variants a PowerPC binary carries. Four bytes therefore tell you the format, the word size and the byte order at once. |
| 4 | 4 | cputype | 0x01000007 for x86-64 and 0x0100000C for arm64: the high bit 0x01000000 means 64-bit, and the low bits are the base architecture (7 for x86, 12 for ARM). A 32-bit build of the same architecture drops that flag, so 7 alone is i386 and 12 alone is 32-bit ARM. |
| 8 | 4 | cpusubtype | The variant within the architecture, with the top bits used as capability flags. Apple silicon binaries commonly show 0x80000002 here — subtype 2, arm64e, with the high flag set — which is the field that separates an arm64e system binary from an ordinary arm64 app. |
| 12 | 4 | filetype | 1 MH_OBJECT (a .o), 2 MH_EXECUTE, 6 MH_DYLIB, 8 MH_BUNDLE, plus MH_DSYM for a debug companion. This is the byte-level answer to whether the thing you are holding is a program, a library or a loadable plug-in, since .dylib, .so and no extension at all are all used in practice. |
| 16 | 4 | ncmds | How many load commands follow the header. A trivial command-line tool has roughly fifteen to twenty; a large framework has many more, and each one is walked in sequence because there is no index. |
| 20 | 4 | sizeofcmds | Total bytes occupied by all load commands. Walking each command's own cmdsize and summing must land exactly on this number — the strongest single integrity check the format offers, and the first thing to run on a file you suspect has been edited. |
| 24 | 4 | flags | Attribute bits: whether the image is a PIE, whether two-level namespace lookup is used, whether it has no undefined symbols. A typical modern executable reads something like 0x00200085 here. |
| 28 | 4 | reserved | Present only in the 64-bit header and always zero. It exists purely to align what follows to eight bytes, which is why the first load command sits at 32 in a 64-bit file and at 28 in a 32-bit one. |
| 32 | variable | First load command | Every load command starts with a four-byte cmd and a four-byte cmdsize, so a reader can skip one it does not understand. LC_SEGMENT_64 (0x19) usually comes first, and the commands that matter for triage are LC_LOAD_DYLIB, LC_MAIN, LC_CODE_SIGNATURE and LC_BUILD_VERSION. |
Telling it apart from its neighbours
| Easily confused with | How to tell |
|---|---|
| Mach-O (universal/fat) | A universal binary is not a Mach-O file with extra fields — it is a wrapper holding several complete Mach-O images. It opens CA FE BA BE (big-endian) instead of CF FA ED FE, and the thin binary's magic appears further in, at the offset each fat_arch entry names. Reading a fat file as thin gives a nonsense cputype straight away. |
| Java class file | The famous collision, but only with the fat form: a Java class file also begins CA FE BA BE. The next four bytes settle it — a class file's major_version (bytes 6-7) is 45 or above, while a fat binary's nfat_arch is a small count whose upper half is therefore zero. |
| ELF executable | Both use .o for objects and both call their shared libraries by a bare name, so the extension is no help; a .so built on Linux and a .dylib built on macOS look identical in a directory listing. Offset 0 decides: 7F 45 4C 46 is ELF, CF FA ED FE is Mach-O. Note that Mach-O's magic is stored byte-reversed on disk, so a hex dump reads CF FA ED FE while documentation writes 0xFEEDFACF. |
| Big-endian (CIGAM) Mach-O | Files for the PowerPC era store the same magic byte-swapped: FE ED FA CE and FE ED FA CF. Every field in the header and in every load command is then big-endian too, so a parser that recognises only the little-endian magics does not misread these — it correctly refuses them. |
How these files break
- Any in-place edit at all, on Apple silicon. arm64 code is signed even when the signature is ad-hoc, so changing a single byte in the text segment invalidates the hash in LC_CODE_SIGNATURE and the kernel kills the process on launch rather than running the modified code. Patching a Mach-O means re-signing it; there is no equivalent of quietly editing a byte in an ELF.
- Truncation. The load commands sit at the front and parse fine, so a partial download reports the right architecture and file type while segment data past the cut is missing. The check is walking the load commands and testing each segment's fileoff plus filesize against the actual file length.
- sizeofcmds no longer matching the commands. Inserting or removing a load command without fixing ncmds and sizeofcmds leaves a header that looks plausible and a walk that overruns into segment data — the reason a tool reports an absurd load-command type instead of an error message.
- A slice extracted from a fat binary with the wrong length. Copying out a slice using the fat header's offset but somebody else's size yields a file with a perfect Mach-O header and a truncated tail, which is the usual result of hand-carving architectures instead of using lipo.
- Text-mode transfer or a stray BOM. Standard for any binary: line-ending translation shifts everything after the first 0D 0A, and a prepended BOM pushes the magic off offset 0, at which point the file is unrecognisable even though every byte of code is still present.
Where the format stands today
Mach-O is what every macOS and iOS executable, framework, dylib, kernel extension and .o file is, and it is the only format Apple's loader accepts, so nothing has ever displaced it there. It is inherited from NeXTSTEP by way of Mach, which is why its vocabulary — load commands, segments and sections, dyld — differs from ELF's for the same ideas. In practice most shipped Mach-O files today are single-architecture arm64 images inside an app bundle; the universal form is what a developer builds during a transition and what Homebrew and Rosetta-era software still ship. For anyone inspecting bytes, the important consequence of Apple's code-signing rules is that Mach-O is effectively read-only: you can dissect it freely, but a modified copy will not launch until it is signed again.
Open Mach-O executable files on this site
Every one of these runs in your browser — the file never leaves your machine.
Related formats
Formats that are easy to confuse with Mach-O executable — compare their leading bytes.
| Format | First bytes | Extension |
|---|---|---|
| Mach-O (universal/fat) | CA FE BA BE | .dylib |
| ELF executable | 7F 45 4C 46 | .elf |
| PE (Windows executable) | 4D 5A | .exe |