基于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-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

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值