點(diǎn)贊評(píng)論乃摹,感覺有用的朋友可以關(guān)注筆者公眾號(hào) iOS 成長(zhǎng)指北孵睬,持續(xù)更新
原書為 iOS Crash Dump Analysis Book掰读,已得作者授權(quán)蹈集,歡迎 star
在本章中,我們將研究應(yīng)用程序中止崩潰减响。
通過崩潰報(bào)告異常類型來區(qū)分這類崩潰—— EXC_CRASH (SIGABRT)
支示。
我們關(guān)注這些從網(wǎng)上收集的大量崩潰信息颂鸿。
一般原則
許多操作系統(tǒng)語(yǔ)言的支持模塊和庫(kù)都有用于檢測(cè)致命程序錯(cuò)誤的代碼据途。一旦觸發(fā)叙甸,操作系統(tǒng)將終止該應(yīng)用程序裆蒸,這會(huì)導(dǎo)致 SIGABRT
崩潰僚祷。
SIGABRT
并沒有特別的原因辙谜。所以我們將查看各種示例,以便我們可以分析它們出現(xiàn)的各種情況罐脊。
有時(shí)萍桌,這種崩潰會(huì)在崩潰報(bào)告的 Application Specific Information
區(qū)域中提供一些信息上炎。如果這不能揭示我們所需的詳細(xì)信息藕施,但通常可以找到引發(fā)崩潰的模塊润绵,并對(duì)代碼進(jìn)行逆向工程以了解具體是什么癥狀。
Kindle Create 崩潰
Kindle Create是一款應(yīng)用程序烦绳,作者可以利用手稿(比如 docx
文件)創(chuàng)建電子書径密。它通過 QuartzCore 庫(kù)進(jìn)行大量的繪制躺孝。
在發(fā)布預(yù)覽時(shí)植袍,它發(fā)生崩潰并產(chǎn)生以下崩潰報(bào)告于个,為便于演示厅篓,將其截?cái)啵?/p>
Process: Kindle Create [3010]
Path: /Applications/Kindle Create.app/
Contents/MacOS/Kindle Create
Identifier: com.amazon.kc
Version: 1.10 (1.10)
Code Type: X86 (Native)
Parent Process: ??? [1]
Responsible: Kindle Create [3010]
User ID: 501
Crashed Thread: 16 Dispatch queue:
com.apple.root.default-qos
Exception Type: EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note: EXC_CORPSE_NOTIFY
Application Specific Information:
abort() called
Application Specific Signatures:
Graphics kernel error: 0xfffffffb
Thread 16 Crashed:: Dispatch queue: com.apple.root.default-qos
0 libsystem_kernel.dylib 0xa73e7ed6
__pthread_kill + 10
1 libsystem_pthread.dylib 0xa75a0427
pthread_kill + 363
2 libsystem_c.dylib 0xa7336956
abort + 133
3 libGPUSupportMercury.dylib 0xa2aa342d
gpusGenerateCrashLog + 160
4 com.apple.AMDRadeonX4000GLDriver 0x180cbb00
gpusKillClientExt + 23
5 libGPUSupportMercury.dylib 0xa2aa4857
gpusSubmitDataBuffers + 157
6 com.apple.AMDRadeonX4000GLDriver 0x180a293c
glrATI_Hwl_SubmitPacketsWithToken + 143
7 com.apple.AMDRadeonX4000GLDriver 0x180fd9b0
glrFlushContextToken + 68
8 libGPUSupportMercury.dylib 0xa2aa88c8
gldFlushContext + 24
9 GLEngine 0x9b416f5b
glFlushRender_Exec + 37
10 com.apple.QuartzCore 0x9c1c8412
CA::(anonymous namespace)::IOSurface::detach() + 166
11 com.apple.QuartzCore 0x9c1c7631
CAOpenGLLayerDraw(CAOpenGLLayer*, double, CVTimeStamp const*,
unsigned int) + 1988
12 com.apple.QuartzCore 0x9c1c6c9a
-[CAOpenGLLayer _display] + 618
13 com.apple.QuartzCore 0x9c179f62
-[CALayer display] + 158
14 com.apple.AppKit 0x916106ac
-[NSOpenGLLayer display] + 305
15 com.apple.QuartzCore 0x9c1c9f77
display_callback(void*, void*) + 59
16 com.apple.QuartzCore 0x9c1c9efa
CA::DispatchGroup::dispatch(bool) + 88
17 com.apple.QuartzCore 0x9c1c9e9a
CA::DispatchGroup::callback_0(void*) + 16
18 libdispatch.dylib 0xa72565dd
_dispatch_client_callout + 50
19 libdispatch.dylib 0xa7263679
_dispatch_queue_override_invoke + 779
20 libdispatch.dylib 0xa725818b
_dispatch_root_queue_drain + 660
21 libdispatch.dylib 0xa7257ea5
_dispatch_worker_thread3 + 100
22 libsystem_pthread.dylib 0xa759cfa5
_pthread_wqthread + 1356
23 libsystem_pthread.dylib 0xa759ca32
start_wqthread + 34
Thread 16 crashed with X86 Thread State (32-bit):
eax: 0x00000000 ebx: 0xb0a79000 ecx: 0xb0a78acc
edx: 0x00000000
edi: 0xa75a02ca esi: 0x0000002d ebp: 0xb0a78af8
esp: 0xb0a78acc
ss: 0x00000023 efl: 0x00000206 eip: 0xa73e7ed6
cs: 0x0000000b
ds: 0x00000023 es: 0x00000023 fs: 0x00000023
gs: 0x0000000f
cr2: 0xa9847340
Logical CPU: 0
Error Code: 0x00080148
Trap Number: 132
Binary Images:
0x18099000 - 0x1815efff com.apple.AMDRadeonX4000GLDriver
(1.68.20 - 1.6.8)
<DF3BB959-0C0A-3B6C-8E07-11B332128555>
/System/Library/Extensions/AMDRadeonX4000GLDriver.bundle/
Contents/MacOS/AMDRadeonX4000GLDriver
0xa2aa2000 - 0xa2aacfff libGPUSupportMercury.dylib
(16.7.4)
<C71E29CF-D4C5-391D-8B7B-739FB0536387>
/System/Library/PrivateFrameworks/GPUSupport.framework/
Versions/A/Libraries/libGPUSupportMercury.dylib
從堆棧回溯中可以看到 OpenGL 管道已刷新澳盐。
這導(dǎo)致 com.apple.AMDRadeonX4000GLDriver
檢測(cè)到命令問題并觸發(fā)崩潰洞就。 我們看到該代碼為崩潰報(bào)告提供了自定義信息旬蟋。
3 libGPUSupportMercury.dylib 0xa2aa342d
gpusGenerateCrashLog + 160
我們可以在這里使用 Hopper 脫殼和逆向工程工具倾贰。
通過在我們的Mac上找到二進(jìn)制文件,我們可以要求Hopper不僅分解有問題的代碼安寺,而且還生成偽代碼挑庶。對(duì)于大多數(shù)開發(fā)人員來說迎捺,很難立即了解匯編代碼查排,因?yàn)楫?dāng)今很少使用匯編代碼跋核。這就是偽代碼最有價(jià)值的原因砂代。
我們首先從崩潰報(bào)告的 Binary Images
部分找到二進(jìn)制文件的位置泊藕。
/System/Library/PrivateFrameworks/GPUSupport.framework/
Versions/A/Libraries/libGPUSupportMercury.dylib
對(duì)于系統(tǒng)二進(jìn)制文件而言娃圆,遍歷文件層次結(jié)構(gòu)可能會(huì)很麻煩,因?yàn)樗鼈兩钌畹厍短自谖募到y(tǒng)中撩鹿。
如果 Hopper 已經(jīng)正在運(yùn)行节沦,那么快速選擇正確文件的方法是使用命令行甫贯。
'/Applications/Hopper Disassembler v4.app/Contents/
MacOS/hopper' -e /System/Library/PrivateFrameworks/
GPUSupport.framework/Versions/A/Libraries/
libGPUSupportMercury.dylib
如果Hopper沒有運(yùn)行叫搁,我們可以啟動(dòng)它渴逻。我們可以啟動(dòng) Finder 程序并選擇 'Go To Folder' 以選擇文件夾
/System/Library/PrivateFrameworks/GPUSupport.framework/
Versions/A/Libraries/
然后惨奕,我們可以簡(jiǎn)單地將libGPUSupportMercury.dylib
從 Finder 中拖到 Hopper 應(yīng)用的主面板中梨撞,它將開始處理文件聋袋。
我們需要選擇要脫殼的體系結(jié)構(gòu)幽勒。 它必須與我們正在診斷的內(nèi)容匹配啥容。從崩潰報(bào)告中我們可以看到它是 Code Type
X86 (Native)
咪惠,這意味著我們需要在 Hopper 中選擇32位體系結(jié)構(gòu)選項(xiàng)遥昧。
然后我們點(diǎn)擊 Next炭臭,然后點(diǎn)擊 OK
片刻之后鞋仍,文件將被處理威创。 然后我們可以選擇 Navigate -> Go To Address or Symbol 并提供地址 _gpusGenerateCrashLog
肚豺。注意界拦,下劃線是在前面的。 C 編譯器會(huì)在生成目標(biāo)文件之前自動(dòng)將其放入在跳。 在過去猫妙,這樣做是為了使手寫的匯編代碼在鏈接期間不會(huì)與C 語(yǔ)言符號(hào)沖突割坠。
在默認(rèn)視圖中彼哼,Hopper 將顯示該功能的反匯編代碼敢朱。
通過選擇偽代碼按鈕(用紅色圓圈顯示)摩瞎,我們可以使 Hopper 對(duì)該功能生成更容易理解的描述拴签。
這是 Hopper 的輸出:
int _gpusGenerateCrashLog(int arg0, int arg1, int arg2) {
rdi = arg0;
r14 = arg2;
rbx = arg1;
if (*0xc678 != 0x0) {
rax = *___stack_chk_guard;
if (rax != *___stack_chk_guard) {
rax = __stack_chk_fail();
}
}
else {
if (rdi != 0x0) {
IOAccelDeviceGetName(*(rdi + 0x230), 0x0, 0x14);
}
if ((rbx & 0x20000000) == 0x0) {
rdx =
"Graphics kernel error: 0x%08x\n";
}
else {
rdx =
"Graphics hardware encountered an error and was reset:
0x%08x\n";
}
sprintf_l(var_A0, 0x0, rdx);
*0xc680 = var_A0;
rax = abort();
}
return rax;
}
在這里,我們可以看到兩種報(bào)文旗们。一種是 :
"Graphics kernel error: 0x%08x\n"
另一種是:
"Graphics hardware encountered an error and was reset: 0x%08x\n"
實(shí)際上蚓哩,我們?cè)诒罎?bào)告中看到以下內(nèi)容:
Application Specific Signatures:
Graphics kernel error: 0xfffffffb
不幸的是,尚不清楚此錯(cuò)誤是什么意思上渴。 我們需要應(yīng)用程序的作者打開OpenGL命令級(jí)日志記錄岸梨,以便了解圖形驅(qū)動(dòng)程序拒絕了哪些繪圖命令。
通過使用不同的 Mac 和顯卡配置將會(huì)是實(shí)驗(yàn)變得很有趣曹阔。讓我們判斷這是一個(gè)是特定的驅(qū)動(dòng)問題,或一個(gè)通用的 OpenGL 問題括袒。
類型混淆
編譯器在靜態(tài)類型檢查方面做得非常出色次兆。但在動(dòng)態(tài)推斷類型時(shí),可能會(huì)出現(xiàn)問題锹锰。而 配置文件在這方面尤為麻煩芥炭。 很容易為配置參數(shù)設(shè)置錯(cuò)誤的類型
我們借助示例代碼icdab_nsdata
來說明我們的觀點(diǎn)。
從配置文件中獲取 NSData 對(duì)象
思考以下示例代碼
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application
// launch.
NSData *myToken = [[NSData alloc] initWithData:
[[NSUserDefaults standardUserDefaults]
objectForKey:@"SomeKey"]];
NSLog(@"My data is %@ - ok since we can handle a nil",
myToken);
id stringProperty = @"Some string";
NSData *problemToken = [[NSData alloc]
initWithData:stringProperty];
NSLog(@"My data is %@ - we have probably crashed by now",
problemToken);
return YES;
}
這段代碼試圖做兩件事恃慧。 首先园蝠,它嘗試從其配置中獲取 token
。我們假設(shè)在之前的運(yùn)行中痢士,用戶已經(jīng)以 SomeKey
為鍵值保存了一個(gè) NSData
格式的 token
彪薛。
按照設(shè)計(jì)茂装, NSData
對(duì)象可以處理所提供的數(shù)據(jù)是否為 nil
的情況。 因此善延,如果尚未保存數(shù)據(jù)少态,代碼仍可正常運(yùn)行。
token
可能只是一個(gè)簡(jiǎn)單的十六進(jìn)制字符串易遣,例如 7893883873a705aec69e2942901f20d7b1e28dec
上面的代碼中有一個(gè)字符串 stringProperty
彼妻,用于模擬以下情況:用戶存存儲(chǔ)的 token
是一個(gè)字符串而不是 NSData
對(duì)象。
可能是它被手動(dòng)復(fù)制并粘貼到用戶的 plist
文件中豆茫。 如果 initWithData
方法參數(shù)為 NSString
侨歉,那么就無法創(chuàng)建 NSData
對(duì)象。 然后發(fā)生了崩潰揩魂。
如果我們運(yùn)行該代碼幽邓,我們將得到以下崩潰報(bào)告,為了便于演示火脉,將其截?cái)啵?/p>
反序列化崩潰報(bào)告
Incident Identifier: 12F72C5C-E9BD-495F-A017-832E3BBF285E
CrashReporter Key: 56ec2b40764a1453466998785343f1e51c8b3849
Hardware Model: iPod5,1
Process: icdab_nsdata [324]
Path: /private/var/containers/Bundle/Application/
98F79023-562D-4A76-BC72-5E56D378AD98/
icdab_nsdata.app/icdab_nsdata
Identifier: www.perivalebluebell.icdab-nsdata
Version: 1 (1.0)
Code Type: ARM (Native)
Parent Process: launchd [1]
Exception Type: EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Exception Note: EXC_CORPSE_NOTIFY
Triggered by Thread: 0
Filtered syslog:
None found
Last Exception Backtrace:
0 CoreFoundation 0x25aa3916
__exceptionPreprocess + 122
1 libobjc.A.dylib 0x2523ee12
objc_exception_throw + 33
2 CoreFoundation 0x25aa92b0
-[NSObject+ 1045168 (NSObject) doesNotRecognizeSelector:]
+ 183
3 CoreFoundation 0x25aa6edc
___forwarding___ + 695
4 CoreFoundation 0x259d2234
_CF_forwarding_prep_0 + 19
5 Foundation 0x2627e9a0
-[_NSPlaceholderData initWithData:] + 123
6 icdab_nsdata 0x000f89ba
-[AppDelegate application:
didFinishLaunchingWithOptions:] + 27066 (AppDelegate.m:26)
7 UIKit 0x2a093780
-[UIApplication _handleDelegateCallbacksWithOptions:
isSuspended:restoreState:] + 387
8 UIKit 0x2a2bb2cc
-[UIApplication _callInitializationDelegatesForMainScene:
transitionContext:] + 3075
9 UIKit 0x2a2bf280
-[UIApplication _runWithMainScene:transitionContext:
completion:] + 1583
10 UIKit 0x2a2d3838
__84-[UIApplication _handleApplicationActivationWithScene:
transitionContext:completion:]_block_invoke3286 + 31
11 UIKit 0x2a2bc7ae
-[UIApplication workspaceDidEndTransaction:] + 129
12 FrontBoardServices 0x27146c02
__FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 13
13 FrontBoardServices 0x27146ab4
-[FBSSerialQueue _performNext] + 219
14 FrontBoardServices 0x27146db4
-[FBSSerialQueue _performNextFromRunLoopSource] + 43
15 CoreFoundation 0x25a65dfa
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
+ 9
16 CoreFoundation 0x25a659e8
__CFRunLoopDoSources0 + 447
17 CoreFoundation 0x25a63d56
__CFRunLoopRun + 789
18 CoreFoundation 0x259b3224
CFRunLoopRunSpecific + 515
19 CoreFoundation 0x259b3010
CFRunLoopRunInMode + 103
20 UIKit 0x2a08cc38
-[UIApplication _run] + 519
21 UIKit 0x2a087184
UIApplicationMain + 139
22 icdab_nsdata 0x000f8830
main + 26672 (main.m:14)
23 libdyld.dylib 0x2565b86e tlv_get_addr
+ 41
不幸的是牵舵,這種崩潰沒有將更多的有用信息(例如“特定于應(yīng)用程序的信息”)注入到崩潰報(bào)告中。
但是忘分,我們確實(shí)會(huì)在系統(tǒng)日志(應(yīng)用程序的控制臺(tái)日志)中獲取信息:
default 13:36:58.000000 +0000 icdab_nsdata
My data is <> - ok since we can handle a nil
default 13:36:58.000000 +0100 icdab_nsdata
-[__NSCFConstantString _isDispatchData]:
unrecognized selector sent to instance 0x3f054
default 13:36:58.000000 +0100 icdab_nsdata
*** Terminating app due to uncaught exception
'NSInvalidArgumentException', reason:
'-[__NSCFConstantString _isDispatchData]:
unrecognized selector sent to instance 0x3f054'
*** First throw call stack:
(0x25aa391b 0x2523ee17 0x25aa92b5 0x25aa6ee1 0x259d2238
0x2627e9a5 0x3d997
0x2a093785 0x2a2bb2d1 0x2a2bf285 0x2a2d383d 0x2a2bc7b3
0x27146c07
0x27146ab9 0x27146db9 0x25a65dff 0x25a659ed 0x25a63d5b
0x259b3229
0x259b3015 0x2a08cc3d 0x2a087189 0x3d80d 0x2565b873)
default 13:36:58.000000 +0100
SpringBoard Application
'UIKitApplication:www.perivalebluebell.icdab-nsdata[0x51b9]'
crashed.
default 13:36:58.000000 +0100
UserEventAgent
2769630555571: id=www.perivalebluebell.icdab-nsdata
pid=386, state=0
default 13:36:58.000000 +0000 ReportCrash
Formulating report for corpse[386] icdab_nsdata
default 13:36:58.000000 +0000 ReportCrash
Saved type '109(109_icdab_nsdata)'
report (2 of max 25) at
/var/mobile/Library/Logs/CrashReporter/
icdab_nsdata-2018-07-27-133658.ips
從這里我們可以看到問題是 __NSCFConstantString
無法響應(yīng) _isDispatchData
是因?yàn)?NSString
不是數(shù)據(jù)所提供的對(duì)象棋枕。
Apple SDK 具有私有實(shí)現(xiàn)類,以支持我們使用的公共對(duì)象妒峦。 錯(cuò)誤報(bào)告將引用這些私有類。 因此兵睛,他們的名字可能不再令人熟悉肯骇。
可以通過一種簡(jiǎn)單的方法進(jìn)行管理,并找出具體的表示映射到要搜索的類類型定義的哪個(gè)對(duì)象祖很。
方便的是笛丙,其他工程師已經(jīng)在框架上使用 class-dump
工具來生成所有 Objective-C 的類定義,并將它們存儲(chǔ)在 GitHub上假颇。他們使用 class-dump
工具胚鸯。這使得 Objective-C 的所有私有框架符號(hào)都很容易搜索到。
我們可以找到關(guān)于 _isDispatchData
的定義笨鸡。
/* Generated by RuntimeBrowser
Image: /System/Library/Frameworks/Foundation.framework/
Foundation
*/
@interface _NSDispatchData : NSData
+ (bool)supportsSecureCoding;
- (bool)_allowsDirectEncoding;
- (id)_createDispatchData;
- (bool)_isDispatchData;
- (Class)classForCoder;
- (id)copyWithZone:(struct _NSZone { }*)arg1;
- (void)encodeWithCoder:(id)arg1;
- (void)enumerateByteRangesUsingBlock:(id /* block */)arg1;
- (void)getBytes:(void*)arg1;
- (void)getBytes:(void*)arg1 length:(unsigned long long)arg2;
- (void)getBytes:(void*)arg1 range:(struct _NSRange
{ unsigned long long x1; unsigned long long x2; })arg2;
- (unsigned long long)hash;
- (id)initWithCoder:(id)arg1;
- (id)subdataWithRange:(struct _NSRange
{ unsigned long long x1; unsigned long long x2; })arg1;
@end
同樣姜钳,我們可以查找 __NSCFConstantString
。
/* Generated by RuntimeBrowser
Image: /System/Library/Frameworks/CoreFoundation.framework/
CoreFoundation
*/
@interface __NSCFConstantString : __NSCFString
- (id)autorelease;
- (id)copyWithZone:(struct _NSZone { }*)arg1;
- (bool)isNSCFConstantString__;
- (oneway void)release;
- (id)retain;
- (unsigned long long)retainCount;
@end