Создание драйверов. Часть 4. Обмен данными

В данной статье рассмотрены способы обмена данными между приложением и драйвером, при помощи рабочих процедур драйвера с IRP кодом IRP_MJ_READ, IRP_MJ_WRITE и IRP_MJ_DEVICE_CONTROL. Из второй части цикла статей и примеров по созданию и работе с драйверами, мы узнали, как отправлять драйверу IOCTL запросы, в зависимости от которых драйвер будет предпринимать те или иные действия.
В обмене данных между приложением и драйвером активное участие принимают IRP(I/o Request Packet) пакеты, в которых содержится фиксированная часть (заголовок) и изменяющаяся часть (стек), IRP пакет представляет собой структуру, более наглядно представленной на рисунке
 Создание драйверов.Часть 4. Обмен данными.
Таблица: Заголовок IRP пакета
 Создание драйверов.Часть 4. Обмен данными.
Для получения указателя на стек IRP пакета используется функция:
PIO_STACK_LOCATION IoGetCurrentIrpStackLocation(IN PIRP Irp);
аргументом, которой является указатель на IRP пакет.

При работе с IRP необходимо указать диспетчеру ввода/вывода (IOManager), каким способом будет происходить управление буферами, существуют три вида:
— буферизованный ввод-вывод (buffered I/O);
— прямой ввод-вывод (direct I/O);
— ввод-вывод без управления (neither I/O).
Установка одного из видов управления осуществляется сразу после создания объекта устройства, то есть после вызова функции IoCreateDevice:

PDEVICE_OBJECT fdo;
status = IoCreateDevice(..,..,..,..,..,..,&fdo);
fdo->Flags |= DO_BUFFERED_IO;
или:
fdo->Flags |= DO_DIRECT_IO;
или:
fdo->Flags |= 0;

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

NTSTATUS TestControl (IN PDEVICE_OBJECT fdo, IN PIRP irp);
NTSTATUS TestWrite (IN PDEVICE_OBJECT fdo, IN PIRP irp);
NTSTATUS TestRead (IN PDEVICE_OBJECT fdo, IN PIRP irp);

и в функции DriverEntry добавить точки входа в драйвер:

DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = TestControl;
DriverObject->MajorFunction[IRP_MJ_READ] = TestRead;
DriverObject->MajorFunction[IRP_MJ_WRITE] = TestWrite;

