使用asan检测内存泄漏、堆栈溢出等问题

一、使用过程

  • 操作过程参考:链接
  • 缘起:程序在移动端崩溃,mac端复现不了,于是在写个崩溃位置函数的调用demo,使用ASAN工具进行排查。
  • 验证过程
    1、代码
    main.cpp
#include <string.h>
#include "opencv2/core/core.hpp"
#include "opencv2/opencv.hpp"

template<typename T>
inline T fastMin(const T a, const T b) {
    return (a < b ? a : b);
}


cv::Mat letterbox(const cv::Mat &src, const cv::Size &target_size, int stride = 32, bool hold_target = true,
                  int resize_func = 0) {
    int ori_w = src.cols;
    int ori_h = src.rows;
    int tar_w = target_size.width;
    int tar_h = target_size.height;

    auto r = fastMin<float>(float(tar_h) / float(ori_h), float(tar_w) / float(ori_w));
    int in_w = int(round(float(ori_w) * r));
    int in_h = int(round(float(ori_h) * r));
    int pad_w = tar_w - in_w;
    int pad_h = tar_h - in_h;
    if (!hold_target) {
        pad_w = pad_w % stride;
        pad_h = pad_h % stride;
    }
    cv::Mat resize_img;
    cv::resize(src, resize_img, cv::Size(in_w, in_h), 0, 0, resize_func);
    float nw = float(pad_w) / 2;
    float nh = float(pad_h) / 2;
    int top = int(round(nh - 0.1));
    int bottom = int(round(nh + 0.1));
    int left = int(round(nw - 0.1));
    int right = int(round(nw + 0.1));
    auto pad_color = cv::Scalar(114, 114, 114);
    cv::copyMakeBorder(resize_img, resize_img, top, bottom, left, right, cv::BORDER_CONSTANT, pad_color);
    return resize_img;
}

void pre_process(unsigned char *image_rgba, cv::Mat &input, int img_height, int img_width) {
    cv::Size target;
    switch (0) {
        case 1:
            target = cv::Size(160,160);//cfg_.MID_DEVICE_INFER_SIZE, cfg_.MID_DEVICE_INFER_SIZE);
            break;
        case 2:
            target = cv::Size(120,120);//cfg_.LOW_DEVICE_INFER_SIZE, cfg_.LOW_DEVICE_INFER_SIZE);
            break;
        default:
            //get model input h,w
            size_t height_ = 320;//detector_helper_->GetInputHeight();
            size_t width_ = 320;//detector_helper_->GetInputWidth();
            target = cv::Size((int) width_, (int) height_);
    }
    bool hold_target = true;//(options_.coreML_mode ||options_.NPU_mode);
    if(hold_target){
        size_t height_ = 320;//detector_helper_->GetInputHeight();
        size_t width_ = 320;//detector_helper_->GetInputWidth();
        target = cv::Size((int) width_, (int) height_);
    }
    cv::Mat image = cv::Mat(img_height, img_width, CV_8UC4, image_rgba);

    input = letterbox(image, target, 32, hold_target,0);
}


int main() {
	
	cv::Mat image_bgr = cv::imread("/root/Downloads/llp.png");
	cv::Mat imgrgba;
	cv::cvtColor(image_bgr, imgrgba, cv::COLOR_BGR2RGBA);
	int width = image_bgr.cols;
	int height = image_bgr.rows;
	cv::Mat input;
	pre_process(imgrgba.data, input, height, width);
	//letterbox(imgrgba, cv::Size(320, 320), 32, true,0);

    return 0;
}

使用附加ASAN工具的方式进行编译:

g++ main.cpp -o detectBug  -fsanitize=leak -g `pkg-config --cflags --libs opencv`

执行:

./detectBug

没有问题,以上是验证过程,如有问题执行时ASAN会提示有问题的相关位置。

二、ASAN资料

  • 介绍

首先,先介绍一下 Sanitizer 项目,该项目是谷歌出品的一个开源项目,该项目包含了 ASAN、LSAN、MSAN、TSAN等内存、线程错误的检测工具,这里简单介绍一下这几个工具的作用:

ASAN: 内存错误检测工具,在编译命令中添加-fsanitize=address启用

LSAN: 内存泄漏检测工具,已经集成到 ASAN 中,可以通过设置环境变量ASAN_OPTIONS=detect_leaks=0来关闭ASAN上的LSAN,也可以使用-fsanitize=leak编译选项代替-fsanitize=address来关闭ASAN的内存错误检测,只开启内存泄漏检查。

