Виртуализация для самых маленьких #3: готовим структуры, заполняем EPT

Дата публикации 21 июн 2020 | Редактировалось 22 июн 2020
В прошлой части мы получили теоретическую базу по принципам настройки гипервизора и EPT.
Применим их на практике.

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

Так как обработчики IPI выполняются на высоком IRQL (IPI_LEVEL), нам необходимо подготовить все данные, которые могут понадобиться коду виртуализации.
В первую очередь, выделить память под таблицы EPT, под структуры VMCS, а также, под стек для VMM.

На стеке для VMM остановимся подробнее.
Когда процессор переходит из гостя в VMM, из VMCS в процессор загружаются значения RIP (адрес нашей функции-обработчика) и RSP (стек, с которым будет работать VMM), заданные при настройке VMCS на этапе инициализации гипервизора.
Память под стек нам необходимо выделить самостоятельно и освободим мы её лишь после полной остановки гипервизора.

Так как в VMCS хранятся физические адреса дополнительных структур, а в процессор загружается физический адрес VMCS, память под них должна быть физически непрерывной.
Этого можно добиться, выделяя память функцией MmAllocateContiguousMemorySpecifyCache.

Для начала, в Hypervisor.cpp напишем необходимые обёртки над функциями для работы с памятью:
Код (C):
  1.  
  2. namespace VirtualMemory
  3. {
  4.     constexpr ULONG PoolTag = 'LOOP';
  5.  
  6.     static PVOID AllocArray(SIZE_T SizeOfElement, SIZE_T ElementsCount)
  7.     {
  8.         auto BufSize = SizeOfElement * ElementsCount;
  9.         PVOID Buf = ExAllocatePoolWithTag(NonPagedPool, BufSize, PoolTag);
  10.         if (Buf)
  11.         {
  12.             RtlSecureZeroMemory(Buf, BufSize);
  13.         }
  14.         return Buf;
  15.     }
  16.  
  17.     template <typename T>
  18.     inline T* AllocArray(SIZE_T ElementsCount)
  19.     {
  20.         return reinterpret_cast<T*>(AllocArray(sizeof(T), ElementsCount));
  21.     }
  22.  
  23.     _IRQL_requires_max_(DISPATCH_LEVEL)
  24.     static VOID FreePoolMemory(__drv_freesMem(Mem) PVOID Address)
  25.     {
  26.         ExFreePoolWithTag(Address, PoolTag);
  27.     }
  28. }
  29.  
  30. namespace PhysicalMemory
  31. {
  32.     _IRQL_requires_max_(DISPATCH_LEVEL)
  33.     static PVOID AllocPhysicalMemorySpecifyCache(
  34.         PVOID64 LowestAcceptableAddress,
  35.         PVOID64 HighestAcceptableAddress,
  36.         PVOID64 BoundaryAddressMultiple,
  37.         SIZE_T Size,
  38.         MEMORY_CACHING_TYPE CachingType
  39.     ) {
  40.         return MmAllocateContiguousMemorySpecifyCache(
  41.             Size,
  42.             *reinterpret_cast<PHYSICAL_ADDRESS*>(&LowestAcceptableAddress),
  43.             *reinterpret_cast<PHYSICAL_ADDRESS*>(&HighestAcceptableAddress),
  44.             *reinterpret_cast<PHYSICAL_ADDRESS*>(&BoundaryAddressMultiple),
  45.             CachingType
  46.         );
  47.     }
  48.  
  49.     _IRQL_requires_max_(DISPATCH_LEVEL)
  50.     static VOID FreePhysicalMemory(PVOID BaseVirtualAddress)
  51.     {
  52.         MmFreeContiguousMemory(BaseVirtualAddress);
  53.     }
  54.  
  55.     static PVOID64 GetPhysicalAddress(const void* VirtualAddress)
  56.     {
  57.         return reinterpret_cast<PVOID64>(MmGetPhysicalAddress(const_cast<void*>(VirtualAddress)).QuadPart);
  58.     }
  59. }
  60.  
  61. namespace Supplementation
  62. {
  63.     _IRQL_requires_max_(DISPATCH_LEVEL)
  64.     static PVOID AllocPhys(SIZE_T Size, MEMORY_CACHING_TYPE CachingType = MmCached, ULONG MaxPhysBits = 0)
  65.     {
  66.         PVOID64 HighestAcceptableAddress = MaxPhysBits
  67.             ? reinterpret_cast<PVOID64>((1ULL << MaxPhysBits) - 1)
  68.             : reinterpret_cast<PVOID64>((1ULL << 48) - 1);
  69.  
  70.         PVOID Memory = PhysicalMemory::AllocPhysicalMemorySpecifyCache(
  71.             0,
  72.             HighestAcceptableAddress,
  73.             0,
  74.             Size,
  75.             CachingType
  76.         );
  77.  
  78.         if (Memory) RtlSecureZeroMemory(Memory, Size);
  79.         return Memory;
  80.     }
  81.  
  82.     _IRQL_requires_max_(DISPATCH_LEVEL)
  83.     static VOID FreePhys(PVOID Memory)
  84.     {
  85.         PhysicalMemory::FreePhysicalMemory(Memory);
  86.     }
  87. }
  88.  
Теперь подготовим структуры с данными, необходимыми для настройки и работы гипервизора.

Каждому логическому процессору заведём структуру со своими экземплярами VMCS, EPT и прочих структур.
Назовём эту структуру "PRIVATE_VM_DATA" и память под неё выделим динамически для каждого логического процессора.
Также нам понадобится хранилище данных, общих для всех виртуальных процессоров.
Эту общую структуру назовём "SHARED_VM_DATA", она будет в единственном экземпляре и мы поместим её в глобальную область видимости.

Код (C):
  1.  
  2. #include "VMX.h"
  3.  
  4. ...
  5.  
  6. namespace VMX
  7. {
  8.     using namespace Intel;
  9.     ...
  10.  
  11.     struct SHARED_VM_DATA;
  12.  
  13.     // Unique for each processor:
  14.     struct PRIVATE_VM_DATA
  15.     {
  16.         union
  17.         {
  18.             DECLSPEC_ALIGN(PAGE_SIZE) unsigned char VmmStack[KERNEL_STACK_SIZE];
  19.             struct
  20.             {
  21.                 struct INITIAL_VMM_STACK_LAYOUT
  22.                 {
  23.                     PVOID VmcsPa;
  24.                     SHARED_VM_DATA* Shared;
  25.                     PRIVATE_VM_DATA* Private;
  26.                 };
  27.                 unsigned char FreeSpace[KERNEL_STACK_SIZE - sizeof(INITIAL_VMM_STACK_LAYOUT)];
  28.                 INITIAL_VMM_STACK_LAYOUT InitialStack;
  29.             } Layout;
  30.         } VmmStack;
  31.         DECLSPEC_ALIGN(PAGE_SIZE) VMCS Vmxon; // VMXON structure is the same as VMCS with the same size
  32.         DECLSPEC_ALIGN(PAGE_SIZE) VMCS Vmcs;
  33.         DECLSPEC_ALIGN(PAGE_SIZE) MSR_BITMAP MsrBitmap;
  34.     };
  35.  
  36.     struct SHARED_VM_DATA
  37.     {
  38.         PRIVATE_VM_DATA* Processors; // Array: PRIVATE_VM_DATA Processors[ProcessorsCount]
  39.         unsigned int ProcessorsCount;
  40.     };
  41.  
  42.     static SHARED_VM_DATA g_Shared = {};
  43.  
  44.     ...
  45. }
  46.  
Рассмотрим подробнее PRIVATE_VM_DATA.
В этой большой структуре мы будем хранить данные, необходимые виртуальному процессору для работы:
- Стек VMM, который будет использовать наш обработчик при наступлении #VMEXIT.
- Регион VMXON, необходимый для перехода в режим "VMX root operation" - пустой регион с размером, идентичным VMCS, физический адрес которого передаётся в инструкцию vmxon (в документации Intel не описывает деталей его предназначения и, в отличие от VMCS, этот регион не требует особой настройки, кроме одного поля с ревизией VMCS).
- VMCS, описывающая состояние виртуализованной среды и VMM.

В стек мы положим адреса SHARED_VM_DATA и самой PRIVATE_VM_DATA для текущего логического процессора, а также, физический адрес текущей VMCS (который теоретически может нам понадобиться), чтобы иметь к ним быстрый и удобный доступ в VMM.

Следующий шаг - объявление структур EPT. Мы покроем всё физическое адресное пространство, к которому может обратиться процессор, описав в EPT 512 Гб физической памяти.
Для экономии памяти под таблицы и для ускорения трансляции мы будем описывать 512 Гб большими двухмегабайтными страницами.
Для этого нам понадобится:
- Одна запись PML4E, описывающая все 512 Гб через 512 записей PDPTE
- 512 записей PDPTE, каждая из которых описывает 1 Гб физического АП через 512 записей PDE в каждой
- 512 * 512 записей PDE, каждая из которых описывает 2 Мб физического АП (512 PDE в каждом PDPTE, которых самих 512)
- 512 записей PTE, описывающие первые два мегабайта физической памяти, т.к. первый мегабайт описывается фиксированными MTRR и нам нужно более тонко настроить кэширование для этого региона, избежав конфликтов типов кэшей, которые практически наверняка будут при описании этого региона одной большой страницей (и тогда пришлось бы задавать всей странице тип Uncacheable).

Итоговые таблицы опишем в одной структуре:
Код (C):
  1.  
  2. namespace VMX
  3. {
  4.     ...
  5.  
  6.     struct EPT_TABLES
  7.     {
  8.         DECLSPEC_ALIGN(PAGE_SIZE) EPT_PML4E Pml4e;
  9.         DECLSPEC_ALIGN(PAGE_SIZE) EPT_PDPTE Pdpte[512];
  10.         DECLSPEC_ALIGN(PAGE_SIZE) EPT_PDE Pde[512][512];
  11.         DECLSPEC_ALIGN(PAGE_SIZE) EPT_PTE PteForFirstLargePage[2 * 1048576 / 4096]; // 512 entries for the first 2Mb
  12.     };
  13. }
  14.  
Размер этой таблицы чуть превышает 2 Мб.

Пытливый ум спросит: зачем описывать все 512 гигабайт, если установленной физической RAM намного меньше.
Действительно, можно определить количество установленной оперативной памяти и создавать таблицы только для неё, однако другие физические устройства могут отображать своё адресное пространство на физическое адресное пространство процессора (MMIO).
Если процессор обратится к такой памяти, которая не описана в EPT, наш VMM получит исключение "EPT violation" (Volume 3, Chapter 28.2.3.2) и нам придётся динамически создавать для этого региона описание в EPT.
Это сильно усложняет разработку, поэтому для простоты мы используем фиксированный EPT, описывающий все 512 гигабайт ценой большего размера таблиц.

Так как EPT для каждого логического процессора своя, добавим её в PRIVATE_VM_DATA:
Код (C):
  1.  
  2. namespace VMX
  3. {
  4.     ...
  5.     struct PRIVATE_VM_DATA
  6.     {
  7.         union
  8.         {
  9.             ...
  10.         } VmmStack;
  11.  
  12.         DECLSPEC_ALIGN(PAGE_SIZE) VMCS Vmxon; // VMXON structure is the same as VMCS with the same size
  13.         DECLSPEC_ALIGN(PAGE_SIZE) VMCS Vmcs;
  14.         DECLSPEC_ALIGN(PAGE_SIZE) EPT_TABLES Ept;
  15.     };
  16. }
  17.  
Как было описано в прошлой статье, тип кэширования страницы задаётся явно на последнем уровне трансляции в EPT и этот тип необходимо определить с помощью регистров MTRR.

Опишем всё, связанное с MTRR, в одной большой структуре (Fixed- и Variable-регистры и значения MSR, необходимые для их обработки):
Код (C):
  1.  
  2. namespace VMX
  3. {
  4.     ...
  5.  
  6.     struct MTRR_INFO
  7.     {
  8.         UINT64 MaxPhysAddrBits;
  9.         UINT64 PhysAddrMask;
  10.  
  11.         IA32_VMX_EPT_VPID_CAP EptVpidCap;
  12.         IA32_MTRRCAP MtrrCap;
  13.         IA32_MTRR_DEF_TYPE MtrrDefType;
  14.  
  15.         // For the first 1 megabyte of the physical address space:
  16.         union
  17.         {
  18.             MTRR_FIXED_GENERIC Generic[11];
  19.             struct {
  20.                 // 512-Kbyte range:
  21.                 IA32_MTRR_FIX64K RangeFrom00000To7FFFF;
  22.  
  23.                 // Two 128-Kbyte ranges:
  24.                 IA32_MTRR_FIX16K RangeFrom80000To9FFFF;
  25.                 IA32_MTRR_FIX16K RangeFromA0000ToBFFFF;
  26.  
  27.                 // Eight 32-Kbyte ranges:
  28.                 IA32_MTRR_FIX4K RangeFromC0000ToC7FFF;
  29.                 IA32_MTRR_FIX4K RangeFromC8000ToCFFFF;
  30.                 IA32_MTRR_FIX4K RangeFromD0000ToD7FFF;
  31.                 IA32_MTRR_FIX4K RangeFromD8000ToDFFFF;
  32.                 IA32_MTRR_FIX4K RangeFromE0000ToE7FFF;
  33.                 IA32_MTRR_FIX4K RangeFromE8000ToEFFFF;
  34.                 IA32_MTRR_FIX4K RangeFromF0000ToF7FFF;
  35.                 IA32_MTRR_FIX4K RangeFromF8000ToFFFFF;
  36.             } Ranges;
  37.         } Fixed;
  38.  
  39.         // For the memory above the first megabyte of the physical address space:
  40.         struct
  41.         {
  42.             IA32_MTRR_PHYSBASE PhysBase;
  43.             IA32_MTRR_PHYSMASK PhysMask;
  44.         } Variable[10];
  45.  
  46.         bool IsSupported;
  47.     };
  48.  
  49.     ...
  50. }
  51.  
Имея все вышеописанные структуры, можем начинать инициализацию.
Начнём с инициализации MTRR:
Код (C):
  1.  
  2. namespace VMX
  3. {
  4.     ...
  5.  
  6.     // E.g.: MaskLow<char>(5) -> 0b00011111:
  7.     template <typename T>
  8.     constexpr T MaskLow(unsigned char SignificantBits)
  9.     {
  10.         return static_cast<T>((1ULL << SignificantBits) - 1);
  11.     }
  12.  
  13.     // E.g.: MaskHigh<char>(3) -> 0b11100000:
  14.     template <typename T>
  15.     constexpr T MaskHigh(unsigned char SignificantBits)
  16.     {
  17.         return MaskLow<T>(SignificantBits) << ((sizeof(T) * 8) - SignificantBits);
  18.     }
  19.  
  20.     static void InitMtrr(__out MTRR_INFO* MtrrInfo)
  21.     {
  22.         memset(MtrrInfo, 0, sizeof(*MtrrInfo));
  23.  
  24.         CPUID::FEATURE_INFORMATION Features = {};
  25.         __cpuid(Features.Regs.Raw, CPUID::Intel::CPUID_FEATURE_INFORMATION);
  26.         MtrrInfo->IsSupported = Features.Intel.MTRR;
  27.         if (!MtrrInfo->IsSupported) return;
  28.  
  29.         CPUID::Intel::VIRTUAL_AND_PHYSICAL_ADDRESS_SIZES MaxAddrSizes = {};
  30.         __cpuid(MaxAddrSizes.Regs.Raw, CPUID::Intel::CPUID_VIRTUAL_AND_PHYSICAL_ADDRESS_SIZES);
  31.         MtrrInfo->MaxPhysAddrBits = MaxAddrSizes.Bitmap.PhysicalAddressBits;
  32.         MtrrInfo->PhysAddrMask = ~MaskLow<unsigned long long>(static_cast<unsigned char>(MtrrInfo->MaxPhysAddrBits));
  33.  
  34.         MtrrInfo->EptVpidCap.Value = __readmsr(static_cast<unsigned long>(INTEL_MSR::IA32_VMX_EPT_VPID_CAP));
  35.         MtrrInfo->MtrrCap.Value = __readmsr(static_cast<unsigned long>(INTEL_MSR::IA32_MTRRCAP));
  36.         MtrrInfo->MtrrDefType.Value = __readmsr(static_cast<unsigned long>(INTEL_MSR::IA32_MTRR_DEF_TYPE));
  37.  
  38.         if (MtrrInfo->MtrrCap.Bitmap.FIX && MtrrInfo->MtrrDefType.Bitmap.FE)
  39.         {
  40.             // 512-Kbyte range:
  41.             MtrrInfo->Fixed.Ranges.RangeFrom00000To7FFFF.Value = __readmsr(static_cast<unsigned long>(INTEL_MSR::IA32_MTRR_FIX64K_00000));
  42.  
  43.             // Two 128-Kbyte ranges:
  44.             MtrrInfo->Fixed.Ranges.RangeFrom80000To9FFFF.Value = __readmsr(static_cast<unsigned long>(INTEL_MSR::IA32_MTRR_FIX16K_80000));
  45.             MtrrInfo->Fixed.Ranges.RangeFromA0000ToBFFFF.Value = __readmsr(static_cast<unsigned long>(INTEL_MSR::IA32_MTRR_FIX16K_A0000));
  46.  
  47.             // Eight 32-Kbyte ranges:
  48.             MtrrInfo->Fixed.Ranges.RangeFromC0000ToC7FFF.Value = __readmsr(static_cast<unsigned long>(INTEL_MSR::IA32_MTRR_FIX4K_C0000));
  49.             MtrrInfo->Fixed.Ranges.RangeFromC8000ToCFFFF.Value = __readmsr(static_cast<unsigned long>(INTEL_MSR::IA32_MTRR_FIX4K_C8000));
  50.             MtrrInfo->Fixed.Ranges.RangeFromD0000ToD7FFF.Value = __readmsr(static_cast<unsigned long>(INTEL_MSR::IA32_MTRR_FIX4K_D0000));
  51.             MtrrInfo->Fixed.Ranges.RangeFromD8000ToDFFFF.Value = __readmsr(static_cast<unsigned long>(INTEL_MSR::IA32_MTRR_FIX4K_D8000));
  52.             MtrrInfo->Fixed.Ranges.RangeFromE0000ToE7FFF.Value = __readmsr(static_cast<unsigned long>(INTEL_MSR::IA32_MTRR_FIX4K_E0000));
  53.             MtrrInfo->Fixed.Ranges.RangeFromE8000ToEFFFF.Value = __readmsr(static_cast<unsigned long>(INTEL_MSR::IA32_MTRR_FIX4K_E8000));
  54.             MtrrInfo->Fixed.Ranges.RangeFromF0000ToF7FFF.Value = __readmsr(static_cast<unsigned long>(INTEL_MSR::IA32_MTRR_FIX4K_F0000));
  55.             MtrrInfo->Fixed.Ranges.RangeFromF8000ToFFFFF.Value = __readmsr(static_cast<unsigned long>(INTEL_MSR::IA32_MTRR_FIX4K_F8000));
  56.         }
  57.  
  58.         for (unsigned i = 0; i < MtrrInfo->MtrrCap.Bitmap.VCNT; ++i)
  59.         {
  60.             if (i == ARRAYSIZE(MtrrInfo->Variable)) break;
  61.  
  62.             MtrrInfo->Variable[i].PhysBase.Value = __readmsr(static_cast<unsigned long>(INTEL_MSR::IA32_MTRR_PHYSBASE0) + i * 2);
  63.             MtrrInfo->Variable[i].PhysMask.Value = __readmsr(static_cast<unsigned long>(INTEL_MSR::IA32_MTRR_PHYSMASK0) + i * 2);
  64.         }
  65.     }
  66. }
  67.  
Разберём пошагово:
1. Проверили, поддерживает ли процессор MTRR (CPUID_FEATURE_INFORMATION::MTRR).
2. Получили максимальное поддерживаемое количество бит в физическом адресе.
3. Прочитали MSR, необходимые для дальнейшей интерпретации MTRR.
4. Прочитали все Fixed-MTRR.
5. Прочитали все существующие Variable-MTRR, количество которых берём из IA32_MTRRCAP.

Для заполнения типов кэшей в EPT нам понадобятся вспомогательные функции, вычисляющие тип заданной страницы памяти на основе прочитанных MTRR.
Принцип простой: для каждой страницы перебираем все MTRR и "смешиваем" типы тех MTRR, которые описывают регионы, входящие в нашу страницу.

Для начала напишем процедуру, "смешивающую" два типа кэша по специальным правилам, описанным в прошлой главе (также см. Volume 3, Chapter 11.11.4.1 MTRR Precedences):
1. Если хотя бы у одной страницы, входящей в регион, тип Uncacheable, весь регион становится Uncacheable.
2. Если в регионе есть только страницы с типами WriteBack и хотя бы один WriteThrough, весь регион помечается как WriteThrough.
3. Все остальные случаи - конфликт типов. В этом случае весь регион должен быть помечен как Uncacheable.
Код (C):
  1.  
  2. namespace VMX
  3. {
  4.    ...
  5.  
  6.     static bool MixMtrrTypes(MTRR_MEMORY_TYPE Type1, MTRR_MEMORY_TYPE Type2, __out MTRR_MEMORY_TYPE& Mixed)
  7.     {
  8.         Mixed = MTRR_MEMORY_TYPE::Uncacheable;
  9.         if (Type1 == MTRR_MEMORY_TYPE::Uncacheable || Type2 == MTRR_MEMORY_TYPE::Uncacheable)
  10.         {
  11.             Mixed = MTRR_MEMORY_TYPE::Uncacheable;
  12.             return true;
  13.         }
  14.  
  15.         if (Type1 == Type2)
  16.         {
  17.             Mixed = Type1;
  18.             return true;
  19.         }
  20.         else
  21.         {
  22.             if ((Type1 == MTRR_MEMORY_TYPE::WriteThrough || Type1 == MTRR_MEMORY_TYPE::WriteBack)
  23.                 && (Type2 == MTRR_MEMORY_TYPE::WriteThrough || Type2 == MTRR_MEMORY_TYPE::WriteBack))
  24.             {
  25.                 Mixed = MTRR_MEMORY_TYPE::WriteThrough;
  26.                 return true;
  27.             }
  28.         }
  29.  
  30.         return false; // Memory types are conflicting, returning Uncacheable
  31.     }
  32.  
  33.    ...
  34. }
  35.  
И напишем большую функцию, которая для заданной страницы заданного размера обойдёт все MTRR, выберет подходящие и "смешает" их типы:
Код (C):
  1.  
  2. namespace VMX
  3. {
  4.     struct MEMORY_RANGE
  5.     {
  6.         unsigned long long First;
  7.         unsigned long long Last;
  8.     };
  9.  
  10.     static bool AreRangesIntersects(const MEMORY_RANGE& Range1, const MEMORY_RANGE& Range2)
  11.     {
  12.         return Range1.First <= Range2.Last && Range1.Last >= Range2.First;
  13.     }
  14.  
  15.     static MTRR_MEMORY_TYPE CalcMemoryTypeByFixedMtrr(
  16.         MTRR_FIXED_GENERIC FixedMtrrGeneric,
  17.         const MEMORY_RANGE& MtrrRange,
  18.         const MEMORY_RANGE& PhysRange
  19.     ) {
  20.         bool Initialized = false;
  21.  
  22.         MTRR_MEMORY_TYPE MemType = MTRR_MEMORY_TYPE::Uncacheable;
  23.         constexpr unsigned long long RangeBitsMask = 0b11111111;
  24.         constexpr unsigned long long RangeBitsCount = 8;
  25.         constexpr unsigned long long RangesCount = (sizeof(FixedMtrrGeneric) * 8) / RangeBitsCount;
  26.         const unsigned long long SubrangeSize = (MtrrRange.Last - MtrrRange.First + 1) / RangeBitsCount;
  27.  
  28.         for (unsigned int i = 0; i < RangesCount; ++i)
  29.         {
  30.             MEMORY_RANGE Subrange;
  31.             Subrange.First = MtrrRange.First + i * SubrangeSize;
  32.             Subrange.Last = Subrange.First + SubrangeSize - 1;
  33.  
  34.             if (AreRangesIntersects(PhysRange, Subrange))
  35.             {
  36.                 MTRR_MEMORY_TYPE SubrangeType = static_cast<MTRR_MEMORY_TYPE>((FixedMtrrGeneric.Value >> (i * RangeBitsCount)) & RangeBitsMask);
  37.                 if (Initialized)
  38.                 {
  39.                     bool MixingStatus = MixMtrrTypes(MemType, SubrangeType, OUT MemType);
  40.                     if (!MixingStatus)
  41.                     {
  42.                         // Cache types are conflicting in overlapped regions, returning Uncacheable:
  43.                         MemType = MTRR_MEMORY_TYPE::Uncacheable;
  44.                     }
  45.                 }
  46.                 else
  47.                 {
  48.                     MemType = SubrangeType;
  49.                     Initialized = true;
  50.                 }
  51.  
  52.                 // If at least one range is Uncacheable - then
  53.                 // all overlapped ranges are Uncacheable:
  54.                 if (MemType == MTRR_MEMORY_TYPE::Uncacheable)
  55.                 {
  56.                     break;
  57.                 }
  58.             }
  59.         }
  60.  
  61.         return MemType;
  62.     }
  63.  
  64.     static const MEMORY_RANGE FixedRanges[] = {
  65.         { 0x00000, 0x7FFFF },
  66.         { 0x80000, 0x9FFFF },
  67.         { 0xA0000, 0xBFFFF },
  68.         { 0xC0000, 0xC7FFF },
  69.         { 0xC8000, 0xCFFFF },
  70.         { 0xD0000, 0xD7FFF },
  71.         { 0xD8000, 0xDFFFF },
  72.         { 0xE0000, 0xE7FFF },
  73.         { 0xE8000, 0xEFFFF },
  74.         { 0xF0000, 0xF7FFF },
  75.         { 0xF8000, 0xFFFFF },
  76.     };
  77.  
  78.     static MTRR_MEMORY_TYPE GetMtrrMemoryType(__in const MTRR_INFO* MtrrInfo, unsigned long long PhysicalAddress, unsigned int PageSize)
  79.     {
  80.         if (!MtrrInfo || !PageSize || !MtrrInfo->MtrrDefType.Bitmap.E)
  81.             return MTRR_MEMORY_TYPE::Uncacheable;
  82.  
  83.         constexpr unsigned long long FIRST_MEGABYTE = 0x100000ULL;
  84.  
  85.         MEMORY_RANGE PhysRange = {};
  86.         PhysRange.First = PhysicalAddress;
  87.         PhysRange.Last = PhysicalAddress + PageSize - 1;
  88.  
  89.         bool IsMemTypeInitialized = false;
  90.  
  91.         // Default type:
  92.         MTRR_MEMORY_TYPE MemType = static_cast<MTRR_MEMORY_TYPE>(MtrrInfo->MtrrDefType.Bitmap.Type);
  93.         if (PhysicalAddress < FIRST_MEGABYTE && MtrrInfo->MtrrCap.Bitmap.FIX && MtrrInfo->MtrrDefType.Bitmap.FE)
  94.         {
  95.             for (unsigned int i = 0; i < ARRAYSIZE(FixedRanges); ++i)
  96.             {
  97.                 MTRR_FIXED_GENERIC MtrrFixedGeneric = {};
  98.                 MtrrFixedGeneric.Value = MtrrInfo->Fixed.Generic[i].Value;
  99.                 if (AreRangesIntersects(PhysRange, FixedRanges[i]))
  100.                 {
  101.                     MTRR_MEMORY_TYPE FixedMemType = CalcMemoryTypeByFixedMtrr(MtrrFixedGeneric, FixedRanges[i], PhysRange);
  102.                     if (FixedMemType == MTRR_MEMORY_TYPE::Uncacheable) return FixedMemType;
  103.  
  104.                     if (IsMemTypeInitialized)
  105.                     {
  106.                         bool IsMixed = MixMtrrTypes(MemType, FixedMemType, OUT MemType);
  107.                         if (!IsMixed)
  108.                         {
  109.                             return MTRR_MEMORY_TYPE::Uncacheable;
  110.                         }
  111.                     }
  112.                     else
  113.                     {
  114.                         IsMemTypeInitialized = true;
  115.                         MemType = FixedMemType;
  116.                     }
  117.                 }
  118.             }
  119.         }
  120.  
  121.         for (unsigned int i = 0; i < MtrrInfo->MtrrCap.Bitmap.VCNT; ++i)
  122.         {
  123.             // If this entry is valid:
  124.             if (!MtrrInfo->Variable[i].PhysMask.Bitmap.V) continue;
  125.  
  126.             unsigned long long MtrrPhysBase = PFN_TO_PAGE(MtrrInfo->Variable[i].PhysBase.Bitmap.PhysBasePfn);
  127.             unsigned long long MtrrPhysMask = PFN_TO_PAGE(MtrrInfo->Variable[i].PhysMask.Bitmap.PhysMaskPfn) | MtrrInfo->PhysAddrMask;
  128.             unsigned long long MaskedMtrrPhysBase = MtrrPhysBase & MtrrPhysMask;
  129.             MTRR_MEMORY_TYPE VarMemType = MTRR_MEMORY_TYPE::Uncacheable;
  130.  
  131.             bool IsVarMemTypeInitialized = false;
  132.  
  133.             for (unsigned long long Page = PhysicalAddress; Page < PhysicalAddress + PageSize; Page += PAGE_SIZE)
  134.             {
  135.                 if ((Page & MtrrPhysMask) == MaskedMtrrPhysBase)
  136.                 {
  137.                     auto PageMemType = static_cast<MTRR_MEMORY_TYPE>(MtrrInfo->Variable[i].PhysBase.Bitmap.Type);
  138.                     if (IsVarMemTypeInitialized)
  139.                     {
  140.                         bool IsMixed = MixMtrrTypes(VarMemType, PageMemType, OUT VarMemType);
  141.                         if (!IsMixed)
  142.                         {
  143.                             return MTRR_MEMORY_TYPE::Uncacheable;
  144.                         }
  145.                     }
  146.                     else
  147.                     {
  148.                         VarMemType = PageMemType;
  149.                         IsVarMemTypeInitialized = true;
  150.                     }
  151.  
  152.                     if (VarMemType == MTRR_MEMORY_TYPE::Uncacheable)
  153.                     {
  154.                         return MTRR_MEMORY_TYPE::Uncacheable;
  155.                     }
  156.                 }
  157.             }
  158.  
  159.             if (IsVarMemTypeInitialized)
  160.             {
  161.                 if (VarMemType == MTRR_MEMORY_TYPE::Uncacheable)
  162.                 {
  163.                     return MTRR_MEMORY_TYPE::Uncacheable;
  164.                 }
  165.  
  166.                 if (IsMemTypeInitialized)
  167.                 {
  168.                     bool IsMixed = MixMtrrTypes(MemType, VarMemType, OUT MemType);
  169.                     if (!IsMixed)
  170.                     {
  171.                         return MTRR_MEMORY_TYPE::Uncacheable;
  172.                     }
  173.                 }
  174.                 else
  175.                 {
  176.                     MemType = VarMemType;
  177.                     IsMemTypeInitialized = true;
  178.                 }
  179.             }
  180.         }
  181.  
  182.         return MemType;
  183.     }
  184. }
  185.  
Так как код большой и неочевидный, он требует пояснений.
В функцию GetMtrrMemoryType передаём заполненные MTRR; адрес страницы, тип которой хотим получить, и её размер.
Если страница попадает в первый мегабайт, сначала получаем её тип из фиксированных MTRR, перебирая их все и вычисляя тип по каждому MTRR функцией CalcMemoryTypeByFixedMtrr.
В эту функцию передаём значение фиксированного MTRR и диапазон, который он описывает.
Т.к. один фиксированный MTRR описывает 8 поддиапазонов, перебираем их все и, если поддиапазон попадает в нашу страницу, возвращаем тип этого поддиапазона.
Если это был первый вычисленный тип - запоминаем его. Если тип был получен ранее из предыдущих MTRR - "смешиваем" текущий тип с предыдущим.
Затем перебираем набор из Variable-MTRR.
Т.к. они оперируют страницами по 4 Кб, разбиваем наш диапазон на страницы по 4 Кб и вычисляем тип для каждой, исходя из соотношения:
PhysBase & PhysMask == PagePhysAddr & PhysMask.
Если это соотношение выполняется - диапазон в MTRR пересекается со 4х-килобайтной страницей из нашего диапазона - и мы учитываем тип из MTRR.
Как и в случае фиксированных MTRR, если тип уже был вычислен раньше - "смешиваем" текущий тип с прошлым значением.

И, наконец, используя GetMtrrMemoryType, напишем функцию, заполняющую EPT:
Код (C):
  1.  
  2. namespace VMX
  3. {
  4.     ...
  5.  
  6.     static void InitializeEptTables(__in const MTRR_INFO* MtrrInfo, __out EPT_TABLES* Ept)
  7.     {
  8.         using namespace PhysicalMemory;
  9.         memset(Ept, 0, sizeof(EPT_TABLES));
  10.  
  11.         PVOID64 PdptePhys = GetPhysicalAddress(Ept->Pdpte);
  12.         Ept->Pml4e.Page2Mb.ReadAccess = TRUE;
  13.         Ept->Pml4e.Page2Mb.WriteAccess = TRUE;
  14.         Ept->Pml4e.Page2Mb.ExecuteAccess = TRUE;
  15.         Ept->Pml4e.Page2Mb.EptPdptePhysicalPfn = PAGE_TO_PFN(reinterpret_cast<UINT64>(PdptePhys));
  16.  
  17.         for (unsigned int i = 0; i < _ARRAYSIZE(Ept->Pdpte); ++i)
  18.         {
  19.             PVOID64 PdePhys = GetPhysicalAddress(Ept->Pde[i]);
  20.             Ept->Pdpte[i].Page2Mb.ReadAccess = TRUE;
  21.             Ept->Pdpte[i].Page2Mb.WriteAccess = TRUE;
  22.             Ept->Pdpte[i].Page2Mb.ExecuteAccess = TRUE;
  23.             Ept->Pdpte[i].Page2Mb.EptPdePhysicalPfn = PAGE_TO_PFN(reinterpret_cast<UINT64>(PdePhys));
  24.  
  25.             for (unsigned int j = 0; j < _ARRAYSIZE(Ept->Pde[i]); ++j)
  26.             {
  27.                 if (i == 0 && j == 0)
  28.                 {
  29.                     PVOID64 PtePhys = GetPhysicalAddress(Ept->PteForFirstLargePage);
  30.  
  31.                     Ept->Pde[i][j].Page4Kb.ReadAccess = TRUE;
  32.                     Ept->Pde[i][j].Page4Kb.WriteAccess = TRUE;
  33.                     Ept->Pde[i][j].Page4Kb.ExecuteAccess = TRUE;
  34.                     Ept->Pde[i][j].Page4Kb.EptPtePhysicalPfn = PAGE_TO_PFN(reinterpret_cast<UINT64>(PtePhys));
  35.  
  36.                     for (unsigned int k = 0; k < _ARRAYSIZE(Ept->PteForFirstLargePage); ++k)
  37.                     {
  38.                         MTRR_MEMORY_TYPE MemType = MTRR_MEMORY_TYPE::Uncacheable;
  39.  
  40.                         if (MtrrInfo->IsSupported)
  41.                         {
  42.                             MemType = GetMtrrMemoryType(MtrrInfo, PFN_TO_PAGE(static_cast<unsigned long long>(k)), PAGE_SIZE);
  43.                         }
  44.  
  45.                         Ept->PteForFirstLargePage[k].Page4Kb.ReadAccess = TRUE;
  46.                         Ept->PteForFirstLargePage[k].Page4Kb.WriteAccess = TRUE;
  47.                         Ept->PteForFirstLargePage[k].Page4Kb.ExecuteAccess = TRUE;
  48.                         Ept->PteForFirstLargePage[k].Page4Kb.Type = static_cast<unsigned char>(MemType);
  49.                         Ept->PteForFirstLargePage[k].Page4Kb.PagePhysicalPfn = k;
  50.                     }
  51.                 }
  52.                 else
  53.                 {
  54.                     unsigned long long PagePfn = i * _ARRAYSIZE(Ept->Pde[i]) + j;
  55.                     constexpr unsigned long long LargePageSize = 2 * 1048576; // 2 Mb
  56.  
  57.                     MTRR_MEMORY_TYPE MemType = MTRR_MEMORY_TYPE::Uncacheable;
  58.  
  59.                     if (MtrrInfo->IsSupported)
  60.                     {
  61.                         MemType = GetMtrrMemoryType(MtrrInfo, PFN_TO_LARGE_PAGE(PagePfn), LargePageSize);
  62.                     }
  63.  
  64.                     Ept->Pde[i][j].Page2Mb.ReadAccess = TRUE;
  65.                     Ept->Pde[i][j].Page2Mb.WriteAccess = TRUE;
  66.                     Ept->Pde[i][j].Page2Mb.ExecuteAccess = TRUE;
  67.                     Ept->Pde[i][j].Page2Mb.Type = static_cast<unsigned char>(MemType);
  68.                     Ept->Pde[i][j].Page2Mb.LargePage = TRUE;
  69.                     Ept->Pde[i][j].Page2Mb.PagePhysicalPfn = PagePfn;
  70.                 }
  71.             }
  72.         }
  73.     }
  74.  
  75.     ...
  76. }
  77.  
Здесь мы делаем "видимым" и доступным на чтение, запись и исполнение все 512 Гб физического адресного пространства, к которым процессор может запросить доступ, описывая их большими двухмегабайтными страницами.
А первую большую страницу (первые 2Мб) покрываем обычными страницами по 4 Кб, чтобы выставить типы кэширования на основе фиксированных MTRR без конфликтов.

И, наконец, соберём всё вместе, выделив все структуры и инициализировав все таблицы в функции VirtualizeAllProcessors:
Код (C):
  1.  
  2. namespace VMX
  3. {
  4.     ...
  5.  
  6.     static void DevirtualizeAllProcessors()
  7.     {
  8.         /* Nothing yet */
  9.     }
  10.  
  11.     static bool VirtualizeProcessor(__inout SHARED_VM_DATA* Shared)
  12.     {
  13.         UNREFERENCED_PARAMETER(Shared);
  14.         return false;
  15.     }
  16.  
  17.     static bool VirtualizeAllProcessors()
  18.     {
  19.         // Determining the max phys size:
  20.         CPUID::Intel::VIRTUAL_AND_PHYSICAL_ADDRESS_SIZES MaxAddrSizes = {};
  21.         __cpuid(MaxAddrSizes.Regs.Raw, CPUID::Intel::CPUID_VIRTUAL_AND_PHYSICAL_ADDRESS_SIZES);
  22.  
  23.         // Initializing MTRRs shared between all processors:
  24.         MTRR_INFO MtrrInfo;
  25.         memset(&MtrrInfo, 0, sizeof(MtrrInfo));
  26.         InitMtrr(&MtrrInfo);
  27.  
  28.         ULONG ProcessorsCount = KeQueryActiveProcessorCountEx(ALL_PROCESSOR_GROUPS);
  29.         g_Shared.Processors = VirtualMemory::AllocArray<PRIVATE_VM_DATA*>(ProcessorsCount);
  30.         for (ULONG i = 0; i < ProcessorsCount; ++i)
  31.         {
  32.             g_Shared.Processors[i] = reinterpret_cast<PRIVATE_VM_DATA*>(AllocPhys(sizeof(PRIVATE_VM_DATA), MmCached, MaxAddrSizes.Bitmap.PhysicalAddressBits));
  33.             if (!g_Shared.Processors[i])
  34.             {
  35.                 for (ULONG j = 0; j < ProcessorsCount; ++j)
  36.                 {
  37.                     if (g_Shared.Processors[j])
  38.                     {
  39.                         FreePhys(g_Shared.Processors[j]);
  40.                     }
  41.                 }
  42.  
  43.                 VirtualMemory::FreePoolMemory(g_Shared.Processors);
  44.                 g_Shared.Processors = NULL;
  45.  
  46.                 return false;
  47.             }
  48.  
  49.             InitializeEptTables(&MtrrInfo, OUT &g_Shared.Processors[i]->Ept);
  50.         }
  51.  
  52.         KeIpiGenericCall([](ULONG_PTR Arg) -> ULONG_PTR
  53.         {
  54.             auto* Shared = reinterpret_cast<SHARED_VM_DATA*>(Arg);
  55.             VirtualizeProcessor(Shared);
  56.             return TRUE;
  57.         }, reinterpret_cast<ULONG_PTR>(&g_Shared));
  58.  
  59.         bool Status = static_cast<unsigned int>(g_Shared.VirtualizedProcessors) == g_Shared.ProcessorsCount;
  60.         if (!Status)
  61.         {
  62.             DevirtualizeAllProcessors();
  63.             VirtualMemory::FreePoolMemory(g_Shared.Processors);
  64.             g_Shared.Processors = NULL;
  65.         }
  66.  
  67.         return Status;
  68.     }
  69.  
  70.     ...
  71. }
  72.  
Здесь мы выделили память под структуры для каждого логического процессора и инициализировали EPT для каждого.
С этими данными мы можем начинать перевод логических процессоров в виртуальное окружение.
Вызываем функцию-заготовку VirtualizeProcessor на каждом логическом процессоре через IPI и по итогам смотрим, сколько логических процессоров было виртуализовано.
Если были виртуализованы не все - считаем, что в процессе виртуализации произошла ошибка и снимаем виртуализацию полностью.

А наполнением заготовок займёмся в следующей части. Итоговый код в аттаче. Итоговый код не прикрепляется из-за 414 Request URI Too Large...
А на сегодня всё. Всем спасибо, все свободны.

21 4.485
HoShiMin

HoShiMin
Well-Known Member

Регистрация:
17 дек 2016
Публикаций:
5

