作为 C/ C++ 工程师,在开发过程中会遇到各类问题,最常见便是内存使用问题,比如,越界泄漏。过去常用的工具是 Valgrind,但使用 Valgrind 最大问题是它会极大地降低程序运行的速度,初步估计会降低 10 倍运行速度。而 Google 开发的 AddressSanitizer 这个工具很好地解决了 Valgrind 带来性能损失问题,它非常快,只拖慢程序 2 倍速度。

AddressSanitizer 概述

AddressSanitizer 是一个基于编译器的测试工具,可在运行时检测 C/C++ 代码中的多种内存错误。严格上来说,AddressSanitizer 是一个编译器插件,它分为两个模块,一个是编译器的 instrumentation 模块,一个是用来替换 malloc/free 的动态库。

Instrumentation 主要是针对在 llvm 编译器级别对访问内存的操作(store,load,alloc等),将它们进行处理。动态库主要提供一些运行时的复杂的功能(比如 poison/unpoison shadow memory)以及将 malloc/free 等系统调用函数 hook 住。

AddressSanitizer 基本使用

根据 AddressSanitizer Wiki 可以检测下面这些内存错误

  • Use after free:访问堆上已经被释放的内存
  • Heap buffer overflow:堆上缓冲区访问溢出
  • Stack buffer overflow:栈上缓冲区访问溢出
  • Global buffer overflow:全局缓冲区访问溢出
  • Use after return:访问栈上已被释放的内存
  • Use after scope:栈对象使用超过定义范围
  • Initialization order bugs:初始化命令错误
  • Memory leaks:内存泄漏

这里我只简单地介绍下基本的使用,详细的使用文档可以看官方的编译器使用文档,比如 Clang 的文档:https://clang.llvm.org/docs/AddressSanitizer.html

Use after free 实践例子

下面这段代码是一个很简单的 Use after free 的例子:

//use_after_free.cpp
#include <iostream>
int main(int argc, char **argv) {
int *array = new int[100];
delete [] array;
std::cout << array[0] << std::endl;
return 1;
}

编译代码,并且运行,这里可以看到只需要在编译的时候带上 -fsanitize=address  选项就可以了。

clang++  -O -g -fsanitize=address ./use_after_free.cpp
./a.out

最终我们会看到如下的输出:

==10960==ERROR: AddressSanitizer: heap-use-after-free on address 0x614000000040 at pc 0x00010d471df0 bp 0x7ffee278e6b0 sp 0x7ffee278e6a8
READ of size 4 at 0x614000000040 thread T0
#0 0x10d471def in main use_after_free.cpp:6
#1 0x7fff732c17fc in start (libdyld.dylib:x86_64+0x1a7fc) 0x614000000040 is located 0 bytes inside of 400-byte region [0x614000000040,0x6140000001d0)
freed by thread T0 here:
#0 0x10d4ccced in wrap__ZdaPv (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x51ced)
#1 0x10d471ca1 in main use_after_free.cpp:5
#2 0x7fff732c17fc in start (libdyld.dylib:x86_64+0x1a7fc) previously allocated by thread T0 here:
#0 0x10d4cc8dd in wrap__Znam (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x518dd)
#1 0x10d471c96 in main use_after_free.cpp:4
#2 0x7fff732c17fc in start (libdyld.dylib:x86_64+0x1a7fc) SUMMARY: AddressSanitizer: heap-use-after-free use_after_free.cpp:6 in main

可以看到一目了然,非常清楚的告诉了我们在哪一行内存被释放,而又在哪一行内存再次被使用。

还有一个是内存泄漏,比如下面的代码,显然 p 所指的内存没有被释放。

void *p;

int main() {
p = malloc(7);
p = 0; // The memory is leaked here.
return 0;
}

编译然后运行

clang -fsanitize=address -g  ./leak.c
./a.out

可以看到如下的结果

=================================================================
==17756==ERROR: LeakSanitizer: detected memory leaks Direct leak of 7 byte(s) in 1 object(s) allocated from:
#0 0x4ffc80 in malloc (/home/simon.liu/workspace/a.out+0x4ffc80)
#1 0x534ab8 in main /home/simon.liu/workspace/./leak.c:4:8
#2 0x7f127c42af42 in __libc_start_main (/usr/lib64/libc.so.6+0x23f42) SUMMARY: AddressSanitizer: 7 byte(s) leaked in 1 allocation(s).

不过这里要注意内存泄漏的检测只会在程序最后退出之前进行检测,也就是说如果你在运行时如果不断地分配内存,然后在退出的时候对内存进行释放,AddressSanitizer 将不会检测到内存泄漏,这种时候可能你就需要另外的工具了 JeMalloc / TCMalloc。

AddressSanitizer 基本原理

这里简单介绍一下 AddressSanitizer 的实现,更详细的算法实现可以看《AddressSanitizer: a fast address sanity checker》:https://www.usenix.org/system/files/conference/atc12/atc12-final39.pdf

