C++内存泄漏检测

C++进阶专栏:http://t.csdnimg.cn/aTncz

相关系列文章

C++技术要点总结, 面试必备, 收藏起来慢慢看

C++惯用法之RAII思想: 资源管理

C++智能指针的自定义销毁器(销毁策略)

目录

1.内存泄漏概述

1.1.内存泄漏产生原因

1.2 内存泄漏导致的后果

1.3 内存泄漏解决思路

2.宏定义方法

2.1.宏定义

2.2.检测位置

2.3.结果分析

3.hook方法

3.1.hook

3.2.检测位置

3.3 递归调用

3.4.结果分析

3.5.addr2line

4.__libc_malloc 和 __libc_free


1.内存泄漏概述

1.1.内存泄漏产生原因

内存泄漏是在没有自动 gc 的编程语言里面,经常发生的一个问题。

自动垃圾回收(Automatic Garbage Collection,简称 GC)是一种内存管理技术,在程序运行时自动检测和回收不再使用的内存对象,以避免内存泄漏和释放已分配内存的负担。

因为没有 gc,所以分配的内存需要程序员自己调用释放。其核心原因是调用分配与释放没有符合开闭原则,没有配对,形成了有分配,没有释放的指针,从而产生了内存泄漏。

void myTest(size_t s1)
{void a1=malloc(s1);void a2=malloc(s1);free(a1);
}

以上代码段,分配了两个s1大小的内存块,由 a1 与 a2 指向。而代码块执行完以后,释放了 a1,而 a2 没有释放。形成了有分配没有释放的指针,产生了内存泄漏。

1.2 内存泄漏导致的后果

随着工程代码量越来越多,有分配没有释放,自然会使得进程堆的内存会越来越少,直到耗尽。从而导致后面的运行时代码不能成功分配内存,使程序崩溃。

1.3 内存泄漏解决思路

最好的办法肯定是引入自动垃圾回收gc。但是这不适合C/C++语言。

解决内存泄漏,我们需要解决两点:

1)能够检测出来是否发送内存泄漏

2)如果发生内存泄漏,能够检测出来具体是哪一行代码所引起的。

内存泄漏是由于内存分配与内存释放,不匹配所引起的。因此对内存分配函数malloc/calloc/realloc,以及内存释放函数free进行“劫持”hook,就能能够统计出内存分配的位置,内存释放的位置,从而判断是否匹配。

2.宏定义方法

2.1.宏定义

使用宏定义,替换系统的内存分配接口。并利用__FILE__、__LINE__分别获取当前编译文件的文件名、行号,进行追踪位置信息。

#define malloc(size)    _malloc(size, __FILE__, __LINE__)
#define free(ptr)       _free(ptr, __FILE__, __LINE__)

需要注意的是,宏定义一定要放在内存分配之前,这样预编译阶段才会替换为我们自己实现的_malloc和_free。

2.2.检测位置

为了方便观察,我们可以在内存分配_malloc的时候,创建一个文件。文件名为指向新分配内存的指针值,文件内容为指针值、调用_malloc时的文件名、行号。

在该内存释放_free的时候,删除该指针对应的文件。

最后,程序运行结束,如果没有文件说明没有内存泄漏,否则说明存在内存泄漏。

2.3.结果分析

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>void *_malloc(size_t size, const char *filename, int line){void *ptr = malloc(size);char buffer[128] = {0};sprintf(buffer, "./memory/%p.memory", ptr);FILE *fp = fopen(buffer, "w");fprintf(fp, "[+]addr: %p, filename: %s, line: %d\n", ptr, filename, line);fflush(fp);fclose(fp);return ptr;
}void _free(void *ptr, const char *filename, int line){char buffer[128] = {0};sprintf(buffer, "./memory/%p.memory", ptr);if (unlink(buffer) < 0){printf("double free: %p\n", ptr);return;}return free(ptr);
}#define malloc(size)    _malloc(size, __FILE__, __LINE__)
#define free(ptr)       _free(ptr, __FILE__, __LINE__)int main() {void *p1 = malloc(5);void *p2 = malloc(18);void *p3 = malloc(15);free(p1);free(p3);
}

最后在memory文件夹里,可以看到存在一个文件,说明有一个地方出现内存泄漏

23fc643d5acd44c99c23e5c0c0b6e2bf.png

[+]addr: 0x559e55b6e8b0, filename: fun1.c, line: 39

