原论文:AddressSanitizer: A Fast Address Sanity Checker
谷歌官方文档:AddressSanitizerAlgorithm
参考博客:Introduction to AddressSanitizer
论文解读:AddressSanitizer论文解读

Intro

内存访问错误,简单地说就是访问了不该访问的区域。如果我们能够记录所有有效的内存区域,并且在访问内存之前进行检查,就可以有效地抓住这些bug。这篇paper介绍的AddressSanitizer (ASAN) 是一个内存错误检测工具,可以检测出heap, stack, global objects的越界访问以及use-after-free错误。

AddressSanitizer主要包括三个部分:

阴影内存 shadow memory:ASAN算法的基石,占用一部分的内存区域,用于记录哪些内存地址是可以安全访问的,哪些内存地址是不可以访问的。

检测模块 instrumentation module:编译期间,对代码进行修改,主要增加了以下两个内容

  • 在每一次内存访问前,先检查对应的内存地址的shadow状态,确定可以安全访问时才访问,否则报错。
  • 在应用数据的前后增加毒区(poisoned redzones),毒区设置为不可寻址,用于检测stack和global objects类型的越界访问的错误。

运行时库 run-time library:运行期间,对代码进行修改,主要是修改了malloc和free函数的实现方法,在heap上分配内存时,在应用数据前后创建毒区(poisoned redzones),用于检测heap类型的越界访问的错误。

Algorithm

Shadow Memory

  • 阴影内存空间 Shadow Memory
  • 应用数据内存空间 Application Memory

很多内存检测工具也会使用Shadow Memory来检测内存错误,比如TaintTrace的设计比较粗糙,一对一映射,使得Shadow Memory与Application Memory占用的内存空间一样大,开销很大、影响性能。

相比之下,ASAN的Shadow Memory设计更加高效和灵活,不仅可以将Shadow Memory所需空间缩小为Application Memory的1/n,而且Shadow Memory的缩小倍数和它在内存中的起始位置也可以调整。

编码规则

首先,ASAN认为,既然(32位编译系统下)malloc函数分配内存是8byte对齐的,所以他们初步把压缩的倍数设置为8,即Application Memory的8byte映射到Shadow Memory的1byte上。那么对于Application Memory中每8byte的内存空间,对应的Shadow Memory用1byte来表示其状态,编码规则:

  • shadow value = 0,代表8byte全部都是可寻址的,全都放满了应用数据。
  • shadow value = k (1 ≤ k ≤ 7) ,代表前k byte的空间是可寻址的,剩余的8-k byte的空间是不可寻址的。
  • shadow value < 0,负数代表8byte空间全部不可寻址,不同的负数代表不同的情况(heap redzones, stack redzones, global redzones, freed memory)

映射

从Application Memory地址映射到Shadow Memory地址的公式是

(Addr>>Scale)+Offset

Scale的取值可以是1~7中的任一数字,如果按照上面所说的8byte映射到1byte的设定,那么Scale=3。相当于Application Memory的地址右移3位后再加上一个Offset偏移后,就是对应的Shadow Memory地址。

Offset是事先确定好的,确定好之后就固定住,设置Offset的值的要求是:假设Max-1是虚拟内存中的最大地址,那么要求从地址Offset到Offset+Max/8这一部分都是空的,全部用作Shadow Memory。

一个例子

在32bit机器里,虚拟内存地址是0x00000000-0xffffffff, 可以设置Offset = 0x20000000 (2^29)

映射规则公式

Shadow = (Mem >> 3) + 0x20000000;

地址空间布局

地址区间
[0x40000000, 0xffffffff] HighMem
[0x28000000, 0x3fffffff] HighShadow
[0x24000000, 0x27ffffff] ShadowGap
[0x20000000, 0x23ffffff] LowShadow
[0x00000000, 0x1fffffff] LowMem

映射图

虚拟内存空间中,Application Memory被分成了高地址和低地址两个部分,对这两部分的地址空间使用映射公式,就会分别映射到对应的两个Shadow Memory。

除此之外,对Shadow Memory的地址使用映射公式会被映射到Bad Memory去,这个空间也叫做ShadowGap,被ASAN设置为不可寻址。这个空间的作用后面会讲到。

Instrumentation

Shadow Memory的编码规则、内存地址布局和映射规则都讲完了,现在的疑问就是,

  • 检测过程:是怎么根据shadow value的值去判断Application Memory地址是否可以安全访问。
  • 构建Shadow:(Instrumentation部分在编译期间修改代码,负责构建Stack and Globals类型的应用数据的redzone和shadow memory)。

检测过程

访问address前先检查对应的shadow值,此时还需要考虑这次内存访问是多少字节对齐的,

  • 如果是8字节对齐,那么必须8byte全部可寻址才能访问,否则都报错。
  • 如果是1-, 2-, or 4- bytes对齐,8byte全部可寻址最好,但是即使不是全部可寻址也不可以直接报错,还需要具体看shadow_value的值才能判断。

检测代码

