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?