【Linux】使用AddressSanitizer分析内存非法使用问题

2024-05-24 06:52

本文主要是介绍【Linux】使用AddressSanitizer分析内存非法使用问题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

      • 1 为什么需要AddressSanitizer?
      • 2 如何使用AddressSanitizer
      • 3 AddressSanitizer的原理
      • 4 总结

1 为什么需要AddressSanitizer?

Valgrind是比较常用的内存问题定位工具,既然已经有了Valgrind,为什么还需要AddressSanitizer呢?

与Valgrind相比,AddressSanitizer存在以下优势:

  • Valgrind通过模拟CPU来检测内存错误,导致会以较慢的速度运行程序;而AddressSanitizer是在编译阶段插入检查的逻辑,执行速度比Valgrind快很多
  • Valgrind是一个独立的工具,可以使用在任何程序上;而AddressSanitizer与编译器紧密集成,可以在构建时自动启用
  • 在错误信息的展示上,AddressSanitizer提供的错误信息比Valgrind容易理解
  • AddressSanitizer作为编译器的一部分,通过编译选项启用;而Valgrind作为独立的工具,需要更多的配置和学习才能使用
  • AddressSanitizer通过编译时插桩和运行时检查来检测内存错误,误报率较低

从使用场景来说,AddressSanitizer专注于发现内存未释放访问非法内存的问题。

2 如何使用AddressSanitizer

从gcc 4.8开始,AddressSanitizer称为gcc的一部分,但是,gcc 4.8的AddressSanitizer没有符号信息,建议使用gcc 4.9及以上版本。

首先使用AddressSanitizer来检测下常见的内存问题。

用例一:未正确释放内存

#include <iostream>int main() {int *ptr = new(int);*ptr = 0;std::cout << *ptr << std::endl;
}

上述代码申请了一个int的空间,但是没有释放,编译该程序:g++ -fsanitize=address -fno-omit-frame-pointer -o main main.cpp,然后直接执行:

0=================================================================
==711759==ERROR: LeakSanitizer: detected memory leaksDirect leak of 4 byte(s) in 1 object(s) allocated from:#0 0x7f37f92201c7 in operator new(unsigned long) ../../../../src/libsanitizer/asan/asan_new_delete.cpp:99#1 0x55581dc742be in main (/root/asan/main+0x12be)#2 0x7f37f8c54fcf in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58SUMMARY: AddressSanitizer: 4 byte(s) leaked in 1 allocation(s).

结果比Valgrind更好理解:

  • 出现了内存泄漏
  • 导致内存泄漏的内存分配的调用栈
  • 总结,在一次分配过程中泄漏了4字节

用例二:分配的内存未使用配对的函数释放

#include <iostream>int main() {int *ptr = new(int);*ptr = 0;free(ptr);
}
=================================================================
==711960==ERROR: AddressSanitizer: alloc-dealloc-mismatch (operator new vs free) on 0x602000000010#0 0x7f8ef531c517 in __interceptor_free ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:127#1 0x5571e62742ef in main (/root/asan/main+0x12ef)#2 0x7f8ef4d52fcf in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58#3 0x7f8ef4d5307c in __libc_start_main_impl ../csu/libc-start.c:409#4 0x5571e62741c4 in _start (/root/asan/main+0x11c4)0x602000000010 is located 0 bytes inside of 4-byte region [0x602000000010,0x602000000014)
allocated by thread T0 here:#0 0x7f8ef531e1c7 in operator new(unsigned long) ../../../../src/libsanitizer/asan/asan_new_delete.cpp:99#1 0x5571e627429e in main (/root/asan/main+0x129e)#2 0x7f8ef4d52fcf in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58SUMMARY: AddressSanitizer: alloc-dealloc-mismatch ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:127 in __interceptor_free
==711960==HINT: if you don't care about these errors you may set ASAN_OPTIONS=alloc_dealloc_mismatch=0
==711960==ABORTING

结果也可以直接看出来:

  • alloc-dealloc-mismatch(operator new vs free):分配和销毁不匹配
  • 分配和销毁的调用栈

用例三:使用已经被回收的内存

#include <iostream>int main() {int *ptr = new(int);*ptr = 0;delete(ptr);*ptr = 1;
}
=================================================================
==712018==ERROR: AddressSanitizer: heap-use-after-free on address 0x602000000010 at pc 0x560c4aac2331 bp 0x7ffd02a2d040 sp 0x7ffd02a2d030
WRITE of size 4 at 0x602000000010 thread T0#0 0x560c4aac2330 in main (/root/asan/main+0x1330)#1 0x7ff2fbc22fcf in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58#2 0x7ff2fbc2307c in __libc_start_main_impl ../csu/libc-start.c:409#3 0x560c4aac21c4 in _start (/root/asan/main+0x11c4)0x602000000010 is located 0 bytes inside of 4-byte region [0x602000000010,0x602000000014)
freed by thread T0 here:#0 0x7ff2fc1ef22f in operator delete(void*, unsigned long) ../../../../src/libsanitizer/asan/asan_new_delete.cpp:172#1 0x560c4aac22f9 in main (/root/asan/main+0x12f9)#2 0x7ff2fbc22fcf in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58previously allocated by thread T0 here:#0 0x7ff2fc1ee1c7 in operator new(unsigned long) ../../../../src/libsanitizer/asan/asan_new_delete.cpp:99#1 0x560c4aac229e in main (/root/asan/main+0x129e)#2 0x7ff2fbc22fcf in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58SUMMARY: AddressSanitizer: heap-use-after-free (/root/asan/main+0x1330) 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[fd]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
==712018==ABORTING