byte *shadow_address = MemToShadow(address);
byte shadow_value = *shadow_address;
if (shadow_value) {if (SlowPathCheck(shadow_value, address, kAccessSize)) {//shadow_value != 0,需要调用SlowPathCheck做进一步的判断ReportError(address, kAccessSize, kIsWrite); //报错}
}
//访问address

可以看到,每次访问内存地址前都必须调用MemToShadow函数来获取对应的Shadow地址,然后检查其中的值。而如果代码中想要直接访问Shadow部分的值,调用MemToShadow后就会映射得到Bad Memory部分的地址,而Bad Memory部分是不可寻址的,无法读取其中的内容。因此,任何想要直接访问Shadow内容的代码都会报错。

SlowPathCheck函数

// Check the cases where we access first k bytes of the qword
// and these k bytes are unpoisoned.
bool SlowPathCheck(shadow_value, address, kAccessSize) {//参数kAccessSize代表这一次内存访问是kAccessSize字节对齐的last_accessed_byte = (address & 7) + kAccessSize - 1;return (last_accessed_byte >= shadow_value); //看这次要读的最后一个byte是否超出8byte里可寻址的范围
}

可以看到,使用ASAN检测后,每次访问内存都需要多读一次内存(读shadow),增加了一定的开销。

构建Shadow

接下来看如何构建Shadow Memory,应用数据主要存储在几个地方,包括stack、global、heap等。由于heap是运行时才分配,而Instrumentation是在编译阶段修改代码来构建Shadow Memory的,所以这里只有对Stack 和 Globals的操作。

Stack

以Stack为例子,一个函数的原始代码,会在栈上分配8个byte的内存空间:

void foo() {char a[8];...return;
}

那么ASAN会怎么构建Shadow Memory来检测stack的越界访问错误呢。

直接看重构后的代码:

void foo() {char redzone1[32];  // 32-byte alignedchar a[8];          // 32-byte alignedchar redzone2[24];char redzone3[32];  // 32-byte alignedint  *shadow_base = MemToShadow(redzone1);shadow_base[0] = 0xffffffff;  // poison redzone1shadow_base[1] = 0xffffff00;  // poison redzone2, unpoison 'a'shadow_base[2] = 0xffffffff;  // poison redzone3...shadow_base[0] = shadow_base[1] = shadow_base[2] = 0; // unpoison allreturn;
}

首先ASAN会在应用数据的前后增设大小为32byte的redzones,然后再给对应的Shadow地址赋值。

可以看到,每8byte的redzones对应的1byte的Shadow的值都为0xff,是负数-1,代表全部8byte都不可寻址。

补码的表示方法是: 最高位是符号位,0为正数,1为负数。

1、正数的补码就是其本身

2、负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1. (即在反码的基础上+1)

[+1] = [00000001]原 = [00000001]反 = [00000001]补

[-1] = [10000001]原 = [11111110]反 = [11111111]补

而8byte的应用数据对应的1byte的Shadow的值为0x00,代表全部8byte都可以寻址。

这样构建完redzone和shadow之后,每次访问stack的数据时经过前面章节的检测过程后,就可以判断到是否有越界访问的问题了。

最后函数结束的时候,再把shadow值全部置0。

Globals

Global的做法也类似,不同的地方在于,global的redzone是编译期间分配内存的,stack的redzone是运行期间分配内存的。

Run-time library

上面已经讲完了如何构建Stack和Global类型应用数据的redzone和shadow,接下来讲Heap类型。

运行过程中在heap上分配内存的相关函数就是malloc和free,而ASAN的运行时库会对这两个函数进行改造。

  • malloc:也是同样的做法,在分配的应用内存的前后加上redzone,以及设置好对应的shadow的值。
  • free:释放内存时,会把这块内存对应的Shadow值设置为不可寻址,并且把内存放进一个隔离队列中,隔离队列是一个先进先出(FIFO)队列,容量固定,满了之后才把最旧的内存放出来。这样做的目的是防止内存释放后马上被malloc,然后被之前残留的指针错读里面的内容,可以检测出use-after-free的错误(没有被malloc但是被访问了,shadow为不可寻址,报错)。

总结

总的来说,ASAN是一个快速的内存错误检测工具,可以检测出对heap、stack、globals的越界访问以及use-after-free错误,相比其他的同行工具更加高效,开销更低。而且从gcc 4.8开始,ASAN就已经成为了gcc的一部分,只需要加上编译选项-fsanitize=address就可以启用ASAN。

缺点

ASAN也有一些使用限制以及检测不出内存错误的情况。比如

1、运行ASAN需要消耗CPU和内存资源的,它的性能相比于之前的工具确实有了质的提升,但平均仍然会拖慢程序73%和消耗3.4倍的内存,因此也还是无法适用于某些压力测试场景,尤其是流量较高的服务,打开ASAN调试某些奇葩问题时,系统总会因为负载过重而跑不起来,内存使用率直接飙上99%。(这个也是我没法在线上机器用ASAN的原因)

2、内存访问不对齐。ASAN只能检测对齐的内存访问,比如1-, 2-, 4-, 8-bytes对齐等。

int *a = new int[2]; // 8-aligned
int *u = (int*)((char*)a + 6);
*u = 1; // Access to range [6-9]

3、越界访问时访问了很远的地方,超过前后的redzone的范围,访问到其他部分的应用数据,而检测不出越界访问错误。可以通过扩大redzone的方法来解决,但是开销也更大。

char *a = new char[100];
char *b = new char[1000];
a[500] = 0; // may end up somewhere in b

4、频繁分配、释放大量到堆内存,导致内存块过快地离开了隔离队列,因而检测不出use-after-free的错误。

char *a = new char[1 << 20]; // 1MB
delete [] a; // <<< "free"
char *b = new char[1 << 28]; // 256MB
delete [] b; // drains the quarantine queue.
char *c = new char[1 << 20]; // 1MB
a[0] = 0;  // "use". May land in ’c’.

内存错误检测工具AddressSanitizer原理相关推荐

  1. 内存错误检测工具——kfence工作原理分析

    一.功能介绍 Linux 5.13引入一个新的内存错误检测工具:KFENCE(Kernel Electric-Fence,内核电子栅栏).KFENCE是一个低开销的.基于采样的内存错误检测工具.KFE ...

  2. vs2019 C++自带了内存问题检测工具:AddressSanitizer(ASan)

    之前做服务器时,一般是linux下的C++,  C++容易出很多内存问题:内存泄漏.内存越界.野指针 空指针之类的问题.  linux下 运行时检测工具,有 valgrind,正常编译debug版,然 ...

  3. 内存泄漏检测工具(转载)

    内存泄漏检测工具2007年08月08日 1.     ccmalloc-Linux和Solaris下对C和C++程序的简单的使用内存泄漏和malloc调试库. 2.     Dmalloc-Debug ...

  4. 内存泄露检测工具比较

    From: http://blog.163.com/zhuang_qianxin/blog/static/29765138201051092529107/ 1.     ccmalloc-Linux和 ...

  5. 插桩valgrind_基于动态插桩的CC++内存泄漏检测工具的设计与实现.pdf

    基于动态插桩的CC++内存泄漏检测工具的设计与实现.pdf 第32卷第6期 计 算 机 应 用 研 究 V01.32No.6 20l5年 6月 ApplicationResearchofCompute ...

  6. 内存错误检测-AddressSanitizer

    目录 简介: 使用范围: 使用方法: 结合gdb: 运行结果: 简介: AddressSanitizer是C/C++ 内存错误检测的工具,它是LLVM3.1版本开始支持&#x

  7. Linux中的常用内存问题检测工具

    原文地址:http://blog.csdn.net/jinzhuojun/article/details/46659155 C/C++等底层语言在提供强大功能及性能的同时,其灵活的内存访问也带来了各种 ...

  8. 内存问题检测工具的介绍

    C/C++等底层语言在提供强大功能及性能的同时,其灵活的内存访问也带来了各种纠结的问题.但是,在这样灵活操作的后面,还隐藏着很危险的操作,那就是关于内存的问题.一看到内存的问题,大部分的初学者就开始傻 ...

  9. C/C++的内存泄漏检测工具Valgrind memcheck的使用经历

    Linux下的Valgrind真是利器啊(不知道Valgrind的请自觉查看参考文献(1)(2)),帮我找出了不少C++中的内存管理错误,前一阵子还在纠结为什么VS 2013下运行良好的程序到了Lin ...

最新文章

  1. Axure RP 9.0 原型设计软件安装教程
  2. Python入门100题 | 第020题
  3. 使用Jmeter进行http接口测试
  4. Hive的基本操作-内置函数
  5. 在运行时打开GC日志记录
  6. 事务相关、不可重复读与幻读的区别
  7. ZTree的全选 反选 全不选 取消 清空
  8. day_01 解析简单的程序
  9. 前端现在有发展前途吗?应届生好找工作吗?
  10. Openssl verify命令
  11. 考上MBA,为自己装一双翅膀
  12. android horizontalscrollview属性,Android中HorizontalScrollView使用方法详解
  13. Excel IRR函数告诉你信用卡分期、贷款的实际利率
  14. 报警c语言程序,PIC单片机警报声C程序
  15. 陈安之超级成功法则(1)
  16. SQL Server 数据库词汇表
  17. Ubuntu 18.04 锁屏 快捷键 无效
  18. 什么是JAVA?JAVA能做什么?
  19. 黑马程序员——JavaSE之集合框架总结二
  20. AnyConnect win10安装

热门文章

  1. vivox9android7.1版本,再战一年:vivo X9获得Anroid 7.1固件更新
  2. emmc挂载,分区及格式化
  3. pytorch版本.post2
  4. 《SQL必知必会阅读思维导图》PART3
  5. 新版TCGA的甲基化数据分析
  6. FastBoot 刷机教程
  7. 「星辰大海」你可能需要知道的 promise 知识的总结(内附思维导图)
  8. 例题4-1 UVA1339 古老的密码 Ancient Cipher
  9. 求职奇安信——售前工程师
  10. 多线程:sleep(0)的意义