Using gcc's 4.8.0 Address Sanitizer with Qt

One of the cool new features of gcc 4.8 is the built in "Address Sanitizer": a memory error detector for C/C++ that will tell you instantly when you e.g. access already deleted memory. This is actually a Google project from Clang/LLVM, so for LLVM users this might be old stuff, but it wasn't for me :) Since documentation on every day use is still a bit scarce on the web, I'm dumping the gist of how to put it to good use here, especially in the Qt context ...

How does it work?

It basically overwrites malloc and free, and does check the memory before every access (see the project wiki for the details). Apparently it does that in a very efficient manner, since the slow down is only about 2x compared to uninstrumented execution! Who knows, maybe we can enable it for the Qt-Project CI system at one point?

Be warned though that it only works so far on Linux and Mac. No luck for MinGW :(

How to enable it?

Since it is part of the compiler suite enabling it is easy: just add -fsanitize=address -fno-omit-frame-pointer to the compiler calls, and -fsanitize=address to the linker calls. Anyhow, to catch issues where the memory is allocated, de-allocated or accessed by Qt you do not only have to instrument your application, but also Qt. There's a tentative patch for Qt 5.2 which makes this easy:

https://codereview.qt-project.org/#change,43420

It is scheduled for the dev branch (Qt 5.2) because it's a new feature, but you should be fine cherry-picking it to e.g. Qt 5.0. You can then configure Qt with -address-sanitizer, and run qmake CONFIG+=address_sanitizer for your own applications.

If you don't want to cherry-pick, you can also pass the additional command line arguments to qmake by explicitly setting QMAKE_CXXFLAGS, QMAKE_CFLAGS, and QMAKE_LFLAGS manually:

$ qmake QMAKE_CXXFLAGS+="-fsanitize=address -fno-omit-frame-pointer" \
QMAKE_CFLAGS+="-fsanitize=address -fno-omit-frame-pointer" \
QMAKE_LFLAGS+="-fsanitize=address"

How to use it?

Just run your application! If you happen to hit a memory issue it will abort, and show you a stack trace with module names and addresses. You will need a separate tool called asan_symbolize.py to get the symbols, and then maybe c++filt to de-mangle the C++ symbols.

Demo!

$ mkdir addresssanitizertest
$ echo '
#include <QDebug>
int main(int, char *[]) {
const char *str = QString("Evil!").toLocal8Bit().constData();
qDebug() << str;
}
' > addresssanitizertest/main.cpp
$ cd addresssanitizertest && qmake -project && qmake CONFIG+=address_sanitizer

...

$ ./addresssanitizertest 2>&1 | asan_symbolize.py | c++filt
=================================================================
==32195== ERROR: AddressSanitizer: heap-use-after-free on address 0x600c0000bcd8 at pc 0x4016ce bp 0x7fff7ccd86c0 sp 0x7fff7ccd86b8
READ of size 1 at 0x600c0000bcd8 thread T0
#0 0x4016cd in QString::fromUtf8(char const*, int) /home/kkoehne/dev/qt/qt-5.1-gcc-4.8.0-64/qtbase/include/QtCore/../../../../qt-5.1/qtbase/src/corelib/tools/qstring.h:478
#1 0x401b1e in QDebug::operator<<(char const*) /home/kkoehne/dev/qt/qt-5.1-gcc-4.8.0-64/qtbase/include/QtCore/../../../../qt-5.1/qtbase/src/corelib/io/qdebug.h:117
#2 0x401282 in main /tmp/addresssanitizertest/main.cpp:6 (discriminator 1)
#3 0x7fac5c1e3a14 in __libc_start_main ??:?
#4 0x401118 in _start /home/abuild/rpmbuild/BUILD/glibc-2.17/csu/../sysdeps/x86_64/start.S:123
0x600c0000bcd8 is located 24 bytes inside of 64-byte region [0x600c0000bcc0,0x600c0000bd00)
freed by thread T0 here:
#0 0x7fac5eab9c5a in __interceptor_free _asan_rtl_
#1 0x7fac5d353e59 in QArrayData::deallocate(QArrayData*, unsigned long, unsigned long) /home/kkoehne/dev/qt/qt-5.1/qtbase/src/corelib/tools/qarraydata.cpp:125 (discriminator 2)
#2 0x401c21 in QTypedArrayData::deallocate(QArrayData*) /home/kkoehne/dev/qt/qt-5.1-gcc-4.8.0-64/qtbase/include/QtCore/../../../../qt-5.1/qtbase/src/corelib/tools/qarraydata.h:230
#3 0x401630 in QByteArray::~QByteArray() /home/kkoehne/dev/qt/qt-5.1-gcc-4.8.0-64/qtbase/include/QtCore/../../../../qt-5.1/qtbase/src/corelib/tools/qbytearray.h:396 (discriminator 1)
#4 0x401231 in main /tmp/addresssanitizertest/main.cpp:5 (discriminator 1)
#5 0x7fac5c1e3a14 in __libc_start_main ??:?
previously allocated by thread T0 here:
#0 0x7fac5eab9e7f in __interceptor_realloc _asan_rtl_
#1 0x7fac5d35944e in QByteArray::reallocData(unsigned int, QFlags) /home/kkoehne/dev/qt/qt-5.1/qtbase/src/corelib/tools/qbytearray.cpp:1472
#2 0x7fac5d358d05 in QByteArray::resize(int) /home/kkoehne/dev/qt/qt-5.1/qtbase/src/corelib/tools/qbytearray.cpp:1431
#3 0x7fac5d77e452 in QUtf8::convertFromUnicode(QChar const*, int, QTextCodec::ConverterState*) /home/kkoehne/dev/qt/qt-5.1/qtbase/src/corelib/codecs/qutfcodec.cpp:130
#4 0x7fac5d780e91 in QUtf8Codec::convertFromUnicode(QChar const*, int, QTextCodec::ConverterState*) const /home/kkoehne/dev/qt/qt-5.1/qtbase/src/corelib/codecs/qutfcodec.cpp:507
#5 0x7fac5d77a483 in QTextCodec::fromUnicode(QString const&) const /home/kkoehne/dev/qt/qt-5.1/qtbase/src/corelib/codecs/qtextcodec.cpp:807
#6 0x7fac5d48dd26 in QString::toLocal8Bit() const /home/kkoehne/dev/qt/qt-5.1/qtbase/src/corelib/tools/qstring.cpp:4020
#7 0x401215 in main /tmp/addresssanitizertest/main.cpp:5
#8 0x7fac5c1e3a14 in __libc_start_main ??:?
Shadow bytes around the buggy address:
0x0c01ffff9740: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c01ffff9750: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c01ffff9760: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c01ffff9770: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x0c01ffff9780: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x0c01ffff9790: fa fa fa fa fa fa fa fa fd fd fd[fd]fd fd fd fd
0x0c01ffff97a0: fa fa fa fa fd fd fd fd fd fd fd fa fa fa fa fa
0x0c01ffff97b0: 00 00 00 00 00 00 00 fa fa fa fa fa 00 00 00 00
0x0c01ffff97c0: 00 00 00 fa fa fa fa fa 00 00 00 00 00 00 00 fa
0x0c01ffff97d0: fa fa fa fa fd fd fd fd fd fd fd fa fa fa fa fa
0x0c01ffff97e0: 00 00 00 00 00 00 01 fa fa fa fa fa 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Heap righ redzone: fb
Freed Heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack partial redzone: f4
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
ASan internal: fe
==32195== ABORTING

Have fun hunting down memory issues :)

Update: Apparently a stock gcc 4.8.0 has still issues with address-sanitizer: libasan isn't linked automatically, an internal compiler error in qtbase ... I'm personally using a gcc from 4.8 branch.


Blog Topics:

Comments