
Пролог
В прошлой статье я рассказывал про WIE — userspace-эмулятор, позволяющий через JIT запускать Windows PE64 бинарники на Apple Silicon. Проект развивался отлично, но в какой-то момент я уперся в непреодолимую стену — критическое падение производительности.
Реальный тест на сжатие проекта объемом 200+ МБ утилитой 7-Zip показал ужасающий результат: эмуляция работала в 80–100 раз медленнее, чем нативный macOS бинарник. Исправить этот провал без радикальной перестройки архитектуры и разрушения самой концепции WIE было невозможно. Разработку пришлось приостановить.
Но идея переносить софт между ОС без тяжелой эмуляции не давала мне покоя. Я начал искать решение, которое позволило бы избавиться от JIT и запускать код нативно.
И я его нашел. Имя ему Kakehashi.
Darling: забытый «брат Wine» и его архитектурный тупик
Когда заходит речь о запуске софта для macOS на Linux, первым всегда вспоминают Darling. Это амбициозный проект, который пытается стать для экосистемы Apple тем же, чем Wine стал для Windows. На бумаге концепция выглядит монументально: воссоздать окружение Darwin, транслируя вызовы Mach-ядра в системные вызовы Linux.
Но почему за годы разработки Darling так и не стал стандартом для DevOps-пайплайнов и CI/CD? Дело в фундаментальном архитектурном выборе, который завел проект в тупик.
Ловушка пространства ядра (Kernel Space)
Чтобы сэмулировать специфичное поведение Mach-ядра, потоков, IPC и Mach-портов, Darling использует кастомный модуль ядра Linux (LKM). Это решение мгновенно ставит крест на использовании утилиты в современной серверной автоматизации:
Docker-несовместимость: Из соображений безопасности ни один облачный провайдер не разрешит загрузить кастомный модуль ядра хоста из контейнера.
Угроза безопасности: Любая уязвимость в модуле ядра Darling — это мгновенный Root-доступ к хост-машине.
Ад поддержки: При каждом обновлении ядра Linux модуль норовит сломаться, требуя постоянной пересборки.
Архитектурный и технологический долг
Мало проблем с пространством ядра — Darling исторически создавался под x86_64 архитектуру старых Mac. Проект до сих пор не смог полноценно перестроиться под современную реальность Apple Silicon (ARM64).
Чтобы сэмулировать экосистему macOS, авторы пытаются воспроизвести фреймворки Apple «в лоб». Внутрь затянули огромное количество тяжелых сторонних зависимостей: от open-source кусков кода Apple до проектов GNUStep и Cocotron (древних попыток воссоздать графический стек Cocoa API).
В результате Darling превратился в монолитного монстра, запертого на bare-metal машинах с полными root-правами. Он оказался слишком неповоротливым для облаков.
Философия Kakehashi: Чистый Userspace и Lazy Stubbing
Kakehashi выбирает принципиально другой путь — Wine-way на максимальной скорости:
Полный отказ от модулей ядра: Мы не эмулируем Mach-ядро на уровне LKM.
Свободная лицензия: Легковесный userspace-транслятор распространяется под лицензией Apache 2.0 (никакого GPL от Darling).
Никакого графического мусора: Мы не тащим внутрь GNUStep или Cocotron. Вместо эмуляции тяжелых
.frameworkизнутри, Kakehashi использует тактику Flat Fallback и Lazy Catch-All. Вызовы к закрытым библиотекам Apple перехватываются на самом верхнем уровне ABI и заворачиваются в компактные Rust/C-мосты (stubs).
Кастомный загрузчик kh-loader парсит Mach-O заголовки, мапит секции бинарника в память Linux один к одному (identity-mapped guest VA), подсовывает ему кастомную libSystem.B.dylib и передает управление на точку входа LC_MAIN.
Гостевой код выполняется нативно на процессоре ARM64. Когда программа пытается выполнить системный вызов BSD, наша библиотека перенаправляет его через быстрый ассемблерный мост на функцию khbsd_hypercall, которая переключает контекст прямо в хостовый kh-runtime на Rust. [1]
Никакого root-доступа. Никаких ядерных модулей. Kakehashi изначально проектируется как легковесная CLI-утилита, которая запустится на любой стандартной Ubuntu и внутри изолированного Docker-контейнера.
Как устроен рантайм
Весь процесс запуска macOS-приложения на Linux разбит на четыре изолированных компонента (крейта): kh-cli(интерфейс командной строки), kh-loader (загрузчик бинарников), kh-runtime (обработка сисколлов и логика памяти) и kh-libsystem (наша гостевая библиотека).
Загрузка Mach-O и Identity-Mapping памяти
Поскольку в Linux нет встроенного загрузчика для формата macOS, за дело берется крейт kh-loader. Он парсит заголовки Mach-O (как чистые thin arm64, так и fat-контейнеры), поднимает базовые сегменты (__TEXT, DATA, LINKEDIT), разбирает зависимости вроде LC_LOAD_DYLIB и строит план отображения сегментов в памяти.
Виртуальная память гостя проецируется на адресное пространство хоста строго один в один: guest VA == host VA. Благодаря этому гостевой указатель на буфер является валидным указателем для хост-системы. Сегменты размещаются через обычный mmap хоста с флагами MAP_FIXED. Главный плюс такого подхода — полное отсутствие расходов на копирование данных «гость \(\leftrightarrow \) хост» на каждый байт, что критично для сетевого трафика или сжатия тяжелых архивов.
Freestanding libSystem.B.dylib
Вместо системных библиотек Apple гостю подсовывается изолированная clean-room dylib-библиотека. Она написана на Rust с небольшими C-вставками для функций с переменным числом аргументов (вроде printf / snprintf).
Это не полный ABI настоящей libSystem, а необходимый минимум под конкретный софт (7zz, curl, Git). Если гость вызывает то, чего ещё нет, он натыкается на заглушку (named missing trampoline) — первый реальный вызов логируется в рантайме, а не «тихо врет». Вот пример малой части из того, что уже реализовано:
Файловый POSIX:
read,write,open,close— обёртки над Darwin-номерами сисколлов, которые переводит рантайм.Собственный Heap: Арена на 64 МиБ + анонимный
mmapдля крупных блоков.Потоки: Модель 1:1, где гостевой pthread мапится на реальный поток Linux-хоста через
bsdthread_createи схему синхронизацииpark/wake(футексы).Сеть и DNS:
getaddrinfoперенаправляется на host-helper, который делает DNS-запросы через сетевой API хоста.
Мост Hypercall и трансляция сисколлов
В macOS системные вызовы выполняются через инструкцию процессора svc #0x80. Но Linux её не понимает. На основном производственном пути Kakehashi использует механизм Hypercall: загрузчик прошивает слот функции _kh_bsd_hypercall адресом ассемблерной заглушки kh_hypercall_entry. Вызовы идут через быстрый прямой прыжок bl в пользовательский рантайм.
Внутри kh_hypercall_entry происходит следующая последовательность действий:
Восстановление хост-TLS: Восстанавливается хостовый регистр
TPIDR_EL0. Гость держит там свой TLS и___error, но хост-код Rust и glibc с гостевым указателем жить не могут.Переключение стека: Поток переходит на изолированный
host alt stack(512 КиБ на поток), чтобы не замусорить стек гостя.Сохранение NEON: Полностью сохраняются и восстанавливаются регистры процессора
Q0–Q31и регистры состоянийFPCR/FPSR. Воркеры сжатия 7-Zip активно используют векторные инструкции, и малейшая потеря данных здесь рушит многопоточность.Диспетчеризация: Вызывается Rust-обработчик, который переводит Darwin BSD-номер сисколла в эквивалент Linux и возвращает результат.
Для отладки оставлен и запасной путь: если гость бьет «голый» svc, рантайм патчит его в инструкцию brk, перехватывает через SIGTRAP ядра Linux и обрабатывает вызов.
Ключевые технические решения
Изолированное дерево ФС (Bottle)
Чтобы гостевые бинарники не пытались ломать системные каталоги Linux, Kakehashi собирает «бутылку» (bottle) — каталог с привычной для macOS раскладкой (/usr, /bin, /private/etc, /Volumes/linux). Абсолютные гостевые пути режутся внутрь этой директории.
Путь /Volumes/linux — это мост к реальной ФС хоста. Так можно не трогать системные каталоги Linux, класть freestanding libSystem.B.dylib в гостевой /usr/lib и ставить утилиты вроде 7zz в /usr/local/bin. CA-сертификаты для HTTPS при вызове kh bottle ensure не вендорятся в git: берется host trust store хоста, либо скачивается актуальный Mozilla-бандл с curl.se.
Потоки 1:1 и корректный join
Каждый гостевой pthread_create порождает реальный host-поток. Чтобы избежать состояния гонки при очистке памяти (когда вызывающий поток pthread_join делает munmap стека до того, как завершающийся поток успел безопасно выйти), используется строгий протокол:
Гостевой поток пишет результат в структуру потока и вызывает сисколл
bsdthread_terminate.Рантайм уходит в hypercall и переключается на стек хоста.
И только находясь на хост-стеке, рантайм выставляет флаг
done = 1и будит джойнеров черезfutex-wake.Только после этого
pthread_joinможет безопасно освобождать гостевой стек.
TLS без Rust-макроса thread_local!
Гость использует аппаратный регистр TPIDR_EL0 как базу для GuestTls. На входе в hypercall нужен быстрый возврат host-значения регистра.
Использовать стандартный макрос thread_local! из стандартной библиотеки Rust здесь нельзя: он сам под капотом неявно завязан на регистр TPIDR_EL0 хоста. В момент, когда там находится гостевой TLS, вызов макроса пытается прочитать данные по гостевым смещениям и роняет процесс в SIGSEGV.
Поэтому host TPIDR_EL0 и альтернативный стек (alt_top) зеркалируются прямо внутри структуры гостевого TLS (offset 24 — host_tpidr, offset 32 — alt_top). Горячий путь hypercall читает эти зеркала прямыми ассемблерными инструкциями, не обращаясь к системным вызовам хоста вроде gettid на каждый сисколл.
Автономная распаковка Xcode Command Line Tools
Чтобы запустить Apple Git, его нужно сначала где-то взять. Тащить проприетарные тяжелые блобы в Git-репозиторий проекта — плохая идея. Использовать внешние утилиты вроде p7zip для ковыряния системных архивов Apple внутри контейнеров — ненадежно.
Поэтому был реализован реализовал полностью автономный, чистый конвейер развертывания окружения по команде kh install xcode-tools:
Парсинг каталогов Apple: Утилита идет на публичный swcdn.apple.com (откуда качает обновления сама macOS), парсит
.sucatalogи находит актуальный стабильный пакет Command Line Tools. Никаких Apple ID, авторизаций и cookies.Скачивание: Оригинальный
.pkgскачивается напрямую с серверов Apple в кэш хоста.Собственный конвейер распаковки: В рантайм встроен чистый разборщик цепочки архивов Apple «на лету» без привлечения стороннего софта. Мы берем архив, разбираем формат XAR, распаковываем поток pbzx, прокидываем его через odc cpio и аккуратно раскладываем готовое дерево CLT (
usr/bin/gitи зависимости) прямо внутрь нашей изолированной бутылки.
О производительности и ограничениях
Несмотря на заметный выигрыш по сравнению со старой эмуляцией WIE, околонативного времени выполнения (wall-clock time) на сценариях с большим количеством системных вызовов здесь нет. Постоянное пересечение границы гость-хост дает о себе знать: налог на вызов накладывается на общее число сисколлов.
Для примера я взял дерево из примерно 8 тысяч мелких файлов общим объемом около 240 МиБ и прогнал тест сжатия (Ubuntu aarch64, команда 7zz -t7z -m0=lzma2 -mx=5 -mmt=4):
Окружение |
Время выполнения |
Отношение |
Нативный Linux 7zz |
~22.5 сек |
~×1.0 |
Darwin 7zz под Kakehashi (kh) |
~118 сек |
~×5.2 |
При этом на задачах класса «мало файлов, много чистых вычислений» разрыв падает и держится в районе ×1.1–1.2. Однако для реальной экономики CI/CD-пайплайнов идеальный паритет с macOS-раннером и не требуется. Главное — это сама возможность стабильно гонять Darwin CLI на дешевом Linux arm64.
Сознательные рамки проекта на данный момент:
Только архитектура aarch64: Поддержка устаревшего x86_64 не является целью.
Минимум графики и подписей: GUI, утилиты
codesignи интерфейс Xcode UI вынесены за рамки приоритетов.Curl как сетевой мост: Гостевой
curlиспользовался как сетевой шлюз для обкатки сокетов, а не для достижения полного паритета по флагам с macOS-версией.-
Размер страниц памяти: В дизайн рантайма изначально заложена поддержка страниц как 4 КиБ (стандартные Linux-контейнеры), так и 16 КиБ (системы класса Asahi Linux). Основным полигоном для тестирования остаются Linux aarch64, Docker и виртуалки UTM.
PS: 16 КиБ страницы еще не протестированы, поэтому гарантировать правильную работу не могу
Что уже работает

