La ejecución de software diseñado para un sistema operativo en otro, especialmente cuando ambos comparten la misma arquitectura de CPU subyacente, presenta un problema fundamental de compatibilidad de ABI (Application Binary Interface). Históricamente, esto se ha abordado mediante emulación completa (QEMU), virtualización (VMware, VirtualBox) o capas de traducción de llamadas al sistema (Wine, WSL). Kakehashi aborda este problema para binarios ARM64 de macOS en Linux aarch64, una combinación cada vez más relevante con la proliferación de hardware ARM en servidores y dispositivos.
El desafío radica en la diferencia de las ABIs del kernel (syscalls), las bibliotecas de usuario (libc, libSystem) y los formatos de binarios (Mach-O vs. ELF). A diferencia de la emulación de CPU, Kakehashi aprovecha la compatibilidad de la arquitectura ARM64 para ejecutar el código de usuario directamente, delegando solo las interacciones con el sistema operativo a una capa de traducción. Esto permite un rendimiento significativamente mejor que la emulación completa, aunque introduce una sobrecarga en el límite de las llamadas al sistema.
La relevancia actual de este enfoque se ve impulsada por la creciente adopción de procesadores ARM en centros de datos y la necesidad de ejecutar cargas de trabajo de desarrollo y CI de macOS en entornos de bajo costo. Los runners de CI de macOS son notablemente más caros que sus contrapartes de Linux, creando un incentivo económico para soluciones como Kakehashi que desacoplan la ejecución del binario de la plataforma de origen.
Arquitectura del Sistema
Kakehashi opera como un cargador y un entorno de ejecución para binarios Mach-O de macOS en Linux aarch64. El componente principal, kh-loader, es responsable de analizar el formato Mach-O, mapear las secciones del binario en el espacio de direcciones del proceso Linux y preparar el entorno de ejecución. Esto incluye la resolución de símbolos y la carga de una versión "freestanding" de libSystem.B.dylib, que es una implementación minimalista de las funciones de la biblioteca estándar de macOS, compilada específicamente para la arquitectura aarch64-apple-darwin y embebida en el runtime de Kakehashi.
El kh-runtime gestiona la memoria, las trampas (traps) y la traducción de las llamadas al sistema BSD. Cuando un binario de macOS realiza una llamada al sistema (syscall) de Darwin, Kakehashi intercepta esta llamada y la traduce a la syscall equivalente de Linux. Este proceso implica un cambio de contexto (TLS switch, alternancia de stack, guardado/restauración de registros NEON) que introduce una latencia. La comunicación entre el guest y el host se facilita a través de un mecanismo de "hypercall" que, aunque no es una hypercall en el sentido de virtualización, actúa como una interfaz optimizada para el paso de argumentos y resultados de syscalls.
El sistema de archivos se gestiona mediante un "bottle" que mapea rutas del sistema de archivos de macOS (como /usr/local/bin/7zz o /usr/lib/libSystem.B.dylib) a ubicaciones en el sistema de archivos del host Linux. Además, se proporciona un puente /Volumes/linux/… que permite a los binarios guest acceder directamente al sistema de archivos del host Linux, facilitando la interacción con herramientas y datos externos. La configuración de OpenSSL y los bundles de CA también se gestionan a través de este mapeo, permitiendo que aplicaciones como curl funcionen con HTTPS.
Ejecución de Binario Darwin en Linux Aarch64
- 1 kh run <binary> Usuario invoca Kakehashi con un binario Mach-O de macOS.
- 2 kh-loader Parse Mach-O, mapea secciones en memoria del proceso Linux.
- 3 kh-runtime Carga `libSystem.B.dylib` freestanding, inicializa entorno.
- 4 Guest Code (Darwin) El binario Darwin ejecuta instrucciones nativas ARM64.
- 5 Darwin Syscall El binario guest invoca una syscall de macOS (ej. `openat`).
- 6 kh-runtime (Trap/Hypercall) Intercepta la syscall, cambia contexto (TLS, stack, NEON).
- 7 Linux Syscall Traduce y ejecuta la syscall equivalente de Linux (ej. `openat`).
- 8 Resultado El resultado de la syscall Linux se devuelve al guest.
| Capa | Tecnología | Justificación |
|---|---|---|
| orchestration | Docker/Colima | Entorno de ejecución para el host Linux aarch64, facilitando el despliegue y la prueba de Kakehashi. |
| compute | Rust | Lenguaje de implementación principal para Kakehashi, elegido por su seguridad de memoria y rendimiento. Rust 1.88+ |
| storage | Filesystem Bridging | Mapeo de rutas de sistema de archivos guest (macOS) a rutas de host (Linux) para acceso a datos y binarios. /Volumes/linux/… → host FS |
| security | OpenSSL / CA Bundle | Manejo de certificados SSL/TLS para aplicaciones guest como `curl`, utilizando bundles de CA del host o descargados. |
Trade-offs
Ganancias
- ▲▲ Costo de runners de CI
- ▲ Disponibilidad de runners de CI
Costes
- ▲ Rendimiento de ejecución (wall-time)
- ▲ Compatibilidad con frameworks de Apple
Fundamentos Teóricos
El concepto de traducción de llamadas al sistema y la virtualización a nivel de sistema operativo se remonta a trabajos fundamentales en sistemas operativos y emulación. Proyectos como WINE (Wine Is Not an Emulator) para ejecutar aplicaciones de Windows en Linux, o más recientemente WSL (Windows Subsystem for Linux), son ejemplos prominentes de este paradigma. Estos sistemas se basan en la interceptación de llamadas al sistema del sistema operativo guest y su traducción a las llamadas al sistema del host.
Desde una perspectiva académica, el diseño de Kakehashi se relaciona con la investigación en "binary translation" y "dynamic binary instrumentation". Aunque Kakehashi no realiza traducción dinámica de instrucciones (JIT), su enfoque en la traducción de ABI a nivel de syscall se alinea con los principios de adaptar un entorno de ejecución para un binario foráneo. Los desafíos de rendimiento asociados con la sobrecarga de las llamadas al sistema son un tema recurrente en la investigación de microkernels y sistemas distribuidos, donde la comunicación entre procesos y la gestión de contextos son factores críticos para la latencia y el throughput.