深刻iOS系統底層之crash解決方法

要學會看crash崩潰和報告

一個應用程序並不總會一直運行的很好,它總會有出現crash崩潰的狀況。若是在應用程序中接入了一些第三方的crash收集工具或者自建crash收集報告平臺的話將會很好的幫助開發者去分析和解決應用程序在線上運行的問題,當出現的崩潰問題能獲得及時的解決和快速的修復時必將會大大的提高應用程序的用戶體驗。html

當前比較流行的crash收集分析工具不少都是基於開源的KSCrash代碼來進行封裝和改進的。蘋果自身也構建了一套crash採集和分析的機制,你能夠從真機的聯機日誌或者從開發者帳號中去查看對應的crash信息。網絡上也有不少關於crash分析的文章,以及crash堆棧符號化處理的文章。這裏假定你已經瞭解了一些查看crash報告的方法和技巧以及一些簡單的crash分析技巧,由於這些是做爲開發者須要具有的技能之一。git

一個objc_msgSend+16崩潰棧

應用程序出現的crash崩潰異常有一些可以簡單的被分析和解決,每每這些crash崩潰異常都會帶有明確的上下文信息和函數調用層級堆棧。但並非全部的crash崩潰異常都能被簡單的解決,尤爲是那些沒有明確上下文信息的函數調用堆棧或者那些調用堆棧中沒有一個函數或者方法可以被直接定位到源代碼的場景,就以下面這個崩潰的函數調用棧(部分信息):github

Incident Identifier: 85BE3461-D7FD-4043-A4B9-1C0D9A33F63D
CrashReporter Key:   9ec5a1d3b8d5190024476c7068faa58d8db0371f
Hardware Model:      iPhone7,2
Code Type:       ARM-64
Parent Process:  ? [1]
Date/Time:       2018-08-06 16:36:58.000 +0800
OS Version:      iOS 10.3.3 (14G60)
Report Version:  104

Exception Type:  EXC_BAD_ACCESS (SIGBUS)
Exception Codes: 0x00000000 at 0x00000005710bbeb8
Crashed Thread:  2

Thread 2 name:  WebThread
 Thread 2 Crashed:
0   libobjc.A.dylib                 objc_msgSend + 16
1   UIKit                           -[UIWebDocumentView _updateSubviewCaches] + 40
2   UIKit                           -[UIWebDocumentView subviews] + 92
3   UIKit                           -[UIView(CALayerDelegate) _wantsReapplicationOfAutoLayoutWithLayoutDirtyOnEntry:] + 72
4   UIKit                           -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 1256
5   QuartzCore                      -[CALayer layoutSublayers] + 148
6   QuartzCore                      CA::Layer::layout_if_needed(CA::Transaction*) + 292
7   QuartzCore                      CA::Layer::layout_and_display_if_needed(CA::Transaction*) + 32
8   QuartzCore                      CA::Context::commit_transaction(CA::Transaction*) + 252
9   QuartzCore                      CA::Transaction::commit() + 504
10  QuartzCore                      CA::Transaction::observer_callback(__CFRunLoopObserver*, unsigned long, void*) + 120
11  CoreFoundation                  __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 32
12  CoreFoundation                  __CFRunLoopDoObservers + 372
13  CoreFoundation                  CFRunLoopRunSpecific + 456
14  WebCore                         RunWebThread(void*) + 456
15  libsystem_pthread.dylib         _pthread_body + 240
16  libsystem_pthread.dylib         _pthread_body + 0

Thread 2 crashed with ARM-64 Thread State:
  cpsr: 0x0000000020000000     fp: 0x000000016e18d7c0     lr: 0x000000018e2765fc     pc: 0x0000000186990150 
    sp: 0x000000016e18d7b0     x0: 0x0000000174859740     x1: 0x000000018eb89b7b    x10: 0x0000000102ffc000 
   x11: 0x00000198000003ff    x12: 0x0000000102ffc290    x13: 0xbadd8a65710bbead    x14: 0x0000000000000000 
   x15: 0x000000018caeb48c    x16: 0x00000005710bbea8    x17: 0x000000018e2765d4    x18: 0x0000000000000000 
   x19: 0x0000000103a52800     x2: 0x0000000000000000    x20: 0x00000000000002a0    x21: 0x0000000000000000 
   x22: 0x0000000000000000    x23: 0x0000000000000000    x24: 0x0000000000000098    x25: 0x0000000000000000 
   x26: 0x000000018ebade52    x27: 0x00000001ad018624    x28: 0x0000000000000000    x29: 0x000000016e18d7c0 
    x3: 0x000000017463db60     x4: 0x0000000000000000     x5: 0x0000000000000000     x6: 0x0000000000000000 
    x7: 0x0000000000000000     x8: 0x00000001acfb9000     x9: 0x000000018ebf8829 

Binary Images:
       0x100030000 -        0x1022cbfff +xxxx arm64  <6b98f446542b3de5818256a8f2dc9ebf> /var/containers/Bundle/Application/441619EF-BD56-4738-B6CF-854492CDFAC9/xxxx.app/xxxx
       0x1063f8000 -        0x106507fff  MacinTalk arm64  <0890ce05452130bb9af06c0a04633cbb> /System/Library/TTSPlugins/MacinTalk.speechbundle/MacinTalk
       0x107000000 -        0x1072e3fff  TTSSpeechBundle arm64  <d583808dd4b9361b99a911b40688ffd0> /System/Library/TTSPlugins/TTSSpeechBundle.speechbundle/TTSSpeechBundle
...
       0x18e03d000 -        0x18ede3fff  UIKit arm64  <314063bdf85f321d88d6e24a0de464a2> /System/Library/Frameworks/UIKit.framework/UIKit
       0x18ede4000 -        0x18ee0cfff  CoreBluetooth arm64  <ced176702d7c37e6a9027eeb3fbf7f66> /System/Library/Frameworks/CoreBluetooth.framework/CoreBluetooth

複製代碼

這是一個在iOS10.3.3版本的64位設備上的一條crash異常報告的片斷信息,要記住這些信息,它對定位crash崩潰異常有很大的幫助。從崩潰的函數調用棧中能夠看出異常是出如今最頂層的函數調用objc_msgSend+16處,也就是在objc_msgSend函數的第5條指令處**(一般狀況下arm體系結構中每條指令佔用4個字節,上述的信息代表是崩潰在函數的第16個字節的偏移地址處,也就是函數的第5條指令處)。崩潰異常類型顯示爲EXC_BAD_ACCESS**代表是產生了無效的地址的讀寫訪問,整個崩潰函數調用棧中沒應用程序中的任何上下文信息。objc_msgSend函數是runtime方法執行的核心引擎並且調用如此的頻繁,函數內部是不可能有BUG的。 那麼爲何會崩潰在這呢?數組

當異常出如今沒有源代碼的函數內部時,惟一的方法就是去看它內部的「源代碼」實現安全

既然出現問題是在objc_msgSend函數的第5條指令處,能夠來看看這個函數實現的彙編代碼指令開頭片斷:bash

