基於Android Ndk/Jni的內存泄漏檢測

之前分析過在Android Native中分析內存泄漏的方法:Android Native內存泄露檢測(針對Android7.0)但是很遺憾這個方法並不適用於Ndk和Jni,因此我們需要爲Ndk和Jni尋找一種合適的方法,他就是LeakTracer
這個工具並沒有之前libc那麼的智能,他需要我們手動的在懷疑的代碼段中加入檢測代碼,原理是將malloc和free函數替換爲LeakTracer中帶有插樁性質的函數替代,然後在檢測前和檢測後比較是否內存有成對的申請和釋放。適用於C和C++的內存泄漏檢測

上代碼:
1. LeakTracer源碼獲取:

https://github.com/zhuyong006/LeakTracer.git

2. 將LeakTracer源碼放入Android Studio中cpp的同級目錄如下 :
在這裏插入圖片描述
3. 修改CMakeLists.txt文件,如下:

# Sets the minimum version of CMake required to build the native
# library. You should either keep the default value or only pass a
# value of 3.4.0 or lower.

cmake_minimum_required(VERSION 3.4.1)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds it for you.
# Gradle automatically packages shared libraries with your APK.

include_directories(
  src/main/cpp/leak_tracer/include/
)


add_library( # Sets the name of the library.
             leak_tracer

             # Sets the library as a shared library.
             STATIC

             # Provides a relative path to your source file(s).
             # Associated headers in the same location as their source
             # file are automatically included.
             src/main/cpp/leak_tracer/src/AllocationHandlers.cpp
             src/main/cpp/leak_tracer/src/MemoryTrace.cpp)

add_library( # Sets the name of the library.
             native-lib

             # Sets the library as a shared library.
             SHARED

             # Provides a relative path to your source file(s).
             # Associated headers in the same location as their source
             # file are automatically included.
             src/main/cpp/native-lib.cpp )

# Searches for a specified prebuilt library and stores the path as a
# variable. Because system libraries are included in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library( # Sets the name of the path variable.
              log-lib

              # Specifies the name of the NDK library that
              # you want CMake to locate.
              log )

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in the
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
                       native-lib
                       leak_tracer
                       # Links the target library to the log library
                       # included in the NDK.
                       ${log-lib} )

這一步的目的是爲了將LeakTracer和我們的Native測試代碼打到一個動態庫中native-lib.so

4. 構建測試程序測試下

  • Java側代碼
package com.sunmi.mmleak;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
    private int index = 0;
    private TextView tv = null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Example of a call to a native method
        tv = (TextView) findViewById(R.id.sample_text);

//        new Thread(new Runnable() {
//            @Override
//            public void run() {
//                do {
//                    NativeMmLeak();
//                    index++;
//                    Log.e("Jon","Leak Mem");
//                    try {
//                        Thread.sleep(500);
//                    } catch (InterruptedException io) {
//
//                    }
//                }while (true);
//            }
//        }).start();
        NativeMmLeak();

    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String NativeMmLeak();

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }
}
  • Native側代碼
#include <jni.h>
#include <string>
#include "leak_tracer/include/MemoryTrace.hpp"
#include <fstream>

#ifdef ANDROID

#include <android/log.h>

#define TAG "Jon"

#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


char *mm = NULL;
extern "C"
jstring
Java_com_sunmi_mmleak_MainActivity_NativeMmLeak(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    leaktracer::MemoryTrace::GetInstance().startMonitoringAllThreads();
    mm = (char *)malloc(4096);
    memset(mm,0x0,4096);
    leaktracer::MemoryTrace::GetInstance().stopAllMonitoring();

    std::ofstream out;
    out.open("/data/leak.out", std::ios_base::out);
    if (out.is_open()) {
        leaktracer::MemoryTrace::GetInstance().writeLeaks(out);
    } else {
        ALOGE("Failed to write to \"leaks.out\"\n");
    }

    return env->NewStringUTF(hello.c_str());
}

這裏說明下:

leaktracer::MemoryTrace::GetInstance().startMonitoringAllThreads();

啓動內存泄漏檢測

leaktracer::MemoryTrace::GetInstance().stopAllMonitoring();

停止內存泄漏檢測

leaktracer::MemoryTrace::GetInstance().writeLeaks(out);

將內存泄漏檢測的結果寫入文件,當前demo中我是寫入"/data/leak.out"中的

5. 讓我們開始測試吧

  • 首先我們先在data目錄下創建一個空文件leak.out
msm8953_64:/data # touch leak.out
touch leak.out
msm8953_64:/data #
  • 打開apk,蒐集內存泄漏
    在apk啓動後,就會蒐集到4K的內存泄漏,然後將堆棧信息寫入到/data/leak.out,我們看看都蒐集到了什麼呢

