内存问题的排查工具和方法– Clang的AddressSanitizer

本文介绍如何使用Clang的AddressSanitizer工具检测C/C++程序中的内存错误,包括越界访问、释放后使用、重复释放及内存泄漏等问题。通过示例代码演示了如何编译并运行程序来定位内存错误。

https://blog.youkuaiyun.com/fishmai/article/details/72863326

1 概述

Valgrind可以有效地监测处大多数内存问题,你肯定忍不住会想,既然c/c++的内存问题这么常见,为什么不在编译器中加入内存问题检测的功能呢? 很可惜,GCC中还目前还不支持内存检测,可喜的是,clang支持。这里我们看看如何用clang发现内存问题

2 clang

clang 是一个C、C++、Objective-C编程语言的编译器前端。它采用了底层虚拟机作为其后端。它的目标是提供一个GNU编译器套装(GCC)的替代品, 作者是克里斯·拉特纳,在苹果公司的赞助下进行开发。

3 内存泄漏监测

AddressSanitizer是clang中的一个内存错误检测器,它可以检测到以下问题:

  • Out-of-bounds accesses to heap, stack and globals
  • Use-after-free
  • Use-after-return (to some extent)
  • Double-free, invalid free
  • Memory leaks (experimental)

使用clang编译代码时用-fsanitize=address就能打开AddressSanitizer工具,为了在检测到内存错误时打印出您的程序调用栈,需要在编译时加上选项 -fno-omit-frame-pointer选项,同时为了得出更清晰的调用栈信息,请用-O1选项编译程序。

4 示例代码

下面我用clang3.4做一个示例

 1:  int main()
 2:  {
 3:      char *p = malloc(sizeof(char) * 10);
 4:      if (p == NULL) {
 5:          return 0;
 6:      }
 7:  
 8:      struct elem *e = malloc(sizeof(struct elem));
 9:      if (e == NULL) {
10:          free(p);
11:          return 0;
12:      }
13:  
14:      e->a = 10;
15:      e->b = 10.10;
16:      e->c = p;
17:  
18:      double *xx = &e->b;
19:  
20:      printf("%f\n", *xx);
21:  
22:      free(e);
23:  
24:      printf("%f\n", *xx);
25:  
26:      return 0;
27:  }

上面的代码中有两处问题,一是p未被释放,导致了内存泄漏;二是xx指向了一块被释放了的内存。我们看看怎么用clang检测这两个问题

4.1 编译它

1:  clang -O1 -g -fsanitize=address -fno-omit-frame-pointer -o core core.c

4.2 用AddressSanitizer监测进程的内存泄漏

直接运行core文件,它就会自动打印出检测到的内存错误

 1:  [cobbliu@kftest25 test]$ ./core
 2:  10.100000
 3:  =================================================================
 4:  ==11254==ERROR: AddressSanitizer: heap-use-after-free on address 0x60300000efe8 at pc 0x48211a bp 0x7fff2c776450 sp 0x7fff2c776448
 5:  READ of size 8 at 0x60300000efe8 thread T0
 6:      #0 0x482119 in main /home/cobbliu/test/core.c:35
 7:      #1 0x36a101ecdc in __libc_start_main (/lib64/libc.so.6+0x36a101ecdc)
 8:      #2 0x481f3c in _start (/home/cobbliu/test/core+0x481f3c)
 9:  
10:  0x60300000efe8 is located 8 bytes inside of 24-byte region [0x60300000efe0,0x60300000eff8)
11:  freed by thread T0 here:
12:      #0 0x46bca9 in __interceptor_free /home/ads/build23_6u0_x64/workspace/t-coresystem-clang/label/build23_6u0_x64/t-coresystem-clang/llvm-3.4/projects/compiler-rt/lib/asan/asan_malloc_linux.cc:64
13:      #1 0x4820c0 in main /home/cobbliu/test/core.c:32
14:      #2 0x36a101ecdc in __libc_start_main (/lib64/libc.so.6+0x36a101ecdc)
15:  
16:  previously allocated by thread T0 here:
17:      #0 0x46be29 in malloc /home/ads/build23_6u0_x64/workspace/t-coresystem-clang/label/build23_6u0_x64/t-coresystem-clang/llvm-3.4/projects/compiler-rt/lib/asan/asan_malloc_linux.cc:74
18:      #1 0x48202a in main /home/cobbliu/test/core.c:18
19:      #2 0x36a101ecdc in __libc_start_main (/lib64/libc.so.6+0x36a101ecdc)
20:  
21:  SUMMARY: AddressSanitizer: heap-use-after-free /home/cobbliu/test/core.c:35 main
22:  Shadow bytes around the buggy address:
23:    0x0c067fff9da0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
24:    0x0c067fff9db0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
25:    0x0c067fff9dc0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
26:    0x0c067fff9dd0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
27:    0x0c067fff9de0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
28:  =>0x0c067fff9df0: fa fa fa fa fa fa fa fa fa fa fa fa fd[fd]fd fa
29:    0x0c067fff9e00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
30:    0x0c067fff9e10: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
31:    0x0c067fff9e20: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
32:    0x0c067fff9e30: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
33:    0x0c067fff9e40: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
34:  Shadow byte legend (one shadow byte represents 8 application bytes):
35:    Addressable:           00
36:    Partially addressable: 01 02 03 04 05 06 07
37:    Heap left redzone:     fa
38:    Heap right redzone:    fb
39:    Freed heap region:     fd
40:    Stack left redzone:    f1
41:    Stack mid redzone:     f2
42:    Stack right redzone:   f3
43:    Stack partial redzone: f4
44:    Stack after return:    f5
45:    Stack use after scope: f8
46:    Global redzone:        f9
47:    Global init order:     f6
48:    Poisoned by user:      f7
49:    ASan internal:         fe
50:  ==11254==ABORTING

可以看到,程序在提示core.c的第35行有个heap-use-after-free的错误,而且在最后还有个summary,把出错的代码位置和相应的栈信息打了出来。

5 示例代码2

上面的代码做一些小修改,我们看看它对double-free问题的检测

1:  /...
2:      struct elem *e2 = e;
3:      free(e);
4:      free(e2);
5:  /...
6:  }

按照上面相同的方法编译并运行后,提示信息如下:

 1:  [cobbliu@kftest25 test]$ ./core
 2:  10.100000
 3:  =================================================================
 4:  ==11952==ERROR: AddressSanitizer: attempting double-free on 0x60300000efe0 in thread T0:
 5:      #0 0x46bca9 in __interceptor_free /home/ads/build23_6u0_x64/workspace/t-coresystem-clang/label/build23_6u0_x64/t-coresystem-clang/llvm-3.4/projects/compiler-rt/lib/asan/asan_malloc_linux.cc:64
 6:      #1 0x4820bd in main /home/cobbliu/test/core.c:34
 7:      #2 0x36a101ecdc in __libc_start_main (/lib64/libc.so.6+0x36a101ecdc)
 8:      #3 0x481f3c in _start (/home/cobbliu/test/core+0x481f3c)
 9:  
