符號(Symbol):簡單來講,類、函數和變量的統稱;類名、函數名或變量名稱爲符號名(Symbol Name);git
按類型分,符號能夠分三類:github
符號表(Symbol Table):符號表是內存地址與函數名、文件名、行號的映射表;每一個定義的符號有一個對應的值,叫作符號值(Symbol Value),對於變量和函數來講,符號值就是他們的地址;符號表元素以下所示:objective-c
<起始地址> <結束地址> <函數> [<文件名:行號>]
複製代碼
dSYM(debug symbols):是iOS的符號表文件,存儲16進制地址信息和符號的映射文件;文件名一般爲:xxx.app.dSYM,相似Android構建release產生的mapping文件;利用dSYM文件文件,能夠將堆棧信息中地址信息還原成對應的符號,幫助問題排查;shell
Symbol Table
存儲着全局符號和局部符號; DWARF
存儲着符號的行號信息。Xcode 編譯時有幾個選項是和符號是相關的。數組
Debug Information Format:DWARF
OR DWARF with dSYM File
。 這配置對於靜態庫會無影響;對動態庫有影響:設置爲 DWARF with dSYM File
,生成動態庫時會生成相應的 dSYM 文件;若是設置爲 DWARF,則 dwarf 段即調試信息沒有地方存放將丟失。bash
Generate Debug Symbols:設置爲YES,編譯生成目標文件時會生成調試信息;設置爲 NO,那麼 dwarf 段不會生成,也不會有 dSYM 文件生成,而且調試過程使用的斷點也不會生效,由於地址已經沒法和對應代碼行關聯起來了。app
Deployment:函數
Deployment Postprocessing:若是爲 YES
,在編譯生成目標文件以後要進行後續處理;若是爲 NO
,則不會有後續處理;使用 Xcode Archive
進行編譯,Deloyment Postprocessing
的值恆爲YES
;post
Strip Linked Product:若是爲 YES
,則進行裁剪;若是爲 NO,則不進行裁剪;至於裁剪什麼級別的符號由 Strip Style 配置決定;若是Deployment Postprocessing爲NO,Strip Linked Product設置無效;測試
Strip Style(Deployment Postprocessing和Strip Linked Product都爲YES,才生效;去除的是二進制中的符號):
DWARF
中的調試信息以及Symbol Table
中目標模塊定義的全局、局部符號信息。補充1: 動態庫和靜態庫不能去除所有符號(Strip All Symbols),要保留全局符號(選擇Non-Global Symbols),他們是庫和其餘庫連接時溝通的橋樑;失去了全局符號,動態庫和靜態庫就成爲了黑盒。
補充2: 去除符號的操做對於 dSYM 文件中的符號信息沒有影響;對於動態庫和可執行二進制文件,能夠將符號儘量去除掉減小二進制體積的大小。須要符號進行符號化崩潰日誌時,再從 dSYM 文件中找對應符號。
Symbols Hidden by Default :這是全局的開關,用來設置符號的默承認見性,設置爲YES,會把全部符號都定義成」private extern」;
也能夠可使用編譯器屬性__attribute__((visibility("default")))
和__attribute__((visibility("hidden")))
來控制符號的可見性;
__attribute__((visibility("default"))) void MyFunction1() {} //可見
__attribute__((visibility("hidden"))) void MyFunction2() {} //不可見
複製代碼
Debug Information Format 設置爲 DWARF
;由於生成 dSYM 文件是一個比較耗時的過程,選擇DWARF
能節省調試時間;
Generate Debug Symbols:設置爲 YES;這樣才能支持斷點調試;注意Debug模式下,Deployment Postprocessing 必定要NO,不然Generate Debug Symbols的設置了YES,也不支持斷點調試;
Deployment配置:
如此配置後,App二進制中帶有全局符號和局部符號信息,二進制自己能夠支持不使用 dSYM 文件自解析出符號(自解析出的符號不包含行號)。
Symbols Hidden by Default 設置爲YES;
Debug Information Format 設置爲 DWARF with dSYM File
;這樣生成ipa的同時,會一併生成 dSYM文件。
Generate Debug Symbols:設置爲 NO;這樣才能支持斷點調試;注意Debug模式下,Deployment Postprocessing 必定要NO,不然Generate Debug Symbols的設置了YES,也不支持斷點調試;
Deployment配置:
如此配置,App中不帶任何符號,能夠減小安裝包大小,還能避免符號泄漏。定位問題時,能夠經過 dSYM 文件去獲取符號。
Symbols Hidden by Default 設置爲YES;
靜態庫配置:Debug Information Format
默認就好,Generate Debug Symbols
設置YES,Deployment Postprocessing
設置爲 NO,Strip Linked Product
設置爲 NO,Strip Style
默認就行(Strip Linked Product設置爲 NO,Strip Style配置無所謂;Symbols Hidden by Default
設置爲NO;
靜態庫如此配置,實際上是沒有裁剪二進制的符號的;所以,靜態庫的二進制的大小將會大大增長,可是靜態庫的大小並不影響最終安裝包二進制的大小,同時調試符號能支持安裝包或者連接的動態庫生成相應的 dSYM 文件,方便定位靜態庫中的問題。
動態庫配置:Debug Information Format
分Debug和Release,配置同App;Generate Debug Symbols
設置YES,Symbols Hidden by Default
設置爲NO;
Deployment Postprocessing
設置爲 YES;Strip Linked Product
設置爲 YES; Strip Style
設置爲 Non-Global Symbols
Deployment Postprocessing
設置爲 NO;Strip Linked Product
設置爲 NO;Strip Style
設置啥均可以(由於Strip Linked Product 是NO,Strip Style隨便配置什麼都無影響)若是動態庫
Symbols Hidden by Default
設置爲 YES,動態庫仍然能編譯經過,可是App會報一堆連接錯誤,由於符號變成了hidden。
symbol默認是strong的,可是能夠增長 __attribute__ ((weak))
屬性將其變成weak symbol;weak symbol在連接時候比較特殊:
應用場景:用weak symbol提供默認實現,外部能夠提供strong symbol把實現注入進來,以此來實現依賴注入。
.a
中的某個.o
符號被引用的時候,這個.o
纔會被ld寫到最後的二進制文件中,不然會被丟掉,other linker flags
提供三個選項來解決保留代碼的問題。
-ObjC
保留全部Objective C的代碼;-force_load
保留某一個靜態庫的所有代碼;-all_load
保留參與連接的所有的靜態庫代碼;other linker flags
裏添加*-ObjC
*。當連接的時候類找不到了,會報錯符號_OBJC_CLASS_$_CLASSNAME
找不到;
以前在接入 AlipaySDK遇到過(緣由是: AlipaySDK和阿里百川SDK衝突致使,須要接入UTDID framework)
Undefined symbols for architecture x86_64:
"_OBJC_CLASS_$_UTDevice", referenced from:
objc-class-ref in AlipaySDK
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
複製代碼
補充1:若是類的符號沒有被裁減掉,運行時就用
_OBJC_CLASS_$_CLASSNAME
做爲參數,經過dlsym來獲取類指針。補充2:nm app_name.app/app_name 執行返回中,小寫字母對應着本地符號,大寫字母表示全局符號;U表示undefined,即未定義的外部符號;
運行時,使用還能夠用lldb去查詢符號相關的信息;
查看符號的定義
image lookup -t symbol_name
複製代碼
查看符號的位置
image lookup -s symbol_name
複製代碼
設置符號斷點
breakpoint set -F "symbol" #也可經過Xcode的GUI能設置
複製代碼
開發階段,不會裁剪符號,因此一切都比較美好;對一個地址進行符號化比較直接:找到地址所屬的內存鏡像,而後定位該鏡像中的符號表,最後從符號表中匹配目標地址的符號。
可是裁剪符號的包,如企業內測包AppStore選擇了裁剪符號的方式,甚至是裁剪所有符號(Strip Style 設置爲 All Symbols );常規符號化不能解決問題;
符號裁剪的好處:減小了安裝包大小,還避免符號泄漏;
企業內測包不一樣於AppStore包,主要用於內部測試和灰度,有時候須要收集問題的上下文信息,這些信息中包括髮生問題的代碼行數、代碼文件和函數名,甚至包括堆棧符號;
裁剪符號後,[NSThread callStackSymbols]
獲取的不少堆棧地址須要符號恢復;而此時的dladdr
也不能根據地址獲取符號信息;
不論符號有沒有被裁剪,均可以經過如下C語言中的預約義符獲取,具體以下:
__FILE__ //File path
__LINE__ //Code Line
__FUNCTION__ //Funcation Name
//demo
printf("File = %s\nLine = %d\nFunc=%s\n", __FILE__, __LINE__, __FUNCTION__);
複製代碼
不論符號有沒有被裁剪,也能夠經過Objective-C的_cmd
方法獲取當前方法名,eg以下
printf("call %s", [NSStringFromSelector(_cmd) UTF8String]);
複製代碼
經過預約義符和_cmd
方法獲取當前調試符號信息,在系統API中也常有使用,如NSAssert宏的使用,源碼以下:
#define NSAssert(condition, desc, ...) \
do { \
__PRAGMA_PUSH_NO_EXTRA_ARG_WARNINGS \
if (__builtin_expect(!(condition), 0)) { \
NSString *__assert_file__ = [NSString stringWithUTF8String:__FILE__]; \
__assert_file__ = __assert_file__ ? __assert_file__ : @"<Unknown File>"; \
[[NSAssertionHandler currentHandler] handleFailureInMethod:_cmd \
object:self file:__assert_file__ \
lineNumber:__LINE__ description:(desc), ##__VA_ARGS__]; \
} \
__PRAGMA_POP_NO_EXTRA_ARG_WARNINGS \
} while(0)
#endif
複製代碼
NSAssert適合於Objective-C方法,利用
__FILE__
、__LINE__
、_cmd
來獲取發生問題時候的代碼文件路徑、代碼行數、方法名,而後將這些交給[[NSAssertionHandler currentHandler] handleFailureInMethod:object:file:lineNumber:description:]
處理;
咱們習慣性利用[NSThread callStackSymbols]
獲取當前線程調用堆棧符號信息,可是這隻在Debug模式下比較理想;在符號被裁剪的狀況下,獲取的地址須要作符號恢復;
若是利用dSYM文件來符號化是能夠的;能夠參考個另類方案:根據全部的類方法、方法名、方法實現地址,將調用棧的內存地址符號化;相似Frida調用棧符號恢復,方案具體描述:
[NSThread callStackReturnAddresses]
獲取調用棧的內存地址;須要說明的是,這裏的符號指的是:Objective-C的函數符號
,由於若是C函數符號被strip後,是沒有辦法恢復其符號的;
在符號裁剪狀況下,dladdr
通常不能經過地址獲取到符號;能夠用以下代碼測試
NSArray<NSNumber *> *addresses = [NSThread callStackReturnAddresses];
NSNumber *firstAddress = [addresses objectAtIndex:0];
Dl_info info;
int result = dladdr((const void *)[firstAddress integerValue], &info);
if (result != 0 && info.dli_sname) {
//Debug模式配置
printf("經過dladdr函數獲取symbol_name = %s", [[NSString stringWithUTF8String:info.dli_sname] UTF8String]);
} else {
//Release模式配置
printf("符號裁剪後,不能經過dladdr函數獲取符號,須要[新符號恢復方案]");
}
複製代碼
若是
dladdr
能經過地址拿到符號信息,就說明符號沒有裁剪,能夠直接用[NSThread callStackSymbols]
Crash主要有兩類:Mach 異常和Objective-C 異常(NSException)引發的;
Mach異常是最底層的內核級異常,如EXC_BAD_ACCESS(內存訪問異常);而Objective-C 層不能獲取Mach異常,可是Mach 異常到了 BSD 層會轉換爲對應的 Signal 信號,咱們能夠註冊SIGABRT, SIGBUS, SIGSEGV等信號發生時的處理函數。
//註冊處理SIGSEGV信號
signal(SIGSEGV,handleSignal);
// 註冊處理其餘信號 ....
//信號處理函數
static void handleSignal( int sig ) {
}
複製代碼
NSException異常是iOS庫或者各類第三方庫或Runtime驗證出錯誤而拋出的異常。如NSRangeException
(數組越界異常),它們能夠被try catch捕獲(蘋果不建議用),若是未被捕獲或被@throw拋出,能夠經過註冊NSSetUncaughtExceptionHandler
函數來捕獲處理。
//註冊異常處理函數
NSSetUncaughtExceptionHandler(&uncaught_exception_handler);
//異常處理函數
static void uncaught_exception_handler (NSException *exception) {
//能夠取到 NSException 信息
//...
abort();
}
複製代碼
目前常見的符號號手段
第一種作法通常是研發本身用;第二種適用於作成標準方案,批量幫助將線上的Crash 堆棧符號還原;