AddressSanitizer 会替换你的所有 malloc 以及 free,然后已经被分配(malloc)的内存区域的前后会被标记为 poisoned (主要是为了处理 overflow 这种情况),而释放(free)的内存会被标记为 poisoned(主要是为了处理 Use after free)。你的代码中的每一次的内存存取都会被编译器做类似下面的翻译.

before:

*address = ...;  // or: ... = *address;

after:

shadow_address = MemToShadow(address);
if (ShadowIsPoisoned(shadow_address)) {
ReportError(address, kAccessSize, kIsWrite);
}
*address = ...; // or: ... = *address;

这里可以看到首先会对内存地址有一个翻译(MemToShadow)的过程,然后再来判断当所访问的内存区域是否为 poisoned,如果是则直接报错并退出。

这里之所以会有这个翻译是因为 AddressSanitizer 将虚拟内存分为了两部分:

  1. Main application memory(Mem)也就是被当前程序自身使用的内存
  2. Shadow memory 简单来说就是保存了主存元信息的一块内存,比如主存的那些区域被 posioned 都是在 Shadow memory 中保存的

AddressSanitizer 和其他内存检测工具对比

下图是 AddressSanitizer 与其他的一些内存检测工具的对比:

AddressSanitizer Valgrind/Memcheck Dr. Memory Mudflap Guard Page gperftools
technology CTI DBI DBI CTI Library Library
ARCH x86, ARM, PPC x86, ARM, PPC, MIPS, S390X, TILEGX x86 all(?) all(?) all(?)
OS Linux, OS X, Windows, FreeBSD, Android, iOS Simulator Linux, OS X, Solaris, Android Windows, Linux Linux, Mac(?) All (1) Linux, Windows
Slowdown 2x 20x 10x 2x-40x ? ?
Detects:
Heap OOB yes yes yes yes some some
Stack OOB yes no no some no no
Global OOB yes no no ? no no
UAF yes yes yes yes yes yes
UAR yes (see AddressSanitizerUseAfterReturn) no no no no no
UMR no (see MemorySanitizer) yes yes ? no no
Leaks yes (see LeakSanitizer) yes yes ? no yes

参数说明:

  • DBI: dynamic binary instrumentation(动态二进制插桩)
  • CTI: compile-time instrumentation (编译时插桩)
  • UMR: uninitialized memory reads (读取未初始化的内存)
  • UAF: use-after-free (aka dangling pointer) (使用释放后的内存)
  • UAR: use-after-return (使用返回后的值)
  • OOB: out-of-bounds (溢出)
  • x86: includes 32- and 64-bit.

可以看到相比于 Valgrind,AddressSanitizer 只会拖慢程序 2 倍运行速度。当前 AddressSanitizer 支持 GCC 以及 Clang,其中 GCC 是从 4.8 开始支持,而 Clang 的话是从 3.1 开始支持。

AddressSanitizer 的使用注意事项

  1. AddressSanitizer 在发现内存访问违规时,应用程序并不会自动崩溃。这是由于在使用模糊测试工具时,它们通常都是通过检查返回码来检测这种错误。当然,我们也可以在模糊测试进行之前通过将环境变量 ASAN_OPTIONS 修改成如下形式来迫使软件崩溃:
export ASAN_OPTIONS='abort_on_error=1'/
  1. AddressSanitizer 需要相当大的虚拟内存(大约 20 TB),不用担心,这个只是虚拟内存,你仍可以使用你的应用程序。但像 american fuzzy lop 这样的模糊测试工具就会对模糊化的软件使用内存进行限制,不过你仍可以通过禁用内存限制来解决该问题。唯一需要注意的就是,这会带来一些风险:测试样本可能会导致应用程序分配大量的内存进而导致系统不稳定或者其他应用程序崩溃。因此在进行一些重要的模糊测试时,不要去尝试在同一个系统上禁用内存限制。

在 Nebula Graph 中开启 AddressSanitizer

我们在 Nebula Graph 中也使用了 AddressSanitizer,它帮助我们发现了非常多的问题。而在 Nebula Graph 中开启 AddressSanitizer 很简单,只需要在 Cmake 的时候带上打开 ENABLE_ASAN 这个 Option 就可以,比如:

Cmake -DENABLE_ASAN=On

这里建议所有的开发者在开发完毕功能运行单元测试的时候都打开 AddressSanitizer 来运行单元测试,这样可以发现很多不容易发现的内存问题,节省很多调试的时间。

附录

