Всем привет!

В этой статье я расскажу, почему у нас действительно получится заменить Wine, и что мы для этого делаем.

В прошлой статье я писал о простейшем способе создать и запустить импровизированный исполняемый файл, и на тот момент проект Vodka уже существовал на своей ранней стадии, поэтому у меня был план постепенно вводить читателя в эту тематику. Но с тех пор многое поменялось, поэтому теперь статьи будут посвящены тому, чего уже удалось достичь при разработке проекта.

С тех пор главным изменением стало то, что я познакомился с прекрасным человеком и глыбой‑разработчиком Александром Борисовым (@lastmac) создателем самого быстрого в мире парсера HTML — lexbor. Он стал куратором и принёс тот опыт, которого мне не хватало.

Мотивация проекта

Волею судьбы меня занесло в пару наших IT‑компаний, где мы занимались переходом с Windows на отечественные ОС семейства Linux, которые вы все прекрасно знаете. Конкретно я занимался импортозамещением программного обеспечения, которое существует и работает на Windows. Конечно, для этого мы использовали Wine, который приходилось постоянно дорабатывать под конкретное ПО, что создавало большие сложности и часто порождало непродуманные и неверные решения, а многие проблемы и вовсе не удалось победить.

Видя эти трудности, я захотел создать нечто, что позволит раз и навсегда переломить сложившуюся ситуацию, в которой большинство пользовательского и специализированного ПО остаётся непереносимым между различными ОС.

Именно для решения этой проблемы и разрабатывается Vodka, а идейно она описывается так:

Создать универсальный слой совместимости, чтобы избавить исполняемые файлы от «гражданства» в виде операционной системы.

Но это всё лирика, пора приступать к делу!

Наш подопытный кролик

Как минимальное доказательство работоспособности, соберём и исполним всеми любимый «Hello, world!» как исполняемый файл Windows на основе настоящих библиотек Microsoft — hello.exe, а получим его через mingw‑gcc.

Здесь всё как обычно, написали да собрали.

#include <stdio.h>

int main()
{
    printf("%s, %s!\n", "Hello", "World");

    return 0;
}

В чём сложность?

  • Загрузчик на ОС умеет запускать только определённый формат;

  • Форматы исполняемых файлов разные, и каждый имеет свои уникальные возможности;

  • У каждой ОС свой контракт между User Space и Kernel Space, API системных вызовов;

  • ОС имеют разный системный ABI, то есть используют отличающиеся соглашения о вызовах;

  • Различное представление ОС о том, какой информацией они должны делиться с ПО;

  • И многие другие нюансы разного уровня сложности.

Чтобы побороть эти несостыковки, в Vodka есть не только различный инструментарий, но и фундаментальные новшества.

Собственный загрузчик Vodka

Итак, сердце проекта, загрузчик Vodka, уже на данном этапе не уступает нативному загрузчику Linux по скорости и может служить его полноценной заменой в будущем. Он реализует весь необходимый функционал для подготовки файла к исполнению, включая, в том числе, отображение страниц, вычисление релокаций, поиск и загрузку зависимостей, заполнение TLS и shared‑данных, а также динамическое создание проксирующих функций для трассировки, конвертации ABI и подстановки заглушек на пути нереализованного функционала в нашем слое совместимости.

Архитектура с самого начала предполагает последующую модификацию загрузчика, чтобы имелась возможность дополнять и модернизировать его поведение. В такой функционал входит, например, возможность создавать runtime‑ссылки, которые перенаправляют пути в нужные пользователю места, что позволяет избавиться от Wine‑специфичного понятия префикса и напрямую «накладывать» пространство Windows на Linux.

Но одного загрузчика для запуска нам не хватит.

Новый формат — VDK

Формат исполняемого файла VDK создан с максимальной гибкостью и по принципу конструктора (насколько это возможно), так как в нашем видении уже пора отказаться от монолитности и идеи, что исполняемый файл это немодифицируемый артефакт, который нельзя пересобрать, отредактировать или изменить впоследствии.