10:  0x60300000efe0 is located 0 bytes inside of 24-byte region [0x60300000efe0,0x60300000eff8)
11:  freed by thread T0 here:
12:      #0 0x46bca9 in __interceptor_free /home/ads/build23_6u0_x64/workspace/t-coresystem-clang/label/build23_6u0_x64/t-coresystem-clang/llvm-3.4/projects/compiler-rt/lib/asan/asan_malloc_linux.cc:64
13:      #1 0x4820b0 in main /home/cobbliu/test/core.c:33
14:      #2 0x36a101ecdc in __libc_start_main (/lib64/libc.so.6+0x36a101ecdc)
15:  
16:  previously allocated by thread T0 here:
17:      #0 0x46be29 in malloc /home/ads/build23_6u0_x64/workspace/t-coresystem-clang/label/build23_6u0_x64/t-coresystem-clang/llvm-3.4/projects/compiler-rt/lib/asan/asan_malloc_linux.cc:74
18:      #1 0x482026 in main /home/cobbliu/test/core.c:18
19:      #2 0x36a101ecdc in __libc_start_main (/lib64/libc.so.6+0x36a101ecdc)
20:  
21:  SUMMARY: AddressSanitizer: double-free /home/ads/build23_6u0_x64/workspace/t-coresystem-clang/label/build23_6u0_x64/t-coresystem-clang/llvm-3.4/projects/compiler-rt/lib/asan/asan_malloc_linux.cc:64 __interceptor_free
22:  ==11952==ABORTING

可以看到,AddressSanitizer报错,说core.c的34行有一个double-free的错误

6 示例代码3

上面的代码做一些小修改,把释放e的代码注释掉,看看它对内存泄漏的检测

1:  /...
2:      //free(e);
3:  /...
4:  }

按照上面相同的方法编译并运行后,提示信息如下:

1:  [cobbliu@kftest25 test]$ ./core
2:  10.100000

可以看到,对内存泄漏,AddressSanitizer无法检测出来 clang中有一个工具叫LeakSanitizer,它的设计目标是用来检测内存泄漏。直到3.7版,LeakSanitizer也是在实验阶段。

7 AddressSanitizer的缺陷

  • AddressSanitizer工具编译的程序的堆栈和栈占用比原生程序的大。
  • AddressSanitizer不支持静态编译

 

更新:gcc4.8版本之后,有了对AddressSanitizer的支持!

 

Date: 2015-04-16T21:24+0800

Author: Cobbliu

Org version 7.9.3f with Emacs version 24

Validate XHTML 1.0

