指令碼處理iOS的Crash日誌

VanchChen發表於2018-12-29

背景

當我們打包app時,可以選擇生成對應的符號表,其儲存 16 進位制函式地址對映資訊,通過給定的函式起始地址和偏移量,可以對應函式具體資訊以供分析。

所以我們拿到測試給的閃退日誌(.crash)時,需要找到打包時對應生成的符號表(.dSYM)作為鑰匙解析。具體分為下面幾個步驟

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

  2. 開啟 .crash 檔案,在特定位置找到 uuid

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

  4. Xcode 目錄下尋找 symbolicatecrash 工具

    不同版本檔案路徑不同,具體版本請谷歌。Xcode9路徑是/Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/

  5. 設定終端環境變數

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

  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,歡迎大家指教共同進步!給個關注最好啦~

相關文章