1. 程式人生 > >Android NDK 記憶體洩露檢測

Android NDK 記憶體洩露檢測

前言

最近寫C++程式碼,老是擔心程式碼存在記憶體洩露,膽戰心驚的,Andorid中Java層程式碼記憶體洩露可以藉助leakcanary進行檢測;找了一番,找到了PC上C++上的記憶體洩露檢測庫LeakTracer,於是再找了下,找到了Android上的移植版。

首先建立一個專案,在根目錄下建立thirdparty目錄,進入該目錄,clone相關庫程式碼

1git clone git@github.com:lizhangqu/LeakTracer.git

在專案src/main/cpp下建立CMakeLists.txt,內容如下

12345678910111213141516171819202122232425262728293031project(Test)cmake_minimum_required (VERSION 3.6)include_directories( ${PROJECT_SOURCE_DIR}/include/ ${PROJECT_SOURCE_DIR}/../../../../thirdparty/LeakTracer/libleaktracer/include/ )set(LEAKTRACER_SOURCES ${PROJECT_SOURCE_DIR}/../../../../thirdparty/LeakTracer/libleaktracer/src/AllocationHandlers.cpp #${PROJECT_SOURCE_DIR}
/../../../../thirdparty/LeakTracer/libleaktracer/src/LeakTracerC.c #檢測c程式碼時開啟此註釋,否則不要開啟 ${PROJECT_SOURCE_DIR}/../../../../thirdparty/LeakTracer/libleaktracer/src/MemoryTrace.cpp )add_library(leaktracer STATIC ${LEAKTRACER_SOURCES})set(TEST_FILES ${CMAKE_SOURCE_DIR}/native.cpp)add_library( test-jni SHARED ${TEST_FILES}
)target_link_libraries( test-jni leaktracer log)

建立src/main/cpp/include/native.h和src/main/cpp/native.cpp檔案

native.h

1234567891011121314151617181920212223242526272829303132333435363738//// Created by 李樟取 on 2017/6/4.//#ifndef TEST_H#define TEST_H#include "jni.h"#ifndef NELEM# define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))#endif#ifndef CLASSNAME#define CLASSNAME "io/github/lizhangqu/test/Test"#endif#ifdef ANDROID#include <android/log.h>#define TAG "Test"#define ALOGE(fmt, ...) __android_log_print(ANDROID_LOG_ERROR, TAG, fmt, ##__VA_ARGS__)#define ALOGI(fmt, ...) __android_log_print(ANDROID_LOG_INFO, TAG, fmt, ##__VA_ARGS__)#define ALOGD(fmt, ...) __android_log_print(ANDROID_LOG_DEBUG, TAG, fmt, ##__VA_ARGS__)#define ALOGW(fmt, ...) __android_log_print(ANDROID_LOG_WARN, TAG, fmt, ##__VA_ARGS__)#else#define ALOGE printf#define ALOGI printf#define ALOGD printf#define ALOGW printf#endif#endif //TEST_H

native.cpp

1234567891011121314151617181920212223242526272829303132333435363738394041#include "native.h"#include "MemoryTrace.hpp"#include <fstream>void test(JNIEnv *env, jobject thiz) {}static const JNINativeMethod sMethods[] = { { const_cast<char *>("test"), const_cast<char *>("()V"), reinterpret_cast<void *>(test) },};int registerNativeMethods(JNIEnv *env, const char *className, const JNINativeMethod *methods, const int numMethods) { jclass clazz = env->FindClass(className); if (!clazz) { ALOGE("Native registration unable to find class '%s'\n", className); return JNI_FALSE; } if (env->RegisterNatives(clazz, methods, numMethods) != 0) { ALOGE("RegisterNatives failed for '%s'\n", className); env->DeleteLocalRef(clazz); return JNI_FALSE; } env->DeleteLocalRef(clazz); return JNI_TRUE;}jint JNI_OnLoad(JavaVM *vm, void *reserved) { JNIEnv *env; if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) { return -1; } registerNativeMethods(env, CLASSNAME, sMethods, NELEM(sMethods)); return JNI_VERSION_1_6;}

編寫test函式,簡單進行內測洩露檢測

1234567891011121314151617class MemoryTest {};void test(JNIEnv *env, jobject thiz) { leaktracer::MemoryTrace::GetInstance().startMonitoringAllThreads(); MemoryTest *memoryTest = new MemoryTest; leaktracer::MemoryTrace::GetInstance().stopAllMonitoring(); std::ofstream out; out.open("/sdcard/leaks.out", std::ios_base::out); if (out.is_open()) { leaktracer::MemoryTrace::GetInstance().writeLeaks(out); } else { ALOGE("Failed to write to \"leaks.out\"\n"); }}

執行程式後呼叫test函式,將/sdcard/leaks.out pull到專案根目錄

1adb pull /sdcard/leaks.out

其內容類似如下內容

12# LeakTracer report diff_utc_mono=1496511718.682943leak, time=135120.534718, stack=0x36fd6 0x35a90 0x359a4 0x32fea 0xc952d3d0, size=1, data=�

藉助thirdparty/LeakTracer/helper/leak-analyze-addr2line工具還原內測洩露堆疊。

進入專案根目錄,執行leak-analyze-addr2line

1./thirdparty/LeakTracer/helpers/leak-analyze-addr2line ./library/build/intermediates/cmake/debug/obj/armeabi/libtest-jni.so ./leaks.out

在mac上,會出現一個錯誤,原因是leak-analyze-addr2line中用到了addr2line工具,而mac上如果沒有此工具,就會報錯,錯誤如下:

error.png

解決方法很簡單,將ndk目錄中的arm-linux-androideabi-addr2line拷到./thirdparty/LeakTracer/helpers/下,並重命名為addr2line,然後將./thirdparty/LeakTracer/helpers/加到環境變數中,如下程式碼

12cp $ANDROID_NDK_HOME/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin/arm-linux-androideabi-addr2line ./thirdparty/LeakTracer/helpers/addr2lineexport PATH=$PATH:`pwd`/thirdparty/LeakTracer/helpers

$ANDROID_NDK_HOME表示ndk的根目錄。

之後再次呼叫命令

1./thirdparty/LeakTracer/helpers/leak-analyze-addr2line ./library/build/intermediates/cmake/debug/obj/armeabi/libtest-jni.so ./leaks.out

這時候輸出如下

out_leak.png

native.cpp:15行出現洩露,找到15行對應的程式碼,即如下程式碼出現洩露

1MemoryTest *memoryTest = new MemoryTest;

加入delete程式碼,再跑一次

12MemoryTest *memoryTest = new MemoryTest;delete memoryTest;

輸出如下

out_no_leak.png

發現之前的洩露不見了

最後,值得注意的是,要想使用LeakTracer需要保留so足夠多的debug資訊,否則可能不能正常檢測。