onq*_*tam 6 c++ g++ clang address-sanitizer
我的库doctest在travis CI上进行了200多次构建测试- x86/x64 Debug/Release linux/osx以及各种编译器 - 从gcc 4.4到6以及clang 3.4到3.8
我的所有测试都是通过valgrind和地址消毒剂(也是UB消毒剂)进行的.
我最近发现并非所有ASAN功能都默认开启 - 例如:
check_initialization_order=truedetect_stack_use_after_return=truestrict_init_order=true所以我启用了它们并开始获取代码错误,如下例所示.
int& getStatic() {
static int data;
return data;
}
int reg() { return getStatic() = 0; }
static int dummy = reg();
int main() { return getStatic(); }
Run Code Online (Sandbox Code Playgroud)
编译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)
Run Code Online (Sandbox Code Playgroud)
同样的是 g++-6 (Ubuntu 6.1.1-3ubuntu11~12.04.1) 6.1.1 20160511
当我执行以下三项操作之一时,错误消失:
-O2并使用-O0static前面的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(); }
Run Code Online (Sandbox Code Playgroud)
这是gcc 使用asan 的问题。我知道的不足以说这是一个错误(因为我所知道的一切都来自逆向工程),但是 gcc 至少还有一些改进的空间。但阿三在处理此案时可能会更加稳健。
出了什么问题?为了我的解释,我想看一下 vadikrobot 示例的汇编代码,然后再转到您的问题:
static int data = 0;
static int dummy = data;
int main() { }
Run Code Online (Sandbox Code Playgroud)
首先我们不优化编译:(g++ -O0 -S 这里是整个汇编代码)
最重要的几点是:
- 有两个全局变量,fordata和dummyinteger 静态变量:
.local _ZL4data
.comm _ZL4data,4,4
.local _ZL5dummy
.comm _ZL5dummy,4,4
Run Code Online (Sandbox Code Playgroud)
- 在该部分.init_array中注明了在 之前调用的所有函数main。在我们的例子中,这是_GLOBAL__sub_I_main:
.section .init_array,"aw"
.align 8
.quad _GLOBAL__sub_I_main
Run Code Online (Sandbox Code Playgroud)
- 正如预期的那样,全局变量在_GLOBAL__sub_I_main以下位置初始化:
_GLOBAL__sub_I_main:
...
#in this function is the initialization
call _Z41__static_initialization_and_destruction_0ii
...
Run Code Online (Sandbox Code Playgroud)
确定后,让我们来看看优化的版本:
static变量是局部的,只能从这个转换单元访问,这里不再使用,所以他们根本不使用,因此进行了优化。.init_array,因为没有什么要初始化的。_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
Run Code Online (Sandbox Code Playgroud)
优化版有什么不同?
- 没有全局变量(毕竟它们被优化掉了),所以__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
...
Run Code Online (Sandbox Code Playgroud)
问题在于:似乎不允许在__asan_before_dynamic_init没有事先调用的情况下调用,__asan_register_globals因为某些指针似乎是NULL- 您的错误跟踪是失败的断言。
确定之后,让我们来解决你的问题:
static int dummy = reg();在这个翻译单元的任何地方都没有使用,因此被优化掉了,没有全局变量,你将在__asan_before_dynamic_init没有__asan_register_globals.
没有static,变量dummy可以从不同的翻译单元使用,因此无法优化掉 - 有全局变量,因此 __asan_register_globals被调用。
为什么 5.0 之前的 gcc 版本有效?可悲的是,他们不会优化未使用的全局static变量。
该怎么办?
例如:
int& getStatic() {
static int data=0;
return data;
}
Run Code Online (Sandbox Code Playgroud)
并删除静态变量dummy,也可能删除函数reg(),如果它不用于其他目的。