Руководство по написанию драйвера Windows Hello World (Kernel-Mode Driver Framework)
В этой статье описывается, как написать небольшой универсальный драйвер Windows с помощью Kernel-Mode Driver Framework (KMDF), а затем развернуть и установить драйвер на отдельном компьютере.
Необходимые условия
Выполните действия по установке комплекта драйверов Windows (WDK). Средства отладки для Windows включаются при установке WDK.
Установите Visual Studio 2022. При установке Visual Studio 2022 выберите рабочую среду Рабочая среда для разработки на C++, а затем в разделе Отдельные компоненты добавьте:
- MSVC версии 143 — VS 2022 C++ ARM64/ARM64EC библиотеки с ослаблением уязвимостей Spectre (последние)
- MSVC v143 — VS 2022 C++ x64/x86 библиотеки с защитой от Spectre (последняя версия)
- ATL C++ для последних средств сборки версии 143 с устранением рисков Spectre (ARM64/ARM64EC)
- ATL для C++ для последних инструментов сборки версии v143 с устранением уязвимостей Spectre (x86 & x64)
- C++ MFC для последних средств сборки версии 143 с устранением рисков Spectre (ARM64/ARM64EC)
- C++ MFC для последних средств сборки версии 143 с мерами защиты от уязвимостей Spectre (x86 & x64)
- Комплект драйверов Windows
Создание и сборка драйвера
Откройте Microsoft Visual Studio. В меню файла выберите "Новый проект" >.
В диалоговом окне Создание проекта выберите C++ в раскрывающемся списке слева, выберите Windows в среднем раскрывающемся списке и выберите Driver в правом раскрывающемся списке.
Выберите драйвер режима ядра, пустой (KMDF) из списка типов проектов. Выберите Далее.
Совет
Если вы не можете найти шаблоны проектов драйверов в Visual Studio, расширение WDK Visual Studio не было установлено должным образом. Чтобы устранить эту проблему, запустите установщик Visual Studio, выберите Изменить, добавьте комплекты драйверов Windows в разделе Индивидуальные компоненты и выберите Изменить.
В диалоговом окне Настройка нового проекта введите "KmdfHelloWorld" в поле имени проекта.
Заметка
При создании нового драйвера KMDF или UMDF необходимо выбрать имя драйвера с 32 символами или меньше. Это ограничение длины определяется в wdfglobals.h.
В поле расположение введите каталог, в котором нужно создать проект.
Проверьте Разместить решение и проект в том же каталоге и выберите Создать.
Visual Studio создает один проект и решение. Их можно увидеть в окне обозревателя решений. (Если окно обозревателя решений не отображается, выберите обозреватель решений в меню представления.) Решение имеет проект драйвера с именем KmdfHelloWorld.
В окне Обозревателя решений щелкните правой кнопкой мыши по Решению 'KmdfHelloWorld' (1 из 1 проекта) и выберите Диспетчер конфигураций. Выберите конфигурацию и платформу для проекта драйвера. Например, выберите отладки и x64.
В окне обозревателя решений щелкните правой кнопкой мыши на проекте KmdfHelloWorld, выберите Добавить, и затем выберите Новый элемент.
В диалоговом окне Добавление нового элемента введите Driver.c.
Заметка
Расширение имени файла .c, а не .cpp.
Выберите Добавить. Файл Driver.c добавляется в разделе исходных файлов, как показано здесь.
Написание первого кода драйвера
Теперь, когда вы создали пустой проект Hello World и добавили исходный файл Driver.c, вы напишете самый простой код, необходимый для запуска драйвера, реализуя две основные функции обратного вызова событий.
В 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).
Затем предоставьте определения для двух обратных вызовов.
DRIVER_INITIALIZE DriverEntry; EVT_WDF_DRIVER_DEVICE_ADD KmdfHelloWorldEvtDeviceAdd;
Используйте следующий код для записи 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, настроили объект драйвера для регистрации точки входа обратного вызова EvtDeviceAdd, а затем создали объект драйвера и вернулись.Объект драйвера выступает в качестве родительского объекта для всех других объектов платформы, которые могут создаваться в драйвере, включая объекты устройств, очереди ввода-вывода, таймеры, спинлоки и многое другое. Дополнительные сведения о объектах платформы см. в разделе Введение в объекты Framework.
Совет
Для DriverEntryнастоятельно рекомендуется сохранить имя DriverEntry, чтобы помочь в анализе кода и отладке.
Затем напишите 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 является единственным, который следует называть именно так.
Теперь ваш полный 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; }
Сохраните Driver.c.
В этом примере показана фундаментальная концепция драйверов: они являются "коллекцией обратных вызовов", которая после инициализации, сидит и ожидает, пока система будет вызывать их, когда ей требуется что-то. Системный вызов может быть новым событием прибытия устройства, запросом ввода-вывода из приложения пользовательского режима, событием завершения работы системы, запросом от другого драйвера или событием неожиданного удаления, когда пользователь неожиданно отключает устройство. К счастью, чтобы сказать "Hello World", вам нужно только беспокоиться о создании драйверов и устройств.
Затем вы создадите драйвер.
Создание драйвера
В окне обозревателя решений щелкните правой кнопкой мыши по Решение 'KmdfHelloWorld' (1 из 1 проекта) и выберите Диспетчер конфигурации. Выберите конфигурацию и платформу для проекта драйвера. Для этого упражнения выберите Отладка и x64.
В окне Обозреватель решений правой кнопкой мыши щелкните по KmdfHelloWorld и выберите Свойства. В Следе Wpp > Все опции, установите Выполнить трассировку Wpp на Нет. Выберите Применить, а затем ОК.
Чтобы создать драйвер, выберите Сборка решения в меню Сборка. Visual Studio отображает ход сборки в окне выходных данных. (Если окно вывода не отображается, выберите вывод из меню Представление.) Убедившись, что решение собрано успешно, можно закрыть Visual Studio.
Чтобы просмотреть встроенный драйвер в проводнике, перейдите в папку KmdfHelloWorld, а затем x64\Debug\KmdfHelloWorld. Папка включает:
- KmdfHelloWorld.sys — файл драйвера в режиме ядра
- KmdfHelloWorld.inf - информационный файл, который Windows использует при установке драйвера.
- KmdfHelloWorld.cat — файл каталога, который установщик использует для проверки тестовой подписи драйвера.
Совет
Если вы видите DriverVer set to a date in the future
при создании драйвера, измените параметры проекта драйвера таким образом, чтобы Inf2Cat устанавливал /uselocaltime
. Для этого используйте свойства конфигурации inf2Cat->>general->использоватьлокального времени. Теперь оба Stampinf и Inf2Cat используют локальное время.
Развертывание драйвера
Обычно при тестировании и отладке драйвера отладчик и драйвер выполняются на отдельных компьютерах. Компьютер, на котором запущен отладчик, называется компьютером хоста, а компьютер, на котором запущен драйвер, называется компьютером цели. Целевой компьютер также называется тестовым компьютером.
До сих пор вы использовали Visual Studio для создания драйвера на хост-компьютере. Теперь необходимо настроить целевой компьютер.
Следуйте инструкциям в по подготовке компьютера для развертывания и тестирования драйверов (WDK 10).
Совет
При выполнении действий по автоматической подготовке целевого компьютера с использованием кабельной сети обратите внимание на порт и ключ. Вы будете использовать их позже в шаге отладки. В этом примере в качестве порта используется 50000, а в качестве ключа — 1.2.3.4.
В реальных сценариях отладки драйверов рекомендуется использовать ключ, созданный KDNET. Дополнительные сведения о том, как использовать KDNET для создания случайного ключа, см. в статье Пошаговая лаборатория по драйверам отладки (режим ядра Sysvad).
На хост-компьютере откройте решение в Visual Studio. Вы можете дважды щелкнуть файл решения KmdfHelloWorld.sln в папке KmdfHelloWorld.
В окне обозревателя решений щелкните правой кнопкой мыши проект KmdfHelloWorld и выберите Свойства.
Перейдите к установке и развертыванию драйвера: >.
Для имени целевого устройства выберите имя компьютера, настроенного для тестирования и отладки. В этом упражнении мы используем компьютер с именем MyTestComputer.
Чтобы убедиться, что вы тестируете последнюю версию драйвера, проверьте Удалите предыдущие версии драйверов перед развертыванием.
Выберите обновление драйвера по идентификатору оборудования и введите идентификатор оборудования для вашего драйвера. Для этого упражнения идентификатор оборудования — Root\KmdfHelloWorld. Выберите опцию OK.
Заметка
В этом упражнении идентификатор оборудования не определяет реальный элемент оборудования. Идентифицируется мнимое устройство, которое располагается в дереве устройств в качестве дочернего элемента корневого узла. Для реального оборудования не выбирайте обновление драйвера идентификатора оборудования; вместо этого выберите Установить и проверить. Идентификатор оборудования отображается в файле сведений о драйвере (INF). В окне обозревателя решений перейдите в KmdfHelloWorld > Файлы драйверов, и дважды щелкните KmdfHelloWorld.inf. Идентификатор оборудования расположен в разделе [Standard.NT$ARCH$].
[Standard.NT$ARCH$] %KmdfHelloWorld.DeviceDesc%=KmdfHelloWorld_Device, Root\KmdfHelloWorld
В меню Сборка выберите Развернуть решение. Visual Studio автоматически копирует файлы, необходимые для установки и запуска драйвера на целевой компьютер. Развертывание может занять минуту или два.
При развертывании драйвера файлы драйверов копируются в папку %Systemdrive%\drivertest\drivers на тестовом компьютере. Если во время развертывания что-то не так, можно проверить, копируются ли файлы на тестовый компьютер. Убедитесь, что в папке %systemdrive%\drivertest\drivertest\drivers присутствуют файлы INF, CAT, test cert и .sys и любые другие необходимые файлы.
Дополнительные сведения о развертывании драйверов см.: Развертывание драйвера на тестовом компьютере.
Установка драйвера
С помощью драйвера Hello World, развернутого на целевом компьютере, теперь вы устанавливаете драйвер. Когда вы ранее настроили целевой компьютер с помощью Visual Studio, выбрав параметр автоматического, Visual Studio настроила целевой компьютер на запуск тестовых подписанных драйверов как часть процесса настройки. Теперь необходимо просто установить драйвер с помощью средства DevCon.
На хост-компьютере перейдите в папку Tools в установке WDK и найдите средство DevCon. Например, просмотрите следующую папку:
C:\Program Files (x86)\Windows Kits\10\Tools\x64\devcon.exe
Скопируйте средство DevCon на удаленный компьютер.
На целевом компьютере установите драйвер, перейдя к папке, содержащей файлы драйверов, а затем запустите средство DevCon.
Вот общий синтаксис для инструмента devcon, который вы будете использовать для установки драйвера:
установка <INF-файла><идентификатора оборудования>
INF-файл, необходимый для установки этого драйвера, — KmdfHelloWorld.inf. INF-файл содержит идентификатор оборудования для установки двоичного файла драйвера KmdfHelloWorld.sys. Помните, что идентификатор оборудования, находящийся в файле INF, — Root\KmdfHelloWorld.
Откройте окно командной строки от имени администратора. Перейдите в папку со встроенным файлом драйвера .sys и введите следующую команду:
devcon install kmdfhelloworld.inf root\kmdfhelloworld
Если появится сообщение об ошибке, что devcon не распознается, попробуйте добавить путь к инструменту devcon. Например, если вы скопировали его в папку на целевом компьютере с именем C:\Tools, попробуйте выполнить следующую команду:
c:\tools\devcon install kmdfhelloworld.inf root\kmdfhelloworld
Откроется диалоговое окно, указывающее, что проверяемый драйвер является драйвером без цифровой подписи. Выберите Установить этот драйвер в любом случае продолжить.
Отладка драйвера
Теперь, когда вы установили драйвер KmdfHelloWorld на целевом компьютере, подключите отладчик удаленно с хост-компьютера.
На хост-компьютере откройте окно командной строки от имени администратора. Перейдите в каталог WinDbg.exe. Вы используете x64-версию WinDbg.exe из комплекта драйверов Windows (WDK), который был установлен как часть установки комплекта Windows. Ниже приведен путь по умолчанию к WinDbg.exe:
C:\Program Files (x86)\Windows Kits\10\Debuggers\x64
Запустите WinDbg, чтобы подключиться к сеансу отладки ядра на целевом компьютере с помощью следующей команды. Значение порта и ключа должно совпадать с тем, что использовалось для настройки целевого компьютера. Вы используете 50000 для порта и 1.2.3.4 для ключа, значения, используемые на этапе развертывания. Флаг k указывает, что это сеанс отладки ядра.
WinDbg -k net:port=50000,key=1.2.3.4
В меню "Отладка" выберите "Разрыв". Отладчик на хост-компьютере проникает в целевой компьютер. В окне команды отладчика отображается командная строка отладки ядра: kd>.
На этом этапе можно поэкспериментировать с отладчиком, введя команды в командной строке kd>. Например, можно попробовать следующие команды:
Чтобы позволить целевому компьютеру снова запуститься, выберите Go в меню Отладка или нажмите клавишу G, а затем нажмите клавишу ВВОД.
Чтобы остановить сеанс отладки, выберите Отсоединить отладчик в меню Отладка.
Важный
Убедитесь, что вы используете команду go, чтобы позволить целевому компьютеру снова запуститься, прежде чем выйти из отладчика, или целевой компьютер не будет отвечать на ввод мыши и клавиатуры, так как он по-прежнему разговаривает с отладчиком.
Подробное пошаговое руководство по отладке драйвера см. в разделе Отладка универсальных драйверов — пошаговая лабораторная работа (Эхо Kernel-Mode).
Для получения дополнительной информации об удалённой отладке, см. раздел "Удалённая отладка с помощью WinDbg".
Связанные статьи
Инструменты отладки для Windows
Отладка универсальных драйверов — пошаговое руководство (Эхо Kernel-Mode)