Android崩溃日志获取与解析

  在程序界面有一句话很流行,那就是不要重复造轮子。现在市面上有很多的崩溃日志抓取工具,比如腾讯的bugly,不管是eclipse还是Android Studio,集成都是非常简单,他可以抓取到JAVA的崩溃,同样也可以抓取到NDK代码的崩溃。

  Java的崩溃就没有什么好说的,集成的步骤以及实现的原理太简单,下面我们来看看如何集成NDK崩溃的抓取

  1. 首先在c/c++代码的任意位置添加代码const char SO_FILE_VERSION[]  __attribute__ ((section (".bugly_version"))) = "1.0.1",注意,如果是cpp文件得话必须加上extern "C",这一点腾讯给出来的文档里面没有说明。如果不加的话,我们编译出来的动态库是没有版本号信息的,为什么是.bugly_version,这个只有腾讯知道,我们知道腾讯定义了这个一个符号,用来读取出动态库的版本号来。
  2. 编译出so后,我们可以查看版本号,使用NDK工具arm-linux-androideabi-readelf.exe来读取。命令行工具下运行arm-linux-androideabi-readelf.exe -p .bugly_version libXXXX.so就可以读取到第一步中写入的version name了。Windows系统arm-linux-androideabi-readelf.exe的路径为android-ndk-r10e\toolchains\arm-linux-androideabi-4.x\prebuilt\windows-x86_64\bin。
  3. 从编译结果的obj目录取出对应的动态库,利用腾讯提供的批处理文件生成map文件,并将map文件上传给腾讯,这样我们的apk里面发布的动态库是release版本的,但是通过腾讯的网页看到的堆栈还是可以定位到崩溃代码的行数以及所在的文件。

  集成就这么结束了,还是很简单的,如下是我测试程序产生的崩溃日志。非常清晰的可以看到代码崩溃的位置、动态库支持的平台、动态库的版本

#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/[email protected]@com.openflight.bugly-2@[email protected]

  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系统的解析方法吧。

  1. 下载google-breakpad源代码,编译linux版本,找到以下两个文件

    • google-breakpad-read-only/src/tools/linux/dump_syms/dump_syms
    • google-breakpad-read-only/src/processor/minidump_stackwalk
  2. 任意位置建立一个文件夹,文件夹中包含dump_syms、minidump_stackwalk、*.dmp、所有的动态库文件(多个动态库需要重复3、4步骤)
  3. 执行命令./dump_syms libXXX.so > libXXX.so.sym
  4. 创建文件夹 symbols/libgame.so/6D7D7B4FAAE9D2686CF45FA12A9E3AD30,并将生成的libXXX.so.sym拷贝到该文件夹中,6D7D7B4FAAE9D2686CF45FA12A9E3AD30为生成的libXXX.so.sym的第一行的内容
  5. 执行命令./minidump_stackwalk XXXXXXXXXXX.dmp symbols/ > result.txt

  大功告成,我们来看一下解析出来的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------

时间: 2024-10-19 03:21:14

Android崩溃日志获取与解析的相关文章

【Android应用开发】 Android 崩溃日志 本地存储 与 远程保存

示例代码下载 : http://download.csdn.net/detail/han1202012/8638801; 一. 崩溃日志本地存储 1. 保存原理解析 崩溃信息本地保存步骤 : -- 1. 自定义类实现 UncaughtExceptionHandler : public class CrashHandler implements UncaughtExceptionHandler; -- 2. 设置该自定义的 CrashHandler 类为单例模式 : // 单例模式 private

【iOS测试】【随笔】崩溃日志获取

◆版权声明:本文出自胖喵~的博客,转载必须注明出处. 转载请注明出处:http://www.cnblogs.com/by-dream/p/5942779.html 本打算按照Android的套路去写,但是在iOS上没有像Android的adb这样“恶心”的东西,因此第一篇文章从如何获取崩溃log开始. 首先需要安装Xcode(这里我使用的mac版的7.3版本) 当时为了装这个版本,把mac的系统版本升级到了10.11.4 选择Xcode中device 点击之后进入如下页面,我们插入自己的iPho

崩溃日志

因为用的是华为,默认状态手机log为关闭状态,所以看不到详细错误信息.手机拨号*#*#2846579#*#*,进入projectmenu–后台设置–LOG设置–LOG开关–打开:这样就可以了. Android崩溃日志获取与解析 http://blog.csdn.net/zhouhaibo_xm/article/details/50405513 不集成SDK,如何获取android崩溃日志---360加固宝 http://jingyan.baidu.com/article/f25ef2545ae8

常用获取Android崩溃日志和IOS崩溃日志的几种方法

一:前言 在日常测试app时,经常会遇到崩溃问题,测试快速抓取到崩溃日志可以有效方便开发进行定位,快速解决问题所在测试做到测试分析,定位是非常重要的,这也是判断一个测试能力指标的一大维度. 二:Android崩溃日志 一.通过adb logcat获取 # 清除日志,新手上路时,日志内容很多,对于能毕现的日志,可以先清除后重新获取 adb logcat -c # 然后再次运行崩溃操作,再抓取日志 # 存储日志到当前目录下的 carsh.log 中 adb logcat -d *:W > crash

IOS崩溃日志解析(crash log)

IOS的应用程序少不了crash,互联网统计分析工具友盟有一项目错误分析的功能,专门用于应用程序崩溃日志统计,最近研究友盟上统计到的崩溃日志,在此对崩溃日志做一个简单的总结. IOS崩溃日志分类: 一.低内存崩溃:IOS设备检测到低内存时,虚拟内存系统发出通知请求应用释放内存.这些通知发送到所有正在运行的应用和进程,试图收回一些内存.如果内存使用依然居高不下,系统将会终止后台线程以缓解内存压力.如果可用内存足够,应用将能够继续运行而不会产生崩溃报告.否则,应用将被iOS终止,并产生低内存崩溃报告

android app崩溃日志收集以及上传

源码获取请到github:https://github.com/DrJia/AndroidLogCollector 已经做成sdk的形式,源码已公开,源码看不懂的请自行google. 如果想定制适应自己app的sdk请自行fork. AndroidLogCollector android app崩溃日志收集sdk 1.0 作者:贾博士 崩溃日志收集方法: 1.LogCollector是lib包,在需要添加崩溃日志sdk的工程中导入此包. 2.导入lib后,在自己的工程的AndroidManife

Xcode7.3工具解析App崩溃日志(.crash文件)

Xcode7.3工具解析App崩溃日志(.crash文件) 原文链接:http://blog.csdn.net/u011056605 开发的App或者游戏提交审核后,偶尔会收到测试反馈的消息,说应用崩溃了,bug偶尔出现,难以找到确定的重现方法. 怎么办?可以分析崩溃文件啊,也就是app崩溃后,自动保存在设备本地的.crash文件. 获得崩溃日志的方式,在 获取设备上的调试信息与崩溃日志分析 中有说. 在环境ok的情况下,xcode中是可以自动解析.crash文件的.旧版本的xcode甚至可以导

android开发之应用Crash自动抓取Log_自动保存崩溃日志到本地

http://blog.csdn.net/jason0539/article/details/45602655 应用发生crash之后要查看log,判断问题出在什么地方,可是一旦应用发布出去,就要想办法把用户的崩溃日志拿到分析. 所以要在发生crash之后抓取log,然后上传到服务器,方便开发者查看,现在都有很多第三方做这方面的服务,这里说下如何自己来实现. 其实原理很简单,应用出现异常后,会由默认的异常处理器来处理异常, 我们要做的就是把这个任务接管过来,自己处理异常,包括收集日志,保存到本地

捕获android程序崩溃日志

主要类: package com.example.callstatus; import java.io.File; import java.io.FileOutputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.Thread.UncaughtExceptionHandler; import java.lang.reflect.Field; import java.net.Unkn