- PVSM.RU - https://www.pvsm.ru -
В данной статье я хочу рассмотреть практические аспекты создания простого гипервизора на основе технологии аппаратной виртуализации Intel VMX.
Аппаратная виртуализация достаточно узкоспециализированная область системного программирования и не имеет большого комьюнити, в России уж точно. Я надеюсь, что материал статьи поможет тем, кто захочет открыть для себя аппаратную виртуализацию и те возможности которые она предоставляет. Как было сказано в начале, я хочу рассмотреть именно практический аспект без погружения в теорию, поэтому предполагается что читатель знаком с архитектурой x86-64 и имеет хотя бы общее представление о механизмах VMX. Исходники к статье [1].
Начнем с постановки задач для гипервизора:
В качестве гостевой ОС я выбрал Windows 7 x32, в которой были заданы следующие ограничения:
Для того чтобы гипервизор запускался при старте PC я выбрал самый простой путь, а именно записал свой загрузчик в MBR сектор диска на который установлена гостевая ОС. Так же нужно было где-то на диске разместить код гипервизора. В моем случае, оригинальная MBR считывает bootloader начиная с 2048 сектора, что дает условно свободную область для записи в (2047 * 512) Кб. Этого более чем достаточно для размещения всех компонентов гипервизора.
Ниже приведена схема размещения гипервизора на диске, все значения заданы в секторах.

Процесс загрузки происходит следующим образом:

Хочу обратить внимание на то что загрузчик после перехода в long mode больше не может пользоваться сервисами BIOS для работы с физическими дисками, поэтому для чтения диска я использовал «Advance Host Controller Interface».
Более подробно о котором можно почитать тут [2].
После того как гипервизор получает управление его первая задача заключается в том, чтобы инициализировать окружение в котором ему предстоит работать, для этого последовательно вызываются функции:
[0x00000000 – 0xFFFFFFFF] – page size 2MB,cache disable;
[0x100000000 – 0x13FFFFFFF] – page size 2 MB, cache write back, global pages;
[0x140000000 – n] – not present;
Далее необходимо убедиться что процессор поддерживает VMX, проверка выполняется функцией CheckVMXConditions():
Если все в порядке и гипервизор работает на процессоре, поддерживающем аппаратную виртуализацию переходим к начальной инициализации VMX, смотрим функцию InitVMX():
Выполнение гостевой ОС начнется в реальном режиме с адреса 0x7C00 по которому, как мы помним, загрузчик loader.main размещает win7.mbr. Для того чтобы воссоздать виртуальную среду идентичную той в которой обычно выполняется mbr, вызывается функция InitGuestRegisterState() которая устанавливает регистры vmx non-root следующим образом:
CR0 = 0x10
CR3 = 0
CR4 = 0
DR7 = 0
RSP = 0xFFD6
RIP = 0x7C00
RFLAGS = 0x82
ES.base = 0
CS.base = 0
SS.base = 0
DS.base = 0
FS.base = 0
GS.base = 0
LDTR.base = 0
TR.base = 0
ES.limit = 0xFFFFFFFF
CS.limit = 0xFFFF
SS.limit = 0xFFFF
DS.limit = 0xFFFFFFFF
FS.limit = 0xFFFF
GS.limit = 0xFFFF
LDTR.limit = 0xFFFF
TR.limit = 0xFFFF
ES.access rights = 0xF093
CS.access rights = 0x93
SS.access rights = 0x93
DS.access rights = 0xF093
FS.access rights = 0x93
GS.access rights = 0x93
LDTR.access rights = 0x82
TR.access rights = 0x8B
ES.selector = 0
CS.selector = 0
SS.selector = 0
DS.selector = 0
FS.selector = 0
GS.selector = 0
LDTR.selector = 0
TR.selector = 0
GDTR.base = 0
IDTR.base = 0
GDTR.limit = 0
IDTR.limit = 0x3FF
Следует обратить внимание на то что поле limit дескрипторного кэша для сегментных регистров DS и ES равно 0xFFFFFFFF. Это пример использования unreal mode — особенности процессора x86 позволяющей обходить лимит сегментов в реальном режиме. Подробней об этом можно почитать тут [3].
Находясь в vmx not-root режиме гостевая ОС может столкнутся с ситуацией, когда необходимо вернуть управление хосту в режим vmx root. В таком случае происходит VM exit во время которого сохраняется текущее состояние vmx non-root и загружается vmx-root. Инициализация vmx-root выполняется функцией InitHostStateArea(), которая устанавливает следующее значение регистров:
CR0 = 0x80000039
CR3 = PML4_addr
CR4 = 0x420A1
RSP = адрес на начало фрейма STACK64
RIP = адрес обработчика VMEXIT_handler
ES.selector = 0x10
CS.selector = 0x08
SS.selector = 0x10
DS.selector = 0x10
FS.selector = 0x10
GS.selector = 0x10
TR.selector = 0x18
TR.base = адрес TSS
GDTR.base = адрес GDT64
IDTR.base = адрес IDTR
Далее выполняется создание гостевого физического адресного пространства (функция InitEPT()). Это один из самых важных моментов при создании гипервизора, потому что неправильно заданный размер или тип на каком-нибудь из участков памяти могут привести к ошибкам которые могут и не проявить себя сразу, но с большой вероятностью будут приводит к неожиданным тормозам или зависаниям гостевой ОС. В общем приятного тут мало и лучше уделить настройке памяти достаточно внимания.
На следующем изображении приведена модель гостевого физического адресного пространства:

Итак, что мы тут видим:
Информацию для создания таких областей я взял из утилиты RAMMap (вкладка Physical Ranges) так же я воспользовался данными из Windows Device Manager. Разумеется, на другом PC диапазоны адресов скорее всего будут отличаться. Что касается типа гостевой памяти, в моей реализации тип определяется только значением, указанным в таблицах EPT. Это просто, но не совсем корректно и вообще следует учитывать тот тип памяти который хочет установить гостевая ОС в своей страничной адресации.
После того как завершено создание гостевого адресного пространства, можно перейти к настройкам VM Execution control field (функция InitExecutionControlFields()). Это довольно большой набор опций, которые позволяют задать условия работы гостевой ОС в режиме vmx not-root. Можно, к примеру, отслеживать обращения к портам ввода вывода или контролировать изменение MSR регистров. Но нашем случае я использую только возможность контролировать установку определенных бит в регистре CR0. Дело в том, что 30(CD) и 29(NW) биты общие как для vmx non-root так и для vmx root режимов и если гостевая ОС установит эти биты в 1 это негативно скажется на производительности.
Процесс настройки гипервизора почти завершен, осталось только установить контроль за переходом в гостевой режим vmx non-root и возвращением в режим хоста vmx root. Настройки задаются в функциями:
InitVMEntryControl() настройки для перехода в vmx non-root:
InitVMExitControl() настройки для перехода в vmx root:
Теперь, когда все настройки выполнены, функция VMLaunch() переводит процессор в режим vmx non-root и начинает выполняться гостевая ОС. Как я упоминал ранее, в настройках vm execution control могут быть заданы условия, при возникновении которых гипервизор вернет себе управления в режиме vmx root. В моем простом примере, я предоставляю гостевой ОС полную свободу действий, однако в некоторых случаях гипервизор все же должен будет вмешаться и скорректировать работу ОС.

Написанный в качестве примера к статье гипервизор вполне способен поддерживать стабильную работу гостевой ОС, хотя конечно и не является законченным решением. Не используется Intel VT-d, реализована поддержка только одного логического процессора, нет контроля за прерываниями и работой периферийных устройств. В общем я не использовал почти ничего из богатого набора средств, которые предоставляет Intel для аппаратной виртуализации. Впрочем, если сообщество заинтересуется я продолжу писать про Intel VMX, тем более что написать есть о чем.
Да, чуть не забыл, отладку гипервизора и его компонентов удобно проводить с помощью Bochs. На первое время это незаменимый инструмент. К сожалению, загрузка гипервизора в Bochs отличается от загрузки на физическом PC. В свое время я делал специальную сборку чтобы упростить этот процесс, постараюсь привести исходники в порядок и так же выложить вместе с проектом в ближайшее время.
На этом все. Спасибо за внимание.
Автор: staticbear
Источник [4]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/sistemnoe-programmirovanie/288045
Ссылки в тексте:
[1] Исходники к статье: https://github.com/staticbear/simple_hypervisor
[2] тут: https://wiki.osdev.org/AHCI
[3] тут: https://wiki.osdev.org/Unreal_Mode
[4] Источник: https://habr.com/post/419065/?utm_campaign=419065
Нажмите здесь для печати.