Использование с плавающей запятой в драйвере 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.