异常捕获和分析

主要内容

  • 闪退捕获
  • 日志分析

闪退捕获

  • 内核级异常:Mach异常->Unit信号(Mach层捕获到异常通过发送信号到Unit层)
  • 应用层异常:NSException

本文实现方式:捕获Unit信号和捕获NSException

为什么要分两类

在没有设置捕获NSException时,因为某个NSException导致异常时,Mach异常或Unit信号也是可以捕获到的只是有些不完成。

捕获Unit信号获取的异常

reason:Signal SIGABRT was raise
callStackSymbols:
0 MTCrashCaughtDemo             0x00000001000c321c signalExceptionHandler + 196
1 libsystem_platform.dylib      0x0000000183a2193c _sigtramp + 52
2 libsystem_pthread.dylib       0x0000000183a28ef8 pthread_kill + 112
3 libsystem_c.dylib             0x00000001838cddc8 abort + 140
4 libc++abi.dylib               0x00000001834013f4 __cxa_bad_cast + 0
5 libc++abi.dylib               0x000000018341de98 <redacted> + 0
6 libobjc.A.dylib               0x0000000183428248 <redacted> + 124
7 libc++abi.dylib               0x000000018341af44 <redacted> + 16
8 libc++abi.dylib               0x000000018341ab10 __cxa_rethrow + 144
9 libobjc.A.dylib               0x0000000183428120 objc_exception_rethrow + 44
10 CoreFoundation               0x0000000183ca0cf8 CFRunLoopRunSpecific + 552
11 GraphicsServices             0x0000000185588088 GSEventRunModal + 180
12 UIKit                        0x0000000188f86088 UIApplicationMain + 204
13 MTCrashCaughtDemo            0x00000001000c45ac main + 124
14 libdyld.dylib                0x000000018383e8b8 <redacted> + 4

捕获NSException的异常

name:NSRangeException
reason:*** -[__NSArrayI objectAtIndex:]: index 1 beyond bounds [0 .. 0]
0 CoreFoundation      0x0000000183dc2dc8 <redacted> + 148
1 libobjc.A.dylib     0x0000000183427f80 objc_exception_throw + 56
2 CoreFoundation      0x0000000183ca3098 CFRunLoopRemoveTimer + 0
3 MTCrashCaughtDemo   0x000000010001c1e0 -[ViewController test3] + 192
4 MTCrashCaughtDemo   0x000000010001c088 -[ViewController touchUpInsideButton:] + 68
5 UIKit               0x0000000188f54be8 <redacted> + 100
6 UIKit               0x0000000188f54b64 <redacted> + 80
7 UIKit               0x0000000188f3c870 <redacted> + 436
8 UIKit               0x0000000188f54454 <redacted> + 572
9 UIKit               0x0000000188f54084 <redacted> + 804
10 UIKit              0x0000000188f4cc20 <redacted> + 784
11 UIKit              0x0000000188f1d04c <redacted> + 248
12 UIKit              0x0000000188f1b628 <redacted> + 6568
13 CoreFoundation     0x0000000183d7909c <redacted> + 24
14 CoreFoundation     0x0000000183d78b30 <redacted> + 540
15 CoreFoundation     0x0000000183d76830 <redacted> + 724
16 CoreFoundation     0x0000000183ca0c50 CFRunLoopRunSpecific + 384
17 GraphicsServices   0x0000000185588088 GSEventRunModal + 180
18 UIKit              0x0000000188f86088 UIApplicationMain + 204
19 MTCrashCaughtDemo  0x000000010001c564 main + 124
20 libdyld.dylib      0x000000018383e8b8 <redacted> + 4

PS:当同时设置捕获NSException和捕获Unix信号时,如果某个NSException出现异常,Unix信号那边是得不到回调的,所以不要担心同一个异常出现两份日志

捕获Unix信号实现

例如:捕获SIGABRT信号

注册监听信号

signal(SIGABRT, signalExceptionHandler)

注销监听信号

signal(SIGABRT, SIG_DFL)

读取异常数据

void signalExceptionHandler(int signal)
{
    NSMutableString *exceptionDescription = [NSMutableString string];
    [exceptionDescription appendFormat:@"reason:Signal %d was raise\n", signal];
    [exceptionDescription appendString:@"callStackSymbols:\n"];

     void *callstack[128];
     int frames = backtrace(callstack, 128);
     char **strs = backtrace_symbols(callstack, frames);
     for(int i = 0; i < frames; ++i)
     {
         [exceptionDescription appendFormat:@"%s\n", strs[i]];
     }
}

Unit信号定义在<sys/signal.h>中,至于每种信号对应的描述可以参考这里iOS异常捕获

捕获NSException实现

注册监听

NSSetUncaughtExceptionHandler(&uncaughtExceptionHandler)

读取异常数据

void uncaughtExceptionHandler(NSException *exception)
{
    NSMutableString *exceptionDescription = [NSMutableString string];
    [exceptionDescription appendFormat:@"name:%@\n", exception.name];
    [exceptionDescription appendFormat:@"reason:%@\n",exception.reason];

    for(NSString *symbol in exception.callStackSymbols)
    {
        [exceptionDescription appendFormat:@"%@\n", symbol];
    }
}

