【内存泄漏】编码实现内存泄漏检测功能

news2025/1/23 22:38:25

编码实现内存泄漏检测功能

使用脚本统计 meminfo 判断是否有内存泄漏

  1. 使用 bash 或 python 脚本循环抓取指定进程的 meminfo 保存到 txt 文件;
  2. 使用 python 脚本解析出txt 文件中的 PSS 信息,借助 pyecharts 或其他可视化三方库将数据以折线图可视化;
    优点:操作简单。缺点:没有检测结果返回。
    具体方案 略

C++ 编码统计 meminfo 判断是否有内存泄漏

需要在待测试程序中添加代码。
整体分为三个部分:初始化,记录数据,统计数据;


初始化:
设置保存统计数据的路径,记录内存的次数以及保存折线图的间隔;
记录数据:
2.1 调用 dumpsys meminfo 接口获得内存信息;
2.2 使用异步线程将记录到的信息按照记录的间隔绘图存储(防止程序奔溃没有保存出数据);
2.3 每次保存数据时检查内存数据判断是否有泄漏;
获取内存泄漏的检查结果:
返回是否有内存泄漏的检查结果;

优点:可以控制记录内存泄漏的时机,避免记录大量重复的 meminfo;
缺点:内存数据以kb 返回,可能会遗漏小 size 的泄漏。不能定位泄漏的代码行。

具体方案 略

Malloc Hooks 自定义内存泄漏检测逻辑

Malloc Hooks 允许程序拦截执行期间发生的所有分配/释放调用。 它仅适用于 Android P 及之后的系统。它的流程和Malloc Debug 可以说基本上一样的,只是设置的属性名不一样。
有两种方法可以启用这些 hooks,设置系统属性或环境变量,并运行应用程序/程序。
adb shell setprop libc.debug.hooks.enable 1export LIBC_HOOKS_ENABLE=1

初始化过程和 malloc debug 类似,只是判断的属性不同;在malloc hooks 的初始化函数中将从 libc_malloc_hooks.so 解析出来的函数symbol 都存放到 MallocDispatch;

// malloc_common_dynamic.cpp
static constexpr char kHooksSharedLib[] = "libc_malloc_hooks.so";
static constexpr char kHooksPrefix[] = "hooks";
static constexpr char kHooksPropertyEnable[] = "libc.debug.hooks.enable";
static constexpr char kHooksEnvEnable[] = "LIBC_HOOKS_ENABLE";
...
// Initializes memory allocation framework once per process.
static void MallocInitImpl(libc_globals* globals) {
...
  // Prefer malloc debug since it existed first and is a more complete
  // malloc interceptor than the hooks.
  bool hook_installed = false;
  if (CheckLoadMallocDebug(&options)) {
    hook_installed = InstallHooks(globals, options, kDebugPrefix, kDebugSharedLib);
  } else if (CheckLoadMallocHooks(&options)) {
    hook_installed = InstallHooks(globals, options, kHooksPrefix, kHooksSharedLib);
  }

  if (!hook_installed) {
    if (HeapprofdShouldLoad()) {
      HeapprofdInstallHooksAtInit(globals);
    }
  } else {
    // Record the fact that incompatible hooks are active, to skip any later
    // heapprofd signal handler invocations.
    HeapprofdRememberHookConflict();
  }
}

系统调用 malloc 函数时实际会调用到 hooks_malloc() 中开发者自行实现的逻辑。

void* hooks_malloc(size_t size) {
  if (__malloc_hook != nullptr && __malloc_hook != default_malloc_hook) {
    return __malloc_hook(size, __builtin_return_address(0));
  }
  return g_dispatch->malloc(size);
}

官方示例

    void* new_malloc_hook(size_t bytes, const void* arg) {
      return orig_malloc_hook(bytes, arg);
    }

    auto orig_malloc_hook = __malloc_hook;
    __malloc_hook = new_malloc_hook;

Malloc Hooks 自定义内存泄漏检测逻辑
更新__malloc_hook 和__malloc_free 指向新增函数;

bool hooks_initialize(const MallocDispatch* malloc_dispatch, bool*, const char*) {
  g_dispatch = malloc_dispatch;
  // __malloc_hook = default_malloc_hook;
  __malloc_hook = cus_malloc_hook;
  __realloc_hook = default_realloc_hook;
  // __free_hook = default_free_hook;
  __free_hook = cus_free_hook;
  __memalign_hook = default_memalign_hook;
  return true;
}

在新增函数内实现检测逻辑;


// static int allocated_count = 0;
static void* cus_malloc_hook(size_t size, const void* ) {
  auto malloced_addr = g_dispatch->malloc(size);
  // printf 可能会产生循环调用!
  // printf("[malloc hooks] malloc %p size %zu at %p\n", malloced_addr, size, malloc_return_addr);
  error_log("[malloc hooks] malloc %p size %zu\n", malloced_addr, size);
  // allocated_count++;
  return malloced_addr;
}

static void cus_free_hook(void* pointer, const void* ) {
  // printf 可能会产生循环调用!
  // printf("[malloc hooks] free %p, at %p\n", pointer, free_addr);
  error_log("[malloc hooks] free %p\n", pointer);
  // allocated_count--;
  g_dispatch->free(pointer);
}

在 hooks_finalize 打印统计信息或者 dump 信息到文件。
在这里直接打印的话可能计数和预期不同,因为 malloc hooks 记录了整个程序执行过程中的申请和释放,是多于测试程序里面申请和释放的次数的。

void hooks_finalize() {
  // error_log("allocated_count %d\n", allocated_count);
}

避坑

  1. apex/com.android.runtime/lib64/libc_malloc_hooks.so没有权限替换,放到 /data/local/tmp/ 路径下也没有权限读取。临时调试建议指定路径 kHooksSharedLib[] = “/system/lib64/libc_malloc_hooks.so“;
  2. 不要使用 printf 打印信息,会出现malloc/free 的循环调用,程序崩溃;

通过 dlsym 库函数对 malloc/free 进行 hook

方案:

  1. 通过 dlsym 拿到系统 malloc/free 函数,起个别名;
  2. 使用 atexit 注册退出时要调用的函数;
  3. 用自定义的 malloc/free 函数把 libc 的 malloc/free 包装一层;
  4. 程序中调用 malloc/free 时实际先调用自定义的函数,之后调用实际的 malloc/free。
    在增加的函数中实现:
  5. 每次调用 malloc 和 free 时打印地址;
  6. 使用一个变量记录已经申请但没有释放的数量;
  7. 程序退出时打印没有 free 的数量;
    示例:
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef void* (*malloc_func_type)(size_t size);
typedef void (*free_func_type)(void* p);
malloc_func_type malloc_origin_ = NULL;
free_func_type free_origin_ = NULL;

int enable_malloc_hook = 1;
int enable_free_hook = 1;
static size_t allocate_cnt = 0;
void* malloc(size_t size) {
  void* malloced_addr = NULL;
  if (enable_malloc_hook) { // 避免 printf 循环调用
    enable_malloc_hook = 0;
    allocate_cnt++;
    malloced_addr = malloc_origin_(size);
    printf("malloc %p size %zu\n", malloced_addr, size);
    enable_malloc_hook = 1;
  }
  return malloced_addr;
}

void free(void* p) {
  if (enable_free_hook) {
    enable_free_hook = 0;
    printf("free [%p]\n", p);
    enable_free_hook = 1;
  }
  allocate_cnt--;
  free_origin_(p);
}

void finish() { printf("allocate_cnt %zu\n", allocate_cnt); }

void f(void);
void f(void) {
  // printf("[memtest] function f\n");
  int* x = (int*)malloc(10 * sizeof(int));
  x[0] = 0;
  int* y = (int*)malloc(5 * sizeof(int));
  y[0] = 0;
  free(x);
}

int main(void) {
  // 获取系统默认的 malloc 和 free 函数
  if (malloc_origin_ == NULL) {
    malloc_origin_ =
        reinterpret_cast<malloc_func_type>(dlsym(RTLD_NEXT, "malloc"));
  }

  if (free_origin_ == NULL) {
    free_origin_ = reinterpret_cast<free_func_type>(dlsym(RTLD_NEXT, "free"));
  }

  // printf("[memtest] hello main\n");
  f();

  // 注册程序退出时调用的函数
  atexit(finish);
  return 0;
}

输出

$ ./memtest_dlsym
malloc 0x56127a995260 size 40
malloc 0x56127a995290 size 20
free [0x56127a995260]
allocate_cnt 1

总结

本文介绍了一些自行编码实现内存泄漏检测的工具的方式,但还有很多其他可行的方案本文没有一一涵盖,比如使用宏定义替换的方式,有兴趣的读者可以多探索一下。

参考链接

  1. Malloc Hooks (googlesource.com)

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

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

相关文章

探秘 Python 协程:解锁异步编程的超能力

概要 在 Python 的异步编程领域&#xff0c;协程&#xff08;Coroutines&#xff09;扮演了核心角色。协程提供了一种轻量级的并发编程方式&#xff0c;使得开发者能够有效地处理 I/O 密集型任务和高并发需求。本文旨在深入探讨 Python 中的协程概念&#xff0c;其底层机制及实…

XPM_CDC_SINGLE(UG974)

Parameterized Macro: Single-bit Synchronizer&#xff08;参数化宏&#xff1a;单比特同步器&#xff09; MACRO_GROUP: XPMMACRO_SUBGROUP: XPM_CDCFamilies: UltraScale, UltraScale 1、 Introduction&#xff08;介绍&#xff09; 此宏将一个一位信号从源时钟域同步到目…

MyBatis增删改查基础及其xml文件

目录 一.增删改查基础 1.增 增Insert 获取自增ID 对insert参数进行重命名 2.删 3.改 4.查 1)对MySQL查询结果进行重命名 从MySQL层面--as 从mybatis层面--Results注解 复用Results的定义 2)配置自动转换驼峰命名&#xff08;推荐&#xff09; 二.Mybatis xml配置…

80x86汇编—指令系统

顺序是按照我们老师教的顺序&#xff0c;仅仅作为复习笔记。 汇编入门真的简单&#xff0c;深入难&#xff0c;毕竟学过计组CPU都只寄组的难处&#xff0c;指令系统不在话下了。 MOV 下图说明了一个MOV指令能够从哪里传到哪里&#xff0c;总结成一句话就是&#xff1a;立即数不…

【Linux】进程周边007之进程控制

&#x1f440;樊梓慕&#xff1a;个人主页 &#x1f3a5;个人专栏&#xff1a;《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C》《Linux》 &#x1f31d;每一个不曾起舞的日子&#xff0c;都是对生命的辜负 目录 前言 1.进程创建 2.进程终止 2.…

【MySQL】事务、事务隔离级别、死锁

文章目录 1. 事务1.1 事务的属性 ACID1.2 创建事务1.3 autocommit 2. 并发和锁定2.1 并发问题 3. 事务隔离级别3.1 读未提交3.2 读已提交3.3 可重复读&#xff1a;MySQL的默认事务隔离级别3.4 序列化 4. 死锁 1. 事务 事务&#xff1a;单个工作单元的一组SQL语句。事务中的所有…

【NI-RIO入门】理解Windows、Real Time与FPGA之间数据通信的原理

于NI kb摘录 1.概述 对于NI RIO系列设备&#xff08;CompactRIO、sbRIO、myRIO等&#xff09;进行编程时&#xff0c;需要注意有三个不同的组件。 人机界面 (HMI) 。有时称为“主机”&#xff0c;为用户提供图形用户界面&#xff08;GUI&#xff09;&#xff0c;用于监控系统…

基于SpringBoot简洁优雅的个人博客系统

源代码下载地址&#xff1a; 点击这里下载 项目备注 1、该资源内项目代码都经过测试运行成功&#xff0c;功能ok的情况下才上传的&#xff0c;请放心下载使用&#xff01; 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或…

React学习计划-React16--React基础(四)生命周期和diffing算法,key的作用

1. 生命周期 1. 声命周期的三个阶段&#xff08;旧&#xff09; 初始化阶段&#xff1a;由ReactDOM.render()触发—初次渲染 1. constructor() 2. componentWillMount() 3. render() 4. componentDidMount() > 常用一般在这个钩子中做一些初始化的事情&#xff0c;例如&am…

Javascript知识点锦集

【版权声明】未经博主同意&#xff0c;谢绝转载&#xff01;&#xff08;请尊重原创&#xff0c;博主保留追究权&#xff09; https://blog.csdn.net/m0_69908381/article/details/135165704 出自【进步*于辰的博客】 文章目录 1、其他知识点链接7、关于 false8、关于 null 与 …

