linux服务器开发—手写内存泄漏检测组件

一口Linux 2022-04-09 13:22

击上方“一口Linux”,选择“置顶/星标公众号


干货福利,第一时间送达!

内存泄漏原因

内存泄漏在C/C++这种不带GC(Garbage Collection)的语言里,是一个经常发生的问题。因为没有GC,所以分配的内存需要程序员自己调用释放。内存泄漏的根本原因是程序对于在申请的内存没有进行释放。

{
void *p1 = malloc(10);
void *p2 = malloc(20);

free(p1);
}

上面的代码段,申请了两块内存p1,p2,只释放了p1,没有释放p2,产生了内存泄漏。

内存泄漏会产生哪些后果?

随着程序运行时间越来越久,内存有分配没有释放,会使得进程堆中的内存会越来越少,直到耗尽。会造成后面的运行时代码不能成功分配内存。

内存泄漏如何解决?

方案一 引入gc,从语言层面解决内存泄漏;

方案二 当发生内存泄漏的时候,能够精准的定位代码那个文件、那个函数、哪一行所引起的。

我们实现的是方案二,核心需求有两个。

需求1:能够检测出内存泄漏

需求2:能够指出是由代码的哪个文件、哪个函数、哪一行引起的内存泄漏

内存泄漏检测如何实现?

内存泄漏检测实现的核心思想就是对系统的malloc/free进行hook,用我们自己的malloc/free代替系统调用,将free的地址和malloc的地址进行匹配,查看最后又哪些malloc没有进行free,并将没有free的malloc操作的代码段地址进行记录,通过代码段定位所在的文件、函数、代码行。

方案一

采用__libc_malloc, libc_free与__builtin_return_address。它们是gcc提供的函数。

__libc_malloc, libc_free用来代替malloc/free。可以用来实现hook。需要注意的是,我们实现的malloc/free函数,内部会有一些函数如printf,fopen,需要防止它们会嵌套调用malloc/free。

__builtin_return_address,能够返回调用所在函数的代码段的地址。能够定位内存泄漏的具体位置。

malloc的时候,创建一个文件,文件名使用申请内存的地址,并记录申请该内存的代码段的地址;free的时候,删除对应的文件。

#include 
#include
#include

int enable_malloc_hook = 1;
extern void *__libc_malloc(size_t size);

int enable_free_hook = 1;
extern void *__libc_free(void *p);

void *malloc(size_t size) {

if (enable_malloc_hook) {
enable_malloc_hook = 0;

void *p = __libc_malloc(size);

void *caller = __builtin_return_address(0);

char buff[128] = {0};
sprintf(buff, "./mem/%p.mem", p);

FILE *fp = fopen(buff, "w");
fprintf(fp, "[+%p]malloc --> addr:%p size:%lu\n", caller, p, size);
fflush(fp);

enable_malloc_hook = 1;
return p;
} else {

return __libc_malloc(size);

}
return NULL;
}

void free(void *p) {

if (enable_free_hook) {
enable_free_hook = 0;
char buff[128] = {0};
sprintf(buff, "./mem/%p.mem", p);

if (unlink(buff) < 0) {
printf("double free: %p\n", p);
}

__libc_free(p);


enable_free_hook = 1;
} else {
__libc_free(p);
}

}

// gcc -o memleak_0 memleak_0.c -g
// addr2line -f -e memleak_0 -a 0x4006d8
int main() {


void *p1 = malloc(10);
void *p2 = malloc(20);

free(p1);

void *p3 = malloc(30);
void *p4 = malloc(40);

free(p2);
free(p4);

return 0;
}

方案二

使用宏定义, 开启宏定义使用我们的版本,不开启就使用系统的。可以方便debug。

内存泄漏检测使用malloc_hook/free_hook, 定位内存泄漏位置,使用__FILE__, __LINE__.

#define malloc(size) malloc_hook(size, __FILE__, __LINE__)

#define free(p) free_hook(p, __FILE__, __LINE__)

可以使用fclose,没有double free的问题了

#include 
#include
#include

void *malloc_hook(size_t size, const char *file, int line) {

void *p = malloc(size);

char buff[128] = {0};
sprintf(buff, "./mem/%p.mem", p);

FILE *fp = fopen(buff, "w");
fprintf(fp, "[+%s:%d]malloc --> addr:%p size:%lu\n", file, line, p, size);
fflush(fp);

fclose(fp);

return p;

}

void free_hook(void *p, const char *file, int line) {

char buff[128] = {0};
sprintf(buff, "./mem/%p.mem", p);

if (unlink(buff) < 0) {
printf("double free: %p\n", p);
return;
}

free(p);

}

#define malloc(size) malloc_hook(size, __FILE__, __LINE__)

#define free(p) free_hook(p, __FILE__, __LINE__)


// gcc -o memleak_0 memleak_0.c -g
// addr2line -f -e memleak_0 -a 0x4006d8
int main() {


void *p1 = malloc(10);
void *p2 = malloc(20);

free(p1);

void *p3 = malloc(30); // memory leak
void *p4 = malloc(40);

free(p2);
free(p4);
free(p4); // double free

return 0;

}

检测出两个问题,一次内存泄漏 p3,一次double free p4。结果OK。


方案三

使用malloc.h中提供的hook: __malloc_hook, __free_hook.

这两个hook,默认是malloc,free。

参考mtrace的做法,通过改变这两个值来进行检测。

#include 
#include
#include
#include

typedef void *(*malloc_hoot_t)(size_t size, const void *caller);
malloc_hoot_t malloc_f;

typedef void (*free_hook_t)(void *p, const void *caller);
free_hook_t free_f;

void mem_trace(void);
void mem_untrace(void);

void *malloc_hook_f(size_t size, const void *caller) {

mem_untrace();
void *ptr = malloc(size);
// printf("+%p: addr[%p]\n", caller, ptr);
char buff[128] = {0};
sprintf(buff, "./mem/%p.mem", ptr);

FILE *fp = fopen(buff, "w");
fprintf(fp, "[+%p]malloc --> addr:%p size:%lu\n", caller, ptr, size);
fflush(fp);

fclose(fp);

mem_trace();
return ptr;

}

void free_hook_f(void *p, const void *caller) {
mem_untrace();
// printf("-%p: addr[%p]\n", caller, p);

char buff[128] = {0};
sprintf(buff, "./mem/%p.mem", p);

if (unlink(buff) < 0) {
printf("double free: %p\n", p);
}

free(p);
mem_trace();

}

void mem_trace(void) {

malloc_f = __malloc_hook;
free_f = __free_hook;

__malloc_hook = malloc_hook_f;
__free_hook = free_hook_f;
}

void mem_untrace(void) {

__malloc_hook = malloc_f;
__free_hook = free_f;

}


// gcc -o memleak_0 memleak_0.c -g
// addr2line -f -e memleak_0 -a 0x4006d8
int main() {

mem_trace();
void *p1 = malloc(10);
void *p2 = malloc(20);

free(p1);

void *p3 = malloc(30);
void *p4 = malloc(40);

free(p2);
free(p4);
mem_untrace();

return 0;

}


方案四

使用mtrace

#include 
#include
#include
#include
#include

// gcc -o memleak_0 memleak_0.c -g
// addr2line -f -e memleak_0 -a 0x4006d8
int main() {

// export MALLOC_TRACE=./test.log
mtrace();
void *p1 = malloc(10);
void *p2 = malloc(20);

free(p1);

void *p3 = malloc(30);
void *p4 = malloc(40);

free(p2);
free(p4);
muntrace();

#endif
return 0;

}


方案五

使用dlsym对malloc,free进行hook。

#define _GNU_SOURCE
#include

#include
#include
#include

typedef void *(*malloc_t)(size_t size);
malloc_t malloc_f;

typedef void (*free_t)(void *p);
free_t free_f;


int enable_malloc_hook = 1;

int enable_free_hook = 1;

