当前位置: 首页 > news >正文

内存泄漏专题(7)hook之宏定义

前面介绍的mtrace也好,bcc也罢,其实都是hook技术的一种实现,但是mtrace本身使用场景上有局限,而bcc环境依赖则十分复杂。因此,这些调试手段只适用于开发环境用来调试,对于生产环境,均不是一个非常好的方法。

但其实所谓的hook技术,本身并不复杂,说白了就是重载malloc/free函数,让代码在调用malloc函数时,先调用我们自己定义的malloc,由自定义的malloc完成一些前置统计工作后,再去调用系统的malloc,从而完成一些内存的统计分析。明白了这一点,其实我们可以轻而易举地自己实现一套hook机制,嵌入到代码中,这样带来的好处显而易见:

  • 不依赖其他检测工具,可以非常方便地在开发环境甚至在线上进行调试
  • 可以在代码里通过开关的方式控制是否检测内存泄漏,如线上默认关闭,如果怀疑出现内存泄漏,则将开关打开,可以非常方便地定位问题
  • 系统无关,可以针对不同操作系统有不同的实现,这对于跨平台的应用非常有帮助。

对于C语言,我们可以用dlsym去修改动态链接的函数(备注:dlsym函数是GNU扩展),但dlsym的最大劣势仍然是只能在Linux下使用,那么,有没有一套跨平台的方案,可以在所有平台上运行呢?其实也是有的。

那就是通过宏定义的方式,将malloc/free这些函数替换成我们自己实现的钩子函数。比如我们有如下定义:

//mcheck.h
#ifndef _MCHECK_H_
#define _MCHECK_H_#include<stddef.h>void mcheck_initialize();
void mcheck_terminate();void *malloc_hook(size_t size, const char *file, int line);
void *calloc_hook(size_t nmemb, size_t size, const char *file, int line);
void *realloc_hook(void *ptr, size_t size, const char *file, int line);
void free_hook(void *p, const char *file, int line);#define malloc(size)            malloc_hook(size, __FILE__, __LINE__)
#define calloc(nmemb, size)     calloc_hook(nmemb, size, __FILE__, __LINE__)
#define realloc(ptr, size)       realloc_hook(ptr, size, __FILE__, __LINE__)
#define free(p)                 free_hook(p, __FILE__, __LINE__)#endif

由于宏定义是直接替换,因此,有了上面的代码,在执行malloc的时候,实际上执行的是malloc_hook函数。而我们只需要在实现malloc_hook/free_hook的时候加上一些统计信息就行了。

在此之前,我们封装一个链表,用来存储每次申请内存的地址以及申请内存的大小,在每次申请内存的时候,向链表添加一条数据,每次释放的时候,将对应的记录删除掉,那么,当程序结束,如果链表还有数据,那就是没有释放的泄露部分的内存。

当然你也可以用其他的数据接口来存储,这里为了简单演示,就直接使用链表了。

// meminfo.h
#ifndef _MEMINFO_H_
#define _MEMINFO_H_#include<stddef.h>
#include<stdio.h>
#include<stdlib.h>typedef enum mcheck_errcode_t
{MCHECK_SUCCESS,MCHECK_FAILED,
}mcheck_errcode_t;typedef struct mcheck_caller_t
{const char *file;int line;const char *func;
} mcheck_caller_t;typedef struct mcheck_meminfo {void    *address;size_t  size;mcheck_caller_t caller;
}mcheck_meminfo;typedef struct mcheck_mem_list {mcheck_meminfo mem_info;struct mcheck_mem_list *next;
}mcheck_mem_list;int mcheck_mem_list_create(mcheck_mem_list **list);
int mcheck_mem_list_add(mcheck_mem_list *mlist, mcheck_meminfo mem_info);
mcheck_mem_list *mcheck_mem_list_get(mcheck_mem_list *mlist, void *address);
int mcheck_mem_list_delete(mcheck_mem_list *mlist, void *address);
int mcheck_list_size(mcheck_mem_list *mlist);
void mcheck_list_report_leak(mcheck_mem_list *mlist);
void mcheck_list_destory(mcheck_mem_list *mlist);
void init_report_file();#endif

定义如下:

//meminfo.c
#include "meminfo.h"int mcheck_mem_list_create(mcheck_mem_list **mlist){if (*mlist != NULL) {mcheck_list_destory(*mlist);}*mlist = (mcheck_mem_list *)calloc(1, sizeof(mcheck_mem_list));if (*mlist == NULL) {return MCHECK_FAILED;}(*mlist)->next = NULL;return MCHECK_SUCCESS;
}int mcheck_mem_list_add(mcheck_mem_list *mlist, mcheck_meminfo mem_info)
{mcheck_mem_list *node = NULL;mcheck_mem_list_create(&node);node->mem_info = mem_info;if (mlist == NULL){mlist = node;return MCHECK_SUCCESS;}while (mlist->next != NULL){mlist = mlist->next;}mlist->next = node;return MCHECK_SUCCESS;
}mcheck_mem_list *mcheck_mem_list_get(mcheck_mem_list *mlist, void *address){if (mlist == NULL) {return NULL;}mcheck_mem_list *node = mlist;while (mlist != NULL) {if (address == mlist->mem_info.address) {return node;}mlist = mlist->next;}return NULL;
}int mcheck_mem_list_delete(mcheck_mem_list *mlist, void *address){if (mlist == NULL) {return MCHECK_FAILED;}mcheck_mem_list *current = mlist;mcheck_mem_list *prev = mlist;while (current != NULL) {if (current->mem_info.address == address) {if (mlist == current) {if (current->next = NULL) {mlist = NULL;} else {mlist = mlist->next;}} else {prev->next = current->next;}free(current);return MCHECK_SUCCESS;}prev = current;current = current->next;}return MCHECK_FAILED;
}int mcheck_list_size(mcheck_mem_list *mlist){if (mlist == NULL)   {return 0;}int size = 0;while(mlist->next != NULL){size++;mlist = mlist->next;}return size;
}void mcheck_list_destory(mcheck_mem_list *mlist){if (mlist == NULL) {return;}mcheck_mem_list *current = mlist;mcheck_mem_list *prev = mlist;while (prev != NULL) {current = current->next;free(prev);prev = current;}mlist = NULL;
}static char *get_report_filename(){char *fname = getenv("MCHECK_TRACE");if (fname == NULL) {fname = "mcheck.rpt";}return fname;
}void init_report_file(){char *fname = get_report_filename();FILE *fp = NULL;fp = fopen(fname, "w+");fclose(fp);
}static void write_report_file(char *message){char *fname = get_report_filename();FILE *fp = NULL;fp = fopen(fname, "a+");fprintf(fp, "%s\n", message);fflush(stdout);fclose(fp);
}void mcheck_list_report_leak(mcheck_mem_list *mlist){if (mcheck_list_size(mlist) == 0){write_report_file("All memory was free, congratulations, well done!");return;}write_report_file("Memory Not Free:");write_report_file("-----------------------------");write_report_file("\tAddress\t\tSize\t\tCaller");mlist = mlist->next;while (mlist != NULL){mcheck_meminfo mem_info = mlist->mem_info;char message[1024] = {0};sprintf(message, "\t%p\t%lu\tat\t%s:%d[%s]", mem_info.address, mem_info.size, mem_info.caller.file, mem_info.caller.line, mem_info.caller.func);write_report_file(message);mlist = mlist->next;}
}

以上代码逻辑比较简单 ,就不一一解释了。接下来实现一下钩子函数:

#include<stdio.h>
#include<malloc.h>
#include "meminfo.h"static mcheck_mem_list *mlist = NULL;void mcheck_initialize()
{init_report_file();mcheck_mem_list_create(&mlist);
}void mcheck_terminate(){mcheck_list_report_leak(mlist);mcheck_list_destory(mlist);
}void *malloc_hook(size_t size, const char *file, int line)
{void *p = malloc(size);char buff[128] = {0};mcheck_meminfo meminfo = {0};mcheck_caller_t caller = {0};caller.file = file;caller.line = line;caller.func = "malloc";meminfo.address = p;meminfo.size = size;meminfo.caller = caller;mcheck_mem_list_add(mlist, meminfo);return p;
}void *calloc_hook(size_t nmemb, size_t size, const char *file, int line)
{void *p = calloc(nmemb, size);char buff[128] = {0};mcheck_meminfo meminfo = {0};mcheck_caller_t caller = {0};caller.file = file;caller.line = line;caller.func = "calloc";meminfo.address = p;meminfo.size = nmemb*size;meminfo.caller = caller;mcheck_mem_list_add(mlist, meminfo);return p;
}void *realloc_hook(void *ptr, size_t size, const char *file, int line)
{void *p = realloc(ptr, size);char buff[128] = {0};mcheck_meminfo meminfo = {0};mcheck_caller_t caller = {0};caller.file = file;caller.line = line;caller.func = "realloc";meminfo.address = p;meminfo.size = size;meminfo.caller = caller;mcheck_mem_list_add(mlist, meminfo);mcheck_mem_list_delete(mlist, ptr);return p;
}void free_hook(void *p, const char *file, int line){mcheck_mem_list_delete(mlist, p);free(p);
}