【设计模式】命令模式

其他系列文章导航 Java基础合集数据结构与算法合集 设计模式合集 多线程合集 分布式合集 ES合集 文章目录 其他系列文章导航 文章目录 前言 一、什么是命令模式&#xff1f; 二、命令模式的优点和应用场景 三、命令模式的要素和实现 3.1 命令 3.2 具体命令 3.3 接受者 …

抖店只能用官方电子面单?2024抖店玩法解读,附面单使用教程

我是王路飞。 正在做抖店的商家&#xff0c;应该都发现一件事情了&#xff0c;那就是现在的抖店好像不让拍单了&#xff0c;只能使用抖音的电子面单&#xff0c;打单发货。 说实话&#xff0c;这种情况已经出现过太多次了&#xff0c;导致很多商家不以为然。 我曾经也说过&a…

Midjourney v6 正式发布,AI创新工坊同步更新

Midjourney v6 开发团队将从2023 年 12 月 21 日今晚开始&#xff0c;在寒假期间让社区测试Midjourney v6模型的 alpha 版本。 要打开它&#xff0c;V6请从提示下方的下拉菜单中选择/settings或--v 6在提示后键入。 Midjourney v6 基本型号有哪些新功能&#xff1f; 更准确的…

受控代码生成-CCS 23

Large Language Models for Code: Security Hardening and Adversarial Testing 1.Introduction2.Background3.受控代码生成4.SVEN4.1.Inference4.2.Training4.2.1.训练样本4.2.2.loss函数 4.3.数据集构建 5.使用场景5.1.安全性增强5.2.对抗测试 6.实验6.1.实验设置6.2.Main Ex…

【数据结构和算法】---栈和队列的互相实现

目录 一、用栈实现队列1.1初始化队列1.2模拟入队列1.3模拟出队列1.4取模拟的队列头元素1.5判断队列是否为空 二、用队列实现栈2.1初始化栈2.2模拟出栈2.3模拟入栈2.4取模拟的栈顶元素2.5判读栈是否为空 一、用栈实现队列 具体题目可以参考LeetCode232. 用栈实现队列 首先要想到…

目标检测应用场景—数据集【NO.24】行人车辆检测数据集2

写在前面&#xff1a;数据集对应应用场景&#xff0c;不同的应用场景有不同的检测难点以及对应改进方法&#xff0c;本系列整理汇总领域内的数据集&#xff0c;方便大家下载数据集&#xff0c;若无法下载可关注后私信领取。关注免费领取整理好的数据集资料&#xff01;今天分享…

Neural-Chat-7B:Intel如何塑造下一代对话AI

引言 在人工智能的领域中&#xff0c;对话模型的进步正改变我们与技术的互动方式。Intel近期推出的Neural-Chat-7B模型&#xff0c;不仅标志着对话AI的一大步前进&#xff0c;更预示着未来人机交流的新方向。本文将深入探索这一模型的核心特性、训练过程及其在实际应用中的潜力…

【内存泄漏】Malloc Debug 和 libmenunreacbale 原理介绍

内存泄漏检测原理介绍 malloc debug 原理介绍 分为初始化和内存泄漏检测两个阶段介绍。 初始化阶段 整体流程如下图 libc 初始化时通过 __libc_init_malloc 函数调用 MallocInitImpl 来初始化 memory allocation framework。 // malloc_common_dynamic.cpp static const…

2024应届大学生,为云计算高薪岗位做好准备了吗?

云计算正处于快速发展阶段&#xff0c;对于企业和个人来说&#xff0c;云计算提供了方便、灵活和智能的解决方案&#xff0c;对各行各业都有着重要的影响和推动作用。 随着云计算新市场、新业务、新应用的不断出现&#xff0c;人力需求迅猛。国家相继出台一系列政策大力扶持云…

结构型模式 | 适配器模式

一、适配器模式 1、原理 适配器模式&#xff08;Adapter&#xff09;&#xff0c;将一个类的接口转换成客户希望的另外一个接口&#xff0c;使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。适配器模式主要分为三类&#xff1a;类适配器模式、对象适配器模式、接口…