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

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

Все мои тесты проходят через valgrind и средство очистки адресов (также средство очистки UB).

Недавно я обнаружил, что не все функции 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

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

  • используйте 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(); }

person onqtam    schedule 22.08.2016    source источник
comment
Хороший вопрос, но в нем отсутствует небольшой автономный компилируемый пример, чтобы действительно понять это.   -  person rubenvb    schedule 22.08.2016
comment
@rubenvb хорошо, я попытаюсь придумать один и дам ссылку на него в конце вопроса.   -  person onqtam    schedule 22.08.2016
comment
Было бы хорошо, если бы для этого требовался ваш проект, но было бы еще полезнее (для вас и для нас), если бы вы могли сократить его до минимума кода.   -  person rubenvb    schedule 22.08.2016
comment
@rubenvb Я отредактировал вопрос, предоставив минимальный пример.   -  person onqtam    schedule 23.08.2016
comment
@onqtam Кажется, вы можете упростить код примера, удалив return getStatic();   -  person vadikrobot    schedule 25.08.2016
comment
@onqtam Ваш пример можно упростить до следующего статического int data = 0; статическое целое фиктивное = данные; int main () { } . И этот код все равно будет получать ту же ошибку. Кажется, это ошибка gcc   -  person vadikrobot    schedule 25.08.2016
comment
@onqtam У меня есть аккаунт в gcc bugzilla. Если хотите, я могу сообщить об этой ошибке.   -  person vadikrobot    schedule 25.08.2016
comment
Я пытаюсь увидеть ассемблерный код здесь gcc.godbolt.org. И он идентичен в g++ и clang. Вот почему это ошибка gcc, и кажется, что это не отчет. Полная ссылка: gcc.godbolt.org/   -  person vadikrobot    schedule 25.08.2016


Ответы (2)


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

Что пойдет не так? Для моего объяснения я хотел бы взглянуть на ассемблерный код примера 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(), если она не используется для других целей.

person ead    schedule 25.08.2016
comment
Я не понял последнюю часть - «выполнение оптимизации вручную» - статические данные int инициализируются до 0, в отличие от моего кода? попробовал, ошибка осталась - person onqtam; 26.08.2016
comment
Также это очень странно, потому что в этом надуманном примере — да, статика оптимизируется — но я не думаю, что она оптимизируется, когда я использую свою библиотеку — потому что там вызов reg() имеет побочные эффекты, и он не пропускается ( или, может быть, вызов reg() не пропускается, но статический int, который инициализируется им, удаляется...). - person onqtam; 26.08.2016
comment
@onqtam вам также следует удалить dummy (вы это сделали? Смотрите мое редактирование). - person ead; 26.08.2016
comment
@onqtam вызов reg() не пропускается, но его результат не сохраняется в переменной dummy, поскольку к его значениям никогда не обращаются. - person ead; 26.08.2016
comment
о, удаление манекена - это «оптимизация» ... ну, мне это нужно - весь смысл в том, чтобы reg() вызывался до main() - person onqtam; 26.08.2016
comment
спасибо за ответ - вы получаете награду - а также посмотрите мое последнее редактирование моего вопроса - я нашел способ заставить его замолчать :) - person onqtam; 26.08.2016

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

person yugr    schedule 24.10.2016
comment
хорошо, спасибо! Я несколько раз пытался сообщить об этом сам, но я не мог создать учетную запись в их багзилле - мой адрес электронной почты был отклонен или что-то в этом роде о_О - person onqtam; 25.10.2016
comment
AFAIR у них были проблемы со спамерами. Вы также можете сообщить об этом в [систему отслеживания проблем ASan (хотя они, скорее всего, откажут, если ошибка связана с GCC). - person yugr; 26.10.2016