An Introduction to IRPs and Device Stacks


IRPs (I/O request packets) are the probably one of most important concepts to understand since they form the foundation of I/O. Please note that the I/O Manager and IRPs are a very lengthy topic, there are many nuances to learn and most of the I/O Manager’s functionality is undocumented. In this article, we’ll be covering the general structure of IRPs and how they are related to the device stack. This will help you understand the purpose of I/O stack locations and how they relate to IRP completion. With this knowledge, you’ll be able to understand and solve a number of more difficult bugchecks, especially crashes which involve a Stop 0x9F, Stop 0x133 or Stop 0x44.

With almost every I/O operation, wherever it be a disk read or a network request, an IRP is usually involved at some point during the lifetime of the operation. IRPs are created during the initiation of a I/O operation and disposed of once the I/O operation has been completed. They are used to enable drivers to communicate with each other during I/O, and the IRP is merely an abstraction of a I/O operation being performed. They are represented by a structure named _IRP. Here the word “opaque” refers to the fact that the structure is partially undocumented.

Typically, an IRP is intialised by the I/O Manager or one of its children: PnP Manager and the Power Manager. Please note that I said “typically” because drivers can create their own IRPs, and as you can imagine, the number of issues which can come with it. In fact, the I/O Manager is simply a set of APIs, there is no process or service which runs in the background.

IRPs are always allocated using non-paged pool. When an IRP is initialised it will specify a number of I/O stack locations. Since most IRPs typically use a common number of stack locations, lookaside lists are used for efficiency. The small lookaside list is used for IRPs which have one stack location; the medium lookaside list is used for requests which have between 2 and 4 stack locations and the large lookaside list is used for requests which have more than 4 stack locations. A non-paged lookaside list is typically represented by an opaque structure called NPAGED_LOOKASIDE_LIST.

The I/O Manager will initialise the first I/O stack location, however, it is the responsibility of all the subsequent drivers to initialise the other required I/O stack locations. A driver will only initialise the next I/O stack location directly below it and not the others. If not enough I/O stack locations are created with the IRP, then eventually the system will crash with a Stop 0x35.

I/O Stack Locations:

Let’s examine the general structure of an IRP and see where I/O stack locations come into play.

8: kd> dt _IRP
   +0x000 Type             : Int2B
   +0x002 Size             : Uint2B
   +0x004 AllocationProcessorNumber : Uint2B
   +0x006 Reserved         : Uint2B
   +0x008 MdlAddress       : Ptr64 _MDL
   +0x010 Flags            : Uint4B
   +0x018 AssociatedIrp    : <anonymous-tag>
   +0x020 ThreadListEntry  : _LIST_ENTRY
   +0x030 IoStatus         : _IO_STATUS_BLOCK
   +0x040 RequestorMode    : Char
   +0x041 PendingReturned  : UChar
   +0x042 StackCount       : Char
   +0x043 CurrentLocation  : Char
   +0x044 Cancel           : UChar
   +0x045 CancelIrql       : UChar
   +0x046 ApcEnvironment   : Char
   +0x047 AllocationFlags  : UChar
   +0x048 UserIosb         : Ptr64 _IO_STATUS_BLOCK
   +0x050 UserEvent        : Ptr64 _KEVENT
   +0x058 Overlay          : <anonymous-tag>
   +0x068 CancelRoutine    : Ptr64     void
   +0x070 UserBuffer       : Ptr64 Void
   +0x078 Tail             : <anonymous-tag>

The StackCount member indicates the number of stack locations which have been allocated to the IRP. The CurrentLocation member indicates which I/O stack location is currently active. As you can see, both members are char types and thus only take up 1 byte.

In fact, the I/O stack locations are connected to the end of the IRP as an array of I/O stack location structures. You can find the first I/O stack location by using the following technique. Firstly, dump the size of the IRP structure using the following expression.

0: kd> ?? sizeof(_IRP)
unsigned int64 0xd0

Next, find the size of the I/O stack location structure.

0: kd> ?? sizeof(_IO_STACK_LOCATION)
unsigned int64 0x48

Now, we have two offsets to work with and we’ll be using these with the address of a known IRP. I’m going to dump all the stack locations using the !irp command to demonstrate where we are in the stack.

0: kd> !irp ffff870aba01f8a0
Irp is active with 6 stacks 4 is current (= 0xffff870aba01fa48)
No Mdl: No System Buffer: Thread 00000000:  Irp stack trace.
     cmd  flg cl Device   File     Completion-Context
[N/A(0), N/A(0)]
            0  0 00000000 00000000 00000000-00000000

            Args: 00000000 00000000 00000000 00000000
[N/A(0), N/A(0)]
            0  0 00000000 00000000 00000000-00000000

            Args: 00000000 00000000 00000000 00000000
            0  0 ffff870ab6e72050 00000000 fffff80486559190-ffff870aba05a5c0
           \Driver\storahci    CLASSPNP!ClasspPowerUpCompletion
            Args: 00000000 00000000 00000000 00000000
            0 e1 ffff870aba05a080 00000000 fffff804855f4730-00000000 Success Error Cancel pending
           \Driver\disk    partmgr!PmPowerCompletion
            Args: 00000000 00000001 00000001 00000000
            0 e1 ffff870ab6fa58d0 00000000 fffff80482177aa0-ffff870aba0721e8 Success Error Cancel pending
           \Driver\partmgr    nt!PopRequestCompletion
            Args: 00000000 00000001 00000001 00000000
[N/A(0), N/A(0)]
            0  0 00000000 00000000 00000000-ffff870aba0721e8

            Args: 00000000 00000000 00000000 00000000

We’re got the base address of the structure and we know the size, so let’s apply that together to find the first I/O stack location. Please note that it will directly follow the stack shown above.

0: kd> ? ffff870aba01f8a0+d0
Evaluate expression: -132994836596368 = ffff870a`ba01f970

Let’s dump it like so.

0: kd> dt _IO_STACK_LOCATION ffff870a`ba01f970
   +0x000 MajorFunction    : 0 ''
   +0x001 MinorFunction    : 0 ''
   +0x002 Flags            : 0 ''
   +0x003 Control          : 0 ''
   +0x008 Parameters       : _IO_STACK_LOCATION::<unnamed-type-Parameters>
   +0x028 DeviceObject     : (null)
   +0x030 FileObject       : (null)
   +0x038 CompletionRoutine : (null)
   +0x040 Context          : (null)

It’s empty as shown in the stack produced by the !irp command. This is to be expected. Let’s jump to the third frame since we know that has data.

0: kd> ? ffff870a`ba01f970+(48*2)
Evaluate expression: -132994836596224 = ffff870a`ba01fa00

Let’s break down this expression quickly. I’ve used the address of the first I/O stack location and then added an offset equivalent to the size of two I/O stack locations since we’re jumping to the third one. Remember the size of a single I/O stack location is 0x48 bytes.

0: kd> dt _IO_STACK_LOCATION ffff870a`ba01fa00
   +0x000 MajorFunction    : 0x16 ''
   +0x001 MinorFunction    : 0 ''
   +0x002 Flags            : 0 ''
   +0x003 Control          : 0 ''
   +0x008 Parameters       : _IO_STACK_LOCATION::<unnamed-type-Parameters>
   +0x028 DeviceObject     : 0xffff870a`b6e72050 _DEVICE_OBJECT
   +0x030 FileObject       : (null)
   +0x038 CompletionRoutine : 0xfffff804`86559190     long  CLASSPNP!ClasspPowerUpCompletion+0
   +0x040 Context          : 0xffff870a`ba05a5c0 Void

As we can see, it’s the same as the third I/O stack location. For completeness, let’s dump the next I/O stack location.

0: kd> ? ffff870a`ba01fa00+48
Evaluate expression: -132994836596152 = ffff870a`ba01fa48
0: kd> dt _IO_STACK_LOCATION ffff870aba01fa48
   +0x000 MajorFunction    : 0x16 ''
   +0x001 MinorFunction    : 0x2 ''
   +0x002 Flags            : 0 ''
   +0x003 Control          : 0xe1 ''
   +0x008 Parameters       : _IO_STACK_LOCATION::<unnamed-type-Parameters>
   +0x028 DeviceObject     : 0xffff870a`ba05a080 _DEVICE_OBJECT
   +0x030 FileObject       : (null)
   +0x038 CompletionRoutine : 0xfffff804`855f4730     long  partmgr!PmPowerCompletion+0
   +0x040 Context          : (null)

Since we had known the address of the active I/O stack location, we could have also simply used +- 0x48 with the known address to see the next and previous I/O stack location.

The number of I/O stack locations should match the number of devices in the device stack. Please note that an IRP may pass through several different device stacks, so as a simplification, when I refer to the device stack, I’ll be talking about all the different device stacks merged together. In our example above, there would have been three different device stacks or three different device nodes. A single device stack represents a device node. Bear in mind, when I mention the word “device” I’m referring to the device node.

Device nodes are located within the device tree and represent an entire device. The device could be a SSD for example. Before we delve into device stacks, it should be noted that there are three different types of device object in the WDM (Windows Driver Model): physical device objects, functional device objects and filter device objects.

Physical device objects aren’t strictly “physical” and merely represent the fact that a device exists within the bus slot. For example, a graphics card driver would generate a form of physical device object. Physical device objects are created by bus drivers such as pci.sys. Upon boot, the PnP Manager will sent out an IRP with the Minor Code (we’ll discuss these later) QUERY_DEVICE_RELATIONS, this is what will be used to determine if a device exists on that particular bus, and if so, a physical device object will generated. You’ll actually end up with a Stop 0xCA crash if this isn’t handled properly.

Functional device objects are the objects which are the actually ‘heavy-duty’ devices which complete the work for the device. These are created by the functional device driver (nvlddmkm.sys) and provide the actual functionality.

Filter device objects are optional and a device may have several filter objects associated to it. These are created by device filter drivers.

Device Stacks:

Now that we’ve discussed device objects and I/O stack locations, let’s see how they relate to each other. To begin, let’s dump the device stack shown in the IRP above. We can do this with a known device object and the !devstack command.

Notice how the device objects correspond to the same device objects shown in the IRP? The first device object to be created at the bottom and the last device object to be created is shown at the top. Each device stack will have one physical device object which is always created first, followed by one functional device object and then a set of optional filter drivers. From the example below, we can see that storahci.sys is the bus driver; disk.sys is the functional device driver and partmgr.sys is the upper filter driver. Any filter drivers which are created before the functional device object are known as lower filter drivers.

Each device object is “attached” to the device object below it through the use of a pointer. This pointer is stored in the the AttachedDevice member of the device object structure shown earlier. The member shows the device object which was attaches above it. If no device object exists above itself, then the member will be null.

0: kd> dt _DEVICE_OBJECT -y AttachedDevice ffff870ab6e72050
   +0x018 AttachedDevice : 0xffff870a`ba05a080 _DEVICE_OBJECT

Each device object attaches itself to the device object below it by calling IoAttachDeviceToDeviceStack API function. Alternatively, in some cases, the IoAttachDevice API function may be used instead.

0: kd> !devstack ffff870ab6e72050
  !DevObj           !DrvObj            !DevExt           ObjectName
  ffff870ab6fa58d0  \Driver\partmgr    ffff870ab6fa5a20  InfoMask field not found for _OBJECT_HEADER at ffff870ab6fa58a0

  ffff870aba05a080  \Driver\disk       ffff870aba05a1d0  InfoMask field not found for _OBJECT_HEADER at ffff870aba05a050

> ffff870ab6e72050  \Driver\storahci   ffff870ab6e721a0  Cannot read info offset from nt!ObpInfoMaskToOffset

!DevNode ffff870ab6e9f420 :
  DeviceInst is "SCSI\Disk&Ven_WDC&Prod_WD2500BEVT-22ZCT\5&399a7c98&0&040000"
  ServiceName is "disk"

The command also provides us with the corresponding device node. However, as we can already see from the instance path, it is likely we are dealing with a Western Digital hard-disk. Nevertheless, let’s dump the device node and see what we can find.

0: kd> !devnode ffff870ab6e9f420
DevNode 0xffff870ab6e9f420 for PDO 0xffff870ab6e72050
  Parent 0xffff870ab6dae9a0   Sibling 0xffff870ab6e9f750   Child 0000000000
  InstancePath is "SCSI\Disk&Ven_WDC&Prod_WD2500BEVT-22ZCT\5&399a7c98&0&040000"
  ServiceName is "disk"
  State = DeviceNodeStarted (0x308)
  Previous State = DeviceNodeEnumerateCompletion (0x30d)
  StateHistory[09] = DeviceNodeEnumerateCompletion (0x30d)
  StateHistory[08] = DeviceNodeEnumeratePending (0x30c)
  StateHistory[07] = DeviceNodeStarted (0x308)
  StateHistory[06] = DeviceNodeStartPostWork (0x307)
  StateHistory[05] = DeviceNodeStartCompletion (0x306)
  StateHistory[04] = DeviceNodeStartPending (0x305)
  StateHistory[03] = DeviceNodeResourcesAssigned (0x304)
  StateHistory[02] = DeviceNodeDriversAdded (0x303)
  StateHistory[01] = DeviceNodeInitialized (0x302)
  StateHistory[00] = DeviceNodeUninitialized (0x301)
  StateHistory[19] = Unknown State (0x0)
  StateHistory[18] = Unknown State (0x0)
  StateHistory[17] = Unknown State (0x0)
  StateHistory[16] = Unknown State (0x0)
  StateHistory[15] = Unknown State (0x0)
  StateHistory[14] = Unknown State (0x0)
  StateHistory[13] = Unknown State (0x0)
  StateHistory[12] = Unknown State (0x0)
  StateHistory[11] = Unknown State (0x0)
  StateHistory[10] = Unknown State (0x0)
  Flags (0x24000130)  DNF_ENUMERATED, DNF_IDS_QUERIED,
  CapabilityFlags (0x00002180)  SilentInstall, RawDeviceOK,

As mentioned earlier, device nodes are part of a larger structure known as a device tree. From the output shown above, we can see that the device node has one sibling node and one parent node. We can use these addresses to transverse through the device tree if we so wish.

IRP Major Codes & Minor Codes:

Now that we have a general idea of how I/O stack locations of an IRP and device stacks are related to each other. Let’s discuss what happens when a device driver receives an IRP. An IRP is always sent to the uppermost driver of a device stack, or put another way, the first I/O stack location associated to the IRP. The device driver receives the IRP via it’s dispatch routine. To illustrate this, I’ll dump the dispatch table for the storahci.sys driver.

0: kd> !drvobj \Driver\storahci 7
Driver object (ffffe3845e4ec410) is for:

Driver Extension List: (id , addr)
(fffff8003ad03b60 ffffe3845d6f5ba0)
Device Object list:

DriverEntry:   fffff8003acbe010    storahci!GsDriverEntry
DriverStartIo: 00000000
DriverUnload:  fffff8003ad02e30    storport!RaDriverUnload
AddDevice:     fffff8003ad02b60    storport!RaDriverAddDevice

Dispatch routines:
[00] IRP_MJ_CREATE                      fffff8003ad41780    storport!RaDriverCreateIrp
[01] IRP_MJ_CREATE_NAMED_PIPE           fffff80038706e00    nt!IopInvalidDeviceRequest
[02] IRP_MJ_CLOSE                       fffff8003ad41830    storport!RaDriverCloseIrp
[03] IRP_MJ_READ                        fffff80038706e00    nt!IopInvalidDeviceRequest
[04] IRP_MJ_WRITE                       fffff80038706e00    nt!IopInvalidDeviceRequest
[05] IRP_MJ_QUERY_INFORMATION           fffff80038706e00    nt!IopInvalidDeviceRequest
[06] IRP_MJ_SET_INFORMATION             fffff80038706e00    nt!IopInvalidDeviceRequest
[07] IRP_MJ_QUERY_EA                    fffff80038706e00    nt!IopInvalidDeviceRequest
[08] IRP_MJ_SET_EA                      fffff80038706e00    nt!IopInvalidDeviceRequest
[09] IRP_MJ_FLUSH_BUFFERS               fffff80038706e00    nt!IopInvalidDeviceRequest
[0a] IRP_MJ_QUERY_VOLUME_INFORMATION    fffff80038706e00    nt!IopInvalidDeviceRequest
[0b] IRP_MJ_SET_VOLUME_INFORMATION      fffff80038706e00    nt!IopInvalidDeviceRequest
[0c] IRP_MJ_DIRECTORY_CONTROL           fffff80038706e00    nt!IopInvalidDeviceRequest
[0d] IRP_MJ_FILE_SYSTEM_CONTROL         fffff80038706e00    nt!IopInvalidDeviceRequest
[0e] IRP_MJ_DEVICE_CONTROL              fffff8003ad27230    storport!SrbShimHookDeviceControl
[0f] IRP_MJ_INTERNAL_DEVICE_CONTROL     fffff8003acda230    storport!RaDriverScsiIrp
[10] IRP_MJ_SHUTDOWN                    fffff80038706e00    nt!IopInvalidDeviceRequest
[11] IRP_MJ_LOCK_CONTROL                fffff80038706e00    nt!IopInvalidDeviceRequest
[12] IRP_MJ_CLEANUP                     fffff80038706e00    nt!IopInvalidDeviceRequest
[13] IRP_MJ_CREATE_MAILSLOT             fffff80038706e00    nt!IopInvalidDeviceRequest
[14] IRP_MJ_QUERY_SECURITY              fffff80038706e00    nt!IopInvalidDeviceRequest
[15] IRP_MJ_SET_SECURITY                fffff80038706e00    nt!IopInvalidDeviceRequest
[16] IRP_MJ_POWER                       fffff8003acdee00    storport!RaDriverPowerIrp
[17] IRP_MJ_SYSTEM_CONTROL              fffff8003ad43dd0    storport!RaDriverSystemControlIrp
[18] IRP_MJ_DEVICE_CHANGE               fffff80038706e00    nt!IopInvalidDeviceRequest
[19] IRP_MJ_QUERY_QUOTA                 fffff80038706e00    nt!IopInvalidDeviceRequest
[1a] IRP_MJ_SET_QUOTA                   fffff80038706e00    nt!IopInvalidDeviceRequest
[1b] IRP_MJ_PNP                         fffff8003acd7d30    storport!RaDriverPnpIrp

If a driver supports a particular IRP then it will have associated dispatch routine in its dispatch table. In this case, when the driver receives an IRP with a major code IRP_MJ_POWER, the storport!RaDriverPowerIrp function will be called. Each major code will then have a set of minor codes associated with it. When the driver receives the IRP via its dispatch routine, it will check what the minor code of the request is and then respond accordingly. In our case, we can see that the device from resuming from a sleep state.

The dispatch table is simply an array of function pointers which point to a dispatch routine. On x64 systems, each entry in the table is 8 bytes. For those interested, we can dump the dispatch table without the assistance of !drvobj using the following method.

0: kd> dt _DRIVER_OBJECT ffffe3845e4ec410
   +0x000 Type             : 0n4
   +0x002 Size             : 0n336
   +0x008 DeviceObject     : 0xffffe384`5e576050 _DEVICE_OBJECT
   +0x010 Flags            : 0x412
   +0x018 DriverStart      : 0xfffff800`3ac90000 Void
   +0x020 DriverSize       : 0x32000
   +0x028 DriverSection    : 0xffffe384`5c2806d0 Void
   +0x030 DriverExtension  : 0xffffe384`5e4ec560 _DRIVER_EXTENSION
   +0x038 DriverName       : _UNICODE_STRING "\Driver\storahci"
   +0x048 HardwareDatabase : 0xfffff800`3912d990 _UNICODE_STRING "\REGISTRY\MACHINE\HARDWARE\DESCRIPTION\SYSTEM"
   +0x050 FastIoDispatch   : (null)
   +0x058 DriverInit       : 0xfffff800`3acbe010     long  storahci!GsDriverEntry+0
   +0x060 DriverStartIo    : (null)
   +0x068 DriverUnload     : 0xfffff800`3ad02e30     void  storport!RaDriverUnload+0
   +0x070 MajorFunction    : [28] 0xfffff800`3ad41780     long  storport!RaDriverCreateIrp+0
0: kd> ? ffffe3845e4ec410+70
Evaluate expression: -31317319302016 = ffffe384`5e4ec480

This is the base address of the array, from here, we can use the dps command and dump all the function pointers.

0: kd> dps ffffe384`5e4ec480 L20
ffffe384`5e4ec480  fffff800`3ad41780 storport!RaDriverCreateIrp << Each offset is 8 bytes; second address is the dispatch routine address
ffffe384`5e4ec488  fffff800`38706e00 nt!IopInvalidDeviceRequest
ffffe384`5e4ec490  fffff800`3ad41830 storport!RaDriverCloseIrp
ffffe384`5e4ec498  fffff800`38706e00 nt!IopInvalidDeviceRequest
ffffe384`5e4ec4a0  fffff800`38706e00 nt!IopInvalidDeviceRequest
ffffe384`5e4ec4a8  fffff800`38706e00 nt!IopInvalidDeviceRequest
ffffe384`5e4ec4b0  fffff800`38706e00 nt!IopInvalidDeviceRequest
ffffe384`5e4ec4b8  fffff800`38706e00 nt!IopInvalidDeviceRequest
ffffe384`5e4ec4c0  fffff800`38706e00 nt!IopInvalidDeviceRequest
ffffe384`5e4ec4c8  fffff800`38706e00 nt!IopInvalidDeviceRequest
ffffe384`5e4ec4d0  fffff800`38706e00 nt!IopInvalidDeviceRequest
ffffe384`5e4ec4d8  fffff800`38706e00 nt!IopInvalidDeviceRequest
ffffe384`5e4ec4e0  fffff800`38706e00 nt!IopInvalidDeviceRequest
ffffe384`5e4ec4e8  fffff800`38706e00 nt!IopInvalidDeviceRequest
ffffe384`5e4ec4f0  fffff800`3ad27230 storport!SrbShimHookDeviceControl
ffffe384`5e4ec4f8  fffff800`3acda230 storport!RaDriverScsiIrp
ffffe384`5e4ec500  fffff800`38706e00 nt!IopInvalidDeviceRequest
ffffe384`5e4ec508  fffff800`38706e00 nt!IopInvalidDeviceRequest
ffffe384`5e4ec510  fffff800`38706e00 nt!IopInvalidDeviceRequest
ffffe384`5e4ec518  fffff800`38706e00 nt!IopInvalidDeviceRequest
ffffe384`5e4ec520  fffff800`38706e00 nt!IopInvalidDeviceRequest
ffffe384`5e4ec528  fffff800`38706e00 nt!IopInvalidDeviceRequest
ffffe384`5e4ec530  fffff800`3acdee00 storport!RaDriverPowerIrp
ffffe384`5e4ec538  fffff800`3ad43dd0 storport!RaDriverSystemControlIrp
ffffe384`5e4ec540  fffff800`38706e00 nt!IopInvalidDeviceRequest
ffffe384`5e4ec548  fffff800`38706e00 nt!IopInvalidDeviceRequest
ffffe384`5e4ec550  fffff800`38706e00 nt!IopInvalidDeviceRequest
ffffe384`5e4ec558  fffff800`3acd7d30 storport!RaDriverPnpIrp
0: kd> ? ffffe384`5e4ec480+0n8
Evaluate expression: -31317319302008 = ffffe384`5e4ec488

This produces the following:

0: kd> dps ffffe384`5e4ec488 L1
ffffe384`5e4ec488  fffff800`38706e00 nt!IopInvalidDeviceRequest

And, again:

0: kd> ? ffffe384`5e4ec480+0n16
Evaluate expression: -31317319302000 = ffffe384`5e4ec490
0: kd> dps ffffe384`5e4ec490 L1
ffffe384`5e4ec490  fffff800`3ad41830 storport!RaDriverCloseIrp

Before we move onto describing how an IRP is completed, I should mention the difference between a Major Code (MJ) and Minor Code (MN) for an IRP is. An MJ provides a category for the type of I/O operation, there is a MJ for every I/O stack location for the associated IRP, and it will be the exact same for each location. A device driver must provide an appropriate dispatch routine for every MJ it wishes to support. For example, if a device driver wishes to support read requests, then it must provide a routine for the IRP_MJ_READ major code.

In some cases, a MJ might be accompanied by a MN, this is used to describe a particular kind of I/O request for the corresponding MJ. For instance, let’s take a look at the IRP_MJ_POWER IRP. It has four different minor codes associated with it, each of which represents a slightly different kind of power request. It wouldn’t be wise to have one general dispatch routine for handling a device powering up and a device powering down. Instead, a handler method is provided as the dispatch routine, and smaller functions are assigned for each individual minor code. One of the MNs you’ll frequently see for a IRP_MJ_POWER request is the IRP_MN_SET_POWER minor code; this is used to set the power state of a particular device.

IRP Completion:

Let’s take a brief look at the completion of an IRP and how it transverses through the device stack. Please note that we’ll assume that the IRP will pass through the device stack with no issues and completes successfully for the sake of simplicity. This is usually not the case unfortunately, hence why you’ll see many Stop 0x9Fs and Stop 0x133s.

As noted earlier, the IRP will always be sent to the top of the device stack, from which the driver will examine through its dispatch routine. When the driver successfully receives the IRP, it has a few different decisions it can make. Please note that the scenarios described are general cases, please bear in mind, there are particular nauances which must be followed for Power and PnP IRPs.

If the IRP is uninteresting to the driver and it has no further input, it can simply pass the IRP onto the next device in the stack by calling IoSkipCurrentIrpLocation and then subsequently calling IoCallDriver. Remember it is the responsibility of the driver to properly initialise the next I/O stack location before passing the IRP on. Additionally, it can add a completion routine to the stack location by calling IoSetCompletionRoutine and then pass the request to the next driver in the stack.

Alternatively, it can complete the IRP immediately and the lower-level drivers will never see the IRP. This is because once an IRP has been completed, it transverses up the device stack, with each stack location being examined to see if it has a registered completion routine, and if so, this is called as well. This process continues until the request reaches the original caller or the I/O Manager.

Lastly, it could do what we all love, and mark the IRP as pending by calling IoMarkIrpPending and returning a status code of STATUS_PENDING. This successfully pends the IRP with the idea that it will be completed at a later time.

I hope that this article helps to clarify how I/O stack locations and device stacks are related, along with their role in IRP completion. If you have any questions, then please let me know.


Windows Internals 7th Edition Part 1

Programming the Windows Kernel

Programming the Windows Driver Model 2nd Edition

About 0x14c

I'm currently a Software Developer. My primary interests are Mathematics, Programming and Windows Internals.
This entry was posted in Windows Internals, Debugging, WinDbg, Stop 0x9F, Stop 0x133. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.