Skip to content

How to Build C++ Projects with the Address Sanitizer on Windows memory bug detection via compiler extension

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:

  1. with Visual Studio’s cl compiler
  2. 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

clang-cl -fsanitize=address main.cpp

When doing compilation and linking in separate steps, we need an extra step to provide the ASAN runtime explicitly.

clang-cl -fsanitize=address -c main.cpp -o main.o
# Call vcvars64 here so lld-link will be able to link all the default libraries
lld-link main.o -libpath:"%ProgramFiles(x86)%\Microsoft Visual Studio\2019\Professional\VC\Tools\Llvm\x64\lib\clang\10.0.0\lib\windows" ^
    -wholearchive:clang_rt.asan-x86_64.lib -wholearchive:clang_rt.asan_cxx-x86_64.lib

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:

clang-cl -fsanitize=address /MD main.cpp

But now the executable should use a different ASAN runtime when linked:

clang-cl -fsanitize=address -c main.cpp -o main.o
# Call vcvars64 here so all the default libraries are added to lld-link
lld-link main.o -libpath:"%ProgramFiles(x86)%\Microsoft Visual Studio\2019\Professional\VC\Tools\Llvm\x64\lib\clang\10.0.0\lib\windows" ^
    clang_rt.asan_dynamic-x86_64.lib -wholearchive:clang_rt.asan_dynamic_runtime_thunk-x86_64.lib

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.

add_executable(exec main.cpp)
target_compile_options(exec PRIVATE -fsanitize=address) # /MD will be used implicitly
target_link_directories(exec PRIVATE "${ENV:PROGRAMFILES(X86)}/Microsoft Visual Studio/2019/Professional/VC/Tools/Llvm/x64/lib/clang/10.0.0/lib/windows")
target_link_libraries(exec PRIVATE clang_rt.asan_dynamic-x86_64 clang_rt.asan_dynamic_runtime_thunk-x86_64)
target_link_options(exec PRIVATE /wholearchive:clang_rt.asan_dynamic_runtime_thunk-x86_64.lib)

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.

 

About KDAB

If you like this blog and want to read similar articles, consider subscribing via our RSS feed.

Subscribe to KDAB TV for similar informative short video content.

KDAB provides market leading software consulting and development services and training in Qt, C++ and 3D/OpenGL. Contact us.

FacebookTwitterLinkedInEmail

2 thoughts on “How to Build C++ Projects with the Address Sanitizer on Windows”

  1. 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:
    * there is a prototype of qmake support here: https://codereview.qt-project.org/c/qt/qtbase/+/278998 and it might be feasible to get that patch finished (hopefully with a pick-to 5.15 comment 😉 ). This would come with the *HUGE* benefit that one could build qt itself with sanitizer support on Windows – and applications could just write “CONFIG += sanitizer sanitize_address” just as on Linux.
    * QBS has asan support on Windows with clang-cl already, see https://codereview.qt-project.org/c/qbs/qbs/+/284529 . The approach used there is to call the compiler driver for linking, i.e. use “cl.exe /link” instead of “lld-link.exe”. This pulls in the asan libs automatically with clang-cl (but not with cl yet), which is a great simplification.
    * your linking parameters differ a bit from clang’s automatic linking parameters. Have a look at the output of the following command:
    clang-cl -v -fsanitize=address /MD main.cpp 2>&1 | findstr “link.exe”
    On x86, i get an additional “-include:___asan_seh_interceptor” and I am not really sure if that is needed and why.
    * I didn’t see any really missing features in msvc with 16.7, in fact they have added quite some stuff like debug builds that clang doesn’t support yet. Is there anything other than the (IMHO not so important) “features coming in/beyond 16.8” from the blog posts that is really missing?
    * Missing support for debug builds and for exceptions have been the biggest trouble that I have seen so far. Have the msvc additions of debug builds in 16.7 been ported back to clang? Did MS’s work improve anything regarding exception support?
    * ubsan is also supported with clang on Windows. This tends to be forgotten but can also be helpful …

    1. Thanks for the feedback, Markus!

      > * I didn’t see any really missing features in msvc with 16.7, in fact they have added quite some stuff like debug builds that clang doesn’t support yet. Is there anything other than the (IMHO not so important) “features coming in/beyond 16.8” from the blog posts that is really missing?

      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.

      > * your linking parameters differ a bit from clang’s automatic linking parameters. Have a look at the output of the following command:
      > clang-cl -v -fsanitize=address /MD main.cpp 2>&1 | findstr “link.exe”
      > On x86, i get an additional “-include:___asan_seh_interceptor” and I am not really sure if that is needed and why.

      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.

      > * Missing support for debug builds and for exceptions have been the biggest trouble that I have seen so far. Have the msvc additions of debug builds in 16.7 been ported back to clang? Did MS’s work improve anything regarding exception support?

      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.

Leave a Reply

Your email address will not be published. Required fields are marked *