背景
當我們打包app時,可以選擇生成對應的符號表,其儲存 16 進位制函式地址對映資訊,通過給定的函式起始地址和偏移量,可以對應函式具體資訊以供分析。
所以我們拿到測試給的閃退日誌(.crash
)時,需要找到打包時對應生成的符號表(.dSYM
)作為鑰匙解析。具體分為下面幾個步驟
-
dwarfdump --uuid
命令獲取.dSYM
的uuid
-
開啟
.crash
檔案,在特定位置找到uuid
-
根據
arm
版本比對兩者是否一致 -
到
Xcode
目錄下尋找symbolicatecrash
工具不同版本檔案路徑不同,具體版本請谷歌。Xcode9路徑是/Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/
-
設定終端環境變數
export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"
-
使用
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
複製程式碼
迴圈獲取匹配 UUID
的 dSYM
,這裡使用了另一種方法獲取方法返回值,具體之後章節會總結。
查詢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,歡迎大家指教共同進步!給個關注最好啦~