Пользователь
0,0
рейтинг
1 июля 2012 в 23:20

Разработка → Простейший WDM-драйвер

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


Подготовка стенда


Установка необходимого ПО для написания простейшего драйвера

Необходимое ПО:
  1. Windows DDK (Driver Development Kit);
  2. VMware Workstation или Virtual Box;
  3. Windows XP;
  4. Visual Studio 2005;
  5. DDKWizard;
  6. KmdManager
  7. DebugView;

Я использую две виртуальные машины, пишу драйверы на одной, а запускаю на другой. Если вы тоже решите так делать то для той машины, на которой вы будете запускать драйверы, хватит 4 Гбайтового жесткого диска и 256 Мбайт оперативной памяти.

Настройка рабочего места

Установка DDK

Установка предельно проста. Единственное на что необходимо обратить внимание — это диалог, в котором Вам предлагается выбрать компоненты, которые будут установлены. Настоятельно рекомендую отметить всю документацию и примеры.

Установка и настройка Microsoft® Visual Studio 2005

Установка Microsoft® Visual Studio 2005 ничем не сложнее установки DDK. Если Вы будете использовать её только для написания драйверов, то когда инсталлятор спросит какие компоненты необходимо установить, выберите только Visual C++.
Далее можно установить Visual Assist X. С помощью этой программы (аддона) можно будет легко настроить подсказки для удобного написания драйверов.
После установки Visual Assist X в Visual Studio 2005 появится новое меню VAssistX. Далее в этом меню: Visual Assist X Options -> Projects -> C/C++ Directories -> Platform: Custom, Show Directories for: Stable include files. Нажимаем Ins или на иконку добавить новую директорию и в появившейся строке, если у вас Windows XP вписываем %WXPBASE%\inc\ddk\wxp.

Установка и настройка DDKWizard

Для того чтобы в Visual Studio можно было компилировать драйверы нужно установить DDKWizard. Его можно скачать с сайта ddkwizard.assarbad.net. Также с этого сайта скачайте скрипт ddkbuild.cmd.
После того как мастер установится необходимо выполнить следующие шаги:
  • Создать системные (рекомендуется) или пользовательские переменные со следующими именами и значением, которое соответствует пути к DDK
    Версия DDK
    Имя переменной
    Путь по умолчанию
    Windows XP DDK
    WXPBASE
    C:\WINDDK\2600
    Windows 2003 Server DDK
    WNETBASE
    C:\WINDDK\3790.1830
    Windows Vista/Windows 2008 Server WDK
    WLHBASE
    Windows 7/Windows 2008 Server R2 WDK
    W7BASE

    Например, если я использую Windows XP DDK, то я должен создать переменную WXPBASE со значением, которое соответствует пути к DDK. Так как я не изменял путь установки, то значение у меня будет C:\WINDDK\2600.
  • Скопируйте скачанный скрипт ddkbuild.cmd, например, в папку с DDK. У меня это C:\WINDDK\.
  • Добавьте в конец системной переменной Path путь к скрипту ddkbuild.cmd.

Всё, машина, на которой будем запускать драйверы, готова.

Установка необходимого ПО для запуска драйверов

Теперь настроим машину, на которой будем запускать написанные драйверы.
Нам потребуются следющие программы:
  • DebugView (link) — это утилитка, которая позволяет просматривать отладочный вывод как режима пользователя так и режима ядра.
  • KmdManager (link) — утилита динамической загрузки/выгрузки драйверов

Всё, машина готова для запуска драйверов.

Постановка задачи


Задача: написать драйвер, который будет выводить в дебаг скан-коды нажатых клавиш и их комбинаций.

Немного теории

Драйвер — это набор функций, которые вызываются операционной системой при наступлении некоторых событий, приходящих от устройства или пользовательского режима.
Существует достаточно много типов драйверов, ниже перечисленны некоторые из них:
  • драйверы классов;
  • минидрайверы;
  • функциональные драйверы;
  • фильтрующие драйверы.

Драйверы классов — это драйверы, котрые пишет Microsoft. Это общие драйвера для определенного класса (неужели!) устройств.
Минидрайверы — драйверы, которые используеют драйвер класса для управления устройством.
Функциональные драйверы — это драйверы, которые работают самостоятельно и определяет все что связано с устройством.
Фильтрующие драйверы — драйверы, которые используются для мониторинга или изменения логики другого драйвера путем изменения данных, которые идут к нему.

