Хабрахабр

Виртуальный мир Intel. Часть 2: SMP

В предыдущей статье(ссылка) я рассказал о базовой концепции гипервизора, основанного на технологии аппаратной виртуализации Intel. Теперь же я предлагаю расширить возможности гипервизора добавив поддержку многопроцессорной архитектуры (SMP), а также рассмотреть пример того, как гипервизор может вносить изменения в работу гостевой ОС.

Все дальнейшие действия будут проводится на PC со следующей конфигурацией:

CPU: Intel Core i7 5820K
Motherboard: Asus X99-PRO
Ram: 16GB
Гостевая ОС: Windows 7 x32 с отключенным PAE
Начну с описания расположения компонентов гипервизора на жестком диске (все величины заданы в секторах).

image
Процесс загрузки гипервизора отличается от предыдущей версии только наличием нового модуля hypervisor.ap, цель которого базовая инициализация AP процессора.

Процесс загрузки модулей в память:

Поддержка SMP

Кроме того, общими для всех логических процессоров будут таблицы IDT и GDT, а также таблицы для страничной организации памяти. Я реализовал гипервизор по принципу симметричной многопроцессорности, что означает что одна и та же копия VMX будет запущена на всех присутствующих логических процессорах. Так же при таком подходе можно не следить на стороне гипервизора за соответствием TLB кэшей процессоров.
Процесс инициализации для BSP и AP будет отличаться. Я сделал так потому что в гипервизоре сразу будет инициализирована память под адресное пространство гостевой ОС и нет необходимости динамически переназначать физические адреса отдельных страниц. Кроме того, Activity state для vmx non root режима AP процессоров будут установлены в HLT состояние. Все основные структуры участвующие в работе гипервизора будут созданы во время инициализации BSP. Таким образом будет эмулироваться окружение гостевой ОС в соответствии с тем какое бы оно было без использования виртуализации.

Инициализация BSP:

  1. Инициализация спинлоков
  2. Инициализация и загрузка таблиц GDT и IDT
  3. Инициализация таблиц страничной адресации
  4. Инициализация VMCS структур и создание общей EPT таблицы
  5. Активация AP процессоров. Для этого на каждый AP передается последовательность прерываний INIT — SIPI. Вектор для SIPI прерывания равен 0x20, что соответствует передачи управления AP по адресу 0x20000 (модуль hypervisor.ap)
  6. Запуск гостевой ОС по адресу 0x7C00 (модуль win7.mbr)

Инициализация AP:

  1. После активации AP процессор находится в реальном режиме. В модуле hypervisor.ap выполняется первичная инициализация памяти и таблиц страничной адресации для перехода в long mode
  2. Загрузка IDT, GDT, а также каталога таблиц страничной адресации, созданных на этапе инициализации BSP
  3. Инициализация VMCS структур, и загрузка EPT таблицы созданной на этапе инициализации BSP
  4. Переход в режим vmx non-root с активным HLT состоянием

Можно сказать, что реализация поддержки SMP в гипервизоре выполняется довольно просто, однако есть несколько моментов на которые я бы хотел обратить внимание.

USB Legacy Support 1.

Это означает, что работать с usb клавиатурой или мышью можно используя те же методы (порты ввода вывода) как это было с PS/2 стандартом. В новых моделях материнских плат могут отсутствовать PS/2 разъёмы, поэтому с целью обеспечения обратной совместимости используется механизм USB Legacy Support. На моей материнской плате Asus X99-PRO, USB Legacy Support реализован через SMI прерывания, в обработчике которых происходит эмуляция PS/2. Реализация USB Legacy Support зависит не только от модели материнской платы, но может так же манятся в различных версиях firmware. Я пишу об этом так подробно, потому что в моем случае (firmware version 3801), USB Legacy Support не совместим с режимом long mode и при возврате из SMM процессор уходит в shutdown состояние.

Однако в Windows на этапе выбора вариантов загрузки используется метод опроса клавиатуры по стандарту PS/2, поэтому USB Legacy Support должен быть снова активирован перед начало загрузки гостевой ОС. Самое простое решение в такой ситуации — это выключить USB Legacy Support перед переходом в long mode.

Аппаратный Task Switch 2.

Однако в Windows7, для прерываний 2 — NMI и 8 — Double Fault назначены селекторы, указывающие на TSS, а значит такие прерывания приведут к аппаратному переключению контекста. В современных ОС переключение между задачами реализуется, как правило, программными методами. Для подобных случаев я написал свой обработчик Task Switch (функция GuestTaskSwitch). Intel VMX не поддерживает аппаратный Task Switch, а попытка его выполнения приводит к VM Exit. В процессе отладки я с ним не сталкивался. Прерывание Double Fault происходит только при серьезном конфликте в системе, вызванном неправильной обработкой других прерываний. Это до сих пор вызывает у меня сомнения потому как непонятно являются ли эти NMI результатом штатного процесса перезагрузки или же это некорректная работа гипервизора на каком-то из предыдущих этапов. А вот NMI появляется на AP процессорах в момент перезагрузки Windows. Если у вас есть на этот счет какая-то информация прошу высказаться в комментариях или написать мне в личном сообщении.

Изменения в работе гостевой ОС

Дело в том, что с одной стороны хотелось показать что-нибудь интересное, как например внедрение своих обработчиков в базовые сетевые протоколы, но с другой стороны это все упиралось бы в большой объем кода, причем уже мало связанного с тематикой гипервизора. Честно говоря, я долго не мог определится с тем, какие именно изменения в работе гостевой ОС должен вносить гипервизор. Кроме того, не хотелось привязывать гипервизор к какому-то определенному набору железа.

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

