Использование с плавающей запятой в драйвере WDM
Последнее обновление
- Июль 2016 г.
Драйверы WDM в режиме ядра для Windows должны следовать определенным рекомендациям при использовании операций с плавающей запятой. Они различаются в системах x86 и x64. По умолчанию Windows отключает арифметические исключения для обеих систем.
Системы x86
Драйверы WDM в режиме ядра для систем x86 должны использовать вычисления с плавающей запятой между вызовами KeSaveExtendedProcessorState и KeRestoreExtendedProcessorState. Операции с плавающей запятой должны быть помещены в нестроковую подпрограмму, чтобы убедиться, что вычисления с плавающей запятой не выполняются перед проверкой возвращаемого значения KeSaveExtendedProcessorState из-за переупорядочения компилятором.
Компилятор использует для таких вычислений регистры MMX/x87, также известные как регистры единиц с плавающей запятой (FPU), которые могут одновременно использоваться приложением пользовательского режима. Сбой сохранения этих регистров перед их использованием или их восстановление по завершении может привести к ошибкам вычислений в приложениях.
Драйверы для систем x86 могут вызывать KeSaveExtendedProcessorState и выполнять вычисления с плавающей запятой в IRQL <= DISPATCH_LEVEL. Операции с плавающей запятой не поддерживаются в подпрограммах обслуживания прерываний (ISR) в системах x86.
Системы x64
64-разрядный компилятор не использует регистры MMX/x87 для операций с плавающей запятой. Вместо этого он использует регистры SSE. Коду режима ядра x64 запрещен доступ к регистрам MMX/x87. Компилятор также заботится о правильном сохранении и восстановлении состояния SSE, поэтому вызовы KeSaveExtendedProcessorState и KeRestoreExtendedProcessorState являются ненужными , а операции с плавающей запятой можно использовать в ISR. Использование других расширенных функций процессора, таких как AVX, требует сохранения и восстановления расширенного состояния. Дополнительные сведения см. в статье Использование расширенных функций процессора в драйверах Windows.
Примечание. Arm64 в целом похож на AMD64 тем, что вам не нужно сначала вызывать сохранение состояния с плавающей запятой. Однако код, который необходимо переносить в x86 на ядрах, может по-прежнему выполнять это, чтобы быть кроссплатформенным.
Пример
В следующем примере показано, как драйвер WDM должен упаковывать доступ к FPU:
__declspec(noinline)
VOID
DoFloatingPointCalculation(
VOID
)
{
double Duration;
LARGE_INTEGER Frequency;
Duration = 1000000.0;
DbgPrint("%I64x\n", *(LONGLONG*)&Duration);
KeQueryPerformanceCounter(&Frequency);
Duration /= (double)Frequency.QuadPart;
DbgPrint("%I64x\n", *(LONGLONG*)&Duration);
}
NTSTATUS
DriverEntry(
_In_ PDRIVER_OBJECT DriverObject,
_In_ PUNICODE_STRING RegistryPath
)
{
XSTATE_SAVE SaveState;
NTSTATUS Status;
Status = KeSaveExtendedProcessorState(XSTATE_MASK_LEGACY, &SaveState);
if (!NT_SUCCESS(Status)) {
goto exit;
}
__try {
DoFloatingPointCalculation();
}
__finally {
KeRestoreExtendedProcessorState(&SaveState);
}
exit:
return Status;
}
В этом примере назначение переменной с плавающей запятой происходит между вызовами KeSaveExtendedProcessorState и KeRestoreExtendedProcessorState. Так как любое назначение переменной с плавающей запятой использует FPU, драйверы должны убедиться, что KeSaveExtendedProcessorState вернулся без ошибок перед инициализацией такой переменной.
Приведенные выше вызовы являются ненужными в системе x64 и безвредны при указании флага XSTATE_MASK_LEGACY. Поэтому нет необходимости изменять код при компиляции драйвера для системы x64.
В системах на базе x86 FPU сбрасывается в состояние по умолчанию путем вызова FNINIT при возвращении из KeSaveExtendedProcessorState.