<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          iOS Crash防護(hù)你看這個(gè)就夠了(下)

          共 4208字,需瀏覽 9分鐘

           ·

          2022-10-25 05:12

          ????關(guān)注后回復(fù) “進(jìn)群” ,拉你進(jìn)程序員交流群????

          轉(zhuǎn)自:掘金  茉莉兒

          https://juejin.cn/post/6959015601536761893

          0x1: Previously

          [上篇]中講到了Crash處理流程分為四個(gè)環(huán)節(jié),也分析了Crash防護(hù)的方法,本章來講下其余三個(gè)環(huán)節(jié)。

          0x2: Crash的攔截

          所有的未被防護(hù)住的Crash最終會(huì)走到這里,在這里我們必須要保證攔截的 全面性穩(wěn)定性盡可能多的攔截到所有類型的異常,同時(shí)攔截邏輯本身不能產(chǎn)生異常。那么我們需要通過以下幾個(gè)方面去考慮。

          I: Crash類型

          和多數(shù)操作系統(tǒng)一樣,iOS的異常也基本分為 用戶層 系統(tǒng)底層 信號 這三個(gè)類別,接下來我們看下每種異常都做了哪些事情

          • Mach Exception
            Mach異常,分為兩種情況,第一種是本身就是硬件層面或者系統(tǒng)層面的異常,這個(gè)大家比較好理解,因?yàn)閙ach是微內(nèi)核,所以底層的內(nèi)核級別的包括硬件的異常都是mach異常。另一種是iOS系統(tǒng)獨(dú)有的邏輯或者說是蘋果獨(dú)有的。就是用戶層面的異常也都首先下沉到mach層再發(fā)出來,也等于是另一種意義上的mach異常。蘋果官方文檔上寫的是為了統(tǒng)一機(jī)制才做了這樣的處理,但是沒有說具體原因.他的觸發(fā)流程大概為下圖

            然后我去看Runtime的源碼進(jìn)一步證明了這個(gè)說法,runloop中大量使用這種方式監(jiān)聽mach異常消息,一旦Crash隨時(shí)準(zhǔn)備打破循環(huán),因?yàn)橄到y(tǒng)也需要監(jiān)聽crash,統(tǒng)一出口將對監(jiān)聽來說對系統(tǒng)將變得非常方便。

            根據(jù)代碼上下文可以判斷出,蘋果會(huì)監(jiān)聽統(tǒng)一的異常端口,在出現(xiàn)異常后進(jìn)行相應(yīng)的操作,也印證了我當(dāng)時(shí)的推斷。

          • Exception
            很常見的異常,觸發(fā)流程大概為

          • signal

            signal的產(chǎn)生流程大概分為幾種情況

            但是需要注意一點(diǎn):收到signal不一定會(huì)Crash,但是Crash一定會(huì)有Signal發(fā)出

            • 由于 MachExcption 轉(zhuǎn)換而成的signal

            • 由于Exception而發(fā)出的abort信號

            • 用戶自定義的信號

          II: Crash傳遞流程

          上面分析了每種Crash的類型,那么這三種類型的Crash是如何在App生命周期中傳遞的呢?他們又是如何相互轉(zhuǎn)化以及相互之間有什么關(guān)系呢?

          幫大家提取下上圖中的幾個(gè)關(guān)鍵信息

          • 1:Exception 最終會(huì)轉(zhuǎn)化為Mach Exception

          • 2:通過Mach端口攔截的較為全面

          • 3:如果發(fā)生了exception那么就不會(huì)拋出對應(yīng)的signal只能拋出abort();

          • 4:通過捕獲signal是無法攔截到exception。

          III: 攔截的選擇

          通過上面的分析大家一定會(huì)說通過Mach端口的攔截更加全面,畢竟蘋果自己也在用。但是在實(shí)際使用中有一個(gè)問題,mach會(huì)攔截所有的異常以及信號量,也就是隨便一個(gè)操作(比如發(fā)一個(gè)自定義signal等)可能都被mach捕獲,那么如果在其捕獲回調(diào)中再進(jìn)行捕獲就會(huì)很容易發(fā)生死鎖,而且容易和系統(tǒng)的處理產(chǎn)生沖突。當(dāng)時(shí)看了PLCrash的文檔,也看到了開發(fā)者寫的一句話:

          這樣說明了大家確實(shí)被坑過。

          那接下來只剩signal 和 exception,其實(shí)細(xì)心的同學(xué)早已發(fā)現(xiàn)這兩個(gè)的優(yōu)缺點(diǎn)是一個(gè)互補(bǔ)的狀態(tài)

          • singal能捕獲除Exception之外的所有異常。

          • exception只能獲取應(yīng)用層的異常而對信號量無法處理

          那么最終的方式采用 singnal + exception的方式進(jìn)行捕獲,最終的流程為:

          IV: 坑點(diǎn)

          上面的流程圖可以看出在每一個(gè)CustomHandle之前都會(huì)有一個(gè)PreviousHandle,其實(shí)是因?yàn)樵趇OS系統(tǒng)中只能存在一個(gè)customHandel,如果你的項(xiàng)目中接入了或者準(zhǔn)備接入多個(gè) Crash 防護(hù)相關(guān)的SDK(雖然不建議這么做),那么多個(gè)Handle之間一定會(huì)產(chǎn)生沖突,導(dǎo)致堆棧不明確,或者丟失。所以在注冊我們的handle前先將之前的handle指針保存下來,等我們的handle處理完后在通過函數(shù)指針調(diào)用回去,這樣就能保證每一個(gè)handle都能被正常調(diào)用。

          • exception:通過NSGetUncaughtExceptionHandler獲得之前handle指針,之后再通過NSSetUncaughtExceptionHandler(oldHandler);調(diào)用回去。

          • signal: 使用sigaction函數(shù)獲得之前的handle指針。

          0x3: 堆棧獲取

          因?yàn)樘O果使用了(Address Space Layout Randomization ) 地址空間配置隨機(jī)加載技術(shù),所以線上堆棧必須要通過符號表堆棧還原進(jìn)行解讀,不然的話就是內(nèi)存地址。所以當(dāng)我們使用NSThread的相關(guān)函數(shù)在Debug下雖然能看到可讀性行的堆棧,但是在線上包上并不可取,那我們要怎么獲取堆棧呢?先來看下符號表的構(gòu)造:

          之前拿到這樣的符號表,我們通常手動(dòng)還原,找一個(gè)相同系統(tǒng)的真機(jī),找到對應(yīng)庫的基地址按照符號表上函數(shù)的偏移量進(jìn)行計(jì)算(通過LLDB的相關(guān)函數(shù))

          通過看Mach-o相關(guān)接口可以找到相關(guān)函數(shù)進(jìn)行端內(nèi)符號表還原,大致流程為:

          • 獲取函數(shù)地址:

            • 遍歷Mach-o中的所有image

            • 獲取每個(gè)image的基地址

            • 通過堆棧偏移地址獲取棧幀函數(shù)地址

          • 將函數(shù)地址翻譯成函數(shù)名

            • 找到對應(yīng)Image的symple table段的nlist_64結(jié)構(gòu)體

            • 通過nlist_64.n_un.n_strx獲取函數(shù)對應(yīng)的字符串

          最終的效果:

          0x4: Crash后續(xù)

          通常在AppCrash后會(huì)在handle中做些上報(bào)操作.

          但是這樣做有兩個(gè)問題:

          • 蘋果不推薦在Handle中做太多操作,而且數(shù)據(jù)上報(bào)等網(wǎng)絡(luò)請求屬于耗時(shí)操作,有可能沒有完成App就被殺死。

          • App直接閃退,體驗(yàn)不好

          通過查看runloop源碼可以看出,在Crash發(fā)生后當(dāng)前runloop中斷

          注意:runloop本次循環(huán)還在繼續(xù),但是循環(huán)已經(jīng)被打破,本次循環(huán)結(jié)束后app才退出 既下圖的retVal被置為NO

          iOS Crash發(fā)生后 runloop中的do-while循環(huán)的條件會(huì)被置為 NO,然后Handler函數(shù)走完之后當(dāng)前循環(huán)后直接結(jié)束,不會(huì)在進(jìn)行下一次循環(huán)了,此時(shí)我們只需要再handler中再重啟runloop,便可以繼續(xù)執(zhí)行代碼,通過觀察runloop源碼可以看出 這樣的操作是在之前已經(jīng)中斷但是還沒結(jié)束的runloop中開啟一個(gè)新的runloop,他依然可以接受各種事件,比如交互事件等,前提是每個(gè)model都要開啟,因?yàn)椴煌僮魇前l(fā)生在不同階段的。但是之前runloop中的內(nèi)容處于不可控狀態(tài),且之前的東西被永遠(yuǎn)的留在內(nèi)存中,不可恢復(fù),所以在做完相關(guān)操作后要立即結(jié)束App,避免其他異常情況,這種做法類似于一種安全模式,在安全模式中處理相關(guān)的東西。

          函數(shù)調(diào)用:

          void continueAfterCrash()
          {
              CFRunLoopRef runLoop = CFRunLoopGetCurrent();
              CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);

              for (NSString *mode in (__bridge NSArray *)allModes)
              {
                  CFRunLoopRunInMode((CFStringRef)mode, 1.0e10, false);
              }
          }

          在新的runloop中我們做一些操作后再調(diào)用abort退出App,比如彈出友好提示之類的操作,告知用戶app即將退出,但是該操作存在風(fēng)險(xiǎn),需要注意以下情況

          • 新開runloop后之前的runloop內(nèi)容便會(huì)永遠(yuǎn)的留在內(nèi)存中變成不可控的狀態(tài)如果一旦被訪問可能會(huì)有異常,所以在做完我們必要的操作后要及時(shí)結(jié)束App。

          • 安全模式必須保證穩(wěn)定,在新runloop中執(zhí)行的上報(bào)、彈窗或者其他邏輯必須要使用系統(tǒng)原生的API,不能依賴任何第三方。

          • 盡量不要做太多的操作,及時(shí)結(jié)束。

          0x5: 參考資料

          • Apple iOS Api (https://developer.apple.com/documentation)

          • iOS Open Sourcre (https://opensource.apple.com/)

          • CFRunloop (https://opensource.apple.com/source/CF/CF-1151.16/)

          • XNU 3248.60.10源碼 (http://opensource.apple.com/tarballs/xnu/xnu-3248.60.10.tar.gz)

          • Understanding Crash Reports on iPhone OS (https://developer.apple.com/documentation/xcode/diagnosing_issues_using_crash_reports_and_device_logs/analyzing_a_crash_report)

          • 《深入解析 MAC OS X & IOS 操作系統(tǒng)》

          0x6: 最后

          大概這就是所有Crash防護(hù)的流程,通過兩篇文章講解,希望大家對iOS系統(tǒng)的Crash流程能有些許的了解,并沒有貼太多的源碼,其實(shí)還是解耦度不夠,思路有了代碼就很簡單了。文章只是個(gè)人簡介,可能存在不正確的地方,如果大家有什么不同的觀點(diǎn)歡迎留言討論。

          -End-

          最近有一些小伙伴,讓我?guī)兔φ乙恍?nbsp;面試題 資料,于是我翻遍了收藏的 5T 資料后,匯總整理出來,可以說是程序員面試必備!所有資料都整理到網(wǎng)盤了,歡迎下載!

          點(diǎn)擊??卡片,關(guān)注后回復(fù)【面試題】即可獲取

          在看點(diǎn)這里好文分享給更多人↓↓

          瀏覽 47
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  大鸡巴91| 黄色操逼国产 | 美女黄页视频 | 黄色成人三级 | 中文字字幕在线中文 |