漫談iOS Crash收集框架 (轉(zhuǎn)載)

來源:程序媛念茜的博客?

Crash日志收集

為了能夠第一時(shí)間發(fā)現(xiàn)程序問題,應(yīng)用程序需要實(shí)現(xiàn)自己的崩潰日志收集服務(wù),成熟的開源項(xiàng)目很多矾飞,如KSCrash坐梯,plcrashreporterCrashKit等晓猛。追求方便省心,對(duì)于保密性要求不高的程序來說,也可以選擇各種一條龍Crash統(tǒng)計(jì)產(chǎn)品轰枝,如CrashlyticsHockeyapp组去,友盟鞍陨,Bugly等等。

是否集成越多的Crash日志收集服務(wù)就越保險(xiǎn)从隆?

自己收集的Crash日志和系統(tǒng)生成的Crash日志有分歧诚撵,應(yīng)該相信誰缭裆?

為什么有大量Crash日志顯示崩在main函數(shù)里,但函數(shù)棧中卻沒有一行自己的代碼?

野指針類的Crash難定位寿烟,有何妙招來應(yīng)對(duì)澈驼?

想解釋清這些問題,必須從Mach異常說起

Mach異常與Unix信號(hào)

iOS系統(tǒng)自帶的 Apple’s Crash Reporter 記錄在設(shè)備中的Crash日志筛武,Exception Type項(xiàng)通常會(huì)包含兩個(gè)元素: Mach異常 和 Unix信號(hào)盅藻。

1

2Exception?Type:?????????EXC_BAD_ACCESS?(SIGSEGV)

Exception?Subtype:??????KERN_INVALID_ADDRESS?at?0x041a6f3

Mach異常是什么?它又是如何與Unix信號(hào)建立聯(lián)系的畅铭? Mach是一個(gè)XNU的微內(nèi)核核心氏淑,Mach異常是指最底層的內(nèi)核級(jí)異常,被定義在下 硕噩。每個(gè)thread假残,task,host都有一個(gè)異常端口數(shù)組炉擅,Mach的部分API暴露給了用戶態(tài)辉懒,用戶態(tài)的開發(fā)者可以直接通過Mach API設(shè)置thread,task谍失,host的異常端口眶俩,來捕獲Mach異常,抓取Crash事件快鱼。

所有Mach異常都在host層被ux_exception轉(zhuǎn)換為相應(yīng)的Unix信號(hào)颠印,并通過threadsignal將信號(hào)投遞到出錯(cuò)的線程。iOS中的 POSIX API 就是通過 Mach 之上的 BSD 層實(shí)現(xiàn)的抹竹。

因此线罕,EXC_BAD_ACCESS (SIGSEGV)表示的意思是:Mach層的EXC_BAD_ACCESS異常,在host層被轉(zhuǎn)換成SIGSEGV信號(hào)投遞到出錯(cuò)的線程窃判。既然最終以信號(hào)的方式投遞到出錯(cuò)的線程钞楼,那么就可以通過注冊(cè)signalHandler來捕獲信號(hào):

1

signal(SIGSEGV,signalHandler);

捕獲Mach異常或者Unix信號(hào)都可以抓到crash事件袄琳,這兩種方式哪個(gè)更好呢询件? 優(yōu)選Mach異常,因?yàn)镸ach異常處理會(huì)先于Unix信號(hào)處理發(fā)生唆樊,如果Mach異常的handler讓程序exit了宛琅,那么Unix信號(hào)就永遠(yuǎn)不會(huì)到達(dá)這個(gè)進(jìn)程了。轉(zhuǎn)換Unix信號(hào)是為了兼容更為流行的POSIX標(biāo)準(zhǔn)(SUS規(guī)范)窗轩,這樣不必了解Mach內(nèi)核也可以通過Unix信號(hào)的方式來兼容開發(fā)夯秃。

因?yàn)橛布a(chǎn)生的信號(hào)(通過CPU陷阱)被Mach層捕獲,然后才轉(zhuǎn)換為對(duì)應(yīng)的Unix信號(hào)痢艺;蘋果為了統(tǒng)一機(jī)制仓洼,于是操作系統(tǒng)和用戶產(chǎn)生的信號(hào)(通過調(diào)用kill和pthread_kill)也首先沉下來被轉(zhuǎn)換為Mach異常,再轉(zhuǎn)換為Unix信號(hào)堤舒。

Crash收集的實(shí)現(xiàn)思路

正如上述所說色建,可以通過捕獲Mach異常、或Unix信號(hào)兩種方式來抓取crash事件舌缤,于是總結(jié)起來實(shí)現(xiàn)方案就一共有3種箕戳。

1)Mach異常方式

2)Unix信號(hào)方式

1

signal(SIGSEGV,signalHandler);