从结果上看,内存泄漏发生第39行。

3.hook方法

利用 hook 机制改写系统的内存分配函数。

3.1.hook

hook方法的实现分三个步骤

1)定义函数指针。

typedef void *(*malloc_t)(size_t size);
malloc_t malloc_f = NULL;typedef void (*free_t)(void *ptr);
free_t free_f = NULL;

2)函数实现,函数名与目标函数名一致。

void *malloc(size_t size)
{//改写的功能
}void free(void *ptr)
{//改写的功能
}

3)初始化hook,调用dlsym()。

void init_hook(){if (!malloc_f){malloc_f = dlsym(RTLD_NEXT, "malloc");}if (!free_f){free_f = dlsym(RTLD_NEXT, "free");}
}

3.2.检测位置

宏定义的方法在检测调用所在行号的时候使用了系统定义的__LINE__,因为是宏定义的malloc,预编译时候直接嵌入。因此__LINE__返回的就是调用malloc的位置。

但是hook方法不一样,系统定义的__LINE__在函数内部调用,无法确定在主函数中的调用位置。比如

fprintf(fp, "[+]addr: %p, filename: %s, line: %d\n", ptr, filename, line);

返回的就是fprintf所在的行号。

因此使用gcc 提供的__builtin_return_address,该函数返回当前函数或其调用者之一的返回地址。参数level 表示向上扫描调用堆栈的帧数。比如对于 main --> f1() --> f2() --> f3() ,f3()函数里面调用 __builtin_return_address (0),返回f3的地址;调用 __builtin_return_address (1),返回f2的地址;

3.3 递归调用

hook的时候,要考虑其他函数也用到所hook住的函数,比如在printf()函数里面也调用了malloc,那么就需要防止内部递归进入死循环。

dfe968f0844c4a8882197a4282565d98.png

通过gdb调试,在第23行打断点,发现每次运行都回到了23行。

这是因为sprintf隐含调用了malloc,这样就陷入一个循环:

23行的sprintf —> 自定义的malloc —> 23行的sprintf —> 自定义的malloc --> 23行的sprintf —> 自定义的malloc --> ……

解决办法是,限制调用次数。当进入 malloc 函数内部后,根据自己的需要,设置 hook 的开关。在关闭的区域内调用 malloc 后进入到 else 部分执行原来的 hook 函数,避免了无限递归的发生。

int enable_malloc_hook = 1;
void *malloc(size_t size) { // 执行改写的 malloc 函数if (enable_malloc_hook) {enable_malloc_hook = 0;// 关闭 hook, printf 内部的 malloc 执行 else 的部分// 其他代码enable_malloc_hook = 1;}// 执行原来的 malloc 函数else {p = malloc_f(size);}
}

3.4.结果分析

// gcc -o fun2 fun2.c -ldl -g#define _GNU_SOURCE
#include <dlfcn.h>#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <link.h>typedef void *(*malloc_t)(size_t size);
malloc_t malloc_f = NULL;typedef void (*free_t)(void *ptr);
free_t free_f = NULL;int enable_malloc_hook = 1;
int enable_free_hook = 1;void *malloc(size_t size){void *ptr = NULL;if (enable_malloc_hook ){enable_malloc_hook = 0; enable_free_hook = 0;ptr = malloc_f(size);void *caller = __builtin_return_address(0);char buffer[128] = {0};sprintf(buffer, "./memory/%p.memory", ptr);FILE *fp = fopen(buffer, "w");fprintf(fp, "[+] caller: %p, addr: %p, size: %ld\n", caller, ptr, size);fflush(fp);fclose(fp);enable_malloc_hook = 1;enable_free_hook = 1;}else {ptr = malloc_f(size);}return ptr;
}void free(void *ptr){if (enable_free_hook ){enable_free_hook = 0;enable_malloc_hook = 0;char buffer[128] = {0};sprintf(buffer, "./memory/%p.memory", ptr);if (unlink(buffer) < 0){printf("double free: %p\n", ptr);return;}free_f(ptr);enable_malloc_hook = 1;enable_free_hook = 1;}else {free_f(ptr);}
}void init_hook(){if (!malloc_f){malloc_f = dlsym(RTLD_NEXT, "malloc");}if (!free_f){free_f = dlsym(RTLD_NEXT, "free");}
}
int main(){init_hook();void *p1 = malloc(5);void *p2 = malloc(18);void *p3 = malloc(15);free(p1);free(p3);
}

