为什么在用户程序中dynamic分配缓冲区会使内核驱动程序崩溃

我有一个程序,分配一个缓冲区的指针通过自定义的IOCTL传递给内核驱动程序。 在驱动程序中,我得到一个Mdl,并用“MmGetSystemAddressForMdlSafe”locking用户程序缓冲区的页面,然后使用Mdl填充用户程序缓冲区。

如果在用户程序中缓冲区是一个正常的数组,驱动程序总是按照它应该的那样工作。 (WORD缓冲区[256],其中字是一个无符号的短)

如果用户程序缓冲区被分配了新的关键字(WORD *buffer = new WORD[256])或malloc关键字(WORD *buffer=(WORD*) malloc(sizeof(*buffer)*256)))到时候我得到一个蓝屏,错误是“页面错误在非分页区”。

为什么?

谢谢!

编辑(更多详情):

在驱动程序中,我使用MmGetSystemAddressForMdlSafe这种方式:

PVOID p_buffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, HighPagePriority);

Irp是我在处理IRP_MJ_DEVICE_CONTROL MajorFunction时作为第二个参数接收的IRP_MJ_DEVICE_CONTROL

在检查了p_buffer不为空之后,我使用该指针写入用户缓冲区:

READ_PORT_BUFFER_USHORT((PUSHORT)(USHORT)current_port.address, (PUSHORT)p_buffer, 256)

IOCTL定义:

 #define IOCTL_TEST_READPORT CTL_CODE(FILE_DEVICE_TEST, \ TEST_IOCTL_INDEX + 0, \ METHOD_OUT_DIRECT, \ FILE_ANY_ACCESS) 

处理IRP_MJ_DEVICE_CONTROL驱动程序函数:

  NTSTATUS TESTDispatch(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { PIO_STACK_LOCATION IrpStack; ULONG input_buffer_size; ULONG output_buffer_size; ULONG control_code; PVOID p_buffer; NTSTATUS nt_status; struct port current_port; UNREFERENCED_PARAMETER(DeviceObject); PAGED_CODE(); Irp->IoStatus.Status = STATUS_SUCCESS; Irp->IoStatus.Information = 0; IrpStack = IoGetCurrentIrpStackLocation(Irp); switch (IrpStack->MajorFunction) { case IRP_MJ_DEVICE_CONTROL: control_code = IrpStack->Parameters.DeviceIoControl.IoControlCode; switch (control_code) { case IOCTL_TEST_READPORT: p_buffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, HighPagePriority); input_buffer_size = IrpStack->Parameters.DeviceIoControl.InputBufferLength; if (!p_buffer) { nt_status = STATUS_INSUFFICIENT_RESOURCES; break; } if (input_buffer_size) { memcpy (&current_port, Irp->AssociatedIrp.SystemBuffer, input_buffer_size); switch (current_port.size) { case 1: current_port.value = (ULONG)READ_PORT_UCHAR((PUCHAR)(USHORT)current_port.address); memcpy (p_buffer, &current_port.value, sizeof(current_port.value)); Irp->IoStatus.Information = sizeof(current_port.value); break; case 0xF0: READ_PORT_BUFFER_USHORT((PUSHORT)(USHORT)current_port.address, (PUSHORT)p_buffer, 256); Irp->IoStatus.Information = sizeof(current_port.value); break; case 2: current_port.value = (ULONG)READ_PORT_USHORT((PUSHORT)(USHORT)current_port.address); memcpy (p_buffer, &current_port.value, sizeof(current_port.value)); Irp->IoStatus.Information = sizeof(current_port.value); break; } } else Irp->IoStatus.Status = STATUS_INVALID_PARAMETER; break; case IRP_MJ_CREATE: KdPrint(("IRP_MJ_CREATE")); break; case IRP_MJ_CLOSE: KdPrint(("IRP_MJ_CLOSE")); break; default: Irp->IoStatus.Status = STATUS_INVALID_PARAMETER; break; } break; } nt_status = Irp->IoStatus.Status; IoCompleteRequest (Irp, IO_NO_INCREMENT); return nt_status; } 

相关的情况是case 0xF0: case IOCTL_TEST_READPORT:

从我的理解你是误解MmGetSystemAddressForMdlSafe的目的。 根据本文档 ,您可以使用此函数来获取由MDL(内存描述符列表)描述的虚拟地址。

。 如果驱动程序必须使用虚拟地址访问由MDL描述的页面,则必须使用MmGetSystemAddressForMdlSafe将这些页面映射到系统地址空间

同样的文件也这样说:

要使用虚拟地址访问由MDL描述的缓冲区,驱动程序调用MmGetSystemAddressForMdlSafe将缓冲区映射到系统空间。

MmGetSystemAddressForMdlSafe :将MDL描述的物理页面映射到系统空间,并返回MDL的虚拟地址。 返回的虚拟地址可以在任何IRQL和任何进程上下文中使用。

如果您查看MmGetSystemAddressForMdlSafe的MSDN文档 ,那么您将看到以下行:

MmGetSystemAddressForMdlSafe宏为指定的MDL描述的缓冲区返回一个未分页的系统空间虚拟地址。

它说这个函数返回一个由MDL描述的缓冲区的非分页虚拟地址。

MDL的定义如下:

内存描述符列表(MDL)描述物理内存中的页面列表。

这是对物理内存中的页面的描述,而不是虚拟内存。 new分配的缓冲区已经有了虚拟地址,试图使用MmGetSystemAddressForMdlSafe ,是错误的。 您应该使用该函数从MDL获取虚拟地址,而不是从虚拟地址范围获取MDL。

现在,转到解释page fault in non-paged areapage fault in non-paged area

现在,如果你仔细想想,你的缓冲区可能是由new或者malloc分配的,这个缓冲区已经存在于一个分页的内存区域(事实上,看到这个缓冲区在用户的土地里,这很有可能),这意味着试图获得一个虚拟地址这个缓冲区(这是因为它不是一个MDL已经错了),将导致非页面区域页面错误 ,因为缓冲区的内存是在一个页面区域,而你映射到一个非页面区域内核和非分页区域不能导致页面错误。 (最有可能将与错误的IRQL级别有关)