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

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

使用脚本统计 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)
  • 16
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在 C# 中,可以使用 .NET 框架自带的垃圾回收器(Garbage Collector)来自动管理内存。垃圾回收器会自动跟踪并清理不再使用的对象,从而避免内存泄漏的问题。但是,在某些情况下,可能会出现一些难以检测和解决的内存泄漏问题,因此需要采取一些措施来检测和解决内存泄漏。 下面是一些常用的检测内存泄漏的方法: 1. 使用性能分析器(Profiler):Visual Studio 自带性能分析器,可以帮助开发者定位内存泄漏问题。可以在代码执行过程中,记录对象的创建和销毁情况,分析对象的引用关系,从而找出可能存在内存泄漏的问题。 2. 使用代码审查(Code Review):通过仔细阅读代码,查找可能导致内存泄漏的代码段。例如,没有正确释放资源、长时间持有对象的引用、循环引用等。 3. 使用内存分配跟踪工具:使用内存分配跟踪工具来记录和分析对象的创建和销毁情况,以及对象的引用关系。常用的工具有ANTS Memory Profiler、dotMemory、CLR Profiler 等。 4. 使用内存泄漏检测器:可以使用第三方内存泄漏检测器,例如 SciTech .NET Memory Profiler、dotTrace 等。这些工具可以自动检测内存泄漏问题,提供详细的报告和分析结果,方便开发者定位和解决问题。 总之,检测和解决内存泄漏需要开发者掌握一定的技能和经验。如果开发者能够遵循良好的编程习惯,并且使用适当的工具和技术,就可以避免和解决大部分的内存泄漏问题。 ### 回答2: C#是一种高级编程语言,它在内存管理方面具有自动垃圾回收的特性。自动垃圾回收(Garbage Collection,GC)可以帮助我们管理内存资源,释放不再使用的对象,避免内存泄漏的发生。然而,有时候我们仍然需要手动检测内存泄漏以确保性能和资源的有效利用。 以下是一些常用的方法来检测C#中的内存泄漏: 1. 使用性能计数器:使用C#内置的性能计数器(Performance Counter)可以监测应用程序的内存使用情况。通过监测应用程序的内存增长,可以判断是否存在内存泄漏。 2. 内存剖析器(Memory Profiler):使用一些第三方工具(如.NET Memory Profiler、ANTS Memory Profiler等),可以进行内存剖析,分析应用程序的内存使用情况。这些工具可以帮助我们找到内存泄漏的原因,提供可视化的数据和报告。 3. 对象生命周期跟踪:跟踪对象的创建和销毁,以确保对象在不再使用时能够被垃圾回收。可以通过代码调试和日志记录来实现。 4. 监测不正常的内存增长:在运行时,可以通过监测应用程序的内存使用情况来判断是否存在内存泄漏。如果内存使用量不断增长,而又没有明显的原因,可能就发生了内存泄漏。 5. 测试和性能优化:编写各种测试用例和模拟场景,通过大规模的测试和性能优化来发现和解决潜在的内存泄漏问题。 总之,在编写代码和开发应用程序时,我们应该遵循良好的编码习惯和内存管理的原则,避免出现内存泄漏的情况。及时使用合适的工具和方法来检测内存泄漏,并及时修复它们,以确保应用程序的稳定和性能。 ### 回答3: 在C#中,可以通过以下几种方式来检测内存泄漏: 1. 使用垃圾回收器(Garbage Collector):C#的垃圾回收器负责自动释放不再使用的对象和内存空间。通过垃圾回收机制,可以检测是否存在内存泄漏。如果内存中存在无法访问的对象,垃圾回收器会在适当的时候自动释放这些对象占用的内存。 2. 使用性能分析工具:C#提供了一些性能分析工具(如.NET Memory Profiler、ANTS Memory Profiler等),可以帮助我们检测内存泄漏。这些工具可以跟踪应用程序的内存使用情况,并提供详细的内存分析报告。通过分析报告,可以找到内存泄漏的根本原因。 3. 运行时调试:通过在代码中显示地释放不再使用的资源,可以减少内存泄漏的发生。在使用完对象后,及时调用它们的Dispose()方法或者使用using语句,确保资源在不再需要时得到释放。此外,还可以使用标准的调试技术,如断点调试、内存断点等,来检查内存泄漏问题。 4. 手动跟踪对象的引用:在代码中,可以手动追踪对象引用的生命周期。通过记录对象创建和销毁的时间点,可以判断是否有对象未被正确释放。通过监控对象引用的数量和生命周期,可以找出潜在的内存泄漏问题。 需要注意的是,内存泄漏并非只有在代码中显式地保留了对对象的引用才会发生,还可能由于事件处理、线程管理等问题导致。因此,在检测和解决内存泄漏问题时,需要综合考虑以上几种方法,并结合具体的业务场景进行分析和调试。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值