Creating and Using IOCTLs

June 7th, 2008 - Fernando Roberto

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

June 5th, 2008 - Fernando Roberto

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.

Step into Kernel (Vista + USB2)

March 6th, 2008 - Fernando Roberto

I have already mentioned into another post that it is possible to perform Kernel Debugging via a 2.0 USB port. In this post I’m going to demonstrate such a 2.0 USB connection working. Yeah, that’s a good thing. So let’s stop this idle talk and go to the business. If you know nothing about Kernel Debugging, you can read this post which brings the concepts and basic steps on the subject or you can ask yourself “How have I ever gotten into this site?” and return to ORKUT.

The USB bus does not allow us to have direct connections between two computers. In order to use a USB port for this purpose, we must rely on the additional hardware helping. Although it is named as Debug Cable, this is a small device that connects computers via USB ports. I do not know if there are already other manufacturers for the Debug Cable, but the one I’m using at this experiment is NET20DC.

Using the Debug Cable

Using the Debug Cable is a luxury that only Windows Vista and posterior systems can enjoy. It needs to be plugged into a 2.0 USB port without going through any hub at the TARGET side. But how am I supposed to know for sure which of my ports is actually 2.0?

An easy way to know that, especially for those ones who have installed WDK, is to build the USBView sample which is in the “C:\WinDDK\6000\src\usb\USBView” folder of WDK. This program lists the drivers and their respective connected USB devices. Thus, when connecting the Debug Cable to the TARGET machine, USBView should inform us what port this new device is connected to. Note that in this window we can see which controller the port we are using belongs to.

Windows will prompt a driver when detecting this new USB device presence. No driver must be installed to use the Debug Cable on the TARGET side. So, you can select “Do not show this message again for this device” in the window shown below. Not installing any driver to the TARGET side makes sense. Remember that this interface will be used by BIOS of the TARGET machine and its control wont be passed to the operating system core. Do not forget, yet, there is another requirement to use the Debug Cable. The TARGET machine’s BIOS must implement this debug interface control. The most unfortunate part of this story is about checking whether your BIOS offers such support. All that you need to do is just to try. That means you may have the Debug Cable, a free 2.0 USB port and the cables but even that, there is a risk of not connecting it because TARGET BIOS might not support the debug interface. I’m glad to know that my laptop offers this support and have not spent this money for anything importing the Debug Cable. Phew!

Modifying Boot.ini

Sorry? Boot.ini? I’m sorry to tell you that the Boot.ini has gone way. Windows Vista uses a new architecture called Boot Configuration Data (BCD). That has been the end for Boot.ini file. To edit the BCD settings, we use the BCDEdit.exe tool, a console application that needs to be run with administrative rights. Executing this tool without any parameters returns it to the following output.

To configure the system in order to have two boot options, the one with enabled debugging and the  other isn’t, follow the described steps below. The Boot Manager managers the Boot Loaders which are either possible to be the other operating systems, even prior to Windows Vista or configuration sets of the same system. Each of these items is identified by a GUID. Note that the first command makes a copy of the current configuration to the  one contains the “Windows Vista Guinea Pig Mode” string as a description. In response to this copy, the tool returns a resulting GUID of the copy. Also, observe that the second command enables the kernel debugging to the setting identified by the GUID just received. Right now, if we take a look at the settings, it will have a result like the same image being displayed below.

Nice!!! Now we have to configure the media to be used by the Kernel Debugger. I believe that most systems still use serial ports for this purpose. In this case, the most common configuration is to use the COM1 serial port with a 115200 baud rate. To configure these settings, use the following command line. To see the current settings, just run the second command line as it is shown below.

If you do not know how to use the serial port as a communication way for the kernel debug, this is the post that talks about it. However, in this post I am commenting about kernel debug using a 2.0 USB as a communication way. In this case, the settings are described at the command line that is shown below. The TARGETNAME is able to receive any name. I have used the “WINDBG” name for obvious reasons, but you can use anyone.

As you may have guessed, the settings on the debug mode of communication is global, that is not linked to one or another boot configuration identified by a GUID.

I believe that on the TARGET side everything is ready to make the link. Thus, when you reboot the victim machine, the following menu will be displayed by the Boot Manager:

Configuring the HOST

At the HOST side of this game, we must add the driver that controls the Debug Cable to it and allow Windbg to use this device. Although I’m using Windows Vista on the HOST side, nothing would prevent me from using Windows 2000 or higher on this side of the conversation. The fun starts when you plug the device and click “Locate and install driver (recommended)” into the “New Hardware Found” window that has already appeared into this post. At this time, Windows searches for the driver at Windows Update database.

The thing starts to get funny when Windows “asks” me to insert the disk that coming with the Debug Cable. Only two words had been on my mind at that moment, “What disk?”. Without some other options, I clicked on “I do not have that damn disk! Are you crazy? Show me anything, for the God sake.” Some of you might even have this sequence of windows decorated by just not finding drivers for all devices you have had.

Finally, when the last window has been shown to me, a light came to me, just like one of those brilliant ideas that I have had every leap year. That’s when I thought to myself “Now I know! I am going to visit the manufacturer’s website and look for a support session.”

And at the manufacturer’s web site…

And at the Microsoft’s web site…

In the end, I sent this e-mail to get any sign that I had not thrown my money into the trash can. Less than an hour after, I got the answer with a list of requirements and steps to be followed; but the most important thing had just been revealed to me at the end.

I knew I could count on Microsoft intelligence and agility to resolve this but, for those who are reading this post, my tip is: When Windows prompts for new device driver, point out the “C:\Program Files\Debugging Tools for Windows\usb” folder.

