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 写点代码,写点人生!
评论 (0)
  • 在智能终端设备开发中,语音芯片与功放电路的配合直接影响音质表现。广州唯创电子的WTN6、WT588F等系列芯片虽功能强大,但若硬件设计不当,可能导致输出声音模糊、杂音明显。本文将以WTN6与WT588F系列为例,解析音质劣化的常见原因及解决方法,帮助开发者实现清晰纯净的语音输出。一、声音不清晰的典型表现与核心原因当语音芯片输出的音频信号存在以下问题时,需针对性排查:背景杂音:持续的“沙沙”声或高频啸叫,通常由信号干扰或滤波不足导致。语音失真:声音断断续续或含混不清,可能与信号幅度不匹配或功放参数
    广州唯创电子 2025-03-25 09:32 66浏览
  • 核心板简介创龙科技 SOM-TL3562 是一款基于瑞芯微 RK3562J/RK3562 处理器设计的四核 ARM C ortex-A53 + 单核 ARM Cortex-M0 全国产工业核心板,主频高达 2.0GHz。核心板 CPU、R OM、RAM、电源、晶振等所有元器件均采用国产工业级方案,国产化率 100%。核心板通过 LCC 邮票孔 + LGA 封装连接方式引出 MAC、GMAC、PCIe 2.1、USB3.0、 CAN、UART、SPI、MIPI CSI、MIPI
    Tronlong 2025-03-24 09:59 184浏览
  • 在嵌入式语音系统的开发过程中,广州唯创电子推出的WT588系列语音芯片凭借其优异的音质表现和灵活的编程特性,广泛应用于智能终端、工业控制、消费电子等领域。作为该系列芯片的关键状态指示信号,BUSY引脚的设计处理直接影响着系统交互的可靠性和功能拓展性。本文将从电路原理、应用场景、设计策略三个维度,深入解析BUSY引脚的技术特性及其工程实践要点。一、BUSY引脚工作原理与信号特性1.1 电气参数电平标准:输出3.3V TTL电平(与VDD同源)驱动能力:典型值±8mA(可直接驱动LED)响应延迟:语
    广州唯创电子 2025-03-26 09:26 74浏览
  • 在人工智能与物联网技术蓬勃发展的今天,语音交互已成为智能设备的重要功能。广州唯创电子推出的WT3000T8语音合成芯片凭借其高性能、低功耗和灵活的控制方式,广泛应用于智能家居、工业设备、公共服务终端等领域。本文将从功能特点、调用方法及实际应用场景入手,深入解析这款芯片的核心技术。一、WT3000T8芯片的核心功能WT3000T8是一款基于UART通信的语音合成芯片,支持中文、英文及多语种混合文本的实时合成。其核心优势包括:高兼容性:支持GB2312/GBK/BIG5/UNICODE编码,适应不同
    广州唯创电子 2025-03-24 08:42 158浏览
  •        当今社会已经步入了知识经济的时代,信息大爆炸,新鲜事物层出不穷,科技发展更是一日千里。知识经济时代以知识为核心生产要素,通过创新驱动和人力资本的高效运转推动社会经济发展。知识产权(IP)应运而生,成为了知识经济时代竞争的核心要素,知识产权(Intellectual Property,IP)是指法律赋予人们对‌智力创造成果和商业标识等无形财产‌所享有的专有权利。其核心目的是通过保护创新和创意,激励技术进步、文化繁荣和公平竞争,同时平衡公共利益与
    广州铁金刚 2025-03-24 10:46 75浏览
  • WT588F02B是广州唯创电子推出的一款高性能语音芯片,广泛应用于智能家电、安防设备、玩具等领域。然而,在实际开发中,用户可能会遇到烧录失败的问题,导致项目进度受阻。本文将从下载连线、文件容量、线路长度三大核心因素出发,深入分析烧录失败的原因并提供系统化的解决方案。一、检查下载器与芯片的物理连接问题表现烧录时提示"连接超时"或"设备未响应",或烧录进度条卡顿后报错。原因解析接口错位:WT588F02B采用SPI/UART双模通信,若下载器引脚定义与芯片引脚未严格对应(如TXD/RXD交叉错误)
    广州唯创电子 2025-03-26 09:05 77浏览
  • 文/Leon编辑/cc孙聪颖‍“无AI,不家电”的浪潮,正在席卷整个家电行业。中国家电及消费电子博览会(AWE2025)期间,几乎所有的企业,都展出了搭载最新AI大模型的产品,从电视、洗衣机、冰箱等黑白电,到扫地机器人、双足机器人,AI渗透率之高令人惊喜。此番景象,不仅让人思考:AI对于家电的真正意义是什么,具体体现在哪些方面?作为全球家电巨头,海信给出了颇有大智慧的答案:AI化繁为简,将复杂留给技术、把简单还给生活,是海信对于AI 家电的终极答案。在AWE上,海信发布了一系列世俱杯新品,发力家
    华尔街科技眼 2025-03-23 20:46 78浏览
  • 在智慧城市领域中,当一个智慧路灯项目因信号盲区而被迫增设数百个网关时,当一个传感器网络因入网设备数量爆增而导致系统通信失效时,当一个智慧交通系统因基站故障而导致交通瘫痪时,星型网络拓扑与蜂窝网络拓扑在构建广覆盖与高节点数物联网网络时的局限性便愈发凸显,行业内亟需一种更高效、可靠与稳定的组网技术以满足构建智慧城市海量IoT网络节点的需求。星型网络的无线信号覆盖范围高度依赖网关的部署密度,同时单一网关的承载设备数量有限,难以支撑海量IoT网络节点的城市物联系统;而蜂窝网络的无线信号覆盖范围同样高度依
    华普微HOPERF 2025-03-24 17:00 177浏览
  • 在智能终端设备快速普及的当下,语音交互已成为提升用户体验的关键功能。广州唯创电子推出的WT3000T8语音合成芯片,凭借其卓越的语音处理能力、灵活的控制模式及超低功耗设计,成为工业控制、商业终端、公共服务等领域的理想选择。本文将从技术特性、场景适配及成本优势三方面,解析其如何助力行业智能化转型。一、核心技术优势:精准、稳定、易集成1. 高品质语音输出,适配复杂环境音频性能:支持8kbps~320kbps宽范围比特率,兼容MP3/WAV格式,音质清晰自然,无机械感。大容量存储:内置Flash最大支
    广州唯创电子 2025-03-24 09:08 192浏览
  •       知识产权保护对工程师的双向影响      正向的激励,保护了工程师的创新成果与权益,给企业带来了知识产权方面的收益,企业的创新和发明大都是工程师的劳动成果,他们的职务发明应当受到奖励和保护,是企业发展的重要源泉。专利同时也成了工程师职称评定的指标之一,专利体现了工程师的创新能力,在求职、竞聘技术岗位或参与重大项目时,专利证书能显著增强个人竞争力。专利将工程师的创意转化为受法律保护的“无形资产”,避免技术成果被他人抄袭或无偿使
    广州铁金刚 2025-03-25 11:48 125浏览
我要评论
0
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