查詢崩潰問題流程
拿到崩潰日志
查看崩潰線程注暗、崩潰原因
查看崩潰函數(shù)堆棧
確定崩潰調(diào)用參數(shù)
根據(jù)控制臺日志來具體分析問題
例子1:
- 拿到崩潰日志:
- 查看崩潰線程、崩潰原因
如圖墓猎,崩潰線程是線程5捆昏,崩潰類型是EXC_BREAKPOINT(SIGTRAP),下表是常見的崩潰異常,可以看到EXC_BREAKPOINT(SIGTRAP)是一種調(diào)試器相關(guān)的毙沾,跟蹤/斷點捕獲骗卜,多見于異常拋出。
UNIX 信號 | 注釋 |
---|---|
SIGSEGV | 訪問無效的內(nèi)存地址左胞。地址存在寇仓,但是應(yīng)用程序無法訪問。 |
SIGABRT | 程序崩潰烤宙。由 C函數(shù) abort() 初始化遍烦。通常意味著系統(tǒng)檢測到某些事務(wù)出錯,例如 assert() 或者 NSAssert() 校驗失敗躺枕。 |
SIGBUS | 訪問無效的內(nèi)存地址服猪。地址不存在,或?qū)R無效屯远。(The address does not exist, or the alignment is invalid.) |
SIGTRAP | 調(diào)試器相關(guān) |
SIGILL | 嘗試執(zhí)行非法的蔓姚、有缺陷、未知的或者需要權(quán)限的指令慨丐。 |
Mach 異常 | 描述 | 注釋 |
---|---|---|
EXC_BAD_ACCESS | 錯誤內(nèi)存訪問 | 訪問“錯誤”內(nèi)存地址⌒顾剑“錯誤”可能指“地址不存在”或者“應(yīng)用沒有權(quán)限訪問”房揭。因此通常與 SIGBUS 及 SIGSEGV 相關(guān)聯(lián)备闲。 |
EXC_CRASH | 異常跳出 | 通常與 SIGABRT 相關(guān)聯(lián),意思是由于檢測到代碼拋出的未捕獲異常而使應(yīng)用程序異常退出捅暴。 |
EXC_BREAKPOINT | 跟蹤/斷點捕獲 | 通用與 SIGTRAP 相關(guān)聯(lián)恬砂。可以由你自己的代碼或者 NSExceptions 拋出時觸發(fā)蓬痒。 |
EXC_GUARD | 違反了受保護資源的防護(Violated Guarded Resource Protection) | 由違背受保護資源防護觸發(fā)泻骤,例如‘某些文件描述符’。 |
EXC_BAD_INSTRUCTION | 非法指令 | 通常與特定非法或未定義指令/操作數(shù)相關(guān)梧奢。 |
EXC_RESOURCE | 資源限制 | 應(yīng)用由于達到資源消耗限制而退出狱掂。 |
00000020 | 十六進制異常類型 | 非 'OS Kernel' 異常。 |
- 查看函數(shù)堆棧
如圖所示亲轨,我們最后崩潰在libobjc.A.dylib的objc_opt_respondsToSelector+48的地方趋惨,實際上,這是objc是否響應(yīng)selector的地方惦蚊,我們可以查看objc的源碼器虾,以下選自objc4-838
// Calls [obj respondsToSelector]
BOOL
objc_opt_respondsToSelector(id obj, SEL sel)
{
#if __OBJC2__
if (slowpath(!obj)) return NO;
Class cls = obj->getIsa();
if (fastpath(!cls->hasCustomCore())) {
return class_respondsToSelector_inst(obj, sel, cls);
}
#endif
return ((BOOL(*)(id, SEL, SEL))objc_msgSend)(obj, @selector(respondsToSelector:), sel);
}
為了弄清楚究竟崩在哪一行,我們需要把它轉(zhuǎn)成匯編
注意蹦锋,我們最后走到的是+48,這并不代表我們是執(zhí)行完+48所對應(yīng)的代碼才崩潰的兆沙,恰恰是執(zhí)行上一句代碼崩潰,而上一句代碼轉(zhuǎn)成匯編后的返回地址是+48莉掂,而上一句對應(yīng)的是
if (slowpath(!obj)) return NO;
也就是說此時objc不存在挤悉,結(jié)合前面的DDLog打印函數(shù),我們基本可以確定我們打印的對象已經(jīng)被釋放了巫湘,但是指針還沒有清空装悲,即指針?biāo)赶虻膬?nèi)存已釋放,而指針本身的地址不為null尚氛,所以它指向了一塊不可訪問的內(nèi)存诀诊。我們回到第5行,ResetVTSession阅嘶,來確定打印的是個啥
-
確定崩潰參數(shù)属瓣,還記得我們前面說的嗎,崩潰的偏移號不是代表我們執(zhí)行完這一句才崩潰讯柔,而是上一句抡蛙,之所以顯示+112偏移地址是因為上一句執(zhí)行完畢的返回地址是這個,可以很清楚的看到匯編其實已經(jīng)給我們注釋出來了魂迄,是
"ResetVtSession = %@"
調(diào)用出現(xiàn)的問題粗截,我們轉(zhuǎn)成正常代碼image-20220801151644486.png
現(xiàn)在我們確定了引發(fā)崩潰的參數(shù) vtSession.
- 現(xiàn)在我們來具體分析一下這個函數(shù)的究竟有什么問題,其實我們都不需要具體分析自己的日志就能看出來捣炬。
問題出在這里熊昌,vtSession = NULL
绽榛,這是一句沒什么作用的代碼,反而很有迷惑性婿屹,為什么呢灭美?我們來分析一下這個方法想干什么,先強制編完剩下的幀VTCompressionSessionCompleteFrames
,相當(dāng)于快速處理完還沒處理的內(nèi)容昂利,然后VTCompressionSessionInvalidate(vtSession)
和CFRelease(vtSession)
,這兩步是銷毀session届腐,并釋放內(nèi)存,最后再把vtSession置空蜂奸,看起來perfect犁苏,但是不要忘了我們的參數(shù)vtSession是值傳遞!換句話說我們在函數(shù)內(nèi)部的vtSession只是外部調(diào)用的值拷貝窝撵,就算我們把它置為空傀顾,也不影響外部的指針不為空,下次如果有其他線程重新調(diào)進來碌奉,就會引發(fā)崩潰短曾。所以解決方案有兩種,一是改為址傳遞赐劣,改為
void MHH264VideoSource::ResetVtSession(VTCompressionSessionRef& vtSession)
二是仍然是值傳遞嫉拐,不過外面手動把調(diào)用指針置空
// before:
ResetVtSession(this->m_portrait_vtSession);
ResetVtSession(this->m_landscape_vtSession);
// after:
ResetVtSession(this->m_portrait_vtSession);
this->m_portrait_vtSession = nullptr;
ResetVtSession(this->m_landscape_vtSession);
this->m_landscape_vtSession = nullptr;
總結(jié)
我們先通過崩潰日志確定崩潰類型和崩潰原因,然后根據(jù)崩潰堆棧來具體鎖定誘發(fā)崩潰的原因魁兼,然后再回到SDK層去查看具體引發(fā)崩潰的代碼和變量婉徘,最后我們再根據(jù)自己的代碼來具體排查為什么會這樣。
后話:雖然后來復(fù)盤的時候第5步我并沒有寫根據(jù)日志來排查咐汞,那是因為最后看了一圈日志最后又查回來到這個函數(shù)盖呼,發(fā)現(xiàn)是這里的問題,我一開始其實沒看出來這里的問題化撕,復(fù)盤的時候想寫簡單點几晤,畢竟業(yè)務(wù)上的設(shè)計各家各有不同,但是最基本的程序bug卻是類似的植阴。
后記
后面再分享一下我排查的具體操作吧蟹瘾,總體繞了一圈彎路
首先查看調(diào)用ResetVtSession的地方是ResetVtSession(this->m_landscape_vtSession)時崩潰,表明橫屏編碼器不存在掠手,但是豎屏編碼器能正常釋放
接著查看調(diào)用釋放的地方是verifyProcess(),這是一個嗅探機制憾朴,旨在查看當(dāng)前的碼流是否正常發(fā)送中,如果不正常喷鸽,就重新創(chuàng)建編碼器或刷新關(guān)鍵幀众雷,根據(jù)日志判斷,當(dāng)時檢測到豎屏碼流不能正常發(fā)送中,于是重新創(chuàng)建了一個豎屏的編碼器报腔,但是橫屏的沒有創(chuàng)建株搔,所以這一步可以得到一個信息:橫屏的編碼器壓根兒沒有(但是不能確定是已經(jīng)釋放了還是根本沒創(chuàng)建)
接著看上面的日志剖淀,發(fā)現(xiàn)走到了創(chuàng)建流程纯蛾,但是到加鎖創(chuàng)建的那一步,直接被return掉了纵隔,這里可以確定翻诉,vtSession并不為空,說明上一次釋放并沒有把指針置空
- 接著就回到了上面捌刮,發(fā)現(xiàn)是釋放函數(shù)的問題碰煌。那么為什么之前一直沒出過問題呢?因為之前是啟動擴展進程绅作,每次都是新創(chuàng)建一個進程芦圾,所以everything is new,上一個擴展進程反正已經(jīng)沒了俄认,沒置空也不影響个少,所以一直沒啥問題,但是這一次咱們是用主進程采集流并發(fā)送的眯杏,導(dǎo)致整一個videoSource壓根兒已經(jīng)創(chuàng)建過就不用再創(chuàng)建了夜焦,所以這里vt_Session指針沒置空就很危險了,不光會導(dǎo)致下一次創(chuàng)建的時候創(chuàng)建不了岂贩,而且一旦走到析構(gòu)就直接咖喱給給了茫经。