PS:Unix信号在Xcode是Run是读取不到的,可以先在Xcode上Run然后Stop,再到模拟器上打开Build的App

日志分析

日志的来源可以分两类

  • 苹果收集
  • 开发者收集

苹果收集日志(只提取了关键信息)

0 CoreFoundation          0x183dc2db0 __exceptionPreprocess + 124
1 libobjc.A.dylib         0x183427f80 objc_exception_throw + 56
2 CoreFoundation          0x183dc2c80 +[NSException raise:format:arguments:] + 108
3 Foundation              0x184748154 -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 112
4 UIKit                   0x189115808 -[UITableView _endCellAnimationsWithContext:] + 12624
5 UIKit                   0x1891301d0 -[UITableView _updateRowsAtIndexPaths:updateAction:withRowAnimation:] + 360
6 InterviewEvaluation     0x10006b3a0 0x100040000 + 177056
7 UIKit                   0x189053dc4 -[UITableView _selectRowAtIndexPath:animated:scrollPosition:notifyDelegate:] + 1316
8 UIKit                   0x1891117d4 -[UITableView _userSelectRowAtPendingSelectionIndexPath:] + 376
9 UIKit                   0x1891cf0c8 _runAfterCACommitDeferredBlocks + 292
10 UIKit                  0x1891dca80 _cleanUpAfterCAFlushAndRunDeferredBlocks + 92
11 UIKit                  0x188f0e5a4 _afterCACommitHandler + 96
12 CoreFoundation         0x183d78728 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 32
13 CoreFoundation         0x183d764cc __CFRunLoopDoObservers + 372
14 CoreFoundation         0x183d768fc __CFRunLoopRun + 928
15 CoreFoundation         0x183ca0c50 CFRunLoopRunSpecific + 384
16 GraphicsServices       0x185588088 GSEventRunModal + 180
17 UIKit                  0x188f86088 UIApplicationMain + 204
18 InterviewEvaluation    0x1000591f8 0x100040000 + 102904
19 libdyld.dylib          0x18383e8b8 start + 4

开发者收集日志(使用上面介绍的方式)

name:NSInternalInconsistencyException

reason:Invalid update: invalid number of rows in section 0. The number of rows contained in an 
existing section after the update (2) must be equal to the number of rows contained in that 
section before the update (1), plus or minus the number of rows inserted or deleted from that 
section (1 inserted, 1 deleted) and plus or minus the number of rows moved into or out of that 
section (0 moved in, 0 moved out).

0 CoreFoundation         0x0000000183dc2dc8 <redacted> + 148
1 libobjc.A.dylib        0x0000000183427f80 objc_exception_throw + 56
2 CoreFoundation         0x0000000183dc2c80 <redacted> + 0
3 Foundation             0x0000000184748154 <redacted> + 112
4 UIKit                  0x0000000189115808 <redacted> + 12624
5 UIKit                  0x00000001891301d0 <redacted> + 360
6 InterviewEvaluation    0x00000001001273a0 InterviewEvaluation + 177056
7 UIKit                  0x0000000189053dc4 <redacted> + 1316
8 UIKit                  0x00000001891117d4 <redacted> + 376
9 UIKit                  0x00000001891cf0c8 <redacted> + 292
10 UIKit                 0x00000001891dca80 <redacted> + 92
11 UIKit                 0x0000000188f0e5a4 <redacted> + 96
12 CoreFoundation        0x0000000183d78728 <redacted> + 32
13 CoreFoundation        0x0000000183d764cc <redacted> + 372
14 CoreFoundation        0x0000000183d768fc <redacted> + 928
15 CoreFoundation        0x0000000183ca0c50 CFRunLoopRunSpecific + 384
16 GraphicsServices      0x0000000185588088 GSEventRunModal + 180
17 UIKit                 0x0000000188f86088 UIApplicationMain + 204
18 InterviewEvaluation   0x00000001001151f8 InterviewEvaluation + 102904
19 libdyld.dylib         0x000000018383e8b8 <redacted> + 4

UUID: 5FFEDD96-E4E1-3008-8D92-D22C2054D9C8
loadAddress: 0x1000fc000

PS:是不是有种懵逼的感觉苹果收集的日志一到关键的地方就看不懂,至于开发者收集的就更看不懂了

关于dSYM文件

  • dSYM文件就是一个内存地址和函数的映射表
  • 每次Archive(打包)都会产生一个dSYM文件与ipa包对应

也就是说只要有dSYM文件就可以解析上面的内存地址了,那么dSYM怎么获取呢?

  1. 如果是脚本打包,完成之后,ipa包的旁边一般会有个dSYM文件
  2. 如果是Xcode打包,完成之后,在Xcode中工具栏上 Window->Organizeer->选择相应的App->Archives->选择相应的包 进入选中的包然后找dSYMs目录

PS:因为每次Archive生成的dSYM文件都是不一样的,当我们发布了很多版本后,获取到的日志匹配dSYM文件就麻烦了,所以我们需要使用一种方式把闪退日志和dSYM文件相互对应

