Memory bugs are tricky. Leaks have a clear impact on performance, and quickly become hard to spot when heap allocated objects move too much. Memory access bugs, on the other hand, make your program crash right away, if you're lucky. In less desirable cases, they may end up corrupting random objects on the heap, and only end up in a crash much later. Or even worse, leading to undetected undefined behaviours or wrong computations.
This is the reason why there are a number of standalone tools (e.g. valgrind, Dr.Memory), as well as compiler extensions (e.g. AddressSanitizer, MemorySanitizer, LeakSanitizer) to help with memory bug detection.
On Windows, and especially using Qt, things are a bit harder, as valgrind is not an option and Dr.Memory often crashes with Qt applications. Unless you use WSL, this only leaves compiler tools on the table.
Memory Sanitizers on Windows
First of all, what are our choices when it comes to using memory sanitizers on Windows?
There are two options:
- with Visual Studio's cl compiler
- with LLVM's clang/clang-cl compilers (also available through Visual Studio)
- {x86,amd64}/Release builds only - AddressSanitizer only
So using AddressSanitizer (from now on ASAN) is the only viable option for memory bug detection with memory sanitizers on Windows. Also, since the support for the cl compiler is still incomplete, in this post we will be using clang.
Furthermore, we will be using Visual Studios's bundled clang to make it possible to generate Visual Studio solutions. If you're using your own clang installation, you should update the following paths accordingly.
You can find Visual Studio 2019's clang in the following directories:
- For x86:
%ProgramFiles(x86)%\Microsoft Visual Studio\2019\Professional\VC\Tools\Llvm\bin
- For x64:
%ProgramFiles(x86)%\Microsoft Visual Studio\2019\Professional\VC\Tools\Llvm\x64\bin
Note that you will need to add the right directory manually to the PATH when using the command line, as there is no vcvars script available for clang tools.
From now on I'm going to assume for all paths that we're using Visual Studio 2019 Professional and we're building 64 bit applications.
Compile an executable with the static C++ runtime (/MT)
If you build and link in one go, it is enough to compile with -fsanitize=address
When doing compilation and linking in separate steps, we need an extra step to provide the ASAN runtime explicitly.
The linker will implicitly link the program against the static CRT (/MT
) and with the static version of clang's ASAN runtime. Note the use of -wholearchive
to force the compiler to include all the symbols from the library, avoiding optimizations.
If the program consumes a library which is also being sanitized, then said library (which should be linked against the dynamic CRT with /MD
) should be linked against clang_rt.asan_dll_thunk-x86_64.lib
. In this way, the library (or libraries) will use the shadow memory of the statically linked ASAN runtime in the executable.
Compile an executable with the dynamic C++ runtime (/MD)
Again, it is easy to compile and link in one go:
But now the executable should use a different ASAN runtime when linked:
Note that only clang_rt.asan_dynamic_runtime_thunk-x86_64.lib
needs the -wholearchive
flag here.
All the included dlls that are also sanitized (which should always be using the dynamic CRT) should be linked against the same ASAN runtime libraries as the executable.
Since clang_rt.asan_dynamic-x86_64.lib
is an import lib pointing to clang_rt.asan_dynamic-x86_64.dll
, when running a sanitized executable (or loading a sanitized lib) its folder should be in the PATH. You can find the dll alongside the .lib in %ProgramFiles(x86)%\Microsoft Visual Studio\2019\Professional\VC\Tools\Llvm\x64\lib\clang\10.0.0\lib\windows
.
Using ASAN with CMake
If you want to use ninja or any other generator other than Visual Studio, just point CMake to your clang-cl installation and add the necessary flags to your targets. Adjust your flags for plain clang if you don't need or don't care about clang-cl.
CMake will always need to be invoked with -DCMAKE_BUILD_TYPE=Release
otherwise the compilation will fail.
To generate a Visual Studio solution, you need to pass a few extra arguments to CMake:
- Generate a Visual Studio solution:
-G "Visual Studio 16 2019"
- Choose between 32 or 64 bit target:
-A {Win32,x64}
- Use the clang toolkit:
-T ClangCL
Or, if you are using Visual Studio Code just select the right entry from the kit list (e.g. Clang 10.0.0 for MSVC with Visual Studio Professional 2019 Release (amd64)).
Conclusions
Using tools to track down memory bugs helps saving time and effort, especially on complex projects. On Windows, the available tooling is limited, and a step by step documentation on how to use what's available may be hard to come by. With this blog post, it will be hopefully easier for you to leverage the AddressSanitizer on Windows and keep memory bugs in your projects under control.
If you'd like to learn more about the tooling available for C++ developers working with Microsoft Windows on the desktop, you might be interested in our Debugging and Profiling C++ Applications on Windows training.
Trusted software excellence across embedded and desktop platforms
The KDAB Group is a globally recognized provider for software consulting, development and training, specializing in embedded devices and complex cross-platform desktop applications. In addition to being leading experts in Qt, C++ and 3D technologies for over two decades, KDAB provides deep expertise across the stack, including Linux, Rust and modern UI frameworks. With 100+ employees from 20 countries and offices in Sweden, Germany, USA, France and UK, we serve clients around the world.
5 Comments
23 - Sept - 2020
Markus H
Thanks for the blog post! Nice to see someone poking around with asan on Windows, I did not see any real activity on this in the Qt community so far. I'm using one dedicated qmake mkspec for building apps with clang-cl's sanitizers against prebuilt Qt so far.
A few additions/questions:
14 - Oct - 2020
Alessandro Ambrosano
Thanks for the feedback, Markus!
You are right, the only missing things are the ones mentioned in "coming beyond 16.8", which of course you may or may not care about. Also, to give you the whole picture, most of the work has been done taking MSVC 16.6 as a reference, where using clang sanitizer was unquestionably a better option (16.7 only got out later). In any case the idea is that the build steps are the same regardless of the compiler of choice, so you should be able to switch to cl effortlessly by only updating a few directories.
The command line options for the linker are different because I avoided adding static libraries twice once without and once with the
-wholearchive
flag, as using-wholearchive
on static libraries implicitly links them. The-include:__asan_seh_interceptor
flag (or___asan_seh_interceptor
for x86) is just telling the linker to avoid optimizing out that specific symbol from the runtime thunk (see also https://github.com/llvm-mirror/clang/blob/3ee1fe728c53ee07305dcb33177f7de493d108c2/lib/Driver/ToolChains/MSVC.cpp#L399), but from what I saw using-wholearchive
with the dynamic thunk is implicitly including that symbol too so that can be dropped.Linking sanitized code against debug runtimes (/MTd, /MDd) is not yet supported with clang 10.0.0 shipped with Visual Studio, nor with clang 11.0.0.
2 - Feb - 2021
Sören
It should read $ENV{PROGRAMFILES(X86)} for the target_link_directories part.
29 - Nov - 2022
Alessandro Ambrosano
Fixed it, thanks!
30 - Nov - 2022
Alex G
Hi Alessandro, Great post ! It seems using MSVC2019 v16.11 it does not work : links and builds correctly but crashes on start