- PVSM.RU - https://www.pvsm.ru -
Здравствуйте. Меня зовут Владислав, и я хочу рассказать о своем экспериментальном проекте на ранней стадии. Называется он WIE (Wie Is Emulator), что отсылает к великим.
WIE — это исследовательский эмулятор пользовательского режима (userspace) для запуска 64-битных Windows‑приложений (PE64) на архитектуре macOS Apple Silicon. Проект написан на Rust 1.97, а в качестве бэкенда компиляции используется Cranelift для трансляции x86-64 инструкций на лету в нативный ARM64.
Главный Proof of Concept на сегодня: эмулятор успешно крутит под честным JIT реальный консольный Windows 7-Zip (7za.exe), выполняя сжатие и распаковку с полным совпадением SHA-256 хэшей. А также для тестирования было разработано более 20 тестовых EXE файлов на различные задачи: От математики и долгих циклов, заканчивая проверкой ввода и корректности многопоточности
32-битные и 16-битные приложения
Никакой полной истории от Windows от 95 до 11. Только Windows 10 и совместимые PE64 со старых версий
Мягкая трансляция памяти (Soft‑translate): принципиально не использую Wine‑style identity mapping (mmap(addr = guest_va)). Гостевые адреса изолированы и всегда транслируются через софтверные таблицы регионов, арены, структуры VAD и PageMap.
«Да ладно, на сайт пришел очередной вайбкодер!» — скажете вы, и в принципе я с вами соглашусь. Значительная часть кода проекта действительно сгенерирована нейросетями, и я этого не скрываю. Но должен сразу уточнить: этот проект не из тех, где «запустил 12 ИИ‑агентов на ночь и получил убийцу Wine».
Я сам очень не люблю подход, когда человек, вообще не разбирающийся в технологиях, слепо верит, что нейросеть создаст шедевр, пока он лежит на диване.
За время работы над разными приватным проектами я научился глубоко читать и понимать Rust: какие конструкции за что отвечают, как взаимодействовать с компилятором и банально как правильно подходить к работе с нейросетями. Ведь всем «нейрокодерам» известно — без жесткой и правильной инструкции ИИ моментально уйдет не туда.
Также я бьюсь за чистоту кода. Мне важно досконально понимать, что происходит в системе. Когда в проекте скапливается куча legacy‑мусора, контролировать логику становится тяжело не только мне, но и самой нейросети.
И, естественно, unsafe. Главный мрак Rust. Практически весь unsafe, который есть в WIE, локализован строго в блоке ядра CPU. Есть пара исключений в блоке WinAPI, но на этом всё. В остальном проект глобально настроен на безопасный код.
Надеюсь, я хоть минимально убедил вас в том, что я полностью контролирую архитектуру и слежу за проектом.
Лично мне никогда не приходилось мучаться с Wine, так как лично мне Windows приложения не нужны были. Я не геймер, так что главная причина использовать отпадает, ничего специфичного не надо было.
Но вот в один момент меня что‑то потянуло на ретроигры, а вернее довольно интересную сферу — Ромхакинг. А точнее Super Mario World и легендарный редактор Lunar Magic. Я когда начал изучать эту тему очень сильно удивился: Ради того чтобы переделать оригинальный ROM файл за примерно 2 десятилетия сообщество придумало такое количество способов расширить файл и настолько перерабатывать игру, что просто жуть.
Мне стало интересно как устроен Lunar Magic авторства FuSoYa, который является одним из главнейших столбов. Кратко: есть полно утилит на отдельную часть (музыка, графика, кастомные предметы) и они разрозненны, а Lunar является сборщиком упаковщиком с понятным графическим интерфейсом. Но есть деталь... Практически все ромхакерское ПО исключительно под Windows и только пара есть и на macOS. Конкретно Lunar у меня спокойно в Wine запустится, но другие у многих вызывают проблемы. Но сначала именно Lunar меня заинтересовал именно из‑за скрепления и понимания всего.
И я предпринял попытку нейросетевого реверс‑инжиниринга и портирования редактора на Rust... Что тут сказать: эта попытка хоть и неплохо начиналась, но с треском рухнула на этапе рендеринга. Чтобы вы понимали масштабы бедствия: более трех тысяч функций в EXE‑файле весом в несколько мегабайт.
Однако меня не отпускало. Тянуло попробовать запустить это на Mac без Wine. Я искал разные способы, но только один показался более‑менее реалистичным: написать заглушки под WinAPI, а сам гостевой EXE‑файл скормить в эмулятор Unicorn Engine. С этого всё и началось. Я генерировал и генерировал заглушки, которые просто пропускали выполнение по инициализации, но почти ничего общего с реальными функциями kernel32 и прочих библиотек не имели.
В какой‑то момент я подумал, что занимаюсь глупостью. То есть я трачу время (пусть код пишу и не я, но, как уже говорил, я жестко контролирую процесс генерации) и добавляю кучу заглушек ради одного нишевого приложения, которое лично мне в будущем никак не пригодится. После этого проект я забросил.
Но долго без дела сидеть не смог — не получалось найти занятие по душе. Мне хотелось создать что‑то уникальное, ведь рынок IT, как известно, перенасыщен стандартными решениями. Проверяя свои старые репозитории в попытке найти то, что можно реанимировать, я всё‑таки вернулся к этой идее. Но на этот раз я полностью поменял перспективу. Вместо попытки запустить один конкретный Lunar Magic я принял решение замахнуться на невозможное: переработать проект так, чтобы он мог запускать самые разные EXE‑файлы. По сути, сделать аналог Wine.
Но Wine — это не эмулятор! Это транслятор системных вызовов. А у тебя под капотом именно эмуляция процессора
Так и родилось название WIE — Wie Is Emulator.
Unicorn сначала казался неплохим. Он был удобен в плане анализа и просто проект изначально под него строился. Но теперь две проблемы которая перекрывает все хорошее: CPU и скорость.
Как‑бы ты не пытался снижать потребление CPU, ты всегда будешь больше тратить чем Wine. Я конечно начал попытку оптимизаций. В какой‑то момент ИИ предлагал варианты оптимизаций один из вариантов был переход на Cranelift + iced‑x86. Я заинтересовался, так как он написал что он легковеснее, JIT движок быстрее чем у Unicorn и вообще предназначен изначально под веб. Решил переходить на него.
Замена Unicorn на Cranelift правда‑сказать сожгла недельные лимиты. И даже не столько замена, сколько попытка ускорить и оптимизировать минимально. В какой то момент на тестах еще сохранившегося в проекте Lunar Magic, который уже проходил цикл инициализации полностью в один момент скорость cranelift превзошла Unicorn почти на 2 секунды.
Вообще кстати они оба со временем ускорялись все больше и больше. Начиналось вроде где‑то с 20 секунд, а закончилось от семи до десяти (если я не ошибаюсь). После этого я решил что пора вычеркивать lunar из проекта: Все переменные, адреса, функции подстроенные только под него, а также Unicorn Engine, который я успешно на Cranelift и iced‑x86. Вот после этого момента можно и сказать, что проект начал принимать облик нынешнего варианта.
Если отбросить дальнейший путь разработки и посмотреть на WIE (Wie Is Emulator) сегодня, то это модульный, легковесный эмулятор пользовательского режима, написанный на Rust. Его архитектура разделена на четыре ключевых блока, которые работают в тесной связке:
wie‑pe (Загрузчик): Берет гостевой 64-битный Windows‑бинарник, парсит его структуру, маппит секции в изолированную хост‑память и полностью переписывает Таблицу импортов (IAT). Все системные вызовы Windows подменяются на кастомные виртуальные адреса‑ловушки.
wie‑winapi (Прослойка окружения): Та самая поверхность WinAPI, которую мы итеративно воссоздаем под нужды приложений. Чтобы не прыгать в контекст хоста по малейшему поводу, базовые и часто вызываемые функции (например, работа с ошибками вроде GetLastError или SetLastError) имеют инлайновые заглушки прямо в гостевой памяти.
wie‑cpu (Движок компиляции): Сердце проекта. С помощью iced-x86 декодируются инструкции x86-64, собираются в базовые блоки и передаются JIT‑бэкенду Cranelift, который на лету превращает их в нативный ARM64-код, оптимизированный под Neon‑векторы Apple Silicon.
wie‑cli (Командный пункт): Место откуда идет управление, слежка, шпионаж... Проще говоря просто CLI блок с тремя командами. trace, run и inspect
Почему Soft‑translate, а не подход Wine? Он использует подход Identity Mapping, когда гостевой адрес пытается напрямую отобразиться на аналогичный адрес хоста через mmap(addr = guest_va).
В WIE реализована мягкая трансляция памяти (Soft‑translate). Гостевое пространство полностью изолировано. Каждый адрес гостя — это виртуальная абстракция, которая принудительно транслируется через софтверные таблицы регионов, арены, структуры VAD (Virtual Address Descriptor) и PageMap. Да, это накладывает свои накладные расходы, но дает тотальный контроль над правами страниц и безопасностью выполнения.
Эмуляция многопоточности — это отдельная тема. В WIE гостевые потоки, создаваемые через CreateThread, маппятся на реальные системные потоки хоста (macOS pthreads) в соотношении 1:1. Для синхронизации используется глобальный мьютекс процессора (CpuEngine process mutex). Когда гостевой поток уходит в законное ожидание (например, через WaitForSingleObject), блокировка движка CPU освобождается, предотвращая холостой простой хост‑системы.
Нет! Это очень ранний прототип. Но это не значит что он ничего не умеет. Как говорил в процессе разработке было создано более 20 EXE. Далее будут примеры:
#include <windows.h>
void entry(void) {
volatile unsigned long long counter = 0;
volatile unsigned long long limit = 100000000ULL;
if (counter < limit) {
do {
volatile unsigned long long tmp = counter ^ 0xDEADBEEF;
tmp = tmp * 3 + 1;
(void)tmp;
counter++;
} while (counter < limit);
}
ExitProcess(0);
}
Это был первый крупный тест. До оптимизации он проходил за 9 секунд, теперь:
time ./target/release/wie-cli run micro-exes/out/long_loop.exe
run_micro: path=micro-exes/out/long_loop.exe
cpu_backend: jit
entry=0x0000000140001000 initial_rsp=0x000000002000eff8
events=1 termination=ExitProcess { code: 0 }
[ 0] KERNEL32.dll!ExitProcess handled=true ret=None
run_micro: ok exit=0
./target/release/wie-cli run micro-exes/out/long_loop.exe 0.31s user 0.01s system 99% cpu 0.325 total
Процессор на 99 процентах, но это потому‑что выполняется полезная работа. В тестах ожидания, процессор падает практически до нуля.
#include <windows.h>
#define WORKERS 4
#define ITERS 512
typedef LONG(WINAPI *PFN_Inc)(LONG volatile *);
static CRITICAL_SECTION g_cs;
static volatile LONG g_atomic = 0;
static volatile LONG g_under_cs = 0;
static volatile LONG g_slots[WORKERS];
static HANDLE g_start_event;
static HANDLE g_done_event;
static volatile LONG g_ready = 0;
static PFN_Inc g_inc;
static DWORD WINAPI worker(LPVOID param) {
int id = (int)(ULONG_PTR)param;
int i;
HANDLE heap;
LONG marker = 0x1000 + id;
if (g_inc(&g_ready) == WORKERS) {
SetEvent(g_done_event);
}
WaitForSingleObject(g_start_event, INFINITE);
heap = GetProcessHeap();
for (i = 0; i < ITERS; i++) {
void *p;
g_inc(&g_atomic);
EnterCriticalSection(&g_cs);
g_under_cs++;
p = HeapAlloc(heap, 0, 64);
if (p) {
*((volatile LONG *)p) = marker;
HeapFree(heap, 0, (LPVOID)p);
}
LeaveCriticalSection(&g_cs);
g_slots[id] = marker;
}
ExitThread(0);
return 0;
}
void entry(void) {
HANDLE threads[WORKERS];
DWORD wait;
int i;
HMODULE k;
const LONG expect = (LONG)(WORKERS * ITERS);
for (i = 0; i < WORKERS; i++) {
g_slots[i] = 0;
}
k = GetModuleHandleA("KERNEL32.dll");
if (!k) {
k = GetModuleHandleA("kernel32.dll");
}
if (!k) {
ExitProcess(6);
}
g_inc = (PFN_Inc)GetProcAddress(k, "InterlockedIncrement");
if (!g_inc) {
ExitProcess(6);
}
InitializeCriticalSection(&g_cs);
g_start_event = CreateEventA(NULL, TRUE, FALSE, NULL); /* manual */
g_done_event = CreateEventA(NULL, TRUE, FALSE, NULL);
if (!g_start_event || !g_done_event) {
ExitProcess(6);
}
for (i = 0; i < WORKERS; i++) {
threads[i] = CreateThread(NULL, 0, worker, (LPVOID)(ULONG_PTR)i, 0, NULL);
if (threads[i] == NULL || threads[i] == INVALID_HANDLE_VALUE) {
ExitProcess(1);
}
}
wait = WaitForSingleObject(g_done_event, 30000);
if (wait != WAIT_OBJECT_0) {
ExitProcess(6);
}
SetEvent(g_start_event);
for (i = 0; i < WORKERS; i++) {
wait = WaitForSingleObject(threads[i], INFINITE);
if (wait != WAIT_OBJECT_0) {
ExitProcess(2);
}
CloseHandle(threads[i]);
}
if (g_atomic != expect) {
ExitProcess(3);
}
if (g_under_cs != expect) {
ExitProcess(4);
}
for (i = 0; i < WORKERS; i++) {
if (g_slots[i] != (LONG)(0x1000 + i)) {
ExitProcess(5);
}
}
DeleteCriticalSection(&g_cs);
CloseHandle(g_start_event);
CloseHandle(g_done_event);
ExitProcess(0);
}
Результат:
time ./target/release/wie-cli run micro-exes/out/mt_stress.exe
run_micro: path=micro-exes/out/mt_stress.exe
cpu_backend: jit
entry=0x00000001400010e0 initial_rsp=0x000000002000eff8
events=17 termination=ExitProcess { code: 0 }
[ 0] kernel32.dll!getmodulehandlea handled=true ret=Some(1627389952)
[ 2] kernel32.dll!initializecriticalsection handled=true ret=Some(0)
[ 3] KERNEL32.dll!CreateEventA handled=true ret=Some(2147483649)
[ 4] KERNEL32.dll!CreateEventA handled=true ret=Some(2147483650)
[ 5] KERNEL32.dll!CreateThread handled=true ret=Some(2147483651)
[ 6] KERNEL32.dll!CreateThread handled=true ret=Some(2147483652)
[ 7] KERNEL32.dll!CreateThread handled=true ret=Some(2147483653)
[ 8] KERNEL32.dll!CreateThread handled=true ret=Some(2147483654)
[ 10] KERNEL32.dll!SetEvent handled=true ret=Some(1)
[ 12] kernel32.dll!closehandle handled=true ret=Some(1)
[ 14] kernel32.dll!closehandle handled=true ret=Some(1)
[ 16] kernel32.dll!closehandle handled=true ret=Some(1)
[ 18] kernel32.dll!closehandle handled=true ret=Some(1)
[ 19] kernel32.dll!deletecriticalsection handled=true ret=Some(0)
[ 20] kernel32.dll!closehandle handled=true ret=Some(1)
[ 21] kernel32.dll!closehandle handled=true ret=Some(1)
[ 22] KERNEL32.dll!ExitProcess handled=true ret=None
run_micro: ok exit=0
./target/release/wie-cli run micro-exes/out/mt_stress.exe 0.05s user 0.07s system 163% cpu 0.074 total
Как вы видите: 163% CPU, то есть больше одного потока, 74 миллисекунды всего, а exit 0
Предупреждение: Поверхность WinAPI для 7-Zip огромна, и проект находится в стадии наполнения, поэтому проверен далеко не весь функционал утилиты. Для демонстрации мы берем оригинальный, немодифицированный консольный Windows‑бинарник 7za.exe (из официального пакета 7-Zip Extra x64) и запускаем его в изолированном окружении («бутылке»). На данный момент эмулятор стабильно запускает пять команд:
a (сжатие) — упаковка файлов в формат.7z, проверенная как в один поток, так и в многопоточных режимах (-mmt2 / -mmt4).
x(распаковка) — извлечение файлов с полным сохранением путей.
l (листинг) — чтение структуры и вывод содержимого архива.
i (инвентаризация) — проверка доступных системе кодеков и хэшеров.
help — вывод списка команд.
Finished `release` profile [optimized] target(s) in 0.05s
blob.bin 262144
zsh: command not found: #
bottle_root: /var/folders/16/y3g8vpb14db0rg6g632rl1_m0000gn/T//wie-7za-bottle-71495
guest_args: ["--help"]
7-Zip (a) 26.02 (x64) : Copyright (c) 1999-2026 Igor Pavlov : 2026-06-25
Usage: 7za <command> [<switches>...] <archive_name> [<file_names>...] [@listfile]
<Commands>
a : Add files to archive
b : Benchmark
d : Delete files from archive
e : Extract files from archive (without using directory names)
h : Calculate hash values for files
i : Show information about supported formats
l : List contents of archive
rn : Rename files in archive
t : Test integrity of archive
u : Update files to archive
x : eXtract files with full paths
<Switches>
-- : Stop switches and @listfile parsing
-ai[r[-|0]][m[-|2]][w[-]]{@listfile|!wildcard} : Include archives
-ax[r[-|0]][m[-|2]][w[-]]{@listfile|!wildcard} : eXclude archives
-ao{a|s|t|u} : set Overwrite mode
-an : disable archive_name field
-bb[0-3] : set output log level
-bd : disable progress indicator
-bs{o|e|p}{0|1|2} : set output stream for output/error/progress line
-bt : show execution time statistics
-i[r[-|0]][m[-|2]][w[-]]{@listfile|!wildcard} : Include filenames
-m{Parameters} : set compression Method
-mmt[N] : set number of CPU threads
-mx[N] : set compression level: -mx1 (fastest) ... -mx9 (ultra)
-o{Directory} : set Output directory
-p{Password} : set Password
-r[-|0] : Recurse subdirectories for name search
-sa{a|e|s} : set Archive name mode
-scc{UTF-8|WIN|DOS} : set charset for console input/output
-scs{UTF-8|UTF-16LE|UTF-16BE|WIN|DOS|{id}} : set charset for list files
-scrc[CRC32|CRC64|SHA256|SHA1|XXH64|*] : set hash function for x, e, h commands
-sdel : delete files after compression
-seml[.] : send archive by email
-sfx[{name}] : Create SFX archive
-si[{name}] : read data from stdin
-slp : set Large Pages mode
-slt : show technical information for l (List) command
-snh : store hard links as links
-snl : store symbolic links as links
-sni : store NT security information
-sns[-] : store NTFS alternate streams
-so : write data to stdout
-spd : disable wildcard matching for file names
-spe : eliminate duplication of root folder for extract command
-spf[2] : use fully qualified file paths
-ssc[-] : set sensitive case mode
-sse : stop archive creating, if it can't open some input file
-ssp : do not change Last Access Time of source files while archiving
-ssw : compress shared files
-stl : set archive timestamp from the most recently modified file
-stm{HexMask} : set CPU thread affinity mask (hexadecimal number)
-stx{Type} : exclude archive type
-t{Type} : Set type of archive
-u[-][p#][q#][r#][x#][y#][z#][!newArchiveName] : Update options
-v{Size}[b|k|m|g] : Create volumes
-w[{path}] : assign Work directory. Empty path means a temporary directory
-x[r[-|0]][m[-|2]][w[-]]{@listfile|!wildcard} : eXclude filenames
-y : assume Yes on all queries
run_micro: path=real_exes/7za.exe
cpu_backend: jit
entry=0x00000000004e9ac0 initial_rsp=0x000000002000eff8
events=243 termination=ExitProcess { code: 0 }
run_micro: ok exit=0
bottle_root: /var/folders/16/y3g8vpb14db0rg6g632rl1_m0000gn/T//wie-7za-bottle-71495
guest_args: ["a", "-mmt2", "-bd", "C:\App\blob.7z", "C:\App\blob.bin"]
7-Zip (a) 26.02 (x64) : Copyright (c) 1999-2026 Igor Pavlov : 2026-06-25
Scanning the drive:
1 file, 262144 bytes (256 KiB)
Creating archive: C:Appblob.7z
Add new data to archive: 1 file, 262144 bytes (256 KiB)
Files read from disk: 1
Archive size: 479 bytes (1 KiB)
Everything is Ok
run_micro: path=real_exes/7za.exe
cpu_backend: jit
entry=0x00000000004e9ac0 initial_rsp=0x000000002000eff8
events=3323 termination=ExitProcess { code: 0 }
Как вы знаете, в любом крупном проекте всегда есть баги: явные, скрытые и архитектурные. А при активном использовании нейросетей шанс поймать неочевидную проблему возрастает в разы. Я уверен, что по мере дальнейшей разработки и увеличения покрытия WinAPI обязательно найдутся новые скрытые проблемы в цепочках JIT или синхронизации потоков, но тем интереснее будет их искать и исправлять.
Проект всё еще находится на очень ранней стадии и ни в коем случае не претендует на статус полноценной «замены Wine», но как исследовательский прототип, доказывающий жизнеспособность концепции, я считаю, что он полностью удался.
В заключение хочу добавить немного личного контекста. Мне 15 лет, и WIE — это мой сугубо исследовательский pet‑проект. Я создал его для того, чтобы глубоко, «руками» прочувствовать системное программирование, разобраться в устройстве JIT‑компиляторов, Cranelift, ассемблере и работе операционных систем с памятью. Согласен, что называть такую разработку нормальной и полноценно обучающей, но по крайней мере мне интересно.
Буду рад любой конструктивной критике архитектуры, советам от старших коллег по оптимизации хелперов памяти и, конечно же, пулл‑реквестам!
Ссылка на репозиторий GitHub: Vladislav‑Kalinkin/wie [1]
Спасибо за внимание! Жду вас в комментариях.
Автор: VladislavKalinkin
Источник [2]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/wine/455339
Ссылки в тексте:
[1] Vladislav‑Kalinkin/wie: https://github.com/Vladislav%E2%80%91Kalinkin/wie
[2] Источник: https://habr.com/ru/articles/1061806/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1061806
Нажмите здесь для печати.