Написание Hello World драйвера Windows (KMDF)

В этой статье описывается, как написать небольшой универсальный драйвер Windows с помощью Kernel-Mode Driver Framework (KMDF), а затем развернуть и установить драйвер на отдельном компьютере.

Прежде чем продолжить, выполните действия по установке, перечисленные в разделе Скачивание пакета драйверов Windows (WDK).

Средства отладки для Windows включаются при установке WDK.

Создание и сборка драйвера

  1. Откройте среду Microsoft Visual Studio. В меню Файл выберите Пункт Создать > проект.

  2. В диалоговом окне Создание проекта выберите C++ в раскрывающемся списке слева, в среднем раскрывающемся списке выберите Windows , а в раскрывающемся списке справа выберите Драйвер .

  3. Выберите Драйвер режима ядра, пустой (KMDF) из списка типов проектов. Выберите Далее.

    Снимок экрана: диалоговое окно создания проекта Visual Studio с выбранным параметром драйвера в режиме ядра.

  4. В диалоговом окне Настройка нового проекта введите "KmdfHelloWorld" в поле Имя проекта .

    Примечание

    При создании нового драйвера KMDF или UMDF необходимо выбрать имя драйвера, которое содержит не более 32 символов. Это ограничение длины определяется в wdfglobals.h.

  5. В поле Расположение введите каталог, в котором вы хотите создать проект.

  6. Установите флажок Разместить решение и проект в одном каталоге и выберите Создать.

    Снимок экрана: диалоговое окно

    Visual Studio создает один проект и решение. Вы можете просмотреть их в обозревателе решений. (Если окно Обозреватель решений не отображается, выберите Обозреватель решений в меню Вид.) Решение содержит проект драйвера с именем KmdfHelloWorld.

    Снимок экрана: окно обозревателя решений Visual Studio, в котором отображается решение и пустой проект драйвера с именем KmdfHelloWorld.

  7. В окне Обозреватель решений выберите и удерживайте (или щелкните правой кнопкой мыши) решение KmdfHelloWorld и выберите Configuration Manager. Выберите конфигурацию и платформу для проекта драйвера. Например, выберите Отладка и x64.

  8. В окне Обозреватель решений снова выберите и удерживайте (или щелкните правой кнопкой мыши) проект KmdfHelloWorld, выберите Добавить, а затем новый элемент.

  9. В диалоговом окне Добавление нового элемента выберите Файл C++. В поле Имя введите Driver.c.

    Примечание

    Расширение имени файла — .c, а не .cpp.

    Выберите Добавить. Файл Driver.c будет добавлен в раздел Исходные файлы, как показано здесь.

    Снимок экрана: окно обозревателя решений Visual Studio с файлом driver.c, добавленным в проект драйвера.

Написание первого кода драйвера