Вот так формат выглядит на его первой версии
Вот так формат выглядит на его первой версии

Весь цимес в том, что внутрь него можно уместить, со всеми их фишками, всех трёх мастодонтов: ELF, PE и Mach‑O. И с такой возможностью, получается, что все они при дистилляции в формат VDK будут существовать как разный программный код, но в едином типе контейнера, и могут работать друг с другом на уровне формата.

Дистилляция и подкоманда distillate

Дистилляция в контексте проекта — преобразование контейнера файла в наш контейнер VDK. Мы просто разбираем имеющийся исполняемый файл на составляющие, убираем ненужную нам служебную ОС‑специфичную информацию и оставляем только полезные данные. Такая промежуточная структура в Vodka называется vdk_mash.

После, мы эту информацию формируем и упаковываем в наш формат. На первый взгляд может показаться, что это бессмысленное действие, но на самом деле это даёт нам инструментарий модификации исполняемого файла под единым API независимо от его происхождения, и позволяет избежать некоторых юридических нюансов. На всякий случай уточню, что дистилляция — это именно способ получения VDK, а не единственный путь к исполнению.

И вот мы дистиллируем наш hello.exe в hello.vdk и с помощью подкоманды sense смотрим, какие зависимости у программы.

Дистилляция и зависимости hello.vdk
Дистилляция и зависимости hello.vdk

Но все эти api‑ms, честно сказать, мне не нравятся в списке зависимостей, поэтому, для наглядности, мы от них избавимся по готовой карте замещений, а на Windows этот функционал встроен в низкоуровневую библиотеку — ntdll.dll. Чтобы это сделать мы используем подкоманду rectify.

Хирургия с помощью подкоманды rectify

Эта подкоманда редактирует VDK‑файлы: изменяет экспорты и импорты, внедряет хуки, смешивает несколько VDK в один и предоставляет другой функционал для мутации файлов.

Изменяем текущие links с помощью rectify и смотрим итоговые зависимости.

Зависимости после rectify
Зависимости после rectify

Ну и самое главное — запуск и результат.

Осталось последнее и самое важное — то, где кроется большая часть работы и в чём принципиальная разница с подходом Wine.

На этапе разрешения зависимостей читается определённая карта редиректов, как та, которую мы использовали для очистки api‑ms библиотек из импортов. Так вот, там есть самый важный редирект, а именно: ntdll.dll => ntdll.vdk— это узкая талия между User Space и Kernel Space, единственное место, где вызываются сами инструкции syscall. Мы подменяем её собственной имплементацией этих системных вызовов через универсальную библиотеку примитивов, которая, можно сказать, является HAL'ом, а он уже подключает необходимую платформенную реализацию, в зависимости от системы, на которой мы работаем.

Данная архитектура позволяет реализовать слой совместимости для любого пользовательского ПО. А также для драйверов — тремя разными способами, но об этом в следующий раз.

В общем, мы выбрали путь реализации идеального слоя совместимости, а не воссоздания каждой библиотеки Windows, которые меняются день ото дня. Предупреждая возможные юридические вопросы, хочу заверить, что было продумано несколько способов их разрешения. А собственная реализация этих библиотек будет необходима исключительно как способ сделать их быстрее, потому что у Windows, оказывается, с этим действительно есть проблемы.

На данный момент мы размышляем о том, какая лицензия должна быть у Vodka и будет ли вообще программный код открытым, поэтому будем рады вашим идеям на этот счёт.

Спасибо, если вы дошли до этого момента, и отдельная благодарность, если вы видите проблему, которую мы хотим решить, и разделяете наши убеждения на этот счёт!

Скрытый текст

P. S.:

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
Как вам?
81.34%Норм414
18.66%Не норм95
Проголосовали 509 пользователей. Воздержались 113 пользователей.