内存优化之-Koom源码分析 - Native内存泄漏(二)

Native层泄漏

源码分析

1.native内存泄漏监控分析

LeakMonitor.INSTANCE.start()

调用startLoop

@Deprecated("Unfriendly API use start()", ReplaceWith("start"))
  override fun startLoop(clearQueue: Boolean, postAtFront: Boolean, delayMillis: Long) {
    throwIfNotInitialized { return }   //判断是否初始化过
    getLoopHandler().post(Runnable {
      if (mIsStart) {
        MonitorLog.e(TAG, "LeakMonitor already start")
        return@Runnable
      }
      mIsStart = true
      if (!nativeInstallMonitor(monitorConfig.selectedSoList,
          monitorConfig.ignoredSoList, monitorConfig.enableLocalSymbolic)) {    //代码1 调用nativeInstallMonitor
        mIsStart = false
        if (MonitorBuildConfig.DEBUG) {
          throw RuntimeException("LeakMonitor Install Fail")
        } else {
          MonitorLog.e(TAG, "LeakMonitor Install Fail")
          return@Runnable
        }
      }

      nativeSetMonitorThreshold(monitorConfig.monitorThreshold)
      AllocationTagLifecycleCallbacks.register()

      super.startLoop(clearQueue, postAtFront, delayMillis)
    })
  }

代码1调用native方法,我们分析下nativeInstallMonitor做了哪些操作

static bool InstallMonitor(JNIEnv *env, jclass clz, jobjectArray selected_array,
                           jobjectArray ignore_array,
                           jboolean enable_local_symbolic) {
  jclass leak_record;
  FIND_CLASS(leak_record, kLeakRecordFullyName);   //代码2 com/kwai/koom/nativeoom/leakmonitor/LeakRecord
  g_leak_record.global_ref =
      reinterpret_cast<jclass>(env->NewGlobalRef(leak_record));  //代码3
  if (!CheckedClean(env, g_leak_record.global_ref)) {
    return false;
  }
  GET_METHOD_ID(g_leak_record.construct_method, leak_record, "<init>",
                "(JILjava/lang/String;[Lcom/kwai/koom/nativeoom/leakmonitor/"
                "FrameInfo;)V");   //代码4

  jclass frame_info;
  FIND_CLASS(frame_info, kFrameInfoFullyName);   //代码5 com/kwai/koom/nativeoom/leakmonitor/FrameInfo
  g_frame_info.global_ref =
      reinterpret_cast<jclass>(env->NewGlobalRef(frame_info));   //代码6
  if (!CheckedClean(env, g_frame_info.global_ref)) {
    return false;
  }
  GET_METHOD_ID(g_frame_info.construct_method, frame_info, "<init>",
                "(JLjava/lang/String;)V");   //代码7

  g_enable_local_symbolic = enable_local_symbolic;

  auto array_to_vector =
      [](JNIEnv *env, jobjectArray jobject_array) -> std::vector<std::string> {   //代码8 []捕获变量 lambda表达式
    std::vector<std::string> ret;
    int length = env->GetArrayLength(jobject_array);  //判断参数的长度

    if (length <= 0) {
      return ret;
    }

    for (jsize i = 0; i < length; i++) {
      auto str = reinterpret_cast<jstring>(
          env->GetObjectArrayElement(jobject_array, i));   //
      const char *data = env->GetStringUTFChars(str, nullptr);
      ret.emplace_back(data);
      env->ReleaseStringUTFChars(str, data);
    }

    return std::move(ret);
  };

  std::vector<std::string> selected_so = array_to_vector(env, selected_array);   //代码9 hook的so
  std::vector<std::string> ignore_so = array_to_vector(env, ignore_array);    //代码10 忽略的so
  return CheckedClean(
      env, LeakMonitor::GetInstance().Install(&selected_so, &ignore_so));   //代码11
}

代码2 获取java层 com/kwai/koom/nativeoom/leakmonitor/LeakRecord 的jclass对象保存在局部变量leak_record
代码3 创建一个全局的com/kwai/koom/nativeoom/leakmonitor/LeakRecord的jclass对象保存在static 变量ClassInfo g_leak_record.global_ref 中
代码4 获取LeakRecord.kt 的构造函数方法id,保存到static ClassInfo g_leak_record.construct_method变量
代码5 到 代码7,同样是获取到com/kwai/koom/nativeoom/leakmonitor/FrameInfo jclass对象以及FrameInfo.kt的构造函数方法id
代码8 定义了一个方法,把数组jobjectArray的jstring转换成char *
代码9 hook的so集合
代码10忽略的so集合
代码11 调用LeakMonitor Install进行hook操作,下边继续看如何进行hook,以及hook哪些方法

在这里插入代码片
bool LeakMonitor::Install(std::vector<std::string> *selected_list,
                          std::vector<std::string> *ignore_list) {
  KCHECK(!has_install_monitor_);

  // Reinstall can't hook again
  if (has_install_monitor_) {
    return true;
  }

  memory_analyzer_ = std::make_unique<MemoryAnalyzer>();
  if (!memory_analyzer_->IsValid()) {
    ALOGE("memory_analyzer_ NOT Valid");
    return false;
  }

  std::vector<const std::string> register_pattern = {"^/data/.*\\.so$"};   //代码12
  std::vector<const std::string> ignore_pattern = {".*/libkoom-native.so$",
                                                   ".*/libxhook_lib.so$"};   //代码13

  if (ignore_list != nullptr) {   //代码14
    for (std::string &item : *ignore_list) {
      ignore_pattern.push_back(".*/" + item + ".so$");
    }
  }
  if (selected_list != nullptr && !selected_list->empty()) {
    // only hook the so in selected list
    register_pattern.clear();
    for (std::string &item : *selected_list) {
      register_pattern.push_back("^/data/.*/" + item + ".so$");   //代码15
    }
  }
  std::vector<std::pair<const std::string, void *const>> hook_entries = {
      std::make_pair("malloc", reinterpret_cast<void *>(WRAP(malloc))),
      std::make_pair("realloc", reinterpret_cast<void *>(WRAP(realloc))),
      std::make_pair("calloc", reinterpret_cast<void *>(WRAP(calloc))),
      std::make_pair("memalign", reinterpret_cast<void *>(WRAP(memalign))),
      std::make_pair("posix_memalign",
                     reinterpret_cast<void *>(WRAP(posix_memalign))),
      std::make_pair("free", reinterpret_cast<void *>(WRAP(free)))};   //代码16

  if (HookHelper::HookMethods(register_pattern, ignore_pattern, hook_entries)) {   //代码17
    has_install_monitor_ = true;
    return true;
  }

  HookHelper::UnHookMethods();
  live_alloc_records_.Clear();
  memory_analyzer_.reset(nullptr);
  ALOGE("%s Fail", __FUNCTION__);
  return false;
}

代码12 栈中开辟一个集合,默认hook data/ 下目录的所有so
代码13 同样是初始化一个集合,默认忽略的so
代码14 代码15 遍历参数 selected_list (hookso 集合) ignore_list(ignoreso集合),添加到集合
代码16 定义了hook方法的集合以及hook以后调用的方法,比如hook malloc方法,然后会进入mallocMonitor
代码17 调用xhook_register

代码17行最后会调用 HookHelper::HookImpl()

bool HookHelper::HookImpl() {
  pthread_mutex_lock(&DlopenCb::hook_mutex);
  xhook_clear();
  for (auto &pattern : register_pattern_) {
    for (auto &method : methods_) {
      if (xhook_register(pattern.c_str(), method.first.c_str(), method.second,
                         nullptr) != EXIT_SUCCESS) {    //代码18
        ALOGE("xhook_register pattern %s method %s fail", pattern.c_str(),
              method.first.c_str());
        pthread_mutex_unlock(&DlopenCb::hook_mutex);
        return false;
      }
    }
  }

  for (auto &pattern : ignore_pattern_) {
    for (auto &method : methods_) {
      if (xhook_ignore(pattern.c_str(), method.first.c_str()) != EXIT_SUCCESS) {  //代码19
        ALOGE("xhook_ignore pattern %s method %s fail", pattern.c_str(),
              method.first.c_str());
        pthread_mutex_unlock(&DlopenCb::hook_mutex);
        return false;
      }
    }
  }

  int ret = xhook_refresh(0);  //代码20
  pthread_mutex_unlock(&DlopenCb::hook_mutex);
  return ret == 0;
}

代码18 进行hook,参数分别是是so目录的正则表达式,hook的symbol,hook后调用的方法,原方法
代码19 ignore hook so
代码20 刷新hook

hook以后,会做什么处理?继续分析下hook以后的流程

HOOK(void *, malloc, size_t size) {
  auto result = malloc(size);   //代码21
  LeakMonitor::GetInstance().OnMonitor(reinterpret_cast<intptr_t>(result),
                                       size);   //代码22
  CLEAR_MEMORY(result, size);
  return result;
}

HOOK是定义的宏,根据参数生成mallocMonitor,hook malloc 会调用这个方法
代码21 调用系统库的malloc正常分配内存
代码22 收集 address(分配内存的地址) size(分配内存的大小) backtrace(堆栈信息),存储到live_alloc_records_集合

ALWAYS_INLINE void LeakMonitor:: RegisterAlloc(uintptr_t address, size_t size) {
  if (!address || !size) {
    return;
  }

  auto unwind_backtrace = [](uintptr_t *frames, uint32_t *frame_count) {
    *frame_count = StackTrace::FastUnwind(frames, kMaxBacktraceSize);
  };

  thread_local ThreadInfo thread_info;
  auto alloc_record = std::make_shared<AllocRecord>();
  alloc_record->address = CONFUSE(address);
  alloc_record->size = size;
  alloc_record->index = alloc_index_++;
  memcpy(alloc_record->thread_name, thread_info.name, kMaxThreadNameLen);
  unwind_backtrace(alloc_record->backtrace, &(alloc_record->num_backtraces));
  live_alloc_records_.Put(CONFUSE(address), std::move(alloc_record));
}

与malloc一样,freeMonitor会在hook free方法调用

HOOK(void, free, void *ptr) {
  free(ptr);
  if (ptr) {
    LeakMonitor::GetInstance().UnregisterAlloc(
        reinterpret_cast<uintptr_t>(ptr));
  }
}

首先进行内存的free,然后根据地址,删除存储咋has_install_monitor_ 的malloc的记录
realloc calloc memalign posix_memalign 与malloc基本一样,不再赘述

2.native 日志分析

NativeLeakTest.triggerLeak 用来模拟native内存泄漏

extern "C" JNIEXPORT jlong
Java_com_kwai_koom_demo_nativeleak_NativeLeakTest_triggerLeak(
    JNIEnv *env,
    jclass,
    jobject unuse/* this */) {
  auto leak_test = []() {   //代码23
    TestMallocLeak();
    TestCallocLeak();
    TestReallocLeak();
    TestMemalignLeak();
    TestNewLeak();
    TestNewArrayLeak();
    TestContainerLeak();
  };

  for (int i = 0; i < NR_TEST_THREAD; i++) {   //代码24
    std::thread test_thread(leak_test);
    test_thread.detach();
  }

  return TestJavaRefNative();
}

代码23 在TestMallocLeak malloc内存,free一部分
代码24 创建线程,在线程中执行测试方法
返回malloc地址到java

LeakMonitor.INSTANCE.checkLeaks()
用来检测内存泄漏

fun checkLeaks() {
    if (!isInitialized) return
    getLoopHandler().post(Runnable {
      if (!mIsStart) {
        MonitorLog.e(TAG, "Please first start LeakMonitor")
        return@Runnable
      }
      mutableMapOf<String, LeakRecord>()
        .apply { nativeGetLeakAllocs(this) }
        .also { AllocationTagLifecycleCallbacks.bindAllocationTag(it) }
        .also { MonitorLog.i(TAG, "LeakRecordMap size: ${it.size}") }
        .also { monitorConfig.leakListener.onLeak(it.values) }
    })
  }

nativegetLeakAllocs函数调用了GetLeakAllocs
GetLeakAllocs主要把收集的数据进行封装,增加数据的可读性,然后调用java方法,回调给上层

static void GetLeakAllocs(JNIEnv *env, jclass, jobject leak_record_map) {
  ScopedLocalRef<jclass> map_class(env, env->GetObjectClass(leak_record_map));
  jmethodID put_method;
  GET_METHOD_ID(put_method, map_class.get(), "put",
                "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");   //get Map put
  std::vector<std::shared_ptr<AllocRecord>> leak_allocs =
      LeakMonitor::GetInstance().GetLeakAllocs();    //代码26

  for (auto &leak_alloc : leak_allocs) {   //代码27
    if (leak_alloc->num_backtraces <= kNumDropFrame) {   //判断收集的泄漏的地址的堆栈大小 <= 2
      continue;
    }

    leak_alloc->num_backtraces -= kNumDropFrame;    //
    std::vector<std::pair<jlong, std::string>> frames;
    for (int i = 0; i < leak_alloc->num_backtraces; i++) {   //代码28 从大于2开始收集
      uintptr_t offset;
      auto *map_entry = g_memory_map.CalculateRelPc(
          leak_alloc->backtrace[i + kNumDropFrame], &offset);    //代码29

      if (!map_entry) {
        continue;
      }

      if (map_entry->NeedIgnore()) {
        leak_alloc->num_backtraces = i;
        break;
      }

      std::string symbol_info =
          g_enable_local_symbolic
              ? g_memory_map.FormatSymbol(
                    map_entry, leak_alloc->backtrace[i + kNumDropFrame])   //代码30
              : basename(map_entry->name.c_str());
      frames.emplace_back(static_cast<jlong>(offset), symbol_info);
    }

    if (!leak_alloc->num_backtraces || frames.empty()) {
      continue;
    }

    char address[sizeof(uintptr_t) * 2 + 1];
    snprintf(address, sizeof(uintptr_t) * 2 + 1, "%lx",
             CONFUSE(leak_alloc->address));
    ScopedLocalRef<jstring> memory_address(env, env->NewStringUTF(address));
    ScopedLocalRef<jobjectArray> frames_ref(env, BuildFrames(env, frames));
    ScopedLocalRef<jobject> leak_record_ref(
        env, BuildLeakRecord(env, leak_alloc->index, leak_alloc->size,
                             leak_alloc->thread_name, frames_ref.get()));
    ScopedLocalRef<jobject> no_use(
        env,
        env->CallObjectMethod(leak_record_map, put_method, memory_address.get(),
                              leak_record_ref.get()));   //代码32
  }
}

//代码26主要调用 libmemunreachable.so的GetUnreachableMemory方法,获取不可达root的内存地址,与收集的地址进行对比,判断出我们malloc收集的数据,哪些是泄漏的
代码30 调用dladdr 根据地址获取so name 基地址,so库有符号表,离地址最近的函数名,符号表中,离地址最近的函数的地址
然后收集信息,代码32调用java层方法,回调收集的数据

欢迎各位在评论区进行沟通交流

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值