Теперь, когда вы создали пустой проект Hello World и добавили исходный файл Driver.c, вы напишете самый простой код, необходимый для запуска драйвера, реализовав две основные функции обратного вызова событий.

  1. В Driver.c начните с включения следующих заголовков:

    #include <ntddk.h>
    #include <wdf.h>
    

    Совет

    Если вы не можете добавить Ntddk.h, откройте раздел Конфигурация —> C/C++ —> Общие —> Дополнительные каталоги включения и добавьте C:\Program Files (x86)\Windows Kits\10\Include\<build#>\km, заменив <build#> соответствующим каталогом в установке WDK.

    Ntddk.h содержит основные определения ядра Windows для всех драйверов, а Wdf.h — определения драйверов на основе Платформы драйверов Windows (WDF).

  2. Затем укажите объявления для двух используемых обратных вызовов:

    DRIVER_INITIALIZE DriverEntry;
    EVT_WDF_DRIVER_DEVICE_ADD KmdfHelloWorldEvtDeviceAdd;
    
  3. Используйте следующий код для написания DriverEntry:

    NTSTATUS 
    DriverEntry(
        _In_ PDRIVER_OBJECT     DriverObject, 
        _In_ PUNICODE_STRING    RegistryPath
    )
    {
        // NTSTATUS variable to record success or failure
        NTSTATUS status = STATUS_SUCCESS;
    
        // Allocate the driver configuration object
        WDF_DRIVER_CONFIG config;
    
        // Print "Hello World" for DriverEntry
        KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "KmdfHelloWorld: DriverEntry\n" ));
    
        // Initialize the driver configuration object to register the
        // entry point for the EvtDeviceAdd callback, KmdfHelloWorldEvtDeviceAdd
        WDF_DRIVER_CONFIG_INIT(&config, 
                               KmdfHelloWorldEvtDeviceAdd
                               );
    
        // Finally, create the driver object
        status = WdfDriverCreate(DriverObject, 
                                 RegistryPath, 
                                 WDF_NO_OBJECT_ATTRIBUTES, 
                                 &config, 
                                 WDF_NO_HANDLE
                                 );
        return status;
    }
    

    DriverEntry — это точка входа для всех драйверов, как Main() и для многих приложений в пользовательском режиме. Задача DriverEntry — инициализировать структуры и ресурсы на уровне драйвера. В этом примере вы напечатали "Hello World" для DriverEntry, настроили объект driver для регистрации точки обратного вызова EvtDeviceAdd, а затем создали объект драйвера и вернули его.

    Объект драйвера выступает в качестве родительского объекта для всех других объектов платформы, которые можно создать в драйвере, включая объекты устройств, очереди ввода-вывода, таймеры, спин-блокировки и многое другое. Дополнительные сведения об объектах платформы см. в статье Общие сведения об объектах платформы.

    Совет

    Для DriverEntry настоятельно рекомендуется оставить имя DriverEntry, чтобы упростить анализ кода и отладку.

  4. Затем используйте следующий код для записи kmdfHelloWorldEvtDeviceAdd:

    NTSTATUS 
    KmdfHelloWorldEvtDeviceAdd(
        _In_    WDFDRIVER       Driver, 
        _Inout_ PWDFDEVICE_INIT DeviceInit
    )
    {
        // We're not using the driver object,
        // so we need to mark it as unreferenced
        UNREFERENCED_PARAMETER(Driver);
    
        NTSTATUS status;
    
        // Allocate the device object
        WDFDEVICE hDevice;    
    
        // Print "Hello World"
        KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "KmdfHelloWorld: KmdfHelloWorldEvtDeviceAdd\n" ));
    
        // Create the device object
        status = WdfDeviceCreate(&DeviceInit, 
                                 WDF_NO_OBJECT_ATTRIBUTES,
                                 &hDevice
                                 );
        return status;
    }
    

    Функция EvtDeviceAdd вызывается системой, когда обнаруживает, что устройство прибыло. Его задача заключается в инициализации структур и ресурсов для этого устройства. В этом примере вы просто распечатали сообщение "Hello World" для EvtDeviceAdd, создали объект устройства и вернули его. В других записываемых вами драйверах можно создавать очереди ввода-вывода для оборудования, настраивать хранилище контекста устройства для сведений, относящихся к устройству, или выполнять другие задачи, необходимые для подготовки устройства.

    Совет

    Чтобы добавить обратный вызов устройства, обратите внимание, как вы назвали его с именем драйвера в качестве префикса (KmdfHelloWorldEvtDeviceAdd). Как правило, рекомендуется именовать функции драйвера таким образом, чтобы отличать их от функций других драйверов. DriverEntry — это единственное имя.

  5. Полный файл Driver.c теперь выглядит следующим образом:

    #include <ntddk.h>
    #include <wdf.h>
    DRIVER_INITIALIZE DriverEntry;
    EVT_WDF_DRIVER_DEVICE_ADD KmdfHelloWorldEvtDeviceAdd;
    
    NTSTATUS 
    DriverEntry(
        _In_ PDRIVER_OBJECT     DriverObject, 
        _In_ PUNICODE_STRING    RegistryPath
    )
    {
        // NTSTATUS variable to record success or failure
        NTSTATUS status = STATUS_SUCCESS;
    
        // Allocate the driver configuration object
        WDF_DRIVER_CONFIG config;
    
        // Print "Hello World" for DriverEntry
        KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "KmdfHelloWorld: DriverEntry\n" ));
    
        // Initialize the driver configuration object to register the
        // entry point for the EvtDeviceAdd callback, KmdfHelloWorldEvtDeviceAdd
        WDF_DRIVER_CONFIG_INIT(&config, 
                               KmdfHelloWorldEvtDeviceAdd
                               );
    
        // Finally, create the driver object
        status = WdfDriverCreate(DriverObject, 
                                 RegistryPath, 
                                 WDF_NO_OBJECT_ATTRIBUTES, 
                                 &config, 
                                 WDF_NO_HANDLE
                                 );
        return status;
    }
    
    NTSTATUS 
    KmdfHelloWorldEvtDeviceAdd(
        _In_    WDFDRIVER       Driver, 
        _Inout_ PWDFDEVICE_INIT DeviceInit
    )
    {
        // We're not using the driver object,
        // so we need to mark it as unreferenced
        UNREFERENCED_PARAMETER(Driver);
    
        NTSTATUS status;
    
        // Allocate the device object
        WDFDEVICE hDevice;    
    
        // Print "Hello World"
        KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "KmdfHelloWorld: KmdfHelloWorldEvtDeviceAdd\n" ));
    
        // Create the device object
        status = WdfDeviceCreate(&DeviceInit, 
                                 WDF_NO_OBJECT_ATTRIBUTES,
                                 &hDevice
                                 );
        return status;
    }
    
  6. Сохраните Driver.c.

