By Don Burn, community contributor
[June 2015 Update: Don’s updated his Lint Driver Extensions to work with all versions of the WDK, up to and including Windows 10 and VS2015. See the link below]
It’s been ages since The NT Insider published All About Lint – PC Lint and Windows Drivers which introduced driver writers to compile time checking tools. The original article pointed out that PC-lint was already fully integrated into the build process used by the DDK at that time, and included a configuration file to tailor the errors and warnings that PC-lint reported. Thanks to changes in the WDK development and build environments, the original setup no longer works well and when it does work it produces a large number of false positives. The new tools that accompany this article easily work with all versions of the Windows Driver Kit (WDK) and at the same time significantly reduce the spew that PC-Lint produces.
A lot of developers figure that once they have their driver building successfully at /W4, and passing PREfast and SDV without errors, they don’t need to do any further checking. While some folks believe those tools are sufficient, I personally don’t. My background is in fault-tolerant computing, and there you can never check too much.
This article is going to take a look at what PC-lint can do for you, and give you some tools and pointers to make the experience of using PC-lint easier. The original article referenced above has a lot of good examples of using PC-lint with drivers.
A number of the errors PC-lint will report are of categories that the Microsoft tools also check. Even here I find the tool to be of significant value. For example, if you fix a problem that PREfast identifies, PC-lint may flag the problem again because Lint’s algorithms are different enough that the tool can identify an edge condition you missed in your original fix. For a code review you are likely to ask more than one person to review your code, right? Well, I think the same should be true of using tools to find potential bugs in your code.
Like all good tools, PC-lint provides ways to easily disable any of its checks that you don’t consider valuable. So don’t be put off if a small number of the checks PC-lint performs are not to your liking. Similarly, options can be used to enable a specific check that you want that the accompanying configuration file disables. In fact, Gimpel provides configuration files for some of the popular coding guidelines such as Scott Meyer’s books and the MISRA consortium.
To me, the most valuable thing that PC-lint does is highlight code that is likely to contain omission errors. Omission errors are where you forget to do something, and tend to be some of the hardest category of errors to find. Thus, any assistance in locating this category of problem is likely to be very worthwhile.
So let’s take a look at some of the things that PC-lint can do for you.
PC-lint is exceptionally good at identifying unused fields and variables. Before you think the Microsoft tools identify unused variables sufficiently well, consider that Visual Studio with /W4 only catches unused local variables. If you have an unused static variable, global variable or structure member, neither the MS VC compiler nor PREfast will flag them; But PC-lint will identify them for you. In many cases I find that an unreferenced variable means that a piece of code is missing. There had to be a reason you declared the item in the first place, right? So what was it?
Related to unused variables are unreferenced variables and values. A simple case is initializing a variable but then never using it is the following:
ULONG index = 0;
If you never use the variable index, the compiler won’t complain – but, once again, PC-lint will. Before you think, “who cares”, I once encountered a driver that initialized a structure it allocated for each channel with over 128KB of data through several thousand lines of code, when only 256 bytes of the structure were ever used! That particular driver consumed over 4MB of space where 8KB was needed.
In the same category, if a function does not have the PREfast annotation __checkreturn, no tool except PC-lint will complain about:
status = Func1(Param1, Param2); return;
If you don’t care about the returned status, it is clearer to future maintainers reading your code if you cast the function call to void indicating that the return value is being ignored, like this:
(void)Func1(Param1, Param2); return;
How about data and functions that are only used in one module? The original author may know what was intended, but the next person who maintains your code may appreciate if you put a static qualifier on the variable or function. It definitely makes it clear this is only used in this source file. PC-lint will flag these in its global wrap-up of the analysis.
PC-lint also does a better job of expression checking than any of the Microsoft tools. This can be a great help when you wonder why a complex piece of code in a conditional statement is never executed. A simple example of this can be found in a number of WDK driver samples, like the following:
ULONG ux; ... if ( ux < 0 ) { // do something }
Of course ux can never be less than zero because it’s an nsigned variable! There were an amazing number of cases like this in the samples I used for testing, with a significant amount of code that will never be executed.
PC-lint tracks values and reports questionable actions. For example:
#define FLAG 0 // definition in some include ... x = x & FLAG;
While this may be the intent, it does not hurt to be reminded that FLAG is zero, and it may confuse the next developer who looks at the code. In my testing, I found some cases where expressions evaluated to zero without any constants hiding the result.
Another area that PC-lint checks is compatibility of variables in expressions. A number of these can show up as integer overflow or underflow problems and lead to security holes or crashes. The Microsoft tools do some of this, but PC-lint is outstanding in finding potential problems such as mixing signed and unsigned variables in the same expression. PC-lint applies a stricter set of tests for loss of precision and loss of sign than Microsoft does. Think of these tests as the equivalent to PREfast’s requirement for using safe string functions. Microsoft does provide safe integer runtimes, but few of us use them.
A number of things that PC-lint checks for could be best described as coding style verification. As previously mentioned, you can disable any of these checks (or any PC-lint check at all) if you wish. Take C preprocessor macro handling:
#define TEST_MACRO(a,b) (a + (b & 0x111) + (2 * a) )
The above may look good but consider what happens when:
x = TEST_MACRO( i++, y + 2 );
The double increment of i and the calculation y + 2 & 0x111 are probably not what you expected. PC-lint will flag these errors. [ED: This is also a great example of why macros are inherently evil and should be avoided in favor of inline functions.]
PC-lint will complain about multiple definitions of a global symbol. If the symbols are different types or qualifiers, this can cause some nasty problems even if the compiler accepts it. When the definitions are the same, having the redundant definition still makes changing the code harder, and is more likely to lead to confusion for the next developer who looks at the code.
And then there are issues with being overly clever with Boolean expressions. For example, consider the following:
if (NdisEqualMemory(pStation->Config.DesiredBSSIDList[index], StaEntry->Dot11BSSID, sizeof(DOT11_MAC_ADDRESS)) == 1)
This example from the WDK may be obvious after a little reflection, but it could be written a lot more clearly, especially given that not everyone remembers the semantics of memcmp with a length of zero!
Another area that PC-lint checks is for bugs in switch statements. The tool will flag the lack of a default case and cases not ending in a break statement.
PC-lint will check for strange indentation that could indicate you messed up on closing braces. The Win7 WDK has an example of code where the indentation goes down in the middle of a compound statement for no reason; luckily that one is correct – the indentation is just broken. A similar case in a demo driver from elsewhere was actually a logic bug that would impact correct execution.
The tool also flags questionable semi-colons:
while ( Hw->PhyState.RadioAccessRef != 0 ) ;
The above example from the WDK is correct, with the code being used to ensure all references by other threads have exited. It could easily have been a mistake, replacing the semi-colon with braces is preferable here.
And those are just a few things that PC-lint does today. One of the nicest things I like about PC-lint is that it keeps evolving. At present they have developed a minimal checking capability for multi-threaded programs. The current support does not provide much in the way of useful data for a Windows driver, but as the tool evolves this and other features will keep PC-lint in my mix of tools for a long time.
Using PC-lint with the WDK
Since the original All About Lint article, a lot has changed in the Windows driver development environment so I’ve developed a new package called Lint Driver Extensions (LDX). You can download a zip archive containing this tool from the link provided at the conclusion of this article.
LDX is packaged with an installer. On your development system run the install package after you have installed the WDK and Gimpel’s PC-lint. For PC-lint you should install all the patches from the Gimpel website and have either the co-msc110.* or co-msc100.* files installed. While the environment is optimized for PC-lint version 9.0 it works fine with the earlier version 8.0 (aside: Version 8.0 will both miss some errors and have more false positives.).If you installed the package before the required software or if you add an additional WDK then just go into Add/Remove programs and select Change for the Lint Driver Extension package. This will update the software for all WDK’s.
To use PC-lint with the WDK you have several options:
- If you are using an older WDK, the tool installs a command in the WDK’s Bin directory called Lint. Simply preface your build command with lint e.g., lint build <options> or lint prefast build <options>. The Lint output is placed in a log file following the conventions of the build log with the name lint replacing build. For example, for a lint in the Windows 7 Checked build environment lintchk_win7_x86.log will be generated.
- For the Windows 8 WDK that is integrated with Visual Studio, you will find a new external tool on the tools menu. This is LDX Solution which will rebuild and lint the current solution, placing the results in the Output pane.
- If you are using a command line build with the Windows 8 WDK, then LdxMsb from the LDX install directory will run MSBuild to rebuild the solution with Lint and display the output to stdout.
- Finally the ultimate tool for using PC-Lint with the WDK is Riverblade’s Visual Lint. This is a third party tool providing an integrated package that works inside VS2012. The tool is an add-on to PC-Lint which you must still purchase. The capabilities include background analysis of the project, coded display listings that like Visual Studio clicking on the error takes you to the line to edit and provides easy lookup of the description of the errors. The latest version of Visual Lint (4.0.2.198) is required for use with the WDK. The tool has a minor bug that if there are two subprojects with the same name, such as filter in the Toaster sample, one needs to be renamed for analysis to work. A fix is in the works.
To use Visual Lint with the WDK choose LintLdx.lnt as the standard lint configuration file for the tool. There is a 30-day free trial of Visual Lint available so if you are considering PC-Lint, take a look at what Visual Lint can add to the experience. I expect to be using it for much of my work.
One of the biggest problems with the configuration from original article was the large number of spurious errors that were reported. As stated earlier my LDX tool contains a much richer lint options file that significantly reduces the errors. The file is far from a complete coverage of the entire set of kernel API’s, and I’ll gladly accept additions. The options do disable a number of errors that PC-lint complains about but which are false positives.
Choosing errors to suppress is always something where people have strong opinions, so the new package provides the ability to customize the lint options. When the package is installed the first time, an empty file LdxCustom.lnt is placed in the PC-lint installation directory. Edit this file to add the options you wish to use for lint. Re-installing the tool will not change the LdxCustom.lnt file.
If you find problems with the package please let me know. My email address is at the end of the article. Hopefully I’ve encouraged you to grab PC-lint and give it a try. Using my LDX tool makes it easy. Enjoy writing better, more reliable, drivers using PC-lint!
Code associated with this article:
http://insider.osr.com/2013/code/ldx.zip
June 2015 Update:
http://insider.osr.com/2013/code/ldx2015.zip
Don Burn is a Windows system software architect, specializing in drivers and file systems, with 35 years of industry experience. He provides consulting services for Windows drivers, specializing in challenging problems for the Windows architecture. Don can be reached at burn@windrvr.com
[…] for using PC-Lint with the Windows Driver Kit (WDK) that was described in my NT Insider article, Another Look at Lint . For those unfamiliar with Lint, it is the checking program for the C language that came out of […]