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.