В этом примере показана основная концепция драйверов: это "коллекция обратных вызовов", которые после инициализации находятся и ожидают, пока система вызовет их, когда ей что-то нужно. Системный вызов может быть событием поступления нового устройства, запросом ввода-вывода из приложения пользовательского режима, событием выключения системы питания, запросом от другого драйвера или событием неожиданного удаления, когда пользователь неожиданно отключает устройство. К счастью, чтобы сказать "Hello World", вам нужно только беспокоиться о создании драйвера и устройства.

Далее вы создадите драйвер.

Сборка драйвера

  1. В окне Обозреватель решений выберите и удерживайте (или щелкните правой кнопкой мыши) Решение KmdfHelloWorld (1 проект) и выберите Configuration Manager. Выберите конфигурацию и платформу для проекта драйвера. Для этого упражнения мы выбираем Отладка и x64.

  2. В окне Обозреватель решений выберите и удерживайте (или щелкните правой кнопкой мыши) KmdfHelloWorld и выберите Свойства. В разделе Параметры трассировки > Wpp установите для параметра Запустить трассировку Wpp значение Нет. Нажмите кнопку Apply (Применить), а затем — ОК.

  3. Чтобы выполнить сборку драйвера, выберите в меню Сборкапункт Сборка решения. Visual Studio отображает ход выполнения сборки в окне Вывода . (Если окно Вывод не отображается, выберите Вывод в меню Вид .) Убедився, что решение успешно создано, можно закрыть Visual Studio.

  4. Чтобы просмотреть встроенный драйвер, в проводник перейдите в папку KmdfHelloWorld, а затем в папку x64\Debug\KmdfHelloWorld. В папку входят:

    • KmdfHelloWorld.sys — файл драйвера в режиме ядра.
    • KmdfHelloWorld.inf — информационный файл, который Windows использует при установке драйвера.
    • KmdfHelloWorld.cat — файл каталога, используемый установщиком для проверки тестовой сигнатуры драйвера.

Совет

Если вы видите DriverVer set to a date in the future при сборке драйвера, измените параметры проекта драйвера, чтобы Inf2Cat установил ./uselocaltime Для этого используйте свойства конфигурации-Inf2Cat-General-Use>>> Local Time. Теперь и Stampinf , и Inf2Cat используют местное время.

Развертывание драйвера

