Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >LLVM 工具系列 - Address Sanitizer 基本原理介绍及案例分析(1)

LLVM 工具系列 - Address Sanitizer 基本原理介绍及案例分析(1)

作者头像
JoeyBlue
发布于 2023-01-08 01:14:35
发布于 2023-01-08 01:14:35
2.7K00
代码可运行
举报
文章被收录于专栏:代码手工艺人代码手工艺人
运行总次数:0
代码可运行

Address Sanitizer 介绍

LLVM 提供了一系列的工具帮助 C/C++/Objc/Objc++ 开发者检查代码中可能的潜在问题,这些工具包括 Address Sanitizer,Memory Sanitizer,Thread Sanitizer,XRay 等等, 功能各异。

本篇主要介绍可能是最常用的一个工具 Address Sanitizer,它的主要作用是帮助开发者在运行时检测出内存地址访问的问题,比如访问了释放的内存,内存访问越界等。

全部种类如下,也都是非常常见的几类内存访问问题。

  1. Use after free
  2. Heap buffer overflow
  3. Stack buffer overflow
  4. Global buffer overflow
  5. Use after return,
  6. Use after scope
  7. Initialization order bugs
  8. Memory leaks

这里为了便于理解,先介绍一下大概的工作原理。然后从上面几种场景中挑出几个有代表性的介绍一下。

Address Sanitizer 的基本工作原理

我们对一个内存地址的 访问 无外乎两种操作:,也就是

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
*address = ...;  // 写操作
... = *address;  // 读操作

Address Sanitizer 的工作依赖编译器运行时库,当开启 Address Sanitizer 之后, 运行时库将会替换掉 mallocfree 函数,在 malloc 分配的内存区域前后设置“投毒”(poisoned)区域, 使用 free 释放之后的内存也会被隔离并投毒,poisoned 区域也被称为 redzone

这样对内存的访问,编译器会在编译期自动在所有内存访问之前做一下 check 是否被“投毒”。所以以上的代码,就会被编译器改成这样:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
if (IsPoisoned(address)) {
  ReportError(address, kAccessSize, kIsWrite);
}
*address = ...;  // or: ... = *address;

这样的话,当我们不小心访问越界,访问到 poisoned 的内存(redzone),就会命中陷阱,在运行时 crash 掉,并给出有帮助的内存位置的信息,以及出问题的代码位置,方便开发者排查和解决。

Note: 从基本工作原理来看,我们可以获知,打开 Address Sanitizer 会增加内存占用,且因为所有的内存访问之前都会有 check 是否访问了“投毒”区域的内存,会有额外的运行开销,对运行性能造成一定的影响,因此通常只在 Debug 模式或测试场景下打开

更详细的原理参考第二篇 // TODO

如何开启 Address Sanitizer

默认 clang 是不打开 Address Sanitizer 的,需要增加 -fsanitize=address -g 参数,-g 用来在出现问题的报告中,增加有助于 debug 的信息,比如出问题的代码位置和行数等,非常建议带上。

如何使用我们在下个例子里进行展示。

分析一个 Use after free 的 case

来看一个简单的例子, test_use_after_free.c 文件有以下内容:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
  int *p = malloc(sizeof(int));
  free(p);
  return *p;  // 访问了已经释放的内存地址
}

这段代码很简单,在堆上创建了一块 int 大小的内存,随后释放,然后 *p 来读取位于 p 内存地址的值,显然是有问题的。实际场景往往会更杂,free 的位置和访问的位置可能离得很远,不容易发现,而且编译期并不会提示错误。

编译:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
clang -fsanitize=address -g test_use_after_free.c -o use_after_free

运行之后crash,并提供给我们一些错误信息:

这些错误信息很重要,可以协助我们排查出现问题的位置。我们从上往下看,第一行告诉我们了内存地址访问错误类型为 heap-use-after-free,并给出了地址和寄存器的值:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
==65906==ERROR: AddressSanitizer: heap-use-after-free on address 0x000105000730 at pc 0x000102c57f48 bp 0x00016d1ab190 sp 0x00016d1ab188

接下来就是告诉我们是在 test_use_after_free.c 文件的 第 7 行 Read 时出的问题,也就是 return *p 时出现的问题。 接着就是该内存区域是在哪里释放的,就是第 6 行, 以及之前在哪里分配的,也就是第 5 行。 可以说非常清晰。

接下来就是 Shadow 的 bytes,具体这里先按下不表,放到下篇具体实现原理里来具体解释。从图上我标记的箭头可以看出访问的是一块已经释放的堆内存。