如上所示,我们会在每次申请内存之前,收集其调用栈信息,并且封装了mcheck_initializemcheck_terminate两个接口函数,我们只需要在程序里调用这两个函数,就能检测出内存泄露的问题。

类似与下面这种:

#include <mcheck.h>int main(void){mcheck_initialize();//your codemcheck_terminate();return 0;
}

下面我们用一个具体的示例演示一下:

#include "mcheck.h"int main(void){mcheck_initialize();void *p1 = malloc(10);void *p2 = malloc(20);free(p1);//free(p2);void *p3 = malloc(30);free(p3);void *p4 = calloc(1, 64);void *p5 = malloc(32);void *p6 = realloc(p5, 128);mcheck_terminate();return 0;

我们注意在第1行包含了头文件,并在第4行和14行分别调用了mcheck的接口,这样的话,就可以检测出这个过程中出现的内存泄露问题,以上代码运行会产生一个名为mcheck.rpt的报告,内容如下:

Memory Not Free:
-----------------------------Address		Size		Caller0x1fde0b0	20	at	mcheck_sample.c:6[malloc]0x1fde300	64	at	mcheck_sample.c:11[calloc]0x1fde390	128	at	mcheck_sample.c:13[realloc]

它告诉我们第6,11,13行分别出现了内存泄露,大小是多少,调用的那么函数申请的内存,还是比较详细的。

但是这种方式也有缺陷。首先就是调用栈只有一层,不能打印出更深层的调用栈,不利于复杂程序的问题排查。其次是如果要使mcheck生效,必须每个.c里都要include该头文件,对于第三方库是没有办法检测到的,因此使用面也是比较有限。


本专栏知识点是通过<零声教育>的系统学习,进行梳理总结写下文章,对C/C++课程感兴趣的读者,可以点击链接,查看详细的服务:C/C++Linux服务器开发/高级架构师

相关文章:

内存泄漏专题(7)hook之宏定义

前面介绍的mtrace也好&#xff0c;bcc也罢&#xff0c;其实都是hook技术的一种实现&#xff0c;但是mtrace本身使用场景上有局限&#xff0c;而bcc环境依赖则十分复杂。因此&#xff0c;这些调试手段只适用于开发环境用来调试&#xff0c;对于生产环境&#xff0c;均不是一个非…...

Python 基础(十八):异常处理

❤️ 博客主页&#xff1a;水滴技术 &#x1f338; 订阅专栏&#xff1a;Python 入门核心技术 &#x1f680; 支持水滴&#xff1a;点赞&#x1f44d; 收藏⭐ 留言&#x1f4ac; 文章目录 一、异常是什么&#xff1f;二、异常处理的基本语法三、捕获特定的异常类型四、finall…...

iTOP-RK3568开发板Docker 安装 Ubuntu 18.04

Docker 下载安装 Ubuntu18.04&#xff0c;输入以下命令&#xff1a; sudo apt update docker pull ubuntu:18.04 切换 Shell 到 Ubuntu 18.04&#xff0c;输入以下命令&#xff1a; docker container run -p 8000:3000 -it ubuntu:18.04 /bin/bash -p 参数&#xff1a;容器的…...

FFmpeg AVFilter的原理(三)- filter是如何被驱动的

首先上官方filter的链接&#xff1a;https://ffmpeg.org/ffmpeg-filters.html 关于filter命令行&#xff1a;FFmpeg-4.0 的filter机制的架构与实现.之一 Filter原理 1、下面是一个avfilter的graph 上图是ffmpeg中doc/examples中filtering_video.c案例的示意图。 特别注意上面蓝…...

ARM day8 key1/2/3led

key_led.h #ifndef _KEY_H_ #define _KEY_H_#include "stm32mp1xx_rcc.h" #include "stm32mp1xx_gpio.h" #include "stm32mp1xx_exti.h" #include "stm32mp1xx_gic.h"//EXTI编号 typedef enum {EXTI0,EXTI1,EXTI2,EXTI3,EXTI4,EXTI5,…...

windows 系统安装sonarqube

SonarQube是一种自动代码审查工具&#xff0c;用于检测代码中的错误&#xff0c;漏洞和代码异味。它可以与您现有的工作流程集成&#xff0c;以便在项目分支和拉取请求之间进行连续的代码检查。 官方网站&#xff1a; https://www.sonarqube.org/ 1. 使用前提条件 运行SonarQ…...

Unity噪声图生成(编辑器扩展)

最近发现项目里很多shader都需要噪声图&#xff0c;&#xff08;shadergraph中有自己的噪声图生成&#xff09;当遇到需要噪声图时去寻找很麻烦&#xff0c;所以从网上查阅资料编写了一个Unity扩展的噪声图生成。 Perlin噪声 Perlin噪声是一种渐变噪声算法&#xff0c;由Ken …...

http-为什么文件上传要转成Base64

# 前言 最近在开发中遇到文件上传采用Base64的方式上传&#xff0c;记得以前刚开始学http上传文件的时候&#xff0c;都是通过content-type为multipart/form-data方式直接上传二进制文件&#xff0c;我们知道都通过网络传输最终只能传输二进制流&#xff0c;所以毫无疑问他们本…...

htmlCSS-----定位

目录 前言 定位 分类和取值 定位的取值 1.相对定位 2.绝对位置 元素居中操作 3.固定定位 前言 今天我们来学习html&CSS中的元素的定位&#xff0c;通过元素的定位我们可以去更好的将盒子放到我们想要的位置&#xff0c;下面就一起来看看吧&#xff01; 定位 定位posi…...

腾讯云大数据型CVM服务器实例D3和D2处理器CPU型号说明

腾讯云服务器CVM大数据型D3和D2处理器型号&#xff0c;大数据型D3云服务器CPU采用2.5GHz Intel Xeon Cascade Lake 处理器&#xff0c;大数据型D2云服务器CPU采用2.4GHz Intel Xeon Skylake 6148 处理器。腾讯云服务器网分享云服务器CVM大数据型CPU型号、处理器主频性能&#x…...

计算机科学cs/电子信息ei面试准备——数学基础/线性代数复习

1. 中值定理 中值定理是反映函数与导数之间联系的重要定理&#xff0c;也是微积分学的理论基础&#xff0c;在许多方面它都有重要的作用&#xff0c;在进行一些公式推导与定理证明中都有很多应用。中值定理是由众多定理共同构建的&#xff0c;其中拉格朗日中值定理是核心&…...

极速查找(2)-算法分析

篇前小言 本篇文章是对查找&#xff08;1&#xff09;的续讲线性索引查找 线性索引查找&#xff08;Linear Index Search&#xff09;是一种基于索引的查找算法。它在数据集合中创建一个索引 结构&#xff0c;然后使用该索引结构来加快对目标元素的查找。 线性索引是一种在数…...

flask路由添加参数

flask路由添加参数 在 Flask 中&#xff0c;可以通过两种方式在路由中添加参数&#xff1a;在路由字符串中直接指定参数&#xff0c;或者通过 request 对象从请求中获取参数。 在路由字符串中指定参数&#xff1a;可以将参数直接包含在路由字符串中。参数可以是字符串、整数、…...

网络安全系统教程+学习路线(自学笔记)

一、什么是网络安全 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“安全运维”则研究防御技术。 无论网络、Web、移动、桌面、云等哪个领域&#xff0c;都有攻与防两面…...

23. 合并 K 个升序链表

题目描述 给你一个链表数组&#xff0c;每个链表都已经按升序排列。 请你将所有链表合并到一个升序链表中&#xff0c;返回合并后的链表。 示例 1&#xff1a; 输入&#xff1a;lists [[1,4,5],[1,3,4],[2,6]] 输出&#xff1a;[1,1,2,3,4,4,5,6] 解释&#xff1a;链表数组…...

Nexus3部署、配置+SpringBoot项目Demo