Необязательно определять все возожные функции в своем драйвере, но он обязательно должен содержать DriverEntry и AddDevice.

IRP — это структура, которая используется драйверами для обмена данными.

Итак, для того чтобы выводить скан-коды (что это?) в дебаг, будем использовать фильтрующий драйвер.
Существует два типа фильтрующих драйверов:
  • верхние фильтрующие драйверы;
  • нижние фильтрующие драйверы.

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

Отличия между верхними и нижними фильтрующими драйверами

Через верхние фильтрующие драйверы проходят все запросы, а это значит, что они могут изменять и/или фильтровать информацию, идущую к функциональному драйверу, ну и далее, возможно, к устройству.
Пример использования верхних фильтрующих драйверов:
Фильтр-хук драйвер, который устанавливает свою хук-функцию для системного драйвера IpFilterDirver, для отслеживания и фильтрации траффика. Такие драйверы используются в брандмауэрах.

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

Проблемы синхронизации

В драйвере, который мы будем писать, есть несколько «проблемных» секций. Для нашего драйвера вполне достаточно использования ассемблерных вставок:

__asm {
	lock dec «переменная, которую нужно уменьшить на единицу»
}

или
__asm {
	lock inc «переменная, которую нужно увеличить на единицу»
}

Префикс lock позволяет безопасно выполнить идущую за ним команду. Она блокирует остальные процессоры, пока выполняется команда.

Экшен


Для начала необходимо включить заголовочные файлы «ntddk.h», «ntddkbd.h»

extern "C"
{
	#include "ntddk.h"
}
#include "ntddkbd.h"

Также необходимо описать структуру DEVICE_EXTENSION
typedef struct _DEVICE_EXTENSION{
	PDEVICE_OBJECT pLowerDO;
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;

Объект pLowerDO это объект устройства, который находится ниже нас в стеке. Он нужен нам для того чтобы знать кому дальше отправлять IRP-пакеты.
Еще для работы нашего драйвера нам нужна переменная, в которой будет храниться количество не завершенных запросов.
int gnRequests;

Начнем с функции, которая является главной точкой входа нашего драйвера.
extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT theDriverObject, IN PUNICODE_STRING ustrRegistryPath)

theDriverObject – объект драйвера, содержит указатели на все необходимые операционной системе функции, которые мы должны будем инициализировать.
ustrRegistryPath – имя раздела в реестре, где хранится информация о данном драйвере.
Для начала необходимо объявить и обнулить переменные:
gnRequests = 0;
NTSTATUS status = {0};

Далее, как я и писал выше, нужно инициализировать указатели на функции
for (int i = 0; i<IRP_MJ_MAXIMUM_FUNCTION; ++i)
	{
	theDriverObject->MajorFunction[i] = DispatchThru;
	}
theDriverObject->MajorFunction[IRP_MJ_READ] = DispatchRead;
theDriverObject->DriverUnload = DriverUnload;

Функция DispatchRead будет обрабатывать запросы на чтение. Она будет вызываться, когда нажата или отпущена клавиша клавиатуры.
Функция DriverUnload вызывается, когда драйвер уже не нужен и его можно выгрузить из памяти, или когда пользователь сам выгружает драйвер. В данной функции должна производиться «зачистка», т.е. освобождаться ресурсы, которые использовались драйвером, завершаться все незавершенные запросы и т.д.
Функция DispatchThru это функция-заглушка. Все что она делает это передача IRP-пакета следующему драйверу (драйверу который находится под нашим в стеке, т.е. pLowerDO из DEVICE_EXTENSION).
Далее мы вызываем нашу функцию, для создания и установки нашего устройства в стек устройств:
status = InstallFilter(theDriverObject);

Эту функцию я опишу чуть ниже.
Возвращаем status, в котором, если функция InstallFilter завершилась удачей, хранится значение STATUS_SUCCESS.
Переходим к функции InstallFilter. Вот её прототип:
NTSTATUS InstallFilter(IN PDRIVER_OBJECT theDO);

Эта функция создает объект устройства, настраивает его и включает в стек устройств поверх \\Device\\KeyboardClass0

