[譯]理解 iOS 異常類型


原文鏈接:Understanding iOS Exception Types (PS.由於未知原因已失效,經檢查,文章中引用的鏈接都還有效 :)

翻譯:CoderWangx

當你的iOS應用崩潰的時候,我們需要去分析異常日志以定位根本原因。崩潰可能是 “低內存崩潰 Low Memory Crash” 或者 “普通異常崩潰”。當碰到“異常”時,更好的理解“不同類型的異常”能夠真正幫助我們快速定位問題所在。

在這篇文章中,我們將研究 iOS 應用可能碰到的不同類型的“異常”,例如EXC_CRASHEXC_BAD_ACCESSEXC_RESOURCE00000020 等。

崩潰日志中的“異常”

“異常”這個詞在“崩潰日志”語境下更多與“Mach 異常”(以“EXC_為前綴”)和 “UNIX 信號”(如: SIGSEGVSIGBUS等)相關。在某些情況下(應該是有對應的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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM