Небольшая предыстория

Совсем недавно я перешёл на 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 приложений и попробовать что-то новое. Критика и дополнения приветствуются, постараюсь по возможности оперативно исправлять все ошибки. Теперь точно всё :)

Установка компонентов

  1. В данной статье я не буду рассматривать вопрос о создании Hello world драйвера, этот вопрос полностью разобран тут. Рекомендую чётко следовать всем указаниям этого руководства, для того чтобы собрать и запустить свой первый драйвер. Это может занять некоторое время, но как можно достигнуть цели не пройдя никакого пути? Если у вас возникнут проблемы с этим процессом, я с радостью помогу вам в комментариях.

  2. Полезно будет установить утилиту WinObj, которая позволяет вам просматривать имена файлов устройств и находить символические ссылки, которые на них указывают. Качаем тут.

  3. Также неплохой утилитой является 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)


  1. isadora-6th
    17.09.2023 09:12

    > // Компилятор ругается, если мы не используем какие-либо параметры функции (мы не используем параметр Driver)

    // Это наиболее корректный способ избежать этого предупреждения

    UNREFERENCED_PARAMETER(Driver);

    Выглядит очень костыльно.

    NTSTATUS KmdfHelloWorldEvtDeviceAdd( In WDFDRIVER /*Driver*/, Inout PWDFDEVICE_INIT DeviceInit )

    Простой коммементарий или отпил имении параметра выглядит логичней.

    Но я ++овик, могу быть неправ, что это валидное решение для мира Си. Но так в среднем решают такую проблему в Google style guide.


    1. cher-nov
      17.09.2023 09:12
      +4

      Выглядит очень костыльно.

      Но это общепринятый способ для всего WinAPI. К слову, в обычном Си также есть похожая идиома с приведением выражения к void. Чтобы такое выглядело чуть менее костыльно, подобные стопки следует писать уже после return, в самом хвосте кода функции.

      Простой комментарий или отпил имени параметра выглядит логичней.

      В стандартном переносимом Си (по крайней мере, до C99 включительно) такого делать нельзя.
      https://stackoverflow.com/questions/8 776 810/parameter‑name‑omitted‑c-vs‑c


  1. ajratr
    17.09.2023 09:12
    +7

    Зачем два компьютера? Виртуалки наше всё. Устанавливаете систему, делаете контрольную точку. Если отлаживаемая система легла, восстанавливается в течении минуты.


    1. fedorro
      17.09.2023 09:12
      +5

      Если драйвер для реального устройства - то его не всегда возможно\удобно пробрасывать в виртуалку. Но для чисто программных драйверов я бы тоже выбрал виртуалку - да.


    1. Racesas Автор
      17.09.2023 09:12

      Да, согласен, совсем забыл об этом упомянуть. Сейчас добавлю в статью.


    1. CrashLogger
      17.09.2023 09:12
      +4

      Если пишем драйвер, работающий с реальным железом, например с шиной PCI, как у автора, то виртуалка не поможет. В виртуалку можно прокинуть определенное устройство, но не шину целиком. Но и в случае с отдельным устройством - драйвер из виртуалки вполне может положить нам хостовую систему, в случае некорректной работы с DMA например.


      1. Skykharkov
        17.09.2023 09:12
        +2

        Ух ты. А есть какие-то примеры и так далее?
        Это-ж вектор атаки такой что мама не горюй. Ради интереса из под стандартного Windows Sandbox пытался сделать что-то с хостом. Не получилось. А уж из под Hyper-V даже пробовать не стал. Там вроде как полная изоляция...


        1. Qweritos
          17.09.2023 09:12

          Думаю, речь шла о пробросе заведомо сбойного PCI устройства в виртуалку, и уже непосредственно из виртуалки создание условия, когда устройство начинает "делать грязь", к примеру, через DMA. Вряд ли это можно назвать уязвимостью.


        1. nev3rfail
          17.09.2023 09:12
          +2

          Китайские USB2COM адаптеры ложат vmware только в путь.


          1. Skykharkov
            17.09.2023 09:12
            +2

            Да виртуалку то положить большого ума не надо. :) A вот из виртуалки положить хост... Это уже интересно.


            1. pvvv
              17.09.2023 09:12

              попадались usb2com с кривыми драйверами, которые например при физическом выдёргивании шнурка без закрытия порта стабильно вызывали у виндов синий экран.

              ну а положив каким-нибудь таким образом сначала виртуалку и проброс устройства можно затем и за хоста взяться :)

              без относительно виртуалки у хоста же ведь тот же кривой драйвер для этого устройства.


      1. nev3rfail
        17.09.2023 09:12

        Если хост — линукс, то вполне себе поможет. Там можно кусок шины застабить и прицепить к виртуалке. Но нужна поддержка в железе.


      1. arteast
        17.09.2023 09:12
        +1

        Если надо работать чисто с шиной PCI, как у автора, то поможет и виртуалка (там виртуальная шина PCI ничем не хуже, чем настоящая), и, например, qemu. Если надо отладить драйвер работы с простым устройством, то можно взять qemu и туда добавить стаб-версию этого устройства (что может быть полезно само по себе для вещей типа автотестов). Если надо работать с реальным железом, то конечно лучше отдельный подопытный комп, независимо от того, есть там DMA, проецируемая память, прерывания или нет.


  1. ajratr
    17.09.2023 09:12
    +6

    Интересно ещё. Сам подобным давно уже не занимаюсь, но в своё время был класный пакет Numega driver studio назывался. Устанавлвался как плагин для VS6, ставил в систему драйвера свои и позволял прямо из VS, из режима пользователя драйвера отлаживать. Я прямо в виртуалке всё разворачивал и работал. Есть ли что то подобное сегодня?


    1. gdt
      17.09.2023 09:12
      +4

      Да и SoftICE был хорош, тоже интересно :)


    1. boris768
      17.09.2023 09:12
      +1

      NT ядро сейчас не терпит вмешательств (пресловутый PatchGuard), так что хорошим решением для более-менее быстрой отладки - это vmware/vbox + VirtualKD


  1. MikalaiR
    17.09.2023 09:12
    +1

    Вместо KdPrintEx гораздо удобнее использовать TraceEvents из WPP Software Tracing (если создавать проект в Visual Studio, то он уже будет настроен в шаблоне проекта). Это позволит потом в TraceView фильтровать логи по топикам и уровням.

    Вместо копирования структур и определений ioctl в юзерспейсную программу, надо использовать Public.h в проекте драйвера. Там еще и есть полезные макросы, которые позволят нормально работать с 64-битным драйвером из 32-битного приложения (это очень полезно для разного рода библиотек).


    1. Racesas Автор
      17.09.2023 09:12

      Спасибо за хорошие советы. Обязательно учту их при дальнейшем изучении темы.


  1. eugene_e
    17.09.2023 09:12
    +2

    1. Так а под какую винду в итоге этот драйвер, под 10-ую?

    2. А что, в 10-11 винде можно вот так просто запустить неподписанный драйвер? У меня в свое время проблема была загрузкой драйвера для программатор. Под семеркой он работал, а десятка посылала лесом, потому что драйвер был не подписан.


    1. valera5505
      17.09.2023 09:12
      +3

      Неподписанный драйвер можно запустить если только специально загружаться с отключенной проверкой подписей


      1. boris768
        17.09.2023 09:12
        +3

        Необязательно, есть и другие способы загрузить свой драйвер, но драйвер должен быть к этому готов


    1. Racesas Автор
      17.09.2023 09:12
      +1

      1. Да, под 10-ую

      2. Если следовать руководству Майкрософт по разработке первого драйвера, на которое я ссылаюсь в статье, то Visual Studio автоматически сконфигурирует целевой компьютер для запуска неподписанных драйверов.
        Так же можно отключить проверку подписей вручную с помощью следующих команд:
        bcdedit.exe -set loadoptions DISABLE_INTEGRITY_CHECKS
        bcdedit.exe -set TESTSIGNING ON


    1. Racesas Автор
      17.09.2023 09:12

      Здесь был комментарий, оставленный случайно))


  1. Raimon
    17.09.2023 09:12

    Неужели нужно писать драйвер чтобы перечислить подключенные устройства?

    Вроде даже в реестре это лежит? Или это другое?


    1. Racesas Автор
      17.09.2023 09:12
      +2

      Суть задания была сделать это через порты ввода-вывода, да и мне просто интересно было в этом поковыряться :)


  1. 0xdead926e
    17.09.2023 09:12
    -3

    ну, к слову говоря, драйвер можно было и не писать, а взять уже готовый подписанный валидный легитимный, типа AsrDrv101, гигабайтовский GIO, асусовский AsUpIO или вообще RwDrv... но да, так интереснее (:


  1. modsamara
    17.09.2023 09:12

    Кривой драйвер потер файлы с сохранениями игр?


    1. Racesas Автор
      17.09.2023 09:12

      Кривой драйвер не давал запуститься Виндовс, так что пришлось возвращать её в исходное состояние(то есть в состочние на момент установки). При этом все файлы на системном диске были удалены, в том числе и часть сохранений.


      1. modsamara
        17.09.2023 09:12

        сохранить нужные файлы перед возвращением в исходное состояние вы еще не проходили?


        1. Racesas Автор
          17.09.2023 09:12

          В тот момент принципиально важным было спасти свои лабы и курсовой, которые я и сохранил. Про игры как то не вспомнилось.

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


          1. modsamara
            17.09.2023 09:12

            из опыта - вы слишком идеализируете работу точек восстановления

            ну и в посте про лабы ничего не сказано, инфа подается так, что кривой драйвер форматнул системный диск


            1. Racesas Автор
              17.09.2023 09:12

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


      1. Apoheliy
        17.09.2023 09:12

        Загрузиться с livecd и удалить кривой драйвер (или подпихнуть ей заведомо работающий драйвер), потом загрузить винду, которая может чуть-чуть поругается - профит. Не зайдёт?

        Также, по задаче, нужно определиться:

        • либо делаете некоторый драйвер в стиле Hello World (что Вы собственно и сделали) и это хорошо и полезно.

        • либо делаете работу с портами ввода-вывода. Пардонь-те, но ioctl это не порты ввода-вывода, от слова "совсем". Для общения через порты ввода/вывода лучше сделать физическое устройство, оно отобразит (например) регистры на адреса портов и тогда будете гонять данные взад/вперёд. Либо Вам задана задача пробежать все устройства и подёргать у них порты. В общем, туманно.


  1. CaptGg
    17.09.2023 09:12

    ЕС смогли продавить USB-C, осталось продавить сторонние магазины приложений и тогда вопрос с банковскими приложениями будет решен.


    1. CaptGg
      17.09.2023 09:12
      +1

      Как этот комментарий тут оказался? ????


      1. exTvr
        17.09.2023 09:12
        +1

        Его кто-то написал :))