溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Android崩潰日志獲取與解析

發(fā)布時(shí)間:2020-06-27 03:59:11 來(lái)源:網(wǎng)絡(luò) 閱讀:20632 作者:openflight 欄目:移動(dòng)開(kāi)發(fā)

今天來(lái)寫(xiě)點(diǎn)Android崩潰的東西,在我們寫(xiě)代碼的過(guò)程中,代碼寫(xiě)的再好再嚴(yán)謹(jǐn),也總是會(huì)有一些程序的崩潰,對(duì)于release出去的APP,我們肯定是希望我們能夠拿到崩潰的日志,便于bug的發(fā)現(xiàn)以及修復(fù),在下一個(gè)版本中再修復(fù)。所以,今天我們來(lái)說(shuō)說(shuō)Android崩潰日志的抓取。
在程序界面有一句話很流行,那就是不要重復(fù)造輪子。現(xiàn)在市面上有很多的崩潰日志抓取工具,比如騰訊的bugly,不管是eclipse還是Android Studio,集成都是非常簡(jiǎn)單,他可以抓取到JAVA的崩潰,同樣也可以抓取到NDK代碼的崩潰。
Java的崩潰就沒(méi)有什么好說(shuō)的,集成的步驟以及實(shí)現(xiàn)的原理太簡(jiǎn)單,下面我們來(lái)看看如何集成NDK崩潰的抓取
1、首先在c/c++代碼的任意位置添加代碼
const char SO_FILE_VERSION[]  __attribute__ ((section (".bugly_version"))) = "version name";
注意,如果是cpp文件得話必須加上extern "C",這一點(diǎn)騰訊給出來(lái)的文檔里面沒(méi)有說(shuō)明。如果不加的話,我們編譯出來(lái)的動(dòng)態(tài)庫(kù)是沒(méi)有版本號(hào)信息的,為什么是.bugly_version,這個(gè)只有騰訊知道,我們知道騰訊定義了這個(gè)一個(gè)符號(hào),用來(lái)讀取出動(dòng)態(tài)庫(kù)的版本號(hào)來(lái)。
2、編譯出so后,我們可以查看版本號(hào),使用NDK工具arm-linux-androideabi-readelf.exe來(lái)讀取。
命令行工具下運(yùn)行arm-linux-androideabi-readelf.exe -p .bugly_version libXXXX.so就可以讀取到第一步中寫(xiě)入的version name了。Windows系統(tǒng)arm-linux-androideabi-readelf.exe的路徑為android-ndk-r10e\toolchains\arm-linux-androideabi-4.x\prebuilt\windows-x86_64\bin。
3、從編譯結(jié)果的obj目錄取出對(duì)應(yīng)的動(dòng)態(tài)庫(kù),利用騰訊提供的批處理文件生成map文件,并將map文件上傳給騰訊,這樣我們的apk里面發(fā)布的動(dòng)態(tài)庫(kù)是release版本的,但是通過(guò)騰訊的網(wǎng)頁(yè)看到的堆棧還是可以定位到崩潰代碼的行數(shù)以及所在的文件。
集成就這么結(jié)束了,還是很簡(jiǎn)單的,如下是我測(cè)試程序產(chǎn)生的崩潰日志
#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的集成就講到這里了,那么問(wèn)題來(lái)了,bugly是怎么做到的呢?以上都是基于我們的APP運(yùn)行在有網(wǎng)絡(luò)的環(huán)境下,那么如果我們開(kāi)發(fā)的APP是要運(yùn)行在一個(gè)沒(méi)有網(wǎng)絡(luò)的環(huán)境中呢,怎么辦?怎么辦?怎么辦?很悲劇,本人是做車載導(dǎo)航開(kāi)發(fā)的,而很多車載設(shè)備是沒(méi)有網(wǎng)絡(luò)的,那么就只能是抓取log保存在本地,然后取出對(duì)應(yīng)的log來(lái)給開(kāi)發(fā)人員分析了。JAVA的崩潰很好辦,我們實(shí)現(xiàn)一下UncaughtExceptionHandler,然后將crash信息保存到本地的文件中就好了。那么NDK的崩潰呢?我們要感謝偉大的google把google-breakpad(https://github.com/svn2github/google-breakpad)開(kāi)源出來(lái)了。

那接下來(lái)我們來(lái)看一下breakpad的集成吧。本例采用eclipse工程的方式,為啥不用Android Studio?因?yàn)閎reakpad給android提供的編譯方式就是使用mk文件來(lái)編譯的,而且個(gè)人感覺(jué)用eclipse來(lái)做NDK開(kāi)發(fā)更方便。

先看一下jni目錄的結(jié)構(gòu)

Android崩潰日志獲取與解析

google-breakpad-master中有提供一個(gè)android的例子,先不管那么多,直接進(jìn)入到例子的目錄,ndk-build一下再說(shuō),什么?編譯不過(guò),怎么可能,google一下為什么,好吧,真的是編譯不過(guò),為什么,因?yàn)楣雀璧墓こ處熢趯?xiě)breakpad的mk文件得時(shí)候居然漏了一些東西,好吧,我們都給補(bǔ)上。修改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

好了,再來(lái)一次ndk-build,這次沒(méi)有問(wèn)題了,可以正常編譯,把編譯的結(jié)果push到手機(jī)上,運(yùn)行一下,生成了一個(gè)dmp文件,恩,這個(gè)dmp文件就是我們要的東西了。可以開(kāi)始移植了,jni的代碼和mk文件也都非常簡(jiǎn)單,直接貼出來(lái)
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();
}

// 設(shè)置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);
}


其中有幾個(gè)需要注意的地方,大家可能看到Android.mk中加了一句LOCAL_STATIC_LIBRARIES += libgcc,這一句在例子中是沒(méi)有的,為啥呢,因?yàn)楸救耸稚蟽膳_(tái)手機(jī),N5以及Note2,如果如加這一句的話Note2一運(yùn)行就崩潰。還有一點(diǎn),為啥要用static google_breakpad::ExceptionHandler *handler呢,例子里面是直接在main函數(shù)里面聲明并初始化的,好吧,我最開(kāi)始也是這么認(rèn)為的,直接放在Jni_OnLoad里面,結(jié)果dmp文件無(wú)法生成。那為什么例子中可以正常生成呢?好吧,因?yàn)槔又衏rash是在main函數(shù)里面的,handler的作用域是整個(gè)main函數(shù),所以他可以生成。如果你把這段代碼放到Jni_OnLoad中,那么他的作用域也是Jni_OnLoad,Jni_OnLoad返回之后就沒(méi)有效果了,這顯然不是我們想要的,我們希望它的作用域跟APP的生命周期是一樣的,所以把他定義為static。

工作都做完了,拿臺(tái)手機(jī)來(lái)跑吧,調(diào)用crash方法會(huì)崩潰,崩潰之后我們可以看到在我們?cè)O(shè)置的目錄下面已經(jīng)有一個(gè)dmp文件生成了,那dmp文件這么解析呢?windows實(shí)在是太不方便了, 下面還是給出ubuntu系統(tǒng)的解析方法吧。

1、下載google-breakpad源代碼,編譯linux版本,找到以下兩個(gè)文件

google-breakpad-read-only/src/tools/linux/dump_syms/dump_syms
google-breakpad-read-only/src/processor/minidump_stackwalk

2、任意位置建立一個(gè)文件夾,文件夾中包含dump_syms、minidump_stackwalk、*.dmp、所有的動(dòng)態(tài)庫(kù)文件(多個(gè)動(dòng)態(tài)庫(kù)需要重復(fù)3、4步驟)

3、執(zhí)行命令./dump_syms libXXX.so > libXXX.so.sym

4、創(chuàng)建文件夾 symbols/libgame.so/6D7D7B4FAAE9D2686CF45FA12A9E3AD30,并將生成的libXXX.so.sym拷貝到該文件夾中,6D7D7B4FAAE9D2686CF45FA12A9E3AD30為生成的libXXX.so.sym的第一行的內(nèi)容

5、執(zhí)行命令./minidump_stackwalk XXXXXXXXXXX.dmp symbols/ > result.txt

好了,大功告成,我們來(lái)看一下解析出來(lái)的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是什么東西?應(yīng)該是armv5架構(gòu)的CPU,crash函數(shù)中崩潰,而且該函數(shù)的返回值是void,就這些信息,如果NDK代碼多的話, 還是很難定位到崩潰的地方 ,意義不是很大吶,好吧,這個(gè)是從libs中取出來(lái)的so庫(kù),那么我們?cè)囋噊bj中取出來(lái)的so去解析結(jié)果是怎么樣的。

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

哇,這個(gè)結(jié)果就比較完美了,c++中崩潰的堆棧都有了,哪個(gè)文件哪一行都有,但是呢,跟bugly比起來(lái)還是差了一些,因?yàn)閎ugly中可以看到j(luò)ava層調(diào)用的堆棧。結(jié)果是比較完美了,可是可是可是,操作還是麻煩了一點(diǎn),如果我的應(yīng)用有很多個(gè)動(dòng)態(tài)庫(kù)的話,那解析一個(gè)dmp文件就要好久了,很不幸,我曾經(jīng)開(kāi)發(fā)的應(yīng)用里面包含了將近10個(gè)庫(kù),天啊,這就算再熟練解析一個(gè)文件也要好幾分鐘啊,而且中途還不能出錯(cuò),殺了我吧,我不干了。我的理念是,凡是重復(fù)的工作都讓電腦去做,腳本是個(gè)很好的東西,下面就把shell腳本放出來(lái)吧

#!/bin/bash

#[usage]
#將本腳本、dump_sys、minidump_stackwalk放在同級(jí)目錄下,并創(chuàng)建libs文件夾,所有動(dòng)態(tài)庫(kù)放到libs文件夾內(nèi)
#./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()
{
#檢查參數(shù) $1:dmp文件路徑;$2:生成的txt文件的路徑
Check $1 $2
echo "start convert "$1" to "$2"...."

#創(chuàng)建解析dmp文件相關(guān)的目錄以及文件
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

好了,寫(xiě)到這里本文也就接近尾聲了,什么?不知道怎么用shell腳本?好吧,好人做到底,送佛送到西。新建文件xxx.sh,雙擊打開(kāi),將腳本內(nèi)容復(fù)制進(jìn)去,控制臺(tái)執(zhí)行命令chmod a+x xxx.sh,然后照著腳本里面注釋的內(nèi)容做吧,Good Luck!沒(méi)有Ubuntu系統(tǒng)怎么辦?我是裝了個(gè)虛擬機(jī)跑Ubuntu系統(tǒng),如果你是做NDK開(kāi)發(fā)的,建議你也這么做,比Windows方便太多了,那如果你不想裝,那就用你的手機(jī)試試吧,你的手機(jī)也是linux系統(tǒng)的,shell腳本也是可以跑,但是本文中提出的dump_sys和minidump_stackwalk兩個(gè)文件就得編譯arm版本的了,這個(gè)由于我沒(méi)有需求,有這個(gè)需求的人可以研究一下。


------End------


向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI