内存泄漏检测组件

news2024/11/28 22:52:59

文章目录

  • 一、内存泄漏概述
    • 1.1 什么是内存泄漏
    • 1.2 内存泄漏导致的后果
    • 1.3 内存泄漏解决思路
  • 二、宏定义方法
    • 2.1 宏定义
    • 2.2 检测位置
    • 2.3 结果分析
  • 三、hook方法
    • 3.1 hook
    • 3.2 检测位置
    • 3.3 递归调用
    • 3.4 结果分析
    • 3.5 addr2line
  • 四、__libc_malloc 和 __libc_free

一、内存泄漏概述

1.1 什么是内存泄漏

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

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

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

void func(size_t s1)
{
	void p1=malloc(s1);
	void p2=malloc(s1);
	free(p1);
}

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

1.2 内存泄漏导致的后果

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

1.3 内存泄漏解决思路

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

解决内存泄漏,我们需要解决两点:
1)能够检测出来是否发送内存泄漏
2)如果发生内存泄漏,能够检测出来具体是哪一行代码所引起的。

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

二、宏定义方法

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文件夹里,可以看到存在一个文件,说明有一个地方出现内存泄漏
在这里插入图片描述

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

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

三、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,那么就需要防止内部递归进入死循环。
在这里插入图片描述
通过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);

}

在这里插入图片描述
从结果看存在一个内存泄漏,但是 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 位于内存映像上,所以会产生乱码。
在这里插入图片描述
解决办法是利用动态链接库的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);

}

四、__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.coloradmin.cn/o/741742.html

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

相关文章

Linux 进度条

文章目录 一、理解回车换行二、认识行缓冲1、代码一、二&#xff08;回车换行理解&#xff09;2、代码三、四&#xff08;sleep函数和ffush函数理解&#xff09; 三、简单倒计时1、效果展示2、倒计时代码3、实现过程分析 四、进度条1、效果展示2、进度条代码   makefile   …

Mac上快速将视频转化为GIF动图

1、找到需要转为GIF的视频&#xff0c;使用QuickTime Player打开&#xff0c;找到屏幕左上角的QuickTime Player菜单&#xff0c;点击【编辑】-【修剪】 2、视频下方会出现一个时间轴&#xff0c;拖动选取自己想要的时间段&#xff0c;修剪完成后保存 3、右键剪辑好的视频&…

前端学习——Web API (Day4)

日期对象 实例化 日期对象方法 案例 <!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content…

【前端】网页开发精讲与实战 CSS Day 2

&#x1f680;Write In Front&#x1f680; &#x1f4dd;个人主页&#xff1a;令夏二十三 &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd; &#x1f4e3;系列专栏&#xff1a;前端 &#x1f4ac;总结&#xff1a;希望你看完之后&#xff0c;能对你有…

『点云处理任务 』用PCL库 还是 深度学习模型?

深度学习和PCL库都可以用来做点云处理任务&#xff0c;但是二者侧重点有所不同。 1、PCL库&#xff08;点云库&#xff09;是一个专门用于点云处理和三维几何分析的开源类库&#xff0c;常用于以下任务&#xff1a; 1、点云滤波&#xff1a;用于去除噪音、下采样和平滑等操作&…

GM/T0015-2012学习笔记

GM/T0015-2012学习笔记 文章目录 GM/T0015-2012学习笔记数字证书数字证书特性用户证书形式 数字证书格式DER资料1资料2 //TODO 吐槽一下&#xff1a;既然是标准&#xff0c;就应该是广而告知&#xff0c;被一些信息查的网站&#xff0c;高价出售。 我从“密码行业标准化技术委…

一文读懂单分子标签UMI

背景 在整理分子标签&#xff08;unique molecular identifier&#xff0c;UMI&#xff09;之前&#xff0c;先了解下&#xff1a; NGS 中潜在的错误来源有哪些&#xff1f; 1. 来源建库过程&#xff1a;文库制备、靶向序列捕获和测序均涉及 DNA 聚合酶以及扩增步骤。这些过程…

和LangChain CEO一起讲解深度学习在数据领域的应用;如何识别语音DeepFake?

&#x1f989; AI新闻 &#x1f680; 如何识别语音DeepFake&#xff1f; 摘要&#xff1a;加拿大滑铁卢大学的研究人员开发了一种语音DeepFake软件&#xff0c;成功qipian语音认证系统概率高达99%。其他安全研究人员也开始应对这一技术挑战&#xff0c;亚马逊研究人员尝试检查…

【实验四】多态