关于UUID

  • 每个dSYM文件都有一个UUID
  • 每个ipa包也一个UUID

也就是说只要在产生日志时获取当前ipa包的UUID,并和日志一起上传就可以了

dSYM的UUID获取方式

命令:dwarfdump --uuid appName.app.dSYM

例如:dwarfdump --uuid /Users/mtry/Desktop/155401/InterviewEvaluation.app.dSYM
结果:UUID: E1F36C5C-5F16-3FC4-8F51-BE25B49C0B1D (armv7) 
     UUID: 5FFEDD96-E4E1-3008-8D92-D22C2054D9C8 (arm64)

开发者自己收集日志,出现闪退时获取当前ipa包的UUID,来自于stackoverflow

#import <mach-o/ldsyms.h>
NSString *executableUUID()
{
    const uint8_t *command = (const uint8_t *)(&_mh_execute_header + 1);
    for (uint32_t idx = 0; idx < _mh_execute_header.ncmds; ++idx) {
        if (((const struct load_command *)command)->cmd == LC_UUID) {
            command += sizeof(struct load_command);
            return [NSString stringWithFormat:@"%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X",
                command[0], command[1], command[2], command[3],
                command[4], command[5],
                command[6], command[7],
                command[8], command[9],
                command[10], command[11], command[12], command[13], command[14], command[15]];
        } else {
            command += ((const struct load_command *)command)->cmdsize;
        }
    }
    return nil;
}

苹果收集的日志,自动带上了UUID,可以在日志的里面找到比如

image_1.png

PS:图片标记了两个关键信息,一个UUID,另一个时加载地址后面会提到

苹果收集的日志解析

获取当前设备的闪退日志:连上你的Mac,在Xcode中工具栏 Window->Devices->DEVICES 中选择你的设备->View Device Logs

比如上面的闪退在匹配上dSYM文件之后解析的结果

image_2.png

获取苹果收集用户的闪退日志:在Xcode中工具栏 Window->Organizeer->选择你的App->Crashes->选择版本,就可以下载了

解析方式:

  1. 下载完的日志可以直接拖进上面一步的列表中进行解析
  2. 使用命令行工具symbolicatecrash,如果解析的日志很多建议使用这种,因为可以写脚本。具体使用看这里Symbolicating Your iOS Crash Reports

注意:

  1. 只有按照苹果要求的格式收集的日志才可以使用上面的方式解析,至于格式是什么样的看下苹果的日志就知道了
  2. 苹果收集普通用户的闪退日志需要用户在:设置->隐私->诊断于用量->自动发送->与应用开发者共享 是开启的,默认是关闭的,所以一些上线的App可以收集的闪退日志还是相当有限的。对于一些有需要TestFlight的App是非常好的,因为TestFlight的用户默认自动发送日志,具体可以看这里Analyzing Crash Reports
image_3.png

开发者收集的日志解析

使用苹果的atos命令

atos [-o AppName.app/AppName] [-l loadAddress] [-arch architecture] [address ...]

方式一:需要AppName.app和dSYM文件(AppName.ipa解压出AppName.app)

例如:atos -o /Users/mtry/Desktop/155401/InterviewEvaluation.app/InterviewEvaluation -l 0x1000fc000 -arch arm64 0x00000001001273a0
结果:-[IECandidateFilterPopView tableView:didSelectRowAtIndexPath:] (in InterviewEvaluation) (IECandidateFilterPopView.m:382)

方式二:只需要dSYM文件

例如:atos -o /Users/mtry/Desktop/155401/InterviewEvaluation.app.dSYM/Contents/Resources/DWARF/InterviewEvaluation -l 0x1000fc000 -arch arm64 0x00000001001273a0
结果:-[IECandidateFilterPopView tableView:didSelectRowAtIndexPath:] (in InterviewEvaluation) (IECandidateFilterPopView.m:382)

loadAddress地址的获取方式

方式一:内存地址 - 偏移量 = 加载地址(比如上面闪退日志)

0x00000001001273a0 - 0x2b3a0(177056) = 0x00000001001151f8 - 0x191f8(102904) = 0x1000fc000

方式二:代码实现来源于iOS Crash Report 的加载地址、dSYM 与 UUID

#include <mach-o/dyld.h>
uintptr_t get_load_address(void) {
    const struct mach_header *exe_header = NULL;
    for (uint32_t i = 0; i < _dyld_image_count(); i++) {
        const struct mach_header *header = _dyld_get_image_header(i);
        if (header->filetype == MH_EXECUTE) {
            exe_header = header;
            break;
        }
    }
    return (uintptr_t)exe_header;
}

GitHub具体实现

MTCrashCaught

参考资料

漫谈iOS Crash收集框架
iOS异常捕获
Analyzing Crash Reports
Understanding and Analyzing iOS Application Crash Reports
Symbolicating Your iOS Crash Reports
分析iOS Crash文件:符号化iOS Crash文件的3种方法
iOS Crash Report 的加载地址、dSYM 与 UUID

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容