漫談 iOS Crash 收集框架

以下為文章正文贮勃,如果覺得有用,歡迎給她打賞苏章。

為了能夠第一時(shí)間發(fā)現(xiàn)程序問題寂嘉,應(yīng)用程序需要實(shí)現(xiàn)自己的崩潰日志收集服務(wù),成熟的開源項(xiàng)目很多枫绅,如KSCrash泉孩,plcrashreporterCrashKit等并淋。追求方便省心寓搬,對(duì)于保密性要求不高的程序來說,也可以選擇各種一條龍 Crash 統(tǒng)計(jì)產(chǎn)品县耽,如Crashlytics句喷,Hockeyapp友盟兔毙,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)。

Exception 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):

signal(SIGSEGV,signalHandler);

捕獲 Mach 異澈趾桑或者 Unix 信號(hào)都可以抓到 crash 事件,這兩種方式哪個(gè)更好呢嘹悼?優(yōu)選 Mach 異常叛甫,因?yàn)?Mach 異常處理會(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)方式

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 anEXC_CRASHmach 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 forEXC_CRASH.

另外伏伐,需要重點(diǎn)說明的是:對(duì)于應(yīng)用級(jí)異常 NSException,還需要特殊處理晕拆。

你是否見過崩潰在 main 函數(shù)的 crash 日志藐翎,但是函數(shù)棧里面沒有你的代碼:

Thread 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? ? ? ? ? ? ? 0x3a5c7fe0? abort + 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捕獲異常信息:

static void my_uncaught_exception_handler (NSException *exception) {

// 這里可以取到 NSException 信息

}

NSSetUncaughtExceptionHandler(&my_uncaught_exception_handler);

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

Application Specific Information:

*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<__NSDictionaryI 0x14554d00> 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? ? -[NSException raise] + 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)行覆蓋等等的惡意競爭蛹含,總會(huì)有人默默被坑。

1)拒絕傳遞 UncaughtExceptionHandler

如果同時(shí)有多方通過 NSSetUncaughtExceptionHandler 注冊(cè)異常處理程序塞颁,和平的作法是:后注冊(cè)者通過 NSGetUncaughtExceptionHandler 將先前別人注冊(cè)的 handler 取出并備份浦箱,在自己 handler 處理完后自覺把別人的 handler 注冊(cè)回去吸耿,規(guī)規(guī)矩矩的傳遞。不傳遞強(qiáng)行覆蓋的后果是酷窥,在其之前注冊(cè)過的日志收集服務(wù)寫出的 Crash 日志就會(huì)因?yàn)槿〔坏?NSException 而丟失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è)代碼示例:

static NSUncaughtExceptionHandler *g_vaildUncaughtExceptionHandler;

static void (*ori_NSSetUncaughtExceptionHandler)( NSUncaughtExceptionHandler * );

void my_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 率

http://bugly.qq.com/blog/?p=200

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

http://bugly.qq.com/blog/?p=308

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

http://bugly.qq.com/blog/?p=335

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

http://www.sealiesoftware.com/blog/archive/2008/09/22/objc_explain_So_you_crashed_in_objc_msgSend.html

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末畔师,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子牧牢,更是在濱河造成了極大的恐慌看锉,老刑警劉巖姿锭,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異伯铣,居然都是意外死亡呻此,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門懂傀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來趾诗,“玉大人,你說我怎么就攤上這事蹬蚁∈牙幔” “怎么了?”我有些...
    開封第一講書人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵犀斋,是天一觀的道長贝乎。 經(jīng)常有香客問我,道長叽粹,這世上最難降的妖魔是什么览效? 我笑而不...
    開封第一講書人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮虫几,結(jié)果婚禮上锤灿,老公的妹妹穿的比我還像新娘。我一直安慰自己辆脸,他們只是感情好但校,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著啡氢,像睡著了一般状囱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上倘是,一...
    開封第一講書人閱讀 52,156評(píng)論 1 308
  • 那天亭枷,我揣著相機(jī)與錄音,去河邊找鬼搀崭。 笑死叨粘,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的门坷。 我是一名探鬼主播宣鄙,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼默蚌!你這毒婦竟也來了冻晤?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤绸吸,失蹤者是張志新(化名)和其女友劉穎鼻弧,沒想到半個(gè)月后设江,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡攘轩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年叉存,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片度帮。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡歼捏,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出笨篷,到底是詐尸還是另有隱情瞳秽,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布率翅,位于F島的核電站练俐,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏冕臭。R本人自食惡果不足惜腺晾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望辜贵。 院中可真熱鬧悯蝉,春花似錦、人聲如沸托慨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽榴芳。三九已至,卻和暖如春跺撼,著一層夾襖步出監(jiān)牢的瞬間窟感,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來泰國打工歉井, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留柿祈,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓哩至,卻偏偏與公主長得像躏嚎,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子菩貌,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359

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

  • 轉(zhuǎn)載(漫談 iOS Crash 收集框架) 前言 很早以前就和念茜認(rèn)識(shí)卢佣,念茜不但技術(shù)功底扎實(shí),而且長得很漂亮箭阶,說她...
    狂風(fēng)無跡閱讀 3,320評(píng)論 1 11
  • 比較好的轉(zhuǎn)載:http://www.cocoachina.com/ios/20151218/14748.html轉(zhuǎn)...
    liudhkk閱讀 931評(píng)論 0 2
  • 來源:程序媛念茜的博客 Crash日志收集 為了能夠第一時(shí)間發(fā)現(xiàn)程序問題虚茶,應(yīng)用程序需要實(shí)現(xiàn)自己的崩潰日志收集服務(wù)戈鲁,...
    幸福的魚閱讀 1,170評(píng)論 0 2
  • 本文就捕獲iOS Crash、Crash日志組成嘹叫、Crash日志符號(hào)化婆殿、異常信息解讀、常見的Crash五部分介紹罩扇。...
    xukuangbo_閱讀 1,587評(píng)論 0 0
  • 自我介紹模版 【編號(hào) 】007-3639 【姓名】 凌英 【城市】 東莞 【職業(yè)】家居空調(diào)營銷 【電話】 1382...
    神的孩子win閱讀 358評(píng)論 1 3