Self-hosted service
wie-project/kakehashi avatar
wie-project/kakehashi

Kakehashi translates macOS binaries to Linux without a JIT, and its README opens with an ending

Userspace macOS translation layer for Linux ARM64

435 stars10 forksRustApache-2.0

At a glance

What is it?
A Rust workspace that loads Darwin Mach-O binaries on Linux aarch64, maps a freestanding libSystem, and translates BSD syscalls so real command line tools run natively on the CPU. The engineering is genuinely interesting, and the first paragraph of the README is the author telling everyone they are stopping work on it.
Who is it for?
This fits someone studying how a translation layer can avoid emulation, or with a very specific need to run one Apple command line tool on an ARM64 Linux host without a virtual machine. It does not fit anyone planning to depend on it, because the author has stated they will no longer maintain it and has invited forks instead.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 16 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 1, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The README opens by announcing the author is stopping maintenance

Before the project description there is a callout, and it is the most consequential thing in the repository.

The author thanks people for supporting the project with stars and then states they will no longer be able to maintain it. Three reasons are given: too many things to keep track of, the need to manually transfer macOS binaries, and most importantly a lack of issues and pull requests that would reveal what interests people.

The notice is careful not to blame anyone, and it acknowledges the difficulty of maintaining such a project and that it uses AI in development. It then invites three things: continuing support through pull requests, creating forks, or proposing new projects, since the author has none at present.

The repository metadata is consistent with that rather than contradicting it. It is not archived, the last push is dated 2026-09-17, and there are no GitHub releases at all. Installation is through cargo rather than a published release artefact, so there is no last-good binary to fall back on.

That framing changes how to read everything below. This is a body of work offered as a starting point, not a dependency.

The bottle path is hard-coded and must sit on the internal drive

Running macOS binaries needs a guest filesystem root, called the bottle, and it is not configurable.

The location is stated as strictly fixed and cannot be changed:

text
~/.local/share/kakehashi/bottle/

The storage constraint is sharper than the path. Because of filesystem and path mechanics, the bottle must reside on the host's internal system drive. External drives and non-native mounts such as exFAT are called out as strictly unsupported.

Populating it is manual. Four directories are copied out of a macOS 26 or newer installation: /bin, /sbin, /usr/bin and /usr/lib/zsh, each into the matching path under the bottle. The base utility set, which includes rm, zsh and codesign, is around 256 MB uncompressed, with /usr/lib/zsh adding roughly 1 MB for the interactive shell and its modules such as zle.so.

Two libraries cannot be copied at all, because they live in the dyld shared cache rather than as files: libpcre.0.dylib and libiconv.2.dylib. The runtime works around that by aliasing them to libSystem, which is what the kh bottle ensure command does.

The rest of the environment is bootstrapped by a single command, kh install xcode-tools, which pulls and unpacks the official Apple Command Line Tools including clang, git and the SDK.

Live execution needs Linux aarch64, and dry-load works anywhere including macOS

There are two modes, and the difference between them decides whether you can try the project at all.

Live execution, the kh run command, is Linux aarch64 only. The listed environments are bare metal, UTM, Colima, Docker and OrbStack. Dry-load, kh run --dry-load, runs on any host, and macOS is named explicitly as one where it works.

That split exists because dry-load does not execute guest code. It loads the Mach-O and its dylibs and stops short of the run, which is why the same binary can inspect a macOS binary while sitting on a Mac.

The dry-load mode is also what continuous integration uses. The container image's default command runs a probe through it rather than executing anything, and the image itself is built on an amd64 Debian base while live execution requires aarch64. The image is a test path, not a deployment path.

Two path behaviours are worth knowing. Guest execution uses the host's current working directory, so relative paths resolve where you expect. And guest paths under /Volumes/linux map directly to the host root, which is how a guest program reaches the host filesystem.

The workspace denies unsafe Rust for a loader that runs Mach-O binaries

The lint configuration is the first thing worth reading in Cargo.toml, because it is an unusual combination for this kind of project.