MSAN: 对程序中未初始化内存读取的检测工具,可以在编译命令中添加-fsanitize=memory -fPIE -pie启用,还可以添加-fsanitize-memory-track-origins选项来追溯到创建内存的位置

TSAN: 对线程间数据竞争的检测工具,在编译命令中添加-fsanitize=thread启用 其中ASAN就是我们今天要介绍的重头戏。

ASAN,全称 AddressSanitizer,可以用来检测内存问题,例如缓冲区溢出或对悬空指针的非法访问等。

根据谷歌的工程师介绍 ASAN 已经在 chromium 项目上检测出了300多个潜在的未知bug,而且在使用 ASAN 作为内存错误检测工具对程序性能损耗也是及其可观的。

根据检测结果显示可能导致性能降低2倍左右,比Valgrind(官方给的数据大概是降低10-50倍)快了一个数量级。

而且相比于Valgrind只能检查到堆内存的越界访问和悬空指针的访问,ASAN 不仅可以检测到堆内存的越界和悬空指针的访问,还能检测到栈和全局对象的越界访问。

这也是 ASAN 在众多内存检测工具的比较上出类拔萃的重要原因,基本上现在 C/C++ 项目都会使用ASAN来保证产品质量,尤其是大项目中更为需要。

  • 如何使用 ASAN

作为如此强大的神兵利器,自然是不会在程序员的战场上失宠的。

从LLVM3.1、GCC4.8、XCode7.0、MSVC16.9开始ASAN就已经成为众多主流编译器的内置工具了,因此,要在项目中使用ASAN也是十分方便。

现在只需要在编译命令中加上-fsanitize=address检测选项就可以让ASAN在你的项目中大展神通,接下来通过几个例子来看一下 ASAN 到底有哪些本领。

注意:

在下面的例子中打开了调试标志-g,这是因为当发现内存错误时调试符号可以帮助错误报告更准确的告知错误发生位置的堆栈信息,如果错误报告中的堆栈信息看起来不太正确,请尝试使用-fno-omit-frame-pointer来改善堆栈信息的生成情况。
如果构建代码时,编译和链接阶段分开执行,则必须在编译和链接阶段都添加-fsanitize=address选项。

检测内存泄漏

// leak.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, const char *argv[]) {
    char *s = (char*)malloc(100);
    strcpy(s, "Hello world!");
    printf("string is: %s\n", s);
    return 0;
}

上述代码中我们分配了100个字节的内存空间,但在main函数返回前始终没有释放,接下来我们使用ASAN看一下是否能够检测出来,添加-fsanitize=address -g参数构建代码并执行:

~/Code/test$ gcc noleak.c -o noleak -fsanitize=address -g
~/Code/test$ ./leak 
string is: Hello world!

=================================================================
==1621572==ERROR: LeakSanitizer: detected memory leaks    // 1)

Direct leak of 100 byte(s) in 1 object(s) allocated from:   // 2)
    #0 0x7f5b986bc808 in __interceptor_malloc ../../../../src/libsanitizer/ASAN/ASAN_malloc_linux.cc:144
    #1 0x562d866b5225 in main /home/chenbing/Code/test/leak.c:7
    #2 0x7f5b983e1082 in __libc_start_main ../csu/libc-start.c:308

SUMMARY: AddressSanitizer: 100 byte(s) leaked in 1 allocation(s).

这里,ASAN 提供的报告说明了错误原因是detected memory leaks内存泄漏了1),同时,2)说明ASAN检测到应用程序分配了100个字节,并捕获到了内存分配位置的堆栈信息,还告诉了我们内存是在leak.c:7分配的。

有了这么详细的且准确的错误报告,内存问题是不是不那么头疼了?

<think>我们已经在之前的对话中详细讨论了Google Test (gTest) 本身不直接提供内存泄漏检测功能,但可以通过结合其他工具来实现。根据用户当前的问题,他特别询问在Linux系统下使用gTest进行内存泄漏检测的方法。结合用户提供的引用(特别是引用[1]和引用[3]),我们可以重点介绍使用Valgrind和AddressSanitizer(ASAN)这两种在Linux下常用的方法。 ### 回答结构: 1. **明确核心方法**:直接点出两种主要方法(Valgrind和ASAN)并简述其特点。 2. **Valgrind方法详细步骤**:提供从编译到运行的完整命令和解释。 3. **ASAN方法详细步骤**:同样提供完整步骤,并说明如何与gTest结合。 4. **对比与选择建议**:简要对比两种方法,帮助用户根据场景选择。 5. **常见问题与注意事项**:比如调试符号、多线程等。 ### 特别注意: - 用户引用中提到了ASAN实战指南(引用[1])和内存泄漏检测方法(引用[3]),所以我们会强调ASAN使用。 - 用户引用[2]虽然提到了gtest和内存泄漏检测,但内容较情绪化,技术信息较少,因此不作为主要参考。 ### 回答: 在Linux系统中,使用Google Test(gTest)进行内存泄漏检测主要通过两种工具实现:**Valgrind** 和 **AddressSanitizer (ASAN)**。以下是具体方法: --- ### 一、使用 Valgrind 检测内存泄漏(系统级检测) Valgrind 是一个强大的内存调试工具,可以检测内存泄漏、非法内存访问等问题。结合 gTest 的步骤如下: 1. **编译测试程序** 编译时务必添加 `-g` 选项生成调试符号: ```bash g++ -std=c++11 -g -o test_app your_test.cpp -lgtest -lgtest_main -lpthread ``` 2. **运行 Valgrind 检测** 使用 `memcheck` 工具并开启完整泄漏检测: ```bash valgrind --tool=memcheck \ --leak-check=full \ --show-leak-kinds=all \ --track-origins=yes \ ./test_app ``` - `--leak-check=full`:显示泄漏的详细堆栈 - `--show-leak-kinds=all`:报告所有类型的泄漏(包括间接泄漏) - `--track-origins=yes`:追踪未初始化值的来源 3. **解读输出** 若存在泄漏,Valgrind 会输出如下信息: ``` ==12345== 40 bytes in 1 blocks are definitely lost ==12345== at 0x4C2A1F3: malloc (vg_replace_malloc.c:908) ==12345== by 0x400A56: Foo::leak() (Foo.cpp:15) ==12345== by 0x400B22: Test_Foo_Leak (test_Foo.cpp:25) ``` 其中 `definitely lost` 表示确定泄漏的位置和大小[^1][^3]。 --- ### 二、使用 AddressSanitizer (ASAN) 检测(编译期插桩) ASAN 是 Clang/GCC 内置的内存检测工具,速度比 Valgrind 更快,且能检测更多类型的内存错误(如堆栈溢出)。 1. **编译启用 ASAN** 添加 `-fsanitize=address` 和 `-g` 编译选项: ```bash g++ -std=c++11 -fsanitize=address -g -o test_app your_test.cpp -lgtest -lgtest_main -lpthread ``` > 注意:若使用 CMake,需在 `CMakeLists.txt` 中添加: > ```cmake > set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -g") > ``` 2. **运行测试程序** 直接执行二进制文件,ASAN 会在检测到错误时自动输出报告: ```bash ./test_app ``` 3. **解读 ASAN 报告** 内存泄漏报告示例: ``` ================================================================= ==12345==ERROR: LeakSanitizer: detected memory leaks Direct leak of 40 byte(s) in 1 object(s) allocated from: #0 0x7f2a1b2c7d28 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.4+0xded28) #1 0x400b36 in Foo::leak() /path/to/Foo.cpp:15 #2 0x400c22 in Test_Foo_Leak /path/to/test_Foo.cpp:25 ``` - 输出包含泄漏大小、分配位置和调用堆栈[^1]。 --- ### 三、方法对比与选择建议 | 工具 | 优点 | 缺点 | 适用场景 | |------------|------------------------------|--------------------------|-----------------------| | Valgrind | 无需重新编译,支持旧版 GCC | 速度慢(10-20倍) | 生产环境调试 | | ASAN | 速度快(仅慢2倍),检测类型多 | 需重新编译,Linux 专用 | 开发/持续集成测试 | > **关键提示**:两者均需 `-g` 选项生成调试符号,否则无法定位到源码行号[^1]。 --- ### 四、常见问题解决 1. **多线程泄漏检测** Valgrind 和 ASAN 均支持多线程,但 Valgrind 需添加 `--trace-children=yes` 追踪子线程。 2. **ASAN 与 gTest 的集成技巧** 在测试代码中可强制检查未释放内存(适用于特殊场景): ```cpp #include <sanitizer/lsan_interface.h> TEST(LeakTest, CheckNoLeak) { __lsan_do_leak_check(); // 主动触发泄漏检查 } ``` 3. **忽略第三方库的泄漏** 创建 `lsan.supp` 文件列出忽略规则,运行时指定: ```bash export LSAN_OPTIONS="suppressions=lsan.supp" ./test_app ``` --- 通过上述方法,您可以在 Linux 系统下高效检测 gTest 测试中的内存泄漏问题。建议开发阶段使用 ASAN,生产环境使用 Valgrind 进行深度验证[^1]。 §§相关问题§§ 1. Valgrind 如何检测 C++ 多线程程序的内存泄漏? 2. 如何在 CMake 项目中自动集成 ASAN 检测? 3. ASAN 能否检测溢出和全局变量溢出? [^1]: 告别内存泄漏!Linux (CentOS 7) 上 GTest + ASAN 实战指南 : linux下检测和定位内存泄漏位置的方法 </think> ### Linux 系统下使用 gTest 进行内存泄漏检测方法详解 在 Linux 系统中,Google Test (gTest) 本身不直接检测内存泄漏,但可通过以下两种主流方法实现内存泄漏检测: --- ### 一、Valgrind 方案(推荐) #### 操作步骤: 1. **编译测试程序**(添加调试符号) ```bash g++ -std=c++11 -g -o test_app your_test.cpp -lgtest -lpthread ``` 2. **运行 Valgrind 检测** ```bash valgrind --tool=memcheck \ --leak-check=full \ --show-leak-kinds=all \ --track-origins=yes \ ./test_app ``` 3. **解读检测报告** - `definitely lost`:确认泄漏(未释放内存) - `indirectly lost`:间接泄漏(如结构体成员未释放) - 输出示例: ``` ==12345== 40 bytes in 1 blocks are definitely lost at 0x4C2A1F3: malloc (vg_replace_malloc.c:908) by 0x400A56: MyClass::leaky_func() (MyClass.cpp:15) by 0x400B22: TestSuite_LeakDetection (test_MyClass.cpp:25) ``` #### 优势: - 无需修改代码 - 支持检测所有堆内存泄漏类型 - 提供详细堆栈跟踪[^3] --- ### 二、AddressSanitizer (ASAN) 方案 #### 操作步骤: 1. **编译时启用 ASAN** ```bash g++ -fsanitize=address -g -O1 -fno-omit-frame-pointer \ -o test_app your_test.cpp -lgtest -lpthread ``` > 注:`-O1` 优化级别为 ASAN 必需 2. **运行测试程序** ```bash ASAN_OPTIONS=detect_leaks=1 ./test_app ``` 3. **解读 ASAN 报告** 检测到泄漏时自动输出: ``` ================================================================= ==12345==ERROR: LeakSanitizer: detected memory leaks Direct leak of 40 byte(s) in 1 object(s) allocated from: #0 0x7f2a1b2c7d28 in malloc (/usr/lib/x86_64-linux-gnu/libasan.so.4+0xded28) #1 0x400b36 in MyClass::leaky_func() /path/to/MyClass.cpp:15 #2 0x400c22 in TestSuite_LeakDetection /path/to/test_MyClass.cpp:25 ``` #### 优势: - 执行速度快(比 Valgrind 快 2-10 倍) - 额外检测:缓冲区溢出/悬垂指针等内存错误 - 与 gTest 无缝集成 --- ### 三、方法对比与选择建议 | **特性** | Valgrind | AddressSanitizer (ASAN) | |------------------|------------------------------|------------------------------| | 检测精度 | 高(字节级) | 高 | | 运行速度 | 慢(降低 10-20 倍) | 快(降低约 2 倍) | | 内存开销 | 高 | 中等 | | 额外内存错误检测 | 部分 | 全面(堆/栈/全局变量溢出等) | | 适用场景 | 生产环境/深度检测 | 开发阶段/持续集成 | > **推荐组合使用**:开发阶段用 ASAN 快速迭代,发布前用 Valgrind 深度验证[^3] --- ### 四、注意事项 1. **必须添加调试符号** 编译时 `-g` 选项不可或缺,否则无法定位源码行号 2. **多线程程序处理** - Valgrind:添加 `--trace-children=yes` - ASAN:默认支持多线程检测 3. **排除第三方库泄漏** 创建抑制文件过滤已知误报: ```bash valgrind --suppressions=my_suppressions.supp ./test_app ``` 4. **容器环境适配** Docker 中需添加 `--cap-add=SYS_PTRACE` 权限: ```bash docker run --cap-add=SYS_PTRACE -it your_image ``` 通过上述方法,您可以在 Linux 系统中高效检测 gTest 测试用例中的内存泄漏问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值