腳本處理iOS的Crash日誌

背景

當咱們打包app時,能夠選擇生成對應的符號表,其保存 16 進制函數地址映射信息,經過給定的函數起始地址和偏移量,能夠對應函數具體信息以供分析。git

因此咱們拿到測試給的閃退日誌(.crash)時,須要找到打包時對應生成的符號表(.dSYM)做爲鑰匙解析。具體分爲下面幾個步驟github

  1. dwarfdump --uuid 命令獲取 .dSYMuuidshell

  2. 打開 .crash 文件,在特定位置找到 uuidbash

  3. 根據 arm 版本比對二者是否一致app

  4. Xcode 目錄下尋找 symbolicatecrash 工具函數

    不一樣版本文件路徑不一樣,具體版本請谷歌。Xcode9路徑是/Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/工具

  5. 設置終端環境變量測試

    export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"ui

  6. 使用 symbolicatecrash 工具解析日誌 symbolicatecrash .crash .dsym > a.out編碼

雖然過程不復雜,可是每次都須要手動執行一次檢查與命令,過於繁瑣,因此決定用腳本化提升效率。


步驟實現

輸入Crash日誌

#要求輸入crash文件路徑
inputFile 'Please Input Crash File' 'crash'
crashPath=$filePath
複製代碼

因爲須要輸入兩種不一樣後綴的文件路徑,且都須要檢查,所以統必定義一個方法。

#定義全局變量
filePath=
#輸入文件路徑
inputFile() {
    readSuccess=false
    #首先清空變量值
    filePath=
    while [ $readSuccess = false ]; do 
        echo $1
        #讀取到變量中
        read -a filePath
        if [[ ! -e $filePath || ${filePath##*.} != $2 ]]; then
            echo "Input file is not ."$2
        else
            readSuccess=true
        fi
    done
}
複製代碼

.dSYM 是文件夾路徑,因此這裏簡單的判斷了路徑是否存在,若是不存在就繼續讓用戶輸入。

Shell命令中判斷分爲[]與[[]],後者比前者更通用,可使用 || 正則運算等。

判斷中,-f表示檢查是否存在該文件,-d表示檢查是否存在文件夾,-e表示檢查是否存在該路徑

輸入dSYM符號表

dsymSuccess=false
while [ $dsymSuccess = false ]; do
    #要求輸入dSYM文件路徑
    inputFile 'Please Input dSYM File' 'dSYM'
    dsymPath=$filePath
    #檢查是否匹配
    checkUUID "$crashPath" "$dsymPath"
    match=$?
    if [ $match -eq 0 ]; then
        echo 'UUID not match!'
    else
        dsymSuccess=true
    fi
done
複製代碼

循環獲取匹配 UUIDdSYM ,這裏使用了另外一種方法獲取方法返回值,具體以後章節會總結。

查找symbolicatecrash工具

Xcode 文件夾指定路徑下查找工具,加快效率,若是沒找到就中止運行。

# 查找symbolicatecrash解析工具,內置在Xcode的庫文件中
toolPath=`find /Applications/Xcode.app/Contents/SharedFrameworks -name symbolicatecrash | head -n 1`
if [ ! -f $toolPath ]; then
    echo "Symbolicatecrash not exist!"
    exit 0
fi
複製代碼

執行解析命令

#先設置環境變量
export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"
#指定解析結果路徑
crashName=`basename $crashPath`
afterPath="$(dirname "$crashPath")"/"${crashName%%.*}""_after.crash"
#開始解析
$toolPath "$crashPath" "$dsymPath" > "$afterPath" 2> /dev/null 
複製代碼

這裏我將錯誤信息導流到 /dev/null,保證解析文件沒有雜亂信息。


遇到的問題

怎麼獲取函數返回值?

以前沒有處理過須要返回數值的方法,因此一開始有點懵,查詢資料後最終採用了兩種方式實現了效果,如今作一些總結。

全局變量記錄

#定義全局變量
filePath=
inputFile() {
    #讀取到變量中
    read -a filePath
}
inputFile
crashPath=$filePath
複製代碼

經過 inputFile 方法來了解一下,首先定義一個全局變量爲 filePath,在方法中從新賦值,方法結束後讀取全局變量中的數據。

這種方法的好處是能夠自定義返回參數類型和個數,缺點是容易和其餘變量搞混。

Return返回值

相似與C語言中的用法,腳本也支持 retrun 0 返回結果並中止運行。

checkUUID() {
    grep "$arm64id" "$1"
    if [ $? -ne 0 ]; then
        return 1;
    fi
    return 0;
}
checkUUID "$crashPath" "$dsymPath"
match=$?
複製代碼

獲取結果的方式爲 $?,其可以返回環境中最後一個指令結果,也就是以前執行的checkUUID的結果。

優勢是簡潔明瞭,符合編碼習慣,缺點是返回值只能是 0-255 的數字,不能返回其餘類型的數據。

獲取打印值

還有一種方法其實平時一直在使用,只不過並不瞭解其運行方式。

crashName=`basename $crashPath`

print() {
    echo "Hello World"
}
text=$(print)
複製代碼

運行系統預設的方法或者自定義方法,將執行命令用 $() 的方式使用,就能夠獲取該命令中全部打印的信息,賦值到變量就能夠拿到須要的返回值。

優勢是功能全效率高,使用字符串的方式能夠傳遞定製化信息,缺點是不可預期返回結果,須要經過字符串查找等命令輔助。

循環輸入合法路徑

在個人設想中,須要用戶輸入匹配的 dSYM 文件路徑,若是不匹配,則從新輸入,直到合法。爲了支持嵌套,須要定義局部變量控制循環,具體代碼以下

dsymSuccess=false
while [ $dsymSuccess = false ]; do
    #要求輸入dSYM文件路徑
    inputFile 'Please Input dSYM File' 'dSYM'
    dsymPath=$filePath
    #檢查是否匹配
    checkUUID "$crashPath" "$dsymPath"
    match=$?
    if [ $match -eq 0 ]; then
        echo 'UUID not match!'
    else
        dsymSuccess=true
    fi
done
複製代碼

處理字符串

獲取到 UUID 全部輸出信息後,須要截取出對應平臺的信息,處理仍是不太熟悉,特意整理以下

#原始信息
UUID: 92E495AA-C2D4-3E9F-A759-A50AAEF446CD (armv7) /Volumes/.dSYM/Contents/Resources/DWARF/app
UUID: 536527A8-0243-34DB-AE08-F1F64ACA4351 (arm64) /Volumes/.dSYM/Contents/Resources/DWARF/app
 #去除中間間隔-
uuid=${uuid//-/}
 #從後往前找第一個匹配 \(arm64的,而且都刪除
arm64id=${uuid% \(arm64*}
#處理後
UUID: 92E495AAC2D43E9FA759A50AAEF446CD (armv7) /Volumes/.dSYM/Contents/Resources/DWARF/app
UUID: 536527A8024334DBAE08F1F64ACA4351
 #從前日後找最後一個UUID: ,並刪除
arm64id=${arm64id##*UUID: }
#處理後 
536527A8024334DBAE08F1F64ACA4351
複製代碼

總結

看似簡單的腳本,也花了一天時間編寫,整體仍是不太熟練,仍需努力聯繫。

此次特意嘗試了與上次不一樣的參數輸入方法,使用提示輸入的方式,果真遇到了新的問題。好在都查資料解決了,結果還算滿意。

腳本我提交到了Github,歡迎你們指教共同進步!給個關注最好啦~

相關文章
相關標籤/搜索