美文网首页
Android Native内存问题检测

Android Native内存问题检测

作者: 蛋西 | 来源:发表于2019-07-26 15:40 被阅读0次

    android中的内存总体上可以分为两块,java heap与native heap。java heap的内存问题检测,由于java采用自动垃圾回收机制,所以大部分情况下,只需要关注内存泄漏问题、内存用量问题,确保activity等组件不发生内存泄漏,使用Profilter可以关注内存使用程度以及某个时刻对象的占用内存大小。而native heap的内存问题,则更为复杂而且多样,由于使用c/c++编写,所以可能存在的数组越界、内存泄漏等等问题。本文重点讲一下native的内存问题检测

    工具

    linux上的程序开发,经常使用valgrind工具套件来检测内存问题,Valgrind 工具套件包括 Memcheck(用于检测 C 和 C ++ 中与内存相关的错误)、Cachegrind(缓存分析器)、Massif(堆分析器)和其他几种工具。具体的如何集成,可以参考最后连接中的使用valgrind文章。
    目前Valgrind已被google弃用,并已从AOSP master中移除。官方强烈建议我们改用 AddressSanitizer工具。
    当然还有很多其他工具可以检测内存问题,这里我们就拿官方推荐的AddressSanitizer来看看能用它来做些什么。

    AddressSanitizer

    AddressSanitizer (ASan) 是一种基于编译器的快速检测工具,用于检测原生代码中的内存错误。AddressSanitizer 可以检测以下问题:

    • 堆栈和堆缓冲区上溢/下溢
    • 释放之后的堆使用情况
    • 超出范围的堆栈使用情况
    • 重复释放/错误释放

    很多朋友可能会想,那能不能检测内存泄漏呢?很抱歉,AddressSanitizer目前其实并不能检测内存泄漏的情况,这点已经在github官方上也有人提了issue,作者表示他们也很想支持,无奈人力有限,我们只好静待吧~
    那么它与传统的内存问题检测工具,例如 Valgrind ,有何区别?
    用过Valgrind的朋友应该都清楚,其会极大的降低程序运行速度,大约降低10倍,而 AddressSanitizer大约只降低2倍,这是什么概念,5倍的差距,所以从性能上如何抉择,一目了然了吧!
    Android NDK从API 27开始支持(Android O MR 1)。下面讲一下将AddressSanitizer集成到应用中。

    如何使用

    ASAN(Address-Sanitizier)早先是LLVM中的特性,后被加入GCC4.8,在GCC4.9后加入对ARM平台的支持。因此其他平台,例如linux,GCC4.8以上版本使用ASAN时不需要安装第三方库,通过在编译时指定编译CFLAGS即可打开开关。如果是android平台,编译arm平台的库时,建议使用clang。
    目前native编译主要通过两种方式:NDK-BUILD与CMAKE,分别对这两个种编译方式来讲一下如何集成。

    NDK-BUILD方式集成

    环境

    • android-ndk-r10d
    • 目标armeabi平台

    构建
    在Application.mk中添加

    APP_STL := c++_shared # Or system, or none.
    APP_CFLAGS := -fsanitize=address -fno-omit-frame-pointer
    APP_LDFLAGS := -fsanitize=address
    

    一个共享库STL是必须的,因为默认的c++库libstdc++通常没有帧指针。如果你连接静态的c++stdlib到你的应用中,是不能用的。
    并且指定编译器未clang:

    NDK_TOOLCHAIN := arm-linux-androideabi-clang3.5
    NDK_TOOLCHAIN_VERSION := clang
    

    这里为什么指定用clang,因为我刚开始用的是gcc,编译的时候,总会出现error: cannot find -lasan错误,原因是因为gcc没有完全支持asan,所以必须要用clang编译器,才能完全支持。当然clang的版本,需要你自己指定
    在Android.mk中添加

    # 指定采用arm指令集,而不是默认的thumb指令集
    LOCAL_ARM_MODE := arm 
    

    如果你的LOCAL_LDLIBS使用了-lstdc++,那么请将它移出,因为上面已经指定了APP_STL。

    运行
    在Android O MR1(API 27)或更高api等级的设备上运行程序,我们需要提供一个wrap.sh脚本,主要用于包装和替换应用进程,它允许一个可调式的应用定制他的应用启动过程,这个过程中,可以在生产环境的终端设备上使用ASan。

    • 在manifest中添加android:debuggable="true"属性,不过在新版的android studio中,可以不用添加,默认gradle中buildTyle是debug,该值默认是true。
    • 添加ASan运行时库到你的app或者module中的jniLibs目录下,或者在编译目标库时,将ASan运行时库作为一个动态库链接到你的目标库中。
    • 编写wrap.sh脚本,并且将文件放入到相同的目录下
    #!/system/bin/sh
    HERE="$(cd "$(dirname "$0")" && pwd)"
    export ASAN_OPTIONS=log_to_syslog=false,allow_user_segv_handler=1
    ASAN_LIB=$(ls $HERE/libclang_rt.asan-*-android.so)
    if [ -f "$HERE/libc++_shared.so" ]; then
        # Workaround for https://github.com/android-ndk/ndk/issues/988.
        export LD_PRELOAD="$ASAN_LIB $HERE/libc++_shared.so"
    else
        export LD_PRELOAD="$ASAN_LIB"
    fi
    "$@"
    

    ASan目标库我们该从哪里获取呢?有多个该如何选择呢?


    libclang_rt.asan-xxx

    我们看到在ndk目录的交叉编译工具链中,有针对许多平台的libclang_rt.asan开头的库,目前我们目标库是armeabi平台,所以这里我们选libclang_rt.asan-arm-android.so,将该库拷贝到jniLibs或者作为动态库加入到目标库的编译中去。例如,链接到目标动态库中:

    include $(CLEAR_VARS)
    PATH_TO_ASAN_LIB := XXXXX/libclang_rt.asan-arm-android.so
    LOCAL_MODULE := libasan
    LOCAL_SRC_FILES := $(PATH_TO_ASAN_LIB)  
    include $(PREBUILT_SHARED_LIBRARY)
    

    这样我们就能运行了,最后在apk包中,lib/armeabi会有warp.sh、libclang_rt.asan-arm-android.so、以及我们的目标动态库。

    运行结果
    在程序运行中,如果发生数组越界,在日志中,会出现AddressSanitizer开头的错误提示,奔溃堆栈如下:

    奔溃异常
    红色区域是奔溃堆栈,我们通过android平台的addr2line工具,将堆栈地址还原为源文件地址,很清楚的提示哪行代码数组越界了

    CMAKE方式集成

    cmake的集成方式相对简单,在gradle中配置

    android {
        defaultConfig {
            externalNativeBuild {
                cmake {
                    # Can also use system or none as ANDROID_STL.
                    arguments "-DANDROID_ARM_MODE=arm", "-DANDROID_STL=c++_shared"
                    cppFlags "-fsanitize=address -fno-omit-frame-pointer"
                }
            }
        }
    }
    

    即可,其他运行方式与ndk-build一致,运行结果也一致。

    总结

    使用AddressSanitizer可以很方便的检测出内存问题,可以作为我们排查native heap问题的一个利器,运用好它,可以事半功倍。不过建议在debug模式下使用,在发布的正式版本中不可带上,因为会影响程序的运行效率。

    参考

    使用valgrind
    Address Sanitizer
    AddressSanitizerOnAndroid

    相关文章

      网友评论

          本文标题:Android Native内存问题检测

          本文链接:https://www.haomeiwen.com/subject/blkdrctx.html