Windows Driver Development

Here are a few hints which I figured out myself while writing a Windows driver.

Setting up the Environment

  • Install Visual Studio 2019 Community Edition, select C++ workload
  • Install Windows Driver Kit (WDK)
  • This tutorial explains how to provision the target computer for kernel mode debugging. Here is the summary:
    • On the target machine: Turn off secure boot (look here how to do it on an ASUS motherboard)
    • On the target machine: Run “WDK Test Target Setup MSI”. This comes with the WDK:
      c:/Program Files (x86)/Windows Kits/10/Remote/x64/WDK Test Target Setup x64-x64_en-us.msi
      This opens a port which allows the host computer to do its thing later.
    • When I tried the next step, it was failing with a minor quirk. On the host machine, Visual Studio was looking for the C runtime libraries here:
      c:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Redist/MSVC/14.21.27702/debug_nonredist/x64/Microsoft.VC141.DebugCRT
      However, my Visual Studio had the runtime installed in this directory:
    • c:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Redist/MSVC/14.21.27702/debug_nonredist/x64/Microsoft.VC142.DebugCRT
      Seems like it was looking for the previous version in the “14.21.27702” sub directory. I solved it by installing the 141-version of the Build-Tools and the libs. Then I copied the contents of the folder
      c:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Redist/MSVC/14.16.27012/debug_nonredist/x64
      to
      c:/Program Files (x86)/Microsoft Visual Studio/2019/Community/VC/Redist/MSVC/14.21.27702/debug_nonredist/x64
    • On the host computer: Start Visual Studio, go to Extensions->Driver->Test->Configure Devices. Click “Add New Device”. Use port 50000, key 1.2.3.4. This will install all sorts of things on the target PC. It will create a directory c:/DriverTest which is used as working directory.
      Note that this step also creates a new user named WDKRemoteUser. Windows will be configured to automatically log on to this user after reboot. Looks as if we are not supposed to know the password. It also seems that after provisioning the target computer, we are allowed to run drivers with a test signature. Before provisioning the driver showed up having errors in the device manager. Even when I added the test certificate to the trusted root certificates in the certificate store.
    • On the host machine: Log files during provisioning are created here:
      c:/Users/harald/AppData/Roaming/Microsoft/WDKTestInfrastructure/ProvisioningLogs/
      On the target machine: Log files from the driver installation are created here:
      c:/Windows/inf/setupapi.dev.log

Compiling the Hello World Driver

  • I followed this tutorial. But it contained a few unnecessary steps. Here are the necessary steps:
    • On the host computer: Create project from template “Kernel Mode Driver, Empty (KMDF)”
    • Add file named “Driver.c” and copy source code into it.
    • Compile configuration Debug x64.
    • Set up the connection to the target computer:
    • Then all you need to do is right-click the project and select “Deploy”. The driver will automatically be copied to c:/DriverTest on the target machine and installed. You con’t need to run devcon.exe yourself.
    • The driver shows up in the device manager under Samples/KmdHelloWorld Device

Using WinDbg

  • On the host machine: Run WinDbg from this directory
    c:/Program Files (x86)/Windows Kits/10/Debuggers/x64
    with this command line:
    WinDbg -k net:50000,key=1.2.3.4
  • Use CTRL-Break to stop execution on the target machine. Type
    x KmdfHelloWorld!*
    to see messages regarding the installed driver. Note: You won’t see the debug output coming from the KdPrintEx calls. Follow the next steps to make them appear.
  • It seems that the debug print calls are not logged. They are printed in real time on the debugger window while they are happening. Obviously the debugger must not be in halted state for those messages to show up. I also noticed that the error level DPFLTR_INFO_LEVEL is so low that the messages are suppressed. At first glance, only the error level DPFLTR_ERROR_LEVEL produces any output. I am sure this can be configured somewhere. But I just changed the source code of the driver. The following command alone did not help.
  • Increase verbosity of debug outputs:
    ed nt!Kd_Default_Mask 8

More Examples

  • Can be found here.

The ECHO Example

The example projects don’t compile in VS2019. Don’t bother fixing it. Just create a new project from the template “Kernel Mode Driver, Empty (KMDF)” and copy the source files. For the echo example, the automatically generated INF file works fine. Make sure to name the project “ECHO”.

WDK API calls used:

WdfDriverCreate Creates a framework driver
WdfStringCreate Creates a framework string
WdfDriverRetrieveVersionString Retrieves a string that identifies the framework library’s version.
WdfStringGetUnicodeString Obtain the Unicode string that is assigned to a specified framework string object.
WdfObjectDelete Deletes a framework object and its child objects.
WdfDriverIsVersionAvailable Boolean value that indicates whether the driver is running with a specified version of the Kernel-Mode Driver Framework library.
WdfDeviceInitSetPnpPowerEventCallbacks Registers a driver’s Plug and Play and power management event callback functions.
WdfDeviceCreate Creates a framework device object.
WdfDeviceCreateDeviceInterface Creates a device interface for a specified device.
WdfDeviceGetDefaultQueue Returns a handle to a device’s default I/O queue.
WdfIoQueueStart Enables an I/O queue to start receiving and delivering new I/O requests.
WdfTimerStart Starts a timer’s clock.
WdfIoQueueStopSynchronously Prevents an I/O queue from delivering I/O requests, but the queue receives and stores new requests.
WdfTimerStop Stops a timer’s clock.
WdfIoQueueCreate Creates and configures an I/O queue for a specified device.
WdfTimerCreate Creates a framework timer object.
WdfRequestGetIoQueue Returns a handle to the framework queue object from which a specified I/O request was delivered.
WdfRequestCompleteWithInformation Stores completion information and then completes a specified I/O request with a supplied completion status.
WdfRequestRetrieveOutputMemory Retrieves a handle to a framework memory object that represents an I/O request’s output buffer.
WdfVerifierDbgBreakPoint Breaks into a kernel debugger, if a debugger is running.
WdfMemoryCopyFromBuffer Copies the contents of a specified source buffer into a specified memory object’s buffer.
WdfRequestComplete Completes a specified I/O request and supplies a completion status.
WdfRequestSetInformation Sets completion status information for a specified I/O request.
WdfRequestMarkCancelable Enables cancellation of a specified I/O request.
ExAllocatePoolWithTag Allocates pool memory of the specified type and returns a pointer to the allocated block.
WdfMemoryCopyToBuffer Copies the contents of a specified memory object’s buffer into a specified destination buffer.
WdfTimerGetParentObject Returns a handle to the parent object of a specified framework timer object.
WdfRequestUnmarkCancelable Disables cancellation of a specified I/O request.
  • What is WPP Tracing?

Leave a comment

Your email address will not be published. Required fields are marked *