3)Mach異常+Unix信號(hào)方式

Github上多數(shù)開源項(xiàng)目都采用的這種方式,即使在優(yōu)選捕獲Mach異常的情況下国撵,也放棄捕獲EXC_CRASH異常陵吸,而選擇捕獲與之對(duì)應(yīng)的SIGABRT信號(hào)。著名開源項(xiàng)目plcrashreporter在代碼注釋中給出了詳細(xì)的解釋:

We still need to use signal handlers to catch SIGABRT in-process. The kernel sends an EXC_CRASH mach exception to denote SIGABRT termination. In that case, catching the Mach exception in-process leads to process deadlock in an uninterruptable wait. Thus, we fall back on BSD signal handlers for SIGABRT, and do not register for EXC_CRASH.

另外介牙,需要重點(diǎn)說明的是:對(duì)于應(yīng)用級(jí)異常NSException壮虫,還需要特殊處理。 你是否見過崩潰在main函數(shù)的crash日志环础,但是函數(shù)棧里面沒有你的代碼:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28Thread?0?Crashed:

0???????libsystem_kernel.dylib??????????0x3a61757c???__semwait_signal_nocancel?+?0x18

1???????libsystem_c.dylib???????????????0x3a592a7c???nanosleep$NOCANCEL?+?0xa0

2???????libsystem_c.dylib???????????????0x3a5adede???usleep$NOCANCEL?+?0x2e

3???????libsystem_c.dylib???????????????0x3a5c7fe0abort+?0x50

4???????libc++abi.dylib?????????????????0x398f6cd2???abort_message?+?0x46

5???????libc++abi.dylib?????????????????0x3990f6e0???default_terminate_handler()?+?0xf8

6???????libobjc.A.dylib?????????????????0x3a054f62???_objc_terminate()?+?0xbe

7???????libc++abi.dylib?????????????????0x3990d1c4???std::__terminate(void(*)())?+?0x4c

8???????libc++abi.dylib?????????????????0x3990cd28???__cxa_rethrow?+?0x60

9???????libobjc.A.dylib?????????????????0x3a054e12???objc_exception_rethrow?+?0x26

10??????CoreFoundation??????????????????0x2f7d7f30???CFRunLoopRunSpecific?+?0x27c

11??????CoreFoundation??????????????????0x2f7d7c9e???CFRunLoopRunInMode?+?0x66

12??????GraphicsServices????????????????0x346dd65e???GSEventRunModal?+?0x86

13??????UIKit???????????????????????????0x32124148???UIApplicationMain?+?0x46c

14??????XXXXXX??????????????????????????0x0003b1f2???main?+?0x1f2

15??????libdyld.dylib???????????????????0x3a561ab4???start?+?0x0

可以看出是因?yàn)槟硞€(gè)NSException導(dǎo)致程序Crash的囚似,只有拿到這個(gè)NSException,獲取它的reason线得,name饶唤,callStackSymbols信息才能確定出問題的程序位置。

/*?NSException?Class?Reference?*/

@property(readonly,?copy)?NSString?*name;

@property(readonly,?copy)?NSString?*reason;

@property(readonly,?copy)?NSArray?*callStackSymbols;

@property(readonly,?copy)?NSArray?*callStackReturnAddresses;

方法很簡單贯钩,可通過注冊(cè)NSUncaughtExceptionHandler捕獲異常信息:

staticvoidmy_uncaught_exception_handler?(NSException?*exception)?{

//這里可以取到?NSException?信息

}

NSSetUncaughtExceptionHandler(&my_uncaught_exception_handler);

將拿到的NSException細(xì)節(jié)寫入Crash日志募狂,精準(zhǔn)的定位出錯(cuò)程序位置:

1

2

3

4

5

6

7

8

9

10Application?Specific?Information:

***?Terminating?app?due?to?uncaught?exception'NSUnknownKeyException',?reason:'[?setValue:forUndefinedKey:]:?this?class?is?not?key?value?coding-compliant?for?the?key?key.'

Last?Exception?Backtrace:

0?CoreFoundation?0x2f8a3f7e?????__exceptionPreprocess?+?0x7e

1?libobjc.A.dylib?0x3a054cc?????objc_exception_throw?+?0x22

2?CoreFoundation?0x2f8a3c94?????-[NSExceptionraise]?+?0x4

3?Foundation?0x301e8f1e?????????-[NSObject(NSKeyValueCoding)?setValue:forKey:]?+?0xc6

4?DemoCrash?0x00085306??????????-[ViewController?crashMethod]?+?0x6e

5?DemoCrash?0x00084ecc??????????main?+?0x1cc

6?DemoCrash?0x00084cf8??????????start?+?0x24