Более наглядно взаимодействие приложения и драйвера показано на рисунке:
Создание драйверов.Часть 4. Обмен данными.
Рассмотрим обмен данными между приложение и драйвером при помощи функции DeviceIoControl, которая позволяет одновременно передавать в драйвер данные и принимать, для этого служат 3, 4, 5 и 6 аргументы, то есть 3 аргумент, это указатель на данные которые буду передаваться в драйвер, 4 аргумент размер передаваемых данных, соответственно 5 аргумент функции, буфер куда будет происходить прием данных и 6 аргумент размер принятых данных. Более подробное описание функции можно найти в MSDN`е. В прилагаемом примере мною был создан собственный IOCTL запрос при помощи макроса CTL_CODE:
#define IOCTL_FROM_CORE CTL_CODE(0x00008000,0x801,METHOD_BUFFERED,FILE_ANY_ACCESS)
при помощи которого указаны тип устройства, управляющий код, способ получения доступа к буферу и тип доступа, более наглядно вы можете это видеть на следующем рисунке:
 Создание драйверов

char szoutBuff[] = "DeviceIoControl+IOCTL_FROM_CORE";
char szinBuff[80]; ZeroMemory(&szinBuff,sizeof(szinBuff));
if(DeviceIoControl(hHandl,ioctl,
&szinBuff,sizeof(szinBuff),//in
&szoutBuff,sizeof(szoutBuff),//out
&dwReturnBytes,NULL)){/*ok*/}else{/*error*/ }

Рабочая процедура драйвера по обработке IOCTL запросов выглядит следующим образом:

NTSTATUS TestControl (IN PDEVICE_OBJECT fdo, IN PIRP irp)
{
        PVOID pBuffer = NULL;
        UCHAR usString[] = "www.code.hut1.ru";
        PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(irp);
        ULONG ControlCode = IrpStack->Parameters.DeviceIoControl.IoControlCode;
 
        ULONG InputLength  = IrpStack->Parameters.DeviceIoControl.InputBufferLength;
        ULONG OutputLength = IrpStack->Parameters.DeviceIoControl.OutputBufferLength;
 
        pBuffer = irp->UserBuffer;
        if(ControlCode == IOCTL_FROM_CORE)
        {
                #if DBG
                        DbgPrint("IoControlCode IOCTL_FROM_CORE: %d",ControlCode);
                        DbgPrint("TestControl(InputLength): %d",InputLength);
                        DbgPrint("TestControl(OutputLength): %d",OutputLength);
                        DbgPrint("TestControl(pBuffer): %s",pBuffer);
                #endif
 
                RtlCopyMemory(irp->AssociatedIrp.SystemBuffer,&usString,InputLength);
        }
 
        irp->IoStatus.Status      = STATUS_SUCCESS;
        irp->IoStatus.Information = InputLength;
        IoCompleteRequest(irp, IO_NO_INCREMENT);
 
        return STATUS_SUCCESS;
}

Теперь подробнее о коде:
PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(irp); — получение указателя на стек пакета;
ULONG ControlCode = IrpStack->Parameters.DeviceIoControl.IoControlCode; — получение управляющего IOCTL кода;
ULONG InputLength = IrpStack->Parameters.DeviceIoControl.InputBufferLength; — получение размера передаваемого из приложения буфера;
ULONG OutputLength = IrpStack->Parameters.DeviceIoControl.OutputBufferLength; — получение размера буфера которое ожидает приложение;
pBuffer = irp->UserBuffer; — получение переданной строки, в данном случае «DeviceIoControl+IOCTL_FROM_CORE»;
RtlCopyMemory(irp->AssociatedIrp.SystemBuffer,&usString,InputLength); — передача строки приложению, в данном случае «www.code.hut1.ru»;
irp->IoStatus.Information = InputLength; — количество переданных байт.

Следующим шагом будет, запись в драйвер при помощи функции WriteFile:

char outBuffer[] = "This is a test string";
DWORD outCount = sizeof(outBuffer);
DWORD bW;
if(WriteFile(hHandl,outBuffer,outCount,&bW,NULL)){/*ok*/}else{/*error*/}

Код рабочей процедуры драйвера по обработке IRP с кодом IRP_MJ_WRITE:

NTSTATUS TestWrite(IN PDEVICE_OBJECT fdo, IN PIRP irp)
{
        PVOID pBuffer = NULL;
        ULONG ulSize = 0;
 
        PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(irp);
 
        ulSize  = IrpStack->Parameters.Write.Length;
        pBuffer = irp->AssociatedIrp.SystemBuffer;
        
        
#if DBG
        DbgPrint("Run TestWrite");
        DbgPrint("ulSize:  %d", ulSize);
        DbgPrint("pBuffer: %s",pBuffer);
#endif
 
        irp->IoStatus.Status      = STATUS_SUCCESS;
        irp->IoStatus.Information = ulSize;
        IoCompleteRequest(irp, IO_NO_INCREMENT);
                
        return STATUS_SUCCESS;
}

Здесь драйвер просто выводит полученный буфер при помощи функции DbgPrint().
ulSize = IrpStack->Parameters.Write.Length; — получение количества прочитанных байт;
pBuffer = irp->AssociatedIrp.SystemBuffer; — получение переданного буфера.

Чтение данных из драйвера при помощи функции ReadFile:

char inBuffer[80];DWORD inCount = sizeof(inBuffer);DWORD bR;
if(ReadFile(hHandl,inBuffer, inCount, &bR, NULL)){/*ok*/}else{/*error*/ }

Код рабочей процедуры драйвера по обработке IRP с кодом IRP_MJ_READ:

NTSTATUS TestRead(IN PDEVICE_OBJECT fdo, IN PIRP irp)
{
        PVOID pBuffer = NULL;
        UCHAR usString[] = "Code by Lazy_elf";
        ULONG ulSize = 0;
 
        PIO_STACK_LOCATION IrpStack = IoGetCurrentIrpStackLocation(irp);
 
        ulSize  = IrpStack->Parameters.Read.Length;
        pBuffer = irp->AssociatedIrp.SystemBuffer;
 
        RtlCopyMemory(pBuffer,&usString,ulSize);
 
 
 
#if DBG
        DbgPrint("Run TestRead");
        DbgPrint("ulSize:  %d", ulSize);
        DbgPrint("pBuffer: %s",pBuffer);
#endif
        irp->IoStatus.Status      = STATUS_SUCCESS;
        irp->IoStatus.Information = ulSize;
        IoCompleteRequest(irp, IO_NO_INCREMENT);
                
        return STATUS_SUCCESS;
}

Самый важный момент в это участке кода: RtlCopyMemory(pBuffer,&usString,ulSize); — где происходит копирование внутреннего буфера содержавшего строку «Code by Lazy_elf» в буфер который будет передан приложению.
Приложение и драйвер тестировались при помощи программы Dbgview(Mark Russinovich), результат которой представлен на рисунке:
Создание драйверов.Часть 4. Обмен данными.
Создание драйверов.Часть 4. Обмен данными.

Создание драйверов. Часть 4. Обмен данными: 4 комментария

  1. brazer

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

  2. Хлуденьков Владимир

    Очень познавательно. Спасибо большое. Требуем продолжения банкета!

    1. Благин Константин

      Благодарю за положительный комментарий. К сожалению, продолжений не будет, так как в данный момент занимаюсь программированием 1С.

Комментарии запрещены.