So, I’m glag to show you this next window into the figure below:

Configuring WinDbg

Now it’s children’s play. Select the “Kernel Debug …” item from the “File” menu. Click on the “USB 2.0” tab and type the same TARGETNAME you have chosen at TARGET configuration with BCDEdit.exe. In my case, the name is WINDBG as it is displayed at the figure.

Clicking OK, the WinDbg command window should display the text highlighted in red below and wait for the connect completion. The output below was obtained with a CTRL+Break after having the debugger connected.






Microsoft (R) Windows Debugger Version 6.8.0004.0 AMD64
Copyright (c) Microsoft Corporation. All rights reserved.
 
Using USB2 for debugging
Waiting to reconnect...
USB2: Write opened
Connected to Windows Vista 6000 x86 compatible target, ptr64 FALSE
Kernel Debugger connection established.
Symbol search path is: SRV*c:\websymbols*http://msdl.microsoft.com/download/symbols
Executable search path is: 
Windows Vista Kernel Version 6000 MP (1 procs) Free x86 compatible
Built by: 6000.16584.x86fre.vista_gdr.071023-1545
Kernel base = 0x82000000 PsLoadedModuleList = 0x82111e10
System Uptime: not available
Break instruction exception - code 80000003 (first chance)
*******************************************************************************
*                                                                             *
*   You are seeing this message because you pressed either                    *
*       CTRL+C (if you run kd.exe) or,                                        *
*       CTRL+BREAK (if you run WinDBG),                                       *
*   on your debugger machine's keyboard.                                      *
*                                                                             *
*                   THIS IS NOT A BUG OR A SYSTEM CRASH                       *
*                                                                             *
* If you did not intend to break into the debugger, press the "g" key, then   *
* press the "Enter" key now.  This message might immediately reappear.  If it *
* does, press "g" and "Enter" again.                                          *
*                                                                             *
*******************************************************************************
nt!RtlpBreakWithStatusInstruction:
820818d0 cc              int     3
0:kd>

Now the easy part has been finished. The next steps are referring to investigate the issue, however this will be described in a future post.

I hope having  helped.
CYA 🙂

DriverEntry.com.br shows up

March 4th, 2008 - Fernando Roberto

Hi there, I promise to you this post will be pretty short. That news is for the ones who love playing with bits and bytes.

The Forth Programmer’s Meeting, that will be organized by the C/C++ Brazil group, will take place next March 29th.

The event will be counting on a series of lectures with real authorities on the subject. I guess the only distinguished unknown person will be me. I have been invited to talk about driver architecture and development for Windows.

If you have no idea how the drivers are organized and how you can build them, then this is your chance to continue not knowing it, but with a good opportunity to see other excellent talks. I have already given some training, but I cannot say I have had great experiences with lectures. I think the biggest lecture I have ever given was composed by an audience of about three people. That had included my mother, who was telling me all the time to shut up so that she could watch the soap opera.

Anyway, I’ll try to do my best there. Fortunately, my friend Strauss, who incidentally is one of the speakers, has given me a help publishing this post with tips for speakers.

I’ll see you there!

KeWaitForMultipleObjects, pero no mucho!

February 27th, 2008 - Fernando Roberto

A while ago, my friend Slug published a post about his adventures in Kernel looking for the reason for a blue screen. The problem was caused by an incorrect call to the KeWaitForMultipleObjects() function in an attempt to wait for 4 objects. Yeah Wanderley, that’s a result of inheriting a code from everyone. Just because the function has the word “multiple” in its name, it  does not mean we can wait for that absurd amount of four objects. In this post I am giving you a simple example of how to use this feature.

Where does the limit live?

The KeWaitForMultipleObjects() function uses an array of elements of KWAIT_BLOCK type which contains the data referring to the multiple waiting objects being performed. In many cases, this amount of multiple waiting objects is no more than three. In order to avoid ever having to build an array of KWAIT_BLOCK structures to perform a multiple waiting, the structure that represents a thread in kernel mode, the KTHREAD, contains an array of four elements of this structure embedded in it, as it is shown below.