1、完成第133页实验题目2 import java.util.Scanner;public class Application{private UserDao dao;public Application(UserDao dao){this.daodao;}public void setDao(UserDao dao){this.dao dao;}public void registe()//注册函数{Scanner scnnew Scanner(System.in);//获…

10分钟理解RNN、LSTM、Transformer结构原理!

文章目录 一、RNN1.1 RNN基本架构1.2 RNN经典的三种结构1.2.1 vector-to-sequence结构1.2.2 sequence-to-vector结构1.2.3 Encoder-Decoder结构 1.3 RNN常用领域1.4 RNN的优缺点1.5 RNN中为什么会出现梯度消失 二、LSTM2.1 LSTM与RNN差异2.2 LSTM核心思想图解2.2.1 忘记层门2.2…

小程序上传头像功能

前台wxml代码 点击navigator&#xff0c;跳转到裁剪页面 <navigator url"/pages/cropper/cropper?userid{{user._id}}&&imgSrc{{user.img}}" hover-class"none"><view class"user-logo-section"><text class"user…

1亿条数据批量插入 MySQL,哪种方式最快?

利用JAVA向Mysql插入一亿数量级数据—效率测评 这几天研究mysql优化中查询效率时&#xff0c;发现测试的数据太少&#xff08;10万级别&#xff09;&#xff0c;利用 EXPLAIN 比较不同的 SQL 语句&#xff0c;不能够得到比较有效的测评数据&#xff0c;大多模棱两可&#xff0c…

深化校企合作,开源网安为软件安全人才培养按下“加速键”

开源网安一直以来十分重视网络安全人才的培养&#xff0c;已陆续与湖北大学、武汉工业大学、桂林电子科技大学等多所高校建立战略合作&#xff0c;打造产学研协同的多类型人才培养模式。6月29日&#xff0c;开源网安与桂林电子科技大学携手举办了软件安全开发与DevSecOps实训课…

简要介绍 | 心脏机械-电耦合理论:原理、研究现状与未来展望

注1&#xff1a;本文系“简要介绍”系列之一&#xff0c;仅从概念上对心脏机械-电耦合理论进行非常简要的介绍&#xff0c;不适合用于深入和详细的了解。 心脏机械-电耦合理论&#xff1a;原理、研究现状与未来展望 心脏中精密的血流局部调控机制&#xff1a;electro-metabolic…

使用ChatGPT进行个性化学习

推荐&#xff1a;将 NSDT场景编辑器 加入你的3D工具链 3D工具集&#xff1a; NSDT简石数字孪生 在这篇文章中&#xff0c;您将发现 ChatGPT 作为机器学习和数据科学爱好者的个人导师的好处。特别是&#xff0c;您将学习 如何让ChatGPT引导你学习抽象代数如何让 ChatGPT 帮助您…

代码随想录day9

28. 找出字符串中第一个匹配项的下标 思路&#xff1a; 没有。。。。真不会。。。。下次再来吧 代码&#xff1a; def strStr(self, haystack: str, needle: str) -> int:if not needle:return 0next [0] * len(needle)self.getNext(next, needle)j -1for i in range(…

路径规划算法:基于猎食者优化的路径规划算法- 附代码

路径规划算法&#xff1a;基于猎食者优化的路径规划算法- 附代码 文章目录 路径规划算法&#xff1a;基于猎食者优化的路径规划算法- 附代码1.算法原理1.1 环境设定1.2 约束条件1.3 适应度函数 2.算法结果3.MATLAB代码4.参考文献 摘要&#xff1a;本文主要介绍利用智能优化算法…

如何搭建自己的图床(GitHub版)

文章目录 1.图床的概念2.用GitHub创建图床服务器2.1.新建仓库2.2.生成Token令牌2.3.创建img分支和该分支下的img文件夹(可选) 3.使用PicGo软件上传图片3.1 下载PicGo软件3.2配置PicGo3.3用PicGo实现上传 4. Typora实现自动上传5.免费图片网站 前言&#xff1a; 如果没有自己的服…

暑假第六天打卡

离散&#xff1a; 极小项&#xff1a; &#xff08;1&#xff09;简单合取式 &#xff08;2&#xff09;每个字母只出现一次 &#xff08;3&#xff09;按字典顺序排列 &#xff08;4&#xff09;成真赋值&#xff0c;且化为十进制 极大项 &#xff08;1&#xff09;简单…

智能化客流系统-实时监测人流趋势,助力商场销售策略优化

随着人们对安全和便利性的要求不断提高&#xff0c;智慧客流人数管理系统的应用已经成为各类场所管理的必备工具。它可以帮助管理者实时监测人流情况&#xff0c;提供精准的服务和安全保障。 一、案例展示 智慧客流人数管理系统在图书馆的应用&#xff0c;通过实时监测和数据…