Heap buffer overflow 堆内存溢出的 case

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// heap-buffer-overflow.cpp
int main(int argc, char **argv) {
  int *array = new int[100];
  array[0] = 0;
  int res = array[100];  // 内存地址访问越界
  delete [] array;
  return res;
}

编译,这里用的是 C++,因此加上 -lc++ 来使用 libc++ 库

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
clang -fsanitize=address -g -lc++ test_heap_buffer_overflow.cpp -o heap_buffer_overflow

运行 & 错误信息:

分析: 第一行告诉我们错误类型为 heap-buffer-overflow,访问出错的内存地址为 0x00010613a7d4, 我们先记下来。

然后告诉我们是第 5 行的 读操作 导致的, 也就是 int res = array[100]; 这里。

接下来的信息是告诉我们出现错误读操作的内存地址 0x00010613a7d4 是位于 400 bytes 内存的右边 4 个 byte 的位置,根据代码,我们知道这 400bytes,其实就是代码中创建的 100 个 int 值所在的内存地址。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
0x00010403a7d4 is located 4 bytes to the right of 400-byte region [0x00010403a640,0x00010403a7d0)
allocated by thread T0 here:
    #0 0x1025de018 in wrap__Znam+0x74 (libclang_rt.asan_osx_dynamic.dylib:arm64e+0x4e018)
    #1 0x1021d3e6c in main test_heap_buffer_overflow.cpp:3
    #2 0x193e4be4c  (<unknown module>)

但实际中往往更复杂,访问的内存可能是距离很远的一块内存上,虽然也可以从这段错误信息里的 allocated by 的堆栈中找到实际分配这块的内存地址的位置,但是可能跟这个访问地址并没有什么关联,要注意辨别。

我们来这样模拟一下,在 array 后面再创建一个 array2,分配 100 个 int 的空间,然后访问 array 的时候,让其越界到 array2 的后面。为了方便查看,我们这里打印出来 array 和 array2 的内存地址范围。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <cstdio>
int main(int argc, char **argv) {
  int *array = new int[100];
  printf("array: %p\n", array);
  array[0] = 0;
  int *array2 = new int[100];
  printf("array2: %p\n", array2);
  int res = array[(array2-array + 100)];  // 首先肯定是越界了,甚至越界到 array2 的右边区域了
  delete [] array;
  return res;
}

我们来看下错误信息:

第二段错误信息里,相当于告诉我们访问的这块内存位于 array2 的紧挨着的右边的位置, 但是这个内存位置其实和访问出错并无关系,此时,这个位置信息价值就不大了,应该参考第一段错误信息(红框位置),根据出现访问问题的源代码位置来分析即可,第二段相当于一个辅助的信息。

Note: 到这里大家可能会思考一个问题,如果上面访问 array 的代码,正好越界到 array2 的地址合法范围内,比如,int res = array[(array2-array + 1)], 会不会被检测到并 crash 呢? 很遗憾,这种 case 虽然越界了,但根据前面的运行原理来看,访问的内存区域并未被“投毒”(poisoned),因此不会被检测到越界,也不会 crash。

最后我们再看一个检查内存泄漏的 case。

分析一个 Memory leak 的 case

我们在 test_memory_leak.cpp 模拟一个 leak:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include <stdlib.h>

class BadClass {
public:
  BadClass(int value): value_(new int(value)) {}
  ~BadClass() {
    // 没有 delete value_ 导致泄漏
  }

private:
  int *value_;
};

int main() {
  BadClass *bad = new BadClass(10);
  delete bad;
  return 0;
}

Note: Memory leak 检测目前不支持 ARM,因此 M1 芯片的 MBP 也是不支持的, 运行时会出现以下的错误提示。 ASAN_OPTIONS=detect_leaks=1 ./test_memory_leak.out ==39355==AddressSanitizer: detect_leaks is not supported on this platform. [1] 39355 abort ASAN_OPTIONS=detect_leaks=1 ./test_memory_leak.out

这里我在 X86_64 的 Linux 机器上进行测试。

编译:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
clang -fsanitize=address -g -lstdc++ test_memory_leak.cpp -o test_memory_leak

运行:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# LeakSanitizer 在 X86 的 linux 上开启 Address Sanitizer 时默认打开的,因此直接运行即可
./test_memory_leak
# 如果是 Intel 版本的 macos,默认没有打开 LeakSanitizer,需要在运行前面增加一个环境变量来开启
ASAN_OPTIONS=detect_leaks=1 ./test_memory_leak

运行结果:

第一行告诉我们检测到了内存泄露,然后告诉我们泄漏了一个对象,共 4 个字节。泄漏的的位置是在 test_memory_leak.cpp 文件的第 15 行。

Summary

