从KOOM看native内存泄漏监控

本文讨论了Java中的垃圾回收机制如何影响内存泄漏检测,以及在Native层使用libmemunreachable进行内存泄漏监控的方法,包括hookmalloc/free、内存分析和使用koom-native-leak模块的示例。
摘要由CSDN通过智能技术生成

对于Java而言,语言本身有自动垃圾回收机制,所以导致在内存泄漏检测上需要确定对象应该被回收的特征,以便进行检查,如LeakCanary,Matrix中监听生命周期,KOOM中检查finished或者destroyed的Activity,究其根本,主要还是要对对象应该被回收的状态进行定义。不同于Java,在Native层主要使用C或者C++来进行编码,语言本身并没有垃圾回收机制,对于对象的回收依赖于开发者手动释放空间,这也就意味着在native层进行泄漏检测相对而言更加困难,那么Native层内存泄漏就没办法监控了吗?

当然有的,在Android N(7.0)以后系统新增了libmemunreachable模块,该模块是一个零开销的本地内存泄漏检测器,其使用不精确的标记-清楚垃圾回收器遍历所有Native内存,并将不可访问的内存块报告为泄漏内存区域。基于libmemunreachable我们可以设计一套机制用于监控Native层内存泄漏问题,主要原理如下:

  1. hook malloc/free 等内存分配器方法,用于记录 Native 内存分配元数据「大小、堆栈、地址等」
  2. 周期性的使用 mark-and-sweep 分析整个进程 Native Heap,获取不可达的内存块信息「地址、大小」
  3. 利用不可达的内存块的地址、大小等从我们记录的元数据中获取其分配堆栈,产出泄漏数据「不可达内存块地址、大小、分配堆栈等」

Native层内存泄漏对象 = 不可达的内存块信息

接下来我们来看下koom-native-leak模块的实现,与koom-java-leak模块的实现类似,该模块也是通过XXXMonitor对象来启动监控的,我们可以通过LeakMonitor.INSTANCE.start();启动监控,通过MonitorManager.addMonitorConfig(config)为其添加通用配置(配置参数可以查看github上的说明)。

LeakMonitor.INSTANCE.start()

image-20230902175202437

通过start代码可以看到,最终真正执行的函数是nativeInstallMonitor和nativeGetLeakAllocs。

nativeInstallMonitor

image-20230903113706778

如上代码所示,install阶段的主要功能是hook malloc/free 等内存分配器方法。

nativeGetLeakAllocs

image-20230903114408034

memory_analyzer_->CollectUnreachableMem
namespace kwai {
namespace leak_monitor {
static const char *kLibMemUnreachableName = "libmemunreachable.so";
  
// GetUnreachableMemory函数名在libmemunreachable.so中在不同Android版本上的标记
// Just need the symbol in arm64-v8a so
// API level > Android O
static const char *kGetUnreachableMemoryStringSymbolAboveO =
    "_ZN7android26GetUnreachableMemoryStringEbm";
// API level <= Android O
static const char *kGetUnreachableMemoryStringSymbolBelowO =
    "_Z26GetUnreachableMemoryStringbm";

MemoryAnalyzer::MemoryAnalyzer()
    : get_unreachable_fn_(nullptr), handle_(nullptr) {
  auto handle = kwai::linker::DlFcn::dlopen(kLibMemUnreachableName, RTLD_NOW);
  if (!handle) {
    ALOGE("dlopen %s error: %s", kLibMemUnreachableName, dlerror());
    return;
  }

  // 通过kwai-linker去调用libmemunreachable.so的GetUnreachableMemory函数(分Android版本)
  if (android_get_device_api_level() > __ANDROID_API_O__) {
    get_unreachable_fn_ =
        reinterpret_cast<GetUnreachableFn>(kwai::linker::DlFcn::dlsym(
            handle, kGetUnreachableMemoryStringSymbolAboveO));
  } else {
    get_unreachable_fn_ =
        reinterpret_cast<GetUnreachableFn>(kwai::linker::DlFcn::dlsym(
            handle, kGetUnreachableMemoryStringSymbolBelowO));
  }
}

MemoryAnalyzer::~MemoryAnalyzer() {
  if (handle_) {
    kwai::linker::DlFcn::dlclose(handle_);
  }
}

bool MemoryAnalyzer::IsValid() { return get_unreachable_fn_ != nullptr; }

std::vector<std::pair<uintptr_t, size_t>>
MemoryAnalyzer::CollectUnreachableMem() {
  std::vector<std::pair<uintptr_t, size_t>> unreachable_mem;

  if (!IsValid()) {
    ALOGE("MemoryAnalyzer NOT valid");
    return std::move(unreachable_mem);
  }

  // libmemunreachable NOT work in release apk because it using ptrace
  if (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) == -1) {
    ALOGE("Set process dumpable Fail");
    return std::move(unreachable_mem);
  }

  // Note: time consuming
  std::string unreachable_memory = get_unreachable_fn_(false, 1024);

  // Unset "dumpable" for security
  prctl(PR_SET_DUMPABLE, 0, 0, 0, 0);

  // 正则表达式匹配输出内容
  std::regex filter_regex("[0-9]+ bytes unreachable at [A-Za-z0-9]+");
  std::sregex_iterator unreachable_begin(
      unreachable_memory.begin(), unreachable_memory.end(), filter_regex);
  std::sregex_iterator unreachable_end;
  for (; unreachable_begin != unreachable_end; ++unreachable_begin) {
    const auto& line = unreachable_begin->str();
    auto address =
        std::stoul(line.substr(line.find_last_of(' ') + 1,
                               line.length() - line.find_last_of(' ') - 1),
                   0, 16);
    auto size = std::stoul(line.substr(0, line.find_first_of(' ')));
    unreachable_mem.emplace_back(address, size);
  }
  return std::move(unreachable_mem);
}
}  // namespace leak_monitor
}  // namespace kwai

通过libmemunreachable.so的GetUnreachableMemory函数获取native内存情况。

Is_leak
auto is_leak = [&](decltype(unreachable_allocs)::value_type &unreachable,
                   std::shared_ptr<AllocRecord> &live) -> bool {
  auto live_start = CONFUSE(live->address);
  auto live_end = live_start + live->size;
  auto unreachable_start = unreachable.first;
  auto unreachable_end = unreachable_start + unreachable.second;
  return live_start == unreachable_start ||
         live_start >= unreachable_start && live_end <= unreachable_end;
};

参考链接

libmemunreachable

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值