It sounds obvious, but sometimes it needs to be stated. For instance, let's say that you are allocating your own IRP, your context contains I/O related data (like a URB) and you encounter the issue where the DeviceObject passed to your I/O completion routine is NULL. Adding another stack location is one solution I wrote about, but it is a little complicated and has a certain black magic feeling to it, besides it does not work with any IoBuildXxx API calls. Since you are allocating memory for your context, just allocate a little bit more. Instead of allocating just the URB
PURB urb = ExAllocatePoolWithTag(NonPagedPool, sizeof(URB), ...); PIRP irp = IoAllocateIrp(...); ... // format the URB IoSetCompletionRoutine(Irp, CompletionRoutine, urb, TRUE, TRUE, TRUE); ... IoCallDriver(..., Irp); ... NTSTATUS CompletionRoutine(PDEVIC_OBJECT Device, PIRP Irp, PURB Urb) { // Do not touch Device, it is NULL! // Process Urb and Irp results ExFreePool(Urb); IoFreeIrp(Irp); return STATUS_MORE_PROCESSING_REQUIRED; }
allocate a structure to contain the URB and your DeviceObject pointer (or WDFDEVICE or anything else)
typedef struct _COMPLETION_DATA { PDEVICE_OBJECT DeviceObject; URB Urb; } COMPLETION_DATA, *PCOMPLETION_DATA; PCOMPLETION_DATA data = ExAllocatePoolWithTag(NonPagedPool, sizeof(COMPLETION_DATA), ...); PIRP irp = IoAllocateIrp(...); ... // format data->Urb data->DeviceObject = DeviceObject; IoSetCompletionRoutine(Irp, CompletionRoutine, data, TRUE, TRUE, TRUE); ... IoCallDriver(..., Irp); ... NTSTATUS CompletionRoutine(PDEVIC_OBJECT Device, PIRP Irp, PCOMPLETION_DATA Data) { // Do not touch Device, it is NULL, but Data->Device is valid! // Process Data->Urb and Irp results ExFreePool(Data); IoFreeIrp(Irp); return STATUS_MORE_PROCESSING_REQUIRED; }
Like I said, it is an obvious thing to do :), but when you are in the depths of debugging a bugcheck, sometimes the obvious solution is the last thing you think of.