那么,是不是收到了大量crash在main函數(shù)卻沒有NSException信息的日志角雷,就代表自己集成的Crash日志收集服務(wù)沒有注冊(cè)NSUncaughtExceptionHandler呢熬尺?不一定,還有另外一種可能谓罗,就是被同時(shí)存在的其他Crash日志收集服務(wù)給坑了粱哼。

多個(gè)Crash日志收集服務(wù)共存的坑

是的,在自己的程序里集成多個(gè)Crash日志收集服務(wù)實(shí)在不是明智之舉檩咱。通常情況下揭措,第三方功能性SDK都會(huì)集成一個(gè)Crash收集服務(wù),以及時(shí)發(fā)現(xiàn)自己SDK的問題刻蚯。當(dāng)各家的服務(wù)都以保證自己的Crash統(tǒng)計(jì)正確完整為目的時(shí)绊含,難免出現(xiàn)時(shí)序手腳,強(qiáng)行覆蓋等等的惡意競(jìng)爭(zhēng)炊汹,總會(huì)有人默默被坑躬充。

1)拒絕傳遞 UncaughtExceptionHandler

如果同時(shí)有多方通過NSSetUncaughtExceptionHandler注冊(cè)異常處理程序,和平的作法是:后注冊(cè)者通過NSGetUncaughtExceptionHandler將先前別人注冊(cè)的handler取出并備份,在自己handler處理完后自覺把別人的handler注冊(cè)回去充甚,規(guī)規(guī)矩矩的傳遞以政。不傳遞強(qiáng)行覆蓋的后果是,在其之前注冊(cè)過的日志收集服務(wù)寫出的Crash日志就會(huì)因?yàn)槿〔坏絅SException而丟失Last Exception Backtrace等信息伴找。(P.S. iOS系統(tǒng)自帶的Crash Reporter不受影響)

在開發(fā)測(cè)試階段盈蛮,可以利用fishhook框架去hookNSSetUncaughtExceptionHandler方法,這樣就可以清晰的看到handler的傳遞流程斷在哪里技矮,快速定位污染環(huán)境者抖誉。不推薦利用調(diào)試器添加符號(hào)斷點(diǎn)來檢查,原因是一些Crash收集框架在調(diào)試狀態(tài)下是不工作的衰倦。

檢測(cè)代碼示例:

1

2

3

4

5

6

7

8

9

10

11

12

13staticNSUncaughtExceptionHandler?*g_vaildUncaughtExceptionHandler;

staticvoid(*ori_NSSetUncaughtExceptionHandler)(?NSUncaughtExceptionHandler?*?);

voidmy_NSSetUncaughtExceptionHandler(?NSUncaughtExceptionHandler?*?handler)

{

g_vaildUncaughtExceptionHandler?=?NSGetUncaughtExceptionHandler();

if(g_vaildUncaughtExceptionHandler?!=?NULL)?{

NSLog(@"UncaughtExceptionHandler=%p",g_vaildUncaughtExceptionHandler);

}

ori_NSSetUncaughtExceptionHandler(handler);

NSLog(@"%@",[NSThread?callStackSymbols]);

g_vaildUncaughtExceptionHandler?=?NSGetUncaughtExceptionHandler();

NSLog(@"UncaughtExceptionHandler=%p",g_vaildUncaughtExceptionHandler);

}

對(duì)于越獄插件注入應(yīng)用進(jìn)程內(nèi)部袒炉,惡意覆蓋NSSetUncaughtExceptionHandler的情況,應(yīng)用程序本身處理起來比較弱勢(shì)樊零,因?yàn)樵姜z環(huán)境下操作時(shí)序的玩法比較多權(quán)利比較大我磁。

2)Mach異常端口換出+信號(hào)處理Handler覆蓋

和NSSetUncaughtExceptionHandler的情況類似,設(shè)置過的Mach異常端口和信號(hào)處理程序也有可能被干掉淹接,導(dǎo)致無法捕獲Crash事件十性。

3)影響系統(tǒng)崩潰日志準(zhǔn)確性

應(yīng)用層參與收集Crash日志的服務(wù)方越多,越有可能影響iOS系統(tǒng)自帶的Crash Reporter塑悼。由于進(jìn)程內(nèi)線程數(shù)組的變動(dòng)劲适,可能會(huì)導(dǎo)致系統(tǒng)日志中線程的Crashed 標(biāo)簽標(biāo)記錯(cuò)位,可以搜索abort()等關(guān)鍵字來復(fù)查系統(tǒng)日志的準(zhǔn)確性厢蒜。 若程序因NSException而Crash霞势,系統(tǒng)日志中的Last Exception Backtrace信息是完整準(zhǔn)確的,不會(huì)受應(yīng)用層的胡來而影響斑鸦,可作為排查問題的參考線索愕贡。

