Building drivers using the standard Win32 project template has
a number of advantages over the "official" driver template:
Enabling the code browser lets me use auto-completion and automatic
background compilation with automatic error highlighting.
I can compile individual files without having to rebuild the
entire project.
Better control over the output directories.
It makes it easier to use WinDbg rather than the (broken)
Visual Studio kernel debugger.
Avoids dealing with the (broken) target provisioning
system.
Solution RtClick > Add > New Project. Choose Visual C++ > Windows > Win32 and
Win32 Project.
Click "Application Settings" to expose the settings.
Select "Windows application"
Check "Empty Project"
Uncheck SDL
Click "Finish" to create the project.
I need to add a C++ file to enable the C++ properties. RtClick
Driver > Source Files and add DrvEntry.cpp. Add just a comment line.
Open the configuration manager and make sure both x86 and x64 are
enabled for the project.
Open the properties panel for DrvEntry.cpp Make sure All
Configurations and All Platforms are selected. There should
be as little variation as possible (ideally none!) between
configurations and platforms. The .vcxproj file is a terrible
place to manage configurations, use header files instead whenever
possible.
Configuration Properties > General
Output Directory: $(SolutionDir)Out\Win$(PlatformName)$(Configuration)\
This will put all the final output files in CryptDisk\Out\Winx64Debug
Whole Program Optimization: No Whole Program Optimization
C/C++
General
Additional Include Directories: $(ProjectDir);$(SolutionDir)Include;$(WDKPATH)\Include\km;$(WDKPATH)\Include\shared;$(VS12PATH)\VC\include WDKPATH is an environment variable that points to the WDK, which is typically found in C:\Program Files (x86)\Windows Kits\8.010
This directory should include the file "SDKManifest.xml" VC12PATH is an environment variable that points to the Visual Studion installation.
This contains the Common7, SDK, and VC directories.
Debug Information Format: Program Database (/Zi)
Common Language RunTime Support: No Common Language RunTime Support
Treat Warnings as Errors: Yes (/WX)
SDL checks: Leave blank
Multi-processor Compilation: Yes (/MP)
Optimization
Optimization: Full Optimization (/Ox)
The project should always be built with full optimization,
including debug builds. The optimizer will be disabled for individual
files using #pragma only while I am debugging that file, then re-enabled
before committing the changes. This is easy with two lines at the top
of every file (just below the includes):
//#pragma message(__FILE__": Optimizer disabled.")
//#pragma optimize("",off) I uncomment both lines (never just one!) when I need to
debug the file, step through the un-optimized code (because it is much
easier to follow), fix the bug, re-comment both lines, build again and
test, then commit the changes. Sticking to this practice will bring
the debug and release builds very close to each other, avoiding
last-minute surprises in the release builds.
Inline Function Expansion: Only _inline (/Ob1)
Enable Intrinsic Functions: Yes (/Oi)
Favor Size or Speed: Favor fast code (/Ot)
Omit Frame Pointers: No (/Oy-)
Whole Program Optimization: No
Preprocessor
Preprocessor Definitions: <different options>
This field should contain as little information as possible,
only the defines that are necessary to define a specific build
version. Everything else goes into the WdkDefines.h file.
Leave this for now, I will deal with it later.
Ignore Standard Include Paths: Yes (/X)
We need to use only the APIs from the WDK.
Code Generation
Enable String Pooling: Yes (/GF)
Enable Minimal Rebuild: No (/Gm-)
Enable C++ Exceptions: No
Windows does support C++ exceptions in the kernel. Whether to
use try/except in the kernel is a religious argument. I prefer
to use extensive error handling and let the kernel crash during
debugging.
Basic Runtime Checks: Default
Runtime checks and full optimization are mutually exclusive.
I prefer to have the optimizer enabled during debug builds so
that my debug code more closely resembles my release builds.
Relying too heavily on runtime checks and never debugging
optimized code is why so many projects end up shipping their
debug builds.
Runtime Library: Multi-threaded (/MT)
The driver won't actually link with the default libraries,
but there is a lot of overlap.
Security Check: Disable Security Check (/GS-)
Security checks and full optimization are mutually exclusive.
Enable Function-Level Linking: Yes (/Gy)
Enable Parallel Code Generation: Yes (/Qpar)
Language
Enable Run-Time Type Information: No (/GR-)
Trying to use run-time type information will result in link
error like: 1>Device.obj : error LNK2001: unresolved external symbol "const type_info::`vftable'" (??_7type_info@@6B@)
Precompiled Headers
Precompiled Header: Not using Precompiled Headers
Precompiled headers are a symptom of putting too much
code in header files where doesn't belong.
Output Files
Program Database File Name: $(OutDir)$(TargetName).pdb
The PDB should be located in the same directory as the driver
binary so it can be easily found for debugging.
Browse Information
Enable Browse Information: Yes (/FR)
Advanced
Calling Convention: __stdcall (/Gz)
The NT kernel uses stdcall by default. This can be left as
__cdecl if desired.
Disable Specific Warnings: 4996;4200;4091
4996: Allow strncpy()
4200: Allow zero-length arrays.
4091: 'typedef': ignored on left of 'type' when no variable is declared.
This suppresses warnings generated in the 8.1 version of ntddscsi.h.
Forced Include File: $(ProjectDir)WdkDefines.h;$(SolutionDir)Include\SlnDefines.h
I put all my WDK flags in WdkDefines.h. It is easier to
manage a file than settings buried inside the project
definition.
WDKDefines.h
Create a new header file named WDKDefines.h which will
contain the defines used to build the driver. Since this
is really more of a build configuration file than a source
code file, I like to put it into the "Build" folder rather
than "Source Files" or "Header Files".
WDKDefines.h:
/*************************************************************************/
/** WinDefines.h: Compiler flags that must be defined before including **/
/** any other files. **/
/** (C)2013 nlited systems inc, Chip Doran **/
/*************************************************************************/
#ifndef __WINDEFINES_H__
#define __WINDEFINES_H__ 0x0101
// See ReadMe.txt for notes about the build environment.
#ifdef _NTDDK_
#pragma error(__FILE__ " must be included BEFORE NtDDK.h !!")
#endif
//These are the WDK platform configuration flags.
//Normally these are passed to the compiler as command
//line flags (ie /DKERNEL). This creates a real mess
//as critical information is buried in the makefile,
//project specification (.vcxproj), or (worst of all!)
//configuration setup scripts. To make things
//even worse, all but three of these flags are common to
//all platforms!
//A much better solution is to put these defines in a
//source file that can include comments (such as these).
//The trick is to make sure this file is included before
//any others (especially NtDDK.h). This is accomplished
//by setting (for all configurations and platforms) the
//project option:
// C/C++ > Advanced > Force Include file = $(ProjectDir)WinDefines.h
#define KERNEL 1
#define _KERNEL_MODE 1 //Required for Win10 DDK
#define _WINDOWS 1
#ifdef DEBUG
//#define DBGOUT 1
#endif
//How thorough is IsBadPtr() ?
//0: Check for NULL
//1: Probe physical page
#define ISBADPTR 1
//Calling conventions
#define STD_CALL 1
#define CONDITIONAL_HANDLING
#define DEVL 1
#define FPO 1
#define _IDWBUILD 1
//Windows target version: 8.1 (WINBLUE)
#define _WIN32_WINNT 0x0603 //Win81
#define NTDDI_VERSION 0x06030000 //Win81 driver interface
#define WIN32_LEAN_AND_MEAN 1 //Discard the frills and laces.
#define NT_UP 1
#define NT_INST 0
//#define WIN32 100
#define _NT1X 100
#define WINNT 1
#endif
//EOF: WINDEFINES.H
SlnDefines.h
Create a new header file named SlnDefines.h in the $(SolutionDir)Include
directory. This will contains flags that are universal to all projects
in the solution.
SlnDefines.h:
/*************************************************************************/
/** SlnDefines.h: CryptDisk solution flags. **/
/** (C)2017 nlited systems, chip doran. **/
/*************************************************************************/
#pragma once
#define __SLNDEFINES_H__
#include "Build.h" //Sets DEBUG flags
#include "Platform.h" //Sets _X86_ or _AMD64_
//NOTE: DEBUG, _DEBUG, NDEBUG, and _NDEBUG will all be defined as 0 or 1.
#ifndef UNICODE
#define UNICODE //Unicode for everything.
#endif
#ifndef _UNICODE
#define _UNICODE
#endif
/*************************************************************************/
/** Flags for kernel DbgPrintf() **/
/*************************************************************************/
#if _DEBUG
/*** IMPORTANT *** IMPORTANT *** IMPORTANT *** IMPORTANT *** IMPORANT ***
*** **************************************************************** ***
*** !!!!!! DO NOT SHIP DRIVERS THAT PRINT TO ERROR !!!!!! ***
*** !!!!!! This is a debug HACK to get around the unfortunate !!!!!! ***
*** !!!!!! situation that every driver uses the same filter. !!!!!! ***
*** !!!!!! This is why no one should ever ship a debug build. !!!!!! ***
*** **************************************************************** ***
*** IMPORTANT *** IMPORTANT *** IMPORTANT *** IMPORTANT *** IMPORANT ***/
// In DEBUG builds, print everything at ERROR level.
#define PRINT_LEVEL 0 //DPFLTR_ERROR_LEVEL
#define DO_BREAK 1 //Enable DbgBreakPoint()
#else
#define PRINT_LEVEL 3 //DPFLTR_INFO_LEVEL
#define DO_BREAK 0 //Disable DbgBreakPoint()
#endif
#define PRINT_ID 77 //DPFLTR_IHVDRIVER_ID
/*************************************************************************
To see text from DbgPrint in WinDbg, I need to enable the filters.
On the target machine, add the registry values:
HKEY_LOCAL_MACHINE\
SYSTEM\
CurrentControlSet\
Control\
Session Manager\
Debug Print Filter\
DEFAULT = DWORD 0xFF
IHVDRIVER = DWORD 0xFF
Beware: Enabling anything other than ERROR unleashes a flood of text
from every driver in the system.
See the docs for DbgPrintEx() for more detail.
*************************************************************************/
//EOF: SLNDEFINES.H
Compile
I should now be able to compile the empty DrvEntry.cpp file.
DriverEntry
Add some actual driver code:
DriverEntry:
/*************************************************************************/
/** DrvEntry.c: Windows kernel driver entry point. **/
/** (C)2017 nlited systems inc, Chip Doran **/
/*************************************************************************/
#include "Globals.h"
#pragma message(__FILE__": Optimizer disabled.")
#pragma optimize("",off)
DRIVER_INITIALIZE DriverEntry;
static void DriverNtUnload(DRIVER_OBJECT *pDriver);
NTSTATUS DriverEntry(DRIVER_OBJECT *pDriver, UNICODE_STRING *RegPath) {
int Err= 0;
//NOTE: By default, only error messages are printed by Windows.
DbgPrintEx(DPFLTR_IHVDRIVER_ID,DPFLTR_ERROR_LEVEL,"CryptDriver has arrived! [%llX]\r\n",DriverEntry);
if(KD_DEBUGGER_ENABLED && !KD_DEBUGGER_NOT_PRESENT)
DbgBreakPoint();
pDriver->DriverUnload= DriverNtUnload;
return(STATUS_SUCCESS);
}
static void DriverNtUnload(DRIVER_OBJECT *pDriver) {
DbgPrintEx(DPFLTR_IHVDRIVER_ID,DPFLTR_ERROR_LEVEL,"CryptDriver has left the building.\r\n");
}
//EOF: DRVENTRY.C
This does nothing but print a "Hello, world!" message and set the
unload function. I am hijacking the IHV filter driver's device ID to
make sure my message is printed without requiring any registry
changes.
Globals.h:
/*************************************************************************/
/** Globals.h: Global declarations for the CryptDisk kernel driver. **/
/** (C)2017 nlited systems inc, Chip Doran **/
/*************************************************************************/
#pragma once
#define __WINK_H__
#include <NtDDK.h>
#include <WinDef.h>
#include "StdTypes.h"
There was a time when I was opposed to embedded #include's inside header
files, but I have changed my mind with the recent versions of Visual Studio.
Embedding the includes allows VS to compile on the fly while editting the
header files, so it can flag undefined names and other errors. This is very
handy.
If I try to compile now, I get an error "No Target Architecture"
This is because the WDK needs more specific architecture names than the
standard Windows applications. I need to go back and set the flags for
each build variation separately.
UPDATE: A better solution is to move the defines to Build.h
and Platform.h, then use the $(Configuration) and $(PlatformName)
build macros to select which versions are used. This doesn't become
apparent until very late in project lifecycle management.
Versioning
Debug/Win32: _X86_;_DEBUG
Release/Win32: _X86_;NDEBUG
Debug/x64: _X64_;_AMD64_;_DEBUG
Release/x64: _X64_;_AMD64_;NDEBUG
I should now be able to successfully compile the DrvEntry.cpp file.
Linking
Open the Driver project's property panel and expand the Linker item.
Note that the "Additional Library Directories" parameter is specific
to the build and needs to be set to different values for the x86 and
x64 platform.
Comments are moderated.
Anonymous comments are not visible to other users until approved.
The content of comments remains the intellectual property of the poster.
Comments may be removed or reused (but not modified) by this site at
any time without notice.