[Перевод] Способы эмуляции Win32

cyqhrnax4rhswbgq8azsbz6utys.jpeg


Самый популярный вопрос о моём эмуляторе Windows retrowin32 (после «Зачем вообще это нужно?») — это вопрос о том, как он работает. Сегодня ответ кажется мне очевидным, но прежде чем я разобрался, он представлял для меня огромную загадку. Поэтому я постараюсь объяснить так, чтобы вам тоже стало понятно.

Эмуляция Windows API


Для начала представьте, что вы работаете с машиной x86, на которой установлена какая-то операционная система (не Windows), и вам нужно как-то запустить программу для Windows. Основной вывод из наблюдения за Wine (который, как следует из расшифровки аббревиатуры Wine Is Not Emulator, не является эмулятором) заключается в том, что исполняемый файл Windows в конечном итоге содержит последовательность команд x86, а ваша машина x86 уже способна напрямую исполнять их. Значит, для исполнения файла .exe Windows вам достаточно просто загрузить его в память (для чего нужно распаковать формат файлов .exe) и приказать процессору перейти к первой команде.

Единственное, что остаётся (и это очень масштабная задача) — способ взаимодействия этого exe с операционной системой, например, как он открывает файлы или выводит что-то на экран. Механизм сильно отличается для разных операционных систем, но в конечном итоге зависит от интерфейса ядра. В конкретном случае Windows интерфейс ядра довольно замороченный (идентификаторы системных вызовов различаются для разных версий Windows), и обычно считается, что стабильная граница API находится в DLL со знакомыми вам именами наподобие kernel32.dll. (Это является полной противоположностью Linux, который известен своим вниманием к наличию очень стабильного интерфейса на границе ядра.)

Это работает следующим образом: формат файлов .exe может объявить: «эй, мне нужно будет вызвать функцию kernel32.dll с именем WriteFile()», и когда .exe загружается, операционная система помещает соответствующий код в соответствующее место так, чтобы вызов функции сработал. Затем в Windows функция kernel32 вызывает соответствующий интерфейс ядра. Это удобно для выполнения нашей цели запуска файла в ОС, отличной от Windows, потому что нам достаточно лишь предоставить собственные реализации этих функций, даже не обращая внимания на интерфейс ядра.

Именно так и поступает Wine: он загружает файлы .exe и предоставляет реализации всех DLL Windows. Естественно, на практике всё существенно сложнее, и Wine потребовались века человеко-часов работы программистов, чтобы воспроизвести все особенности и странности интерфейса Windows, который сам десятки лет подвергался воздействию закона Хайрама.

Один из примеров того, насколько глубока может быть кроличья нора, можно увидеть в этом фрагменте, находящемся в блобе ассемблерного кода x86 диспетчера системных вызовов Wine:

/* Legends of Runeterra обрабатывает первую команду возврата системного вызова,
 * и ждёт, что мы вернём его. Изменяем адрес возврата соответствующим образом. */
"subq $0xb,0x70(%rcx)\n\t"


В отличной статье how Wine works ещё подробнее рассказывается о Wine. Я намеренно опустил множество деталей.

(Кстати, у Wine есть один интересный способ применения, никак не связанный с задачей этого поста — если у вас есть исходный код программы для Windows, то можно скомпилировать его для созданной в Wine реализации Windows API и получить на выходе нативный исполняемый файл.)

Эмуляция x86


Всё описанное выше отлично работает на оборудовании x86, но что если вы на какой-то другой архитектуре, например, на новых Mac с процессорами ARM? Тогда вам нужно эмулировать набор команд x86 точно так же, как эмулятор Game Boy может эмулировать процессор Game Boy.

А это непростая задача! Apple даже добавила в свои процессоры ARM специальную поддержку x86, чтобы ускорить эмуляцию. Но после того, как вы справитесь, то дальше можете пойти по двум разным путям.

Первый: дополнительно эмулировать всё оборудование, находящееся в машине x86, например, BIOS и интерфейсы дисков, так, чтобы можно было установить в ваш эмулятор настоящую ОС Windows. Этот подход используется в qemu. Есть также веб-эмулятор v86, способный запускать множество разных ОС, в том числе и Windows.

Такой подход прекрасен тем, что запускается настоящая копия Windows; следовательно, все программы для Windows будут работать корректно. Основной недостаток этого подхода в том, что необходимо устанавливать реальную копию Windows, что требует много пространства на диске и времени на запуск Windows, прежде чем приступать к исполнению программ.

Второй подход: эмулировать набор команд x86 и использовать описанный выше Wine в качестве реализации огромного Windows API, основывающегося на чём-то более удобном для эмуляции, например, на API ядра Linux. После публикации retrowin32 я узнал о BoxedWine, который выполняет эту задачу в вебе и способен запускать множество сложных программ для Windows.

Подход retrowin32


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

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

© Habrahabr.ru