[译]《iOS Crash Dump Analysis》- 应用程序中止崩溃

点赞评论,感觉有用的朋友可以关注笔者公众号 iOS 成长指北,持续更新
原书为 iOS Crash Dump Analysis Book,已得作者授权,欢迎 star

在本章中,我们将研究应用程序中止崩溃。

通过崩溃报告异常类型来区分这类崩溃—— EXC_CRASH (SIGABRT)

我们关注这些从网上收集的大量崩溃信息。

一般原则

许多操作系统语言的支持模块和库都有用于检测致命程序错误的代码。一旦触发,操作系统将终止该应用程序,这会导致 SIGABRT 崩溃。

SIGABRT 并没有特别的原因。所以我们将查看各种示例,以便我们可以分析它们出现的各种情况。

有时,这种崩溃会在崩溃报告的 Application Specific Information 区域中提供一些信息。如果这不能揭示我们所需的详细信息,但通常可以找到引发崩溃的模块,并对代码进行逆向工程以了解具体是什么症状。

Kindle Create 崩溃

Kindle Create是一款应用程序,作者可以利用手稿(比如 docx 文件)创建电子书。它通过 QuartzCore 库进行大量的绘制。

在发布预览时,它发生崩溃并产生以下崩溃报告,为便于演示,将其截断:

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)
 
  /System/Library/Extensions/AMDRadeonX4000GLDriver.bundle/
  Contents/MacOS/AMDRadeonX4000GLDriver

0xa2aa2000 - 0xa2aacfff  libGPUSupportMercury.dylib
 (16.7.4)
 
  /System/Library/PrivateFrameworks/GPUSupport.framework/
  Versions/A/Libraries/libGPUSupportMercury.dylib

从堆栈回溯中可以看到 OpenGL 管道已刷新。
这导致 com.apple.AMDRadeonX4000GLDriver 检测到命令问题并触发崩溃。 我们看到该代码为崩溃报告提供了自定义信息。

3   libGPUSupportMercury.dylib      0xa2aa342d
 gpusGenerateCrashLog + 160

我们可以在这里使用 Hopper 脱壳和逆向工程工具。

通过在我们的Mac上找到二进制文件,我们可以要求Hopper不仅分解有问题的代码,而且还生成伪代码。对于大多数开发人员来说,很难立即了解汇编代码,因为当今很少使用汇编代码。这就是伪代码最有价值的原因。

我们首先从崩溃报告的 Binary Images 部分找到二进制文件的位置。

/System/Library/PrivateFrameworks/GPUSupport.framework/
Versions/A/Libraries/libGPUSupportMercury.dylib

对于系统二进制文件而言,遍历文件层次结构可能会很麻烦,因为它们深深地嵌套在文件系统中。

如果 Hopper 已经正在运行,那么快速选择正确文件的方法是使用命令行。

'/Applications/Hopper Disassembler v4.app/Contents/
MacOS/hopper' -e /System/Library/PrivateFrameworks/
GPUSupport.framework/Versions/A/Libraries/
libGPUSupportMercury.dylib

如果Hopper没有运行,我们可以启动它。我们可以启动 Finder 程序并选择 'Go To Folder' 以选择文件夹

/System/Library/PrivateFrameworks/GPUSupport.framework/
Versions/A/Libraries/
finder_support_mercury.png

然后,我们可以简单地将libGPUSupportMercury.dylib从 Finder 中拖到 Hopper 应用的主面板中,它将开始处理文件。

drag_file_to_hopper.png

我们需要选择要脱壳的体系结构。 它必须与我们正在诊断的内容匹配。从崩溃报告中我们可以看到它是 Code Type X86 (Native),这意味着我们需要在 Hopper 中选择32位体系结构选项。

hopper_32bit.png

然后我们点击 Next,然后点击 OK

片刻之后,文件将被处理。 然后我们可以选择 Navigate -> Go To Address or Symbol 并提供地址 _gpusGenerateCrashLog 。注意,下划线是在前面的。 C 编译器会在生成目标文件之前自动将其放入。 在过去,这样做是为了使手写的汇编代码在链接期间不会与C 语言符号冲突。

在默认视图中,Hopper 将显示该功能的反汇编代码。

hopper_diss.png

通过选择伪代码按钮(用红色圆圈显示),我们可以使 Hopper 对该功能生成更容易理解的描述。

hopper_pseudocode.png

这是 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;
}

在这里,我们可以看到两种报文。一种是 :

"Graphics kernel error: 0x%08x\n"

另一种是:

"Graphics hardware encountered an error and was reset: 0x%08x\n"

实际上,我们在崩溃报告中看到以下内容:

Application Specific Signatures:
Graphics kernel error: 0xfffffffb

不幸的是,尚不清楚此错误是什么意思。 我们需要应用程序的作者打开OpenGL命令级日志记录,以便了解图形驱动程序拒绝了哪些绘图命令。

通过使用不同的 Mac 和显卡配置将会是实验变得很有趣。让我们判断这是一个是特定的驱动问题,或一个通用的 OpenGL 问题。

类型混淆

编译器在静态类型检查方面做得非常出色。但在动态推断类型时,可能会出现问题。而 配置文件在这方面尤为麻烦。 很容易为配置参数设置错误的类型

我们借助示例代码icdab_nsdata来说明我们的观点。

从配置文件中获取 NSData 对象

思考以下示例代码

- (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。我们假设在之前的运行中,用户已经以 SomeKey为键值保存了一个 NSData 格式的 token

按照设计, NSData 对象可以处理所提供的数据是否为 nil 的情况。 因此,如果尚未保存数据,代码仍可正常运行。

token可能只是一个简单的十六进制字符串,例如 7893883873a705aec69e2942901f20d7b1e28dec

上面的代码中有一个字符串 stringProperty,用于模拟以下情况:用户存存储的 token 是一个字符串而不是 NSData对象。
可能是它被手动复制并粘贴到用户的 plist 文件中。 如果 initWithData 方法参数为 NSString,那么就无法创建 NSData 对象。 然后发生了崩溃。

如果我们运行该代码,我们将得到以下崩溃报告,为了便于演示,将其截断:

反序列化崩溃报告

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

不幸的是,这种崩溃没有将更多的有用信息(例如“特定于应用程序的信息”)注入到崩溃报告中。

但是,我们确实会在系统日志(应用程序的控制台日志)中获取信息:

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 无法响应 _isDispatchData 是因为 NSString 不是数据所提供的对象。

Apple SDK 具有私有实现类,以支持我们使用的公共对象。 错误报告将引用这些私有类。 因此,他们的名字可能不再令人熟悉。

可以通过一种简单的方法进行管理,并找出具体的表示映射到要搜索的类类型定义的哪个对象。

方便的是,其他工程师已经在框架上使用 class-dump 工具来生成所有 Objective-C 的类定义,并将它们存储在 GitHub上。他们使用 class-dump工具。这使得 Objective-C 的所有私有框架符号都很容易搜索到。

我们可以找到关于 _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

你可能感兴趣的:([译]《iOS Crash Dump Analysis》- 应用程序中止崩溃)