Flutter App crash日志搜集包括三部分,一部分來自於Dart code引起的異常,可以在flutter framework的main函數進行全局捕獲,此外還需對Native端
iOS
和Android
的異常進行捕獲.
iOS異常搜集與分析
開啟
DWARF
文件搜集
獲取Mach異常和Unix信號(
),用於捕獲系統內核的異常,在 http://opensource.apple.com 可以看到內核的完整代碼; void InstallUncaughtExceptionHandler() {
//根據需要選擇性的配置
signal(SIGABRT, CustomSignalHandler);
signal(SIGILL, CustomSignalHandler);
signal(SIGSEGV, CustomSignalHandler);
signal(SIGFPE, CustomSignalHandler);
signal(SIGBUS, CustomSignalHandler);
signal(SIGPIPE, CustomSignalHandler);
...
}注冊
NSSetUncaughtExceptionHandler
捕獲應用異常事件通過三方庫搜集,KSCrash,plcrashreporter,CrashKit,Crashlytics,Hockeyapp,友盟,Bugly,App Dynamic
獲取Crash符號日志
- 通過手機鏈接上
Xcode
直接獲取 - 通過應用內置的異常獲框架獲取crash日志並上傳到服務器.
- 通過手機鏈接上
Crash日志解析
隱藏符號替換: 根據官方文檔的說明通過
AppStore
發布之后下載的dysm文件會有部分敏感符號被隱藏,本地生成的則不會替換。所以在使用時需要先將其替換dsymutil -symbol-map <PathToXcodeArchive>/MyGreatApp.xcarchive/BCSymbolMaps <PathToDownloadedDSYMs>/<UUID>.dSYM
檢測
Crash
日志的uuid和ipa的執行文件uuid是否相同dwarfdump --uuid <PathToDSYMFile>/Contents/Resources/DWARF/<BinaryName> dwarfdump --uuid <PathToBinary>
當crash日志和對應的ipa執行文件uuid對應之后,利用xcode的
Symbolicate
工具進行符號化,注意這里的-l
后面的參數,分別指定了atos -arch <BinaryArchitecture> -o <PathToDSYMFile>/Contents/Resources/DWARF/<BinaryName> -l <LoadAddress> <AddressesToSymbolicate> atos -arch arm64 -o TouchCanvas.app.dSYM/Contents/Resources/DWARF/TouchCanvas -l 0x1022c0000 0x00000001022df754
ViewController.touchesEstimatedPropertiesUpdated(_:) (in TouchCanvas) + 304使用內置的工具解析
export DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer ./symbolicatecrash /Users/yourUserName/Desktop/CrashSignifying/crashFileName.crash /Users/UserName/Desktop/CrashSignifying/dSYMFileName.dSYM > crashFileName.crash
Crash日志介紹,
一份crash日志包含了以下幾個部分
Header:
Incident Identifier: 6156848E-344E-4D9E-84E0-87AFD0D0AE7B CrashReporter Key: 76f2fb60060d6a7f814973377cbdc866fffd521f
Hardware Model: iPhone8,1
Process: TouchCanvas [1052]
Path: /private/var/containers/Bundle/Application/51346174-37EF-4F60-B72D-8DE5F01035F5/TouchCanvas.app/TouchCanvas
Identifier: com.example.apple-samplecode.TouchCanvas
Version: 1 (3.0)
Code Type: ARM-64 (Native)
Role: Foreground
Parent Process: launchd [1]
Coalition: com.example.apple-samplecode.TouchCanvas [1806]
Date/Time: 2020-03-27 18:06:51.4969 -0700
Launch Time: 2020-03-27 18:06:31.7593 -0700
OS Version: iPhone OS 13.3.1 (17D50)Exception Information:
Exception Type: EXC_BREAKPOINT (SIGTRAP) 異常類型 Exception Subtype: 異常類型可讀取的描述信息
Exception Message: 對Exception Type的解釋
Exception Codes: 0x0000000000000001, 0x0000000102afb3d0 一個64bit的異常code
Exception Note:
不屬於一個特定的異常類型。如果此字段包含EXC_corpost_NOTIFY,則崩潰並非源自硬件陷阱,原因可能是該進程已被操作系統或名為abort()的進程顯式終止。如果此字段包含SIMULATED(這不是崩潰),則進程沒有崩潰,但操作系統可能隨后請求終止進程。如果此字段包含非致命條件(這不是崩潰),則進程不會終止,因為創建崩潰報告的問題不是致命的。
Termination Reason: 應用程序終止的具體原因
Triggered by Thread or Crashed Thread: 觸發崩潰或者崩潰的線程Diagnostic Messages: 系統當前對app的診斷信息
Termination Description: SPRINGBOARD, scene-create watchdog transgression: application<com.example.MyCoolApp>:667
exhausted real (wall clock) time allowance of 19.97 seconds
常見的異常處理方案
WatchDog timeout: 這種異常通常是由於主線程阻塞造成,同步的網絡請求,處理大量的model轉換,如json解析,3d models的解析,同步的處理大量的coredata數據,應用計算機視覺算法對輸入圖像和視頻執行各種任務。
- 下面2張圖時是改善前后的方案
改進之后
scence-create超時
Termination Description: SPRINGBOARD, scene-create watchdog transgression: application<com.example.MyCoolApp>:667
exhausted real (wall clock) time allowance of 19.97 seconds
| ProcessVisibility: Foreground
| ProcessState: Running
| WatchdogEvent: scene-create
| WatchdogVisibility: Foreground
| WatchdogCPUStatistics: (
| "Elapsed total CPU time (seconds): 15.290 (user 15.290, system 0.000), 28% CPU",
| "Elapsed application CPU time (seconds): 0.367, 1% CPU"
| )后台任務執行超時
Termination Reason: CAROUSEL, WatchConnectivity watchdog transgression. Exhausted wall time allowance of 15.00 seconds.
Termination Description: SPRINGBOARD,
CSLHandleBackgroundWCSessionAction watchdog transgression: xpcservice<com.example.MyCoolApp.watchkitapp.watchextension>:220:220
exhausted real (wall clock) time allowance of 15.00 seconds
| <FBExtensionProcess: 0x16df02a0; xpcservice<com.example.MyCoolApp.watchkitapp.watchextension>:220:220; typeID: com.apple.watchkit>
Elapsed total CPU time (seconds): 24.040 (user 24.040, system 0.000), 81% CPU
| Elapsed application CPU time (seconds): 1.223, 6% CPU, lastUpdate 2020-01-20 11:56:01 +0000
Memory Access Crash: 內存訪問crash
- 取消引用指向無效的地址空間,往只讀的空間寫入數據,訪問無效地址的指令,通常會出現
EXC_BAD_ACCESS
(SIGSEGV),或者是EXC_BAD_ACCESS
(SIGBUS)異常
text
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000000 - 壞的內存訪問有時候不會生成
EXC_BAD_ACCESS
,直接拋出一個signal
異常,例如:SIGSEGV, SEGV_MAPERR, or SEGV_NOOP
text Exception Type: SIGSEGV
Exception Codes: SEGV_MAPERR at 0x41e0af0c5ab8 內存訪問異常分析
- Address Sanitizer(gcc ... -fsanitize=address): 內存錯誤檢查,在Xcode運行面板中可以開啟此項檢查。
- 比如使用一個釋放的對象
- 數組越界,棧緩存溢出
- Undefined Behavior Sanitizer: 不安全的操作指令
- 緩沖區溢出
- 使用未初始化的變量
- 使用釋放后的變量
- 重復釋放
- 多線程數據競爭
- Thread Sanitizer: 線程安全檢查
- 記錄內存訪問信息,判斷是否有不同線程訪問修改其值
- Profile Analytics: 靜態分析檢查
- 在運行時檢查,未初始化變量,泄漏,未使用變量
- Enabling the Malloc Debugging Features: 開啟僵屍檢查模式,可以給釋放的對象發送消息,來檢測異常
- Address Sanitizer(gcc ... -fsanitize=address): 內存錯誤檢查,在Xcode運行面板中可以開啟此項檢查。
異常子類型檢查
Exception Type: EXC_BAD_ACCESS (SIGSEGV) Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000000
...
Exception Type: EXC_BAD_ACCESS (SIGBUS)
Exception Codes: KERN_MEMORY_ERROR at 0x00000001098c1000
KERN_INVALID_ADDRESS: 無效的內存地址
KERN_PROTECTION_FAILURE: 使用受保護的地址空間,不屬於當前線程的空間
KERN_MEMORY_ERROR: 訪問的地址無法提供數據,如分頁缺失
EXC_ARM_DA_ALIGN: 訪問的內存地址未對齊虛擬內存引用錯誤,應用了其它的app的vm region
Exception Type: EXC_BAD_ACCESS (SIGSEGV) Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000000
VM Region Info: 0 is not in any region. Bytes before following region: 4307009536
REGION TYPE START - END [ VSIZE] PRT/MAX SHRMOD REGION DETAIL
UNUSED SPACE AT START
--->
__TEXT 0000000100b7c000-0000000100b84000 [ 32K] r-x/r-x SM=COW ...pp/MyGreatApp- 如果
objc_msgSend, objc_retain, or objc_release
在堆棧信息的最上方,考慮開啟Zombie Objects
檢查 . - 如果
gpus_ReturnNotPermittedKillClient
出現,則說明試圖在后台使用OpenGL ES進行渲染,需要將OpenGL ES代碼移植到Metal,移植參考Migrating OpenGL Code to Metal.
- 如果
- 取消引用指向無效的地址空間,往只讀的空間寫入數據,訪問無效地址的指令,通常會出現
EXC_BREAKPOINT
- EXC_BREAKPOINT (SIGTRAP) and EXC_BAD_INSTRUCTION (SIGILL): 跟中陷阱終斷,或者不合法的指令(illegal), 模擬 __builtin_trap
EXC_CRASH (SIGABRT): 程序執行了abort()函數,如算數邏輯錯誤保護,程序啟動的必選參數強制保護.擴展初始化占用時間過長被系統執行了abort()
EXC_CRASH (SIGKILL): kill,
- 0x8badf00d(bad food,watch dog issue);
- 0xc00010ff(cool off),系統由於熱事件終止應用程序, 運行環境異常,電量使用過大造成,可以參考iOS Performance and Power Optimization with Instruments;
- 0xbaadca11(bad call),操作系統終止了應用程序,因為未能報告對PushKit通知的CallKit調用。
- 0xbad22222,系統終止了VoIP應用程序,因為它恢復得太頻繁,(提示,2重復了很多次);
- 0xc51bad01(backgroud),watchOS終止了應用程序,因為它在執行后台任務時占用了太多的CPU時間。(自身的問題)
- 0xc51bad02(backgroud), watchOS終止了應用程序,因為它未能在分配的時間內完成后台任務。(自身的問題)
- 0xc51bad03(backgroud), watchOS終止了應用程序,因為它未能在分配的時間內完成后台任務,但系統總體上非常繁忙,應用程序可能沒有收到太多CPU時間來執行后台任務。(系統的問題)
- EXC_CRASH (SIGQUIT): 進程在另一個進程的請求下終止(具備管理這個進程的生命周期)。
- EXC_GUARD: 違反了保護資源的原則。,但大多數受保護的資源崩潰都來自受保護的文件描述符,這些描述符在異常子類型字段中具有GUARD_TYPE_FD(guard type file descriptor)值。操作系統將文件描述符標記為受保護的,這樣普通的文件描述符API就無法修改它們, CLOSE,DUP(duplicate),NOCLOEXEC,SOCKET_IPC,FILEPORT,WRITE
- EXC_RESOURCE: 進程超出了資源消耗限制。CPU and CPU_FATAL,MEMORY,IO(disk task),WAKEUPS(每秒醒來次數太多,這會消耗電池壽命), such as perform(_:on:with:waitUntilDone:), async(execute:), or dispatch_async(_:_:),執行過於頻繁,GCD負荷過重,參考Modernizing Grand Central Dispatch Usage, 隊列控制,計划執行,並發數控制
- EXC_ARITHMETIC: 邏輯運算錯誤,除0或浮點運算錯誤
App內存不足被強制殺掉: 虛擬內存占用過高導致被殺掉
- app在啟動后以pageFault的方式,將物理空間上的內存以分頁的形式映射到內存中
- crash demo: 通常它不會作為crash日志搜集,在系統設置統計分析中可以找到這些日志.
text
"crashReporterKey" : "b9aa251a63bd9e743afbb906f43eb7ea5f206292",
"product" : "iPad8,2",
"incident" : "32B05E3C-CB45-40F8-BA66-5668779740E1",
"date" : "2019-10-10 23:30:39.48 -0700",
"build" : "iPhone OS 13.1.2 (17A860)",
"memoryStatus" : {
"pageSize" : 16384,
},
"largestProcess" : "OneCoolApp", //占用內存最大進程,因某種原因被系統殺掉
- 特別注意: 一個jetsam event報告包括一個數組進程,如果你的app崩潰了,但是被掛掉的進程不是你的app,就需通過其它的診斷途徑來查看問題,確認手機內是否有其它的crash日志
- reason: per-process-limit,進程超過了系統限定的程序內存駐留最大限制,超過此大小,將極大可能被系統殺掉,應用程序的擴展進程也被考慮在內,並且他們的內存占用限制更少
text
{
"uuid" : "a02fb850-9725-4051-817a-8a5dc0950872",
"states" : [
"frontmost"
],
"lifetimeMax" : 92802,
"purgeable" : 0,
"coalition" : 68,
"rpages" : 92802,
"reason" : "per-process-limit",
"name" : "MyCoolApp"
}- reason: vm-pageshortage,系統內存壓力過重,殺掉后台應用
- reason: vnode-limit,系統打開了太多的文件,內核有限制vnode數量,會放棄部分后台進程,保證前台應用存活
- reason: highwater,系統守護進程超出了最高內存占用
- reason: fc-thrashing, 當內存映射文件的非連續部分讀寫過於頻繁時,就會發生這種情況。為了避免終止最前端的應用程序,系統可能會在后台終止您的應用程序,以釋放文件緩存中的空間,即使您的應用程序沒有破壞文件緩存。
- reason: jettisoned,系統殺掉了應用程序因為一些其它的原因
- pageSize默認為16KB
- pageSize優化: 方法重排,常用方法集中到一個頁,避免頻繁切換,合理利用數據結構,減少不必要的開銷,字節對齊,進一步優化數據占用空間,
- 可以通過
vm_stat
查看進程的vm空間text Mach Virtual Memory Statistics: (page size of 4096 bytes)
Pages free: 3194.
Pages active: 34594.
Pages inactive: 17870.
Pages wired down: 9878.
"Translation faults": 6333197.
Pages copy-on-write: 81385.
Pages zero filled: 3180051.
Pages reactivated: 343961.
Pageins: 33043.
Pageouts: 78496.
Object cache: 66227 hits of 96952 lookups (68% hit rate)
Diagnosing Memory, Thread, and Crash Issues Early
UBSan check Compiler flag Misaligned Pointer -fsanitize=alignment Invalid Boolean -fsanitize=bool Out-of-Bounds Array Access -fsanitize=bounds Invalid Enumeration Value -fsanitize=enum Dynamic Type Violation -fsanitize=vptr Invalid Float Cast -fsanitize=float-cast-overflow Division by Zero -fsanitize=integer-divide-by-zero,-fsanitize=float-divide-by-zero Nonnull Argument Violation -fsanitize=nonnull-attribute, -fsanitize=nullability-arg Nonnull Return Value Violation -fsanitize=returns-nonnull-attribute, -fsanitize-nullability-return Nonnull Variable Assignment Violation -fsanitize=nullability-assign Null Reference Creation and Null Pointer Dereference -fsanitize=null Invalid Object Size -fsanitize=object-size Invalid Shift -fsanitize=shift Integer Overflow -fsanitize=signed-integer-overflow Reaching of Unreachable Point -fsanitize=unreachable Invalid Variable-Length Array -fsanitize=vla-bound
參考地址
Understanding the Exception Types in a Crash Report
iOS Performance and Power Optimization with Instruments
Adding Identifiable Symbol Names to a Crash Report
Diagnosing Memory, Thread, and Crash Issues Early
Improving Your App's Performance
Examining the Fields in a Crash Report
Viewing Virtual Memory Usage
Writing ARM64 Code for Apple Platforms
Investigating Memory Access Crashes
Apple官方文檔WatchDog處理方案
Identifying High-Memory Use with Jetsam Event Reports