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

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

使用脚本统计 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)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Earth Engine】协同Sentinel-1/2使用随机森林回归实现高分辨率相对财富(贫困)制图

目录 1 简介与摘要2 思路3 效果预览4 代码思路5 完整代码6 后记 1 简介与摘要 最近在做一些课题&#xff0c;需要使用Sentinel-1/2进行机器学习制图。 然后想着总结一下相关数据和方法&#xff0c;就花半小时写了个代码。 然后再花半小时写下这篇博客记录一下。 因为基于多次拍…

学校和老师如何制作自己免费的成绩查询系统

在当今数字化的时代&#xff0c;许多学校都采用信息技术来管理和提高工作效率。其中&#xff0c;成绩查询系统是一个非常实用的工具&#xff0c;它可以让老师和学生们快速、方便地查询成绩。那么&#xff0c;学校和老师如何制作自己免费的成绩查询系统呢&#xff1f;本文将为你…

【Amazon 实验①】使用 Amazon CloudFront加速Web内容分发

文章目录 实验架构图1. 准备实验环境2. 创建CloudFront分配、配置动、静态资源分发2.1 创建CloudFront分配&#xff0c;添加S3作为静态资源源站2.2 为CloudFront分配添加动态源站 在本实验——使用CloudFront进行全站加速中&#xff0c;将了解与学习Amazon CloudFront服务&…

Python办公自动化Day1

目录 文章声明⭐⭐⭐让我们开始今天的学习吧&#xff01;xlwt创建Excelxlrd读取Excelxlutils修改Excelxlwt设置样式常见的字体样式单元格宽高内容对齐方式设置单元格边框设置背景颜色样式整合起来的写法 文章声明⭐⭐⭐ 该文章为我&#xff08;有编程语言基础&#xff0c;非编…

RabbitMQ笔记(高级篇)

RabbitMQ笔记_高级篇 问题代码准备1. 新建生产者2. 新建消费者 RabbitMQ 高级特性1. 消息的可靠投递☆1.1 两种模式1.2 测试confirm 确认模式1.3 测试return 退回模式1.4 小结 2. Consumer ACK☆2.1 三种ACK2.2 测试手动ACK2.3 小结2.4 消息可靠性总结 3. 消费端限流测试消费端…

旅游海报图怎么做二维码展示?扫码即可查看图片

现在旅游攻略的海报可以做成二维码印刷在宣传单单页或者分享给用户来了解目的地的实际情况&#xff0c;出行路线、宣传海报等。用户只需要扫描二维码就可以查看内容&#xff0c;更加的方便省劲&#xff0c;那么旅游海报的图片二维码制作的技巧有哪些呢&#xff1f;使用图片二维…

【算法设计与分析】——动态规划算法

&#x1f383;个人专栏&#xff1a; &#x1f42c; 算法设计与分析&#xff1a;算法设计与分析_IT闫的博客-CSDN博客 &#x1f433;Java基础&#xff1a;Java基础_IT闫的博客-CSDN博客 &#x1f40b;c语言&#xff1a;c语言_IT闫的博客-CSDN博客 &#x1f41f;MySQL&#xff1a…

关于“Python”的核心知识点整理大全36

目录 13.4.4 向下移动外星人群并改变移动方向 game_functions.py alien_invasion.py 13.5 射杀外星人 13.5.1 检测子弹与外星人的碰撞 game_functions.py alien_invasion.py 13.5.2 为测试创建大子弹 13.5.3 生成新的外星人群 game_functions.py alien_invasion.py …

【github】github设置项目为私有

点击setting change to private 无脑下一步

为什么c++的开源库那么少?

为什么c的开源库那么少&#xff1f; 在开始前我有一些资料&#xff0c;是我根据自己从业十年经验&#xff0c;熬夜搞了几个通宵&#xff0c;精心整理了一份「c的资料从专业入门到高级教程工具包」&#xff0c;点个关注&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&…

热部署 和 热加载

