【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 leaks

Direct 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:58

SUMMARY: 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:58

SUMMARY: 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:58

previously 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:58

SUMMARY: 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 00
  0x0c047fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c047fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c047fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c047fff7ff0: 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 fa
  0x0c047fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8050: 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:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
  Shadow 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 00
  0x0c047fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c047fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c047fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c047fff7ff0: 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 fa
  0x0c047fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8050: 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:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
  Shadow 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 00
  0x1000522afc60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000522afc70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000522afc80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000522afc90: 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 00
  0x1000522afcb0: f3 f3 f3 f3 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000522afcc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000522afcd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000522afce0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000522afcf0: 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:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
  Shadow 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分析

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/633960.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Leetcode刷题笔记2:数组基础2

导语 leetcode刷题笔记记录&#xff0c;本篇博客记录数组基础1部分的题目&#xff0c;主要题目包括&#xff1a; 977.有序数组的平方 &#xff0c;209.长度最小的子数组 &#xff0c;59.螺旋矩阵II 知识点 滑动窗口 所谓滑动窗口&#xff0c;就是不断的调节子序列的起始位…

(二)vForm 动态表单设计器之下拉、选择

系列文章目录 &#xff08;一&#xff09;vForm 动态表单设计器之使用 目录 系列文章目录 前言 一、后端需提供接口 二、组件配置 总结 前言 动态表单下拉、选择等组件&#xff0c;大概率要使用数据库中的数据&#xff0c;那么vForm如何拿到数据库中的数据呢&#xff1f;跟随…

Xed编辑器开发第二期:使用Rust从0到1写一个文本编辑器

第三篇 这部分接着处理用户退出命令以及一些其他新功能&#xff1b; 3.1 使用CtrlQ退出 modifiers: event::KeyModifiers::CONTROL,使用CONTROL替换之前的NONE值即可&#xff1b; 3.2 重构键盘输入 让我们重构我们的代码&#xff0c;以便我们有一个用于低级按键读取的函数&…

php部分特性漏洞学习

php部分函数漏洞学习 简单总结一些我遇到的ctf中的php的一些函数或特性的漏洞&#xff0c;我刷题还是太少了&#xff0c;所以很多例子来自ctfshow&#xff0c;以后遇到相关赛题再更新 1.MD5和其他hash 弱类型比较 php中&#xff0c;有两中判断相等的符号&#xff0c;和&…

flutter使用dbus插件时,在终端无法使用“dart-dbus”命令

不用flutter的人&#xff0c;可能都不会找到这儿&#xff0c;遇到这个问题&#xff0c;所以这里默认flutter已经装过了&#xff0c;且对flutter如何使用插件也有所了解了。 由于我在项目中用到了dbus插件&#xff0c;用法如图所示&#xff0c;我需要使用这条命令来生成一个sou…

前端javascript包管理,npm升级用pnpm

一 pnpm 介绍 pnpm&#xff08;Package Manager&#xff09;是一个快速、节省磁盘空间的 JavaScript 包管理器&#xff0c;它是 Node.js 生态系统中 npm 的一个替代品。pnpm 解决了传统包管理工具在处理依赖时的一些痛点&#xff0c;特别是关于存储空间使用和依赖地狱的问题。…

vite vue-cli 之vue3安装Vue devtools调试工具

一.vite的官方配置-vite-plugin-vue-devtools vite.config.js import { fileURLToPath, URL } from node:urlimport vue from vitejs/plugin-vue import vueJsx from vitejs/plugin-vue-jsx import { defineConfig } from vite import VueDevTools from vite-plugin-vue-devt…

Android JetPack ViewModel

一、什么是ViewModel&#xff1f; Android ViewModel在我们使用MVVM开发模式时会经常用到&#xff0c;对我来说就是好用&#xff0c;好维护。 它相对于MVC模式&#xff0c; 一来可以减少Activity层的代码&#xff0c;可以把一些业务逻辑和对数据的交互到ViewModel层去&#…

Excel模板计算得出表格看板

背景 表格看板及导出&#xff0c;单元格时间年是根据筛选器时间变化的 较往年和往年是计算单元格 思路 1.通过excel模板来把数据填入excel再数据清洗得到数据返回前端 2.数据填充&#xff0c;通过行列作为key 列如&#xff1a;key整体20241月&#xff0c;根据key匹配数据填…

计算机操作系统总结(1)

1操作系统的概念&#xff08;定义&#xff09;功能和目标 (1)什么是操作系统&#xff1f; &#xff08;2&#xff09;操作系统的功能和目标—作为系统资源的管理者 &#xff08;3&#xff09;操作系统的功能和目标—向上层提供方便易用的服务 &#xff08;4&#xff09;操作系…

中银基金软件开发工程师春招群面记录

本文介绍2024届春招中&#xff0c;中国银行下属中银基金管理有限公司的软件开发工程师岗位1场面试的基本情况、提问问题等。 2024年04月投递了中国银行的共计4个部门或单位&#xff0c;包括中银基金管理有限公司的软件开发工程师岗位&#xff0c;暂时不清楚所在部门。目前完成了…

平均分配问题

将dwd_phone_shop这六行数据按照1 3 5,2 4 6的排序分为两组,与dwd_phone_base连接,分别与为其中1 3 5 为shop_Id为1的那一组 2 4 6 为shop_Id为2 的那一组 取余法 开窗函数法

南加州大学字节提出MagicPose,提供逼真的人类视频生成,实现生动的运动和面部表情传输,以及不需要任何微调的一致的野外零镜头生成。

MagicPose可以精确地生成外观一致的结果&#xff0c;而原始的文本到图像模型(如Stable Diffusion和ControlNet)很难准确地保持主体身份信息。 此外&#xff0c;MagicPose模块可以被视为原始文本到图像模型的扩展/插件&#xff0c;而无需修改其预训练的权重。 相关链接 论文链…

javaSwing飞机订票系统

摘要 Java swing实现的飞机票预定系统&#xff0c;系统数据库原本采用的是Oracle&#xff0c;我又改了一个mysql版本的&#xff0c;所以这套系统有两个版本&#xff0c;一个是mysql数据库版的&#xff0c;一个是Oracle数据库版 一&#xff0e; 已经完成的功能 &#xff1a; …

Linux基础命令[27]-gpasswd

文章目录 1. gpasswd 命令说明2. gpasswd 命令语法3. gpasswd 命令示例3.1 不加参数3.2 -a&#xff08;将用户加入组&#xff09;3.3 -d&#xff08;从组中删除用户&#xff09;3.4 -r&#xff08;删除组密码&#xff09;3.5 -M&#xff08;多个用户一起加入组&#xff09;3.6 …

excel里如何将数据分组转置?

这个表格怎样转换为下表&#xff1f;按照国家来分组&#xff0c;把不同年份对应的不同序列值进行转置&#xff1f;&#xff1f; 这演示用数据透视表就完成这个数据转换。 1.创建数据透视表 选中数据中任意单元格&#xff0c;点击插入选项卡&#xff0c;数据透视表&#xff0c;…

C++设计模式|结构型 适配器模式

1.什么是适配器模式&#xff1f; 可以将⼀个类的接⼝转换成客户希望的另⼀个接⼝&#xff0c;主要⽬的是 充当两个不同接⼝之间的桥梁&#xff0c;使得原本接⼝不兼容的类能够⼀起⼯作。 2. 适配器模式的组成 &#xff08;1&#xff09;接口类&#xff0c;给客户端调用&…

vue打包部署到springboot,通过tomcat运行

tomcat默认端口 8080springboot端口 9132vue 端口 9131 框架 项目是基于SpringBootVue前后端分离的仓库管理系统 后端&#xff1a;SpringBoot MybatisPlus前端&#xff1a;Node.js Vue element-ui数据库&#xff1a;mysql 一. 打包Vue项目 cmd中输入命令 npm run build 后…

PHP在线制作表白网源码

PHP在线制作表白网源码&#xff0c;送女友个惊喜吧&#xff0c;无数据库&#xff0c;上传就能用&#xff0c;后台/admin&#xff0c;账号密码都是admin 百度网盘&#xff1a;https://pan.baidu.com/s/1rbD2_8IsP9UPLK-cdgEXfA?pwdre59

前端绘制流程节点数据

根据数据结构和节点的层级、子节点id&#xff0c;前端自己绘制节点位置和关联关系、指向、已完成节点等 <template><div><div>通过后端节点和层级&#xff0c;绘制出节点以及关联关系等</div><div class"container" ref"container&…