原文鏈接:Understanding iOS Exception Types (PS.由於未知原因已失效,經檢查,文章中引用的鏈接都還有效 :)
翻譯:CoderWangx
當你的iOS應用崩潰的時候,我們需要去分析異常日志以定位根本原因。崩潰可能是 “低內存崩潰 Low Memory Crash” 或者 “普通異常崩潰”。當碰到“異常”時,更好的理解“不同類型的異常”能夠真正幫助我們快速定位問題所在。
在這篇文章中,我們將研究 iOS 應用可能碰到的不同類型的“異常”,例如EXC_CRASH、EXC_BAD_ACCESS、EXC_RESOURCE、00000020 等。
崩潰日志中的“異常”
“異常”這個詞在“崩潰日志”語境下更多與“Mach 異常”(以“EXC_為前綴”)和 “UNIX 信號”(如: SIGSEGV, SIGBUS等)相關。在某些情況下(應該是有對應的dSYM符號文件時)系統會通過映射將底層的 Mach 異常 翻譯為 UNIX 信號。這就是為什么你能log中看到有用 “EXC_CRASH(SIGABRT)” 及 “EXC_BAC_ACCESS(SIGSEGV)” 作為 異常類型(Exception Type)
。
對於某些異常,還會附帶一個關聯的 處理器定制異常碼(processor-specific Exception Code) 或者 異常子類型(Exception Subtype),用以包含更多問題相關信息。舉例來說, “EXC_BAC_ACCESS” 類型異常可能有一行如“KERN_INVALID_ADDRESS at 0x80000010”作為“異常碼”; “EXC_RESOURCE” 可能有一行"WAKEUPS"作為"異常子類別"。
UNIX 信號
iOS開發者常見的 UNIX 信號 如下:
UNIX 信號 | 注釋 |
---|---|
SIGSEGV | 訪問無效的內存地址。地址存在,但是應用程序無法訪問。 |
SIGABRT | 程序崩潰。由 C函數 abort() 初始化。通常意味着系統檢測到某些事務出錯,例如 assert() 或者 NSAssert() 校驗失敗。 |
SIGBUS | 訪問無效的內存地址。地址不存在,或對齊無效。(The address does not exist, or the alignment is invalid.) |
SIGTRAP | 調試器相關 |
SIGILL | 嘗試執行非法的、有缺陷、未知的或者需要權限的指令。 |
更多 UNIX 信號 可以參考這里:Unix_signal。
Mach 異常
Mach 異常 | 描述 | 注釋 |
---|---|---|
EXC_BAD_ACCESS | 錯誤內存訪問 | 訪問“錯誤”內存地址。“錯誤”可能指“地址不存在”或者“應用沒有權限訪問”。因此通常與 SIGBUS 及 SIGSEGV 相關聯。 |
EXC_CRASH | 異常跳出 | 通常與 SIGABRT 相關聯,意思是由於檢測到代碼拋出的未捕獲異常而使應用程序異常退出。 |
EXC_BREAKPOINT | 跟蹤/斷點捕獲 | 通用與 SIGTRAP 相關聯。可以由你自己的代碼或者 NSExceptions 拋出時觸發。 |
EXC_GUARD | 違反了受保護資源的防護(Violated Guarded Resource Protection) | 由違背受保護資源防護觸發,例如‘某些文件描述符’。 |
EXC_BAD_INSTRUCTION | 非法指令 | 通常與特定非法或未定義指令/操作數相關。 |
EXC_RESOURCE | 資源限制 | 應用由於達到資源消耗限制而退出。 |
00000020 | 十六進制異常類型 | 非 'OS Kernel' 異常。 |
查看完整 Mach 異常列表請參考 這里(
sys/osfmk/mach/exception_types.h
)的源碼文件。
異常
EXC_BAD_ACCESS(錯誤內存訪問)
“EXC_BAD_ACCESS” 是APP崩潰時最常見的異常之一。不幸的是,調試起來卻不容易。
一般有兩種可能性:
- 訪問某些尚未初始化的對象。(SIGBUS)
- 訪問已經被 ARC 釋放(導致地址變為不可訪問)的對象。如果是這個情況,你通常可以在崩潰日志中的 “Backtrace” 頂部附近看到
objc_release
。
示例如:
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_INVALID_ADDRESS at 0x6d783f44
...
Exception Type: EXC_BAD_ACCESS (SIGBUS)
Exception Codes: KERN_PROTECTION_FAILURE at 0x00000011
復制代碼
“EXC_BAD_ACCESS”也有關聯的“異常碼”以幫助提供額外信息。舉例來說,KERN_PROTECTION_FAILURE
表示內存有效,但是不允許當前形式的訪問,KERN_INVALID_ADDRESS
意思是地址當前無效。
查看這里的源碼文件獲取完整的可能值列表。
為了輔助調試 “EXC_BAD_ACCESS” 類型異常,你可以勾選 Xcode 中的 “Enable Zombie Objects” 后再嘗試。
EXC_CRASH(異常跳出)
相較於 “EXC_BAD_ACCESS”,“EXC_CRASH" 更容易遇到。它通常發生在對象接收到未實現的消息時,如 Xcode 調試器中顯示的 “unrecognized selector sent to instance 0x6a33840”。
一般情況里這個異常會與調試器一起發揮作用,因為調試器可以中斷進程。如果沒有附加調試器,會生成一個崩潰日志。
崩潰日志中展示的信息示例:
Exception Type: EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
...
## Usually you will see a similar line in the "backtrace" part 2 CoreFoundation 0x36c02e02 -[NSObject(NSObject) doesNotRecognizeSelector:] + 166 復制代碼
可能存在某些與“unrecognized selector”無關的特殊情況。如果碰到了,請注意到處都有可能發生這種事情。
另一個常見的“EXC_CRASH”情況是關於“應用擴展(App Extensions)”。應用擴展如果“花了太長時間來初始化”則會被系統終止。在這種情況下,異常子類型(Exception Subtype)顯示為 LAUNCH_HANG
,附帶一個得體的異常消息(Exception Message):
Exception Type: EXC_CRASH (SIGABRT)
Exception Subtype: LAUNCH_HANG
Exception Message: The extension took too much time to initialize
復制代碼
EXC_BREAKPOINT(跟蹤捕獲)
與“EXC_CRASH”非常相似,EXC_BREAKPOINT 也往往與調試器一起發揮作用,在測試階段被捕獲。 當使用 Swift 時,在以下情況這個異常會在運行時拋出:
- 一個非可選類型值為nil
- 強制類型轉換失敗
示例信息如:
Exception Type: EXC_BREAKPOINT (SIGTRAP)
Exception Codes: 0x0000000000000002, 0x0000000000000000
復制代碼
你可以在代碼中手動調用
__builtin_trap()
來觸發這個異常。
EXC_GUARD(違反了受保護資源的防護)
與其他所有“EXC_”前綴的異常不同,這個異常不是一個“原生”的 Mach 異常。事實上,它是為 XNU - 一個蘋果開發的衍生操作系統內核 而添加的。
"XNU" 代表 "X 不是 Unix"(X is Not Unix)。 “EXC_GUARD”的定義可以在這里-
osfmk/mach/exception_types.h
找到。
這個異常的一個較好例子是應用程序在 Core Data 訪問 SQLite 文件時關閉了它的“文件描述符(file descriptor)”。
在 iOS7 之前,這個異常會附帶一部分“異常碼(Exception Codes)”以幫助理解情況。異常碼包含“兩個”位域代碼(如:0x400000010000005e
)及subcode(如:0x00007f8254a019c0
)。
位域代碼部分可分解為“3”個區:
- Guard Type - 這個時候只有一種類型 - 受保護的文件描述符(guarded file descriptor(GUARD_TYPE_FD))。值為
0x2
。如果你看到時 0x4 作為代碼的前綴,則這個崩潰與“文件描述符”相關。 - Flavor - 當違反“受保護的文件描述符”時的不同條件: 如果設置了“第1”(
[32]: "1 << 0"
)位(kGUARD_EXC_CLOSE),則它曾試圖在“受保護的文件描述”上調用close()
。 如果設置了“第2”([33]: "1 << 1"
)位(kGUARD_EXC_DUP),則它試圖在“受保護的文件描述符”上使用F_DUPFD
或F_DUPFD_CLOEXEC
調用dup(2)
,dup2(2)
,fcntl(2)
。還包含了嘗試使用/dev/fd/
打開“文件描述符”。 如果設置了“第3”([34]: "1 << 2"
)位(kGUARD_EXC_NOCLOEXEC),則它試圖關閉“文件描述符”上的“close-on-exec”標志。 如果設置了“第4”([35]: "1 << 3"
)位(kGUARD_EXC_SOCKET_IPC),則它試圖通過 套接字(socket)發送“受保護的文件描述符”。 如果設置了“第5”([36]: "1 << 4"
)位(GUARD_FILEPORT),則它曾試圖通過 套接字(socket)從“受保護的文件描述符”創建一個文件端口。 如果設置了“第6”([37]: "1 << 5"
)位(kGUARD_EXC_MISMATCH),說明“受保護的文件描述符”與“守衛”不相符。 如果設置了“第7”([38]: << 6
)位(kGUARD_EXC_WRITE),則它曾試圖通過 套接字(socket)寫入一個“受保護的文件描述符”。 - File Descriptor - 應用嘗試操作的受保護的文件描述符。- subcode部分包含“受保護的值”。
詳細定義可以在這里(
/bsd/sys/guarded.h
)找到。
從 iOS 7 開始,“Exception Codes”被提供更詳細解釋的“Exception Subtype”和“Exception Message”替代。
# iOS 6 Exception Type: EXC_GUARD Exception Codes: 0x400000010000005e, 0x00007f8254a019c0 # The type is "GUARD_TYPE_FD" (0x4), with "kGUARD_EXC_CLOSE". The FD is "94". # ------- # iOS 7 and above Exception Type: EXC_GUARD Exception Subtype: GUARD_TYPE_FD Exception Message: CLOSE on file descriptor 81 (guarded with 0x0000000017e6eed0) 復制代碼
EXC_BAD_INSTRUCTION(非法指令)
“EXC_BAD_INSTRUCTION”,通常與“SIGILL”關聯,是一個非常容易理解的異常 - 即你正在使用“錯誤”的指令或操作。然而,有時候也很難去調試。
以下是一些較常見的情況。 由於Xcode提供的調試信息,這個很容易識別 - 它是由於不安全的解包導致的。
## Usually show "EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)" in Xcode. “fatal error: unexpectedly found nil while unwrapping an Optional value” 復制代碼
但是,像這樣和這樣(均為StackOverflow上的問題)的就不容易了 - 第一個是有關於 GCD 的使用,另一個是蘋果的bug。 以下是崩潰日志中的顯示格式:
Exception Type: EXC_BAD_INSTRUCTION (SIGILL)
Exception Codes: 0x0000000000000001, 0x000000000000b6d2
復制代碼
EXC_RESOURCE
“EXC_RESOURCE”意思是進程“達到資源消耗上限”。通常,當你的應用在一定時間內持續超出限制時會被觸發。 這個異常包含“Exception Subtype”以幫助理解實際情況:
- CPU - 限制為
50%
,時間不超過180秒
。 - WAKEUPS - 表示線程每秒喚醒次數太多。限制為
150次/每秒
, 時間不超過300秒
。 - MEMORY - 沒有相關文檔描述限制信息。
與“EXC_GUARD”類似,它曾使用“位域”來傳遞信息,現在也使用“Exception SubType”和“Exception Message”。
Exception Type: EXC_RESOURCE
Exception Subtype: CPU
Exception Message: (Limit 50%) Observed 85% over 180 secs
---
Exception Type: EXC_RESOURCE
Exception Subtype: WAKEUPS
Exception Message: (Limit 150/sec) Observed 206/sec over 300 secs
---
Exception Type: EXC_RESOURCE
Exception Subtype: MEMORY
Exception Message: Crossed High Water Mark
復制代碼
00000020
與“EXC_”異常不同,這個“異常類型”實際上不能告訴你任何信息。取而代之,你應該查看“異常代碼”獲取更多詳情。
0x8badf00d
(讀作 ate bad food)- 表示由於 watchdog 出現超時而導致應用被操作系統終止。通常意味着應用程序花了太長時間啟動、關閉或者響應系統事件。一個非常典型的情況是“在主線程上做同步網絡請求”。0xbaaaaaad
(讀作 “plooookhy”)- 表示日志是整個系統的堆棧,而不是崩潰報告。0xc00010ff
(讀作 cool off(冷靜))- 表示應用程序被系統關閉以響應熱事件。0xbad22222
- 表示操作系統終止了一個VoIP程序,因為它過於頻繁的執行恢復。0xdead10cc
(讀作 dead lock(死鎖))- 表示應用在后台運行時保持了系統資源。0xdeadfa11
(讀作 deadfall)- 表示應用被用戶強制關閉了。強制關閉發生於用戶先按下電源鍵直到“滑動來關機”出現然后按住主屏幕按鈕。
這些“十六進制”代碼實際上是六音詞 - 由我們開發者創建作為不容易忘記的魔法數字。
擴展閱讀
你可查看這篇文章 Demystifying iOS Application Crash Logs 以了解iOS異常日志結構。
https://juejin.im/post/5d04bdf4f265da1b83338b8b