Now that you have darwinOS built and running in a VM, the smallest meaningful thing you can do is run a userland program against it. This guide walks through a hello-world in C, links it against the darwinOS SDK, gets it into the guest, and runs it.
The point isn’t the program itself — it’s to confirm your build, your SDK, and your ABI are all aligned.
Write the program
Anywhere on your host, create hello.c:
#include <stdio.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
printf("Hello from PID %d on darwinOS.\n", getpid());
return 0;
}Nothing Darwin-specific — it’s plain POSIX. The ABI you link against does all the work.
Cross-compile against the SDK
The darwinOS build produces an SDK tarball alongside the rootfs:
build/arm64-debug/sdk.tar.gzExtract it once:
mkdir -p ~/.darwinos-sdk
tar -xzf build/arm64-debug/sdk.tar.gz -C ~/.darwinos-sdkThen compile with the bundled wrapper:
~/.darwinos-sdk/bin/darwinos-clang \
--target=arm64-darwin \
-o hello \
hello.cThe wrapper sets --sysroot, -isysroot, and the library search paths so your binary links against the darwinOS libSystem — not your host’s libc.
Confirm the result:
file hello
# hello: Mach-O 64-bit executable arm64Copy the binary into the guest
The simplest path is to mount the guest’s root filesystem on the host and drop the binary in:
sudo mount -o loop build/arm64-debug/rootfs.img /mnt/darwinos
sudo cp hello /mnt/darwinos/usr/local/bin/
sudo umount /mnt/darwinosThen boot the VM as usual. For repeated iteration, this gets tedious — see Next steps for faster options.
Run it
Inside the guest:
hello
# Hello from PID 47 on darwinOS.If you see that line, you have a working toolchain, a working libSystem, a working dyld, and a working launchd + shell chain. Everything else is an extension of this.
What just happened
In sequence:
darwinos-clangproduced a Mach-O binary with aLC_LOAD_DYLIBentry for/usr/lib/libSystem.B.dylib- The shell forked a child process and invoked the kernel’s
execveimplementation - The kernel loaded the Mach-O, mapped dyld into the child, and jumped to dyld’s entry point
- dyld resolved libSystem and wired up
printf,getpid, and friends via lazy binding - dyld jumped to
main, which syscalled into the BSD personality for thewritethatprintfeventually issued
Next steps
For faster iteration, set up a shared 9p filesystem between host and guest so you can recompile and run without copying:
-virtfs local,path=./share,mount_tag=host0,security_model=passthrough,id=host0Mount it in the guest with
mount -t 9p host0 /mnt.Debug the kernel — attach lldb when something goes wrong.
Write a kext — the kernel-space equivalent of this guide.