6

My library doctest is tested with 200+ builds on travis CI - x86/x64 Debug/Release linux/osx and with a wide range of compilers - from gcc 4.4 to 6 and clang 3.4 to 3.8

All my tests are ran through valgrind and the address sanitizer (also UB sanitizer).

I recently discovered that not all features of ASAN are on by default - for example:

  • check_initialization_order=true
  • detect_stack_use_after_return=true
  • strict_init_order=true

so I enabled them and started getting errors for code like the example below.

int& getStatic() {
    static int data;
    return data;
}

int reg() { return getStatic() = 0; }

static int dummy = reg();

int main() { return getStatic(); }

compiled with g++ (Ubuntu 5.2.1-22ubuntu2) 5.2.1 20151010:

g++ -fsanitize=address -g -fno-omit-frame-pointer -O2 a.cpp

and ran like this:

ASAN_OPTIONS=verbosity=0:strict_string_checks=true:detect_odr_violation=2:check_initialization_order=true:detect_stack_use_after_return=true:strict_init_order=true ./a.out

produces the following error:

==23425==AddressSanitizer CHECK failed: ../../../../src/libsanitizer/asan/asan_globals.cc:255 "((dynamic_init_globals)) != (0)" (0x0, 0x0)
    #0 0x7f699bd699c1  (/usr/lib/x86_64-linux-gnu/libasan.so.2+0xa09c1)
    #1 0x7f699bd6e973 in __sanitizer::CheckFailed(char const*, int, char const*, unsigned long long, unsigned long long) (/usr/lib/x86_64-linux-gnu/libasan.so.2+0xa5973)
    #2 0x7f699bcf2f5c in __asan_before_dynamic_init (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x29f5c)
    #3 0x40075d in __static_initialization_and_destruction_0 /home/onqtam/a.cpp:10
    #4 0x40075d in _GLOBAL__sub_I__Z9getStaticv /home/onqtam/a.cpp:10
    #5 0x40090c in __libc_csu_init (/home/onqtam/a.out+0x40090c)
    #6 0x7f699b91fa4e in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x20a4e)
    #7 0x4007b8 in _start (/home/onqtam/a.out+0x4007b8)

The same is with g++-6 (Ubuntu 6.1.1-3ubuntu11~12.04.1) 6.1.1 20160511

The error disappears when I do one of these 3 things:

  • use clang++ (any version) instead of g++
  • remove the -O2 and use -O0
  • remove the static in front of dummy

Why is this happening? If it is a bug - is it reported? How to avoid it?

EDIT:

@vadikrobot said that even this: static int data = 0; static int dummy = data; int main() { } produces the problem.

EDIT:

the answer of @ead is correct, however I found a way to circumvent the removal of the static dummy and asan doesn't assert anymore:

int& getStatic() {
    static int data = 0;
    return data;
}

int __attribute__((noinline)) reg(int* dummy_ptr) { *dummy_ptr = 5; return getStatic() = 0; }

static int __attribute__((unused)) dummy = reg(&dummy);

int main(int argc, char** argv) { return getStatic(); }
onqtam
  • 4,356
  • 2
  • 28
  • 50
  • 1
    Good question, but it's missing a small self-contained compilable example to really figure it out. – rubenvb Aug 22 '16 at 14:02
  • @rubenvb ok I'll try to come up with one and will link to it at the end of the question. – onqtam Aug 22 '16 at 14:05
  • 1
    It would be fine if it requires your project, but it would be even more helpful (to you and us) if you could reduce it to a bare minimum of code. – rubenvb Aug 22 '16 at 14:05
  • @rubenvb I edited the question - providing a minimal example. – onqtam Aug 23 '16 at 10:59
  • 1
    @onqtam It seems, that you can simplify your example code by delete "return getStatic(); " – vadikrobot Aug 24 '16 at 23:34
  • 1
    @onqtam Your example can be simplified to next "static int data = 0; static int dummy = data; int main() { }" . And this code will still get same error. It seems that it is gcc bug – vadikrobot Aug 24 '16 at 23:38
  • @onqtam I have account in gcc bugzilla. If you want I can report this bug. – vadikrobot Aug 25 '16 at 07:48
  • I try to see assembler code here https://gcc.godbolt.org. And it is identical in g++ and clang. That's why this is gcc bug, and seems that it is not report. Full link: https://gcc.godbolt.org/#compilers:!((compiler:clang380,options:'-O3',source:'int%26+getStatic()+%7B%0A++++static+int+data%3B%0A++++return+data%3B%0A%7D%0A%0Aint+reg()+%7B+return+getStatic()+%3D+0%3B+%7D%0A%0Astatic+int+dummy+%3D+reg()%3B%0A%0Aint+main()+%7B+return+getStatic()%3B+%7D')),filterAsm:(colouriseAsm:!t,commentOnly:!t,directives:!t,labels:!t),version:3 – vadikrobot Aug 25 '16 at 08:22

2 Answers2

5

This is a problem with the usage of asan by gcc. I don't know enough to say that this is a bug (because all I know comes from reverse engineering), but there is at least some room for improvement for gcc. But also asan could be more robust in its handling of this case.

What goes wrong? For my explanation I would like to take a look at the assembler code of vadikrobot's example and later move to your problem:

static int data = 0; 
static int dummy = data; 
int main() { }

First we compile without optimization: g++ -O0 -S (here the whole assembler code)

The most important points are:

-There are two globals, for data and dummy integer static variables:

.local  _ZL4data
.comm   _ZL4data,4,4
.local  _ZL5dummy
.comm   _ZL5dummy,4,4

-In the section .init_array are noted all functions which are called prior to main. In our case this is _GLOBAL__sub_I_main:

.section    .init_array,"aw"
.align 8
.quad   _GLOBAL__sub_I_main

-As expected, the global variables are initialized somewhere in _GLOBAL__sub_I_main:

_GLOBAL__sub_I_main:
    ...
    #in this function is the initialization
    call    _Z41__static_initialization_and_destruction_0ii
    ...

After establishing that, let's take a look at the optimized version:

  1. The static variables are local and can only be accessed from this translation unit, they are not used here, so they are not used at all and thus are optimized.
  2. There is nothing in the section .init_array, because there is nothing to initialize.
  3. strangely, there is still an unused _GLOBAL__sub_I_main function, which does just nothing. I guess it should be optimized away as well.

Now let's take a look at unoptimized version with -fsanitize=address (full assembler code here):

The most important thing: section .init_array has now more functions which are needed for initialization of the sanitizer, in the end it all results in these important functions being called in this order:

call    __asan_init
call    __asan_register_globals
call    __asan_before_dynamic_init
call    __asan_report_store4
call    __asan_after_dynamic_init

What is different for optimized version?

-There are no globals (they are optimized away after all), so __asan_register_globals is not called. This is Ok.

-But strangely the section .init_array contains now again the not needed method _GLOBAL__sub_I_main which does not initialize any globals (they are optimized away), but calls __asan_before_dynamic_init:

_GLOBAL__sub_I_main:
    .cfi_startproc
    subq    $8, %rsp
    .cfi_def_cfa_offset 16
    movl    $.LC0, %edi
    call    __asan_before_dynamic_init
    ...

The problem with this: It seems as if it were not allowed to call __asan_before_dynamic_init without a prior call to __asan_register_globals because some pointer seems to be NULL - your error trace is a failed assertion.


After having established that, let's go to you problem:

  1. static int dummy = reg(); is not used anywhere in this translation unit and thus is optimized away, there are no globals and you will run in the bad case of __asan_before_dynamic_init without __asan_register_globals.

  2. without static, the variable dummy could be used from a different translation unit and thus cannot be optimized away - there are globals and thus __asan_register_globals is called.

  3. why does gcc version prior to 5.0 work? Sadly, they would not optimize unused global static variables away.


What to do?

  1. You should report this problem to gcc.
  2. As a work around, I would do the optimization by hand.

For example:

int& getStatic() {
    static int data=0;
    return data;
}

and remove the static variable dummy and maybe also the function reg(), if it is not used for other purposes.

ead
  • 32,758
  • 6
  • 90
  • 153
  • I didn't understand the last part - the 'doing the optimization by hand' - the static int data gets initialized to 0 as opposed to my code? I tried it and the error is still there – onqtam Aug 26 '16 at 07:59
  • Also this is very weird because in this contrived example - yes the statics get optimized away - but I don't think they get optimized away when I'm using my library - because there the ```reg()``` call has side effects and it is not skipped (or maybe the call to ```reg()``` isn't skipped but the static int that gets initialized by it gets removed...). – onqtam Aug 26 '16 at 08:06
  • @onqtam you should also remove `dummy` (did you do it? See my edit). – ead Aug 26 '16 at 08:09
  • @onqtam the call to `reg()` is not skipped, but its result is not stored in the `dummy` variable, because its values is never accessed. – ead Aug 26 '16 at 08:11
  • oh - removing the dummy is 'the optimization'... well I kinda need it - the whole point is to have ```reg()``` called before ```main()``` – onqtam Aug 26 '16 at 08:12
  • thanks for the answer - you get the bounty - and also see my last edit of my question - I found a way to silence it :) – onqtam Aug 26 '16 at 08:14
1

This should have been fixed in GCC recently: https://gcc.gnu.org/bugzilla/show_bug.cgi?format=multiple&id=77396

yugr
  • 19,769
  • 3
  • 51
  • 96
  • cool - thanks! I tried a few times to report it myself but I couldn't create an account in their bugzilla - it rejected my email or something o_O – onqtam Oct 25 '16 at 10:51
  • AFAIR they had problems with spammers. You can also report to [ASan issue tracker (though they are likely to reject if bug is GCC-specific). – yugr Oct 26 '16 at 08:58