Объявляем переменные:
PDEVICE_OBJECT pKeyboardDevice;
NTSTATUS status = {0};

pKeyboardDevice – это объект устройсва, которое мы должны создать.
Вызываем IoCreateDevice для создания нового устройства
status = IoCreateDevice(theDO, sizeof(DEVICE_EXTENSION), NULL, FILE_DEVICE_KEYBOARD, 0, FALSE, &pKeyboardDevice);

Разберем подробнее параметры:
  • Первый аргумент это объект драйвера, который мы получили как параметр функции InstallFilter. Он передается в IoCreateDevice для того чтобы установить связь между нашим драйвером и новым устройством.
  • Третий параметр это имя устройства
  • Четвертый параметр это тип устройства
  • Пятый параметр это флаги, которые обычно устанавливаются для запоминающих устройств.
  • Шестой параметр описывает можно ли открывать манипуляторы устройства в количестве больше одного. Если FALSE можно открыть только один манипулятор. Иначе можно открыть любое количество манипуляторов.
  • Седьмой параметр это память, в которой будем сохранен созданный объект устройства.

Далее устанавливаем флаги устройства.
pKeyboardDevice->Flags = pKeyboardDevice->Flags | (DO_BUFFERED_IO | DO_POWER_PAGABLE);
pKeyboardDevice->Flags = pKeyboardDevice->Flags & ~DO_DEVICE_INITIALIZING;

Флаги, которые мы устанавливаем для нашего устройства, должны быть эквивалентными флагам устройства, поверх которого мы включаемся в стек.
Далее мы должны выполнить преобразования имени устройства, которое мы включаем в стек.
CCHAR cName[40] = "\\Device\\KeyboardClass0";
STRING strName;
UNICODE_STRING ustrDeviceName;

RtlInitAnsiString(&strName, cName);
RtlAnsiStringToUnicodeString(&ustrDeviceName, &strName, TRUE);

Функция IoAttachDevice внедряет наше устройство в стек. В pdx->pLowerDO будет храниться объект следующего (нижнего) устройства.
IoAttachDevice(pKeyboardDevice, &ustrDeviceName, &pdx->pLowerDO);

Освобождаем ресурсы:
RtlFreeUnicodeString(&ustrDeviceName);

Далее разберем функцию DispatchRead с прототипом:
NTSTATUS DispatchRead(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp);

Данная функция будет вызываться операционной системой при нажатии или отпускании клавиши клавиатуры
Увеличиваем счетчик незавершенных запросов
__asm {
	lock inc gnRequests
}

Перед тем как передать запрос следующему драйверу мы должны настроить указатель стека для драйвера. IoCopyCurrentIrpStackLocationToNext копирует участок памяти, который принадлежит текущему драйверу, в область памяти следующего драйвера.
IoCopyCurrentIrpStackLocationToNext(theIrp);
Когда запрос идет вниз по стеку в нем еще нет нужных нам данных, поэтому мы должны задать функцию, которая вызовется, когда запрос будет идти вверх по стеку с нужными нам данными.
IoSetCompletionRoutine(theIrp, ReadCompletionRoutine, pDeviceObject, TRUE, TRUE, TRUE)

где ReadCompletionRoutine наша функция.
Передаем IRP следующему драйверу:
return IoCallDriver(((PDEVICE_EXTENSION) pDeviceObject->DeviceExtension)->pLowerDO ,theIrp);

Теперь разберем функцию, которая будет вызываться каждый раз при завершении IRP. Прототип:
NTSTATUS ReadCompletionRoutine(IN PDEVICE_OBJECT pDeviceObject, IN PIRP theIrp, IN PVOID Context);

Получаем DEVICE_EXTENSION:
PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)pDeviceObject->DeviceExtension;

Структура PKEYBOARD_INPUT_DATA используется для описания нажатой клавиши.
PKEYBOARD_INPUT_DATA kidData;

Проверяем, удачно завершен запрос или нет
if (NT_SUCCESS(theIrp->IoStatus.Status))

Чтобы достать структуру KEYBOARD_INPUT_DATA нужно обратиться к системному буферу IRP-пакета.
kidData = (PKEYBOARD_INPUT_DATA)theIrp->AssociatedIrp.SystemBuffer;

