手把手教你檢視和分析iOS的crash崩潰

歐陽大哥2013發表於2018-09-05

要學會看crash崩潰和報告

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

當前比較流行的crash收集分析工具很多都是基於開源的KSCrash程式碼來進行封裝和改進的。蘋果自身也構建了一套crash採集和分析的機制,你可以從真機的聯機日誌或者從開發者賬號中去檢視對應的crash資訊。網路上也有很多關於crash分析的文章,以及crash堆疊符號化處理的文章。這裡假定你已經瞭解了一些檢視crash報告的方法和技巧以及一些簡單的crash分析技巧,因為這些是作為開發者需要具備的技能之一。

一個objc_msgSend+16崩潰棧

應用程式出現的crash崩潰異常有一些能夠簡單的被分析和解決,往往這些crash崩潰異常都會帶有明確的上下文資訊和函式呼叫層級堆疊。但並不是所有的crash崩潰異常都能被簡單的解決,尤其是那些沒有明確上下文資訊的函式呼叫堆疊或者那些呼叫堆疊中沒有一個函式或者方法能夠被直接定位到原始碼的場景,就如下面這個崩潰的函式呼叫棧(部分資訊):

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條指令處,可以來看看這個函式實現的彙編程式碼指令開頭片段:

;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物件繼續呼叫了例項方法而導致的。因此當出現這種型別的崩潰時,不管是否有明確上下文,其原因都是一致的。下面這張圖就能很清楚的說明其中的原因了:

物件被銷燬前後記憶體佈局對比圖

實際上在arm64位系統中isa中儲存的並不是物件的Class物件地址,上面的圖目的是為了更加直觀的顯示問題原因。

一個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地址

相關文章