n l i t e d

:



Thread Links
next

Versioning

📢 PUBLIC Page 1024:12/12 | edit | chip 2018-04-15 12:42:13
Tags: CryptDisk

August 18 2017

Versioning

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.

VerID.h: #pragma once #define __VERID_H__ 0x0101 struct VerNum_s { unsigned char Major; unsigned char Minor; unsigned short Build; }; struct VerID_s { struct VerNum_s File; struct VerNum_s Prod; const char *Builder; const char *BuildTime; const char *BuildStr; }; #ifdef __cplusplus extern "C" const struct VerID_s gVerID; #else extern const struct VerID_s gVerID; #endif #define VER_STR(S) #S #define VER_BUILDSTR(MJ,MN,BD,ID,SUF) VER_STR(MJ)"."VER_STR(MN)"."VER_STR(BD)" " SUF " [" ID "]" /**EOF: VERID.H**/

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.

BldVerID.bat: perl -IC:\CL\Perl\lib ../Bin/VerUpdate.pl VerFile.h prod=../Include/VerProd.h resource=Resource/CryptDriver.rc cl /I ..\Include /I . /c /nologo /Fo%1\VerID.obj 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.

  1. Save everything in the Solution.
  2. RtClick the resource script (Driver.rc) and click "Remove". Do not click "Delete"!
  3. Remove resource.h
  4. Create a new directory named "Resource" in the Driver project directory.
  5. Move Driver.rc and resource.h into the Resource directory.
  6. Rename Driver.rc to CryptDriver.rc
  7. In the Solution Explorer, RtClick the "Resource Files" folder in the Driver project and click Add > Existing Item...
  8. 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:

  1. 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.
  2. 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.
  3. 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:

  1. Update the version info using the Perl script VerUpdate.pl
  2. Recompile the resources
  3. Compile VerID.c
BldVerID.bat: perl -IC:\CL\Perl\lib ../Bin/VerUpdate.pl VerFile.h prod=../Include/VerProd.h resource=Resource/CryptDisk.rc rc /D _UNICODE /D UNICODE /l"0x0409" /nologo /I ..\Include /I ..\Include\%2 /I ..\Include\%3 /fo%1\CryptDisk.res Resource\CryptDisk.rc cl /I ..\Include /I ..\Include\%2 /I ..\Include\%3 /I . /c /nologo /Fo%1\VerID.obj VerID.c

BldVerID.bat expects 3 parameters:

%1 is the intermediate output directory, contained in $(IntDir).

%2 is the build configuration (Debug or Release), contained in $(Configuration).

%3 is the target platform (win32 or x64), contained in $(PlatformName).



close comments Comments are closed.

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.

  1. [] ok delete


Page rendered by tikope in 297.322ms