void *malloc(size_t size) {

if (enable_malloc_hook) {
enable_malloc_hook = 0;

void *p = malloc_f(size);

void *caller = __builtin_return_address(0);

char buff[128] = {0};
sprintf(buff, "./mem/%p.mem", p);

FILE *fp = fopen(buff, "w");
fprintf(fp, "[+%p]malloc --> addr:%p size:%lu\n", caller, p, size);
fflush(fp);

enable_malloc_hook = 1;
return p;
} else {

return malloc_f(size);

}
return NULL;
}

void free(void *p) {

if (enable_free_hook) {
enable_free_hook = 0;
char buff[128] = {0};
sprintf(buff, "./mem/%p.mem", p);

if (unlink(buff) < 0) {
printf("double free: %p\n", p);
}

free_f(p);


enable_free_hook = 1;
} else {
free_f(p);
}

}

static int init_hook() {

malloc_f = dlsym(RTLD_NEXT, "malloc");

free_f = dlsym(RTLD_NEXT, "free");

}

// gcc -o memleak_0 memleak_0.c -ldl -g
// addr2line -f -e memleak_0 -a 0x4006d8
int main() {

init_hook();

void *p1 = malloc(10);
void *p2 = malloc(20);

free(p1);

void *p3 = malloc(30);
void *p4 = malloc(40);

free(p2);
free(p4);

return 0;

}


共享内存

mmap方法1

匿名mmap

#include 
#include
#include



void *shm_mmap_alloc(int size) {

void *addr = mmap(NULL, size, PROT_READ|PROT_WRITE,
MAP_ANON | MAP_SHARED, -1, 0);
if (addr == MAP_FAILED) {
return NULL;
}

return addr;

}

int shm_mmap_free(void *addr, int size) {

return munmap(addr, size);

}


int main() {

char *addr = (char *)shm_mmap_alloc(1024);

pid_t pid = fork();
if (pid == 0) {
// child
int i = 0;

while(i < 26) {
addr[i] = 'a' + i++;
addr[i] = '\0';
sleep(1);
}
} else if (pid > 0) {
int i = 0;
while (i++ < 26) {
printf("parent: %s\n", addr);
sleep(1);
}
}

shm_mmap_free(addr, 1024);

}


mmap方法2

/dev/zero

#include 
#include
#include

#include
#include
#include


void *shm_mmap_alloc(int size) {
int fd = open("/dev/zero", O_RDWR);

void *addr = mmap(NULL, size, PROT_READ|PROT_WRITE,
MAP_SHARED, fd, 0);
close(fd);

if (addr == MAP_FAILED) {
return NULL;
}

return addr;

}

int shm_mmap_free(void *addr, int size) {

return munmap(addr, size);

}


int main() {

char *addr = (char *)shm_mmap_alloc(1024);

pid_t pid = fork();
if (pid == 0) {
// child
int i = 0;

while(i < 26) {
addr[i] = 'a' + i++;
addr[i] = '\0';
sleep(1);
}
} else if (pid > 0) {
int i = 0;
while (i++ < 26) {
printf("parent: %s\n", addr);
sleep(1);
}
}

shm_mmap_free(addr, 1024);

}

shmget方法

#include 
#include
#include

#include
#include
#include

#include
#include


void *shm_alloc(int size) {

int segment_id = shmget(IPC_PRIVATE, size,
IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR);
char *addr = (char *)shmat(segment_id, NULL, 0);

return addr;

}

int shm_free(void *addr) {

return shmdt(addr);

}


int main() {

char *addr = (char *)shm_alloc(1024);

pid_t pid = fork();
if (pid == 0) {
// child
int i = 0;

while(i < 26) {
addr[i] = 'a' + i++;
addr[i] = '\0';
sleep(1);
}
} else if (pid > 0) {
int i = 0;
while (i++ < 26) {
printf("parent: %s\n", addr);
sleep(1);
}
}

shm_free(addr);

}

end



一口Linux 


关注,回复【1024】海量Linux资料赠送