本文主要讲热部署和热加载的区别、原理&#xff0c;以及常用的热部署的方式实践心得&#xff0c;其中包括HotSwap、Spring-loaded、Spring-boot-devtools、HotCode2和JRebel&#xff0c;诸多方式任你选择&#xff0c;希望能为你的开发进一步提效 1 热部署和热加载 开篇先说下热…

在Linux系统中安装MySQL数据库

目录 一、MySQL简介 二、MySQL安装步骤 1、下载MySQL的YUM仓库文件 2、安装MySQL源 3、解决密钥异常问题 4、安装MySQL服务器 5、开启MySQL服务 6、查看MySQL服务器中root用户的初始密码 7、使用初始密码登录MySQL服务器 8、修改root用户登录MySQL服务器的密码 三、…

使用Java语言判断一个年度是不是闰年

一、 代码说明 引入Scanner函数&#xff0c;将类命名为Judge类&#xff0c;使用try语句和catch语句将整体代码包围起来&#xff0c;使用if语句来判断是否为闰年&#xff0c;输入年份&#xff0c;然后得到相应的结论。 二、代码 import java.util.Scanner; public class Judg…

ElementUI中修改el-table的滚动条样式

注意&#xff1a;本文仅基于webkit引擎浏览器&#xff1b; 如果是火狐浏览器&#xff0c;则是-moz-&#xff1b; 部分webkit引擎浏览器&#xff1a;Google Chrome谷歌浏览器、Safari浏览器、搜狗高速浏览器、QQ浏览器、360极速浏览器等… 当内容超出容器时会出现滚动条&#…

HarmonyOS4.0系统性深入开发01应用模型的构成要素

应用模型的构成要素 应用模型是HarmonyOS为开发者提供的应用程序所需能力的抽象提炼&#xff0c;它提供了应用程序必备的组件和运行机制。有了应用模型&#xff0c;开发者可以基于一套统一的模型进行应用开发&#xff0c;使应用开发更简单、高效。 HarmonyOS应用模型的构成要…

Spring基础-IOC-DI-AOP

第一部分:Spring基础 文章目录 第一部分:Spring基础一、核心概念1.什么是Spring?2.Spring架构3.Spring优势 二、控制反转1.为什么要控制反转?2.组件化方式编程案例(Test01_di)3.采用组件化思维,装配打印机(Test01_di) 三、面向切面编程(AOP)方面编程1.什么是AOP?2.如何实现A…

AI时代Python量化交易实战:ChatGPT引领新时代

文章目录 《AI时代Python量化交易实战&#xff1a;ChatGPT让量化交易插上翅膀》关键点内容简介作者简介购买链接 《AI时代架构师修炼之道&#xff1a;ChatGPT让架构师插上翅膀》关键点内容简介作者简介 赠书活动 《AI时代Python量化交易实战&#xff1a;ChatGPT让量化交易插上翅…

MATLAB - 读取双摆杆上的 IMU 数据

系列文章目录 前言 本示例展示了如何从安装在双摆杆上的两个 IMU 传感器生成惯性测量单元 (IMU) 读数。双摆使用 Simscape Multibody™ 进行建模。有关使用 Simscape Multibody™ 构建简易摆的分步示例&#xff0c;请参阅简易摆建模&#xff08;Simscape Multibody&#xff09…

设计模式(4)--对象行为(2)--命令

1. 意图 将一个请求封装为一个对象&#xff0c;从而使你可用不同的请求对客户进行参数化&#xff1b;对请求排队或记录请 求日志&#xff0c;以及支持可撤销的操作。 2. 四种角色 接收者(Receiver)、抽象命令(Command)、具体命令(Concrete Command)、请求者(Invoker) 3. 优点…

【NI-RIO入门】使用其他文本语言开发CompactRIO

1.FPGA 接口Python API Getting Started — FPGA Interface Python API 19.0.0 documentation 2.FPGA接口C API FPGA 接口 C API 是用于 NI 可重配置 I/O (RIO) 硬件&#xff08;例如 NI CompactRIO、NI Single-Board RIO、NI 以太网 RIO、NI FlexRIO、NI R 系列多功能 RIO 和…