1. 程式人生 > >基於Android Ndk/Jni的記憶體洩漏檢測

基於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} )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
這一步的目的是為了將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");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
 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());
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
這裡說明下:

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 #
1
2
3
 開啟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-addr2line和leak-analyze-gdb
推薦用leak-analyze-addr2line

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# 
1
2
3
4
5
6
7
8
9
10
如上,非常清晰的告訴我們記憶體洩漏在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: 沒有那個檔案或目錄.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
leak-analyze-gdb這個工具顯示的資訊有些凌亂,不建議使用

最後測試Demo:
https://github.com/zhuyong006/Android-Samples/tree/master/MmLeak
--------------------- 
作者:zhuyong006 
來源:CSDN 
原文:https://blog.csdn.net/zhuyong006/article/details/88537499 
版權宣告:本文為博主原創文章,轉載請