Wednesday, March 19, 2014

ASAN and gcc: How to get line numbers in the stacktrace

ASAN or Address Sanitizer is a nice project by Google that is a memory error detector for C/C++. According to their web it finds:
  • Use after free (dangling pointer dereference)
  • buffer overflow
  • Stack buffer overflow
  • Global buffer overflow
  • Use after return
  • Initialization order bugs

Also it's much faster than valgrind :-) Unfortunately it's not as easy to use as valgrind since it requires a rebuild of your code.

The basic instructions to use ASAN are:
Use the -fsanitize=address switch
together with the suggestion of
To get nicer stack traces in error messages add -fno-omit-frame-pointer.

As a simple example let's see what happens when we run
int main(int, char **)
{
    int a[3];
    a[3] = 4;
    return 0;
}
compiled with
g++ -fno-omit-frame-pointer -fsanitize=address main.cpp

The result:
==8403== ERROR: AddressSanitizer: stack-buffer-overflow
on address 0x7fffbe7c7d8c at pc 0x400779
bp 0x7fffbe7c7d40 sp 0x7fffbe7c7d38
WRITE of size 4 at 0x7fffbe7c7d8c thread T0
    #0 0x400778 (/home/tsdgeos_work/test/a.out+0x400778)
    #1 0x7f2f5cfa6ec4 (/lib/x86_64-linux-gnu/libc-2.19.so+0x21ec4)
    #2 0x400638 (/home/tsdgeos_work/test/a.out+0x400638)
Address 0x7fffbe7c7d8c is located at offset 44 in frame 
of T0's stack: This frame has 1 object(s): [32, 44) 'a'

In this case it's pretty obvious what's wrong with just looking at the code and ASAN is even telling us it is a bad write just after the 'a' object, but note that we are not getting the line number of where the error happens. You'll say, that's why you didn't use the -g switch! So let's see what do we get after compiling with
g++ -g3 -fno-omit-frame-pointer -fsanitize=address main.cpp

The result:
==8467== ERROR: AddressSanitizer: stack-buffer-overflow
on address 0x7fff59ccaa2c at pc 0x400779
bp 0x7fff59cca9e0 sp 0x7fff59cca9d8
WRITE of size 4 at 0x7fff59ccaa2c thread T0
    #0 0x400778 (/home/tsdgeos_work/test/a.out+0x400778)
    #1 0x7f6f75601ec4 (/lib/x86_64-linux-gnu/libc-2.19.so+0x21ec4)
    #2 0x400638 (/home/tsdgeos_work/test/a.out+0x400638)
Address 0x7fff59ccaa2c is located at offset 44 in frame 
of T0's stack: This frame has 1 object(s): [32, 44) 'a'

Same thing :-/

Here is where people may give up or think to switch to clang and try there, but since clang is sometimes more picky with the code (not bad per se) it may be a lot of work to get your code to compile under clang, if you keep digging you'll read some news about GCC not having a ASAN symbolizer [yet], but clang has one and you can still use it with GCC, so if you
export ASAN_SYMBOLIZER_PATH=/usr/bin/llvm-symbolizer-3.4
export ASAN_OPTIONS=symbolize=1
and then run the binary again (the one we just compiled with -g3)

The result:
WRITE of size 4 at 0x7fff4f5f0ebc thread T0
    #0 0x400778 in main /home/tsdgeos_work/test/main.cpp:5
    #1 0x7f3d4dce2ec4 (/lib/x86_64-linux-gnu/libc.so.6+0x21ec4)
    #2 0x400638 in _start (/home/tsdgeos_work/test/a.out+0x400638)
Address 0x7fff4f5f0ebc is located at offset 44 in frame 
of T0's stack: This frame has 1 object(s): [32, 44) 'a'

And now we have line numbers :-)

4 comments:

Manuel said...

Hopefully this is fixed in GCC 4.9, which uses libbacktrace as a replacement for llvm-symbolizer: http://gcc.gnu.org/ml/gcc-patches/2013-11/msg02055.html

so there is no need to use an external program. You probably need to use '-g', since libbacktrace relies on debug info, but

export ASAN_OPTIONS=symbolize=1

should be the default in GCC 4.9.

Anonymous said...

yeeha!!!

Anonymous said...

Thank you! This was really helpful, I was about to move to Clang :D

Iulian said...

Amazing! Thank you very much.