<think>好的,用户现在想了解如何使用AddressSanitizer检查内存泄漏问题。我需要根据之前的对话现有的引用内容来构建一个详细的回答。首先,回顾用户之前的报错是关于堆栈溢出的问题,我提供了解决方案,现在用户转向内存泄漏的检测,这可能是因为他们遇到了新的问题或者想进一步优化代码。 首先,我需要明确用户的需求:他们想要一个使用AddressSanitizer(ASAN)检查内存泄漏的教程。根据引用内容,用户已经提供了一些关于ASAN的信息,比如如何配置CMake,内存泄漏的报告格式,以及抑制无关泄漏的方法。因此,我需要整合这些信息,并提供一个结构化的步骤指南。 接下来,我需要确保回答的结构清晰,符合用户之前的示例结构,即步骤分明的解决方案相关问题。同时,要引用用户提供的引用内容,例如引用[1]描述了内存泄漏的定义工具,引用[4]提到CMake配置,引用[5]涉及编译选项等。 需要注意用户可能的环境差异,比如不同的操作系统(Linux、macOS、Windows WSL)构建工具(GCC、Clang、CMake)。因此,在教程中需要涵盖这些不同情况,确保用户无论使用哪种环境都能找到对应的指导。 另外,用户可能对如何解读ASAN的输出报告不太清楚,需要详细解释错误信息的各个部分,如泄漏类型、分配位置等,如引用[1]中的示例所示。同时,引用[2]提到的抑制技巧也应该包含在内,帮助用户过滤掉第三方库的干扰。 需要验证步骤的正确性,例如编译命令中的选项是否正确,比如-fsanitize=address,以及运行时环境变量的设置。同时,提醒用户注意ASAN的局限性,比如对栈变量全局变量的泄漏检测限制,避免用户误以为ASAN能捕捉所有类型的内存泄漏。 最后,生成的相关问题需要与主题紧密相关,帮助用户深入理解,例如如何抑制误报、与其他工具对比、集成到CI/CD等,这些都是用户可能感兴趣的后续步骤。</think>### 如何使用AddressSanitizer检测内存泄漏 #### **一、基本概念** AddressSanitizer(ASan)是Google开发的内存错误检测工具,可检测**堆内存泄漏**、**越界访问**、**悬垂指针**等问题。其内存泄漏检测能力尤其强大,能精确定位未释放的内存分配位置[^1][^3][^4]。 --- #### **二、配置方法** ##### **1. 编译器启用ASan** - **GCC/Clang**:添加编译选项 `-fsanitize=address` ```bash gcc -fsanitize=address -g your_program.c -o output ``` - **CMake集成**(引用[^4]): ```cmake set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer") ``` ##### **2. 动态库支持** - **Linux/macOS**:需安装`libasan`库(部分系统默认包含) - **Windows WSL**:通过GCC或Clang工具链支持 --- #### **三、检测内存泄漏步骤** ##### **1. 编译并运行程序** ```bash # 编译示例 gcc -fsanitize=address -g leak_example.c -o leak_test # 运行程序(ASan自动启用内存泄漏检测) ./leak_test ``` ##### **2. 解读泄漏报告** 当检测内存泄漏时,ASan会输出如下格式(引用[^1][^3]): ``` ================================================================= ==12345==ERROR: LeakSanitizer: detected memory leaks Direct leak of 40 byte(s) in 1 object(s) allocated from: #0 0x7f8a5b3d72b0 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.6+0xb12b0) #1 0x55d6c8b6c1a9 in main leak_example.c:5 #2 0x7f8a5a7c00b2 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x270b2) SUMMARY: AddressSanitizer: 40 byte(s) leaked in 1 allocation(s) ``` - **泄漏类型**:`Direct leak`(直接未释放)或`Indirect leak`(指针丢失导致无法释放) - **分配位置**:精确到源代码行号(需`-g`编译选项) ##### **3. 典型泄漏场景检测** - **未释放的malloc**: ```c void leak_func() { int* ptr = malloc(100 * sizeof(int)); // 未释放 } ``` - **异常路径未释放**: ```c void risky_alloc() { int* buf = malloc(1024); if (error_condition) return; // 此处泄漏 free(buf); } ``` --- #### **四、高级技巧** ##### **1. 抑制误报(引用[^2])** 创建抑制文件`asan.supp`: ``` leak:libX11.so leak:libglib-2.0.so ``` 运行时指定: ```bash ASAN_OPTIONS=suppressions=asan.supp ./your_program ``` ##### **2. 运行时配置** - **立即终止检测**:`ASAN_OPTIONS=halt_on_error=0`(继续运行后续检测- **输出文件记录**:`ASAN_OPTIONS=log_path=asan.log` ##### **3. 交叉编译(引用[^5])** 适用于嵌入式开发: ```bash arm-linux-gnueabihf-gcc -fsanitize=address -fno-omit-frame-pointer memtest.c -o memtest ``` --- #### **五、验证与调试** 1. **最小化复现**:逐步注释代码定位泄漏点 2. **结合Valgrind**:使用`valgrind --leak-check=full`交叉验证[^1] 3. **代码审查重点**:检查所有`malloc/calloc`是否匹配`free` --- ### 关键注意事项 - **栈/全局变量泄漏**:ASan无法检测未释放的栈或全局变量内存(仅检测内存- **性能影响**:ASan会使程序运行速度降低约2倍,建议仅在调试时启用 - **与其他工具兼容性**:不要与Valgrind同时使用 --- ### 相关问题 1. **如何区分AddressSanitizer报告的直接泄漏间接泄漏?** 2. **AddressSanitizer检测到系统库的内存泄漏时如何过滤?** 3. **内存泄漏检测工具(ASan vs Valgrind)的性能对比如何?** 4. **如何在CMake项目中自动化集成ASan检测?**
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值