.# LeakTracer report diff_utc_mono=1658677.590256 leak,
time=9441.017442, stack=0x38282 0x36d20 0x36f72 0x33f8c, size=4096,
data=…

stack就是當前內存泄漏的現場堆棧打印,size就是當前內存泄漏的大小
那麼下一步我們就需要還原堆棧了

6. 還原堆棧
LeakTracer有個helpers文件目錄:
在這裏插入圖片描述
我們有2種方法都可以還原堆棧現場:leak-analyze-addr2lineleak-analyze-gdb
推薦用leak-analyze-addr2line

root@Jon:/home/jon# leak-analyze- libnative-lib.so leak.out 
leak-analyze-addr2line  leak-analyze-gdb        
root@Jon:/home/jon# leak-analyze-addr2line libnative-lib.so leak.out 
Processing "leak.out" log for "libnative-lib.so"
Matching addresses to "libnative-lib.so"
found 1 leak(s)
4096 bytes lost in 1 blocks (one of them allocated at 5687.731067), from following call stack:
        D:\Project\Android-Samples\MmLeak\app\.externalNativeBuild\cmake\debug\armeabi-v7a/D:/adt-bundle-windows-x86/android-ndk-r18b/sources/cxx-stl/llvm-libc++/include/streambuf:405
        D:\Project\Android-Samples\MmLeak\app\.externalNativeBuild\cmake\debug\armeabi-v7a/../../../../src/main/cpp/leak_tracer/include/MapMemoryInfo.hpp:187
        D:\Project\Android-Samples\MmLeak\app\.externalNativeBuild\cmake\debug\armeabi-v7a/D:/adt-bundle-windows-x86/android-ndk-r18b/sources/cxx-stl/llvm-libc++/include/ios:759
        D:\Project\Android-Samples\MmLeak\app\.externalNativeBuild\cmake\debug\armeabi-v7a/D:\Project\Android-Samples\MmLeak\app\src\main\cpp/native-lib.cpp:32
root@Jon:/home/jon# 

如上,非常清晰的告訴我們內存泄漏在native-lib.cpp的32行,正是我們內存泄漏的位置。

再看看leak-analyze-gdb

During symbol reading, Child DIE 0xd4e4 and its abstract origin 0xd2fd have different parents.
During symbol reading, Child DIE 0xd56e and its abstract origin 0x4f85 have different parents.
During symbol reading, Child DIE 0xd555 and its abstract origin 0xd37b have different parents.
During symbol reading, Child DIE 0xd5c6 and its abstract origin 0xd3ae have different parents.
During symbol reading, Child DIE 0xd628 and its abstract origin 0x4dfb have different parents.
During symbol reading, Child DIE 0xd600 and its abstract origin 0xd3ea have different parents.
During symbol reading, Child DIE 0xd72a and its abstract origin 0xc8b8 have different parents.
leaktracer::TMapMemoryInfo<leaktracer::MemoryTrace::_allocation_info_struct>::getNextPair(leaktracer::MemoryTrace::_allocation_info_struct**, void**) + 9 in section .text
0x36d20 is in leaktracer::TMapMemoryInfo<leaktracer::MemoryTrace::_allocation_info_struct>::getNextPair(leaktracer::MemoryTrace::_allocation_info_struct**, void**) (../../../../src/main/cpp/leak_tracer/include/MapMemoryInfo.hpp:187).
187     ../../../../src/main/cpp/leak_tracer/include/MapMemoryInfo.hpp: 沒有那個文件或目錄.
std::__ndk1::basic_ostream<char, std::__ndk1::char_traits<char> >::operator<<(void const*) + 245 in section .text
0x36f72 is in std::__ndk1::basic_ostream<char, std::__ndk1::char_traits<char> >::operator<<(void const*) (D:/adt-bundle-windows-x86/android-ndk-r18b/sources/cxx-stl/llvm-libc++/include/ios:759).
759     D:/adt-bundle-windows-x86/android-ndk-r18b/sources/cxx-stl/llvm-libc++/include/ios: 沒有那個文件或目錄.
Java_com_sunmi_mmleak_MainActivity_NativeMmLeak + 111 in section .text
0x33f8c is in Java_com_sunmi_mmleak_MainActivity_NativeMmLeak(JNIEnv*, jobject) (D:\Project\Android-Samples\MmLeak\app\src\main\cpp/native-lib.cpp:32).
32      D:\Project\Android-Samples\MmLeak\app\src\main\cpp/native-lib.cpp: 沒有那個文件或目錄.

leak-analyze-gdb這個工具顯示的信息有些凌亂,不建議使用

最後測試Demo:
https://github.com/zhuyong006/Android-Samples/tree/master/MmLeak

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章