Узнаем количество клавиш
int n = theIrp->IoStatus.Information / sizeof(KEYBOARD_INPUT_DATA);

И выводим каждую клавишу:
for(int i = 0; i<n; ++i)
	DbgPrint("Code: %x\n", kidData[i].MakeCode);

И не забываем уменьшать количество не обработанных запросов
__asm {
	lock dec gnRequests
}

Возвращаем статус запроса
return theIrp->IoStatus.Status;

Разберем функцию завершения работы. Прототип:
VOID DriverUnload(IN PDRIVER_OBJECT theDO);

Получаем DEVICE_EXTENSION:
PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)theDO->DeviceObject->DeviceExtension;

Извлекаем устройство из стека:
IoDetachDevice(pdx->pLowerDO);

Удаляем устройство:
IoDeleteDevice(theDO->DeviceObject);

Проверяем есть незавершенные запросы или нет. Если мы выгрузим драйвер без этой проверки, при первом нажатии на клавишу после выгрузки будет БСоД.
if (gnRequests != 0)
{
	KTIMER ktTimer;
	LARGE_INTEGER liTimeout;
	liTimeout.QuadPart = 1000000;
	KeInitializeTimer(&ktTimer);
	Задаем таймер и пока не завершены все запросы, крутим цикл
	while(gnRequests > 0)
	{
		KeSetTimer(&ktTimer, liTimeout, NULL); // Устанавливаем таймер
		KeWaitForSingleObject(&ktTimer, Executive, KernelMode, FALSE, NULL); // Ждем пока истечет время

	}
}

Код драйвера:
extern "C"
{
	#include "ntddk.h"
}

#include "ntddkbd.h"

typedef struct _DEVICE_EXTENSION{
	PDEVICE_OBJECT pLowerDO;
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;

int gnRequests;

NTSTATUS DispatchThru(PDEVICE_OBJECT theDeviceObject, PIRP theIrp)
{
	IoSkipCurrentIrpStackLocation(theIrp);
	return IoCallDriver(((PDEVICE_EXTENSION) theDeviceObject->DeviceExtension)->pLowerDO ,theIrp);
}

NTSTATUS InstallFilter(IN PDRIVER_OBJECT theDO)
{
	PDEVICE_OBJECT pKeyboardDevice;
	NTSTATUS status = {0};

	status = IoCreateDevice(theDO, sizeof(DEVICE_EXTENSION), NULL, FILE_DEVICE_KEYBOARD, 0, FALSE, &pKeyboardDevice);
	if (!NT_SUCCESS(status))
	{
		DbgPrint("IoCreateDevice error..");
		return status;
	}
	pKeyboardDevice->Flags = pKeyboardDevice->Flags | (DO_BUFFERED_IO | DO_POWER_PAGABLE);
	pKeyboardDevice->Flags = pKeyboardDevice->Flags & ~DO_DEVICE_INITIALIZING;

	RtlZeroMemory(pKeyboardDevice->DeviceExtension, sizeof(DEVICE_EXTENSION));

	PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)pKeyboardDevice->DeviceExtension;

	CCHAR cName[40] = "\\Device\\KeyboardClass0";
	STRING strName;
	UNICODE_STRING ustrDeviceName;

	RtlInitAnsiString(&strName, cName);
	RtlAnsiStringToUnicodeString(&ustrDeviceName, &strName, TRUE);

	IoAttachDevice(pKeyboardDevice, &ustrDeviceName, &pdx->pLowerDO);
	//DbgPrint("After IoAttachDevice");
	RtlFreeUnicodeString(&ustrDeviceName);
	
	return status;
}

VOID DriverUnload(IN PDRIVER_OBJECT theDO)
{
	PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)theDO->DeviceObject->DeviceExtension;
	IoDetachDevice(pdx->pLowerDO);
	IoDeleteDevice(theDO->DeviceObject);
	if (gnRequests != 0)
	{
		KTIMER ktTimer;
		LARGE_INTEGER liTimeout;
		liTimeout.QuadPart = 1000000;
		KeInitializeTimer(&ktTimer);
		
		while(gnRequests > 0)
		{
			KeSetTimer(&ktTimer, liTimeout, NULL);
			KeWaitForSingleObject(&ktTimer, Executive, KernelMode, FALSE, NULL);
		}
	}
}

