文章目录
- 一、 简介
- 二、AddressSanitizer 的使用
-
- 使用方法
- 1.使用添加编译选项的方式使用ASan
- 2.使用CMake添加编译选项
- 三、测试
-
-
- 不添加Asan选项,不会有任何输出
- 添加-fsanitize=address选项:
- 简单修改程序,释放申请的内存空间:
-
- 四、不能在gdb、lldb等调试器运行带有AddressSanitizer的程序
- 参考
一、 简介
所谓工欲善其事,必先利其器。在linux系统下进行C++的开发时,出现内存访问错误是非常常见的事情,且大多数情况下gcc和gdb等编译器和调试器都不会提示我们发生内存错误,通常会导致严重的后果且难以定位问题。
AddressSanitizer (ASan)是一个性能非常好的 C/C++ 内存错误探测工具。它由编译器的插桩模块(目前,LLVM 通过)和替换了 malloc 函数的运行时库组成。这个工具可以探测如下这些类型的错误:
- 对堆,栈和全局内存的访问越界(堆缓冲区溢出,栈缓冲区溢出,和全局缓冲区溢出)
- UAP(Use-after-free,悬挂指针的解引用,或者说野指针)
- Use-after-return(无效的栈上内存,运行时标记
- ASAN_OPTIONS=detect_stack_use_after_return=1)
- Use-After-Scope(作用域外访问,clang 标记 -fsanitize- address-use-after-scope )
- 内存的重复释放
- 初始化顺序的 bug
- 内存泄漏
这个工具非常快。通常情况下,内存问题探测这类调试工具的引入,会导致原有应用程序运行性能的大幅下降,比如大名鼎鼎的 valgrind 据说会导致应用程序性能下降到正常情况的十几分之一,但引入 AddressSanitizer 只会减慢运行速度的一半。
使用 AddressSanitizer 可以在程序发生内存问题的时候及时检查出来,精准定位发生内存问题的位置,大大提高我们debug的效率。
二、AddressSanitizer 的使用
自 LLVM 的版本 3.1 和 GCC 的版本 4.8 开始,AddressSanitizer 就是它们的一部分。因此只要版本正确,无需额外链接库或者编译源码,只需编译的时候加上选项即可使用。
使用方法
1.使用添加编译选项的方式使用ASan
- 使用
-fsanitize=address
选项编译和链接你的程序。
例如:
gcc -fsanitize=address -O1 -g -o main main.cpp
- 如果有其他需要,加上-fno-omit-frame-pointer等其他编译选项
gcc -fsanitize=address -fno-omit-frame-pointer -O1 -g -o main main.cpp
可选择-O1或者更高的优化级别编译
2.使用CMake添加编译选项
在CMakeLists文件中添加如下行
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fsanitize=address")
对于AddressSanitizer还有很多编译选项,这里不一一列举,若有需要自行去官方文档查询。
三、测试
以下程序是有两个很明显的内存错误:
#include <iostream>
using namespace std;int main() {char *c = new char(10);c[1] = 'a';return 0;
}
不添加Asan选项,不会有任何输出
ubuntu@VM-24-14-ubuntu:~/CppTest$ g++ -O1 -g -o test test.cpp
ubuntu@VM-24-14-ubuntu:~/CppTest$ ./test
ubuntu@VM-24-14-ubuntu:~/CppTest$
编译器的缄默不语是导致程序员痛苦整宿的元凶之一。
添加-fsanitize=address选项:
ubuntu@VM-24-14-ubuntu:~/CppTest$ g++ -fsanitize=address -O1 -g -o test test.cpp
ubuntu@VM-24-14-ubuntu:~/CppTest$ ./test
==175312==AddressSanitizer: libc interceptors initialized
|| `[0x10007fff8000, 0x7fffffffffff]` || HighMem ||
|| `[0x02008fff7000, 0x10007fff7fff]` || HighShadow ||
|| `[0x00008fff7000, 0x02008fff6fff]` || ShadowGap ||
|| `[0x00007fff8000, 0x00008fff6fff]` || LowShadow ||
|| `[0x000000000000, 0x00007fff7fff]` || LowMem ||
MemToShadow(shadow): 0x00008fff7000 0x000091ff6dff 0x004091ff6e00 0x02008fff6fff
redzone=16
max_redzone=2048
quarantine_size_mb=256M
thread_local_quarantine_size_kb=1024K
malloc_context_size=30
SHADOW_SCALE: 3
SHADOW_GRANULARITY: 8
SHADOW_OFFSET: 0x7fff8000
==175312==Installed the sigaction for signal 11
==175312==Installed the sigaction for signal 7
==175312==Installed the sigaction for signal 8
==175312==T0: stack [0x7ffc3e50a000,0x7ffc3ed0a000) size 0x800000; local=0x7ffc3ed08294
==175312==AddressSanitizer Init done
==175313==Processing thread 175312.
==175313==Stack at 0x7ffc3e50a000-0x7ffc3ed0a000 (SP = 0x7ffc3ed07e88).
==175313==TLS at 0x7fba89ae14c0-0x7fba89ae2580.=================================================================
==175312==ERROR: LeakSanitizer: detected memory leaksDirect leak of 1 byte(s) in 1 object(s) allocated from:#0 0x7fba8a165587 in operator new(unsigned long) ../../../../src/libsanitizer/asan/asan_new_delete.cc:104#1 0x55840905425a in main /home/ubuntu/CppTest/test.cpp:5SUMMARY: AddressSanitizer: 1 byte(s) leaked in 1 allocation(s).
ubuntu@VM-24-14-ubuntu:~/CppTest$
提示检测到第五行出现内存泄漏问题。原来是源程序new了一个对象,并没有delete。
简单修改程序,释放申请的内存空间:
#include <iostream>
using namespace std;int main() {char *c = new char(10);c[1] = 'a';delete c;return 0;
}
再次编译运行:
ubuntu@VM-24-14-ubuntu:~/CppTest$ g++ -fsanitize=address -O1 -g -o test test.cpp
ubuntu@VM-24-14-ubuntu:~/CppTest$ ./test
==175937==AddressSanitizer: libc interceptors initialized
|| `[0x10007fff8000, 0x7fffffffffff]` || HighMem ||
|| `[0x02008fff7000, 0x10007fff7fff]` || HighShadow ||
|| `[0x00008fff7000, 0x02008fff6fff]` || ShadowGap ||
|| `[0x00007fff8000, 0x00008fff6fff]` || LowShadow ||
|| `[0x000000000000, 0x00007fff7fff]` || LowMem ||
MemToShadow(shadow): 0x00008fff7000 0x000091ff6dff 0x004091ff6e00 0x02008fff6fff
redzone=16
max_redzone=2048
quarantine_size_mb=256M
thread_local_quarantine_size_kb=1024K
malloc_context_size=30
SHADOW_SCALE: 3
SHADOW_GRANULARITY: 8
SHADOW_OFFSET: 0x7fff8000
==175937==Installed the sigaction for signal 11
==175937==Installed the sigaction for signal 7
==175937==Installed the sigaction for signal 8
==175937==T0: stack [0x7ffc9bbc7000,0x7ffc9c3c7000) size 0x800000; local=0x7ffc9c3c5014
==175937==AddressSanitizer Init done
=================================================================
==175937==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000011 at pc 0x55ebd66cc2de bp 0x7ffc9c3c4ff0 sp 0x7ffc9c3c4fe0
WRITE of size 1 at 0x602000000011 thread T0#0 0x55ebd66cc2dd in main /home/ubuntu/CppTest/test.cpp:6#1 0x7fa58b097082 in __libc_start_main ../csu/libc-start.c:308#2 0x55ebd66cc1cd in _start (/home/ubuntu/CppTest/test+0x11cd)0x602000000011 is located 0 bytes to the right of 1-byte region [0x602000000010,0x602000000011)
allocated by thread T0 here:#0 0x7fa58b6c0587 in operator new(unsigned long) ../../../../src/libsanitizer/asan/asan_new_delete.cc:104#1 0x55ebd66cc29a in main /home/ubuntu/CppTest/test.cpp:5SUMMARY: AddressSanitizer: heap-buffer-overflow /home/ubuntu/CppTest/test.cpp:6 in main
Shadow bytes around the buggy address:0x0c047fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 000x0c047fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 000x0c047fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 000x0c047fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 000x0c047fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c047fff8000: fa fa[01]fa fa fa fa fa fa fa fa fa fa fa fa fa0x0c047fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa0x0c047fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa0x0c047fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa0x0c047fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa0x0c047fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):Addressable: 00Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: faFreed heap region: fdStack left redzone: f1Stack mid redzone: f2Stack right redzone: f3Stack after return: f5Stack use after scope: f8Global redzone: f9Global init order: f6Poisoned by user: f7Container overflow: fcArray cookie: acIntra object redzone: bbASan internal: feLeft alloca redzone: caRight alloca redzone: cbShadow gap: cc
==175937==ABORTING
ubuntu@VM-24-14-ubuntu:~/CppTest$
这次提示又不一样了,提示第6行出现了heap-buffer-overflow。熟悉错误信息的同学很快就能意识到,这是提示程序出现了越界访问内存的错误。
通关这几次测试,我们可以发现,AddressSanitizer可以帮助我们快速定位编译器检测不出来的内存问题,大大提高了我们debug的效率。
四、不能在gdb、lldb等调试器运行带有AddressSanitizer的程序
笔者就是为了记录这个问题,特意包了这碗饺子。
当尝试使用gdb调试添加了AddressSanitizer编译选项的程序时,程序会拒绝执行,提示:
==163135==LeakSanitizer has encountered a fatal error.
==163135==HINT: For debugging, try setting environment variable LSAN_OPTIONS=verbosity=1:log_threads=1
==163135==HINT: LeakSanitizer does not work under ptrace (strace, gdb, etc)
根据网上找到的资料,LeakSanitizer在内部使用ptrace,可能会挂起所有线程,以便它可以扫描泄漏而不会产生误报。只有一个应用程序可以使用ptrace,因此,如果您在gdb或strace下运行应用程序,则LeakSanitizer将无法通过ptrace进行附加。
说人话就是,LINUX系统下只同时允许一个进程使用ptrace()这个系统调用,而ptrace是用于进程跟踪的,它提供了父进程可以观察和控制其子进程执行的能力,并允许父进程检查和替换子进程的内核镜像(包括寄存器)的值。
gdb等调试器也会使用ptrace,因此与LeakSanitizer相冲突,不允许执行进程。
参考
Address Sanitizer 用法
Linux 下的 AddressSanitizer
c++ Asan(address-sanitize)的配置和使用
Ptrace 详解