kd> dt nt!_KTHREAD
   +0x000 Header           : _DISPATCHER_HEADER
   +0x010 MutantListHead   : _LIST_ENTRY
   +0x018 InitialStack     : Ptr32 Void
   +0x01c StackLimit       : Ptr32 Void
   +0x020 Teb              : Ptr32 Void
   +0x024 TlsArray         : Ptr32 Void
   +0x028 KernelStack      : Ptr32 Void
   +0x02c DebugActive      : UChar
   +0x02d State            : UChar
   +0x02e Alerted          : [2] UChar
   +0x030 Iopl             : UChar
   +0x031 NpxState         : UChar
   +0x032 Saturation       : Char
   +0x033 Priority         : Char
   +0x034 ApcState         : _KAPC_STATE
   +0x04c ContextSwitches  : Uint4B
   +0x050 IdleSwapBlock    : UChar
   +0x051 Spare0           : [3] UChar
   +0x054 WaitStatus       : Int4B
   +0x058 WaitIrql         : UChar
   +0x059 WaitMode         : Char
   +0x05a WaitNext         : UChar
   +0x05b WaitReason       : UChar
   +0x05c WaitBlockList    : Ptr32 _KWAIT_BLOCK
   +0x060 WaitListEntry    : _LIST_ENTRY
   +0x060 SwapListEntry    : _SINGLE_LIST_ENTRY
   +0x068 WaitTime         : Uint4B
   +0x06c BasePriority     : Char
   +0x06d DecrementCount   : UChar
   +0x06e PriorityDecrement : Char
   +0x06f Quantum          : Char
   +0x070 WaitBlock        : [4] _KWAIT_BLOCK
   +0x0d0 LegoData         : Ptr32 Void
   +0x0d4 KernelApcDisable : Uint4B
   +0x0d8 UserAffinity     : Uint4B
   +0x0dc SystemAffinityActive : UChar
   +0x0dd PowerState       : UChar
   +0x0de NpxIrql          : UChar
   +0x0df InitialNode      : UChar
   +0x0e0 ServiceTable     : Ptr32 Void
   +0x0e4 Queue            : Ptr32 _KQUEUE
   +0x0e8 ApcQueueLock     : Uint4B
   +0x0f0 Timer            : _KTIMER
   +0x118 QueueListEntry   : _LIST_ENTRY
   +0x120 SoftAffinity     : Uint4B
   +0x124 Affinity         : Uint4B
   +0x128 Preempted        : UChar
   +0x129 ProcessReadyQueue : UChar
   +0x12a KernelStackResident : UChar
   +0x12b NextProcessor    : UChar
   +0x12c CallbackStack    : Ptr32 Void
   +0x130 Win32Thread      : Ptr32 Void
   +0x134 TrapFrame        : Ptr32 _KTRAP_FRAME
   +0x138 ApcStatePointer  : [2] Ptr32 _KAPC_STATE
   +0x140 PreviousMode     : Char
   +0x141 EnableStackSwap  : UChar
   +0x142 LargeStack       : UChar
   +0x143 ResourceIndex    : UChar
   +0x144 KernelTime       : Uint4B
   +0x148 UserTime         : Uint4B
   +0x14c SavedApcState    : _KAPC_STATE
   +0x164 Alertable        : UChar
   +0x165 ApcStateIndex    : UChar
   +0x166 ApcQueueable     : UChar
   +0x167 AutoAlignment    : UChar
   +0x168 StackBase        : Ptr32 Void
   +0x16c SuspendApc       : _KAPC
   +0x19c SuspendSemaphore : _KSEMAPHORE
   +0x1b0 ThreadListEntry  : _LIST_ENTRY
   +0x1b8 FreezeCount      : Char
   +0x1b9 SuspendCount     : Char
   +0x1ba IdealProcessor   : UChar
   +0x1bb DisableBoost     : UChar

From these four elements, three of them are intended to be  used by KeWaitForMultipleObjects(), while the fourth one is reserved to be used by the system to implement the waiting with a timeout.

To wait for up to three objects, the code would be as it is shown below:

    //-f--> PVOID array
    PVOID       pObjects[3];
 
    //-f--> We have to initialize a pointer array for 
    //      the objects which the waiting is based in.
    pObjects[0] = &kEvent1;
    pObjects[1] = &kEvent2;
    pObjects[2] = &kEvent3;
 
    //-f--> That's a simple call for a multiple waiting objects
    //      of 3 ones.
    nts = KeWaitForMultipleObjects(3,
                                   pObjects,
                                   WaitAll,
                                   Executive,
                                   KernelMode,
                                   FALSE,
                                   NULL,
                                   NULL);

Does it mean we cannot wait for more than three objects each time? Well, let me explain it. When it is necessary to wait for more than three objects, we must allocate a large buffer, enough to hold an N-element array of the KWAIT_BLOCK structure, where N is less than or equal to 64. If there is more than that, we are going to have a nice MAXIMUM_WAIT_OBJECTS_EXCEEDED.

“If a buffer is supplied, the Count parameter may not exceed MAXIMUM_WAIT_OBJECTS. If no buffer is supplied, the Count parameter may not exceed THREAD_WAIT_OBJECTS.”

//-f--> From wdm.h
 
#define THREAD_WAIT_OBJECTS 3       // Builtin usable wait blocks
 
#define MAXIMUM_WAIT_OBJECTS 64     // Maximum number of wait objects

In that case, to be able to do a multiple 10-objects waiting, we’d have the code below:

   //-f--> PVOID array
    PVOID           pObjects[10];
 
    //-f--> An additional array of KWAIT_BLOCKs
    PKWAIT_BLOCK    pWaitBlocks = NULL;
 
    //-f--> We have to initialize a pointer array for
    //      the objects which the waiting is based in.
    pObjects[0] = &kEvent1;
    pObjects[1] = &kEvent2;
    pObjects[2] = &kEvent3;
    pObjects[3] = &kEvent4;
    pObjects[4] = &kEvent5;
 
    //-f--> You may not believe it but I do know how to use loops and arrays.
    //      I need to show you that the objects don't necessarily
    //      need to be in an array; however, the pointers related
    //      to them do.
    pObjects[5] = &kEvent6;
    pObjects[6] = &kEvent7;
    pObjects[7] = &kEvent8;
    pObjects[8] = &kEvent9;
    pObjects[9] = &kEvent10;
 
    //-f--> We cannot initialize the KWAIT_BLOCKS array structures  
    //      embedded into the KTHREAD structure; we must allocate our own array.
    pWaitBlocks = (PKWAIT_BLOCK) ExAllocatePool(NonPagedPool,
                                                sizeof(KWAIT_BLOCK) * 10);
 
    //-f--> Doing another test doesn't hurt anybody; however, try not
    //      to test a driver to see how many people die.
    ASSERT(pWaitBlocks != NULL);
 
    //-f--> A simple call to a multiple waiting for
    //      10 objects without a bugcheck.
    nts = KeWaitForMultipleObjects(10,
                                   pObjects,
                                   WaitAll,
                                   Executive,
                                   KernelMode,
                                   FALSE,
                                   NULL,
                                   pWaitBlocks);
 
    //-f--> Having memory leak is not polite.
    ExFreePool(pWaitBlocks);

In summary, that’s all.
Good bye!

OSR Seminars at Home

January 15th, 2008 - Fernando Roberto

If one of the problems that had prevented you from making a specialized training in developing device drivers at the OSR was a fear of flying, so there is no problem anymore. OSR, after years and years of experience teaching how to write drivers, has now invested in Webinars. This news has already been announced since last Jan. 4, but it is still surprising when I comment it with some people. So, I have done this post trying to pass it to more people at once.

 

 

Cheaper?

The financial advantage is in the fact of avoiding buying flying tickets and paying hosting for a week in the United States. Okay, okay, it’s that’s not you who is going to  pay it, but figure out that not everyone works for multi-national companies with thousands of dollars earmarked for employee training. When I had done the training at OSR, everything was paid by the company which I used to work for; however,  if I put everything on the pencil, it was approximately R$ 13,000.oo of investment to attend a seminar of U$ 2,350.00. Taking the current dollar exchange rate, it would be approximately R$ 4,143.00, or roughly, a third of the total. This may simply be the factor that is preventing you from doing the training. So, you can go running to Mommy and Daddy to say them that now they have more purchasing power for your training. But, not three times more. It seems to me Webinars are relatively more expensive than the conventional ones. The seminar I had attended lasted for 32 hours and addressed the File System Driver development (a little advanced subject), while the first webinar offered by OSR will is suppose to do a quick introduction to WDF for 20 hours for the same price.

More Advantages

Another very relevant point is the fact that you don’t need to leave home or the company  that you work for during the seminar. Some people whom I talked to about it told me that a big problem is the fact of leaving home for a week. Some of them have small children or many other reasons you might imagine, but I think the most common reason holding a professional at work is the endless urgent bugs which can only be solved by him. Well, at least this webinar consists of 5 lessons of 4 hours. This allows you to take care of your bug collection while participating into the trainings.

It is not only an expensive PPT

Contrary to what one might figure out, the seminars are going to be interactive using two-way connections where the participant may, in addition to receiving all instructions live, ask questions and even receive all the printed material like in other seminars. The student is going to download the tool that establishes the connection and attend a 15-minute session to test the machine to be used in them and thus, make sure that the participant is having no problem during the training.

And now, if your company really does not want to pay training for you, it is, at least, easier to pay it for yourself; this way you may use this as a new star on your resume.
Goodbye!

CLEANUP and CLOSE

January 9th, 2008 - Fernando Roberto

I have written a post that describes the steps of how to create a simple driver. This driver simply keeps a list of strings it receives via writing IRPs and returns them via reading IRPs. Well, if you do not know what an IRP is, another post tries to explain what they are and even describes the needed steps to use the IRP Tracker to observe the IRPs coming and going. If we do a little test using the IRP Tracker on the sample driver that I have just commented, we will have an output similar to the figure below.


We can see all the IRPs that our device has received from IRP_MJ_CREATE to IRP_MJ_CLOSE. Among these IRPs, we can notice that some of them were not completed successfully. Because no routine has been designed to treat IRP_MJ_CLEANUP, these IRPs are completed with STATUS_INVALID_DEVICE_REQUEST. In this post I’m going to talk a little about this IRP and how your device interacts with the processes which have obtained a handle for it.

To see the IRP details, double-click on the IRP line of the IRP Tracker so that the window below can appear. In this window you can see, among the other details, the FileObject used in the IRP. This is helping us in this post during the tests.

When does an IRP_MJ_CLEANUP take place?

The Object Manager sends an CLEANUP IRP in order to notify the driver that the last handle for a given FileObject has been closed. As some of you may know, when an application uses CreateFile() to get a handle to our device, this results in a FileObject creation. The subsequent operations using this handle will be linked to this FileObject. More details about this are going to be found in this post.

A FileObject does not have a direct relation with a handle. A handle might be duplicated  or even inherited from the parent process on creating a new process. The result of these actions is having multiple handles to be translated into the same FileObject. Thus, not always when an application calls the CloseHandle function, a CLEANUP or CLOSE IRP is sent to the driver.

To follow the steps below, the sample program source code will be available for downloading at the end of this post. This program uses the example driver that have been built in another post; the driver source code can be downloaded from here. From the sources, you can compile and install the test driver. If you do not know how to do this, this post can help you. After installing the test driver, it would be interesting to execute the calls below with the help of a debugger, and thus, be able to observe the results on the IRP Tracker for ever executed line.

It’s important to read the source comments below. I got lazy to duplicate this information here in the post.

/****
***     main
**
**      I hope that everyone know this is the
**      application entrypoint. Otherwise,
**      you might be precipitaded on reading a
**      Windows driver blog.
*/
int main(int argc, char* argv[])
{
    HANDLE  h1, h2, h3;
    CHAR    szBuffer[100];
    DWORD   dwBytes;
 
    //-f--> Here we are opening the first handle
    //      to our device. That is the step 1
    //      on the IRP Tracker. Check out the FileObject
    //      value so that you can compare it in
    //      future requests.
    h1 = CreateFile("\\\\.\\EchoDevice",
                    GENERIC_ALL,
                    0,
                    NULL,
                    OPEN_EXISTING,
                    0,
                    NULL);
 
    //-f--> Here we are opening the second handle
    //      to our device. That is the step 2
    //      on the IRP Tracker. Check out the FileObject
    //      value so that you can compare it in
    //      future requests.
    h2 = CreateFile("\\\\.\\EchoDevice",
                    GENERIC_ALL,
                    0,
                    NULL,
                    OPEN_EXISTING,
                    0,
                    NULL);
 
    //-f--> Here we are throwing an IRP_MJ_READ for the
    //      first FileObject we have gotten. Step 3 on
    //      IRP Tracker. Notice that the first FileObject
    //      is going to be used in this IRP.
    ReadFile(h1,
             szBuffer,
             sizeof(szBuffer),
             &dwBytes,
             NULL);
 
    //-f--> Here we are throwing an IRP_MJ_READ for the
    //      second FileObject we have gotten. Step 4 on
    //      IRP Tracker. Notice that the second FileObject
    //      is going to be used in this IRP.
    ReadFile(h2,
             szBuffer,
             sizeof(szBuffer),
             &dwBytes,
             NULL);
 
    //-f--> The handle duplication is not notified
    //      to the driver. Only Object Manager "has known"
    //      about that. There isn't any corresponding
    //      step on IRP Tracker.
    DuplicateHandle(GetCurrentProcess(),
                    h1,
                    GetCurrentProcess(),
                    &h3,
                    0,
                    FALSE,
                    DUPLICATE_SAME_ACCESS);
 
    //-f--> As the third handle was goten from the
    //      first handle duplication, the corresponding
    //      FileObject is the same as the first handle.
    //      Step 5 on IRP Tracker. Notice that the first
    //      FileObject is going to be used on this IRP.
    ReadFile(h3,
             szBuffer,
             sizeof(szBuffer),
             &dwBytes,
             NULL);
 
    //-f--> Because we have two handles for the first FileObject,
    //      closing one of them will not gererate any notification
    //      to our device. There isn't any corresponding step
    //      on IRP Tracker.
    CloseHandle(h1);
 
    //-f--> That handle has not been duplicated, thus, when it is closed,
    //      an IRP_MJ_CLEANUP followed by an IRP_MJ_CLOSE are going to be
    //      sent to the driver. Step 6 on IRP Tracker.
    CloseHandle(h2);
 
    //-f--> Closing that handle, we will have the same behavior
    //      seen during the h2 closing. From the driver
    //      viewpoint, the first FileObject will be destroyed
    //      now. Step 7 on IRP Tracker.
    CloseHandle(h3);
 
    //-f--> And they have all lived happy ever after.
    return 0;
}

Getting a handle to an object assures us that this object will be valid until we close this handle. An object can only be destroyed by the system after all handles to it are closed. For this, the Object Manager maintains two counters, ProcessHandleCount and SystemHandleCount. The first one keeps the amount of open handles to an object in a given process. The other one maintains the sum of all ProcessHandleCount for the object in the system. These counters are decremented as these handles are closed. When they reach zero, a IRP_MJ_CLEANUP is generated.

Usually an IRP_MJ_CLEANUP serves us as an event to cancel any asynchronous operation on the FileObject which is being finalized. These IRPs are linked to the threads that have launched them and any asynchronous IRP should be cancelled at this time.

But what is IRP_MJ_CLOSE used for?

Besides the reference counters above mentioned, there is also the ObjectReferenceCount which, besides being incremented when a new handle is obtained, it is also incremented when a reference is made using kernel functions like ObReferenceObject(), for example. These calls increment the ObjectReferenceCount without a new handle being generated. For those who have known COM, this call has the similar behavior to AddRef(). This allows the object to remain valid itself for the kernel, even after all handles to it have been destroyed. Anyway, when this counter reaches zero, then the IRP_MJ_CLOSE is sent.

A driver can associate data structures for a given FileObject using the FsContext and FsContext2 pointers according to what I had  discussed in another post. These structures can only be deallocated when the driver receives the IRP_MJ_CLOSE.

Operations after IRP_MJ_CLEANUP

When the driver receives an IRP_MJ_CLEANUP, it does not mean that the end is near. Other kernel components can still launch new IRPs for reading or writing, even after this event. Not to mention the IRP_MJ_INTERNAL_DEVICE_CONTROL that can be exchanged among drivers.

A very common scenario at the File Systems development is precisely about a reference to a FileObject that is maintained by the Cache Manager. Even when all handles are closed by the applications, the system still retains this refrence anticipating that some application may want to open the same file again. The Cache Manager has some system threads that perform the so-called “delayed writing”. This feature retains many writes to a file in a single operation aftermost in order to reduce the number of disk accesses, thus gaining performance. It is too often such writings taking place, once the file had all of its handles closed for applications, and then, these data being written after IRP_MJ_CLEANUP.

In the end of it all, I hope to once again have helped more than hindered. Issues regarding the Cache Manager, Memory Manager and File Systems are not very trivial, but they are interesting enough to be read about and understand what the system has done before saying that everything is crap.

Until next time!

TestCleanup.zip

The wrist is still pulsing

January 4th, 2008 - Fernando Roberto

Before completing three months without any sign of life, here I am giving a peek at what has happened at the blogosphere. This year ending has been really hectic. As you may know, my employment by IBM has contributed a little to my absence. Okay, the university has also helped a lot. At the very end of the year, I reserved a time for myself and went for a walk on the beach. But let’s stop talking and get to what matters.

In this post, back to the living world, I am going to only comment on small things and postpone for a more elaborated post later (but still in this life), like you’ve been used to seeing around here.

Debug in Free