И основной целью будет изменение результата работы функции NtQuerySystemInformation таким образом, чтобы при вызове с аргументом SystemProcessInformation(0x05) можно было перехватить информацию о процессах. Контроль за работой прикладных программ будет выполнятся на уровне системных вызовов.

Для возвращения на прикладной уровень r3 используется команда sysexit.
Чтобы получить доступ к результатам выполнения функции NtQuerySystemInformation нужно при каждом выполнении команды sysenter сохранять номер вызываемой функции. В ОС Windows 7 прикладная программа для вызова системной функции использует ассемблерную команду sysenter после чего управление передается в ядро на уровень r0 обработчику KiFastCallEntry. Для того чтобы GP exception вызывало VM Exit нужно установить 13 бит в поле Exception Bitmap из VMCS. Затем при выполнении sysexit сравнивать сохраненное значение с номером перехватываемой функции и в случае совпадения вносить изменения в возвращаемые функцией данные.
Intel VMX не дает прямых средств контроля за выполнением sysenter/sysexit, однако, если записать в Guest MSR IA32_SYSENTER_CS значение 0, то в этом случае команды sysenter/sysexit будут вызывать GP exception которое можно использовать для вызова VM Exit обработчика.

Приведенная ниже структура используется при эмуляции пары sysenter/sysexit.

typedef struct{ QWORD ServiceNumber; QWORD Guest_Sys_CS; QWORD Guest_Sys_EIP; QWORD Guest_Sys_ESP;
} SysEnter_T;

Поле ServiceNumber содержит номер вызываемой функции и обновляется при каждом вызове sysenter.

Для этого задаются маски записи в MSR-Bitmap Address. Поля Guest_Sys_CS,Guest_Sys_EIP,Guest_Sys_ESP обновляются при попытке гостевой ОС выполнить запись в соответствующий MSR регистр.

// 174H 372 IA32_SYSENTER_CS SYSENTER_CS write mask
ptrMSR_BMP[0x100 + (0x174 >> 6)] |= (1UL << (0x174 & 0x3F));
// 175H 373 IA32_SYSENTER_ESP SYSENTER_ESP write mask
ptrMSR_BMP[0x100 + (0x175 >> 6)] |= (1UL << (0x175 & 0x3F));
// 176H 374 IA32_SYSENTER_EIP SYSENTER_EIP write mask
ptrMSR_BMP[0x100 + (0x176 >> 6)] |= (1UL << (0x176 & 0x3F));

Гостевая ОС не должна видеть изменений, внесенных гипервизором в работу вызовов системных функций. Установив маску для чтения MSR IA32_SYSENTER_CS можно при чтении вернуть гостевой ОС оригинальное значение регистра.

// 174H 372 IA32_SYSENTER_CS SYSENTER_CS read mask
ptrMSR_BMP[0x174 >> 6] |= (1UL << (0x174 & 0x3F));

Ниже приведена схема эмуляции команд sysenter/sysexit.

В случае совпадения проверяется что NtQuerySystemInformation вызвана с аргументом System Process Information и если это так то функция ChangeProcessNames(DWORD SPI_GVA, DWORD SPI_size) вносит изменения в структурах содержащих информацию о процессах.
SPI_GVA – это гостевой виртуальный адрес структуры SYSTEM_PROCESS_INFORMATION
SPI_size – общий размер структур в байтах.
Сама структура SYSTEM_PROCESS_INFORMATION выглядит так: На этапе эмуляции sysexit выполняется сравнение сохраненного номера вызванной функции с номером NtQuerySystemInformation (0x105).

typedef struct _SYSTEM_PROCESS_INFORMATION { ULONG NextEntryOffset; ULONG NumberOfThreads; BYTE Reserved1[48]; UNICODE_STRING ImageName; KPRIORITY BasePriority; HANDLE UniqueProcessId; PVOID Reserved2; ULONG HandleCount; ULONG SessionId; PVOID Reserved3; SIZE_T PeakVirtualSize; SIZE_T VirtualSize; ULONG Reserved4; SIZE_T PeakWorkingSetSize; SIZE_T WorkingSetSize; PVOID Reserved5; SIZE_T QuotaPagedPoolUsage; PVOID Reserved6; SIZE_T QuotaNonPagedPoolUsage; SIZE_T PagefileUsage; SIZE_T PeakPagefileUsage; SIZE_T PrivatePageCount; LARGE_INTEGER Reserved7[6];
} SYSTEM_PROCESS_INFORMATION;

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

Для наглядности результата я заменил в именах всех процессов первые два символа на знак ‘🙂’ Результат такой замены виден на скриншоте.

Итоги

Гипервизор обеспечивает стабильную работу гостевой ОС, а также осуществляет контроль за вызовом системных функций с прикладного уровня. В целом поставленные в начале статьи задачи были выполнены. Этот недостаток можно устранить если выполнять контроль за вызовами только в контексте выбранных процессов. Отмечу что главный недостаток применения эмуляции команд sysenter/sysexit это значительное увеличение вызовов VM Exit, что сказывается на производительности и это особенно заметно при работе гостевой ОС в однопроцессорном режиме.

Исходники к статье можно взять тут И на этом пока все.

Спасибо за внимание.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть