1、直接读写方式
操作系统将用户模式下的缓冲区锁住,然后操作系统将这段缓冲区在内核模式地址再映射一遍。这样,用户模式的缓冲区和内核模式的缓冲区指向的是同一区域的物理地址。
操作系统将用户模式的地址锁定后,用内存描述符MDL记录这段内存。
MDL 示意图
比如mdl->ByteCount就是记录的虚拟内存的大小。可以用几个宏来得到其值。
The MmGetMdlByteCount macro returns the length in bytes of the buffer described by a given MDL.
http://msdn.microsoft.com/en-us/library/ff554530%28VS.85%29.aspx
ReadFile的第三个参数为希望读的长度,等于stack->Parameters.Read.Length.派遣函数设置pRrp->IoStatus.Infomation告诉ReadFile实际读了多少字节,对应第四个参数。
pIrp->MdlAddress记录了MDL数据结构
示例代码 P206
1 NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,
2 IN PIRP pIrp)
3 {
4 KdPrint(( " Enter HelloDDKRead\n " ));
5 // 扩展设备
6 PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)pDevObj -> DeviceExtension;
7 NTSTATUS status = STATUS_SUCCESS;
8 // 当前I/O堆栈
9 PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);
10 // 获取指定的的读字节数
11 ULONG ulReadLength = stack -> Parameters.Read.Length;
12 KdPrint(( " ulReadLength:%d\n " ,ulReadLength));
13
14 // 得到锁到的缓冲区的长度
15 ULONG mdl_length = MmGetMdlByteCount(pIrp -> MdlAddress);
16 // 得到锁到的缓冲区的首地址
17 PVOID mdl_address = MmGetMdlVirtualAddress(pIrp -> MdlAddress);
18 // 得到锁到的缓冲区的偏移量
19 ULONG mdl_offset = MmGetMdlByteOffset(pIrp -> MdlAddress);
20
21 KdPrint(( " mdl_address:0X%08X\n " ,mdl_address));
22 KdPrint(( " mdl_length:%d\n " ,mdl_length));
23 KdPrint(( " mdl_offset:%d\n " ,mdl_offset));
24
25 if (mdl_length != ulReadLength)
26 {
27 // MDL的长度应该和读长度相等,否则该操作应该设为不成功
28 pIrp -> IoStatus.Information = 0 ;
29 status = STATUS_UNSUCCESSFUL;
30 } else
31 {
32 // 用MmGetSystemAddressForMdlSafe得到MDL在内核模式下的映射
33 PVOID kernel_address = MmGetSystemAddressForMdlSafe(pIrp -> MdlAddress,NormalPagePriority);
34 KdPrint(( " kernel_address:0X%08X\n " ,kernel_address));
35 memset(kernel_address, 0XAA ,ulReadLength);
36 pIrp -> IoStatus.Information = ulReadLength; // bytes xfered
37 }
38
39 pIrp -> IoStatus.Status = status;
40
41 IoCompleteRequest( pIrp, IO_NO_INCREMENT );
42 KdPrint(( " Leave HelloDDKRead\n " ));
43
44 return status;
45 }
46
The DeviceIoControl function sends a control code directly to a specified device driver, causing the corresponding device to perform the corresponding operation.
来进行读写,及应用程序与驱动间的通信。需要一个I/O控制码,DeviceIoControl 把这个控制码和请求一起传递给驱动程序,在派遣函数中分别对不同I/O控制码进行处理。
DeviceIoControl->lpBytesReturned 对应着IRP中的pIrp->IoStatus.Infomation.
DeviceIoControl的内部,用户提供的输入缓冲区内容被复制到IRP中的pIrp->Associate.SystemBuffer内存地址,复制的字节数由DeviceIoControl指定的输入字节。
派遣函数读取pIrp->Associate.SystemBuffer内存地址,从而获取应用程序提供的输入数据。派遣函数还可以写入pIrp->Associate.SystemBuffer的内存地址,这被当作设备输出数据。系统会把这个地址的数据再次复制到DeviceIoControl提供的缓冲区。复制的字节由pIrp->IoStatus.Infomation指定。DeviceIoControl通过第7个参数得到这个字节数。
示例代码 P211
1 #pragma PAGEDCODE
2 NTSTATUS HelloDDKDeviceIOControl(IN PDEVICE_OBJECT pDevObj,
3 IN PIRP pIrp)
4 {
5 NTSTATUS status = STATUS_SUCCESS;
6 KdPrint(( " Enter HelloDDKDeviceIOControl\n " ));
7
8 // 得到当前堆栈
9 PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(pIrp);
10 // 得到输入缓冲区大小
11 ULONG cbin = stack -> Parameters.DeviceIoControl.InputBufferLength;
12 // 得到输出缓冲区大小
13 ULONG cbout = stack -> Parameters.DeviceIoControl.OutputBufferLength;
14 // 得到IOCTL码
15 ULONG code = stack -> Parameters.DeviceIoControl.IoControlCode;
16
17 ULONG info = 0 ;
18
19 switch (code)
20 { // process request
21 case IOCTL_TEST1:
22 {
23 KdPrint(( " IOCTL_TEST1\n " ));
24 // 缓冲区方式IOCTL
25 // 显示输入缓冲区数据
26 UCHAR * InputBuffer = (UCHAR * )pIrp -> AssociatedIrp.SystemBuffer;
27 for (ULONG i = 0 ;i < cbin;i ++ )
28 {
29 KdPrint(( " %X\n " ,InputBuffer[i]));
30 }
31
32 // 操作输出缓冲区
33 UCHAR * OutputBuffer = (UCHAR * )pIrp -> AssociatedIrp.SystemBuffer;
34 memset(OutputBuffer, 0xAA ,cbout);
35 // 设置实际操作输出缓冲区长度
36 info = cbout;
37 break ;
38 }
39 case IOCTL_TEST2:
40 {
41 KdPrint(( " IOCTL_TEST2\n " ));
42 // 缓冲区方式IOCTL
43 // 显示输入缓冲区数据
44
45 // 缓冲区方式IOCTL
46 // 显示输入缓冲区数据
47 UCHAR * InputBuffer = (UCHAR * )pIrp -> AssociatedIrp.SystemBuffer;
48 for (ULONG i = 0 ;i < cbin;i ++ )
49 {
50 KdPrint(( " %X\n " ,InputBuffer[i]));
51 }
52
53 // pIrp->MdlAddress为DeviceIoControl输出缓冲区地址相同
54 KdPrint(( " User Address:0X%08X\n " ,MmGetMdlVirtualAddress(pIrp -> MdlAddress)));
55
56 UCHAR * OutputBuffer = (UCHAR * )MmGetSystemAddressForMdlSafe(pIrp -> MdlAddress,NormalPagePriority);
57 // InputBuffer被映射到内核模式下的内存地址,必定在0X80000000-0XFFFFFFFF之间
58 memset(OutputBuffer, 0xAA ,cbout);
59 // 设置实际操作输出缓冲区长度
60 info = cbout;
61 break ;
62 }
63 case IOCTL_TEST3:
64 {
65 KdPrint(( " IOCTL_TEST3\n " ));
66 // 缓冲区方式IOCTL
67
68 // 缓冲区方式IOCTL
69 // 显示输入缓冲区数据
70 UCHAR * UserInputBuffer = (UCHAR * )stack -> Parameters.DeviceIoControl.Type3InputBuffer;
71 KdPrint(( " UserInputBuffer:0X%0X\n " ,UserInputBuffer));
72
73 // 得到用户模式地址
74 PVOID UserOutputBuffer = pIrp -> UserBuffer;
75
76 KdPrint(( " UserOutputBuffer:0X%0X\n " ,UserOutputBuffer));
77
78 __try
79 {
80 KdPrint(( " Enter __try block\n " ));
81
82 // 判断指针是否可读
83 ProbeForRead(UserInputBuffer,cbin, 4 );
84 // 显示输入缓冲区内容
85 for (ULONG i = 0 ;i < cbin;i ++ )
86 {
87 KdPrint(( " %X\n " ,UserInputBuffer[i]));
88 }
89
90 // 判断指针是否可写
91 ProbeForWrite(UserOutputBuffer,cbout, 4 );
92
93 // 操作输出缓冲区
94 memset(UserOutputBuffer, 0xAA ,cbout);
95
96 // 由于在上面引发异常,所以以后语句不会被执行!
97 info = cbout;
98
99 KdPrint(( " Leave __try block\n " ));
100 }
101 __except(EXCEPTION_EXECUTE_HANDLER)
102 {
103 KdPrint(( " Catch the exception\n " ));
104 KdPrint(( " The program will keep going\n " ));
105 status = STATUS_UNSUCCESSFUL;
106 }
107
108 info = cbout;
109 break ;
110 }
111
112 default :
113 status = STATUS_INVALID_VARIANT;
114 }
115
116 // 完成IRP
117 pIrp -> IoStatus.Status = status;
118 pIrp -> IoStatus.Information = info; // bytes xfered
119 IoCompleteRequest( pIrp, IO_NO_INCREMENT );
120
121 KdPrint(( " Leave HelloDDKDeviceIOControl\n " ));
122
123 return status;
124 }
[1]驱动详解