Thursday, March 20, 2014

ASAN and libraries

Yesterday we saw how to get line numbers in the stacktrace when using ASAN and gcc, today we're going to see how to deal with ASAN and libraries. For that we're going to use these three very simple files

shared.cpp
#include "shared.h"

Foo::Foo()
{
    int *a = 0;
    *a = 33;
}


shared.h
class Foo
{
public:
    Foo();
};


main.cpp
#include "shared.h"

int main(int, char **)
{
    Foo f;
    return 0;
}

We do the initial compilation:
g++ -Wl,--no-undefined -shared -o libshared.so shared.cpp -g3
g++ main.cpp -lshared -L . -g3
LD_LIBRARY_PATH=. ./a.out
and it segfaults :)

Now we want to use ASAN to find out what's wrong, where do we add the -fsanitize=address -fno-omit-frame-pointer? Logic would say that we add it to both places, but actually it's only necessary to add it to the final binary, i.e

g++ -Wl,--no-undefined -shared -o libshared.so shared.cpp -g3
g++ -fno-omit-frame-pointer -fsanitize=address main.cpp -lshared -L . -g3
LD_LIBRARY_PATH=. ./a.out
and we get
==10226== ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000
(pc 0x7f497563f66a sp 0x7fff77cc4310 bp 0x7fff77cc4310 T0)
AddressSanitizer can not provide additional info.
    #0 0x7f497563f669 in Foo::Foo() /home/tsdgeos/test/shared.cpp:6
    #1 0x40074a in main /home/tsdgeos/test/main.cpp:5
    #2 0x7f497529aec4 (/lib/x86_64-linux-gnu/libc.so.6+0x21ec4)
    #3 0x400638 in _start (/home/tsdgeos/test/a.out+0x400638)
SUMMARY: AddressSanitizer: SEGV /home/tsdgeos/test/shared.cpp:6 Foo::Foo()

There you go, no need to add the -fsanitize=address flag to the library compilation :)

Now, until a few minutes ago I did not know this, I actually was adding -fsanitize=address to the library itself; that comes with a few problems that i'll explain how i solved (just for completeness, since the above example shows you don't need it)

My logic was that by looking at main.cpp i knew nothing could be wrong there so i decided i wanted sanitize the library, my first attempt was:

g++ -Wl,--no-undefined -shared -o libshared.so shared.cpp -g3 \
-fno-omit-frame-pointer -fsanitize=address
which gives a linking error
/tmp/cc18Wen3.o: In function `Foo::Foo()':
/home/tsdgeos/test/shared.cpp:6: undefined reference to `__asan_report_store4'
/usr/bin/ld: /tmp/cc18Wen3.o: relocation R_X86_64_PC32 against undefined symbol
`__asan_report_store4' can not be used when making a shared object;
recompile with -fPIC
/usr/bin/ld: final link failed: Bad value

OK, so we are told to recompile with -fPIC, easy peasy

g++ -Wl,--no-undefined -shared -o libshared.so shared.cpp -g3 \
-fno-omit-frame-pointer -fsanitize=address -fPIC
Which still fails to link!
/tmp/ccNkpRZo.o: In function `Foo::Foo()':
/home/tsdgeos/test/shared.cpp:6: undefined reference to `__asan_report_store4'
/tmp/ccNkpRZo.o: In function `_GLOBAL__sub_I_00099_0_shared.cpp':
/home/tsdgeos/test/shared.cpp:7: undefined reference to `__asan_init_v1'
collect2: error: ld returned 1 exit status

OK, so if ASAN symbols are missing, let's just compile libasan in

g++ -Wl,--no-undefined -shared -o libshared.so shared.cpp -g3 \
-fno-omit-frame-pointer -fsanitize=address -fPIC -lasan
Links!

Next since I know nothing is wrong in main.cpp I don't need ASAN in there

g++  main.cpp -lshared -L . -g3
Compiles!

But when run it segfaults :-/ And if you debug it you'll see a stack trace like
#0  0x0000000000000000 in ?? ()
#1  0x00007ffff4894bf1 in ?? () from /usr/lib/x86_64-linux-gnu/libasan.so.0
#2  0x00007ffff4894e32 in ?? () from /usr/lib/x86_64-linux-gnu/libasan.so.0
#3  0x00007ffff4895d9b in __asan_init_v1 () from /usr/lib/x86_64-linux-gnu/libasan.so.0
#4  0x00007ffff7bd8776 in _GLOBAL__sub_I_00099_0_shared.cpp(void) () at shared.cpp:7
#5  0x00007ffff7dea13a in call_init (l=, argc=argc@entry=1, argv=argv@entry=0x7fffffffde18, env=env@entry=0x7fffffffde28) at dl-init.c:78
#6  0x00007ffff7dea223 in call_init (env=, argv=, argc=, l=) at dl-init.c:36
#7  _dl_init (main_map=0x7ffff7ffe1c8, argc=1, argv=0x7fffffffde18, env=0x7fffffffde28) at dl-init.c:126
#8  0x00007ffff7ddb30a in _dl_start_user () from /lib64/ld-linux-x86-64.so.2
#9  0x0000000000000001 in ?? ()
#10 0x00007fffffffe1ca in ?? ()
#11 0x0000000000000000 in ?? ()

So something weird in ASAN thing is going on, let's compile -lasan in also
g++  main.cpp -lshared -L . -g3 -lasan

Same crash :-/

Ok, so then you think, well, let's just add the fsanitize in. And then it works, but as demonstrated a few lines ago, the whole thing of adding -fsanitize=address and -lasan to the library was unneeded since all that was required was just adding -fsanitize=address to the binary when it's compiled and that's it :)

6 comments:

Anonymous said...

Hey Albert,

what about plugins? They are shared libraries, so do I really only need to compile the main executable with asan which then loads the plugins as-is?

KDevelop e.g. is a very small executable which loads tons of plugins and I'd love to run the sanitizers on our codebase somehow.

If you answer this, please notify me or write a new blog entry. Thanks, much appreciated!

SexyMimi said...

It doesn't help with memory leaks, and I find it doesn't provide as many details as valgrind does.

So we still need valgrind. But considering it doesn't strike the performance down so much, it's definitely a good tool for allowing beta-tester to provide better feedback.

Albert Astals Cid said...

Note the contents of this blogpost are not totally correct. In some cases ASAN is needed also in the library, see http://tsdgeos.blogspot.com/2014/03/asan-and-libraries-2nd-part.html for more information.

Sergey "Shnatsel" Davidoff said...

Here's what Asan FAQ has to say on the topic:

Q: When I link my shared library with -fsanitize=address, it fails due to some undefined ASan symbols (e.g. asan_init_v4)?
A: Most probably you link with -Wl,-z,defs or -Wl,--no-undefined. These flags don't work with ASan.

Source: https://code.google.com/p/address-sanitizer/wiki/AddressSanitizer

So looks like "-Wl,--no-undefined" was the culprit.

Unknown said...

Revisiting this 4 years later. The only correct way to sanitize code is to compile and link it with -fsanitize=address (if it breaks for you, please report bug at https://github.com/google/sanitizers). In particular explicit linking with -lasan is wrong.

As a side note, if you need to run sanitize library with unsanitized executable, do
Q: I've built my shared library with ASan. Can I run it with unsanitized executable?
A: Yes! You'll need to build your library with dynamic version of ASan and then run
executable with LD_PRELOAD=path/to/asan/runtime/lib.
(from FAQ at https://github.com/google/sanitizers/wiki/AddressSanitizer).

Anonymous said...

I get 'cannot find -lasan' when compiling using -fsanitize=address.
NDK r10, gcc 4.8.
Can somebody help?