NTSTATUS ReadCompletionRoutine(IN PDEVICE_OBJECT pDeviceObject, IN PIRP theIrp, IN PVOID Context)
{
		PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)pDeviceObject->DeviceExtension;
	PKEYBOARD_INPUT_DATA kidData;
	if (NT_SUCCESS(theIrp->IoStatus.Status))
	{
		kidData = (PKEYBOARD_INPUT_DATA)theIrp->AssociatedIrp.SystemBuffer;
		int n = theIrp->IoStatus.Information / sizeof(KEYBOARD_INPUT_DATA);
		for(int i = 0; i<n; ++i)
		{
			DbgPrint("Code: %x\n", kidData[i].MakeCode);
		}
	}
	if(theIrp->PendingReturned)
		IoMarkIrpPending(theIrp);
	__asm{
		lock dec gnRequests
	}
	return theIrp->IoStatus.Status;
}

NTSTATUS DispatchRead(IN PDEVICE_OBJECT pDeviceObject, IN PIRP theIrp)
{
	__asm{
		lock inc gnRequests
	}
	IoCopyCurrentIrpStackLocationToNext(theIrp);
	IoSetCompletionRoutine(theIrp, ReadCompletionRoutine, pDeviceObject, TRUE, TRUE, TRUE);
	return IoCallDriver(((PDEVICE_EXTENSION) pDeviceObject->DeviceExtension)->pLowerDO ,theIrp);
}

extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT theDriverObject, IN PUNICODE_STRING RegistryPath)
{
	NTSTATUS status = {0};
	gnRequests = 0;
	for (int i = 0; i<IRP_MJ_MAXIMUM_FUNCTION; ++i)
	{
		theDriverObject->MajorFunction[i] = DispatchThru;
	}
	theDriverObject->MajorFunction[IRP_MJ_READ] = DispatchRead;
	
	status = InstallFilter(theDriverObject);
	
	theDriverObject->DriverUnload = DriverUnload;
	return status;
}


MAKEFILE:
#
# DO NOT EDIT THIS FILE!!!  Edit .\sources. if you want to add a new source
# file to this component.  This file merely indirects to the real make file
# that is shared by all the driver components of the Windows NT DDK
#

!INCLUDE $(NTMAKEENV)\makefile.def

SOURCES:
TARGETNAME=sysfile
TARGETPATH=BIN
TARGETTYPE=DRIVER

SOURCES = DriverMain.cpp

Как запустить драйвер и просмотреть отладочную информацию

Для запуска драйвера я использовал утилиту KmdManager. Для просмотра отладочной информации использовалась утилита DbgView.

P. S. Статью писал давно, ещё на третьем курсе, сейчас уже почти ничего не помню. Но если есть вопросы, постараюсь ответить.
P. P. S. Прошу обратить внимание на комментарии, в частности на этот