ObjC野指針類的Crash

收集Crash日志這個(gè)步驟沒有問題的情況下,還是有很多全系統(tǒng)棧的日志的情況巷屿,沒有自己一行代碼固以,分析起來十分棘手,ObjC野指針類的Crash正是如此嘱巾,這里推薦幾篇好文章:

如何定位Obj-C野指針隨機(jī)Crash(一):先提高野指針Crash率

如何定位Obj-C野指針隨機(jī)Crash(二):讓非必現(xiàn)Crash變成必現(xiàn)

如何定位Obj-C野指針隨機(jī)Crash(三):加點(diǎn)黑科技讓Crash自報(bào)家門

分析objc_msgSend()處崩潰的小技巧

除此之外憨琳,在Crash日志中補(bǔ)充記錄一些額外信息可以輔助定位,如切面標(biāo)記線程出處旬昭、隊(duì)列出處篙螟,記錄用戶操作軌跡等等……

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市问拘,隨后出現(xiàn)的幾起案子遍略,更是在濱河造成了極大的恐慌惧所,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,252評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绪杏,死亡現(xiàn)場(chǎng)離奇詭異下愈,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)寞忿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門驰唬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來顶岸,“玉大人腔彰,你說我怎么就攤上這事∠接叮” “怎么了霹抛?”我有些...
    開封第一講書人閱讀 168,814評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長卷谈。 經(jīng)常有香客問我杯拐,道長,這世上最難降的妖魔是什么世蔗? 我笑而不...
    開封第一講書人閱讀 59,869評(píng)論 1 299
  • 正文 為了忘掉前任端逼,我火速辦了婚禮,結(jié)果婚禮上污淋,老公的妹妹穿的比我還像新娘顶滩。我一直安慰自己,他們只是感情好寸爆,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評(píng)論 6 398
  • 文/花漫 我一把揭開白布礁鲁。 她就那樣靜靜地躺著,像睡著了一般赁豆。 火紅的嫁衣襯著肌膚如雪仅醇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,475評(píng)論 1 312
  • 那天魔种,我揣著相機(jī)與錄音析二,去河邊找鬼。 笑死节预,一個(gè)胖子當(dāng)著我的面吹牛叶摄,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播心铃,決...
    沈念sama閱讀 41,010評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼准谚,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了去扣?” 一聲冷哼從身側(cè)響起柱衔,我...
    開封第一講書人閱讀 39,924評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤樊破,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后唆铐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體哲戚,經(jīng)...
    沈念sama閱讀 46,469評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評(píng)論 3 342
  • 正文 我和宋清朗相戀三年艾岂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了顺少。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,680評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡王浴,死狀恐怖脆炎,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情氓辣,我是刑警寧澤秒裕,帶...
    沈念sama閱讀 36,362評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站钞啸,受9級(jí)特大地震影響几蜻,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜体斩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評(píng)論 3 335
  • 文/蒙蒙 一梭稚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧絮吵,春花似錦弧烤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至粱栖,卻和暖如春话浇,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背闹究。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評(píng)論 1 274
  • 我被黑心中介騙來泰國打工幔崖, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人渣淤。 一個(gè)月前我還...
    沈念sama閱讀 49,099評(píng)論 3 378
  • 正文 我出身青樓赏寇,卻偏偏與公主長得像,于是被迫代替她去往敵國和親价认。 傳聞我的和親對(duì)象是個(gè)殘疾皇子嗅定,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)載(漫談 iOS Crash 收集框架) 前言 很早以前就和念茜認(rèn)識(shí),念茜不但技術(shù)功底扎實(shí)用踩,而且長得很漂亮渠退,說她...
    狂風(fēng)無跡閱讀 3,331評(píng)論 1 11
  • 以下為文章正文忙迁,如果覺得有用,歡迎給她打賞碎乃。 為了能夠第一時(shí)間發(fā)現(xiàn)程序問題姊扔,應(yīng)用程序需要實(shí)現(xiàn)自己的崩潰日志收集服務(wù)...
    赤色追風(fēng)閱讀 2,557評(píng)論 1 11
  • 比較好的轉(zhuǎn)載:http://www.cocoachina.com/ios/20151218/14748.html轉(zhuǎn)...
    liudhkk閱讀 931評(píng)論 0 2
  • [這是第14篇] 序: iOS Crash問題是iOS開發(fā)中難以忽視的存在,本文就捕獲iOS Crash梅誓、Cras...
    南華coder閱讀 9,893評(píng)論 21 116
  • 本文就捕獲iOS Crash恰梢、Crash日志組成、Crash日志符號(hào)化梗掰、異常信息解讀嵌言、常見的Crash五部分介紹。...
    xukuangbo_閱讀 1,587評(píng)論 0 0