iOS Crash 流程化3:Crash 產(chǎn)生和符號化的原理

  • iOS Crash 流程化3:Crash 產(chǎn)生和符號化的原理
    • 異常類型
      • Mach異常
      • Unix信號
    • 異常的產(chǎn)生
    • 線程回溯
      • 符號化回溯線程
        • 符號在二進制中的偏移量
        • atos
    • 符號化內(nèi)幕
      • 小小結(jié)
    • 線程的狀態(tài)寄存器
    • Binary Images
    • 小結(jié)

iOS 的異常類型(Exception Type)由兩部分構(gòu)成:Mach異常、Unix信號異常。

異常類型

Mach異常

蘋果系統(tǒng)有一個微內(nèi)核,叫做XNU,它的源碼可以在opensource上下載到。Mach是XNU的核心,因而,Mach異常就指Mach內(nèi)核異常。Mach包含三部分內(nèi)容:thread,task,host。后續(xù)的章節(jié)中很多地方都會用到Mach。不妨移步到Mach IPC Interface,了解下Mach暴露給用戶的API。

Mach暴露給了用戶部分API,允許用戶和內(nèi)核交互。用戶態(tài)的開發(fā)者可以通過Mach API設(shè)置thread、task、host的異常端口,來捕獲Mach異常,抓取Crash事件。

Mach異常包括:

#define EXC_BAD_ACCESS        1    /* Could not access memory */
    /* Code contains kern_return_t describing error. */
    /* Subcode contains bad memory address. */

#define EXC_BAD_INSTRUCTION    2    /* Instruction failed */
    /* Illegal or undefined instruction or operand */

#define EXC_ARITHMETIC        3    /* Arithmetic exception */
    /* Exact nature of exception is in code field */

#define EXC_EMULATION        4    /* Emulation instruction */
    /* Emulation support instruction encountered */
    /* Details in code and subcode fields    */

#define EXC_SOFTWARE        5    /* Software generated exception */
    /* Exact exception is in code field. */
    /* Codes 0 - 0xFFFF reserved to hardware */
    /* Codes 0x10000 - 0x1FFFF reserved for OS emulation (Unix) */

#define EXC_BREAKPOINT        6    /* Trace, breakpoint, etc. */
    /* Details in code field. */

#define EXC_SYSCALL        7    /* System calls. */

#define EXC_MACH_SYSCALL    8    /* Mach system calls. */

#define EXC_RPC_ALERT        9    /* RPC alert */

#define EXC_CRASH        10    /* Abnormal process exit */

#define EXC_RESOURCE        11    /* Hit resource consumption limit */

Unix信號

信號是通知進程已發(fā)生某種情況的軟中斷技術(shù)。例如:某個進程執(zhí)行了除法操作,其除數(shù)為0,則將名為SIGFPE(浮點異常)的信號發(fā)送給該進程。

異常的產(chǎn)生

那么,怎么會有兩種異常信息呢?

念茜的漫談iOS Crash收集框架闡述了兩者的關(guān)系,這里再重復(fù)下。

蘋果系統(tǒng)是基于Unix系統(tǒng)的,蘋果的大牛們?yōu)榱思嫒軺nix信號,將Mach異常轉(zhuǎn)化為Unix信號,并投射到異常的線程,這樣做的目的是:對于不懂Mach異常的人,也可以使用Unix信號捕獲異常。所以,Crash日志有兩種異常信息。

Mach和Unix關(guān)系圖:


image

所有Mach異常都在host層被ux_exception轉(zhuǎn)換為相應(yīng)的Unix信號,并通過threadsignal將信號投遞到出錯的線程。

捕獲Mach異常或者Unix信號都可以抓到crash事件,這兩種方式哪個更好呢?

優(yōu)選Mach異常,因為Mach異常的處理會先于Unix信號處理,如果Mach異常的handler讓程序exit了,那么Unix信號就永遠不會到達這個進程了。

所以,Crash日志中的EXC_BAD_ACCESS 是Mach異常信息,SIGSEGV是Unix信號異常信息。

小貼士:
因為硬件產(chǎn)生的信號(通過CPU陷阱)被Mach層捕獲,然后才轉(zhuǎn)換為對應(yīng)的Unix信號;蘋果為了統(tǒng)一機制,于是操作系統(tǒng)和用戶產(chǎn)生的信號(通過調(diào)用kill和pthread_kill)也首先沉下來被轉(zhuǎn)換為Mach異常,再轉(zhuǎn)換為Unix信號。

線程回溯

符號化回溯線程

線程的回溯是APP Crash瞬間,程序中所有線程的逆向調(diào)用堆棧。線程回溯對我們修復(fù)Crash非常有用,根據(jù)線程回溯,可以分析、定位程序崩潰的原因。

下面將崩潰的代碼、未符號化崩潰日志、符號化崩潰日志貼出來,做個對比性的理解。

@implementation ViewController

- (IBAction)onCrash:(__unused id)sender {
    char* ptr = (char*)-1;
    *ptr = 10;  ///這里程序崩潰了 
}