На текущий момент рантайм стабильно проходит тесты внутри Docker (Colima) и на чистом железе в виртуальных машинах UTM (Ubuntu Server aarch64):
Apple Git (из Command Line Tools) — наш главный текущий триумф. Базовые команды контроля версий (
git init,git add,git commit) предварительно работают. Проект честно держит многопроцессныйfork, транслирует абсолютные пути и мапит вызовыgetuid/getcwd, обходя проверки безопасностиsafe.directory. Абсолютная надежность на всех сценариях еще доказывается, но ядро системы уже стабильно.Нативный
curl— предварительно полностью закрытый сетевой milestone. На Docker-инфраструктуре скриптdocker-curl-options.shуспешно прогоняет матрицу из 200+ команд (10 тиров тестов: от cookies, параллельной загрузки и ретраев до работы с UNIX-сокетами черезAF_UNIX). Рантайм берет на себя non-blocking сокеты, работу сpollи подменяет проверки сертификатовAppleSecTrustна хостовой OpenSSL с пробросом актуального CA-бандла прямо внутрь бутылки.7-Zip (
7zz) — наш основной многопоточный бенчмарк. На текущий момент успешно проходят более 95% всех команд и флагов утилиты. Проект стабильно держит жесткий стресс-тест сжатия в 4 потока (-mmt=4 -mx=5), что подтверждает корректность сохранения NEON-регистров процессора и TLS при переключении контекста в гиперколлах.
Как попробовать
Для запуска Kakehashi вам понадобится среда Linux aarch64 (bare-metal, вторая ОС, виртуальная машина и т.п.) либо Docker / Colima на Apple Silicon.
из crates.io
cargo install kakehashi kh bottle ensure kh install 7zip kh install curl kh install xcode-tools
Или из git:
git clone https://github.com/wie-project/kakehashi cd kakehashi cargo install --path crates/kh-cli
Развертывание окружения
kh bottle ensure kh install 7zip kh install curl kh install xcode-tools
Быстрый старт через Docker
Если вы находитесь на Mac с Apple Silicon и хотите быстро проверить рантайм в Linux-контейнере, в репозитории подготовлены smoke-скрипты. Они автоматически соберут Docker-образ, развернут бутылку и запустят тесты:
# Например ./scripts/docker-curl-options.sh ./scripts/docker-7zz.sh
Примечание: Скрипты в директории scripts/ выполняют роль быстрых smoke-обвязок для базовой проверки стабильности, а не прогона полной матрицы системных вызовов macOS. То есть не ждите готовый полноценный образ для тестирования
Текущие результаты и вместо заключения
Если оценивать проект Kakehashi на сегодняшний день, то рантайм уже вышел за рамки простой концепции. Вот что получается по фактам и цифрам:
Apple Git: Базовые команды контроля версий (
init,add,commit) предварительно работают на Linux ARM64. Архитектурный скелет для поддержки многопроцессности иforkсобран, хотя абсолютную надежность на всех сценариях еще только предстоит доказать.Darwin curl: Скрипт автоматического тестирования
docker-curl-options.shуспешно прогоняет матрицу из 200+ команд (10 тиров тестов: от обработки cookies и параллельной загрузки до ретраев и UNIX-сокетов).7-Zip (
7zz): Успешно проходят более 95% всех команд и флагов оригинальной macOS-утилиты, включая тесты многопоточного сжатия.Автономия: Рантайм умеет сам ходить на сервера обновлений Apple, забирать пакеты и распаковывать цепочку архивов
XAR -> pbzx -> cpioпрямо внутри контейнера без привлечения внешнего софта хоста.
Эпилог
Как и в прошлый раз, скажу: проект экспериментальный, а код генерируется ИИ. Но повторю важную вещь: если не контролируешь этот процесс напрямую, на выходе легко получить код, который со временем просто превратится в мусор. Просто лежа на диване и нажимая одну кнопку, ничего не добьешься — для тестирования, нахождения нестандартных ситуаций и банальных идей все еще нужен человек.
Не знаю, брошу ли я этот проект через какое-то время, но пока он идет значительно легче и быстрее, чем старый JIT-эмулятор WIE.
Спасибо за внимание! Буду рад фидбеку и конструктивной критике в комментариях.
Ссылки проекта:
Исходный код: https://github.com/wie-project/kakehashi
Лицензия: Apache-2.0
Комментарии (12)