UPD: Проект на GitHub: https://github.com/pbespechnyi/simple-wdm-driver
Беспечный Петр @pbespechnyi
карма
20,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (24)

  • +2
    А почему вы не используете функции семейства InterlockedXxx? К тому же, при компиляции 64-битного когда компиляторы от Microsoft не поддерживают встроенный ассемблер. По крайней мере год назад не поддерживали.
    • –1
      Для этого в ядре есть SpinLocks
      • +1
        Здесь не нужны спинлоки — нужны именно InterlockedXxx функции для рефкаунта
    • 0
      Я не использую их, потому что я посчитал, что
      > для нашего драйвера вполне достаточно использования ассемблерных вставок

      Конечно же, можно использовать InterlockedIncrement и InterlockedDecrement, для этого нужно также поменять тип переменной gnRequests на LONG.
      • +2
        Э-э-э. Для того, чтобы отказываться от стандартных примитивов (это intrinsic функции и они скомпилируются в код, близкий тому, что у Вас но при этом ВАЛИДНЫЙ для любых платформ и в любых обстоятельствах) нужно иметь причины более веские, чем «не хочется».

        Если Вы «без помощи зала» и «звонка гуглу» не сможете ответить на вопрос в чем отличия между InterlockedIncrement, InterlockedIncrementAqcuire, InterlockedIncrementRelease и InterlockedIncrementNoFence, то с вероятностью, близкой к единице, при попытке реализации собственного примитива синхронизации Вы напишете невалидный код. Более того, это будет самая мерзкая разновидность невалидности: race condition на НЕКОТОРЫХ платформах (включая, возможно, будущие IA), к тому же принципиально не трассируемый в отладчике.

        Ну и пара «придирок» по мелочам (хотя статья сама по себе неплохая):
        1. Ваш драйвер не имеет ничего общего с WDM. Ну то есть вообще. Вижу стандартный legacy драйвер, а вот ни одного признака WDM не вижу. Это не хорошо и не плохо — просто название статьи вводит в заблуждение. А вообще, ознакомьтесь с WDF — он предоставляет немало плюшек по сравнению даже с WDM, не говоря уже о legacy.
        2. VS2012 не только имеет темплейты для драйверов (после установки WDK), но умеет сама собирать, умеет сама готовить удаленную машину (включая настройку KD), деплоить туда драйвер, отлаживать, стирать, убирать, готовить есть.
        3. DbgPrint/KdPrint — прошлый век же. WPP (или полноценный ETW) — наш метод.
        4. Хардкод KeyboardClass0 — нехорошо, лучше IoGetDeviceInterfaces. Это не только поможет прицепиться ко всем клавиатурам, но еще и позволит цепляться ко всем новым через IoRegisterPlugPlayNotification. Ну или использовать WDM и регистрироваться фильтром класса (как делает сам kbdclass)
        5. Блокирование Unload до завершения последнего Irp — УЖАСНО. Хотя бы попробовали отменить (IoCancelIrp) их сначала.
        • +1
          Спасибо за комментарий!

          1.
          «В категории драйверов WDM также выделяются драйверы классов, минидрайверы, монолитные функциональные драйверы и фильтрующие драйверы.»
          Уолтер Они, «Использование Microsoft Windows Driver Model», Глава 1, страница 36.
          2. На момент написания статьи VS2012 не было.
          3. Чему научили, так и написали.
          4. Хардкод — плохо, согласен.
          5. Согласен, криво.

          Хочу ещё раз подчеркнуть
          «Статью писал давно, ещё на третьем курсе, сейчас уже почти ничего не помню. Но если есть вопросы, постараюсь ответить.»

          Сейчас занимаюсь делами далекими от ядра, а опубликовал, потому что пообещал в топике о драйвере на PureBasic.
          • +1
            1. Я не совсем про это. All WDM drivers must:
            • Include Wdm.h, not Ntddk.h. (Note that Wdm.h is a subset of Ntddk.h.)
            • Be designed as a bus driver, a function driver, or a filter driver, as described in Types of WDM Drivers.
            • Create device objects as described in WDM Device Objects and Device Stacks.
            • Support Plug and Play (PnP).
            • Support power management.
            • Support Windows Management Instrumentation (WMI).

            Ну ладно, IRP_MJ_PNP, IRP_MJ_POWER и IRP_MJ_SYSTEM_CONTROL проходят вниз по стеку и фильтру их обрабатывать не нужно, но создание девайсов в DriverEntry, а не в AddDevice (и включение ntddk.h вместо wdm.h) — это явные признаки legacy драйвера. А фильтры и девайс стеки были всегда — так что это не показатель.

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

            Сейчас занимаюсь делами далекими от ядра, а опубликовал, потому что пообещал в топике о драйвере на PureBasic.

            Это да, сам офигел когда увидел.

            Но вообще, если Вы в принципе все еще занимаетесь драйверами (несмотря на то что СЕЙЧАС не занимаетесь), то лучше посмотрите в сторону WDF: фрейворк реализует «канонический» драйвер и стороннему девелоперу нужно только переопределять уникальное для его драйвера поведение (при этом, например в WDM/legacy, логику отмены запроса практически невозможно реализовать правильно, не прочитав специальный документ, а WDF берет всю заботу о подобных вещах на себя). Есть объект «буфер» который хранит указатель, размер и «вместимость» и может быть расширен вызываемой функцией (таким образом не нужно делать два вызова с проверкой на то, хватает ли места) и кучу прочих плюшек.

            И да, не стоит использовать DbgPrint — он медленный (весь вывод синхронизируется в отладчик) и поэтому всегда выключен в релизном коде (а WPP/ETW можно включать/выключать в рантайме), он практически не подлежит автоматической обработке, с ним тяжело работать постмортем — при наличии одного только дампа (DebugView имеет какие то эвристики для вытаскивания DbgPrint сообщений из дампа, но это неподдерживаемая фича и никаких гарантий вообще говоря нет) и так далее.
  • +3
    Статья зачетная! Все вроде грамотно.
    Не то, что до этого было на хабре пару статей.
    • 0
      Спасибо! Была ещё статейка о взаимодействии этого драйвера с приложением, но я её не уберёг.
      • +1
        • +1
          Не, это не она.
          • 0
            Я знаю, это просто статья о взаимодействии драйверов с приложениями, думаю такое будет более полезно.
  • +3
    Слава богу, C!
    • +1
      Вы си от с++ не отличили?.. =) Кстати, по этому поводу вопрос автору. Разве писать драйвера на с++ это хороший тон? А как же с++ runtime и всё, что он с собой привносит? На сколько я помню в драйверах надо вызывать kernel space ф-ии выделения памяти и прочего т.к. они не используют обычный runtime заточенный для user space. В общем, было б интересно об этом что-то услышать…
      • +1
        Я не отличаю.
        Покажите, пожалуйста, фрагмент кода из статьи, написанный на C++.
        • +1
          Да вы что граждане :(

          extern "C" NTSTATUS DriverEntry(...)

          extern «C» это с++ директива

          а в makefile так ваще прямым текстом:

          SOURCES = DriverMain.cpp
          • +2
            Да, но это не говорит о том, что при написании драйверов используется C++ (если ошибаюсь, ткните меня в бок).

            Цитата из книги Уолтера Они «Использование Microsoft Windows Driver Model»:
            Before I describe the code you’d write inside DriverEntry, I want to mention a few things about the function prototype itself. Unbeknownst to you and me (unless we look carefully at the compiler options used in the build script), kernel-mode functions and the functions in your driver use the __stdcall calling convention when compiled for an x86 computer. This shouldn’t affect any of your programming, but it’s something to bear in mind when you’re debugging. I used the extern “C” directive because, as a rule, I package my code in a C++ compilation unit—mostly to gain the freedom to declare variables wherever I please instead of only immediately after left braces. This directive suppresses the normal C++ decoration of the external name so that the linker can find this function. Thus, an x86 compile produces a function whose external name is _DriverEntry@8.

            • +1
              Да, но это не говорит о том, что при написании драйверов используется C++

              Вообще-то разговор шёл о том, что кое-кто не отличил си код от с++. =) Затем я просто спросил автора зачем вообще пример писался как с++ код если ни классов, ни других ООП преимуществ не использовалось (да и не может использоваться в kernel окружении)? Для меня это было не очевидно.

              Приведенная цитата объясняет зачем, но в таком случае подозреваю, что автор топика копипастил код из книги ;)
              • 0
                Ну так все мы копипастим время от времени ;)
              • 0
                А по поводу C/C++, да — я в упор не увидел extern «C». =(
              • 0
                Автор не копипасил код из книги, он по ней учился.
              • 0
                >>да и не может использоваться в kernel окружении
                Вы не правы, C++ успешно используется для написания драйверов.
      • +1
        Для написания драйвера на C++ нужно не так уж много:

        1) Перегрузить глобальные операторы new/delete:
        void *__cdecl operator new(size_t size, unsigned long tag, POOL_TYPE pool);
        void __cdecl operator delete(void* p);
        


        2) Не пользоваться глобальными объектами или реализовать initterm/atexit для вызова конструкторов/деструкторов глобальных объектов;

        3) Использовать C++ по минимуму (мы не использовали С++ exceptions и сторонние библиотеки, даже STL)
        • 0
          ontl.googlecode.com

          А вообще я тоже делал самописную библиотеку оберток над WDM. В конце концов затащил туда исключения и даже вытащил часть KSTL из DriverWorkbench (интересно, их еще кто-то помнит?). Исключения — очень приятная штука, если пользоваться осторожно (хотя в ядре нужно и без исключений быть всегда бдительным)

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.