精彩文章合集

文章推荐

【专辑】ARM
【专辑】粉丝问答
【专辑】所有原创
专辑linux入门
专辑计算机网络
专辑Linux驱动
【干货】嵌入式驱动工程师学习路线
【干货】Linux嵌入式所有知识点-思维导图


点击“阅读原文”查看更多分享,欢迎点分享、收藏、点赞、在看

一口Linux 写点代码,写点人生!
评论
  • 戴上XR眼镜去“追龙”是种什么体验?2024年11月30日,由上海自然博物馆(上海科技馆分馆)与三湘印象联合出品、三湘印象旗下观印象艺术发展有限公司(下简称“观印象”)承制的《又见恐龙》XR嘉年华在上海自然博物馆重磅开幕。该体验项目将于12月1日正式对公众开放,持续至2025年3月30日。双向奔赴,恐龙IP撞上元宇宙不久前,上海市经济和信息化委员会等部门联合印发了《上海市超高清视听产业发展行动方案》,特别提到“支持博物馆、主题乐园等场所推动超高清视听技术应用,丰富线下文旅消费体验”。作为上海自然
    电子与消费 2024-11-30 22:03 98浏览
  • RDDI-DAP错误通常与调试接口相关,特别是在使用CMSIS-DAP协议进行嵌入式系统开发时。以下是一些可能的原因和解决方法: 1. 硬件连接问题:     检查调试器(如ST-Link)与目标板之间的连接是否牢固。     确保所有必要的引脚都已正确连接,没有松动或短路。 2. 电源问题:     确保目标板和调试器都有足够的电源供应。     检查电源电压是否符合目标板的规格要求。 3. 固件问题: &n
    丙丁先生 2024-12-01 17:37 102浏览
  • 《高速PCB设计经验规则应用实践》+PCB绘制学习与验证读书首先看目录,我感兴趣的是这一节;作者在书中列举了一条经典规则,然后进行详细分析,通过公式推导图表列举说明了传统的这一规则是受到电容加工特点影响的,在使用了MLCC陶瓷电容后这一条规则已经不再实用了。图书还列举了高速PCB设计需要的专业工具和仿真软件,当然由于篇幅所限,只是介绍了一点点设计步骤;我最感兴趣的部分还是元件布局的经验规则,在这里列举如下:在这里,演示一下,我根据书本知识进行电机驱动的布局:这也算知行合一吧。对于布局书中有一句:
    wuyu2009 2024-11-30 20:30 128浏览
  • 遇到部分串口工具不支持1500000波特率,这时候就需要进行修改,本文以触觉智能RK3562开发板修改系统波特率为115200为例,介绍瑞芯微方案主板Linux修改系统串口波特率教程。温馨提示:瑞芯微方案主板/开发板串口波特率只支持115200或1500000。修改Loader打印波特率查看对应芯片的MINIALL.ini确定要修改的bin文件#查看对应芯片的MINIALL.ini cat rkbin/RKBOOT/RK3562MINIALL.ini修改uart baudrate参数修改以下目
    Industio_触觉智能 2024-12-03 11:28 87浏览
  • 学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&
    youyeye 2024-11-30 14:30 78浏览
  • TOF多区传感器: ND06   ND06是一款微型多区高集成度ToF测距传感器,其支持24个区域(6 x 4)同步测距,测距范围远达5m,具有测距范围广、精度高、测距稳定等特点。适用于投影仪的无感自动对焦和梯形校正、AIoT、手势识别、智能面板和智能灯具等多种场景。                 如果用ND06进行手势识别,只需要经过三个步骤: 第一步&
    esad0 2024-12-04 11:20 58浏览
  •         温度传感器的精度受哪些因素影响,要先看所用的温度传感器输出哪种信号,不同信号输出的温度传感器影响精度的因素也不同。        现在常用的温度传感器输出信号有以下几种:电阻信号、电流信号、电压信号、数字信号等。以输出电阻信号的温度传感器为例,还细分为正温度系数温度传感器和负温度系数温度传感器,常用的铂电阻PT100/1000温度传感器就是正温度系数,就是说随着温度的升高,输出的电阻值会增大。对于输出
    锦正茂科技 2024-12-03 11:50 112浏览
  • 光伏逆变器是一种高效的能量转换设备,它能够将光伏太阳能板(PV)产生的不稳定的直流电压转换成与市电频率同步的交流电。这种转换后的电能不仅可以回馈至商用输电网络,还能供独立电网系统使用。光伏逆变器在商业光伏储能电站和家庭独立储能系统等应用领域中得到了广泛的应用。光耦合器,以其高速信号传输、出色的共模抑制比以及单向信号传输和光电隔离的特性,在光伏逆变器中扮演着至关重要的角色。它确保了系统的安全隔离、干扰的有效隔离以及通信信号的精准传输。光耦合器的使用不仅提高了系统的稳定性和安全性,而且由于其低功耗的
    晶台光耦 2024-12-02 10:40 125浏览
  • 概述 说明(三)探讨的是比较器一般带有滞回(Hysteresis)功能,为了解决输入信号转换速率不够的问题。前文还提到,即便使能滞回(Hysteresis)功能,还是无法解决SiPM读出测试系统需要解决的问题。本文在说明(三)的基础上,继续探讨为SiPM读出测试系统寻求合适的模拟脉冲检出方案。前四代SiPM使用的高速比较器指标缺陷 由于前端模拟信号属于典型的指数脉冲,所以下降沿转换速率(Slew Rate)过慢,导致比较器检出出现不必要的问题。尽管比较器可以使能滞回(Hysteresis)模块功
    coyoo 2024-12-03 12:20 124浏览
  • 作为优秀工程师的你,已身经百战、阅板无数!请先醒醒,新的项目来了,这是一个既要、又要、还要的产品需求,ARM核心板中一个处理器怎么能实现这么丰富的外围接口?踌躇之际,你偶阅此文。于是,“潘多拉”的魔盒打开了!没错,USB资源就是你打开新世界得钥匙,它能做哪些扩展呢?1.1  USB扩网口通用ARM处理器大多带两路网口,如果项目中有多路网路接口的需求,一般会选择在主板外部加交换机/路由器。当然,出于成本考虑,也可以将Switch芯片集成到ARM核心板或底板上,如KSZ9897、
    万象奥科 2024-12-03 10:24 68浏览
  • 11-29学习笔记11-29学习笔记习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记
    youyeye 2024-12-02 23:58 73浏览
  • 当前,智能汽车产业迎来重大变局,随着人工智能、5G、大数据等新一代信息技术的迅猛发展,智能网联汽车正呈现强劲发展势头。11月26日,在2024紫光展锐全球合作伙伴大会汽车电子生态论坛上,紫光展锐与上汽海外出行联合发布搭载紫光展锐A7870的上汽海外MG量产车型,并发布A7710系列UWB数字钥匙解决方案平台,可应用于数字钥匙、活体检测、脚踢雷达、自动泊车等多种智能汽车场景。 联合发布量产车型,推动汽车智能化出海紫光展锐与上汽海外出行达成战略合作,联合发布搭载紫光展锐A7870的量产车型
    紫光展锐 2024-12-03 11:38 105浏览
  • 最近几年,新能源汽车愈发受到消费者的青睐,其销量也是一路走高。据中汽协公布的数据显示,2024年10月,新能源汽车产销分别完成146.3万辆和143万辆,同比分别增长48%和49.6%。而结合各家新能源车企所公布的销量数据来看,比亚迪再度夺得了销冠宝座,其10月新能源汽车销量达到了502657辆,同比增长66.53%。众所周知,比亚迪是新能源汽车领域的重要参与者,其一举一动向来为外界所关注。日前,比亚迪汽车旗下品牌方程豹汽车推出了新车方程豹豹8,该款车型一上市就迅速吸引了消费者的目光,成为SUV
    刘旷 2024-12-02 09:32 120浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