Docker部署Nexus 搜索Nexus3镜像&#xff1a;[rootlocalhost ~]# docker search nexus 拉取Nexus3镜像&#xff1a;[rootlocalhost ~]# docker pull sonatype/nexus3 启动Nexus3前查看虚拟机端口是否被占用&#xff1a;[rootlocalhost ~]# netstat -nultp 通过Docker Hub查看安…...

linux下用docker安装mysql

1.mysql Docker镜像 docker pull mysql:[版本号 或 latest]例&#xff1a;docker pull mysql:5.7 2.查看拉取的docker镜像 docker images3.设置 Docker 卷 docker volume create mysql-data列出 Docker 已知的所有卷 docker volume ls4.运行一个 MySQL Docker 容器 docke…...

Vue - 可视化用户角色、菜单权限、按钮权限配置(动态获取菜单路由)

GitHub Demo 地址 在线预览 前言 关于动态获取路由已在这里给出方案 Vue - vue-admin-template模板项目改造&#xff1a;动态获取菜单路由 这里是在此基础上添加了系统管理模块&#xff0c;包含用户管理&#xff0c;角色管理&#xff0c;菜单管理&#xff0c;字典管理&#xf…...

hive库操作示例

hive库操作示例 1、常规表 创建数据库 CREATE DATABASE mydatabase;使用数据库 USE mydatabase;创建表 CREATE TABLE mytable (id INT,name STRING,age INT ) ROW FORMAT DELIMITED FIELDS TERMINATED BY , STORED AS TEXTFILE;插入数据 INSERT INTO TABLE mytable VALUE…...

LeetCode第 N 个泰波那契数 (认识动态规划)

认识动态规划 编写代码代码空间优化 链接: 第 N 个泰波那契数 编写代码 class Solution { public:int tribonacci(int n) {if(n 0){return 0;}else{if(n 1 || n 2)return 1;}vector<int> dp(n 1);dp[0] 0;dp[1] 1;dp[2] 1;for(int i 3;i < n;i){dp[i] dp[i-3]…...

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇&#xff0c;在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下&#xff1a; 【Note】&#xff1a;如果你已经完成安装等操作&#xff0c;可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作&#xff0c;重…...

Linux 文件类型,目录与路径,文件与目录管理

文件类型 后面的字符表示文件类型标志 普通文件&#xff1a;-&#xff08;纯文本文件&#xff0c;二进制文件&#xff0c;数据格式文件&#xff09; 如文本文件、图片、程序文件等。 目录文件&#xff1a;d&#xff08;directory&#xff09; 用来存放其他文件或子目录。 设备…...

【Oracle APEX开发小技巧12】

有如下需求&#xff1a; 有一个问题反馈页面&#xff0c;要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据&#xff0c;方便管理员及时处理反馈。 我的方法&#xff1a;直接将逻辑写在SQL中&#xff0c;这样可以直接在页面展示 完整代码&#xff1a; SELECTSF.FE…...

Xshell远程连接Kali(默认 | 私钥)Note版

前言:xshell远程连接&#xff0c;私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...

在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:

在 HarmonyOS 应用开发中&#xff0c;手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力&#xff0c;既支持点击、长按、拖拽等基础单一手势的精细控制&#xff0c;也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档&#xff0c…...

线程与协程

1. 线程与协程 1.1. “函数调用级别”的切换、上下文切换 1. 函数调用级别的切换 “函数调用级别的切换”是指&#xff1a;像函数调用/返回一样轻量地完成任务切换。 举例说明&#xff1a; 当你在程序中写一个函数调用&#xff1a; funcA() 然后 funcA 执行完后返回&…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)

设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile&#xff0c;新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...

AspectJ 在 Android 中的完整使用指南

一、环境配置&#xff08;Gradle 7.0 适配&#xff09; 1. 项目级 build.gradle // 注意&#xff1a;沪江插件已停更&#xff0c;推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...

uniapp手机号一键登录保姆级教程(包含前端和后端)

目录 前置条件创建uniapp项目并关联uniClound云空间开启一键登录模块并开通一键登录服务编写云函数并上传部署获取手机号流程(第一种) 前端直接调用云函数获取手机号&#xff08;第三种&#xff09;后台调用云函数获取手机号 错误码常见问题 前置条件 手机安装有sim卡手机开启…...

NPOI Excel用OLE对象的形式插入文件附件以及插入图片

static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...