The help you needed in C, C++, MFC
Complete tutorials with free source code
PDF Print E-mail

Creating a simple driver

Now, you should have the basic idea of a driver. So, the time is right to get in to coding. In this article, you will see how to create a simple driver which does nothing but send some string messages to kernel debugger when it is loaded and unloaded so we can see those messages with DbgView.

In a normal C program, the entry point of the program for the programmer is the main function. In drivers, the name of the entry point function is DriverEntry. When our driver is loaded, this function is called. Here is the DriverEntry function of our simple driver.

#define NTDEVICE_NAME_STRING      L"\\Device\\simple-driver"
#define SYMBOLIC_NAME_STRING      L"\\DosDevices\\simple-driver"

NTSTATUS DriverEntry(IN PDRIVER_OBJECT  DriverObject, IN PUNICODE_STRING RegistryPath)
{
PDEVICE_OBJECT      deviceObject;
UNICODE_STRING      dosdevicename;
UNICODE_STRING      symbolicLinkName;
NTSTATUS            status;


DbgPrint("DriverEntry called.\n");

RtlInitUnicodeString(&dosdevicename, NTDEVICE_NAME_STRING);

status = IoCreateDevice(
DriverObject,               // DriverObject
0,                          // DeviceExtensionSize
&dosdevicename,             // DeviceName
FILE_DEVICE_UNKNOWN,        // DeviceType
FILE_DEVICE_SECURE_OPEN,    // DeviceCharacteristics
FALSE,                      // Not Exclusive
&deviceObject               // DeviceObject
);


if(!NT_SUCCESS(status))
{
DbgPrint("IoCreateDevice failed.\n Error code: 0x%x\n", status);
return status;
}

//registering driver unload routine
DriverObject->DriverUnload = Unload;

// Creating a symbolic link
RtlInitUnicodeString(&symbolicLinkName, SYMBOLIC_NAME_STRING);

status = IoCreateSymbolicLink(&symbolicLinkName, &dosdevicename);
if(!NT_SUCCESS(status))
{
IoDeleteDevice(deviceObject);
DbgPrint("IoCreateSymbolicLink failed.\n Error code: 0x%x\n", status);
return status;
}

DbgPrint("Exiting DriverEntry\n");

return status;
}

In DriverEntry, we first print a message to kernel debugger so we can see when DriverEntry is called, using DbgView. In DbgView, you have to enable capturing kernel by capture->capture kernel from main menu to view the messages printed to kernel debugger.

If the driver is a device driver, it has to create the device it is going to control using IoCreateDevice function. I have shown you how to call that function, but because our driver do not control any device, that is not compulsory. I have used FILE_DEVICE_UNKNOWN for device type because we are not going to control a specific device.

When a new driver is loaded, Windows creates a driver object and sends it to the DriverEntry of the driver as the first argument. This driver object contains fields which contains the addresses of the specific routines of our driver. We have to put the addresses of the functions we write to control the device to these fields and register those events. So, When a user process wants to communicate with the device we are controlling, windows knows which of our functions it should call. For an example, if our driver is controlling a hard drive, when a user requests to write data to the hard drive, Windows should know which function of the driver handles writing to the hard drive.

Because we are not controlling a device, we do not need to register any events. But, if we need to unload the driver later without rebooting, we have to register an unload routine. The following two lines do that task.

//registering driver unload routine
DriverObject->DriverUnload = EventUnload;

If we do not register a driver unload routine, when the driver is started, it cannot be stopped without rebooting. Our unload routine, Unload() is described later in the article.

Then, we create something called a Symbolic Link. A symbolic link is a shortcut name to access our driver. For an example, the name of your c drive, "C:" is a symbolic link used by the hard disk driver. Creating a symbolic link is also not compulsory, but it makes easy for user processes to communicate with our driver. You will see more on this later articles.

An important thing you need to know is the string type we use in drivers is different. In a usual C or C++ program, the end of the string in memory is identified by the null character (null terminated strings). But, in drivers, we use UNICODE_STRING for storing strings. UNICODE_STRING stores the string as Unicode character buffer and it has a separate field to store the length of the string. You can initialize a UNICODE_STRING variable using RtlInitUnicodeString function.

Now, let's take a look at our driver unload routine. We gave it the name "Unload". This function is called when the driver is unloaded.

NTSTATUS Unload(IN PDRIVER_OBJECT DriverObject)
{
UNICODE_STRING dosdevicename;
NTSTATUS status;

RtlInitUnicodeString(&dosdevicename, SYMBOLIC_NAME_STRING);
status=IoDeleteSymbolicLink(&dosdevicename);
if(!NT_SUCCESS(status))
{
DbgPrint("IoDeleteSymbolicLink failed.\n Error code: 0x%x\n", status);
return status;
}

IoDeleteDevice(DriverObject->DeviceObject);
return status;
}

In our unload routine, we do the clean up. We delete the symbolic link we created and we delete the device we created.

You can download the source code of sample project here.

Compiling and building the project

To build the sample project, open the checked build environment command prompt for your target operating system of the driver from WDK and travel to the folder where source files are in using cd command and enter command "build -ceZ" without quotes. A folder will be created in the folder which contains the sys file of the driver.