使用地址清理程序和附加 asan 标志进行静态初始化顺序时出现 g++ 5 和 6 的错误

Posted

技术标签:

【中文标题】使用地址清理程序和附加 asan 标志进行静态初始化顺序时出现 g++ 5 和 6 的错误【英文标题】:errors with g++ 5 and 6 when using address sanitizer and additional asan flags for static initialization order 【发布时间】:2016-08-22 13:42:33 【问题描述】:

我的库 doctest 在 travis CI - x86/x64 Debug/Release linux/osx 上使用 200 多个版本进行了测试,并使用了广泛的编译器 - 从 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也是如此

当我执行以下 3 件事之一时,错误就会消失:

使用 clang++(任何版本)而不是 g++ 删除-O2 并使用-O0 去掉static前面的dummy

为什么会这样?如果它是一个错误 - 是否被报告?如何避免?

编辑:

@vadikrobot 说即使这样:static int data = 0; static int dummy = data; int main() 也会产生问题。

编辑:

@ead 的答案是正确的,但是我找到了一种方法来规避删除静态虚拟对象并且 asan 不再断言:

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

【问题讨论】:

好问题,但它缺少一个小的独立可编译示例来真正弄清楚。 @rubenvb 好的,我会尝试想出一个并在问题结束时链接到它。 如果它需要您的项目会很好,但如果您可以将其减少到最少的代码,它会更有帮助(对您和我们)。 @onqtam 看来,您可以通过删除“return getStatic();”来简化示例代码 @onqtam 您的示例可以简化为下一个 "static int data = 0; static int dummy = data; int main() " 。而且这段代码仍然会得到同样的错误。好像是gcc的bug 【参考方案1】:

这是 gcc 使用 asan 的问题。我还不足以说这是一个错误(因为我所知道的都来自逆向工程),但 gcc 至少还有一些改进的空间。但 asan 在处理这种情况时可能会更加稳健。

出了什么问题?对于我的解释,我想看看 vadikrobot 示例的汇编代码,然后再解决您的问题:

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

首先我们不优化编译:g++ -O0 -S(here the whole assembler code)

最重要的几点是:

-有两个全局变量,用于datadummy 整数静态变量:

.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
    ...

确定之后,我们来看看optimized version:

    static 变量是本地变量,只能从该翻译单元访问,此处未使用它们,因此根本未使用,因此进行了优化。 .init_array 部分没有任何内容,因为没有要初始化的内容。 奇怪的是,还有一个未使用的_GLOBAL__sub_I_main 函数,它什么也没做。我想它也应该被优化掉。

现在让我们看看带有-fsanitize=address的未优化版本(完整的汇编代码here):

最重要的是:.init_array 部分现在有更多初始化消毒剂所需的函数,最终这一切都会导致这些重要函数按此顺序调用:

call    __asan_init
call    __asan_register_globals
call    __asan_before_dynamic_init
call    __asan_report_store4
call    __asan_after_dynamic_init

optimized version 有什么不同?

-没有全局变量(毕竟它们被优化掉了),所以__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_register_globals 的情况下调用__asan_before_dynamic_init,因为某些指针似乎是NULL - 您的错误跟踪是一个失败的断言。


确定之后,让我们来解决你的问题:

    static int dummy = reg(); 没有在这个翻译单元的任何地方使用,因此被优化掉了,没有全局变量,你将在没有__asan_register_globals 的情况下运行__asan_before_dynamic_init 的坏情况。

    李>

    如果没有 static,变量 dummy 可以从不同的翻译单元使用,因此无法优化 - 存在全局变量,因此会调用 __asan_register_globals

    为什么 5.0 之前的 gcc 版本可以工作?遗憾的是,他们不会优化未使用的全局 static 变量。


怎么办?

    您应该将此问题报告给 gcc。 作为一种变通方法,我会手动进行优化。

例如:

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

并删除静态变量dummy,如果不用于其他目的,可能还删除函数reg()

【讨论】:

我不明白最后一部分 - '手动优化' - 静态 int 数据被初始化为 0 而不是我的代码?我试过了,错误仍然存​​在 这也很奇怪,因为在这个人为的例子中 - 是的,静态得到优化 - 但我认为当我使用我的库时它们不会被优化 - 因为那里有 reg() call 有副作用并且没有被跳过(或者可能没有跳过对reg() 的调用,但被它初始化的静态 int 被删除了......)。 @onqtam 你也应该删除dummy(你做了吗?看我的编辑)。 @onqtam 不会跳过对reg() 的调用,但其结果不会存储在dummy 变量中,因为它的值永远不会被访问。 哦 - 删除假人是“优化”......好吧我有点需要它 - 重点是在main()之前调用reg()【参考方案2】:

最近应该在 GCC 中修复了这个问题:https://gcc.gnu.org/bugzilla/show_bug.cgi?format=multiple&id=77396

【讨论】:

酷 - 谢谢!我尝试了几次自己报告,但我无法在他们的 bugzilla 中创建帐户 - 它拒绝了我的电子邮件或其他东西 o_O AFAIR 他们遇到了垃圾邮件发送者的问题。您还可以向 [ASan 问题跟踪器报告(尽管如果错误是 GCC 特定的,他们可能会拒绝)。

以上是关于使用地址清理程序和附加 asan 标志进行静态初始化顺序时出现 g++ 5 和 6 的错误的主要内容,如果未能解决你的问题,请参考以下文章

gcc地址清理程序核心转储出错

nginx下使用asan和valgrind两个静态检查工具

java基础(初始化和清理)

Java 初始化与清理

Java 初始化与清理

静态链接库asan与gcc 4.8