One of the things I had to learn to live together at IBM was to debug the driver we were supposed to write, which had always been built in Free or with optimizations and everything else that a release building deserved. Well, I have tried reluctantly to say it would be important to have a checked version for testing and that would be valuable to run our driver with all ASSERTs turned on check anything unusual that might occur. But believe me, it is not that easy to convince people that have already being accustomed to this situation. I was looking for good reasons to have a usable checked version when I had found this post. Well, the reasons are actually quite good but I still argue that we should have a checked version . I have been preparing a post that says a little about it.

Talking about knowing English

At some posts before this, I had talked about MSDN translated into some languages, including Portuguese. The message was that we could deal with it a little without knowing English but, if you don’t know English and you want to develop drivers for a living, so you have to learn English as soon as possible. I remembered this post today at a Google search when a link was especially rewarding.

IoGetDeviceObjectPointer Tips

Well, if someone has managed to get some tips, please tell me. If one day I decide to learn a third language, now I have a candidate for it.

New Year, New Look

It was supposed to have happened before, but unfortunately the time is has been short for many people. I put in check brother’s web designer skills. I asked him to improve this blog layout. It’s getting pretty good, but I still have had nothing concrete to be shown. I will be migrating to WordPress soon. Most of the work has already been done but much of it has remained to be done. My main intention in migrating to WordPress is to be able to use one of these plug-ins that would allow me to have the same post in both languages, English and Portuguese. Well, we have tried to test a few of them but they got some problems. But we’ll get there.

CYA…

IBM, Here we go!

October 15th, 2007 - Fernando Roberto

After a few months in a long selection process, I finally can share this good news with you. Good news for me, at least. Today, October 15th, 2007 was my first day as an IBMer on the Tutoia Street building. No, I haven’t given up the Windows Kernel to program neither Java nor ABAP. The issue is the usual one, the same blue screen.

How have you learned that?

This is a question that several people have made me. You may not believe it but it seems that things have naturally contributed to my professional development as a Windows Kernel programmer. When I was an intern at Provectus, I started reading the book “Inside Windows NT” for just a hobby, hopeless of being able one day to work with the things addressed by that literature and after all, having the privilege of working in a hardware development company in Brazil would be like winning the lottery. Well, my internship as a C programmer was in a company that had been developing its own hardware. I was maybe lucky. When it was least expected by me, there I was programming services for Windows NT, using shared memory and even a device driver appeared for my inspection. Because of my previous skill about MFC and Win32 API, what really attracted me was the Kernel Mode development. Once more for hobby, just for pleasure, I started reading about it and I must say it was hopeless again. Yeah, that time the result came from the opposite side. I went to work at a company that used to buy and sell shares for stock exchange. I used to develop COM+ using C++ and just one year was sufficient to learn about ASP, SQL and Java to know that this was not what I had been wanting for me. Luckily, the fate took me to SCUA. I did not know they used to make drivers for Windows. It was during an interview to join to the application team that I found out that there was a drivers’ team. Anyway, guess which team I ended up in after a while? By working there, I got in contact with many professionals, books, discussion lists and especially, the opportunity to put into practice what I had learned in books. Then after, it was the time of  Tempest, and with it, a chance to participate into a File System Driver Training with one of the foremost authorities on the subject. Then, a friend made me an indication for me at IBM, and after two interviews, one at the building and the other on Tutoia Street, I had a technical interview by telephone with the U.S. team; and here I am. I’d work on the MVFS for Windows development team.

And one of the points that had excited me the most in this new venture it was the opportunity to work with highly qualified people on the subject and be able to learn much from a team that involved minimally North Americans, Indians, and that time, one more Brazilian.

My first day at IBM was fulfilled with lectures. Initially, about the IBM history, and the other being about the various procedures that such a big company required.

CYA…

Try, Except, Finally and IoWriteLogErrorEntry (Part 2)

October 11th, 2007 - Fernando Roberto

In the first part of this post, I talked a little bit about the demand and the basic logic involved in creating and sending messages to the system event log. Today I’m going to extend a little longer talking about sending parameters in these events, not just getting stuck with fixed messages defined in the file .mc. Another sample driver is available for downloading at the end of this post.

Sending Parameters in Messages

So far, we have seen how to send fixed strings to EventViewer: however, it would be very useful to be able to send strings dynamically generated by the driver. Following the issue of handling exceptions, throughout this post, we will write a function that can be called from the __except block, and thus log which exception was thrown and handled by your driver, so that the administrator can finally know if something wrong is happening. If you do not know what I mean, look at the first part of this post.

Like Jack, the Ripper used to say: “Let’s split it.” First, let’s make the code able to display the event’s exception code. To do this, we must put this information at the allocated package. The dumpdata member of the IO_ERROR_LOG_PACKET structure can take binary data to the EventViewer. This member is an array of ULONG and has its size defined by the DumpDataSize member, which should always be a multiple of sizeof (ULONG). Thinking about our example, the handled exception code could be placed on the first position of this array; that is 0xC0000005 for “Access Violation”. See the following example that adds this functionality. Remember that, in addition to the DumpDataSize member properly set, the bytes used by elements of this array should be taken into consideration when determining the package size to be allocated.

/****
***     SendHelloEventWithData
**
**      Routine that allocates and sends an event which
**      takes an additional 32-bit parameter.
*/
 
