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

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

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

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

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

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

【内存泄漏】编码实现内存泄漏检测功能_第1张图片
初始化:
设置保存统计数据的路径,记录内存的次数以及保存折线图的间隔;
记录数据:
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 
#include 
#include 
#include 

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(dlsym(RTLD_NEXT, "malloc"));
  }

  if (free_origin_ == NULL) {
    free_origin_ = reinterpret_cast(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)

你可能感兴趣的:(内存泄漏,malloc,hooks,meminfo)