;iOS10之後的objc_msgSend的部分實現代碼。
_objc_msgSend:
00000001800bc140<+0>	cmp		x0, #0x0 ;判斷對象receiver和0進行比較
00000001800bc144<+4>    b.le	0x1800bc1ac    ;若是對象指針爲0或者高位爲1則執行特殊處理跳轉。
00000001800bc148<+8>	ldr		x13, [x0]           ;取出對象的isa指針賦值給x13
00000001800bc14c<+12>	and	x16, x13, #0xffffffff8 ;獲得對象的Class對象指針賦值給x16
00000001800bc150<+16>	ldp	x10, x11, [x16, #0x10] ;取出Class對象的cache成員分別保存到x10,x11寄存器中 
-----------------------------------------------------------上面的指令就是代碼崩潰處。
00000001800bc154<+20>	and		w12, w1, w11
複製代碼

不管是真機仍是模擬器,XCODE都支持在運行時來查看任何調用的函數的彙編代碼實現,你能夠經過設置符號斷點或者進入彙編調試模式以及單指令跳轉的方式來查看函數的彙編代碼實現。網絡

從代碼中能夠看出是在讀取對象的Class對象指針的數據成員cache時出現了無效的地址訪問異常。可是對象的Class對象這部分定義數據是存儲在進程內存的數據區段中,而且伴隨着整個應用的生命週期而存在,是不可能被釋放和銷燬的,所以正常狀況下是不可能存在非法內存地址訪問異常的。會出現這種問題的緣由就是調用方法的OC對象被銷燬了,再說具體一點就是對一個已經被釋放掉的OC對象繼續調用了實例方法而致使的。所以當出現這種類型的崩潰時,無論是否有明確上下文,其緣由都是一致的。下面這張圖就能很清楚的說明其中的緣由了:app

對象被銷燬先後內存佈局對比圖

實際上在arm64位系統中isa中保存的並非對象的Class對象地址,上面的圖目的是爲了更加直觀的顯示問題緣由。ide

一個OC對象obj在被銷燬前,其中的isa指針會指向正確的Class對象所在的內存地址。所以調用objc_msgSend方法將會正常的運行,而一旦obj對象被銷燬後,爲其分配的堆內存將被回收用做其餘用途,所以有可能這部份內存區域的數據會被覆寫。當對一個已經釋放了的OC對象繼續調用實例方法時,在objc_msgSend函數內部讀取到obj的isa指針獲得的將是一個未知或者有可能無效的指針值。因此當對這個未知地址指向的內存進行訪問時就出現了上面的EXC_BAD_ACCESS的異常崩潰了。函數

CPU指令中操做寄存器和常數的指令通常不會產生崩潰異常,好比上面的第1,2,4,6條指令;而通常產生訪問異常的指令是發生在那些訪問內存地址的指令當中,好比第3條和5條。

也許你會好奇既然obj對象已經被釋放了,爲何崩潰會出如今objc_msgSend函數的第5條指令,其中的第3條指令是訪問對象的isa數據的,爲何不崩潰在這呢? 其實答案很簡單,由於幾乎全部的OC對象都是從堆內存區域中分配內存的,因此當某個OC對象被銷燬後,其所佔用的內存仍然會放回堆內存區域中進行管理,而堆內存區域的地址是能夠進行任意的讀寫訪問的,因此即便對象被銷燬釋放,仍然是能夠訪問對象所指向的內存區域的數據的。

應用程序出現崩潰異常時除了函數調用棧可提供分析參考外,還能夠從寄存器中的值來進行一步分析。根據上述的函數指令實現中能夠看出:

x0 寄存器中的保存的就是那個被銷燬了的對象指針。 x1 寄存器中保存的就是產生崩潰的對象的方法名稱的地址。 x13 寄存器中保存的就是對象的isa指針值。 x16 寄存器中保存的就是對象的Class指針對象。

函數崩潰處指令爲:

ldp x10, x11, [x16, #0x10] 
複製代碼

這時候由於x16中其實保存的是一個非法的Class對象指針地址了,因此當執行ldp指令來從x16所指向地址的偏移0x10處讀取內存數據時就產生了崩潰,而崩潰的異常代碼:

Exception Codes: 0x00000000 at 0x00000005710bbeb8
複製代碼

中的地址值也恰好和x16寄存器中的值是一致的。也就是代表x16中所保存的Class對象指針就是一個非法和無效的內存地址。

在全部的OC方法中若是你設置了符號斷點那麼在方法開始執行時x0中保存的老是執行方法的對象,也是第一個方法的參數;x1中老是保存的執行的方法的名稱字符串,也是第二個方法的參數;而後x2到x15有可能依次是方法的其餘參數。所以一般狀況下你能夠在調試控制檯中輸入: po $x0 來顯示對象信息, p (char*)$x1 來顯示方法名稱。 具體的詳細介紹能夠參考個人另一篇文章:寄存器介紹

上面的崩潰調用棧中,全部的函數和方法都是系統函數並無程序自身的源代碼,所以很難跟蹤或者發現問題產生的緣由,由於此時是沒法知道是哪一個類的對象執行方法調用而產生的crash了,惟一的線索就是x1寄存器中的值了。這個寄存器中的值保存的是調用的方法名, 它是一個SEL類型的數據,所以能夠根據x1中保存的方法名來進行反推,也就是從方法名來反推出產生崩潰的對象的類名。

x1寄存器中保存的方法的內存地址是存在於某個加載的庫Image的代碼段中,所以能夠在崩潰日誌的Binary Images列表中找到定義方法名的庫Image信息,Binary Images列表中的每一個庫Image都有這個庫加載的開始和結束地址以及路徑名稱,能夠很容易就從這些區間列表中找到x1寄存器所指的方法名到底屬於哪一個庫。就上面的例子來講能夠很明確的看到方法地址0x18eb89b7b是屬於:

0x18e03d000 -  0x18ede3fff  UIKit arm64  <314063bdf85f321d88d6e24a0de464a2> /System/Library/Frameworks/UIKit.framework/UIKit
複製代碼

也就是UIKit庫中定義的某個對象在執行x1所指的方法而產生了崩潰。有了這個更進一步的信息後就能夠在源代碼中進行檢查看看哪部分代碼調用到了產生崩潰的庫中所定義的對象了(固然UIKit這裏不具有表明性,實際中崩潰時方法名也許會在其餘的庫中)。這樣就從必定程度上可以縮小排查問題的範圍。

常見的崩潰異常分析定位方法

當出現了沒有上下文的崩潰異常調用棧時,並非對它一籌莫展。除了能夠根據異常類型(signal的類型)分析外,還能夠藉助搜索引擎以及一些常見的問題解答站點來尋找答案,固然還能夠藉助下面列出幾種定位和分析的方法:

1.開源代碼法

這個方法其實很簡單,蘋果其實開源了很是多的基礎庫的源代碼,所以當程序崩潰在這些開源的基礎庫上時就能夠去下載對應的基礎庫的源代碼進行閱讀。而後從源代碼上進行問題的分析,從而找到產生異常崩潰的緣由。你能夠從https://opensource.apple.com處去下載開源的最新的源代碼。這種方法的缺點是並非全部的代碼都是開源的,並且開源的代碼並不必定是你真機設備上運行的iOS版本。所以這種方法只能是一種輔助方法。

2.方法符號斷點法

採用這種方法時,確保你手頭上要有一臺和產生崩潰異常問題的操做系統版本相同的真機設備,以方便聯機調試和運行。你能夠在崩潰異常報告的:

OS Version:      iOS 10.3.3 (14G60)
複製代碼

部分看到產生異常的操做系統版本號,就如本文的例子裏面產生異常的操做系統版本號爲iOS 10.3.3。由於相同的操做系統版本號中全部庫中代碼實現的都是同樣的。若是實在沒有對應的版本號的設備則能夠試圖找一臺版本號最相近的設備。明確了操做系統版本和真機設備後再從代碼倉庫中檢出和你線上相同版本的應用程序的源代碼(假如崩潰調用棧中沒有任何咱們編寫的函數代碼則這個條件要求沒必要那麼嚴格)。並打開項目工程,而後爲產生崩潰的函數調用棧的棧頂函數或者方法名添加一個符號斷點。若是你不知道如何添加符號斷點請參考文章:blog.csdn.net/xuhen/artic… 或者查找關鍵字:「XCODE 符號斷點"。

設置符號斷點的方法或者函數名時能夠有以下的選擇:

  1. 若是產生崩潰的棧頂是一個OC對象的方法則能夠直接用這個類名和方法名來設置符號斷點。
  2. 若是產生崩潰的棧頂是一個通用的C函數好比objc_msgSend、free、objc_release則考慮用函數調用棧的第二層函數和方法名來設置符號斷點。好比文本例子中的-[UIWebDocumentView _updateSubviewCaches]方法。
  3. 若是產生崩潰的函數調用棧頂是一個沒有對外暴露的C函數,由於這種函數設置符號斷點的難度比交大,因此每每考慮採用函數調用棧的第二層函數或者方法名來作爲符號斷點。

設置符號斷點的目的是爲了在崩潰函數調用堆棧重現時,能在運行時的斷點處進行動態分析。當你設置了符號斷點後,若是程序邏輯運行到這個函數或者方法時,系統就會在設置的方法或者函數的第一條指令處中止下來。這時候就能夠查看此時的函數調用棧是否和產生崩潰時的調用棧相符,若是相符合那麼代表可以重現可能發生問題的邏輯了,若是斷點處的調用棧和產生崩潰的調用棧不相同,則可能須要讓程序繼續運行,以便下次在一樣斷點處時進行調用棧的比較,由於設置斷點的方法名並不必定只在一處被調用。

符號斷點的設置

當程序停在了設置符號斷點的函數或者方法的開始地址後,接下來就須要在這個方法內進行第二個斷點的設置,設置的地方就是崩潰函數調用棧中函數調用上層函數的偏移處,這個能夠在崩潰的報告中看到:

0   libobjc.A.dylib                 objc_msgSend + 16
1   UIKit                          -[UIWebDocumentView _updateSubviewCaches] + 40
複製代碼

也就是須要在_updateSubviewCaches函數的第11條指令或者函數的第40個偏移字節附近處添加一個斷點。這樣當程序運動到斷點處時就能夠在函數調用上層函數前查看各寄存器的值從而進行問題的定位和分析。

運行到產生崩潰異常的指令

通常狀況下崩潰函數棧報告中除棧頂函數外的每一層函數名後 + 的數字代表是在當前函數的對應的地址偏移處附近進行了上層函數的調用,也就是對應的地址偏移附近通常都會存在一條bl指令或者blr這兩條指令,這兩條指令的做用就是執行函數的調用。

經過二次斷點的設置,程序運行到斷點時的指令是:

0x18c0248fc <+36>: bl     0x1893042dc   ;0x1893042dc 這個地址就是objc_msgSend的函數地址
複製代碼

本例子的異常崩潰的緣由是對一個已經釋放的對象繼續調用方法而產生的崩潰。因此當斷點停在指令處時,咱們能夠在右下角的lldb控制檯中打印指令:

(lldb)po $x0
<__NSArrayM 0x1c044c2a0>(
<UIWebOverflowScrollView: 0x1281d7e00; frame = (0 0; 375 603); clipsToBounds = YES; gestureRecognizers = <NSArray: 0x1c0851190>; layer = <WebLayer: 0x1c4426ba0>; contentOffset: {0, 0}; contentSize: {375, 12810}; adjustedContentInset: {0, 0, 0, 0}>
)

(lldb) p (char*)$x1
(char *) $6 = 0x000000018cb9dd70 "release"
(lldb) 
複製代碼

能夠看出x0是一個數組對象,而x1中則是release方法。這樣就進一步明確了是對一個已經釋放了的數組對象調用了release方法而致使異常崩潰了。至於x0是一個什麼數組以及保存在哪裏,則能夠經過彙編指令中的x0寄存器的使用進行回溯往上查找指令來進一步分析了。其實這個問題若是進一步觀察就能夠看出:崩潰的線程並非出如今主線程,而是在一個工做線程中。而視圖的操做基本都應該放在主線程進行,所以當主線程的某些子視圖數組對象被釋放後,這裏又在輔助線程中進行讀取訪問,就出現了上面的異常崩潰問題了。

在函數調用bl或者blr指令處設置斷點後,由於根據ABI規則全部非浮點數的參數分別依次保存在x0,x1,....這些寄存器中。因此能夠在斷點處分別打印出這些寄存器的值就能夠知道函數調用前所傳遞的參數值了。這個方法很是有助於進行問題的定位和分析。

3.手動重現法

有時候即便你設置了符號斷點,場景依然沒法重現,這時候就須要採用一些特殊的手段,那就是手動的執行方法調用。實現方式很簡單就是在某個演示代碼中人爲的進行崩潰棧頂函數的調用。就好比上面的例子當[UIWebDocumentView _updateSubviewCaches]方法一直不被執行時,就能夠本身手動的去建立一個UIWebDocumentView對象,並手動的調用對應的方法_updateSubviewCaches便可。這裏存在的兩個問題是有可能這個類並無對外進行聲明,或者咱們並不知道方法的參數類型或者須要傳遞的值。對於第一個問題解決的方法能夠採用NSClassFromString來獲得類信息並進行對象建立。而第二個問題則能夠藉助一些工具好比class-dump或者一些其餘的手段來確認方法的參數個數和參數類型。總之,目的就是爲了可以進入函數的斷點,甚至均可以在不知道如何傳遞參數時將全部的參數都傳值爲0或者nil來臨時解決問題。下面就是模擬崩潰函數的調用實現代碼:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    
    //由於類名和方法名都未對外公開,咱們能夠藉助一些技術手段來讓某個特定的方法執行,目的是爲了可以進入到方法的內部實現。
    Class cls = NSClassFromString(@"UIWebDocumentView");
    id obj = [[cls alloc] init];
    SEL sel = sel_registerName("_updateSubviewCaches");
    [obj performSelector:sel];

   //...
}
複製代碼

測試代碼能夠寫在任何一個地方,這裏爲了方便就在程序啓動處加上測試代碼。等代碼編寫完畢後,就能夠爲方法設置符號斷點。這樣當程序一運行時就必定可以進入到這個函數的內部去。一旦函數被執行後出現了斷點,就能夠按照第2種方法中的介紹進行崩潰分析了。

其實第3種方法的原則就是隻要能讓產生崩潰異常的方法被調用,這其中能夠嘗試着採用各類手段將對象和方法run起來。

4.第三方工具靜態分析法

前面兩種介紹的都是動態分析法, 有時候還能夠藉助一些反編譯的工具來對程序代碼進行靜態分析。好比像Hopper或者IDA之類的工具。缺點就是這些工具是收費的,並且效果沒有動態分析那麼的好。在使用上我的以爲IDA分析工具更加友好和強大一些。

採用第三方工具時須要找到產生崩潰的函數所在的庫,函數所在的庫在崩潰的函數調用棧列表中就能找到了。若是崩潰函數是在應用程序自己中被定義,那麼須要將上傳到appstore的ipa文件解壓縮並提取出其中的可執行程序用工具打開便可。若是崩潰函數是在某個系統庫中被定義,那麼可從以下的路徑:

~/Library/Developer/Xcode/iOS DeviceSupport/

iOS DeviceSupport這個文件夾下的內容將展現你全部曾經聯機調試過的各類操做系統版本的庫的一份拷貝,若是你沒有真機調試過出現崩潰的操做系統版本,請找一個安裝了這個操做系統版本的真機設備,並聯機,這樣你的文件夾中就會有對應的操做系統版本下的系統庫的拷貝信息了。

UIKit庫的路徑

中找到對應的產生崩潰的手機操做系統版本號的庫文件:10.3.3(14G60)/Symbols/System/Library/Frameworks/UIKit.framework/UIKit

當用IDA工具打開對應的庫文件或者可執行文件時你看到的將是這個庫文件的全部彙編形式的代碼和數據。所以你能夠經過搜索菜單來查找產生崩潰的函數或者方法名。這時候你就能夠進一步對產生問題的函數的彙編代碼進行分析了。採用IDA工具進行彙編代碼分析的缺點是靜態分析沒法看到運行時的各個寄存器的真實的值,所以採用這種方法可能更須要考慮你對彙編代碼的理解能力。下面就是本文例子中的[UIWebDocumentView _updateSubviewCaches]方法的實現彙編代碼:

IDA工具查看_updateSubviewCaches的實現

採用IDA工具進行分析時,須要瞭解一些好比庫基地址和代碼數據偏移地址以及地址重定向相關的知識。蘋果系統爲安全對每一個庫的加載都採用了ASLR的方式,也就是庫所加載的基地址每次運行時都是隨機的,這樣當某次崩潰發生時須要將產生崩潰時的地址轉化爲咱們經過IDA工具打開的地址。 轉換公式爲:

轉換後的地址 = 崩潰時寄存器中保存的原始地址值 -  崩潰時地址所在的庫的基地址值 + 工具打開庫時所設定的基地址。
複製代碼

就以上面崩潰異常爲例,當咱們用IDA工具看看x1寄存器中的值究竟是一個什麼方法名,那麼只須要把x1的值(0x018eb89b7b),減去其所在的庫UIKit的基地址值(0x18e03d000),在加上IDA工具打開庫時的基地址(要想看基地址則滾動到IDA視圖的最開始部分,本次打開的基地址爲:0x187769000)。因此x1寄存器中的地址值被轉化後應該爲:

0x018eb89b7b -  0x18e03d000 + 0x187769000 = 0x1882B5B7B
複製代碼

在IDA工具中將地址跳轉到0x1882B5B7B就能夠看到本例子中產生崩潰的方法名是叫release:

致使崩潰異常的方法名

固然IDA工具是能夠手動進行基地址的自定義設置的,這樣就不須要進行計算以便和線上崩潰的基地址對齊。

若是你手頭上沒有第三方工具,其實系統內置的otools工具也能夠幫咱們進行問題的定位以及彙編代碼的查看和分析了,具體的方法你們就去查找相關的對otools使用的教程便可,這裏就不展開了。

總結

上面列出的全部分析方法中有靜態分析的也有動態分析。當出現了崩潰時除了從崩潰函數調用棧去分析問題,還能夠從寄存器,以及加載的鏡像列表,以及崩潰棧頂部的函數的彙編代碼等等進行綜合的分析和判斷。固然即便這樣也不能保證全部問題就必定可以獲得解決,本文中列舉的例子只是在實際中的一種很是常見的崩潰異常,但願經過這個示例來起到一個拋磚引玉的效果,畢竟不一樣的崩潰異常的差別是比較大的。遇到問題須要具體分析,走進函數的內部實現就必定可以找到產生問題的根源。

👉【返回目錄


歡迎你們訪問個人github地址

相關文章
相關標籤/搜索