VOID SendHelloEventWithData(IN ULONG    ulData)
{
    PIO_ERROR_LOG_PACKET    pLogPacket;
 
    //-f--> It allocates the event entry. We should sum
    //      the used bytes by the DumpData array. That
    //      size should always be a multiple of sizeof(ULONG).
 
    pLogPacket = IoAllocateErrorLogEntry(pDriverObj,
                                         sizeof(IO_ERROR_LOG_PACKET) +
                                         sizeof(ULONG));
 
    //-f--> It Initializes the whole structure.
    RtlZeroMemory(pLogPacket, sizeof(IO_ERROR_LOG_PACKET));
 
    //-f--> Puts up the desired message
    pLogPacket->ErrorCode = EVT_HELLO_MESSAGE;
 
 
    //-f--> It fills up the binary dump and its size.
    pLogPacket->DumpData[0] = ulData;
    pLogPacket->DumpDataSize = sizeof(ULONG);
 
    //-f--> It sends up the event entry to the event list.
    IoWriteErrorLogEntry(pLogPacket);
}

If you call this function passing 0x12345678 as a parameter, you will be able to see the sent data as it is shown in the figure below.

Inserting Strings

But where will these strings appear in the event log? We will have to create new messages making use of the strings on our message file. From here, we will build a routine that will be called within the __except block  to log the exception that was handled. Complete the message file with the following text.

MessageId = 0x0002
Facility = DriverEntryLogs
Severity = Warning
SymbolicName = EVT_EXCEPTION_HANDLED_MESSAGE
 
Language = Portuguese
A casa caiu na rotina %2 com status %3.
.
Language = English
The house fell down at routine %2 with status %3.
.

Notice that in these new messages there are these weird %2 and %3. These are the points where the first and second string will be respectively inserted in the message. But why is the first string indicated by %2 instead of %1? The %1 indicator is reserved to map a string with the driver name or device name. That will depend on what object was passed in the IoAllocateErrorLogEntry() first parameter. Although %1 is well documented, sometimes the string is not replaced in the message when the driver is starting or ending. Do not ask me why.

To make our example more complete, it would be very helpful if we could put a string that carried the symbol defined in the ntstatus.h header. So, we would have STATUS_ACCESS_VIOLATION for the exception code 0xc0000005 in the text displayed by EventViewer.

The first problem is to transform the exception code, which is a numeric value, into a string. This must have been multiple kernel developer’s need, because there is a routine source at the OSR Online site that does this. To get an idea, this routine is implemented in approximately 2200 lines of code. Is it oo much? Take a look at the beginning of it and you’ll understand it.