unsafe_code is set to deny across the workspace, alongside unused_extern_crates, unused_imports, unused_must_use and let_underscore_drop. missing_docs is allowed. rust_2018_idioms is denied with a lowered priority.

A translation layer that loads foreign binaries, maps a freestanding dylib and intercepts syscalls would normally be written in a great deal of unsafe Rust. This one is not, and the design explains why.

There is no JIT and no instruction emulator. The guest ARM64 code runs natively on the host CPU, and the runtime only intervenes at syscalls, threads and faults. The boundary work is therefore about threads, signals and translation rather than about executing untrusted instruction bytes, which is a much smaller surface.

The four crates separate that surface. kakehashi produces the kh binary that users install. kh-loader parses, maps, binds and executes the Mach-O. kh-runtime owns memory, traps, BSD syscalls, the bottle, threads, and embeds libSystem.B.dylib. kh-libsystem is the freestanding dylib source and is marked as targeting aarch64-apple-darwin only.

That last crate is a member of the workspace but deliberately excluded from the default members, because it cannot build on Linux.

kh-libsystem is three directories, and two of them are marked soft

The guest-side library has its own internal layout, and the directory names tell you what is load-bearing.

core/ holds syscalls, errno, the heap, process handling and host helpers. This is the layer the runtime depends on to exist at all.

dylib/ holds the C-compatible surface: libsystem_c, pthread, libcurl, libc++ and libz. These are the shims a portable command line binary links against, which is why a tool like curl can run without being recompiled.

frameworks/ holds CoreFoundation, Security and CoreServices, and the listing marks all three as soft. That single word is the most honest admission in the repository: the framework layer is present in shape but not in behaviour, which lines up with the list of things not yet claimed.

The rest of the workspace is ordinary and well pinned. goblin handles object file parsing, clap with derive provides the command line, serde and serde_json handle data, plist handles property lists, and flate2 plus liblzma with a static feature handle decompression, which is what unpacking the Command Line Tools needs.

Networking is notable for one reason: rustls is configured with the ring provider and tls12, and the comment beside it says it is host-side TLS for the freestanding libcurl, for guest file descriptors, and is not linked into the freestanding dylib itself.

Continuous integration denies clippy warnings and excludes the Apple crate twice

The Dockerfile is a full pipeline rather than a convenience build, and the order of its steps matters.

The builder stage is rust:1.88-bookworm with build-essential and pkg-config installed, and it adds the clippy component with rustup. Sources are copied in, then one command does all three checks:

bash
cargo clippy --workspace --exclude kh-libsystem --all-targets -- -D warnings
cargo test --workspace --exclude kh-libsystem
cargo build -p kakehashi --release

Clippy warnings are errors, across all targets, and the Apple-only crate is excluded from both linting and testing. Only then is the release binary produced and copied out.

The runtime stage is debian:bookworm-slim and it carries three things: the kh binary into /usr/local/bin, the embedded freestanding guest libSystem.B.dylib into the kh-runtime resources path, and the compiled clang probe into the tests directory. It then runs kh --help during the build, which is a smoke test that the binary links.

The default command is the tell:

text
run --dry-load tests/clang-probe/write_exit

A dry-load of a probe that writes an exit code. The image exists so a pipeline can verify the loader, not so a robot can run a tool.

The reference machine is an M1 with 8 GB, and 7zz runs at about 1.24 times native

Performance is described as a boundary cost rather than an emulation cost, and the framing is consistent with the design.

Guest code runs natively, so what is measured is the number of boundary crossings multiplied by the cost of each one. TLS, the alternate stack, NEON register state and dispatch are named as the things that happen at the boundary.

One number is given. A multi-file 7zz run is approximately 1.24 times the time of a native Linux 7zz.

The compiler is called out separately. Nested Apple clang pays a process-start tax on every hop to cc1 and ld, which is why a compile is slower than the arithmetic suggests, and the distinction the project draws is between a tax and a correctness gap.

Wall-clock parity with native macOS is explicitly not the primary continuous integration goal, and the roadmap is where that trade-off is tracked.

