Skip to content
Martin Drab edited this page Mar 14, 2020 · 2 revisions

Each loaded driver is represented by a kernel object of Driver type. The structure of the object body is defined as DRIVER_OBJECT and you can see it below. This chapter describes its members that are interesting in the IRPMon context. The most interesting structure fields in that aspect are printed in bold.

Listing 1: The DRIVER_OBJECT structure

typedef struct _DRIVER_OBJECT {
    CSHORT Type;
    CSHORT Size;
    PDEVICE_OBJECT DeviceObject;
    ULONG Flags;
    PVOID DriverStart;
    ULONG DriverSize;
    PVOID DriverSection;
    PDRIVER_EXTENSION DriverExtension;
    UNICODE_STRING DriverName;
    PUNICODE_STRING HardwareDatabase;
    PFAST_IO_DISPATCH FastIoDispatch;
    PDRIVER_INITIALIZE DriverInit;
    PDRIVER_STARTIO DriverStartIo;
    PDRIVER_UNLOAD DriverUnload;
    PDRIVER_DISPATCH MajorFunction[0x1C];
} DRIVER_OBJECT;

As said in Drivers and Devices chapter, each device is associated with one driver. This fact can also be viewed from the driver perspective: each driver own none, one or multiple devices. The devices owned by one driver are stored in a singly linked list and address of its first item (first device) is stored inside the DeviceObject member of the driver object body. Each device, described by the DEVICE_OBJECT, contains a pointer to the next device in the list (the NextDevice filed of the device object body).

DriverStart and DriverSize members describe position of the driver image in memory: its base address and size in bytes.

DriverEntry and IRP handling

Address of the DriverEntry routine is stored within the DriverInit field. The routine is invoked just after the driver image is mapped to kernel memory and it is responsible for the whole driver initialization. If a driver can be dynamically unloaded, it sets the DriverUnload field to point to its unload routine, which is invoked just before unmapping the driver image from memory. If the driver does not set its unload routine (DriverUnload is NULL) the driver cannot be unloaded from the system without a reboot.

The MajorFunction array defines all callback routines responsible for handling IRPs. When sending a request to driver's device, the kernel selects appropriate callback routine from the array by using the major type of the request as an index. All the callbacks stored in the MajorFunction array has the same signature named DRIVER_DISPATCH showed in Listing 2. The callbacks accept two arguments, the device object to which the request just arrived, and the request itself. They return a NTSTATUS value indicating whether the request was completed and how successfully the driver handled it.

Listing 2: Signature of the callback routines responsible for handling IRPs

typedef NTSTATUS (NTAPI DRIVER_DISPATCH)(
    PDEVICE_OBJECT DeviceObject,
    PIRP Irp);`

StartIo and fast I/O

Driver's StartIo can be used to serialize request targeted at certain device or driver. Its signature reflects the DRIVER_DISPATCH type, however, there are some differences from callbacks stored inside the MajorFunction array:

  • there may be at most one StartIo routine defined per driver, thus serving all types of requests,
  • the routine handles only requests passed to it by the owning driver,
  • the routine does not support device stacks or any other type of layering
  • the routine is optional, the driver can decide not to define any. When a driver decides not to define one of its IRP callbacks, the callback points to a default handler inside the kernel, called IopInvalidDeviceRequest.

If a driver decides to support also fast I/O method of communication (which is completely optional unless the driver is a file system or file system filter), the DriverEntry routine allocates a FAST_IO_DISPATCH structure and stores its address in the FastIoDispatch field of its driver object structure. The FAST_IO_DISPATCH structure consists of various callbacks with various signatures. The callbacks are invoked at various times and places. If the driver does not support fast I/O, it just leaves the FastIoDispatch field as is.

AddDevice

When a driver supports Plug&Play (PnP), it needs to be informed when a device of interesting type is connected to the computer (either physically or virtually). This purpose is served by the AddDevice routine which is stored in an extension of the driver object (address of the extension is stored in the DriverExtension field). Definition of the driver extension structure and signature of the AddDevice routine are described in Listing 3.

Listing 3: Structure of the driver extension and signature of the AddDevice routine

typedef NTSTATUS (DRIVER_ADD_DEVICE) (
    struct _DRIVER_OBJECT *DriverObject,
    struct _DEVICE_OBJECT *PhysicalDeviceObject);

typedef struct _DRIVER_EXTENSION {
    struct _DRIVER_OBJECT *DriverObject;
    PDRIVER_ADD_DEVICE AddDevice;
    ULONG Count;
    UNICODE_STRING ServiceKeyName;
} DRIVER_EXTENSION, *PDRIVER_EXTENSION;

When the kernel considers a driver is worth of being informed about arrival of a new device, it invokes its AddDevice routine. If the driver is not loaded in memory, the kernel does so and does not forget to initialize the driver via its DriverEntry. The kernel passes the following parameters to the AddDevice routine:

  • DriverObject. Address of the driver object representing the driver.
  • PhysicalDeviceObject. Address of the bottom device object in the device stack representing the newly arrived device.

If driver's AddDevice routine considers the newly arrived device interesting enough, it usually creates a new device object and attaches it to the top of the stack, making the driver able of monitoring and filtering all IRPs going to the new device.

A driver may define its AddDevice routine during its DriverEntry by setting the AddDevice member of its driver extension structure. Drivers that have AddDevice routine are sometimes called Plug&Play drivers (PnP drivers).

General

For Users-Developers

Tutorial

Public API

Functions

Types

Clone this wiki locally