Создание драйверов. Часть 4. Обмен данными. В данной статье рассмотрены способы обмена данными между приложением и драйвером, при помощи рабочих процедур драйвера с IRP кодом IRP_MJ_READ, IRP_MJ_WRITE и IRP_MJ_DEVICE_CONTROL. Из второй части цикла статей и примеров по созданию и работе с драйверами, мы узнали, как отправлять драйверу IOCTL запросы, в зависимости от которых драйвер будет предпринимать те или иные действия.
В обмене данных между приложением и драйвером активное участие принимают IRP(I/o Request Packet) пакеты, в которых содержится фиксированная часть (заголовок) и изменяющаяся часть (стек), IRP пакет представляет собой структуру, более наглядно представленной на рисунке
Таблица: Заголовок IRP пакета
Для получения указателя на стек 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;
Более наглядно взаимодействие приложения и драйвера показано на рисунке:
Рассмотрим обмен данными между приложение и драйвером при помощи функции 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), результат которой представлен на рисунке: