C/C++ 内存治理神器 - Google Sanitizers

Posted 芥末的无奈

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C/C++ 内存治理神器 - Google Sanitizers相关的知识,希望对你有一定的参考价值。

简介

Google sanitizers 是由 Google 设计的用于动态代码分析的工具。它属于 LLVM 一部分,并且是开源的。它包括

  • AddressSanitizer (ASan),检测内存问题,包括了 LeakSanitizer
  • LeakSanitizer (LSan),检测内存泄漏问题
  • ThreadSanitizer (TSan),检测数据竞争问题
  • UndefinedBehaviorSanitizer (UBSsan),检测未定义行为
  • MemorySanitizer (MSan),检测未初始化内存问题

Sanitizers 在 Clang(3.1 版本开始)和 GCC(4.8 版本开始)中实现。目前,在 Linux x86_64 上支持所有 sanitizers;在 Windows 10 下可以使用 MSVC 工具链下的 clang-cl 来使用 AddressSanitizer;对于 macOS,支持 AddressSanitizer、ThreadSanitizer 和 UndefinedBehaviorSanitizer

使用

使用非常简单,只需要添加编译 sanitize 编译选项即可,例如你可以在 CMakeLists.txt 中这么写:

set(CMAKE_CXX_FLAGS "$CMAKE_CXX_FLAGS -fsanitize=address")

通过 -fsanitize 来选择开启哪个 sanitizer,选项包括:

  • address 开启 AddressSanitizer
  • leak 开启 LeakSanitizer
  • thread 开启 ThreadSanitizer
  • undefined 开启 UndefinedBehaviorSanitizer
  • memory 开启 MemorySanitizer

我们通常在 Debug 模式下使用 Sanitizers,这样方便定位出问题的代码位置。

日常开发中,我们最常用的是 Asan,它能够帮助的我们分析内存问题,包括:

  • Use after free(dangling pointer dereference):内存释放后继续使用,悬挂指针问题。
  • Heap buffer overflow:堆内存溢出
  • Stack buffer overflow:栈内存溢出
  • Global buffer overflow:全局内存溢出(如全局变量)
  • Use after return:局部变量在函数返回后使用
  • Use after scope:局部变量在作用范围外使用
  • Initialization order bugs:初始化顺序问题
  • Memory leaks:内存泄漏

AddressSanitizer 列举了上述经典错误的代码片段,例如内存后继续使用

// RUN: clang -O -g -fsanitize=address %t && ./a.out
int main(int argc, char **argv) 
  int *array = new int[100];
  delete [] array;
  return array[argc];  // BOOM

内存泄漏的检查,这里需要特别说明一下。AddressSanitizer 本身包含了 LeakSanitizer,你可以在运行程序时添加 ASAN_OPTIONS=detect_leaks=1 开启它。例如

// RUN: clang -O -g -fsanitize=address main.cpp && ASAN_OPTIONS=detect_leaks=1 ./a.out

#include <stdlib.h>
void *p;
int main() 
    p = malloc(7);
    p = 0; // The memory is leaked here.
    return 0;

当然,你还可以单独使用 LeakSanitizer 来检测内存泄漏问题,例如:

clang -O -g -fsanitize=leak main.cpp && ./a.out

在 Mac 下使用内存泄漏检查

在 macOS 下,默认使用的 AppleClang,但遗憾的是,AppleClang 不支持 LeakSanitizer,在开启 LeakSanitizer 时它会报错:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-48ZuJiMD-1657620845677)(evernotecid://A493B2A0-41B1-4481-9536-FBB91B3939BA/appyinxiangcom/23207932/ENResource/p544)]

解决的办法非常简单,我们不适用 AppleClang 而是之间适用 llvm 中的 clang 就好。具体的,首先安装 llvm,具体版本视情况而定:

brew install llvm@13

安装后,llvm clang++ 在我机器上的位置在 /opt/homebrew/opt/llvm@13/bin/clang++
直接适用 llvm clag++ 进行编译即可:

/opt/homebrew/opt/llvm@13/bin/clang++ -O -g -fsanitize=address main.cpp -o main

如果用 cmake 编译,可以在 CMakeLists.txt 中指定 CMAKE_CXX_COMPILER 为 llvm clang++:

set(CMAKE_CXX_COMPILER "/opt/homebrew/Cellar/llvm@13/13.0.1/bin/clang++")

在 CLion 下使用

CLion 中集成了对 Sanitizers 支持,具体参考 CLion - Google sanitizers 。简单来说,你可以在 CMakeLists.txt 中添加一个开关来添加 Asan 编译选项,例如:

cmake_minimum_required(VERSION 3.21)
project(mem_leak_test)
set(CMAKE_CXX_STANDARD 14)

if (ENABLE_ASAN)
    message(STATUS "build with ASAN")
    set(CMAKE_CXX_FLAGS "$CMAKE_CXX_FLAGS -fsanitize=address")
endif ()

add_executable(mem_leak_test main.cpp)

然后在配置 CMake ,传入 ENABLE_ASAN 即可:

同理,如果你在 macOS 上使用 llvm clang++,也可以在配置中指定 compiler 的路径:

设置完毕后,之间运行代码,如果出现内存问题,CLion 会在 Sanitizers 窗口中提示信息:

android 中使用

目前 NDK 中已经支持了 ASAN 的使用,具体参考 Address Sanitizer ,我提供了 Android ASAN demo 可以参考。但并不支持 memory leak 的检查。

当 ASan 检查到内存问题时,app 会 crash,并打印 log,通过 log 信息,你可以定位到崩溃的代码地址,再通过 addr2line 来获取具体的代码行号:

参考

以上是关于C/C++ 内存治理神器 - Google Sanitizers的主要内容,如果未能解决你的问题,请参考以下文章

流量治理神器-Sentinel的限流模式,选单机还是集群?

C/C++面试题分享「虚函数多态内存管理与软件调试篇」

C/C++面试题分享「虚函数多态内存管理与软件调试篇」

测绘程序设计坐标反算神器V1.0(附C/C#/VB源程序)

用Google的gflags优雅的解析命令行参数

用Google的gflags优雅的解析命令行参数