会输出heap-use-after-free类型的结果:

  • 结果包含内存分配、释放以及写已经释放的内存的调用栈
  • 在SUMMARY下面出现两块数据,这个就涉及到AddressSanitizer的实现

3 AddressSanitizer的原理

前面说过,AddressSanitizer主要的使用场景是内存未释放访问非法内存,为了能够发现这两种情况,需要为内存保存一些状态信息。

为了能够为内存保存状态信息,AddressSanitizer会用自己的运行时库替换默认的malloc/free,将需要操作内存的代码进行替换。

例如,如果是读操作:

... = *address;

则在访问内存前执行检查:

if (IsPoisoned(address)) {ReportError(address, kAccessSize, kIsWrite);
}
... = *address;

如果是写操作:

*address = ...;

则在写操作之前进行检查:

if (IsPoisoned(address)) {ReportError(address, kAccessSize, kIsWrite);
}
*address = ...;

其中的IsPoisoned()函数就是用来检查内存地址是否合法

AddressSanitizer使用影子内存的机制进行内存地址的合法性检查:对程序实际使用的内存使用额外的字节来存储它的状态信息。

使用1个字节存储实际使用的8个字节的状态:

  • 8个字节的数据可读写,则1个字节的值为0
  • 8个字节的数据不可读写,则1个字节的值为负数,不同的值表示不同类型的内存,如0xfa表示堆左边的redzone(redzone是在正常可以使用的内存两侧的边界),0xf1表示栈左边的redzone
  • 8个字节中前k个字节可读写,后8-k个字节不可读写,则1个字节的值为k

因此,IsPoisoned()函数就是检查地址对应的影子内存中的状态是否可以访问,如果不能访问,则出现访问非法内存的问题,如果访问的地址的影子内存是redzone,则说明出现内存访问越界的问题。

基于上述描述,再来看上面heap-use-after-free的输出的后面部分的内容:

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[fd]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
==712018==ABORTING

第二部分是图例,也就是影子内存中不同的值代表的含义,例如,fd表示已经被释放的内存。

第一部分显示的就是影子内存的数据,出现了fd表示已经被释放的内存,对该内存再次访问就会出现heap-use-after-free问题。

下面再看一个栈溢出的例子:

#include <iostream>
#include <string.h>int main() {int arr1[10];int arr2[12];memcpy(arr1, arr2, 12*sizeof(int));
}

结果:

=================================================================
==714984==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffe915be528 at pc 0x7f22667182c3 bp 0x7ffe915be4d0 sp 0x7ffe915bdc78
WRITE of size 48 at 0x7ffe915be528 thread T0#0 0x7f22667182c2 in __interceptor_memcpy ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:827#1 0x5620a92e0349 in main (/root/asan/main+0x1349)#2 0x7f22661c8fcf in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58#3 0x7f22661c907c in __libc_start_main_impl ../csu/libc-start.c:409#4 0x5620a92e01c4 in _start (/root/asan/main+0x11c4)Address 0x7ffe915be528 is located in stack of thread T0 at offset 72 in frame#0 0x5620a92e0298 in main (/root/asan/main+0x1298)This frame has 2 object(s):[32, 72) 'arr1' (line 5)[112, 160) 'arr2' (line 6) <== Memory access at offset 72 partially underflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:827 in __interceptor_memcpy
Shadow bytes around the buggy address:0x1000522afc50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 000x1000522afc60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 000x1000522afc70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 000x1000522afc80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 000x1000522afc90: 00 00 00 00 00 00 00 00 00 00 00 00 f1 f1 f1 f1
=>0x1000522afca0: 00 00 00 00 00[f2]f2 f2 f2 f2 00 00 00 00 00 000x1000522afcb0: f3 f3 f3 f3 00 00 00 00 00 00 00 00 00 00 00 000x1000522afcc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 000x1000522afcd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 000x1000522afce0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 000x1000522afcf0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
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
==714984==ABORTING

结果表明,这是一个stack-buffer-overflow的错误,对于栈内存来说,也会给出分配栈空间的变量的地方以及对栈内存出现非法访问的调用栈,还分别给出了两个变量的位置。