foss22
01.08.2026 15:21Буду рад фидбеку и конструктивной критике
Начните со списка программ, которых пока нету под линукс. Небольшой каталог типа wine с зелеными и красными клетками. Комьюнити наполнит.
Для облегчения запуска тестов желательно понизить версию, а не как сейчас: error: cannot install package kakehashi 0.1.7, it requires rustc 1.97 or newer, while the currently active rustc version is 1.92

VladislavKalinkin Автор
01.08.2026 15:21Сделал. Минимальная версия 1.92 теперь. Clippy ошибок не показывает. Надеюсь ничего не сломается. Спасибо за комментарий

V1tol
01.08.2026 15:21Если прям заморачиваться, то можно попробовать прогнать проект через cargo-msrv.

VladislavKalinkin Автор
01.08.2026 15:21Большое спасибо! Очень полезный инструмент! Как оказалось минимальная совместимая версия - 1.88. Дальше мешают только plist, time, time-core. В общем теперь это будет минимальной версией
vadimr
Если имеется в виду запуск на компьютерах архитектуры ARM, отличающихся от Mac, то большинство специфических маковских программ так работать вряд ли смогут в силу использования специфических возможностей Apple Silicon (GPU, Neural Engine и т.д.) А переносимую программу легче просто перекомпилировать под Linux.
VladislavKalinkin Автор
Спасибо за комментарий. Вы правы про перекомпиляцию (смысла в очередном curl, git и 7zip нету конечно), но просто они использовались как стресс тесты, своеобразные вехи. Как бы мне хотелось бы добиться именно уникальных проприетарных бинарников, например для сборки apple приложений. Просто если сразу пойти на такое, будет тяжелее будто. Проще по ступенькам снизу вверх идти. А про GPU, NE вы правы, но я не уверен что в 99% процентах CLI утилит они не понадобятся, кроме может очень редких исключений. Да и GPU в теории можно было-бы сделать - типа moltenvk наоборот, но цель такое делать сейчас не стоит. Еще раз спасибо
vadimr
Мне довольно сложно представить приложение для Mac, которое не было бы завязано на маковскую аппаратную архитектуру и при этом не существовало бы в версии для Linux в силу возможности перекомпиляции (хотя наоборот – бывает: скажем, ffmpeg умеет работать с Silicon GPU, и это на порядок повышает его производительность).
Какое, например, приложение вы в идеале хотели бы исполнять таким образом? Если это система сборки приложений Apple, то подозреваю, что она не будет работать без подсистемы безопасности Mac.
VladislavKalinkin Автор
Также согласен, но опять же вы сами упомянули ненадобность того, что можно самому скомпилировать (тот же
ffmpeg). Конечно, в тех случаях, где критически важны аппаратные особенности Apple Silicon GPU, скорость на стороннем железе без нативных блоков будет проседать. Но задача Kakehashi не в том, чтобы прыгнуть выше головы и выдать скорость на уровне натива, а в том, чтобы добиться стабильной работы с терпимой производительностью для CLI-задач. Кроме того, если программа использует GPU на Mac — то в 100% случаев под капотом это Metal, эмуляция которого сейчас в проекте даже не планируется.Проблемы с криптографией и безопасностью мне тоже не кажутся непреодолимыми. Если Kakehashi некорректно транслирует системные вызовы безопасности — гостевая программа либо откажется идти дальше, либо упадет (как и в случае с любыми другими недописанными функциями).
Тот факт, что я тестирую проект внутри Linux-контейнера на Mac (где физически присутствует Apple Silicon), действительно может скрывать какие-то скрытые зависимости в некоторых ситуациях. Но для отлова таких нюансов как раз и существуют подробное логирование сисколлов, баг-репорты и пул-реквесты.
vadimr
Тут дело не только в сисколлах, но и в системе команд.
VladislavKalinkin Автор
Я правильно понимаю что вы про вещи типа AMX говорите? Если да, то возможно это также обойти, но точно не знаю. Так как я не тащу фреймворки apple, а пишу работоспособные заглушки, то в теории можно такие сигналы перехватывать и обходить. Конечно может немного производительность проседать, но основная цель - работоспособность. Если вы про что-то другое говорите, то уточните пожалуйста.
vadimr
Да, в первую очередь AMX.
VladislavKalinkin Автор
Когда до него дойду - придется разбираться. В любом случае если не удастся, буду сам виноват. Спасибо