Обычно при тестировании и отладке драйвера отладчик и драйвер выполняются на разных компьютерах. Компьютер, на котором выполняется отладчик, называется главным компьютером, а компьютер, на котором выполняется драйвер, — конечным компьютером. Целевой компьютер также называется тестовый компьютер.

До сих пор вы использовали Visual Studio для создания драйвера на хост-компьютере. Теперь необходимо настроить целевой компьютер.

  1. Следуйте инструкциям в статье Подготовка компьютера для развертывания и тестирования драйвера (WDK 10).

    Совет

    При выполнении действий по автоматической подготовке целевого компьютера с помощью сетевого кабеля запишите порт и ключ. Они будут использоваться позже на этапе отладки. В этом примере мы будем использовать 50000 в качестве порта и 1.2.3.4 в качестве ключа.

    В сценариях отладки реальных драйверов рекомендуется использовать ключ, созданный KDNET. Дополнительные сведения об использовании KDNET для создания случайного ключа см. в разделе Пошаговая лабораторная работа драйверов отладки (режим ядра Sysvad).

  2. На хост-компьютере откройте решение в Visual Studio. Вы можете дважды щелкнуть файл решения KmdfHelloWorld.sln в папке KmdfHelloWorld.

  3. В окне Обозреватель решений выберите и удерживайте (или щелкните правой кнопкой мыши) проект KmdfHelloWorld и выберите Свойства.

  4. В окне Страницы свойств KmdfHelloWorld перейдите к разделу Свойства > конфигурации Установка развертывания драйвера>, как показано ниже.

  5. Установите флажок Удалить предыдущие версии драйверов перед развертыванием.

  6. В поле Имя целевого устройства выберите имя компьютера, настроенного для тестирования и отладки. В этом упражнении мы используем компьютер с именем MyTestComputer.

  7. Выберите Hardware ID Driver Update (Обновление драйвера и введите идентификатор оборудования для драйвера). В этом упражнении используется идентификатор оборудования Root\KmdfHelloWorld. Щелкните ОК.

    Снимок экрана: окно страниц свойств kmdfhelloworld с выбранным параметром установки драйвера развертывания.

    Примечание

    В этом упражнении идентификатор оборудования не идентифицирует реальную часть оборудования. Он идентифицирует мнимое устройство, которому будет предоставлено место в дереве устройств в качестве дочернего элемента корневого узла. Для реального оборудования не выбирайте обновление драйвера идентификатора оборудования; Вместо этого выберите Установить и проверить. Вы увидите идентификатор оборудования в файле сведений о драйвере (INF). В окне Обозреватель решений перейдите в раздел Файлы драйверов KmdfHelloWorld >и дважды щелкните файл KmdfHelloWorld.inf. Идентификатор оборудования находится в разделе [Standard.NT$ARCH$].

    [Standard.NT$ARCH$]
    %KmdfHelloWorld.DeviceDesc%=KmdfHelloWorld_Device, Root\KmdfHelloWorld
    
  8. В меню Сборка выберите Развернуть решение. Visual Studio автоматически копирует файлы, необходимые для установки и запуска драйвера, на целевой компьютер. Развертывание может занять одну или две минуты.

    При развертывании драйвера файлы драйвера копируются в папку %Systemdrive%\drivertest\drivers на тестовом компьютере. Если во время развертывания что-то пойдет не так, можно проверка, чтобы узнать, копируются ли файлы на тестовый компьютер. Убедитесь, что файлы INF, CAT, test cert и .sys, а также все необходимые файлы находятся в папке %systemdrive%\drivertest\drivers.

    Дополнительные сведения о развертывании драйверов см. в разделе Развертывание драйвера на тестовом компьютере.

Установка драйвера

Теперь, когда драйвер Hello World развернут на целевом компьютере, вы установите драйвер. Когда вы ранее подготовили целевой компьютер с помощью Visual Studio с помощью автоматического параметра, Visual Studio настроила целевой компьютер для запуска тестовых подписанных драйверов в рамках процесса подготовки. Теперь необходимо просто установить драйвер с помощью средства DevCon.

  1. На хост-компьютере перейдите в папку Сервис в установке WDK и найдите средство DevCon. Например, найдите следующую папку:

    C:\Program Files (x86)\Windows Kits\10\Tools\x64\devcon.exe

    Скопируйте средство DevCon на удаленный компьютер.

  2. На целевом компьютере установите драйвер, перейдя в папку, содержащую файлы драйверов, а затем запустив средство DevCon.

    1. Ниже приведен общий синтаксис средства devcon, который будет использоваться для установки драйвера:

      Devcon install <INF file><hardware ID>

      Для установки этого драйвера требуется INF-файл KmdfHelloWorld.inf. INF-файл содержит идентификатор оборудования для установки двоичного файла драйвера ,KmdfHelloWorld.sys. Помните, что идентификатор оборудования, расположенный в INF-файле, имеет значение Root\KmdfHelloWorld.

    2. Откройте окно командной строки от имени администратора. Перейдите в папку, содержащую файл встроенного драйвера .sys, и введите следующую команду:

      devcon install kmdfhelloworld.inf root\kmdfhelloworld

      Если появляется сообщение об ошибке о том, что devcon не распознается, попробуйте добавить путь к средству devcon . Например, если вы скопировали его в папку на целевом компьютере C:\Tools, попробуйте использовать следующую команду:

      c:\tools\devcon install kmdfhelloworld.inf root\kmdfhelloworld

      Появится диалоговое окно, указывающее, что тестовый драйвер является неподписанным драйвером. Выберите Установить этот драйвер в любом случае , чтобы продолжить.

      Снимок экрана: предупреждение системы безопасности, отображаемое во время установки драйвера.

Отладка драйвера

Теперь, когда вы установили драйвер KmdfHelloWorld на целевом компьютере, вы подключите отладчик удаленно с главного компьютера.

  1. На хост-компьютере откройте окно командной строки с правами администратора. Перейдите в каталог WinDbg.exe. Мы будем использовать x64version WinDbg.exe из комплекта драйверов Windows (WDK), который был установлен в рамках установки комплекта Windows. Ниже приведен путь по умолчанию к WinDbg.exe:

    C:\Program Files (x86)\Windows Kits\10\Debuggers\x64

  2. Запустите WinDbg, чтобы подключиться к сеансу отладки ядра на целевом компьютере с помощью следующей команды. Значения порта и ключа должны совпадать с значениями, которые использовались для подготовки целевого компьютера. Мы будем использовать 50000 для порта и 1.2.3.4 для ключа, значения, которые использовались на этапе развертывания. Флаг k указывает, что это сеанс отладки ядра.

    WinDbg -k net:port=50000,key=1.2.3.4

  3. В меню Отладка выберите Пункт Прервать. Отладчик на хост-компьютере войдет в целевой компьютер. В окне Команда отладчика отображается командная строка отладки ядра: kd>.

  4. На этом этапе можно поэкспериментировать с отладчиком, введя команды в командной строке kd> . Например, можно попробовать следующие команды:

  5. Чтобы снова запустить целевой компьютер, выберите Перейти в меню Отладка или нажмите клавишу "g", а затем нажмите клавишу "ВВОД".

  6. Чтобы остановить сеанс отладки, выберите команду Отсоединить отладчик в меню Отладка .

    Важно!

    Убедитесь, что вы используете команду go, чтобы позволить целевому компьютеру снова запуститься перед выходом из отладчика, иначе целевой компьютер не будет отвечать на ввод с помощью мыши и клавиатуры, так как он по-прежнему взаимодействует с отладчиком.

Подробное пошаговое руководство по процессу отладки драйверов см. в разделе Отладка универсальных драйверов — пошаговая лабораторная работа (режим ядра Echo).

Дополнительные сведения об удаленной отладке см. в статье Удаленная отладка с помощью WinDbg.

Средства отладки для Windows

Отладка универсальных драйверов — пошаговая лабораторная работа (режим ядра Echo)

Написание первого драйвера