IRP dispatch function and communication mode
I. IRP
1.1 IRP introduction to theoretical knowledge
In the Windows kernel, there is a data structure called IRP(I/O Request Package), that is, input / output request package. It is an important data structure related to input and output. As long as you understand IRP, you will understand more than half of the driver development and kernel.
When the upper application communicates with the driver, the application will issue} I/O requests. The operating system will translate the request into the corresponding IRP data. Different IRP data will be passed to different dispatch functions according to types.
1.2 type of IRP
When the application layer calls WINAPI functions such as ReadFile WriteFile CreateFile CloseHandle, the corresponding IRP type IRP will be generated, that is, IRP_MJ_CREATE IRP_MJ_WRITE IRP_MJ_READ IRP_MJ_CLOSE and pass it to the dispatch function in the driver.
In addition, the I/O processing function in the kernel will also generate IRP, so it can be seen that IRP is not completely generated by the application layer. For example, the file operation at the beginning of the Zw Series in the kernel will produce IRP.
IRP type | source |
---|---|
IRP_MJ_CREATE | CreateFile/ZwCreateFile |
IRP_MJ_READ | ReadFile/ZwReadFile |
IRP_MJ_WRITE | WriteFile/ZwWriteFile |
IRP_MJ_CLOSE | CloseHandle/ZwClose |
... | ... |
... | ... |
1.3 dispatch function
When we know the IRP type, we only need to set the dispatch function for the driver. In this way, when the application layer calls the corresponding Winapi to send the IO request packet, our dispatch function will get it.
The code is as follows:
extern "C" NTSTATUS DriverEntry ( IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegistryPath ) { NTSTATUS status; KdPrint(("Enter DriverEntry\n")); //Set unload function pDriverObject->DriverUnload = HelloDDKUnload; //Set dispatch function pDriverObject->MajorFunction[IRP_MJ_CREATE] = HelloDDKDispatchRoutin; pDriverObject->MajorFunction[IRP_MJ_CLOSE] = HelloDDKDispatchRoutin; pDriverObject->MajorFunction[IRP_MJ_WRITE] = HelloDDKDispatchRoutin; pDriverObject->MajorFunction[IRP_MJ_READ] = HelloDDKDispatchRoutin; pDriverObject->MajorFunction[IRP_MJ_CLEANUP] = HelloDDKDispatchRoutin; pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = HelloDDKDispatchRoutin; pDriverObject->MajorFunction[IRP_MJ_SET_INFORMATION] = HelloDDKDispatchRoutin; pDriverObject->MajorFunction[IRP_MJ_SHUTDOWN] = HelloDDKDispatchRoutin; pDriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = HelloDDKDispatchRoutin; //Create drive device object status = CreateDevice(pDriverObject); KdPrint(("Leave DriverEntry\n")); return status; }
There is a driver object parameter in our DriverEntry, where the MajorFunction of this parameter is an array. The array stores the callback function pointer of the dispatch function of IRP type. So we set it according to the above. When winapi sends an IO request, the corresponding dispatch function will be called.
1.4 equipment object and symbol link
The device object is also a very important object in the driver. Our IRP is to be sent to the device. So you need to create a device object. However, if the application layer wants to send IO requests (call WINAPI), the kernel driver must provide a symbolic link to the application layer. After the kernel layer creates the device, it can also specify the communication mode. That is, how the application driver communicates. How data is transmitted. We'll talk about this later.
The code is as follows:
NTSTATUS CreateDevice ( IN PDRIVER_OBJECT pDriverObject) { NTSTATUS status; PDEVICE_OBJECT pDevObj; PDEVICE_EXTENSION pDevExt; //Create device name UNICODE_STRING devName; RtlInitUnicodeString(&devName,L"\\Device\\MyDDKDevice"); //Create device status = IoCreateDevice( pDriverObject, sizeof(DEVICE_EXTENSION), &(UNICODE_STRING)devName, FILE_DEVICE_UNKNOWN, 0, TRUE, &pDevObj ); if (!NT_SUCCESS(status)) return status; pDevObj->Flags |= DO_BUFFERED_IO; //The setting of communication mode will be described later. pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension; pDevExt->pDevice = pDevObj; pDevExt->ustrDeviceName = devName; //Create symbolic links UNICODE_STRING symLinkName; RtlInitUnicodeString(&symLinkName,L"\\??\\HelloDDK"); pDevExt->ustrSymLinkName = symLinkName; status = IoCreateSymbolicLink( &symLinkName,&devName ); if (!NT_SUCCESS(status)) { IoDeleteDevice( pDevObj ); return status; } return STATUS_SUCCESS; }
1.5 introduction to IRP stack
IPR stack is also an important IO packet structure. Because the data recorded in the IRP structure is insufficient to meet our needs. Therefore, an IRP stack is provided. For example, the I/O request issued by the application is a read request, and this request will be sent to the read dispatch function of the kernel. Then the stack is the read stack. Therefore, different types of stacks will be filled with different contents.
Officially, the driver creates a device object and concatenates these device objects together. If a device stack is formed, the IRP will be sent to the top layer of the device stack by the operating system. If the dispatch function of the top-level device object ends the IRP request, the IRP request will end and will not be sent down. Otherwise, the operating system will forward the IRP to the next layer of the device stack for processing. If the device still can't handle it, continue to send it. Therefore, IRP will be forwarded multiple times. In order to record the operation of IRP in each layer of equipment, IRP will have a stack array. The number of stack array elements of IRP should be greater than the number of devices traversed by IRP. Each stack structure element records the operation made by the corresponding device.
The stack array structure described above is as follows:
Array name structure: IO_STACK_LOCATION
typedef struct _IO_STACK_LOCATION { UCHAR MajorFunction; UCHAR MinorFunction; UCHAR Flags; UCHAR Control; union { // // Parameters for IRP_MJ_CREATE // struct { PIO_SECURITY_CONTEXT SecurityContext; ULONG Options; USHORT POINTER_ALIGNMENT FileAttributes; USHORT ShareAccess; ULONG POINTER_ALIGNMENT EaLength; } Create; // // Parameters for IRP_MJ_READ // struct { ULONG Length; ULONG POINTER_ALIGNMENT Key; LARGE_INTEGER ByteOffset; } Read; // // Parameters for IRP_MJ_WRITE // struct { ULONG Length; ULONG POINTER_ALIGNMENT Key; LARGE_INTEGER ByteOffset; } Write; // // Parameters for IRP_MJ_QUERY_INFORMATION // struct { ULONG Length; FILE_INFORMATION_CLASS POINTER_ALIGNMENT FileInformationClass; } QueryFile; // // Parameters for IRP_MJ_SET_INFORMATION // struct { ULONG Length; FILE_INFORMATION_CLASS POINTER_ALIGNMENT FileInformationClass; PFILE_OBJECT FileObject; union { struct { BOOLEAN ReplaceIfExists; BOOLEAN AdvanceOnly; }; ULONG ClusterCount; HANDLE DeleteHandle; }; } SetFile; // // Parameters for IRP_MJ_QUERY_VOLUME_INFORMATION // struct { ULONG Length; FS_INFORMATION_CLASS POINTER_ALIGNMENT FsInformationClass; } QueryVolume; // // Parameters for IRP_MJ_DEVICE_CONTROL and IRP_MJ_INTERNAL_DEVICE_CONTROL // struct { ULONG OutputBufferLength; ULONG POINTER_ALIGNMENT InputBufferLength; ULONG POINTER_ALIGNMENT IoControlCode; PVOID Type3InputBuffer; } DeviceIoControl; .............................. } Parameters; PDEVICE_OBJECT DeviceObject; PFILE_OBJECT FileObject; . . . } IO_STACK_LOCATION, *PIO_STACK_LOCATION;
In this structure, we can see the record field of IRP type
UCHAR MajorFunction; UCHAR MinorFunction;
The device object file object is also recorded
PDEVICE_OBJECT DeviceObject; PFILE_OBJECT FileObject;
The more important one is the Parameters parameter. It records Read write DeviceIoControl create and other structures. When our IRP type is Read. The dispatch function can obtain the Read length offset and other information from the Read field.
The API used to call the stack information of this layer is as follows
PIO_STACK_LOCATION IoGetCurrentIrpStackLocation( IN PIRP Irp );
II. Communication mode between kernel and application layer cache mode (buffer mode)
2.1 cache mode
The caching method is that the application layer sends data to the kernel layer, and the kernel layer establishes a buffer to save. And we can operate this buffer. The advantage is security and stability. The disadvantage is slow efficiency.
The caching method is after we create the device object. Set the flag of the device object to DO+_BUFFERD_IO
Just.
pDevObj->Flags |= DO_BUFFERED_IO;
If set to buffer mode. Then we only need to get the associated IRP in the IRP structure Just systembuffer.
The IRP structure is as follows
typedef struct _IRP { . . PMDL MdlAddress; //Direct IO will use ULONG Flags; union { struct _IRP *MasterIrp; . . PVOID SystemBuffer; //Buffer mode usage } AssociatedIrp; . . IO_STATUS_BLOCK IoStatus; //state KPROCESSOR_MODE RequestorMode; BOOLEAN PendingReturned; . . BOOLEAN Cancel; KIRQL CancelIrql; PDRIVER_CANCEL CancelRoutine; PVOID UserBuffer; //Other ways . . . . . . . } IRP, *PIRP;
2.2 IRP size acquisition such as read and write control
If the buffer mode is specified in our dispatch function. Then we can get the SystemBuffer from IRP and use it.
However, the dispatch function will allocate different dispatch function calls according to different types of IRP. There will be IRP_ MJ_ READ IRP_ MJ_ WRITE IPR_ MJ_ According to different dispatch functions, the size of user transfer buffer obtained by DeviceControl is also different.
Such as IRP_MJ_READ
We want to set the parameters in the IRP stack Read. Length to get the length
If IRP_MJ_WRITE, then the corresponding should be in write Length to get the length
If it is in Control, it is obtained in DeviceIoControl.
Among them, it is also special, and its domain is as follows:
struct { ULONG OutputBufferLength; ULONG POINTER_ALIGNMENT InputBufferLength; ULONG POINTER_ALIGNMENT IoControlCode; PVOID Type3InputBuffer; } DeviceIoControl;
It records the length of the output buffer passed by the application layer. Enter the length of the buffer. Control code.
And a buffer for user areas using other communication types. It will be said later.
2.3 examples of using cache dispatch function
NTSTATUS DisPathchRead_SystemBuffer(PDEVICE_OBJECT pDeviceobj, PIRP pIrp) { KdBreakPoint(); PVOID pBuffer = NULL; ULONG uReadLength = 0; ULONG ustrLen = 0; PIO_STACK_LOCATION pIrpStack = NULL; pIrpStack = IoGetCurrentIrpStackLocation(pIrp); //Get stack pBuffer = pIrp->AssociatedIrp.SystemBuffer; //Get buffer in cache mode uReadLength = pIrpStack->Parameters.Read.Length;//Get the length in different fields according to different types if (pBuffer != NULL && uReadLength > 0) { ustrLen = strlen("HelloWorld"); if (uReadLength < ustrLen) goto END; RtlCopyMemory(pBuffer, "HelloWorld", ustrLen); } END: pIrp->IoStatus.Information = ustrLen; pIrp->IoStatus.Status = STATUS_SUCCESS; IoCompleteRequest(pIrp, IO_NO_INCREMENT); return STATUS_SUCCESS; }
III. MDL mode (direct IO mode)
3.1 direct IO mode
The Mdl method is to map the Buffer passed by the user. A copy is mapped in the kernel. In this way, the buffers of user mode and kernel mode point to the same physical memory. No matter how the operating system switches the process, the address of kernel mode will not change.
Advantages: high speed, safety and stability.
Using MDL mode, first of all, set the device communication mode to direct IO mode after creating the device.
As follows:
pDeviceObj->Flags |= DO_DIRECT_IO;
After setting, the MdlAddress in the IRP domain records the mapped address
3.2Mdl structure
MDL is a structure that records this virtual memory (user's buffer).
Because the memory is discontinuous, MDL records like a linked list
typedef struct _MDL { struct _MDL *Next; //Next MDL CSHORT Size; //Record itself MD CSHORT MdlFlags; struct _EPROCESS *Process; //Record the EP of the current process PVOID MappedSystemVa;//Record the address in the kernel PVOID StartVa; //Record first page address ULONG ByteCount; //Record virtual machine memory size ULONG ByteOffset;//Record offset from page } MDL, *PMDL;
MmGetMdlVirtualAddress Returns the virtual memory address of the i/o buffer described by the MDL.
MmGetMdlByteCount Returns the size of the i/o buffer in bytes.
MmGetMdlByteOffset Returns the offset within the physical page at the beginning of the i/o buffer.
MmGetSystemAddressForMdlSafe The routine maps the physical page described by the specified MDL to the virtual address in the system address space
There are many MDL S. If you need to know more about them, you can see the Microsoft documentation.
Here is only what we need to use.
The first address of virtual memory is VA = StartVa + ByteOffset calculated by us
3.3 example of direct IO communication
NTSTATUS DisPathchRead_Mdl(PDEVICE_OBJECT pDeviceobj, PIRP pIrp) { KdBreakPoint(); PVOID pBuffer = NULL; ULONG uReadLength = 0; ULONG uOffset = 0; ULONG ustrLen = 0; PIO_STACK_LOCATION pIrpStack = NULL; PVOID pKernelbase = NULL; //Get the stack, which is useless in the example. When using, you need to obtain the length of the operation according to the IRP type pIrpStack = IoGetCurrentIrpStackLocation(pIrp); uReadLength = MmGetMdlByteCount(pIrp->MdlAddress);//Get IO length (of array) uOffset = MmGetMdlByteOffset(pIrp->MdlAddress); //Page offset pBuffer = MmGetMdlVirtualAddress(pIrp->MdlAddress);//First page //Get the address mapped in the kernel pKernelbase = MmGetSystemAddressForMdlSafe(pIrp->MdlAddress, NormalPagePriority); if (pKernelbase != NULL && uReadLength > 0) { ustrLen = strlen("HelloWorld"); if (uReadLength < ustrLen) goto END; RtlCopyMemory(pKernelbase, PsGetProcessImageFileName(pIrp->MdlAddress->Process), ustrLen); } END: pIrp->IoStatus.Information = ustrLen; pIrp->IoStatus.Status = STATUS_SUCCESS; IoCompleteRequest(pIrp, IO_NO_INCREMENT); return STATUS_SUCCESS; }
IV. reading and writing in other ways
4.1 other methods
Other ways of reading and writing are to directly pass the user's buffer to the kernel. However, if the process switches in this way, it will cause a blue screen. So we need to judge whether it is readable. A little efficiency is the fastest. Disadvantages are unsafe.
Set the flag in the device object to 0
pDeviceObj->Flags = 0;
The read and write data are in the UserBuffer in the IRP structure.
Examples are as follows:
NTSTATUS DisPathchRead_UserBuffer(PDEVICE_OBJECT pDeviceobj, PIRP pIrp) { KdBreakPoint(); PVOID pBuffer = NULL; ULONG uReadLength = 0; ULONG uOffset = 0; ULONG ustrLen = 0; PIO_STACK_LOCATION pIrpStack = NULL; PVOID pKernelbase = NULL; pIrpStack = IoGetCurrentIrpStackLocation(pIrp); //Get stack pBuffer = pIrp->UserBuffer; uReadLength = pIrpStack->Parameters.Read.Length;//Different lengths are obtained according to different IRP types if (pBuffer != NULL && uReadLength > 0) { ustrLen = strlen("HelloWorld"); if (uReadLength < ustrLen) goto END; __try { ProbeForRead(pBuffer, 1, 1);//Whether it is readable or writable. ProbeForWrite RtlCopyMemory(pKernelbase, PsGetProcessImageFileName(pIrp->MdlAddress->Process), ustrLen); } __except (EXCEPTION_EXECUTE_HANDLER) { ..... } } END: pIrp->IoStatus.Information = ustrLen; pIrp->IoStatus.Status = STATUS_SUCCESS; IoCompleteRequest(pIrp, IO_NO_INCREMENT); return STATUS_SUCCESS; }