ошибки с g ++ 5 и 6 при использовании адресного дезинфицирующего средства и дополнительных флагов asan для статического порядка инициализации

Моя библиотека doctest протестирован с 200+ сборками на Трэвис CI — x86 / x64 Debug / Release linux / osx и с широким спектром компиляторов — от gcc 4.4 до 6 и clang 3.4 до 3.8

Все мои тесты проходят через valgrind и адресное дезинфицирующее средство (также UB sanitizer).

Недавно я обнаружил, что не все функции ASAN включены по умолчанию, например:

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

поэтому я включил их и начал получать ошибки для кода, как в примере ниже.

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

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

static int dummy = reg();

int main() { return getStatic(); }

составлено с g++ (Ubuntu 5.2.1-22ubuntu2) 5.2.1 20151010:

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

и побежал так:

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

выдает следующую ошибку:

==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)

То же самое с g++-6 (Ubuntu 6.1.1-3ubuntu11~12.04.1) 6.1.1 20160511

Ошибка исчезает, когда я делаю одну из этих 3 вещей:

  • используйте clang ++ (любая версия) вместо g ++
  • удалить -O2 и использовать -O0
  • удалить static перед dummy

Почему это происходит? Если это ошибка — сообщается? Как этого избежать?

РЕДАКТИРОВАТЬ:

@vadikrobot сказал, что даже это: static int data = 0; static int dummy = data; int main() { } производит проблему.

РЕДАКТИРОВАТЬ:

ответ @ead правильный, однако я нашел способ обойти удаление статического манекена, и асан больше не утверждает:

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

6

Решение

Это проблема с использованием asan gcc. Я не знаю достаточно, чтобы сказать, что это ошибка (потому что все, что я знаю, происходит из-за реверс-инжиниринга), но есть кое-что для улучшения gcc. Но также Асан может быть более надежным в рассмотрении этого дела.

Что идет не так? Для моего объяснения я хотел бы взглянуть на ассемблерный код примера vadikrobot, а затем перейти к вашей проблеме:

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

Сначала мы компилируем без оптимизации: g++ -O0 -S (здесь весь код ассемблера)

Наиболее важные моменты:

-Есть два глобала, для data а также dummy целочисленные статические переменные:

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

-В разделе .init_array отмечены все функции, которые вызываются до main, В нашем случае это _GLOBAL__sub_I_main:

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

-Как и ожидалось, глобальные переменные инициализируются где-то в _GLOBAL__sub_I_main:

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

Установив это, давайте посмотрим на оптимизированная версия:

  1. static Переменные являются локальными и доступны только из этого модуля перевода, они здесь не используются, поэтому они не используются вообще и, следовательно, оптимизируются.
  2. В разделе ничего нет .init_arrayпотому что инициализировать нечего.
  3. странно, что до сих пор есть неиспользованный _GLOBAL__sub_I_main функция, которая просто ничего не делает. Я думаю, это должно быть также оптимизировано.

Теперь давайте посмотрим на неоптимизированную версию с -fsanitize=address (полный код ассемблера Вот):

Самое главное: раздел .init_array теперь имеет больше функций, необходимых для инициализации дезинфицирующего средства, в итоге все это приводит к вызову этих важных функций в следующем порядке:

call    __asan_init
call    __asan_register_globals
call    __asan_before_dynamic_init
call    __asan_report_store4
call    __asan_after_dynamic_init

Чем отличается для оптимизированная версия?

-Глобалов нет (они все-таки оптимизированы), поэтому __asan_register_globals не называется. Хорошо.

-Но как ни странно раздел .init_array теперь содержит снова ненужный метод _GLOBAL__sub_I_main который не инициализирует глобальные переменные (они оптимизированы), но вызывает __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
...

Проблема с этим: кажется, что ему не разрешили позвонить __asan_before_dynamic_init без предварительного звонка __asan_register_globals потому что какой-то указатель кажется NULL — ваша ошибка трассировки является ошибочным утверждением.


Установив это, давайте перейдем к проблеме:

  1. static int dummy = reg(); не используется нигде в этом модуле перевода и, таким образом, оптимизируется, глобалов нет, и вы будете работать в плохом случае __asan_before_dynamic_init без __asan_register_globals,

  2. без staticпеременная dummy может быть использован из другой единицы перевода и, следовательно, не может быть оптимизирован вне — есть глобальные и, следовательно, __asan_register_globals называется.

  3. почему работает версия gcc до 5.0? К сожалению, они не будут оптимизировать неиспользованные глобальные static Переменные прочь.


Что делать?

  1. Вы должны сообщить об этой проблеме в GCC.
  2. Как обходной путь, я бы сделал оптимизацию вручную.

Например:

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

и удалите статическую переменную dummy и, возможно, также функция reg(), если он не используется для других целей.

4

Другие решения

Это должно было быть исправлено в GCC недавно: https://gcc.gnu.org/bugzilla/show_bug.cgi?format=multiple&ID = 77396

1

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector