一、使用过程
- 操作过程参考:链接
- 缘起:程序在移动端崩溃,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分配的。
有了这么详细的且准确的错误报告,内存问题是不是不那么头疼了?