Build and inspect universal Mach-O binaries with llvm-lipo
You will use Ubuntu's llvm-lipo-18 to inspect Mach-O architecture slices, combine thin files into a universal binary, and extract or replace a slice. The installed package is LLVM 18.1.3 from llvm-18. Allow about fifteen minutes if you already have compatible Mach-O inputs.
The route
Jump straight to the step you need, or tick off Done means at the end.
This tool works on Mach-O files, including universal binaries. It is not a general-purpose linker and it does not combine Linux ELF executables. The examples below leave the original inputs alone, but output options can overwrite a path you choose, so check destinations before running them.
1. Check the installed command
Start with read-only checks. No elevated privileges are needed unless your input directory is unreadable:
$ command -v llvm-lipo-18
/usr/bin/llvm-lipo-18
$ llvm-lipo-18 -version
Ubuntu LLVM version 18.1.3
Optimized build.
The manpage describes the command as llvm-lipo, while this distribution installs the versioned executable as llvm-lipo-18. Use the name returned by command -v in scripts so it is clear which toolchain is being used.
Checkpoint: confirm the option spelling on the machine where the script will run:
$ llvm-lipo-18 -help
The installed help lists -archs, -info, -verify_arch, -create, -thin, -extract, -replace, -output, -segalign and -fat64. The local manpage has the same core operations, although its generated command summary is shorter.
2. Inspect a file before changing anything
Use -archs for one file when you only need its architecture names:
$ llvm-lipo-18 -archs /path/to/input-macho
x86_64 arm64
For a universal file, the names identify its slices. For a thin file, expect one architecture. The exact output depends on the input. Unknown architectures are printed in the form unknown(CPUtype,CPUsubtype).
Use -info when comparing one or more files:
$ llvm-lipo-18 -info /path/to/app-arm64 /path/to/app-universal
Non-fat file: /path/to/app-arm64 is architecture: arm64
Architectures in the fat file: /path/to/app-universal are: x86_64 arm64
The tool reports universal files first and thin files afterwards when both kinds are supplied. Treat the displayed paths and architectures as the checkpoint before selecting inputs for a build.
3. Verify an architecture in a script
-verify_arch takes one input file followed by the architecture names to test. It returns status 0 only when the requested architectures are present, and status 1 otherwise:
$ llvm-lipo-18 -verify_arch x86_64 arm64 /path/to/app-universal
$ status=$?
$ printf 'verification status: %s\n' "$status"
verification status: 0
Keep the status immediately. Do not parse the output of -archs when an exit status is the real requirement. A non-zero result is a failed compatibility check, not a reason to add a slice automatically.
4. Create a universal binary
Use -create with at least one input and an explicit -output path. Each input should be a thin Mach-O file for a different architecture:
$ llvm-lipo-18 -create \
/path/to/app-x86_64 \
/path/to/app-arm64 \
-output /path/to/app-universal
Verify the result without modifying it:
$ llvm-lipo-18 -info /path/to/app-universal
Architectures in the fat file: /path/to/app-universal are: x86_64 arm64
Warning: do not point -output at an input you still need. Use a new destination first, then replace an old file only after -info and a test launch have succeeded. If the destination already exists, stop and decide whether it is disposable; the command is not a backup system.
5. Extract a thin file for one architecture
To make a thin output from a universal input, use -thin followed by the architecture and give a new output path:
$ llvm-lipo-18 -thin arm64 /path/to/app-universal \
-output /path/to/app-arm64-extracted
$ llvm-lipo-18 -archs /path/to/app-arm64-extracted
arm64
This is a file operation, not a source-code rebuild. Keep the universal original until the extracted file has passed the checks that matter to your deployment.
6. Replace one slice carefully
-replace takes the architecture to replace and a thin input file, then writes a new universal output:
$ llvm-lipo-18 -replace arm64 /path/to/app-arm64-new \
/path/to/app-universal \
-output /path/to/app-universal-new
$ llvm-lipo-18 -info /path/to/app-universal-new
The replacement file must contain the architecture named in the command. Compare the new file with the original before switching a release or service to it. Recovery is simple when you use a new destination: discard the new file and keep using app-universal. Do not remove the old binary until the replacement has been tested and archived according to your normal release process.
7. Handle alignment and common failures
With -create or -replace, -segalign can set the alignment for a named architecture. Its alignment argument is hexadecimal and must be a power of two:
$ llvm-lipo-18 -create \
-segalign arm64 0x4000 \
/path/to/app-x86_64 /path/to/app-arm64 \
-output /path/to/app-aligned
Only add this when a consumer or build requirement specifies the alignment. It is not a generic performance switch. If the command rejects an input, first check that the file is Mach-O and readable, that each architecture is present, and that the output path is writable:
$ file /path/to/app-arm64
$ test -r /path/to/app-arm64 && echo readable
$ test -w /path/to && echo destination-writable
Do not use sudo to repair a wrong architecture, malformed Mach-O file or missing output directory. Elevated privileges are only relevant when normal filesystem permissions genuinely block a read or write, and changing ownership or permissions is a separate administrative decision.
Done means
llvm-lipo-18reports the expected LLVM 18.1.3 version.-infoor-archsconfirms the architecture slices before any build operation.-verify_archis used for scripted compatibility checks, with its exit status captured immediately.- Create, thin and replace operations write to deliberate new paths until verification succeeds.
- Original Mach-O inputs remain available for recovery.