@end

未符號化的崩潰日志

image

符號化的崩潰日志

image

符號在二進制中的偏移量

未符號化的崩潰日志中紅色文字展示了幾個名詞:鏡像文件、加載地址、堆棧地址。以及沒有展示出來的一個名詞:符號在二進制中的偏移量。他們的含義分別為:

  • 鏡像文件:是可執(zhí)行二進制文件和二進制文件依賴的動態(tài)庫的總稱。
  • 堆棧地址:是代碼在內(nèi)存中執(zhí)行的內(nèi)存地址。
  • 鏡像的加載地址:程序執(zhí)行時,內(nèi)核會將包含程序代碼的鏡像加載到內(nèi)存中,鏡像在內(nèi)存中的基地址就是加載地址。程序每次啟動時,鏡像的加載地址是隨機的。所以,同一代碼在不同的設(shè)備中執(zhí)行時,堆棧地址是不一樣的。
  • 符號在二進制中的偏移量:按照字面意思理解吧。它以通過下面的公式得到:
符號在二進制中的偏移量 = 堆棧地址 - 鏡像的加載地址  

符號在二進制中的偏移量非常有用,我們就是根據(jù)它,從符號文件中查找出地址對應(yīng)的代碼符號。這里的符號文件指的是:帶有符號表的可執(zhí)行二進制文件、dSYM文件,這兩種文件在后續(xù)章節(jié)中都統(tǒng)稱為符號文件。

那么怎么將未符號化的崩潰日志符號化呢?

atos

蘋果自帶的atos命令行工具可以查找地址對應(yīng)的符號,在終端中輸入:

/usr/bin/atos -o [符號文件] -arch arm64 -l 0x100030000 0x000000010003522c 

輸出結(jié)果如下:

-[ViewController onCrash:] (in Simple-Example) (ViewController.m:10)

是不是很簡單的就將地址轉(zhuǎn)換為符號?是的,只需將符號文件(-o指定)、代碼構(gòu)架(-arch指定)、加載地址(-l指定)、堆棧地址傳入atos命令,就能解析出符號。

atos命令解析出了堆棧地址為0x000000010003522c、加載地址為0x100030000對應(yīng)的符號。符號為[ViewController onCrash:],也驗證了崩潰發(fā)生在onCrash函數(shù)中,也驗證了崩潰日志中的地址是可以符號化的。

符號化原理是什么?怎么就通過地址找到了Crash代碼的符號,這就涉及符號化內(nèi)幕。

符號化內(nèi)幕

符號化的內(nèi)幕就是:==在符號文件中,通過偏移量查找符號==。下面,一步步的來分析,首先計算Crash地址在符號文件中的偏移量,為000000010000522c。

符號在二進制中的偏移量 = 堆棧地址 - 鏡像的加載地址 = 0x000000010003522c -  0x100030000 = 000000010000522c

在符號文件中直接找地址000000010000522c,應(yīng)該是找不到,在后續(xù)你可以理解。我們使用逆向方法,根據(jù)符號-[ViewController onCrash:],找對應(yīng)的地址,比較是不是000000010000522c,如果是,就充分說明了,通過偏移量是可以查找到內(nèi)存地址對應(yīng)的符號的。在終端中輸入下面的命令:

nm [符號文件] | grep "ViewController onCrash:"

輸出如下

00008320 t -[ViewController onCrash:]
0000000100005224 t -[ViewController onCrash:]

輸出的第一行是armv7s構(gòu)架的符號,第二行是arm64構(gòu)架的符號,Crash日志顯示的代碼構(gòu)架是arm64,使用第二行,符號-[ViewController onCrash:]對應(yīng)的偏移量是0000000100005224,而不是 000000010000522c,這個公式是在stack overflow上找到的,就相差8!后來寫日志組織測試用例的時候,忽然明白了為什么差那一點點。

原來,我們通過nm命令查找出的符號地址對,是函數(shù)入口地址和對應(yīng)的函數(shù)調(diào)用的符號對,僅僅是函數(shù)調(diào)用的符號,沒有函數(shù)內(nèi)部代碼的符號,而程序是崩潰到函數(shù)內(nèi)部,崩潰到*ptr = 10這句話,內(nèi)部代碼的地址怎么可能和入口地址一樣呢!相差一點點!

下面根據(jù)偏移量000000010000522c和代碼推算函數(shù)的入口地址吧,看看是什么。崩潰代碼*ptr = 10前面只有一個語句—定義初始化指針char* ptr = (char*)-1,在64位系統(tǒng)上指針的地址占8個字節(jié),000000010000522c - 8= 0000000100005224,果然是0000000100005224。

這個不就是函數(shù)的入口地址嘛。原來那一點點的原因在這里。那么偏移量0000000100005224 對應(yīng)的符號正是-[ViewController onCrash:]

