使用地址清理器和静态初始化顺序的附加 asan 标志时,g++ 5 和 6 出现错误
errors with g++ 5 and 6 when using address sanitizer and additional asan flags for static initialization order
我的图书馆 doctest is tested with 200+ builds on travis CI - x86/x64 Debug/Release linux/osx 和广泛的 运行ge 编译器 - 从 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
- 把
dummy
前面的static
去掉
为什么会这样?如果是错误 - 是否已报告?如何避免?
编辑:
@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(); }
这是 gcc 使用 asan 的问题。我不知道说这是一个错误(因为我所知道的一切都来自逆向工程),但是 gcc 至少有一些改进的空间。但 asan 在处理这种情况时也可以更加稳健。
出了什么问题?对于我的解释,我想看一下 vadikrobot 示例的汇编代码,然后再转到您的问题:
static int data = 0;
static int dummy = data;
int main() { }
首先我们编译没有优化:g++ -O0 -S
(here the whole assembler code)
最重要的几点是:
-有两个全局变量,分别用于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
...
建立之后,让我们来看看 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 , %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();
没有在这个翻译单元的任何地方使用,因此被优化掉了,没有全局变量,在 [=32= 的坏情况下你会 运行 ] 没有 __asan_register_globals
.
没有 static
,变量 dummy
可以从不同的翻译单元使用,因此无法优化 - 有全局变量,因此 __asan_register_globals
是打电话。
为什么 5.0 之前的 gcc 版本可以工作?可悲的是,他们不会优化未使用的全局 static
变量。
怎么办?
- 你应该向 gcc 报告这个问题。
- 作为解决方法,我会手动进行优化。
例如:
int& getStatic() {
static int data=0;
return data;
}
并删除静态变量 dummy
,如果不用于其他目的,也可能删除函数 reg()
。
这应该最近在 GCC 中得到修复:https://gcc.gnu.org/bugzilla/show_bug.cgi?format=multiple&id=77396
我的图书馆 doctest is tested with 200+ builds on travis CI - x86/x64 Debug/Release linux/osx 和广泛的 运行ge 编译器 - 从 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
- 把
dummy
前面的static
去掉
为什么会这样?如果是错误 - 是否已报告?如何避免?
编辑:
@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(); }
这是 gcc 使用 asan 的问题。我不知道说这是一个错误(因为我所知道的一切都来自逆向工程),但是 gcc 至少有一些改进的空间。但 asan 在处理这种情况时也可以更加稳健。
出了什么问题?对于我的解释,我想看一下 vadikrobot 示例的汇编代码,然后再转到您的问题:
static int data = 0;
static int dummy = data;
int main() { }
首先我们编译没有优化:g++ -O0 -S
(here the whole assembler code)
最重要的几点是:
-有两个全局变量,分别用于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
...
建立之后,让我们来看看 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 , %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();
没有在这个翻译单元的任何地方使用,因此被优化掉了,没有全局变量,在 [=32= 的坏情况下你会 运行 ] 没有__asan_register_globals
.没有
static
,变量dummy
可以从不同的翻译单元使用,因此无法优化 - 有全局变量,因此__asan_register_globals
是打电话。为什么 5.0 之前的 gcc 版本可以工作?可悲的是,他们不会优化未使用的全局
static
变量。
怎么办?
- 你应该向 gcc 报告这个问题。
- 作为解决方法,我会手动进行优化。
例如:
int& getStatic() {
static int data=0;
return data;
}
并删除静态变量 dummy
,如果不用于其他目的,也可能删除函数 reg()
。
这应该最近在 GCC 中得到修复:https://gcc.gnu.org/bugzilla/show_bug.cgi?format=multiple&id=77396