The reference hardware is narrow enough to be worth knowing. Building kh-libsystem happens on a MacBook Pro M1 from 2020 with 8 GB of RAM and a 256 GB SSD running macOS 26.6.1. Testing runs Ubuntu 26.04 live-server on arm64 inside UTM on that same machine. Requirements are Rust 1.88 or newer, Linux aarch64 for live runs, and page sizes of 4 KiB and 16 KiB, the latter described as Asahi-class.

Four tools are verified, and the list of what is not claimed is longer

The verified section is deliberately narrow, with one command pair per tool.

7-Zip runs through kh run 7zz with an extract and a test invocation. curl runs with kh run curl for a version check and a silent fetch to a file. Apple git from the Command Line Tools does a version check and a shallow clone of a public repository. Apple clang does a version check and compiles hello.c to an object file.

All four run as native ARM64 with the runtime intervening only at syscalls, threads and faults, and they are verified on Docker, Colima, OrbStack and UTM.

The not-claimed list is the more useful half: the full curl feature surface, the real Apple Security.framework, git LFS and svn, the GUI, codesign, and the full macOS application stack.

That last omission has a practical consequence given the setup instructions. codesign is listed among the binaries copied into the bottle, and also listed as not claimed, so a signed macOS application is not going to launch here.

What is claimed is a command line surface. The stated goal for nested compiler processes is that they pay a process-start tax, not that they are correct in every configuration.

Editorial conclusion

This fits someone studying how a translation layer can avoid emulation, or with a very specific need to run one Apple command line tool on an ARM64 Linux host without a virtual machine. It does not fit anyone planning to depend on it, because the author has stated they will no longer maintain it and has invited forks instead. Two things to weigh before starting. First, getting a working environment means manually copying roughly 256 MB of macOS system directories out of a macOS 26 or newer installation into a hard-coded path on the host's internal drive, which is manual work with a legal and licensing dimension attached. Second, the verified surface is four tools, and codesign, GUI applications and the full macOS app stack are explicitly not claimed. The last push is dated 2026-09-17.

Frequently asked questions

What does Kakehashi do?

It is a userspace translation layer that runs macOS ARM64 binaries on Linux aarch64. It loads Darwin Mach-O binaries, maps a freestanding libSystem, translates BSD syscalls at the boundary, and lets guest ARM64 code execute natively on the host CPU with no JIT and no instruction emulator.

Is Kakehashi still maintained?

No. The README opens with a notice that the author will no longer be able to maintain the project, citing the number of things to track, the manual transfer of macOS binaries, and a lack of issues and pull requests. The repository is not archived, the last push is dated 2026-09-17, and there are no releases. Forks and new projects are invited.

Where does the Kakehashi bottle have to live?

At ~/.local/share/kakehashi/bottle/, a path described as strictly fixed and unchangeable. Due to filesystem and path mechanics it must reside on the host's internal system drive, and external drives or non-native mounts such as exFAT are strictly unsupported.

How do I set up the Kakehashi guest environment?

Manually copy /bin, /sbin, /usr/bin and /usr/lib/zsh from a macOS 26 or newer installation into the matching bottle directories, which is around 256 MB plus about 1 MB for zsh. Then run kh install xcode-tools to pull the Apple Command Line Tools. Two libraries in the dyld shared cache cannot be copied and are aliased to libSystem instead.

Can I test Kakehashi on macOS?

Not live execution. kh run requires Linux aarch64, on bare metal or inside UTM, Colima, Docker or OrbStack. Dry-loading with kh run --dry-load works on any host including macOS, which is what the container image's default command does.

How fast is Kakehashi compared to native Linux?

Guest code runs natively, so the cost is boundary crossings rather than emulation. A multi-file 7zz run is approximately 1.24 times the time of a native Linux 7zz, and nested Apple clang adds a process-start tax on each hop to cc1 and ld. Wall-clock parity with native macOS is not the primary CI goal.

Official sources

  1. Issues
  2. License: Apache-2.0
  3. README
  4. wie-project/kakehashi on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/wie-project-kakehashi.svg)](https://hysenlabs.com/projects/wie-project-kakehashi)