上面通過nm 命令查找符號可能不直觀,可以通過可視化工具MachOView查看。驗證下吧,選擇 Debug Symbols(ARM64_ALL)->Symbol Table->Symbols,然后在右上角的搜索框中輸入符號:-[ViewController onCrash:],結(jié)果如下:

image

通過這個工具可以直觀的查看到符號和地址的對應(yīng)關(guān)系。

小小結(jié)

終于把符號化和符號化原理闡述完了簡單回顧下:

  1. 可以通過系統(tǒng)的atos符號化崩潰日志的單個符號
  2. 符號化內(nèi)部原理就是:根據(jù)符號在二進制中的偏移量,在符號文件中查找對應(yīng)的符號。其中:==符號在二進制中的偏移量 = 堆棧地址 - 鏡像的加載地址==。

線程的狀態(tài)寄存器

Thread 0 crashed with ARM Thread State (64-bit):
    x0: 0x000000010050b460   x1: 0x0000000100102cea   x2: 0x00000001004339d0   x3: 0x00000001740f8f00
    x4: 0x00000001740f8f00   x5: 0x00000001740f8f00   x6: 0x0000000000000001   x7: 0x0000000000000000
    x8: 0xffffffffffffffff   x9: 0x000000000000000a  x10: 0x00000001b3ad0018  x11: 0x00c1580100c15880
   x12: 0x0000000000c15800  x13: 0x0000000000c15900  x14: 0x0000000000c158c0  x15: 0x0000000000c15801
   x16: 0x0000000000000000  x17: 0x00000001000c1224  x18: 0x0000000000000000  x19: 0x00000001740f8f00
   x20: 0x00000001004339d0  x21: 0x0000000100102cea  x22: 0x000000010050b460  x23: 0x0000000170240bd0
   x24: 0x000000017400db90  x25: 0x0000000000000001  x26: 0x0000000000000000  x27: 0x00000001b2822000
   x28: 0x0000000000000040   fp: 0x000000016fd41ab0   lr: 0x0000000194aea7b0
    sp: 0x000000016fd41a90   pc: 0x00000001000c122c cpsr: 0x60000000

這是APP crash的時候,ARM64 構(gòu)架CPU的32個寄存器的值, 其中 fp 幀指針、sp 堆棧指針,lr 是返回地址指針,這三個都比較有用,用來逐級回溯線程調(diào)用棧。

Binary Images

image

鏡像文件就是上面講的可執(zhí)行程序依賴的所有動態(tài)庫

鏡像文件中包括鏡像的加載地址,和線程回溯中的鏡像加載地址指的是一個地址。加載地址后面有個UUID,符號文件中也有個UUID,只有這兩個地址一致,才能解析出地址對應(yīng)的符號。符號文件中的UUID可以通過終端中輸入下面的命令得到:

dwarfdump —u [符號文件]

輸出如下:

UUID: C8E0E6E4-F761-3A19-B231-A31C1BB9037A (armv7) 
UUID: 39BBB8F4-CCB0-3193-8491-C007931CA05E (arm64) 

第二行的arm64構(gòu)架的UUID居然和圖8中的紅色矩形框中UUID必須一致,這才表示代碼對應(yīng)的符號能在這個符號文件中找到,如果不一致,就沒法解析出地址對應(yīng)的符號。不論是Xcode,還是symbolicatecrash,都解析不了。

也可以通過MachOView查看符號文件的UUID,結(jié)果如下:

image

小結(jié)

這里闡述了日志的產(chǎn)生原因和符號化崩潰日志的原理。同時提及了幾個有用的工具:

  1. file 文件類型顯示工具(The file-type displaying tool,位于/usr/bin/file);
  2. atos (將數(shù)字地址轉(zhuǎn)換為鏡像或可執(zhí)行程序中的符號工具,convert numeric addresses to symbols of binary images or processes,位于/usr/bin/atos);
  3. nm(符號表展示工具,The symbol table display tool,位于 /usr/bin/nm);
  4. 可視化查看Mach-O工具,MachOView
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,990評論 2 374

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

  • [這是第14篇] 序: iOS Crash問題是iOS開發(fā)中難以忽視的存在,本文就捕獲iOS Crash、Cras...
    南華coder閱讀 9,920評論 21 116
  • 本文就捕獲iOS Crash、Crash日志組成、Crash日志符號化、異常信息解讀、常見的Crash五部分介紹。...
    xukuangbo_閱讀 1,591評論 0 0
  • Ref:iOS Crash 捕獲及堆棧符號化思路剖析 iOS Crash 流程化:概覽崩潰捕獲Mach 異常捕獲U...
    Vinc閱讀 1,354評論 0 0
  • 《泥步修行》這本書是我第一次通過正版購買的方式獲取的余老著作,算是對余老遲到的敬意吧。余老師的書,我讀過了不少,深...
    放縱天涯閱讀 1,055評論 0 0
  • 已默認讀者了解本篇自言自語的context,且對于module有所了解,對于module的相關(guān)擴展說明將穿插在內(nèi)容...
    蔣啟鉦閱讀 1,118評論 0 4