Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Baby Steps in Windows Device Driver Development: Part 3, Receiving IOCTLs

DZone's Guide to

Baby Steps in Windows Device Driver Development: Part 3, Receiving IOCTLs

·
Free Resource

Now that we can compile, deploy, install and start a driver, it’s time for something more interesting. In this post, we’ll send controls to our driver from a user-mode application.

To receive information from the outside, we need to teach our driver to respond to device I/O control codes (IOCTLs) which can be delivered to it from user mode using the DeviceIoControl Win32 API. We have already seen how our driver can tell Windows what the unload routine is using the PDRIVER_OBJECT structure. Handling IOCTLs is very similar, we just need to provide another routine or two.

Before our driver can receive IOCTLs, it needs to create a device object that can be opened from a user-mode application. An application can open a device object using the CreateFile Win32 API and then issue IOCTLs or read/write requests on the device object handle.

Add the following to your DriverEntry to create a device object and register the DriverDispatch routine to handle IOCTLs directed at your device:

#define FILE_DEVICE_HELLOWORLD 0x00008337

NTSTATUS DriverEntry(
    PDRIVER_OBJECT DriverObject,
    PUNICODE_STRING RegistryPath)
{
    NTSTATUS status;
    WCHAR deviceNameBuffer[]  = L"\\Device\\HelloWorld";
    UNICODE_STRING deviceNameUnicodeString;
    WCHAR deviceLinkBuffer[] = L"\\DosDevices\\HelloWorld";
    UNICODE_STRING deviceLinkUnicodeString; 
    PDEVICE_OBJECT interfaceDevice = NULL;

    DbgPrint("DriverEntry called\n");
    RtlInitUnicodeString (&deviceNameUnicodeString,
                          deviceNameBuffer);
    status = IoCreateDevice (DriverObject,
                             0,
                             &deviceNameUnicodeString,
                             FILE_DEVICE_HELLOWORLD,
                             0,
                             TRUE,
                             &interfaceDevice );
    if (NT_SUCCESS(status))
    {
       RtlInitUnicodeString (&deviceLinkUnicodeString,
                             deviceLinkBuffer);
       status = IoCreateSymbolicLink(
                   &deviceLinkUnicodeString,
                   &deviceNameUnicodeString );

       DriverObject->MajorFunction[IRP_MJ_CREATE] =
       DriverObject->MajorFunction[IRP_MJ_CLOSE]  =
       DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] =
                                   DriverDispatch;
       DriverObject->DriverUnload = DriverUnload;
    }
    return status;
}

What does DriverDispatch have to do? Not much, and indeed for now we are going to handle only a single IOCTL that will trigger a “Hello World” message:

#define IOCTL_SAYHELLO (ULONG) CTL_CODE( FILE_DEVICE_HELLOWORLD, 0x00, METHOD_BUFFERED, FILE_ANY_ACCESS )

NTSTATUS DriverDispatch(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp)
{
    PIO_STACK_LOCATION iosp;
    ULONG  ioControlCode;
    NTSTATUS status;
    DbgPrint("DriverDispatch called\n");

    iosp = IoGetCurrentIrpStackLocation (Irp);
    switch (iosp->MajorFunction) { 
    case IRP_MJ_CREATE:
        status = STATUS_SUCCESS;
        break; 
    case IRP_MJ_CLOSE:
        status = STATUS_SUCCESS;
        break; 
    case IRP_MJ_DEVICE_CONTROL:
        ioControlCode =
            iosp->Parameters.DeviceIoControl.IoControlCode;
        if (ioControlCode == IOCTL_SAYHELLO) {
            DbgPrint("Hello World!\n");
        }
        status = STATUS_SUCCESS;
        break; 
    default:
        status = STATUS_INVALID_DEVICE_REQUEST;
        break;       
    } 
    Irp->IoStatus.Status = status;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    return status;
}

All that’s left is to use the driver from a user-mode application by opening a handle to its device and issuing an IOCTL for it to print a message. In our particular case, something like the following will suffice:

HANDLE hDevice;
DWORD nb;
hDevice = CreateFile(
             TEXT("\\\\.\\HelloWorld"),
             GENERIC_READ | GENERIC_WRITE, 0, NULL,
             OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
DeviceIoControl(
             hDevice, IOCTL_SAYHELLO,
             NULL, 0, NULL, 0, &nb, NULL);
CloseHandle(hDevice);

You are ready to test your driver and your user-mode application. Compile the driver, deploy it to the target machine, register and start it using the OSR Driver Loader (go back for instructions). When the driver is running, run your user-mode application to issue the IOCTL and watch DebugView for debug printouts.

Next, we’ll see how to use WinDbg as a real kernel debugger to set breakpoints and step through your driver’s code.

Topics:

Published at DZone with permission of Sasha Goldshtein, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}