开发调试利器|内存检测工具Sanitizer

嵌入式的大杂烩 2024-04-10 02:59:02

大家好,我是杂烩君。本次我们来分享一个开发调试利器——Sanitizer。

Sanitizer简介

Sanitizer是由Google发起的开源工具集,用于检测内存泄露等问题。

链接:https://github.com/google/sanitizers/wiki/

它包括了AddressSanitizer、MemorySanitizer、ThreadSanitizer、LeakSanitizer等多种工具。这些工具最初是LLVM项目的一部分,后来也被GNU的GCC编译器支持。从GCC的4.8版本开始,就已经支持AddressSanitizer和ThreadSanitizer,而4.9版本则开始支持LeakSanitizer。

Sanitizer使用1、AddressSanitizer的使用例子

AddressSanitizer(ASan) 是一个快速内存检测器,可以检测出缓冲区溢出、使用已释放内存等问题。编译时带上参数 -fsanitize=address及-g。

(1)捕捉栈缓冲区溢出问题:

AddressSanitizer.c:

// 微信公众号:嵌入式大杂烩#include <stdlib.h>void test_func(void){ int a[6] = {0}; int b = a[6]; // 栈缓冲区溢出} int main(int argc, char **argv){ test_func(); return 0;}

编译、运行:

gcc AddressSanitizer.c -fsanitize=address -g -o AddressSanitizer

执行结果分析:

触发了检测错误级别,终止程序并给出了程序运行异常的原因及异常的代码位置。

(2)捕捉使用已释放内存问题:

ThreadSanitizer.c:

// 微信公众号:嵌入式大杂烩#include <stdlib.h>void test_func(void){ char *p = malloc(10); p[0] = 1; free(p); p[0] = 1; // 使用已释放内存} int main(int argc, char **argv){ test_func(); return 0;}

2、ThreadSanitizer的使用例子

ThreadSanitizer(TSan) 是一个数据竞争检测器,可以用来分析线程竞态、死锁等线程相关问题。编译时带上参数 -fsanitize=thread及-g。

捕捉 线程间数据竞争 问题:

// 微信公众号:嵌入式大杂烩#include <stdio.h>#include <pthread.h>int g_counter = 0; // thread1、thread2竞争的数据void *increment(void *arg){ g_counter++;}void *decrement(void *arg){ g_counter--;}void test_func(void){ pthread_t thread1, thread2; pthread_create(&thread1, NULL, increment, NULL); pthread_create(&thread2, NULL, decrement, NULL); pthread_join(thread1, NULL); pthread_join(thread2, NULL); printf("Counter value: %d\n", g_counter);}int main(int argc, char **argv){ test_func(); return 0;}

编译、运行:

gcc ThreadSanitizer.c -fsanitize=thread -g -pthread -o ThreadSanitizer

执行结果分析:

触发了检测警告级别,程序仍能运行,并给出了程序运行有风险的原因及有风险的代码位置。

3、程序中同时存在多处风险?

上面的例子分别使用AddressSanitizer检测器与ThreadSanitizer检测器来检测对应的异常,可以较为精准地检测到对应的异常。

如果程序中同时存在多处风险呢?

这也是比较贴近我们的实际应用的,毕竟我们并不知道我们的代码里有哪些可能存在的风险。这种情况我们要怎么检测?

编译时能同时带上多个-fsanitize参数调用多个检测器吗?

可以同时带,但有些检测器不能同时使用。

AddressSanitizer与ThreadSanitizer检测器不能同时使用。

但是,假如我们的程序中恰好存在address异常与thread异常呢,单独使用AddressSanitizer检测器、ThreadSanitizer检测器的表现是怎样的?

比如,我们把上面3个例子的代码放在一起:

test.c:

// 微信公众号:嵌入式大杂烩#include <stdio.h>#include <stdlib.h>#include <pthread.h>int g_counter = 0; // thread1、thread2竞争的数据void *increment(void *arg){ g_counter++;}void *decrement(void *arg){ g_counter--;}// 测试:资源竞争void test_func(void){ pthread_t thread1, thread2; pthread_create(&thread1, NULL, increment, NULL); pthread_create(&thread2, NULL, decrement, NULL); pthread_join(thread1, NULL); pthread_join(thread2, NULL); printf("Counter value: %d\n", g_counter);}// 测试:使用已释放内存void test_func1(void){ char *p = malloc(10); printf("This is test_func1\n"); p[0] = 1; free(p); p[0] = 1; // 使用已释放内存} // 测试:栈缓冲区溢出void test_func2(void){ int a[6] = {0}; int b = a[6]; // 栈缓冲区溢出} int main(int argc, char **argv){ test_func(); test_func1(); test_func2(); return 0;}

带-fsanitize=thread参数编译、运行:

执行结果分析:

ThreadSanitizer检测器能正常检测出资源竞争的问题,也检测出了test_func1中的使用已释放的堆内存的问题并以警告级别报告,但没有检测出test_func2的栈缓冲区溢出问题。

是不是因为test_func2运行在test_func1后面了,所以test_func2的异常没有被ThreadSanitizer检测器检测出来?

我们调换个位置看看:

int main(int argc, char **argv){ test_func(); test_func2(); test_func1(); return 0;}

显然,执行结果还是一样的,test_func2的栈缓冲区溢出问题还是没有被ThreadSanitizer检测器检测出来。

所以,大致得出结论:当程序里存在thread异常与address异常时,使用ThreadSanitizer检测器能准确检测到thread异常,能检测到部分address异常。

带-fsanitize=address参数编译、运行:

执行顺序:

int main(int argc, char **argv){ test_func(); test_func1(); test_func2(); return 0;}

执行结果分析:

AddressSanitizer检测器检测到了test_func1中的已使用释放的堆内存的异常并以错误级别报告,并终止了程序;没有检测到test_func的资源竞争的风险;也没有检测到test_func2的栈缓冲区溢出的问题,因为执行到test_func1的时候程序已经被终止了,如果把test_func2放在test_func1之前运行,就能检测到test_func2的异常。

结论:当程序里存在thread异常与address异常时,使用AddressSanitizer检测器能准确检测到第一个触发的address异常,不能检测到thread异常。

如果程序中存在多种可能存在的风险时,需要使用多个检测器单独挨个检测。每个检测器都有其擅长检测的方面,可以经过初步分析之后确定大致地方向,选择适合地检测器来做检测。

以上就是关于Sanitizer的一些简单介绍及使用的分享,更多的关于Sanitizer的资料可查阅:https://github.com/google/sanitizers/wiki/

码字不易,如果文章对你有帮助,麻烦帮忙点赞、关注,谢谢大家!

0 阅读:3