Keeping track of the build version is especially important with
drivers. It can be extremely frustrating to spend hours working on a
bug only to discover that the system never unloaded the previous build
of the driver. I have over time developed a versioning system that
works very well.
I like to create a "Build" filter in the project's Solution
Explorer to keep them separate. These are files that very rarely (if
ever) change, so they can be tucked somewhere out of sight.
Create VerID.h in the solution Include directory. This is the
structure that defines the build information that will be embedded
into the final binary output.
Create VerProd.h in the solution Include directory. This will
define the product version.
VerProd.h:
/*************************************************************************/
/** VerProd.h: Version information for the CryptDisk project. **/
/** August 2017, Chip Doran **/
/** NOTE: This file is modified by VerUpdate.pl during every release **/
/** build. **/
/*************************************************************************/
#pragma once
#define VER_PROD_MAJOR 1 //20170818: Version 1.0
#define VER_PROD_MINOR 0 //20170818: Version 1.0
#define VER_PROD_BUILD 1
/**EOF: VERPROD.H**/
Create VerFile.h in the project directory. This will define the
build information for each binary output and will be automatically
updated during every build.
VerFile.h:
/*************************************************************************/
/** VerFile.h: Version information for CryptDriver.sys. **/
/** Aug 2017, Chip Doran **/
/** NOTE: This file is modified automatically by VerUpdate.pl during **/
/** every build. **/
/*************************************************************************/
#pragma once
#define VER_FILE_MAJOR 1 //20170818: 1.0.0
#define VER_FILE_MINOR 0 //20170818: 1.0.0
#define VER_FILE_BUILD 0
#ifndef VER_SUFFIX
#define VER_SUFFIX ""
#endif
#define VER_BUILDER "VS12"
#define VER_BUILD_TIME "20170818111332"
//EOF: VERFILE.H: CryptDriver
Create VerID.c in the project directory. This will instantiate
the build data in the binary.
VerID.c:
/*************************************************************************/
/** VerID.c: Definition of the version and build information. **/
/** May 2011, Chip Doran **/
/*************************************************************************/
#include <Windows.h>
#include "VerID.h"
#include "VerProd.h"
#include "VerFile.h"
//Version information.
//This file should change very rarely, if ever.
const struct VerID_s gVerID= {
{ VER_FILE_MAJOR, VER_FILE_MINOR, VER_FILE_BUILD },
{ VER_PROD_MAJOR, VER_PROD_MINOR, VER_PROD_BUILD },
VER_BUILDER,
VER_BUILD_TIME,
VER_BUILDSTR(VER_FILE_MAJOR,VER_FILE_MINOR,VER_FILE_BUILD,VER_BUILDER,VER_SUFFIX)
};
/**EOF: VERID.C**/
Open the properties for VerID.c and exclude it from the build; it will
be compiled using a custom build step.
Create a Bin directory in the solution and add the Perl script
VerUpdate.pl. This will automatically
update VerFile.h and the project's resource script (Driver.rc).
Create BldVerID.bat in the project directory. This will execute
the VerUpdate.pl script and compile VerID.c.
RtClick the Driver project > Add... > New Resource > Version > New.
Update the version information as appropriate.
Save everything. There is a minor bug in Visual Studio where it
will always save the resource script in the project directory, even
when I specify a different location. I need to correct this by
removing the newly created files from the project, manually moving
them to the proper directory, and re-adding them to the project.
Save everything in the Solution.
RtClick the resource script (Driver.rc) and click "Remove".
Do not click "Delete"!
Remove resource.h
Create a new directory named "Resource" in the Driver project directory.
Move Driver.rc and resource.h into the Resource directory.
Rename Driver.rc to CryptDriver.rc
In the Solution Explorer, RtClick the "Resource Files" folder in
the Driver project and click Add > Existing Item...
Go to the Resource directory and select both CryptDriver.rc and
resource.h
The Driver project should now look like this:
Open the Driver project properties and select Build Events > Pre-Link Event.
Command Line: BldVerID.bat $(IntDir)
Description: Updating version info...
Select Linker > Input and add $(IntDir)VerID.obj to Additional Dependencies.
Now that I have version info, I need to add it to my "Hello, World!" message.
Open DrvEntry.cpp and add another DbgPrint() line. I also need to add VerID.h to the
include list.
DrvEntry.cpp:
/*************************************************************************/
/** DrvEntry.c: Windows kernel driver entry point. **/
/** (C)2017 nlited systems inc, Chip Doran **/
/*************************************************************************/
#include "Globals.h"
#include "VerID.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);
DbgPrintEx(DPFLTR_IHVDRIVER_ID,DPFLTR_ERROR_LEVEL,"CryptDriver version %s %s",gVerID.BuildStr,gVerID.Builder);
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
Build the Driver project again, and I should see "Updating version info..." in the
build output. I should also see the VER_FILE_BUILD number incrementing and the
VER_BUILD_TIME updating in VerFile.h.
⚛
Since VerFile.h is updated with every build, it will always be
newer than what is in the source code repo. The purpose of the version
info for me, the developer, is to make every build unique and
identifiable. This may be different than the release system, which may
want to recreate a build from the past. This is a not my
problem. I would argue that trying to reproduce an older build with
exactly the same build number is a very bad idea.
Code forks, and versions built on different workstations, may create
duplicate version numbers. This is why I include VER_BUILDER in the
VerID struct. This is the name of the machine that was used to build.
BuildFix: Stale Version Info
March 23 2018
I discovered a minor flaw in my versioning scheme. To avoid
unnecessary builds, I increment the build number in a special
"pre-link" custom build step. This step runs only when some other file
was recompiled, triggering a new link. So if nothing changed, the
project is not linked, the version is not incremented, and nothing
happens -- which is a good thing.
The problem is that the pre-link step happens after the resources
are compiled. This means the version info baked into the resources are
stale, left over from the previous build, when the resources are
compiled. The version reported internally, read from gVerID, is
correct. The version reported by File Explorer is not.
I did not notice this until I tried to insert the build
configuration (Debug vs Release) into the version info, then read it
using File Explorer.
A work-around would be to compile the resources again in the
pre-link step after updating the version info. This adds a line to
BldVerID.bat and adds more complexity. This could be a real can of
worms if the resource file has any unexpected dependencies, which it
very well could. Some resource files are fairly complex.
Another approach would be to add VerID.c back into the normal
build, with a custom build rule. Then make the resource script
dependent on VerID.obj. But then the rule to make VerID.obj would be
wrong, it needs to depend on anything that would trigger a relink.
CONSTRAINTS:
VerID.obj must be recompiled during the pre-link custom build
step. Otherwise it doesn't happen when it should, or it happens all
the time.
The resource script must be recompiled after VerID.obj is compiled
and before the link. Otherwise the version info is stale. However, the
resources still need to be compiled normally so editing resources
still behaves normally.
I want to use common project settings for all configurations.
Project settings with different values depending on the current build
configuration are a nuisance to maintain. This frequently causes
broken release builds. I want to rely on MSBuild variables (ie,
PlatformName, Configuration) and not set explicit command line values
(ie, /DDEBUG=0).
#1 and #2 mean recompiling the resource inside BldVerID.bat. I can
pull the command line from the build log (Driver2.tlog\rc.command.1.tlog).
I can replace the explicit references to the output directory with %1.
rc /D _UNICODE /D UNICODE /l"0x0409" /nologo %2 /fo%1\CryptDriver.res Resource\CryptDriver.rc I need to somehow pass the states of _DEBUG and _NDEBUG without
setting them explicitly on the command line.
I think my best option is to create two subdirectories in my global
Include directory, Include\Debug and Include\Release. These will
contain two versions of the same file, Build.h. This is where I
will set the DEBUG, NDEBUG, _DEBUG, and _NDEBUG flags. Then I can
define the release or debug build using the command line option
/I$(SolutionDir)Include\$(Configuration) Build.h would then be implicitly included for all files. I can
also use this trick for the x86/x64 platform configuration, which
would allow me to get rid of the "<different options>" value
for the "C Preprocessor" settings. It would be replaced by including
"Build.h" and "Platform.h" into Include\SlnDefines.h which is implicitly
included into all files by "Forced include file". Then I add to
"Additional Include Directories" the directories
$(SolutionDir)Include\$(Configuration)
$(SolutionDir)Include\$(PlatformName)
= = = = =
This worked well, and I have an improved build process. I was able
to get rid of the <different options> setting for all the
Preprocessor settings.
Build.h and Platform.h
I want to remove all traces of DEBUG, NDEBUG, _X86_, and _X64_ from
the command lines. Instead, I will define them in common header files
and control which version of the headers are included using the MSBuild
macros $(Configuration) and $(PlatformName).
Create a version of Build.h for each supported configuration, typically
"Debug" and "Release", and place them in subdirectories under the common
Include that match the configuration names. This is where I define DEBUG,
_DEBUG, NDEBUG, and _NDEBUG. This will be included before Windows.h,
so it cannot have any external dependencies.
NOTE: The DEBUG, _DEBUG, NDEBUG, and _NDEBUG macros are always
defined as 0 or 1. Do not use #ifdef DEBUG.
Always use #if DEBUG.
Include\Debug\Build.h:
/*************************************************************************/
/** Build.h: Declarations for Debug vs Release builds. **/
/** (C)2017 nlited systems, chip doran. **/
/*************************************************************************/
#pragma once
#define __BUILD_H__
//This is the DEBUG build.
#define DEBUG 1
#define _DEBUG 1
#define NDEBUG 0
#define _NDEBUG 0
//EOF: BUILD.H
Include\Release\Build.h:
/*************************************************************************/
/** Build.h: Declarations for Debug vs Release builds. **/
/** (C)2017 nlited systems, chip doran. **/
/*************************************************************************/
#pragma once
#define __BUILD_H__
//This is the RELEASE build.
#define DEBUG 0
#define _DEBUG 0
#define NDEBUG 1
#define _NDEBUG 1
//EOF: BUILD.H
Create a version of Platform.h for each target platform, typically win32
and x64, and put them in subdirectories that will match $(PlatformName).
This is where I define _X86_, _X64_, and _AMD64_.
Include\win32\Platform.h:
/*************************************************************************/
/** Platform.h: This defines the target platform (win32, x64) **/
/** (C)2018 nlited systems, chip doran. **/
/*************************************************************************/
#pragma once
#define __PLATFORM_H__
//This is the win32 (x86, 32bit) build.
#define _X86_ 1
#ifndef KERNEL
#define WIN32 1
#endif
//EOF: PLATFORM.H
Include\x64\Platform.h:
/*************************************************************************/
/** Platform.h: This defines the target platform (win32, x64) **/
/** (C)2018 nlited systems, chip doran. **/
/*************************************************************************/
#pragma once
#define __PLATFORM_H__
//This is the x64 (64bit) build.
#define _X64_ 1
#define _AMD64_ 1
//EOF: PLATFORM.H
Build.h and Platform.h will be included from SlnDefines.h, which will
be implicitly included into every file in the solution.
Include\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_
#ifndef UNICODE
#define UNICODE //Unicode for everything.
#endif
/*************************************************************************/
/** Flags for kernel DbgPrintf() **/
/*************************************************************************/
#ifdef _DEBUG
#define DEBUG 1
// In DEBUG builds, print everything at ERROR level.
#define PRINT_LEVEL 0 //DPFLTR_ERROR_LEVEL
#define DO_BREAK 1 //Enable DbgBreakPoint()
#else
#define DEBUG 0
#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
Project Settings
I control which version of Build.h and Platform.h is used by setting
the include path.
Project properties > Configuration Properties > C/C++ > General
Additional Include Directories
The path to Build.h uses the $(Configuration) macro:
$(SolutionDir)Include\$(Configuration) The path to Platform.h uses the $(PlaformName) macro:
$(SolutionDir)Include\$(PlatformName)
Since there are no longer any configuration-specific flags that need
to be defined on the command line, I can get rid of (almost) everything
in the "Preprocessor Definitions" field.
SlnDefines.h needs to be the first file included for everything.
The easiest way to make sure this happens is to add it to the "Forced
Include File" setting, as the first item.
SlnDefines.h;PrjDefines.h
I also need to include these files when compiling the resources.
Resources > General
Additional Include Directories
$(SolutionDir)Include;
$(SolutionDir)Include\$(Configuration);
$(SolutionDir)Include\$(PlatformName)
These changes mean I now have access to the build and platform macros in
my resource script. This lets me set custom text for the debug and release
builds that will be visible from File Explorer in the file's properties.
I can now identify if the driver is debug build without needing to run
anything.
I have included the .RC file below, but do not edit the resource
script directly. Use the Resource Editor to add the code.
Driver2\Resource\CryptDriver.rc:
// Microsoft Visual C++ generated resource script.
//
#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "SlnDefines.h"
#include "winres.h"
#if DEBUG
#define BUILD_NAME "CryptDriver2.sys [Debug]"
#else
#define BUILD_NAME "CryptDriver2.sys [Release]"
#endif
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// English (United States) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
BEGIN
"#include ""SlnDefines.h""\r\n"
"#include ""winres.h""\r\n"
"#if DEBUG\r\n"
"#define BUILD_NAME ""CryptDriver2.sys [Debug]""\r\n"
"#else\r\n"
"#define BUILD_NAME ""CryptDriver2.sys [Release]""\r\n"
"#endif\r\n"
"\0"
END
3 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 3,2,0,1282
PRODUCTVERSION 2,4,0,1
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x40004L
FILETYPE 0x0L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", "nlited systems inc."
VALUE "FileDescription", "CryptDisk virtual disk driver."
VALUE "FileVersion", "3,2,0,1282"
VALUE "InternalName", "CryptDriver2"
VALUE "LegalCopyright", "Copyright (C) 2018 nlited systems inc."
VALUE "ProductName", "CryptDisk"
VALUE "ProductVersion", "2,4,0,1"
VALUE "OriginalFilename", BUILD_NAME
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END
#endif // English (United States) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED
I use the pre-link build event to increment the version info
because this will not be invoked unless something changed to trigger a
new link. This avoids needlessly incrementing and relinking when
nothing has changed.
I can consolidate my pre-link step into a single common configuration
using the MSBuild macros. This is where I update the version info and
re-compile the resources.
Build Events > Pre-Link Event > Command Line
BldVerID.bat $(IntDir) $(Configuration) $(PlatformName)
There is a custom version of BldVerID.bat for each project, but most of it
is boilerplate. It needs to do 3 things:
Update the version info using the Perl script VerUpdate.pl
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.