Static analysis for C and C++ can only be done well if you know exactly how the code is compiled. All those compiler flags and other details buried in the build system are crucial for getting a correct and complete picture of the software; the code on its own is not a complete or even valid program.
The obvious approach of just pointing a static analysis tool at a big pile of C and C++ files is similar to using a command like gcc -o program *.c instead of the build system to build a project. That gcc command is very unlikely to succeed for any non-trivial piece of software. What are some reasons why a command like gcc -o program *.c would fail?
- It can’t find include files because of missing -I flags.
- gcc is the wrong compiler for compiling this project. It has only ever been compiled with one version of one commercial compiler, which has a specific set of language extensions.
- gcc is the right compiler, but the version of gcc is wrong.
- The target platform of gcc is wrong. Perhaps it is supposed to be compiled to ARM.
- The preprocessor definitions are wrong and specific -D flags should be specified.
- Generated source code hasn’t been generated yet (e.g., a lexer or parser).
- Environment variables that affect gcc haven’t been specified correctly.
- The software is normally built from a different current working directory, and relative include paths aren’t working.
- There are other C or C++ files or libraries in this project that aren’t in the current directory.
- There are other missing compiler flags.
A thorough static analysis will be sensitive to all these same things. If a static analysis is not sensitive to these things, then it is necessarily doing some amount of guesswork or just skipping over stuff it doesn’t understand.
CodeSonar goes to great lengths to make static analysis easy for the user, but we need to gather information from the build process--specifically the compilation command lines, compiler pedigree, and source code. We could ask the user to create a script that invokes codesonar, providing it the compiler flags on the command line for each translation unit. However, this is too much work. It’s like asking the user to create a second build system for their project. Nobody wants a second build system since the first one is typically hard enough to maintain. Build systems are where the skeletons are buried. They are incredibly fragile and sometimes full of dirty hacks.
Another option is to try to direct the existing build system to invoke CodeSonar in addition to the real compiler. For example, you might try to do this by running:
This is the way we did business over a decade ago. However, in practice, large build systems rarely honor CC consistently, and some build systems utilize multiple compilers.
For the past decade, we have used low level operating system hooks to monitor compiler process creation. This is great because it works with any build system or IDE without modifications no matter how archaic the build system might be. All CodeSonar needs to understand are the compiler executions. It can be happily oblivious to how the build system works otherwise. Using codesonar to analyze software is as easy as using the UNIX time command to time the build:
codesonar analyze myproject make
On Windows, CodeSonar uses a device driver in the kernel implement the compiler execution monitor. Windows 10 build 1607 imposed more stringent requirements on driver signatures, and we had to obtain new signatures for our driver. The new signing process turned out to be quite tricky and not very well documented. In particular, there were a number of pitfalls getting Windows Hardware Lab Kit to execute successfully on a clean Windows 10 machine without any of our software involved. The attached narrative documents the problems we faced and the solutions we eventually discovered, often at great expense.
We hope this narrative may be useful for others who need to sign Windows device drivers.