应用 AddressSanitizer 发现程序内存错误的更多相关文章

  1. AddressSanitizer —— ASAN分析内存错误

    简介 AddressSanitizer 是一个性能非常好的C/C++ 内存错误探测工具. 它由编译器的插桩模块和替换了malloc函数的运行时库组成. 这个工具可以探测如下这些类型的错误: 对堆.栈和 ...

  2. 问题-关于sharemem程序访问WEB出现内存错误处理

    [delphi技术] 关于sharemem造成dll错误的处理办法问题现象:如果程序和dll之间用string作为参数传递时容易出现错误问题处理:需要在程序的uses中使用sharemem.这个sha ...

  3. 如何快速定位找出SEGV内存错误的程序Bug

    通过查看php日志/usr/local/php/var/log/php-fpm.log,有如下警告信息: [16-Mar-2015 16:03:09] WARNING: [pool www] chil ...

  4. Adobe Acrobat 不能打开在线pdf。Adobe Acrobat 应用程序正在被终止,因为内存错误

    Adobe Acrobat 应用程序正在被终止,因为内存错误. Adobe Acrobat 不能打开在线pdf. 当出现上面两种错误时. 原因可能是Acrobat的更新有问题. 解决方法:打开C:\D ...

  5. Unix下C程序内存泄露检测工具:valgrind的安装使用

    Valgrind是一款用于内存调试.内存泄漏检测以及性能分析的软件开发工具. Valgrind的最初作者是Julian Seward,他于2006年由于在开发Valgrind上的工作获得了第二届Goo ...

  6. 小心DLL链接静态库时的内存错误

    本文转自http://www.bennychen.cn/2010/09/%E5%B0%8F%E5%BF%83dll%E9%93%BE%E6%8E%A5%E9%9D%99%E6%80%81%E5%BA% ...

  7. Linux mem 2.7 内存错误检测 (KASAN) 详解

    文章目录 1. 简介 2. Shadow 区域初始化 3. 权限的判断 3.1 read/write 3.2 memxxx() 4. 权限的设置 4.1 buddy 4.1.1 kasan_free_ ...

  8. C++程序内存泄漏检测方法

    一.前言 在Linux平台上有valgrind可以非常方便的帮助我们定位内存泄漏,因为Linux在开发领域的使用场景大多是跑服务器,再加上它的开源属性,相对而言,处理问题容易形成“统一”的标准.而在W ...

  9. Linux C程序内存空间

    linux下内存空间布置: 一个典型的Linux C程序内存空间由如下几部分组成: 代码段(.text).这里存放的是CPU要执行的指令.代码段是可共享的,相同的代码在内存中只会有一个拷贝,同时这个段 ...

随机推荐

  1. ubuntu安装搜狗输入

    百度搜索搜狗输入ubuntu找到官网地址 下载deb包 sogoupinyin_2.3.1.0112_amd64.deb 上传 dkpkg  -i   sogoupinyin_2.3.1.0112_a ...

  2. JQ DOM元素 创建 添加 删除

    创建元素 // 创建元素节点 $('<p></p>'); // 创建属性节点 $('<p class="wow"></p>'); / ...

  3. Mysql主键外键操作

    外键: ·一对多 ·多对多 ·一对一 ·修改表 ·复制表   主键:   rimary key auto_increment(primary key是主键通常和auto_increment自动增加混合 ...

  4. [转]Vue 2.0——渐进式前端解决方案

    前言:框架是什么?为什么要有框架?在众多的框架之中,Vue 独具魅力之处在哪里呢?其背后的核心思想是什么?Vue 究竟火到什么程度?最近发布的 Vue2.0 又做了哪些改进呢?Vue 和 Weex 又 ...

  5. HTML学习(10)图像

    HTML图像标签<img>,没有闭合标签 <img src="" alt="" width="" height=" ...

  6. (转)多进程 & 多线程的区别与适用场景

    转自:http://www.cnblogs.com/huntfor/p/4021327.html 关于多进程和多线程,教科书上最经典的一句话是“进程是资源分配的最小单位,线程是CPU调度的最小单位”, ...

  7. Apollo安装教程

    最近一直在研究Apollo的安装问题,浪费了几天时间,所有特意来记录一下安装心得. UBUNTU 14.04安装 这个我是把自己的笔记本全部转换为ubuntu系统,操作相对简单,大家可以自行查找,另外 ...

  8. win10 superfetch 使系统变慢

    win10 superfetch是干什么的? 时间:2018-12-28 来源:莫回首系统 作者:win7 很多用户喜欢关注CPU的运行状态,来保障系统的运行速度不受影响,今早,有ghost win1 ...

  9. Linux netstat命令详解(检验本机各端口的网络连接情况)

    netstat命令用于显示与IP.TCP.UDP和ICMP协议相关的统计数据,一般用于检验本机各端口的网络连接情况.netstat是在内核中访问网络及相关信息的程序,它能提供TCP连接,TCP和UDP ...

  10. Mysql sql语句技巧与优化

    一.常见sql技巧 1.正则表达式的使用 2.巧用RAND()提取随机行 mysql数据库中有一个随机函数rand()是获取一个0-1之间的数,利用这个函数和order by一起能够吧数据随机排序, ...