Archive for June, 2008

Creating and Using IOCTLs

Saturday, June 7th, 2008

Last week I had received the following question from a reader:

“Is it possible for my application to send a customized IOCTL (made by myself) to a driver, and that driver being able to recognize it with no problems?”

The short-term answer for this question is: “Yes, and good luck!”. However, what is the  fun a blog has  if we can not talk more about this subject and even give a simple example? You’re assumed by this you’re already aware of some basic concepts, such as compiling, installing and testing drivers; but, if you have not known this yet, do not worry, just read this post.

The driver that used to calculate

Let’s create a sample driver that uses the same idea my friend Heldai had already used to illustrate IOCTLs use when I was still learning how to make blue screens. At today’s example, we are making a driver that adds two numbers contained into a structure that will be received via an IOCTL.

With no more yada yada, I think we can start by defining the structure. Like the kindergarten teacher had taught us, we are just creating a header file that is included by both, the application project and the driver project. This header file should not include any other specific one from User Mode nor Kernel Mode. That means, it cannot include files like Windows.h nor Ntddk.h. All this is aleady done, tested and able to be built in a project available for downloading at the end of the post.

typedef struct _KERNEL_MATH_REQUEST
{
    //-f--> I can define this structure in any way I
    //      want to.
    //      These are the two numbers to be added to it.
    ULONG   x;
    ULONG   y;
 
} KERNEL_MATH_REQUEST, *PKERNEL_MATH_REQUEST;
 
 
typedef struct _KERNEL_MATH_RESPONSE
{
    //-f--> To the newbies: I know it's possible to be done it using
    //      just one structure. But I'm only doing in that
    //      way for a better illustration.
    ULONG   r;
 
} KERNEL_MATH_RESPONSE, *PKERNEL_MATH_RESPONSE;

Creating the IOCTL

IOCTL is more than just a number to identify the desired operation. It consists of a bit mask interpreted by Windows. This mask is defined as it is shown below. You can get more details at this macro on this link.

To define an IOCTL, we use the CTL_CODE macro which has its parameters shown below:

#define IOCTL_Device_Function CTL_CODE(DeviceType, Function, Method, Access)

Let’s define our IOCTL, as it is shown below:

//-f--> Here, we define IOCTL for our driver.
#define IOCTL_ADD_THESE_TWO_NUMBERS \
    CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
 
//-f--> Although this driver haa the word "Sum" as part of its name,
//      I've decided to put an IOCTL for subtraction, just as an example.
#define IOCTL_SUBTRACT_THESE_TWO_NUMBERS \
    CTL_CODE(FILE_DEVICE_UNKNOWN, 0x801, METHOD_BUFFERED, FILE_ANY_ACCESS)

Now, let’s take a look at every used parameter and talk a litle about it.

FILE_DEVICE_UNKNOWN – If you take a look at the DriverEntry routine, you can notice this is the same device type used at the call for the IoCreateDevice() routine.

/****
***     DriverEntry
**
**      That's the driver entry point.
**      "A knife among the teeth and some blood in the eyes".
*/
extern "C" NTSTATUS
DriverEntry(__in PDRIVER_OBJECT     pDriverObj,
            __in PUNICODE_STRING    pusRegistryPath)
{
    UNICODE_STRING  usDeviceName = RTL_CONSTANT_STRING(L"\\Device\\KernelSum");
    UNICODE_STRING  usSymbolicLink = RTL_CONSTANT_STRING(L"\\DosDevices\\KernelSum");
    NTSTATUS        nts;
    PDEVICE_OBJECT  pDeviceObj;
 
    //-f--> Setting the unload routine to allow the 
    //      driver to be dynamincaly unloaded.
    pDriverObj->DriverUnload = OnDriverUnload;
 
    //-f--> The Open, Cleanup e Close routines behave in the same way.
    //      So, through the minimum effort law use, they will be the same one.
    //      Besides this one, we must handle the DeviceControl which is
    //      responsible for dealing with the received IOCTLs.
    pDriverObj->MajorFunction[IRP_MJ_CREATE] =
    pDriverObj->MajorFunction[IRP_MJ_CLEANUP] =
    pDriverObj->MajorFunction[IRP_MJ_CLOSE] = OnCreateCleanupClose;
    pDriverObj->MajorFunction[IRP_MJ_DEVICE_CONTROL] = OnDeviceIoControl;
 
    //-f--> Here we are creating the driver control device. Notice that we used
    //      FILE_DEVICE_UNKNOWN as the device type. This same one is used
    //      at the CTL_CODE macro. See: IoCtl.h
    nts = IoCreateDevice(pDriverObj,
                         0,
                         &usDeviceName,
                         FILE_DEVICE_UNKNOWN,
                         0,
                         FALSE,
                         &pDeviceObj);
 
    //-f--> Is everything fine so far? OK...
    if (!NT_SUCCESS(nts))
    {
        ASSERT(FALSE);
        return nts;
    }
 
    //-f--> Here we are creating a symbolic link so that, the application
    //      can get a handle for the control device.
    nts = IoCreateSymbolicLink(&usSymbolicLink,
                               &usDeviceName);
 
    //-f--> Is it all right?
    if (!NT_SUCCESS(nts))
    {
        //-f--> Oops... I think my pant is sort of wet...
        IoDeleteDevice(pDeviceObj);
 
        ASSERT(FALSE);
        return nts;
    }
 
    //-f--> Great, that is it!
    return STATUS_SUCCESS;
}

The secong CTL_CODE parameter Function, receives a 0x800 number, since numbers below this one are reserved for Microsoft.

METHOD_BUFFERED – indicates to the I/O Manager the driver will receive an input buffer copy passed to DeviceIoControl function. I/O Manager allocates a system buffer, which means in kernel space, that it is big enough to fit both the input and output data. When the DeviceIoControl function is called, the I/O Manager allocates the system buffer and copies the input buffer inside it. The driver receives the IRP, gets the input parameters, processes them and writes the output data at the same system buffer. When the IRP is completed, the I/O Manager copies the output data from the system buffer to the output buffer provided by the application.

Besides the Buffered method, there are still the Direct I/O and Neither methods. For our currently example, the Buffered method is great. I am owing a post to you that talks about how to use every one of the other methods.

FILE_ANY_ACCESS – That indicates the handle doesn’t need to be opened with any special kind of access to allow the IOCTL to be excetuted.

From the application to the driver

To send an IOCTL to the driver, you need to use the DeviceIoControl function as it is shown at the example below. This is a very simple program that shows its use.

/****
***     main
**
**      I hope all of you have already known that this is the entry
**      point of the application. Otherwise you might be
**      precipitated on reading a Windows driver blog.
**
*/
int __cdecl main(int argc, CHAR* argv[])
{
    HANDLE                  hDevice;
    DWORD                   dwError = ERROR_SUCCESS,
                            dwBytes;
    KERNEL_MATH_REQUEST     Request;
    KERNEL_MATH_RESPONSE    Response;
 
    printf("Opening \\\\.\\KernelSum device...\n");
 
    //-f--> Here, we are opening a handle for our device, which
    //      has been created by the driver. Remember that our
    //      sample driver must be installed and started
    //      so that the call below can work properly.
    hDevice = CreateFile("\\\\.\\KernelSum",
                         GENERIC_ALL,
                         0,
                         NULL,
                         OPEN_EXISTING,
                         0,
                         NULL);
 
    //-f--> It Verifies whether the open was opened.
    if (hDevice == INVALID_HANDLE_VALUE)
    {
        printf("Error #%d opening device...\n",
               (dwError = GetLastError()));
        return dwError;
    }
 
    //-f--> I got lazy and I put the values fixed at the sample code.
    Request.x = 3;
    Request.y = 2;
 
    printf("Calling DeviceIoControl...\n");
 
    //-f--> It sends the IOCTL
    if (!DeviceIoControl(hDevice,
                         IOCTL_SOMA_QUE_EU_TO_MANDANDO,
                         &Request,
                         sizeof(KERNEL_MATH_REQUEST),
                         &Response,
                         sizeof(KERNEL_MATH_RESPONSE),
                         &dwBytes,
                         NULL))
    {
        //-f--> Oops...
        printf("Error #%d calling DeviceIoControl...\n",
               (dwError = GetLastError()));
 
        CloseHandle(hDevice);
        return dwError;
    }
 
    //-f--> Show the results.
    printf("%d + %d = %d\n",
           Request.x,
           Request.y,
           Response.r);
 
    printf("Closing device...\n");
    //-f--> Close the handle.
    CloseHandle(hDevice);
    return 0;
}

Note that there is no strong relation between the IOCTL and the used input or output buffers, but we must be attentive to the buffer sizes when the driver makes use of them. Nobody wants to corrupt the kernel heap allocations or throwing off exceptions in kernel mode, do they?

You can see how data are processed by the driver when receiving the IRP_MJ_DEVICE_CONTROL. Reading the comments is a good idea; they are a part of the explanation. Wow, that sentence representing my laziness got nice!! Basically, it is the same to say: “Oh my! Seriously, besides preparing this example, you still want me to duplicate all the comment information? I don’t think so!”

/****
***     OnDeviceIoControl
**
**      This routine is called when an application
**      sends an IOCTL to our driver via DeviceIoControl.
*/
NTSTATUS
OnDeviceIoControl(__in PDEVICE_OBJECT   pDeviceObj,
                  __in PIRP             pIrp)
{
    PIO_STACK_LOCATION      pStack;
    NTSTATUS                nts;
    PKERNEL_MATH_REQUEST    pRequest;
    PKERNEL_MATH_RESPONSE   pResponse;
 
    //-f--> We get the parameters referring to our driver.
    pStack = IoGetCurrentIrpStackLocation(pIrp);
 
    switch(pStack->Parameters.DeviceIoControl.IoControlCode)
    {
    case IOCTL_SUM_THIS_TWO_NUMBERS:
    case IOCTL_SUBTRACT_THIS_TWO_NUMBERS:
 
        //-f--> Let's do the checking about the input
        //      and output buffer sizes.
        if (pStack->Parameters.DeviceIoControl.InputBufferLength !=
            sizeof(KERNEL_MATH_REQUEST) ||
            pStack->Parameters.DeviceIoControl.OutputBufferLength !=
            sizeof(KERNEL_MATH_RESPONSE))
        {
            nts = STATUS_INVALID_BUFFER_SIZE;
            pIrp->IoStatus.Information = 0;
            break;
        }
 
        //-f--> The safe then sorry.
        ASSERT(pIrp->AssociatedIrp.SystemBuffer != NULL);
 
        //-f--> Making use of METHOD_BUFFERED, the system allocates only one
        //      buffer to transport either the input and output data. The buffer
        //      size is the same as the biggest one of them. Thus, be careful having read
        //      all the input data before starting to write the output to the buffer.
        pRequest = (PKERNEL_MATH_REQUEST)pIrp->AssociatedIrp.SystemBuffer;
        pResponse = (PKERNEL_MATH_RESPONSE)pIrp->AssociatedIrp.SystemBuffer;
 
        //-f--> It does the math and indicates its success.
        if (pStack->Parameters.DeviceIoControl.IoControlCode == IOCTL_SOMA_QUE_EU_TO_MANDANDO)
            pResponse->r = pRequest->x + pRequest->y;
        else
            pResponse->r = pRequest->x - pRequest->y;
 
        nts = STATUS_SUCCESS;
 
        //-f--> It informs the I/O Manager how many bytes have to be transferred 
        //      back to the application.
        pIrp->IoStatus.Information = sizeof(KERNEL_MATH_RESPONSE);
        break;
 
    default:
        //-f--> Oops... We have received an unknown IOCTL.
        nts = STATUS_INVALID_DEVICE_REQUEST;
        pIrp->IoStatus.Information = 0;
    }
 
    //-f--> It copies the final IRP status and completes it.
    pIrp->IoStatus.Status = nts;
    IoCompleteRequest(pIrp, IO_NO_INCREMENT);
 
    //-f--> NOTE: Remember that we cannot touch the IRP after completing it;
    //      so, don't be the "smart ass" changing the line below to return the
    //      IRP status taking the value from the IRP. (return pIrp->IoStatus.Status;)
    return nts;
}

Building in this or in that way

The sample project available for downloading at the end of this post can be compiled in two ways. One way is using the standard way to compile drivers offered by WDK. Briefly you go through “Start->All Programs->Windows Driver Kits->WDK 6000->Build Environments->Windows XP->Windows XP x86 Checked Build Environment”. This should open a console prompt, as it is shown below. Then, just go to the directory where you download these Internet sources and to the project root directory. There you need to call Build.exe and it is done.

The other way to compile the entire project allows you to build it using Visual Studio 2008, which has been the environment I have used to compose this project. However, to build the project by using the IDE you have to use DDKBUILD, as it is shown in this post referring to  it.

While I was out, I had received another reader’s question who wanted to know how to read or write into the registry using a driver. In the next post, I’ll talk about that and other details regarding to the registry from a driver’s point of view.

CYA!

KernelSum.zip

Getting up-to-date

Thursday, June 5th, 2008

Once more I have not posted any for a long time. Now you might be thinking: “Here he comes with that chatter he has no time, everything is difficult and God does not like the kernel developers and so on.” Well, I am skipping this part of apologies and just saying what I have been doing during that time.

Windows Driver Training

Well, let me see where I have stopped. My last post was during the period when I was teaching a driver development course for a private class. The course was given in five Saturdays of eight hours each. Needless to say, it took me a while. Okay, okay, okay, no excuses.

Forth C/C++ Programmers’ Meeting

On next Saturday following the end of the course, I took a tour around this event. They left the door open and I could get through for a peek and talk a little about Windows drivers’ architecture and development using C language. I confess that the invitation to participate in this event was a surprise to me. I’m not a great C++ programmer but I use the C language with “classes”, as just my friend Strauss gets used to say. I can say it was very interesting to participate in this event with a very high technical level.  I had an opportunity to realize the way the language may be used in different scenarios. The slides from my talk are available at this link.

Going back to Boston

It seems that things only happen on Saturdays. Anyway, on the next Saturday following the meeting, I embarked to Boston on a trip taking about one month. The last time I had been there was due to the OSR training described in this post. This time, it was about an IBM integration program. I could personally meet the people I work with and previously had only met through Web Conferences. Again, it was very interesting and I tried to enjoy it mostly. We were two Brazilians and one Indian. I missed our Brazilian coffee a lot. In this photo, there are below, from left to right, David E. Jones (Manager), Scott D. Hankin (Documentation), William C. Oliveira (Linux), William H. Taber (Linux), Mridula Muppana (Testing), Paul Ganshirt (Windows), Niraj Kumar (AIX), Kaveesh Mishra (Windows), Fernando Roberto (Windows) and Richard Kissel (AIX and Linux). This is just one part of the whole MVFS team.

Don’t you know what MVFS is? It is a File System that is part of a product called ClearCase. Don’t you know ClearCase? Well, beyond Wikipedia, I had the pleasure of bumping into an observation about the ClearCase at Rajeev Nagar’s book. This book, as I have never gotten tired of saying, is still the only respectable reference on Windows File System development, even though it was first published in 1997. Where is ClearCase in this story? Well, if you’re sick like me and have this book, take a peek at the beginning of chapter nine. On the first page of this chapter, you’re going to find the following passages.

Yeah, that’s really cool! 🙂

Back to the Classes

For a whole month after I had come back to Brazil, I spent some time chasing the content I had lost at the university. Yeah, I’m still graduating in Computer Engineering. I have been working on my final project (Final Course Project). My project must obligatorily have a Windows driver; that is the least thing I could do. Finally, I will be able to show what I can do to my classmates. During the course, some friends had asked me what I’ve worked with, since I have often been reading big books from Microsoft. After trying to explain the simplest way possible, drawing or even using puppets, they still get with that interrogation face. Well, at the time that the blue screen shows up, they will eventually understand.

I took the tour in the United States to buy this development kit of Altera. This kit will be part of my project and in the future, I will be able to use it in my courses on driver development. Nowadays I have just counted on the OSR training boards, as I already talked about in this post. Unlike the OSR boards, this kit may have its hardware defined by a language called VHDL, and thus, it can cause the board to have a wide variety of behaviors and be able to illustrate the driver interface construction for every situation. This card costs around R$ 1,700.00 in Brazil, while I had only paid U$ 150.00 there in USA. Practically, a candy price. Do you know, when we used to go to the bakery to buy that ice cream cone that came with a stuck toy? So, it was almost the same thing. Another interesting thing in this kit, besides its price, is the possibility of using its USB and PCI interfaces. That will be very funny.

Portability and Performance Seminar

In this last weekend, I also attended that event organized by Brazil C/C++ group. It’s amazing to notice how these events are bringing more and more people to it. I took some pictures, but I’ll leave the comments on behalf of my friend Slug, who has a cool post about it.

Back to the bloggers’ world, I will try not to abandon you, guys for so long. Some excuses apart, it has not been that easy.

CYA.