Some time ago, I wrote about how to build C++ projects with ASAN on Windows. Now, if you happen to deal with Qt projects you may want to take this one step further and sanitize Qt itself.
Why bother with a sanitized Qt build?
Let's have a closer look on why having a sanitized Qt build around is a good idea.
If you're already patching Qt for your own needs, sanitizing Qt is the only way to check for bugs on your code. This is the reason why we use sanitizers in the first place.
Otherwise, you may argue that Qt is "good" or "safe" or "bug free" enough. This, in principle, is not a safe assumption. Choosing to take your chances may still leave some of your bugs on the table. Let's see an example:
When we build this code with the address sanitizer and run it, we get this output:
Great, all good! Or is it? Let's try to run the same code with a sanitized Qt. This is the output:
This is because, in the call to connect
on line 15, we are copying a pointer to the internal buffer of a QByteArray
, which will be already gone when the timer is started. Note that this is not inherently a Qt bug. Additionally, the documentation warns us that the lifetime of that pointer is bound to the one of the byte array. Since all of the allocations are happening inside Qt without sanitizing Qt itself, this bug will go undetected.
Building Qt with the Address Sanitizer
To generate a sanitized Qt build, we are going to use clang. Clang has been providing the Address Sanitizer on Windows for a while. This means we will only be able to create a release build for Qt, as clang on windows doesn't support debug builds with the Address Sanitizer. If you have a recent version of Visual Studio, you can choose to do the same with the cl compiler in a similar way.
Qt comes with bundled mkspecs, to build with either clang or clang-cl. We will be creating our own sanitized clang-cl mkspec based on the original clang-cl one. To do so, duplicate the folder qt5/qtbase/mkspecs/win32-clang-msvc
to qt5/qtbase/mkspecs/win32-clang-msvc-with-address-sanitizer.
Next, replace the content of qmake.conf
with the following:
Now you can start building by specifying the mkspec with -platform win32-clang-msvc-with-address-sanitizer
. Also, make sure you pass -no-pch
to the configure script. Otherwise, the build will fail.
Note that, since rcc and moc are built with ASAN as well. They may (and will) generate some ASAN warnings at build time. So make sure you have ASAN_OPTIONS=halt_on_error=0
in your build environment. Do the same for building all your projects using this sanitized Qt version.
Some final touches
If you try launching an application built against this sanitized Qt build, it will raise an exception at startup. The error will look like this:
What is this about? Let's have a look at the disassembled code around the affected area:
What's going on is that the cpuid
instruction, when invoked with eax=0
(see xor eax, eax
on address 00007FFA2DD827F4
), will write a 12 bytes string spread on the three registries ebx
, edx
, ecx
- in that order - identifying the manufacturer of the CPU.
In my case, for instance, cpuid returns 'GenuineIntel'
, which translates, adjusting for endianness, to the following values for rbx
, rdx
, rdx:
A few instructions later, where the application breaks, the instruction on 0x00007FFA2DD82807
is trying to write on ebx + 0x40
, whose value is exactly 0x00000000756E6587
(the address mentioned in the error message).
This means the registry wasn't backed up and restored by the compiler around the cpuid
instruction. This is a known clang bug that happens when a heap allocation occurs right after a cpuid
instruction with ASAN enabled. Or, more in general, it is a rare bug that results in ebx
being mismanaged by the compiler (see this and this).
So what can we do about it?
For lack of better options, a quick workaround for this is to replace all the calls to cpuid
(see below) with their return values. To do so, just run all the calls to __cpuid
on a small program. Then, copy the values on the Qt source code. The following is a list of all the occurrences:
Conclusion
The Address Sanitizer is a helpful tool for tracking and solving memory bugs. However, limiting ourselves to sanitize only our own code is not enough. Libraries can prevent bad API usage only up to a certain point. There are still cases where damage can be done undetected. Sanitizing consumed libraries will greatly increase the surface for automatic bug detection. Qt's open source nature allows us to leverage this opportunity.
If you'd like to learn more about the tooling available for C++ developers working with Windows, 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.
1 Comment
28 - Jan - 2021
Leo Schubert
Great post Allessandro ! Any hint where to look at if the clang issue with the CPUID is fixed ? Also I wonder why the precompiled headers are not working under Windows, is this a windows thingy ? Normally clang is very good at precompiled headers. Regards, Leo