在叫用 linphone 的函式庫時,發生 exc_bad_access
的錯誤,因為這個錯誤往往發生的地方跟掛掉的地方是不一樣的,所以比較難除錯。
EXC_BAD_ACCESS
是因為對已經釋放的記憶體進行了非法操作,程式會直接 Crash,無法用 error handle catch 來處理。
Crash 片段
記錄發生 crash 的地方不只有一處,共同的特色就是都是掛在 linphone 的函式庫:
發生位置1 linphone_core_iterate()
:
@objc func iterate(){
let lc = LinphoneManager.getLc()
if lc != nil{
linphone_core_iterate(lc); /* first iterate initiates registration */
}
}
發生位置2 linphone_core_iterate()
:
if let phone = phoneNumber, lc = LinphoneManager.lc {
nameLabel.text = calleeName!
linphone_core_invite(lc, phone)
}
透 Debug build 測試
因為預設從 linphone 網站下載的 iphone sdk 是沒有 debug symbol 的,所以在發生 crash 的時侯,只能看到看組合語言:
雖然有 linphone 的函式名稱,但是對於問題的幫助不大。所以我就直接 build 一個 有 debug symbol 的 liblinphone-sdk,build debug sdk 步驟記錄在另一篇。
經過有 debug build 的協助之後, crash 的資訊從原本的組合語言變成了 c 語言,地點是在submodules/linphone/coreapi/vtables.c
檔案:
NOTIFY_IF_EXIST 是一個 macro,執行 event 的 notify:
#define NOTIFY_IF_EXIST(function_name, ...) \
MSList* iterator; \
VTableReference *ref; \
bool_t has_cb = FALSE; \
for (iterator=lc->vtable_refs; iterator!=NULL; iterator=iterator->next)\
if ((ref=(VTableReference*)iterator->data)->valid && (lc->current_vtable=ref->vtable)->function_name) {\
lc->current_vtable->function_name(__VA_ARGS__);\
has_cb = TRUE;\
}\
if (has_cb) ms_message("Linphone core [%p] notifying [%s]",lc,#function_name)
我開始思考,這邊有什麼變數是已經釋放了?於是我找到了關鍵字current_vtable
,回頭看到自己的 LinphoneCoreTable 是放在 class property 中:
哈,賓果!推斷是因為 linphone 函式使用到這個已經釋放的 object,把程式修改成用 struct static 專門來放 global variables:
再次執行! Problem Solved !!
結論
我本身比較少 C/C++ 的除錯經驗,因為這個 bug 足足花了15個小時才找到問題,把除錯的經驗記錄於此。除錯過程中,盡量讓自己有更多的資訊可以下判斷,像是掛載 debug symbol 就是一例。在 swift 呼叫 c 的 library 時,有些物件的 life cycle 要特別留意,是不是可能會讓外部的 library 呼叫到自己已經釋放的 object。下次遇到這個問題,應該要先把跟外部函式庫有關聯的變數列出來,一一檢視是否釋放的時機過早。我想效率會比較好。
註1: 嘗試過的解法
在用 debug symbol 前,其實嘗試了許多無用的解法:
- Linphone core 物件是 nil? -> 不是這個原因
- 懷疑可能是 liblinphone 的 bug,故升級 liblinphone 的版本到從3.9.1升級到最新版3.13.9 -> 無效
- 把 linphonecore 從 class static properties 改成 struct static variable -> 無效
- 使用
XCode Profiler
-> 看不出問題 - 使用
linphone_core_invite
相關的函式放到 background thread -> 無效 - DismissViewControllerAnimated() 造成 EXC_Bad_ACCESS ref -> 無效
- 嘗試把 debug optimization level 調成 -Os,讓 debug,release 的情況一致
- 嘗試 build 一個簡單的 linphone sample proejct
註2: NSZombie
NSZombie 原本是設計讓你除錯用的,卻因為我把這個功能開啟,反而造成本地端不會 Crash,上傳到 testflight 的 release 版本就會 crash。在這個 Case 中, NSZombie 沒有辦法找到問題,也許是因為 Crash 的點的是發生在外部函式庫中?