onqtam onqtam - 1 month ago 9
C++ Question

errors with g++ 5 and 6 when using address sanitizer and additional asan flags for static initialization order

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(); }

ead ead
Answer

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.

Comments