PUCHAR OsrNTStatusToString(NTSTATUS Status) {
 
    switch (Status) {
    case STATUS_SUCCESS:
        return "STATUS_SUCCESS";
    case STATUS_WAIT_1:
        return "STATUS_WAIT_1";
    case STATUS_WAIT_2:
        return "STATUS_WAIT_2";
...

After that, we will have to convert these strings from ANSI to UNICODE, but all of this is in the sample code.

In order the strings can be displayed by the EventViewer event, even when the driver is not loaded, they need to be copied into the package. So now, it is necessary to determine the package size that will be allocated using the IoAllocateErrorLogEntry() routine. The package size should be added from the number of bytes occupied by the unicode string including the terminating zero. I think it is better for you follow the comments in the source below that already does everything.

/****
***     LogExceptionHandled
**
**      This routine receives an NTSTATUS and sends a record to
**      the system event log.
*/
 
VOID LogExceptionHandled(PWSTR     wzFunctionName,
                         NTSTATUS  nts)
{
    UNICODE_STRING          usNtStatus;
    ANSI_STRING             asNtStatus;
    UCHAR                   ucFinalSize;
    PWSTR                   pwzTarget;
    PIO_ERROR_LOG_PACKET    pLogPacket;
 
    //-f--> It initializes an ANSI_STRING with the error code
    RtlInitAnsiString(&asNtStatus,
                      OsrNTStatusToString(nts));
 
 
    //-f--> Convert up the string into unicode
    RtlAnsiStringToUnicodeString(&usNtStatus,
                                 &asNtStatus,
                                 TRUE);
 
    //-f--> It get the package size, that is, respectively:
    //
    //  IO_ERROR_LOG_PACKET size
    //  DumpData element size
    //  Size of the string containing the error code
 
    //      Remember that we should reserve some space for the terminator.
    //  Size of the string containing the routine name that thrown the
    //      exception plus its respective terminator (/0).
    ucFinalSize = sizeof(IO_ERROR_LOG_PACKET) +
                  sizeof(ULONG) +
                  usNtStatus.Length + sizeof(WCHAR) +
                  (wcslen(wzFunctionName) + 1) * sizeof(WCHAR);
 
 
    //-f--> It allocates an entry for the event
    pLogPacket = IoAllocateErrorLogEntry(g_pDriverObj,
                                         ucFinalSize);
 
    //-f--> It initializes the whole structure
    RtlZeroMemory(pLogPacket, sizeof(IO_ERROR_LOG_PACKET));
 
    //-f--> It saves the error code into DumpData array
    //      and sets up its size into DumpDataSize member.
    pLogPacket->DumpData[0] = nts;
    pLogPacket->DumpDataSize = sizeof(ULONG);
 
 
    //-f--> It informs where the strings are and how many they are.
    pLogPacket->StringOffset = sizeof(IO_ERROR_LOG_PACKET) +
                               pLogPacket->DumpDataSize;
    pLogPacket->NumberOfStrings = 2;
 
    //-f--> It copies the first string into the package.
    pwzTarget = (PWSTR) ((PCHAR)pLogPacket +
                                pLogPacket->StringOffset);
    wcscpy(pwzTarget,
           wzFunctionName);
 
    //-f--> It copies the second string into the package.
    pwzTarget += wcslen(wzFunctionName) + 1;
    wcsncpy(pwzTarget,
            usNtStatus.Buffer,
            usNtStatus.Length / sizeof(WCHAR));
 
 
    //-f--> Since the documentation referring to RtlAnsiStringToUnicodeString 
    //      doesn't mention there will always be a zero terminator
    //      on the converted string, we cannot assume
    //      that this terminator always be there.
    pwzTarget += usNtStatus.Length / sizeof(WCHAR);
    *pwzTarget = 0;
 
    //-f--> It puts the desired message.
    pLogPacket->ErrorCode = EVT_EXCEPTION_HANDLED_MESSAGE;
 
 
    //-f--> It sends the entry to the event list.
    IoWriteErrorLogEntry(pLogPacket);
 
    //-f--> It releases the converted string.
    RtlFreeUnicodeString(&usNtStatus);
}

In that way, our __except block function, which was provided at the first part of this post, could be set as it is shown below:

    ...
    __except(EXCEPTION_EXECUTE_HANDLER)
    {
        //-f--> Oops! An exception has been thrown. Let's log this,
        //      pretend insanity and just return the exception code.
        nts = GetExceptionCode();
 
        //-f--> It sends a record to the system event log.
 
        LogExceptionHandled(__FUNCTIONW__, nts);
        ASSERT(FALSE);
    }
    ...

Just for a test, let’s make a call to the DupString() function passing invalid parameters such as NULL and we will get the following output as it appears below. All the source code of the shown example is available for downloading at the end of this post.

The End is Near

As it was explained in the first part of this post, the event record doesn’t go to the disk synchronously when IoWriteErrorLogEntry() function is called; instead, the event record goes to a linked list before going to the disk. If the machine falls down and a dump is generated, we can consult this list with the !errlog in WinDbg, which analyzes the generated dump. Cool, let’s see if it works? Let’s send the message below and then dereference a null pointer, just to… The code for this function is written in KillYourSelf () routine in the sample code available for downloading.

MessageId = 0x0003
Facility = DriverEntryLogs
Severity = Error
SymbolicName = EVT_GOODBYE_MESSAGE
 
Language = Portuguese
Adeus mundo cruel!
.
Language = English
Goodbye cruel world!
.

The KillYourSelf() function will only be called when a particular symbol is set, as you can see below. Therefore, if you want to test and see the results with your own eyes modify this symbol definition at the beginning of the source code and redo the test with a test machine.

 
#if _WHERE_IS_THE_PENGUIN_
    //-f--> Oh! I dream to be a Linux driver. Good-bye cruel world!
    KillYourSelf();
#endif

Inevitably a blue screen will occur, and with it, its memory dump is generated. Opening this dump for analysis using WinDbg, we should use the !errlog command to have the output, as it is shown below.

Microsoft (R) Windows Debugger  Version 6.7.0005.0
Copyright (c) Microsoft Corporation. All rights reserved.
 
 
Loading Dump File [C:\Documents and Settings\froberto\Desktop\MEMORY.DMP]
Kernel Complete Dump File: Full address space is available
 
Symbol search path is: srv*c:\symbols*http://msdl.microsoft.com/download/symbols
Executable search path is: 
Windows 2000 Kernel Version 2195 UP Free x86 compatible
Product: WinNt
Kernel base = 0x80400000 PsLoadedModuleList = 0x8046a4c0
Debug session time: Thu Oct 11 11:05:56.375 2007 (GMT-3)
System Uptime: 0 days 0:02:06.875
Loading Kernel Symbols
.................................................................................................
Loading User Symbols
 
*******************************************************************************
*                                                                             *
*                        Bugcheck Analysis                                    *
*                                                                             *
*******************************************************************************
 
Use !analyze -v to get detailed debugging information.
 
 
BugCheck 1E, {c0000005, be552682, 1, 0}
 
Probably caused by : EventSender.sys ( EventSender!KillYourSelf+2a )
 
Followup: MachineOwner
---------
 
kd> !errlog
PacketAdr  DeviceObj  DriverObj  Function  ErrorCode  UniqueVal  FinalStat
81551108   00000000   815c1830    0        402a0001   00000000   00000000
        \Driver\EventSender
        DumpData:  12345678 
815b1f88   00000000   815c1830    0        802a0002   00000000   00000000
        \Driver\EventSender
        DumpData:  c0000005 
81535248   00000000   815c1830    0        c02a0003   00000000   00000000
        \Driver\EventSender

At the ErrorCode column, we have the codes that have been defined by the message file compilation and are visible at the header file also generated. Watch up the messages and their identifiers in the excerpt below. As you can see, none of the three test messages used in this example went to the disk. All records were still in memory. Thus, do not expect to see these messages using EventViewer; they have not yet been written at the disk yet.

//
// MessageId: EVT_HELLO_MESSAGE
//
// MessageText:
//
//  Ola mundo!
//
#define EVT_HELLO_MESSAGE                ((NTSTATUS)0x402A0001L)
 
 
//
// MessageId: EVT_EXCEPTION_HANDLED_MESSAGE
//
// MessageText:
//
//  A casa caiu na rotina %2 com status %3.
//
#define EVT_EXCEPTION_HANDLED_MESSAGE    ((NTSTATUS)0x802A0002L)
 
//
// MessageId: EVT_GOODBYE_MESSAGE
//
// MessageText:
//
 
//  Adeus mundo cruel!
//
#define EVT_GOODBYE_MESSAGE              ((NTSTATUS)0xC02A0003L)

Phew! I have thought this post would not finish any more. Once again, I hope this could have helped. CYA!

EventSender.zip