下面的影子内存可以看出:

  • f1是栈左红区,f2是栈中红区,f3是栈右红区
  • 所有的栈空间都处于f1和f3之间,变量之间用f2隔开
  • arr1是10个int,也就是40个字节,使用5个影子字节,即影子内存中=>所在行的左侧的5个字节
  • arr2是12个int,也就是48个字节,使用6个影子字节,即影子内存中=>所在行的左侧的6个字节
  • [f2]表明访问栈中红区,出现栈的访问越界

4 总结

AddressSanitizer是进行内存异常使用分析的工具,该工具已经集成到编译器中,因此,只能用于分析C/C++语言的内存问题分析。与Valgrind相比,运行速度更快,但是,从场景来说,AddressSanitizer主要用于检测内存的非法使用,当然也包括内存未正确释放的问题,而Valgrind则可以分析出导致内存增长的调用栈。

因此:

  • 如果出现内存偏高的问题,可以使用Valgrind工具分析
  • 如果出现内存导致的core问题,可以使用gdb的watchpoint或者AddressSanitizer分析

这篇关于【Linux】使用AddressSanitizer分析内存非法使用问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/997684

相关文章

Java中流式并行操作parallelStream的原理和使用方法

《Java中流式并行操作parallelStream的原理和使用方法》本文详细介绍了Java中的并行流(parallelStream)的原理、正确使用方法以及在实际业务中的应用案例,并指出在使用并行流... 目录Java中流式并行操作parallelStream0. 问题的产生1. 什么是parallelS

Linux join命令的使用及说明

《Linuxjoin命令的使用及说明》`join`命令用于在Linux中按字段将两个文件进行连接,类似于SQL的JOIN,它需要两个文件按用于匹配的字段排序,并且第一个文件的换行符必须是LF,`jo... 目录一. 基本语法二. 数据准备三. 指定文件的连接key四.-a输出指定文件的所有行五.-o指定输出

Linux jq命令的使用解读

《Linuxjq命令的使用解读》jq是一个强大的命令行工具,用于处理JSON数据,它可以用来查看、过滤、修改、格式化JSON数据,通过使用各种选项和过滤器,可以实现复杂的JSON处理任务... 目录一. 简介二. 选项2.1.2.2-c2.3-r2.4-R三. 字段提取3.1 普通字段3.2 数组字段四.

Linux kill正在执行的后台任务 kill进程组使用详解

《Linuxkill正在执行的后台任务kill进程组使用详解》文章介绍了两个脚本的功能和区别,以及执行这些脚本时遇到的进程管理问题,通过查看进程树、使用`kill`命令和`lsof`命令,分析了子... 目录零. 用到的命令一. 待执行的脚本二. 执行含子进程的脚本,并kill2.1 进程查看2.2 遇到的

详解SpringBoot+Ehcache使用示例

《详解SpringBoot+Ehcache使用示例》本文介绍了SpringBoot中配置Ehcache、自定义get/set方式,并实际使用缓存的过程,文中通过示例代码介绍的非常详细,对大家的学习或者... 目录摘要概念内存与磁盘持久化存储:配置灵活性:编码示例引入依赖:配置ehcache.XML文件:配置

Java 虚拟线程的创建与使用深度解析

《Java虚拟线程的创建与使用深度解析》虚拟线程是Java19中以预览特性形式引入,Java21起正式发布的轻量级线程,本文给大家介绍Java虚拟线程的创建与使用,感兴趣的朋友一起看看吧... 目录一、虚拟线程简介1.1 什么是虚拟线程?1.2 为什么需要虚拟线程?二、虚拟线程与平台线程对比代码对比示例:三

Nginx分布式部署流程分析

《Nginx分布式部署流程分析》文章介绍Nginx在分布式部署中的反向代理和负载均衡作用,用于分发请求、减轻服务器压力及解决session共享问题,涵盖配置方法、策略及Java项目应用,并提及分布式事... 目录分布式部署NginxJava中的代理代理分为正向代理和反向代理正向代理反向代理Nginx应用场景

k8s按需创建PV和使用PVC详解

《k8s按需创建PV和使用PVC详解》Kubernetes中,PV和PVC用于管理持久存储,StorageClass实现动态PV分配,PVC声明存储需求并绑定PV,通过kubectl验证状态,注意回收... 目录1.按需创建 PV(使用 StorageClass)创建 StorageClass2.创建 PV

IDEA和GIT关于文件中LF和CRLF问题及解决

《IDEA和GIT关于文件中LF和CRLF问题及解决》文章总结:因IDEA默认使用CRLF换行符导致Shell脚本在Linux运行报错,需在编辑器和Git中统一为LF,通过调整Git的core.aut... 目录问题描述问题思考解决过程总结问题描述项目软件安装shell脚本上git仓库管理,但拉取后,上l

Redis 基本数据类型和使用详解

《Redis基本数据类型和使用详解》String是Redis最基本的数据类型,一个键对应一个值,它的功能十分强大,可以存储字符串、整数、浮点数等多种数据格式,本文给大家介绍Redis基本数据类型和... 目录一、Redis 入门介绍二、Redis 的五大基本数据类型2.1 String 类型2.2 Hash