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

в 19:15, , рубрики: intel, ассемблер, виртуализация, системное программирование

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

Все дальнейшие действия будут проводится на PC со следующей конфигурацией:
CPU: Intel Core i7 5820K
Motherboard: Asus X99-PRO
Ram: 16GB
Гостевая ОС: Windows 7 x32 с отключенным PAE

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

Процесс загрузки модулей в память:
Виртуальный мир Intel. Часть 2: SMP - 2

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

Инициализация 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 в гипервизоре выполняется довольно просто, однако есть несколько моментов на которые я бы хотел обратить внимание.

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

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

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

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

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

// 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.
Виртуальный мир Intel. Часть 2: SMP - 3
На этапе эмуляции sysexit выполняется сравнение сохраненного номера вызванной функции с номером NtQuerySystemInformation (0x105). В случае совпадения проверяется что NtQuerySystemInformation вызвана с аргументом System Process Information и если это так то функция ChangeProcessNames(DWORD SPI_GVA, DWORD SPI_size) вносит изменения в структурах содержащих информацию о процессах.
SPI_GVA – это гостевой виртуальный адрес структуры SYSTEM_PROCESS_INFORMATION
SPI_size – общий размер структур в байтах.
Сама структура SYSTEM_PROCESS_INFORMATION выглядит так:

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().
Для наглядности результата я заменил в именах всех процессов первые два символа на знак ‘:)’ Результат такой замены виден на скриншоте.
Виртуальный мир Intel. Часть 2: SMP - 4

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

Автор: staticbear

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js