Комментарии


      1. Andrey333 8 окт 2022
        HoShiMin, вопрос снимаю. Сглупил. В самом начале же написано. =)

        27.2.3.1 EPT Misconfigurations
        An EPT misconfiguration occurs if translation of a guest-physical address encounters an EPT paging-structure that
        meets any of the following conditions:
        • Bit 0 of the entry is clear (indicating that data reads are not allowed) and bit 1 is set (indicating that data writes
        are allowed).
      2. Andrey333 7 окт 2022
        HoShiMin, а подскажи, пожалуйста! Пытаюсь перехватить чтение к странице, но вместо EptViolationHandler() срабатывает EptMisconfigurationHandler(). В EptMisconfigurationHandler() имею на руках физ. адрес невалидной страницы и указатели на PDE/PTE. Данные следующие:
        [​IMG]
        Где я ошибаюсь? Вроде бы все валидно.
      3. HoShiMin 14 авг 2022
        Andrey333 нравится это.
      4. Andrey333 13 авг 2022
        HoShiMin,

        >> ну, технически, нас, вроде, ничто не ограничивает в модификации EPT из гостя.
        Так как мы не пытаемся скрыть память гипервизора от гостя и живём в общем с ним адресном пространстве, мы можем атомарно что-то пропатчить в EPT.

        Ну да, для твоей реализации это вроде-бы так.

        >> Потом, правда, не помешает сбросить кэш трансляций, и в этот момент гипервизор, скорей всего, поймает invept (не помню, генерирует ли он vmexit на invept безусловно, но это уже мелочи).

        Да, invept генерирует vmexit безусловно. Получается если ты выполнишь invept в non-root тебе придется определять в invept-хэндлере кто выполнил инструкцию. Например проверкой RIP (входит ли в образ твоего драйвера).

        >> Если интересно, могу куда-нибудь залить.

        Да, было бы интересно посмотреть. Если не затруднит.
      5. HoShiMin 13 авг 2022
        Andrey333, да, unordered_map, конечно, дичь. Вечером пробегусь по своему новому гипервизору, там, вроде, ничего подобного больше нет. Если интересно, могу куда-нибудь залить.
        Andrey333 нравится это.
      6. HoShiMin 13 авг 2022
        Andrey333, ну, технически, нас, вроде, ничто не ограничивает в модификации EPT из гостя.
        Так как мы не пытаемся скрыть память гипервизора от гостя и живём в общем с ним адресном пространстве, мы можем атомарно что-то пропатчить в EPT. Потом, правда, не помешает сбросить кэш трансляций, и в этот момент гипервизор, скорей всего, поймает invept (не помню, генерирует ли он vmexit на invept безусловно, но это уже мелочи).
        Скорей всего, VMX на всё это ругаться не будет.
      7. Andrey333 13 авг 2022
        HoShiMin,

        >> Странно, что аллокации у меня вообще работали.

        Кстати. Ты ещё std::unordered_map юзаешь в VMExit.

        >> Ещё не помню, что будет, если из под гостя поменять EPT. Вылетит EPT_MISCONFIGURATION?

        https://rayanfam.com/topics/hypervisor-from-scratch-part-7

        2. Why we shouldn’t modify EPT in VMX Non-Root?
        In the ideal world, no memory of the hypervisor should be visible from the virtualized OS (you cant see XEN internals from the virtualized OS for example).
        in hyperplatform/hvpp, you can see the memory of the hypervisor. Why? This time it’s not because of HOST_CR3 but because of identity EPT mapping - you set EPT tables in such a way, that the virtualized OS can see even the memory of the hypervisor itself.
        My point is - in the ideal world you shouldn’t even see the EPT structures from within the VMX non-root mode, imagine it this way, can you modify regular page-tables from user-mode?
        The answer is it depends. In reality? No. Why? because the page-tables are in kernel memory that is inaccessible from the user-mode. That’s the whole point of memory protection. Could you set page tables in such a way that it would be possible to modify them from user-mode? Yes, but it doesn’t mean you should though. This is sort of a security thing.
        There’s one even more important reason: caches
        Now you might have tried it and it worked most of the time in your case but that doesn’t mean it’s the correct approach.
      8. HoShiMin 13 авг 2022
        Andrey333, да, так и надо. Странно, что аллокации у меня вообще работали.
        Ещё не помню, что будет, если из под гостя поменять EPT. Вылетит EPT_MISCONFIGURATION?
      9. Andrey333 13 авг 2022
        HoShiMin,

        >> Скорей всего vmcall не нужен - сходу не вижу причин, по которым он там был бы необходим.

        Ну как же. Он нужен. Ведь изменять EPT нужно в "VMX root operation". По идеи ты должен был элоцировать буфера в DPC-хэндлере и передать указатели в VMCALL.
      10. HoShiMin 10 авг 2022
        Andrey333, хм, внимательно посмотрел - вроде действительно выделяется в VMM… но так быть не должно.
        Код писал давно и сейчас уже не скажу, почему внутри DPC вызываю ещё vmcall. Скорей всего vmcall не нужен - сходу не вижу причин, по которым он там был бы необходим.
        Насколько помню, предполагалось, что все аллокации делаются в госте.
        Завтра посмотрю ещё в новом кб.
      11. Andrey333 9 авг 2022
        HoShiMin, я как понял ты поставляешь DPC, в DPC-handlers выполняешь инструкцию VMCALL и передаешь указатель на EptHandler::InterceptPage. Последняя (вызывается в обработчике VMCALL), алоццирует память в vmexit.
      12. Andrey333 9 авг 2022
      13. HoShiMin 9 авг 2022
        Andrey333, их тоже нельзя, разумеется.
        Но я нигде в обработчиках vmexit’ов память не выделяю.
      14. Andrey333 9 авг 2022
        HoShiMin, а подскажи пожалуйста. Смотрел твой проект Kernel-Bridge и заметил. В функциях EptHandler::InterceptPage/DeinterceptPage (которые вызываются через VMCALL) есть вызовы MmAllocateContiguousMemory/MmFreeContiguousMemory (через обертки Supplementation::AllocPhys/FreePhys). Но ведь в статьях пишут, что поскольку прерывания запрещены, то мы не можем вызывать функции с ограничением на уровень IRQL из VM-Exit. Эти функции все же можно?
      15. HoShiMin 14 дек 2021
        Andrey333, не обязательно. Внутри она лишь читает регистры в буфер - там нет ничего с требованием к IRQL.
        Andrey333 нравится это.
      16. Andrey333 14 дек 2021
        HoShiMin,

        >> Тем более, что для виртуализации не требуется действий, критичных к IRQL, а память под данные можно подготовить заранее.

        А как же получение контекста через RtlCaptureContext? Её же нужно вызывать при IRQL <= DISPATCH_LEVEL.
      17. HoShiMin 4 дек 2021
        Andrey333, да, посмотрел в доках - действительно, форсить смену контекста не надо
      18. Andrey333 4 дек 2021
        HoShiMin, спасибо за ответ! Воспользуюсь KeIpiGenericCall.

        >> можешь и на PASSIVE_LEVEL, только не забудь вызвать ZwYieldExecution после KeSetSystemAffinityThread, т.к. поток переключается на ядро не сразу, а на следующем переключении контекста

        Судя по документации, это не так:

        KeSetSystemAffinityThread
        If KeSetSystemAffinityThread is called at IRQL <= APC_LEVEL and the call is successful, the new affinity mask takes effect immediately. When the call returns, the calling thread is already running on a processor that is specified in the new affinity mask.
      19. HoShiMin 3 дек 2021
        Andrey333, можешь и на PASSIVE_LEVEL, только не забудь вызвать ZwYieldExecution после KeSetSystemAffinityThread, т.к. поток переключается на ядро не сразу, а на следующем переключении контекста.
        Но всё же, чтобы избежать каких-либо нежелательных сайд-эффектов, рекомендую виртуализовать процессор атомарно - в IPI или через доставку DPC каждому ядру.
        Тем более, что для виртуализации не требуется действий, критичных к IRQL, а память под данные можно подготовить заранее.
        Andrey333 нравится это.
      20. Andrey333 3 дек 2021
        HoShiMin, Спасибо за статьи!

        >> Так как обработчики IPI выполняются на высоком IRQL (IPI_LEVEL), нам необходимо подготовить все данные, которые могут понадобиться коду виртуализации.

        В случае, если я не KeIpiGenericCall использую, а KeSetSystemAffinityThreadEx/KeRevertToUserAffinityThreadEx. Мне нужно повышать IRQL, или я могу на PASSIVE_LEVEL выполнить всю виртуализацию (__vmx_on, запонение VMCS и т.д.)?
      21. HoShiMin 21 июн 2020
        Mikl___, глянь, что не так с nginx'ом? Не могу прикрепить архив в аттач - 414 Request URI Too Large.