内存问题是 C/C++ 项目中比较头疼的问题,为了解决这类的问题,本篇文章主要介绍了 LLVM 的 Address Sanitizer 工具,以及基本的工作的原理;接着分析了 C/C++ 中几种常见的内存地址访问错误的 case,以及如何从错误信息中提取关键的信息进行排查问题。

其余的几种内存问题,大家可以自行模拟来尝试,非常建议在开发阶段 Debug 或者测试场景中打开 Address Sanitizer 提前暴露很多内存问题。

Ref & 扩展阅读

  1. Google AddressSanitizer Wiki
  2. Hardware-assisted AddressSanitizer
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-01-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
C++内存问题排查攻略
GCC提供了-fstack-usage选项,能输出每个函数栈的最大使用量。开启后,为每个编译目标创建.su文件,每行包括函数名、字节数、修饰符(static/dynamic/bounded)中的一个或多个。修饰符的含义如下:
腾讯技术工程官方号
2024/08/14
3500
C++内存问题排查攻略
LLVM 工具系列 - Address Sanitizer 实现原理(2)
上篇文章 「Address Sanitizer 基本原理介绍及案例分析」里我们简单地介绍了一下 Address Sanitizer 基础的工作原理,这里我们再继续深挖一下深层次的原理。
JoeyBlue
2023/01/08
7670
asan内存检测工具实例
GCC和CLANG都已经集成了功能,编译时加编译选项即可。主要是-fsanitize=address,其他便于调试。
mingjie
2023/10/13
6650
ASAN和HWASAN原理解析
由于虚拟机的存在,Android应用开发者们通常不用考虑内存访问相关的错误。而一旦我们深入到Native世界中,原本面容和善的内存便开始凶恶起来。这时,由于程序员写法不规范、逻辑疏漏而导致的内存错误会统统跳到我们面前,对我们嘲讽一番。
Linux阅码场
2020/07/07
4K0
ASAN和HWASAN原理解析
sanitizer工具集
Sanitizers是谷歌发起的开源工具集,包括了Address Sanitizer, undefined behavior Sanitizer, Thread Sanitizer, Leak Sanitizer。GCC从4.8版本开始支持Address sanitizer和Thread Sanitizer,4.9版本开始支持Leak Sanitizer和undefined behavior Sanitizer。
全栈程序员站长
2022/11/17
1.3K0
启用内存泄漏/越界检查工具
只需要添加几行编译选项即可启用内存泄漏/越界检查工具。 注意:目前仅支持GCC 4.8版本以上编译工具,建议使用GCC 4.9版本以上。 0x01 编译选项 开启内存泄露检查功能:-fsanitize=leak 开启地址越界检查功能:-fsanitize=address 开启越界详细错误信息:-fno-omit-frame-pointer 0x02 以Qt工程为例子 .pro项目文件: SOURCES += main.cpp # -fsanitize=leak意思为开启内存泄露检查 QMAKE_CXXFL
Qt君
2020/07/16
4.6K0
Android Address Sanitizer (ASan) 原理简介
前面介绍了 NDK 开发中快速上手使用 ASan 检测内存越界等内存错误的方法,现分享一篇关于 ASan 原理介绍的文章。
字节流动
2021/06/09
5.4K0
Android Address Sanitizer (ASan) 原理简介
nginx下使用asan和valgrind两个静态检查工具
valgrind安装:参考:https://blog.csdn.net/justheretobe/article/details/52986461
用户1215536
2019/09/25
1.8K0
技术解码 | 内存问题的分析与定位
本期的技术解码,为您解析 编程中,内存问题的分析与定位方法 对编程语言设计来说,内存管理分为两大类:手动内存管理(manual memory management) 和垃圾回收(garbage collection). 常见的如C、C++使用手动内存管理,Java使用垃圾回收。本文主要关注手动内存管理。 GC GC使内存管理自动化,缺点是引入了GC时不可预测的暂停(unpredictable stall),对实时性要求高的场景不适用。现代的GC实现一直朝着减小“stop-the-world"影
腾讯云音视频
2021/04/29
4.5K0
高并发性能测试经验分享(下)
本文介绍了如何通过定制化工具链分析定位解决了因内核栈溢出导致的程序core dump问题,以及如何使用AddressSanitizer工具定位解决了因内存泄漏导致的程序性能问题。通过这些方法,可以更高效地解决程序中的core dump和内存泄漏问题,提高程序的稳定性和性能。
腾讯技术工程官方号
2017/09/05
3.8K0
高并发性能测试经验分享(下)
浅谈「内存调试技术」
内存问题在 C/C++ 程序中十分常见,比如缓冲区溢出,使用已经释放的堆内存,内存泄露等。
天存信息
2021/05/11
1K0
浅谈「内存调试技术」
【C语言】内存布局大揭秘 ! -《堆、栈和你从未听说过的内存角落》
在C语言中,内存布局是程序运行时非常重要的概念。内存布局直接影响程序的性能、稳定性和安全性。理解C程序的内存布局,有助于编写更高效和可靠的代码。本文将详细介绍C程序的内存布局,包括代码段、数据段、堆、栈等部分,并提供相关的示例和应用。
LuckiBit
2024/12/11
2500
【C语言】内存布局大揭秘 ! -《堆、栈和你从未听说过的内存角落》
【论文速读 | USENIX Security‘2022】Debloating Address Sanitizer
论文主要研究的问题是如何解决地址消毒器(Address Sanitizer,ASan)(翻译比较抽象,不如直接用 ASan 表示)在检测内存错误时所面临的高运行时开销问题。ASan 是一种广泛使用的内存错误检测工具,但因其开销较大,限制了其在更多场景下的应用。
Lokinli
2024/06/09
1910
在使用asan的时候,如果我们想关闭/取消:use-after-poision 检测
使用这个allow_user_poisoning=0,来取消 以这个例子为例: // example1.cpp // use-after-poison error #include <stdlib.h> extern "C" void __asan_poison_memory_region(void *, size_t); int main(int argc, char **argv) { char *x = new char[16]; x[10] = 0; __asan_poi
Gxjun
2021/11/10
1.8K0
KASAN实现原理【转】
KASAN是一个动态检测内存错误的工具。KASAN可以检测全局变量、栈、堆分配的内存发生越界访问等问题。功能比SLUB DEBUG齐全并且支持实时检测。越界访问的严重性和危害性通过我之前的文章(SLUB DEBUG技术)应该有所了解。正是由于SLUB DEBUG缺陷,因此我们需要一种更加强大的检测工具。难道你不想吗?KASAN就是其中一种。KASAN的使用真的很简单。但是我是一个追求刨根问底的人。仅仅止步于使用的层面,我是不愿意的,只有更清楚的了解实现原理才能更加熟练的使用工具。不止是KASAN,其他方面我也是这么认为。但是,说实话,写这篇文章是有点底气不足的。因为从我查阅的资料来说,国内没有一篇文章说KASAN的工作原理,国外也是没有什么文章关注KASAN的原理。大家好像都在说How to use。由于本人水平有限,就根据现有的资料以及自己阅读代码揣摩其中的意思。本文章作为抛准引玉,如果有不合理的地方还请指正。
233333
2019/01/03
2.6K0
Kasan - Linux 内核的内存检测工具
https://www.ibm.com/developerworks/cn/linux/1608_tengr_kasan/index.html
Linux阅码场
2019/10/08
5.9K0
Kasan - Linux 内核的内存检测工具
Linux内核内存检测工具KASAN
KASAN 是 Kernel Address Sanitizer 的缩写,它是一个动态检测内存错误的工具,主要功能是检查内存越界访问和使用已释放的内存等问题。KASAN 集成在 Linux 内核中,随 Linux 内核代码一起发布,并由内核社区维护和发展。本文简要介绍 KASAN 的原理及使用方法。
233333
2020/11/26
9.2K0
Linux内核内存检测工具KASAN
libfuzzer 文档
就是变异,覆盖率那些都给你做好了,你只需要定义LLVMFuzzerTestOneInput,将编译的数据喂给要fuzz的目标函数就行
用户1423082
2024/12/31
650
C/C++生态工具链——内存泄露检测工具Valgrind
Valgrind提供了很多组件,这些组件可以用来分析和调试程序、检测内存是否正常使用、分析程序的性能等。Valgrind有自己的内核,它可以提供一个虚拟的CPU来运行程序,并完成程序的调试和剖析等任务。
Coder-ZZ
2023/02/23
6.1K0
C/C++生态工具链——内存泄露检测工具Valgrind
android native内存检测方案(二)
android native 代码内存泄露 定位方案(一) 什么是 AddressSanitizer clang 是一个 C、C++、Objective-C 编程语言的编译器前端。它采用 了底层虚拟机作为其后端。它的目标是提供一个 GNU 编译器套装 (GCC)的替代品, 作者是克里斯·拉特纳,在苹果公司的赞助下进 行开发。 AddressSanitizer 是 clang 中的一个内存错误检测器,它可以检测到 以下问题: Out-of-bounds accesses to heap, stack an
用户1263308
2018/02/02
4.6K0
android native内存检测方案(二)
相关推荐
C++内存问题排查攻略
更多 >
LV.1
这个人很懒,什么都没有留下~
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验