Небольшая предыстория
Совсем недавно я перешёл на 3 курс технического университета, где на нашу группу вновь навалилась целая куча новых предметов. Одним из них являлся ИиУВМ(Интерфейсы и устройства вычислительных машин), на котором нам предстояло изучать, как те или иные части компьютера взаимодействуют между собой. Уже на 2 лабораторной работе нам выдали задание, суть которого заключалась в переборе всех устройств на PCI шине и получению их Vendor и Device ID через порты ввода-вывода. Для упрощения задачи преподаватель выдал ссылку на специальный драйвер и dll библиотеку под Windows XP и заявил, что это оптимальный вариант выполнения работы, так как по другому сделать её невозможно. Перспектива писать код под устаревшую OS меня не радовала, а слова про "невозможность" другой реализации лишь разожгли интерес. После недолгих поисков я выяснил, что цель может быть достигнута с помощью самописного драйвера.
В этой статье я хочу поделиться своим опытом, полученным в ходе длительных блужданий по документации Microsoft и попыток добиться от ChatGPT вменяемого ответа. Если вам интересно системное программирование под Windows - добро пожаловать под кат.
Важно знать
Современные драйвера под Windows могут быть основаны на одном из двух фреймворков: KMDF и UMDF (Kernel Mode Driver Framework и User Mode Driver Framework). В данной статье будет рассматриваться разработка KMDF драйвера, так как на него наложено меньше ограничений в отношении доступных возможностей. До UMDF драйверов я пока не добрался, как только поэкспериментирую с ними, обязательно напишу статью!
Разработка драйверов обычно происходит с помощью 2 компьютеров (или компьютера и виртуальной машины), на одном вы пишите код и отлаживаете программу (хост-компьютер), на другом вы запускаете драйвер и молитесь, чтобы система не легла после ваших манипуляций (целевой компьютер).
Перед началом работы с драйверами обязательно создайте точку восстановления, иначе вы рискуете положить свою систему так, что она перестанет запускаться. В таком случае выходом из ситуации станет откат Windows в начальное состояние (то есть в состояние при установке), что уничтожит не сохранённые заранее файлы на системном диске. Автор данной статьи наступил на эти грабли и совсем забыл скопировать свои игровые сохранения в безопасное место...
Первый запуск драйвера после добавления обработки IOCTL запросов на моём компьютере приводит к падению системы с необходимостью откатываться на точку восстановления. После этого драйвер запускается и работает без проблем. Самое странное, что если перенести тот же код в новый проект и запустить этот драйвер, то падения не происходит. Причины этого найти мне не удалось, так что прошу помощи в комментариях.
Я не профессиональный разработчик и данная статья лишь способ проложить дорогу для тех, кому так же, как и мне, стало интересно выйти за рамки привычных user mode приложений и попробовать что-то новое. Критика и дополнения приветствуются, постараюсь по возможности оперативно исправлять все ошибки. Теперь точно всё :)
Установка компонентов
В данной статье я не буду рассматривать вопрос о создании Hello world драйвера, этот вопрос полностью разобран тут. Рекомендую чётко следовать всем указаниям этого руководства, для того чтобы собрать и запустить свой первый драйвер. Это может занять некоторое время, но как можно достигнуть цели не пройдя никакого пути? Если у вас возникнут проблемы с этим процессом, я с радостью помогу вам в комментариях.
Полезно будет установить утилиту WinObj, которая позволяет вам просматривать имена файлов устройств и находить символические ссылки, которые на них указывают. Качаем тут.
-
Также неплохой утилитой является DebugView. Она позволяет вам просматривать отладочные сообщения, которые отправляются из ядра. Для этого необходимо включить опцию Kernel Capture на верхней панели.
Теперь с полным баком всяких утилит и библиотек переходим к самому интересному.
Начинаем веселье
В результате выполнения всех пунктов руководства Microsoft вы должны были сформировать файл драйвера cо следующим содержимым (комментарии добавлены от меня):
#include <ntddk.h>
#include <wdf.h>
// Объявление прототипа функции входа в драйвер, аналогично main() у обычных программ
DRIVER_INITIALIZE DriverEntry;
// Объявление прототипа функции для создания экземпляра устройства
// которым будет управлять наш драйвер
EVT_WDF_DRIVER_DEVICE_ADD KmdfHelloWorldEvtDeviceAdd;
// Пометки __In__ сделаны для удобства восприятия, на выполнение кода они не влияют.
// Обычно функции в пространстве ядра не возвращают данные через return
// (return возвращает статус операции)
// так что при большом числе аргументов такие пометки могут быть полезны
// чтобы не запутаться
NTSTATUS
DriverEntry(
// Фреймворк передаёт нам этот объект, никаких настроек мы для него не применяем
_In_ PDRIVER_OBJECT DriverObject,
// Путь, куда наш драйвер будет помещён
_In_ PUNICODE_STRING RegistryPath
)
{
// NTSTATUS переменная обычно используется для возвращения
// статуса операции из функции
NTSTATUS status = STATUS_SUCCESS;
// Создаём объект конфигурации драйвера
// в данный момент нас не интерсует его функциональность
WDF_DRIVER_CONFIG config;
// Макрос, который выводит сообщения. Они могут быть просмотрены с помощью DbgView
KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "HelloWorld: DriverEntry\n"));
// Записываем в конфиг функцию-инициализатор устройства
WDF_DRIVER_CONFIG_INIT(&config,
KmdfHelloWorldEvtDeviceAdd
);
// Создаём объект драйвера
status = WdfDriverCreate(DriverObject,
RegistryPath,
WDF_NO_OBJECT_ATTRIBUTES,
&config,
WDF_NO_HANDLE
);
return status;
}
NTSTATUS
KmdfHelloWorldEvtDeviceAdd(
_In_ WDFDRIVER Driver, // Объект драйвера
_Inout_ PWDFDEVICE_INIT DeviceInit // Структура-иницализатор устройства
)
{
// Компилятор ругается, если мы не используем какие-либо параметры функции
// (мы не используем параметр Driver)
// Это наиболее корректный способ избежать этого предупреждения
UNREFERENCED_PARAMETER(Driver);
NTSTATUS status;
// Объявляем объект устройства
WDFDEVICE hDevice;
// Снова вывод сообщения
KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "KmdfHelloWorld: DeviceAdd\n"));
// Создаём объект устройства
status = WdfDeviceCreate(&DeviceInit,
WDF_NO_OBJECT_ATTRIBUTES,
&hDevice
);
// Утрированный пример того, как можно проверить результат выполнения операции
if (!NT_SUCCESS(status)) {
return STATUS_ERROR_PROCESS_NOT_IN_JOB;
}
return status;
}
Данный драйвер не делает ничего интересного, кроме 2-х отладочных сообщений и создания экземпляра устройства. Пока мы не можем общаться с устройством из пространства пользователя. Нужно это исправить!
Весь дальнейший код будет добавляться в функциюKmdfHelloWorldEvtDeviceAdd
Для достижения цели необходимо создать файл устройства и символическую ссылку на него в пространстве ядра. С этим нам помогут функции WdfDeviceInitAssignName
и WdfDeviceCreateSymbolicLink
Однако просто вызвать их, передав имена файлов, не получится, нужна подготовка.
Начнём с тех самых имён. Они представляют собой строки в кодировке UTF-8. Следующий пример показывает способ инициализации строки в пространстве ядра.
UNICODE_STRING symLinkName = { 0 };
UNICODE_STRING deviceFileName = { 0 };
RtlInitUnicodeString(&symLinkName, L"\\DosDevices\\PCI_Habr_Link");
RtlInitUnicodeString(&deviceFileName, L"\\Device\\PCI_Habr_Dev");
Желательно придерживаться показанного в примере стиля именования файла устройства, то есть начинаться имя должно с префикса \Device\
Следующим шагом становится установление разрешения на доступ к устройству. Оно может быть доступно или из пространства ядра, или из системных или запущенных администратором программ. Внимание на код.
UNICODE_STRING securitySetting = { 0 };
RtlInitUnicodeString(&securitySetting, L"D:P(A;;GA;;;SY)(A;;GA;;;BA)");
// SDDL_DEVOBJ_SYS_ALL_ADM_ALL
WdfDeviceInitAssignSDDLString(DeviceInit, &securitySetting);
Комментарий капсом - это пометка какой тип разрешения на устройство здесь выставлен. В документации Microsoft описаны константы, аналогичные комментарию, однако у меня компилятор их не видел, и мне пришлось вставлять строку в сыром виде. Ссылка на типы разрешений тут.
Далее необходимо настроить дескриптор безопасности для устройства. Если коротко, то это реакция устройства на обращения к своему файлу.
// FILE_DEVICE_SECURE_OPEN означает, что устройство будет воспринимать обращения
// к файлу устройства как к себе
WdfDeviceInitSetCharacteristics(DeviceInit, FILE_DEVICE_SECURE_OPEN, FALSE);
Наконец-то мы можем создать файл устройства:
status = WdfDeviceInitAssignName(
DeviceInit,
&deviceFileName
);
// Напоминание о том, что результат критичных для драйвера функций нужно проверять
if (!NT_SUCCESS(status)) {
WdfDeviceInitFree(DeviceInit);
return status;
}
Переходим к символической ссылке, следующий код должен быть вставлен после функции WdfDeviceCreate
status = WdfDeviceCreateSymbolicLink(
hDevice,
&symLinkName
);
Итоговый код функции KmdfHelloWorldEvtDeviceAdd
должен иметь следующий вид:
NTSTATUS
KmdfHelloWorldEvtDeviceAdd(
_In_ WDFDRIVER Driver, // Объект драйвера
_Inout_ PWDFDEVICE_INIT DeviceInit // Структура-иницализатор устройства
)
{
// Компилятор ругается, если мы не используем какие-либо параметры функции
// (мы не используем параметр Driver)
// Это наиболее корректный способ избежать этого предупреждения
UNREFERENCED_PARAMETER(Driver);
NTSTATUS status;
UNICODE_STRING symLinkName = { 0 };
UNICODE_STRING deviceFileName = { 0 };
UNICODE_STRING securitySetting = { 0 };
RtlInitUnicodeString(&symLinkName, L"\\DosDevices\\PCI_Habr_Link");
RtlInitUnicodeString(&deviceFileName, L"\\Device\\PCI_Habr_Dev");
RtlInitUnicodeString(&securitySetting, L"D:P(A;;GA;;;SY)(A;;GA;;;BA)");
// SDDL_DEVOBJ_SYS_ALL_ADM_ALL
WdfDeviceInitAssignSDDLString(DeviceInit, &securitySetting);
WdfDeviceInitSetCharacteristics(DeviceInit, FILE_DEVICE_SECURE_OPEN, FALSE);
status = WdfDeviceInitAssignName(
DeviceInit,
&deviceFileName
);
// Hезультат критичных для драйвера функций нужно проверять
if (!NT_SUCCESS(status)) {
WdfDeviceInitFree(DeviceInit);
return status;
}
// Объявляем объект устройства
WDFDEVICE hDevice;
// Снова вывод сообщения
KdPrintEx((DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL,"HelloWorld: EvtDeviceAdd\n"));
// Создаём объект устройства
status = WdfDeviceCreate(&DeviceInit,
WDF_NO_OBJECT_ATTRIBUTES,
&hDevice
);
status = WdfDeviceCreateSymbolicLink(
hDevice,
&symLinkName
);
// Утрированный пример того, как можно проверить результат выполнения операции
if (!NT_SUCCESS(status)) {
return STATUS_ERROR_PROCESS_NOT_IN_JOB;
}
return status;
}
После сборки и установки драйвера, в утилите WinObj по пути "GLOBAL??" вы сможете увидеть следующее:
Общаемся с устройством
Взаимодействие с устройством происходит через IOCTL запросы. В режиме пользователя есть специальная функция, которая позволяет отправлять их устройству и получать данные в ответ. Пока сконцентрируемся на обработке этих запросов на стороне драйвера.
С чего начнётся этот этап? Правильно! С инициализации необходимых компонентов. Следите за руками:
WDF_IO_QUEUE_CONFIG ioQueueConfig;
WDFQUEUE hQueue;
// Инициализируем настройки очереди, в которую будут помещаться запросы
// Параметр WdfIoQueueDispatchSequential говорит то, что запросы будут обрабатываться
// по одному в порядке очереди
WDF_IO_QUEUE_CONFIG_INIT_DEFAULT_QUEUE(
&ioQueueConfig,
WdfIoQueueDispatchSequential
);
// Обработчик HandleIOCTL будет вызываться в ответ на функцию DeiviceIOControl
// Уже скоро мы создадим его
ioQueueConfig.EvtIoDeviceControl = HandleIOCTL;
// Создаём очередь
status = WdfIoQueueCreate(
hDevice, // Объект устройства уже должен существовать
&ioQueueConfig,
WDF_NO_OBJECT_ATTRIBUTES,
&hQueue
);
if (!NT_SUCCESS(status)) {
return status;
}
Очередь есть, но нет обработчика. Работаем:
// Выглядит страшно, но по сути код может быть любым числом, этот макрос использован
// для более подробного описания возможностей IOCTL кода для программиста
#define IOCTL_CODE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x3000, METHOD_BUFFERED, GENERIC_READ | GENERIC_WRITE)
VOID HandleIOCTL(
_In_ WDFQUEUE Queue, // Объект очереди, применения ему я пока не нашёл
_In_ WDFREQUEST Request, // Из этого объекта мы извлекаем входной и выходной буферы
_In_ size_t OutputBufferLength,
_In_ size_t InputBufferLength,
_In_ ULONG IoControlCode // IOCTL код, с которым к устройству обратились
)
{
NTSTATUS status = STATUS_SUCCESS;
UNREFERENCED_PARAMETER(Queue);
UNREFERENCED_PARAMETER(InputBufferLength);
UNREFERENCED_PARAMETER(OutputBufferLength);
UNREFERENCED_PARAMETER(Request);
switch (IoControlCode)
{
case IOCTL_CODE:
{
// Обрабатываем тут
break;
}
}
// По сути своей return из обработчика
// Используется, если запрос не возваращает никаких данных
WdfRequestComplete(Request, status);
}
Вот мы уже и на финишной прямой, у нас есть очередь, есть обработчик, но последний не возвращает и не принимает никаких данных. Сделаем так, чтобы обработчик возвращал нам сумму переданных чисел.
Объявим 2 структуры, из их названий будет понятно для чего они будут использоваться. В режиме ядра лучше использовать системные типы данных, такие как USHORT, UCHAR и другие.
struct DeviceRequest
{
USHORT a;
USHORT b;
};
struct DeviceResponse
{
USHORT result;
};
Обновлённая функция обработки IOCTL запроса:
VOID HandleIOCTL(
_In_ WDFQUEUE Queue, // Объект очереди, применения ему я пока не нашёл
_In_ WDFREQUEST Request, // Из этого объекта мы извлекаем входной и выходной буферы
_In_ size_t OutputBufferLength,
_In_ size_t InputBufferLength,
_In_ ULONG IoControlCode // IOCTL код, с которым к устройству обратились
)
{
NTSTATUS status = STATUS_SUCCESS;
UNREFERENCED_PARAMETER(Queue);
UNREFERENCED_PARAMETER(InputBufferLength);
UNREFERENCED_PARAMETER(OutputBufferLength);
size_t returnBytes = 0;
switch (IoControlCode)
{
case IOCTL_CODE:
{
struct DeviceRequest request_data = { 0 };
struct DeviceResponse *response_data = { 0 };
PVOID buffer = NULL;
PVOID outputBuffer = NULL;
size_t length = 0;
// Получаем указатель на буфер с входными данными
status = WdfRequestRetrieveInputBuffer(Request,
sizeof(struct DeviceRequest),
&buffer,
&length);
// Проверка на то, что мы получили буфер и он соотвествует ожидаемому размеру
// Очень важно делать такие проверки, чтобы не положить систему :)
if (length != sizeof(struct DeviceRequest) || !buffer)
{
status = STATUS_INVALID_DEVICE_REQUEST;
break;
}
request_data = *((struct DeviceRequest*)buffer);
// Получаем указатель на выходной буфер
status = WdfRequestRetrieveOutputBuffer(Request,
sizeof(struct DeviceResponse),
&outputBuffer,
&length);
if (length != sizeof(struct DeviceResponse) || !outputBuffer)
{
status = STATUS_INVALID_DEVICE_REQUEST;
break;
}
response_data = (struct DeviceResponse*)buffer;
// Записываем в выходной буфер результат
response_data->result = request_data.a + request_data.b;
// Вычисляем сколько байт будет возвращено в ответ на данный запрос
returnBytes = sizeof(struct DeviceResponse);
break;
}
}
// Функция-return изменилась, так как теперь мы возвращаем данные
WdfRequestCompleteWithInformation(Request, status, returnBytes);
}
Последний шаг - программа в режиме пользователя. Cоздаём обычный С или C++ проект и пишем примерно следующее:
#include <windows.h>
#include <iostream>
//Эта часть аналогична тем же объявлениям в драйвере
//================
#define IOCTL_CODE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x3000, METHOD_BUFFERED, GENERIC_READ | GENERIC_WRITE)
struct DeviceRequest
{
USHORT a;
USHORT b;
};
struct DeviceResponse
{
USHORT result;
};
// ===========================
int main()
{
HANDLE hDevice = CreateFileW(L"\\??\\PCI_Habr_Link",
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
DeviceRequest request = { 0 };
request.a = 10;
request.b = 15;
LPVOID input = (LPVOID)&request;
DeviceResponse response = { 0};
LPVOID answer = (LPVOID)&response;
DWORD bytes = 0;
bool res = DeviceIoControl(hDevice, IOCTL_CODE, input, sizeof(DeviceRequest),
answer, sizeof(DeviceResponse), &bytes, NULL);
response = *((DeviceResponse*)answer);
std::cout << "Sum : " << response.result << std::endl;
char ch;
std::cin >> ch;
CloseHandle(hDevice);
}
При запуске вы должны получить такой результат:
Заключение
Собственно, на этом всё. В конечном итоге мы получили драйвер, способный принимать и отвечать на запросы пользовательских программ. Дальнейшие модификации проекта ограничиваются лишь вашей фантазией.
Комментарии (36)
ajratr
17.09.2023 09:12+7Зачем два компьютера? Виртуалки наше всё. Устанавливаете систему, делаете контрольную точку. Если отлаживаемая система легла, восстанавливается в течении минуты.
fedorro
17.09.2023 09:12+5Если драйвер для реального устройства - то его не всегда возможно\удобно пробрасывать в виртуалку. Но для чисто программных драйверов я бы тоже выбрал виртуалку - да.
Racesas Автор
17.09.2023 09:12Да, согласен, совсем забыл об этом упомянуть. Сейчас добавлю в статью.
CrashLogger
17.09.2023 09:12+4Если пишем драйвер, работающий с реальным железом, например с шиной PCI, как у автора, то виртуалка не поможет. В виртуалку можно прокинуть определенное устройство, но не шину целиком. Но и в случае с отдельным устройством - драйвер из виртуалки вполне может положить нам хостовую систему, в случае некорректной работы с DMA например.
Skykharkov
17.09.2023 09:12+2Ух ты. А есть какие-то примеры и так далее?
Это-ж вектор атаки такой что мама не горюй. Ради интереса из под стандартного Windows Sandbox пытался сделать что-то с хостом. Не получилось. А уж из под Hyper-V даже пробовать не стал. Там вроде как полная изоляция...Qweritos
17.09.2023 09:12Думаю, речь шла о пробросе заведомо сбойного PCI устройства в виртуалку, и уже непосредственно из виртуалки создание условия, когда устройство начинает "делать грязь", к примеру, через DMA. Вряд ли это можно назвать уязвимостью.
nev3rfail
17.09.2023 09:12+2Китайские USB2COM адаптеры ложат vmware только в путь.
Skykharkov
17.09.2023 09:12+2Да виртуалку то положить большого ума не надо. :) A вот из виртуалки положить хост... Это уже интересно.
pvvv
17.09.2023 09:12попадались usb2com с кривыми драйверами, которые например при физическом выдёргивании шнурка без закрытия порта стабильно вызывали у виндов синий экран.
ну а положив каким-нибудь таким образом сначала виртуалку и проброс устройства можно затем и за хоста взяться :)
без относительно виртуалки у хоста же ведь тот же кривой драйвер для этого устройства.
nev3rfail
17.09.2023 09:12Если хост — линукс, то вполне себе поможет. Там можно кусок шины застабить и прицепить к виртуалке. Но нужна поддержка в железе.
arteast
17.09.2023 09:12+1Если надо работать чисто с шиной PCI, как у автора, то поможет и виртуалка (там виртуальная шина PCI ничем не хуже, чем настоящая), и, например, qemu. Если надо отладить драйвер работы с простым устройством, то можно взять qemu и туда добавить стаб-версию этого устройства (что может быть полезно само по себе для вещей типа автотестов). Если надо работать с реальным железом, то конечно лучше отдельный подопытный комп, независимо от того, есть там DMA, проецируемая память, прерывания или нет.
ajratr
17.09.2023 09:12+6Интересно ещё. Сам подобным давно уже не занимаюсь, но в своё время был класный пакет Numega driver studio назывался. Устанавлвался как плагин для VS6, ставил в систему драйвера свои и позволял прямо из VS, из режима пользователя драйвера отлаживать. Я прямо в виртуалке всё разворачивал и работал. Есть ли что то подобное сегодня?
MikalaiR
17.09.2023 09:12+1Вместо
KdPrintEx
гораздо удобнее использоватьTraceEvents
из WPP Software Tracing (если создавать проект в Visual Studio, то он уже будет настроен в шаблоне проекта). Это позволит потом в TraceView фильтровать логи по топикам и уровням.Вместо копирования структур и определений ioctl в юзерспейсную программу, надо использовать Public.h в проекте драйвера. Там еще и есть полезные макросы, которые позволят нормально работать с 64-битным драйвером из 32-битного приложения (это очень полезно для разного рода библиотек).
Racesas Автор
17.09.2023 09:12Спасибо за хорошие советы. Обязательно учту их при дальнейшем изучении темы.
eugene_e
17.09.2023 09:12+2Так а под какую винду в итоге этот драйвер, под 10-ую?
А что, в 10-11 винде можно вот так просто запустить неподписанный драйвер? У меня в свое время проблема была загрузкой драйвера для программатор. Под семеркой он работал, а десятка посылала лесом, потому что драйвер был не подписан.
valera5505
17.09.2023 09:12+3Неподписанный драйвер можно запустить если только специально загружаться с отключенной проверкой подписей
boris768
17.09.2023 09:12+3Необязательно, есть и другие способы загрузить свой драйвер, но драйвер должен быть к этому готов
Racesas Автор
17.09.2023 09:12+1Да, под 10-ую
Если следовать руководству Майкрософт по разработке первого драйвера, на которое я ссылаюсь в статье, то Visual Studio автоматически сконфигурирует целевой компьютер для запуска неподписанных драйверов.
Так же можно отключить проверку подписей вручную с помощью следующих команд:bcdedit.exe -set loadoptions DISABLE_INTEGRITY_CHECKS
bcdedit.exe -set TESTSIGNING ON
0xdead926e
17.09.2023 09:12-3ну, к слову говоря, драйвер можно было и не писать, а взять уже готовый подписанный валидный легитимный, типа AsrDrv101, гигабайтовский GIO, асусовский AsUpIO или вообще RwDrv... но да, так интереснее (:
modsamara
17.09.2023 09:12Кривой драйвер потер файлы с сохранениями игр?
Racesas Автор
17.09.2023 09:12Кривой драйвер не давал запуститься Виндовс, так что пришлось возвращать её в исходное состояние(то есть в состочние на момент установки). При этом все файлы на системном диске были удалены, в том числе и часть сохранений.
modsamara
17.09.2023 09:12сохранить нужные файлы перед возвращением в исходное состояние вы еще не проходили?
Racesas Автор
17.09.2023 09:12В тот момент принципиально важным было спасти свои лабы и курсовой, которые я и сохранил. Про игры как то не вспомнилось.
В любом случае мой пример с сейвами - это предупреждение о последствиях, к которым может привести отсутствие точки восстановления. Сохранить нужные файлы всегда можно, но зачем делать это самому, если это дело можно поручить системе ?
modsamara
17.09.2023 09:12из опыта - вы слишком идеализируете работу точек восстановления
ну и в посте про лабы ничего не сказано, инфа подается так, что кривой драйвер форматнул системный диск
Racesas Автор
17.09.2023 09:12Окей, на будущее буду знать. Текст в посте подправлю, иногда сложно бывает понять, как интерпретируют твои слова другие люди.
Apoheliy
17.09.2023 09:12Загрузиться с livecd и удалить кривой драйвер (или подпихнуть ей заведомо работающий драйвер), потом загрузить винду, которая может чуть-чуть поругается - профит. Не зайдёт?
Также, по задаче, нужно определиться:
либо делаете некоторый драйвер в стиле Hello World (что Вы собственно и сделали) и это хорошо и полезно.
либо делаете работу с портами ввода-вывода. Пардонь-те, но ioctl это не порты ввода-вывода, от слова "совсем". Для общения через порты ввода/вывода лучше сделать физическое устройство, оно отобразит (например) регистры на адреса портов и тогда будете гонять данные взад/вперёд. Либо Вам задана задача пробежать все устройства и подёргать у них порты. В общем, туманно.
isadora-6th
> // Компилятор ругается, если мы не используем какие-либо параметры функции (мы не используем параметр Driver)
// Это наиболее корректный способ избежать этого предупреждения
UNREFERENCED_PARAMETER(Driver);
Выглядит очень костыльно.
NTSTATUS KmdfHelloWorldEvtDeviceAdd( In WDFDRIVER /*Driver*/, Inout PWDFDEVICE_INIT DeviceInit )
Простой коммементарий или отпил имении параметра выглядит логичней.
Но я ++овик, могу быть неправ, что это валидное решение для мира Си. Но так в среднем решают такую проблему в Google style guide.
cher-nov
Но это общепринятый способ для всего WinAPI. К слову, в обычном Си также есть похожая идиома с приведением выражения к
void
. Чтобы такое выглядело чуть менее костыльно, подобные стопки следует писать уже послеreturn
, в самом хвосте кода функции.В стандартном переносимом Си (по крайней мере, до C99 включительно) такого делать нельзя.
https://stackoverflow.com/questions/8 776 810/parameter‑name‑omitted‑c-vs‑c