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层方法,回调收集的数据
欢迎各位在评论区进行沟通交流