bb751a65b5174dadb8a8cb1e3cd59486.png

从结果看存在一个内存泄漏,但是 caller:0x16bb 是地址,不是具体行号。使用addr2line可以将地址转换为文件名和行号。

3.5.addr2line

利用addr2line工具,将地址转换为文件名和行号,得到源文件的行数(根据机器码地址定位到源码所在行数)

addr2line -f -e fun2 -a 0x16bb

参数:-f:显示函数名信息。-e filename:指定需要转换地址的可执行文件名。-a address:显示指定地址(十六进制)。

但是,高版本 gcc 下使用 addr2line 命令会出现乱码问题。

??
??:0

addr2line 作用于 ELF 可执行文件,而高版本的 gcc 调用 __builtin_return_address返回的地址 caller 位于内存映像上,所以会产生乱码。

d48e4cbda9b848cc882877e7206bd79a.png

解决办法是利用动态链接库的dladdr函数 ,作用于共享目标,可以获取某个地址的符号信息。使用该函数可以解析符号地址。如下:

// gcc -o fun2 fun2.c -ldl -g#define _GNU_SOURCE
#include <dlfcn.h>#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <link.h>// 解析地址
void* converToELF(void *addr) {Dl_info info;struct link_map *link;dladdr1(addr, &info, (void **)&link, RTLD_DL_LINKMAP);// printf("%p\n", (void *)(size_t)addr - link->l_addr);return (void *)((size_t)addr - link->l_addr);
}typedef void *(*malloc_t)(size_t size);
malloc_t malloc_f = NULL;typedef void (*free_t)(void *ptr);
free_t free_f = NULL;int enable_malloc_hook = 1;
int enable_free_hook = 1;void *malloc(size_t size){void *ptr = NULL;if (enable_malloc_hook ){enable_malloc_hook = 0; ptr = malloc_f(size);void *caller = __builtin_return_address(0);char buffer[128] = {0};sprintf(buffer, "./memory/%p.memory", ptr);FILE *fp = fopen(buffer, "w");// converToELF(caller)fprintf(fp, "[+] caller: %p, addr: %p, size: %ld\n", converToELF(caller), ptr, size);fflush(fp);fclose(fp);enable_malloc_hook = 1;}else {ptr = malloc_f(size);}return ptr;
}void free(void *ptr){if (enable_free_hook ){enable_free_hook = 0;char buffer[128] = {0};sprintf(buffer, "./memory/%p.memory", ptr);if (unlink(buffer) < 0){printf("double free: %p\n", ptr);return;}free_f(ptr);enable_free_hook = 1;}else {free_f(ptr);}
}void init_hook(){if (!malloc_f){malloc_f = dlsym(RTLD_NEXT, "malloc");}if (!free_f){free_f = dlsym(RTLD_NEXT, "free");}
}
int main(){init_hook();void *p1 = malloc(5);void *p2 = malloc(18);void *p3 = malloc(15);free(p1);free(p3);
}

4.__libc_malloc 和 __libc_free

思路和hook的一样,因为malloc和free底层调用的也是__libc_malloc和__libc_free。

// gcc -o fun3 fun3.c
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <link.h>void* converToELF(void *addr) {Dl_info info;struct link_map *link;dladdr1(addr, &info, (void **)&link, RTLD_DL_LINKMAP);// printf("%p\n", (void *)(size_t)addr - link->l_addr);return (void *)((size_t)addr - link->l_addr);
}extern void *__libc_malloc(size_t size);
extern void *__libc_free(void *ptr);int enable_malloc_hook = 1;
int enable_free_hook = 1;void *malloc(size_t size){void *ptr = NULL;if (enable_malloc_hook ){enable_malloc_hook = 0; enable_free_hook = 0;ptr = __libc_malloc(size);void *caller = __builtin_return_address(0);char buffer[128] = {0};sprintf(buffer, "./memory/%p.memory", ptr);FILE *fp = fopen(buffer, "w");fprintf(fp, "[+] caller: %p, addr: %p, size: %ld\n", converToELF(caller), ptr, size);fflush(fp);fclose(fp);enable_malloc_hook = 1;enable_free_hook = 1;}else {ptr = __libc_malloc(size);}return ptr;
}void free(void *ptr){if (enable_free_hook ){enable_free_hook = 0;enable_malloc_hook = 0;char buffer[128] = {0};sprintf(buffer, "./memory/%p.memory", ptr);if (unlink(buffer) < 0){printf("double free: %p\n", ptr);return;}__libc_free(ptr);enable_malloc_hook = 1;enable_free_hook = 1;}else {__libc_free(ptr);}
}int main(){void *p1 = malloc(5);void *p2 = malloc(18);void *p3 = malloc(15);free(p1);free(p3);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/726155.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于Springboot免费搭载轻量级阿里云OSS数据存储库(将本地文本、照片、视频、音频等上传云服务保存)

一、注册阿里云账户 打开https://www.aliyun.com/&#xff0c;申请阿里云账户并完成实名认证&#xff08;个人&#xff09;。这种情况就是完成了&#xff1a; 二、开通OSS服务 点击立即开通即可。 三、创建Bucket 申请id和secert&#xff1a; 进去创建一个Accesskey就会出现以…

【Qt学习】QProgressBar的使用(进度条的实现)

文章目录 1. 介绍2. 实例2.1 按钮启动进度条2.2 更改进度条样式2.3 资源文件 1. 介绍 详细的 QProgressBar 内容可以通过 查阅Qt官方文档 &#xff0c;这里进行简要的总结&#xff1a; QProgressBar 是Qt框架中的一个控件&#xff0c;用于显示进度条&#xff1a; QProgressBar…

wordpress免费主题下载

免费wordpress模板下载 简洁大气的文化艺术类wordpress模板&#xff0c;可以免费下载&#xff0c;实用易上手&#xff0c;新手也适合。 https://www.wpniu.com/themes/304.html 免费wordpress主题下载 高端大气上档次的wordpress主题&#xff0c;也可以是免费的&#xff0c;…

修改MonkeyDev默认配置适配Xcode15

上一篇文章介绍了升级Xcode15后,适配MonkeyDev的一些操作,具体操作可以查看:Xcode 15 适配 MonkeyDev。 但是每次新建项目都要去修改那些配置,浪费时间和精力,这篇文章主要介绍如何修改MonkeyDev的默认配置,做到一次修改永久生效。 MonkeyDev的默认安装路径是在/opt/Mo…

iclone更奇怪了用自动对齐才搞得定

1前一个clip的位置 2选root的话就跑到这里了&#xff0c;跟前一个clip差很多 3换了left foot对齐之后才正常 4这时候开不开自动对齐不影响 5奇怪医生的中心似乎是途中的花坐标轴偏离人体好多呀不知何时跑这里的难道前面是应为这个&#xff1f;中心跑了我还不知道 6动画交叉的时…

【常见索引使用】⭐️Mysql中索引的类型以及使用方式和失效场景

目录 一、前言 二、数据准备 三、索引的分类 四、索引示例 示例1、主键索引&#xff08;Primary Key Index&#xff09;与 唯一索引&#xff08;Unique Index&#xff09; 示例2、前缀索引&#xff08;Prefix Index&#xff09; 示例3、联合索引&#xff08;复合索引&am…

GWO-RF|灰狼算法优化随机森林 分类预测|多变量分类预测

目录 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 亮点与优势&#xff1a; 二、实际运行效果&#xff1a; 三、算法介绍&#xff1a; 灰狼优化算法&#xff1a; 随机森林&#xff1a; 四、完整程序下载&#xff1a; 一、程序及算法内容介绍&#xff1a; …

如何规划应用商店优化策略

应用商店是拥挤的地方。拥有超过 600 万个应用程序&#xff0c;制定应用程序商店优化 (ASO) 策略比以往任何时候都更加重要。ASO 有助于确保您的应用在搜索结果中排名更高&#xff0c;以便潜在用户可以轻松找到它。通过针对App Store和 Google Play优化App&#xff0c;能够吸引…

使用ES检索PDF或Word等格式文件方案

#大数据/ES #经验 #方案架构 ES检索PDF/Word等格式文件方案 插件安装 ES有文档预处理插件&#xff0c;但是7.x版本默认发版包不包含这个ingest attachment plugin 。 通过摄取附件插件&#xff0c;Elasticsearch 可以使用 Apache 文本提取库 Tika 提取常见格式的文件附件&a…

Tomcat介绍在IDEA中创建JavaWeb工程

文章目录 一、WEB服务器服务器概述使用Java代码手写web服务器 二、服务器软件Web服务器服务器软件的使用步骤 三、TomcatTomcat的下载Tomcat的安装与卸载Tomcat的启动与关闭常见问题 四、新建Java Web项目并将项目部署到tomcat中新建Java Web项目将项目部署到Tomcat中出现的问题…

[晓理紫]每日论文分享(有中文摘要,源码或项目地址)--大模型

专属领域论文订阅 VX关注{晓理紫}&#xff0c;每日更新论文&#xff0c;如感兴趣&#xff0c;请转发给有需要的同学&#xff0c;谢谢支持 如果你感觉对你有所帮助&#xff0c;请关注我&#xff0c;每日准时为你推送最新论文。 》》 由于精力有限&#xff0c;今后就不在CSDN上更…

去除PDF论文行号的完美解决方案

去除PDF论文行号的完美解决方案 1. 遇到的问题 我想去除论文的行号&#xff0c;但是使用网上的Adobe Acrobat裁剪保存后 如何去掉pdf的行编号&#xff1f; - 知乎 (zhihu.com) 翻译时依然会出现行号&#xff0c;或者是转成word&#xff0c;这样就大大损失了格式&#xff0c;…

第十五届蓝桥杯青少组STEMA测评SPIKE初级真题试卷 2024年1月

第十五届蓝桥杯青少组STEMA测评SPIKE初级真题试卷 2024年1月 ​​​​​​​ 来自&#xff1a;6547网 http://www.6547.cn/doc/vywur8eics

SOC设计:关于时钟门控的细节

有如下几个信号 输入信号 1、同步后的rstnsync_clk 2、时钟&#xff1a;clk 3、test_mode 4、软件控制信号&#xff1a;clk_sub_en 输出信号 1、clk_sub 功能&#xff1a;软件配置的使能信号clk_sub_en经过时钟clk 2拍同步处理后产生clk 域下的enable信号&#xff0c;然…

常用MII接口详解

开放式系统互连 (OSI) 模型 七层开放系统互连 (OSI) 模型中&#xff0c;以太网层 位于最底部两层 - 物理层和数据链路层。 从百兆以太网接口开始 首先是百兆以太网规定的两种接口 介质无关接口 (MII) Media Independent Interface 介质相关接口 (MDI) Medium Depen…

深入探讨javascript的流程控制与分支结构,以及js的函数

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属的专栏&#xff1a;前端泛海 景天的主页&#xff1a;景天科技苑 文章目录 1.流程控制与分支结构1.if分支结构2.switch case 分支结构3.循环结…

九型人格测试,7号活跃型人格的职业分析

九型人格中的7号人格&#xff0c;也叫活跃型人格&#xff0c;正如名字所形容的那样&#xff0c;一个活跃型人格的人&#xff0c;会让你体会到生活的乐趣。跟他们在一起的时候&#xff0c;永远不会感到无聊&#xff0c;即便是在停电的下午&#xff0c;也能在屋子里愉快的折腾。活…

【RK3288 Android6, T8PRO 快捷按键 gpio 配置上拉输入】

文章目录 【RK3288 Android6&#xff0c; T8PRO 快捷按键 gpio 配置上拉输入】需求开发过程尝试找到没有用的上拉gpio尝试修改pwm1的gpio的默认上拉模式 改动 【RK3288 Android6&#xff0c; T8PRO 快捷按键 gpio 配置上拉输入】 需求 T8pro想要模仿T10 的 快捷按键&#xff…

MyBatisPlus(SpringBoot版)的分页插件

目录 一、前置工作: 1.整体项目目录结构 2.创建普通javamaven项目。 3.导入依赖&#xff0c;改造成springboot项目 4.配置启动类 5.创建service接口及其实现类 6.创建接口Mapper 7.配置数据源 8.创建数据库表 二、使用MP&#xff08;mybatisplus&#xff09;的分页插件 二、使…

GWO-RF|灰狼算法优化随机森林 回归预测|多变量回归预测

目录 一、程序及算法内容介绍&#xff1a; 基本内容&#xff1a; 亮点与优势&#xff1a; 二、实际运行效果&#xff1a; 三、算法介绍&#xff1a; 灰狼优化算法&#xff1a; 随机森林&#xff1a; 四、完整程序下载&#xff1a; 一、程序及算法内容介绍&#xff1a; …