标签:
在程序界面有一句话很流行,那就是不要重复造轮子。现在市面上有很多的崩溃日志抓取工具,比如腾讯的bugly,不管是eclipse还是Android Studio,集成都是非常简单,他可以抓取到JAVA的崩溃,同样也可以抓取到NDK代码的崩溃。
Java的崩溃就没有什么好说的,集成的步骤以及实现的原理太简单,下面我们来看看如何集成NDK崩溃的抓取
集成就这么结束了,还是很简单的,如下是我测试程序产生的崩溃日志。非常清晰的可以看到代码崩溃的位置、动态库支持的平台、动态库的版本
#00 pc 00000cd4 libErrorReport.so crash (E:/workspace/BuglyErrorTest/app/src/main/jni/ErrorReport.cpp:14-16) [armeabi-v5te] [1.0.1] #01 pc 00000cdb libErrorReport.so Java_com_openflight_bugly_JniTest_nativeCrash (E:/workspace/BuglyErrorTest/app/src/main/jni/ErrorReport.cpp:21) [armeabi-v5te] [1.0.1] #02 pc 000d3051 /data/dalvik-cache/arm/data@app@com.openflight.bugly-2@base.apk@classes.dex
bugly的集成就讲到这里了,那么问题来了,bugly是怎么做到的呢?以上都是基于我们的APP运行在有网络的环境下,那么如果我们开发的APP是要运行在一个没有网络的环境中呢,怎么办?怎么办?怎么办?很悲剧,本人是做车载导航开发的,而很多车载设备是没有网络的,那么就只能是抓取log保存在本地,然后取出对应的log来给开发人员分析了。JAVA的崩溃很好办,我们实现一下UncaughtExceptionHandler,然后将crash信息保存到本地的文件中就好了。那么NDK的崩溃呢?我们要感谢伟大的google把google-breakpad开源出来了。那接下来我们来看一下breakpad的集成吧。本例采用eclipse工程的方式,为啥不用Android Studio?因为breakpad给android提供的编译方式就是使用mk文件来编译的,而且个人感觉用eclipse来做NDK开发更方便。
先看一下jni目录的结构
google-breakpad-master中有提供一个android的例子,先不管那么多,直接进入到例子的目录,ndk-build一下再说,什么?编译不过,怎么可能,google一下为什么,好吧,真的是编译不过,为什么,因为谷歌的工程师在写breakpad的mk文件得时候居然漏了一些东西,好吧,我们都给补上。修改google-breakpad-master/android/google-breakpad/Android.mk,把LOCAL_SRC_FILES修改为
LOCAL_SRC_FILES := src/client/linux/crash_generation/crash_generation_client.cc src/client/linux/handler/exception_handler.cc src/client/linux/handler/minidump_descriptor.cc src/client/linux/log/log.cc src/client/linux/minidump_writer/linux_dumper.cc src/client/linux/minidump_writer/linux_ptrace_dumper.cc src/client/linux/minidump_writer/minidump_writer.cc src/client/linux/microdump_writer/microdump_writer.cc src/client/linux/dump_writer_common/ucontext_reader.cc src/client/linux/dump_writer_common/seccomp_unwinder.cc src/client/linux/dump_writer_common/thread_info.cc src/client/minidump_file_writer.cc src/common/android/breakpad_getcontext.S src/common/convert_UTF.c src/common/md5.cc src/common/string_conversion.cc src/common/linux/elfutils.cc src/common/linux/file_id.cc src/common/linux/guid_creator.cc src/common/linux/linux_libc_support.cc src/common/linux/memory_mapped_file.cc src/common/linux/safe_readlink.cc
好了,再来一次ndk-build,这次没有问题了,可以正常编译,把编译的结果push到手机上,运行一下,生成了一个dmp文件,恩,这个dmp文件就是我们要的东西了。可以开始移植了,jni的代码和mk文件也都非常简单,直接贴出来
Application.mk
APP_STL := stlport_static
APP_ABI := armeabi
Android.mk
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := ErrorReport LOCAL_SRC_FILES := ErrorReport.cpp LOCAL_STATIC_LIBRARIES += breakpad_client LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -llog LOCAL_STATIC_LIBRARIES += libgcc include $(BUILD_SHARED_LIBRARY) ifneq ($(NDK_MODULE_PATH),) $(call import-module,google_breakpad) else include $(LOCAL_PATH)/google-breakpad-master/android/google_breakpad/Android.mk endif
ErrorReport.cpp
#include <jni.h> #include <stdio.h> #include <android/log.h> #include "google-breakpad-master/src/client/linux/handler/exception_handler.h" #include "google-breakpad-master/src/client/linux/handler/minidump_descriptor.h" #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "ErrorReport", __VA_ARGS__) extern "C" { JNIEXPORT jint JNICALL Java_com_openflight_errorreport_CrashHandler_nativeCrash( JNIEnv* env, jobject thiz); JNIEXPORT jint JNICALL Java_com_openflight_errorreport_CrashHandler_setNativeCrashDir( JNIEnv* env, jobject thiz, jstring path); } static google_breakpad::ExceptionHandler *handler = NULL; bool DumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context, bool succeeded) { LOGD("Dump path: %s", descriptor.path()); return succeeded; } void crash(){ volatile int* a = reinterpret_cast<volatile int*>(NULL); *a = 1; } JNIEXPORT jint Java_com_openflight_errorreport_CrashHandler_nativeCrash( JNIEnv* env, jobject thiz){ crash(); } // 设置dmp文件保存路径 JNIEXPORT jint JNICALL Java_com_openflight_errorreport_CrashHandler_setNativeCrashDir( JNIEnv* env, jobject thiz, jstring path){ const char *filePath = env->GetStringUTFChars(path, 0); google_breakpad::MinidumpDescriptor descriptor(filePath); handler = new google_breakpad::ExceptionHandler(descriptor, NULL, DumpCallback, NULL, true, -1); }
其中有几个需要注意的地方,大家可能看到Android.mk中加了一句LOCAL_STATIC_LIBRARIES += libgcc,这一句在例子中是没有的,为啥呢,因为本人手上两台手机,N5以及Note2,如果如加这一句的话Note2一运行就崩溃。为啥要用static google_breakpad::ExceptionHandler *handler呢,例子里面是直接在main函数里面声明并初始化的,好吧,我最开始也是这么认为的,直接放在Jni_OnLoad里面,结果dmp文件无法生成。因为例子中crash是在main函数里面的,handler的作用域是整个main函数,所以他可以生成。如果你把这段代码放到Jni_OnLoad中,那么他的作用域也是Jni_OnLoad,Jni_OnLoad返回之后就没有效果了,这显然不是我们想要的,我们希望它的作用域跟APP的生命周期是一样的,所以把他定义为static。
工作都做完了,拿台手机来跑吧,调用crash方法会崩溃,崩溃之后我们可以看到在我们设置的目录下面已经有一个dmp文件生成了,那dmp文件这么解析呢?windows实在是太不方便了, 下面还是给出ubuntu系统的解析方法吧。
大功告成,我们来看一下解析出来的result.txt里面崩溃的堆栈
Thread 0 (crashed) 0 libErrorReport.so!_Z5crashv + 0x3 r0 = 0x41803818 r1 = 0x59600019 r2 = 0x00000001 r3 = 0x00000000 r4 = 0x00000000 r5 = 0x57ac67b8 r6 = 0x00000000 r7 = 0x57765dac r8 = 0xbea0a510 r9 = 0x57765da4 r10 = 0x57765da0 r12 = 0x5e0b6555 fp = 0xbea0a524 sp = 0xbea0a508 lr = 0x5e0b655b pc = 0x5e0b6550 Found by: given as instruction pointer in context
这是什么鬼,_Z5crashv是什么东西?应该是armv5架构的CPU,crash函数中崩溃,而且该函数的返回值是void,就这些信息,如果NDK代码多的话, 还是很难定位到崩溃的行数,而且0x3还需要通过idaq之类的软件去解析一下,才能定位到行数 ,意义不是很大呐,好吧,这个是从libs中取出来的so库,那么我们试试obj中取出来的so去解析结果是怎么样的。
Thread 0 (crashed) 0 libErrorReport.so!crash [ErrorReport.cpp : 27 + 0x4] r0 = 0x41803818 r1 = 0x59600019 r2 = 0x00000001 r3 = 0x00000000 r4 = 0x00000000 r5 = 0x57ac67b8 r6 = 0x00000000 r7 = 0x57765dac r8 = 0xbea0a510 r9 = 0x57765da4 r10 = 0x57765da0 r12 = 0x5e0b6555 fp = 0xbea0a524 sp = 0xbea0a508 lr = 0x5e0b655b pc = 0x5e0b6550 Found by: given as instruction pointer in context 1 libErrorReport.so!Java_com_openflight_errorreport_CrashHandler_nativeCrash [ErrorReport.cpp : 32 + 0x3] r4 = 0x00000000 r5 = 0x57ac67b8 r6 = 0x00000000 r7 = 0x57765dac r8 = 0xbea0a510 r9 = 0x57765da4 r10 = 0x57765da0 fp = 0xbea0a524 sp = 0xbea0a508 pc = 0x5e0b655b Found by: call frame info
这个结果就比较完美了,c++中崩溃的堆栈都有了,哪个文件哪一行都有,但是呢,跟bugly比起来还是差了一些,因为bugly中可以看到java层调用的堆栈。结果是比较完美了,可是可是可是操作还是麻烦了一点,如果我的应用有很多个动态库的话,那解析一个dmp文件就要好久了,很不幸,我曾经开发的应用里面包含了将近10个库,天啊,这就算再熟练解析一个文件也要好几分钟啊,而且中途还不能出错,杀了我吧,我不干了。我的理念是,凡是重复的工作都让电脑去做,想了多种方法,感觉最简单的还是shell脚本比较简单,所以就这么干了,脚本的代码也直接放出来。
#!/bin/bash #[usage] #将本脚本、dump_sys、minidump_stackwalk放在同级目录下,并创建libs文件夹,所有动态库放到libs文件夹内 #./dump2txt.sh [dmp文件路径] [生成的txt文件路径] LIBRARY_DIRECTORY="libs" LIBRARY_EXTENDNAME=".sym" LIBRARY_KEYPOS=3 Check() { if [ $# -ne 2 ];then echo please input two param,the first param is the dmp file path,the second param is txt file path exit fi if [ ! -f "$1" ]; then echo $1 is not exsit exit fi } DealLibrary() { if [ ! -f "$LIBRARY_DIRECTORY/$1" ]; then echo $LIBRARY_DIRECTORY/$1 is not exsit return fi SYM_NAME=$1$LIBRARY_EXTENDNAME ./dump_syms libs/$1 > $SYM_NAME cat $SYM_NAME | while read line do LIBRARY_CODE=$line ARR=($LIBRARY_CODE) LIBRARY_CODE="${ARR[$LIBRARY_KEYPOS]}" mkdir -p symbols/$1/$LIBRARY_CODE mv $SYM_NAME symbols/$1/$LIBRARY_CODE break done } Main() { #检查参数 $1:dmp文件路径;$2:生成的txt文件的路径 Check $1 $2 echo "start convert "$1" to "$2"...." #创建解析dmp文件相关的目录以及文件 rm -rf symbols for file in $LIBRARY_DIRECTORY/* do DealLibrary ${file:5} done #生成txt文件 ./minidump_stackwalk $1 symbols/ > $2 echo $2 is generated!!!! } Main $1 $2
好了,写到这里本文也就接近尾声了,shell脚本怎么在windows中运行?不好意思,没办法,只能在linux下才能运行,装个Ubuntu的虚拟机吧。其实android手机也是可以的,但是minidump_stackwalk和dump_syms这两个文件我们只编译了linux版本的,要在手机上运行的话还需要armeabi版本的才行,本人没有研究过,有兴趣的同学可以研究一下。其实windows系统上也是可以的,如果编写一个windows的C/S程序给测试的同学使用还是蛮方便的,但是工作量比较大,本人没有研究,有兴趣的同学也可以研究一下。
2015.12.27
------End------
标签:
原文地址:http://www.cnblogs.com/zhouhaibo/p/5081077.html