Xcode 调试及crash分析

LLVM简介

XCode4.0以后,LLVM是构架编译器(compiler)的框架系统,以C++编写而成,用于优化以任意程序语言编写的程序的编译时间(compile-time)、链接时间(link-time)、运行时间(run-time)以及空闲时间(idle-time),对开发者保持开放,并兼容已有脚本。简单的说,编译器有两个职责:把 Objective-C 代码转化成低级代码,以及对代码做分析,确保代码中没有任何明显的错误

我们可以认为LLVM是一个完整的编译器架构,也可以认为它是一个用于开发编译器、解释器相关的库

Clang是一个C++编写、基于LLVM、发布于LLVM BSD许可证下的C/C++/Objective-C/Objective-C++编译器

LLVM和Clang的关系

Clang其实大致上可以对应到编译器的前端,主要处理一些和具体机器无关的针对语言的分析操作;编译器的优化器部分和后端部分是LLVM后端(狭义的LLVM);而整体的Compiler架构就是LLVM架构

  • 总结:LLVM是Apple官方支持的编译器,而该编译器的前端是Clang,这两个工具都被集成到了Xcode里面

LLDB

LLDB调试器已经取代了GDB,成为了Xcode工程中默认的调试器。它与LLVM编译器一起,带给我们更丰富的流程控制和数据检测的调试功能。LLDB为Xcode提供了底层调试环境,其中包括内嵌在Xcode IDE中的位于调试区域的控制面板,在这里我们可以直接调用LLDB命令

1.Xcode控制台调试命令

想要看所有的调试命令,可以输入help,就会列出所有的调试命令

调试器命令.png
命令缩写.png

更详细的内容可以查看The LLDB Debugger

几个常用命令

p 命令是 print 命令的简写,使用p 命令可以查看基本数据类型的值,但是如果 使用 p 命令 查看的是对象,那么只会返回对象的指针地址。

p 命令后面除了可以接 变量、常量,还可以接 表达式。(但是不可以使用宏)

po 命令可以理解为打印对象。功能与 p 命令类似,所以也是可以打印 常量、变量,打印表达式返回的对象等(也不可以打印宏)


expr 是 expression 的简写, 使用expr 命令,能够在调试时,动态的执行赋值表达式,同时打印出结果

bt命令 可以打印出当前线程的堆栈信息

bt all 命令是打印所有线程的堆栈信息

2. log 打印

在debug模式下输出数据,并且输出在哪个类,第几行

#ifdef DEBUG

#   define NSLog(fmt, ...) {NSLog((@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__);}
#else
#   define NSLog(...)
#endif
__PRETTY_FUNCTION__跟__FUNCTION__的功能相同.提供类名和函数名称的输出

__LINE__     就是该行代码行号,用于日志中,方便查错

__VA_ARGS_  宏定义中参数列表的最后一个参数为省略号(也就是三个点)。这样预定义宏_ _VA_ARGS_ _就可以被用在替换部分中,以表示省略号代表什么

3. 关于断点调试

Xcode 中的断点有普通断点、条件断点、符号断点、异常断点等很多种

  • 普通断点

    在对应行打断点是调试时最常用的调试方法

  • 条件断点

    设定条件可以控制代码在满足条件时断住,如在for 循环中。如果我们需要在i = 3 时添加断点,其他时候不加,那么就可以使用条件断点

  • 符号断点

    其实是针对某一个特定函数的断点

  • 异常断点

    打一个异常断点,这样崩溃时就会触发断点,很容易定位到问题所在,也能看到更多的崩溃相关信息,如Log,函数调用栈

    Objective-C中处理异常是依赖于NSException实现的,它是异常处理的基类,它是一个实体类,而并非一个抽象类,所以你可以直接使用它或者继承它扩展使用,其实控制台输出的日志信息就是NSException产生的(如下)

当某段代码可能存在崩溃的危险,那么你就可以通过捕获异常来防止程序的崩溃

4.系统提供异常捕获方法

崩溃类型
  1. 应用层存在bug ,即OC程序崩溃
    如 数组越界,selector方法没实现等抛出一系列NSException
    可通过系统API 注册UncaughtNSException处理函数捕捉,定位比较容易

  2. 违反系统规则而出错
    如 watchdog超时,访问了不属于本进程的内存地址,用户强制退出,低内存终止等,系统抛出unix信号,但没有错误堆站信息
    可通过注册信号处理函数捕捉,但只能补拙有限的几种类型,定位较困难

NSSetUncaughtExceptionHandler

OS SDK中提供了一个现成的函数 NSSetUncaughtExceptionHandler 用来做异常处理,但功能非常有限,而引起崩溃的大多数原因如:内存访问错误,重复释放等错误就无能为力了。 因为这种错误它抛出的是Signal,如果要处理它,我们还要利用unix标准的signal机制,注册SIGABRT, SIGBUS, SIGSEGV等信号发生时的处理函数

什么是Signal

在计算机科学中, 信号 ( 英语: Signals)是Unix、类Unix以及其他POSIX兼容的操作系统中进程间通讯的一种有限制的方式。它是一种异步的通知机制,用来提醒进程一个事件已经发生。当一个信号发送给一个进程,操作系统中断了进程正常的控制流程,此时,任何非原子操作都将被中断。如果进程定义了信号的处理函数,那么它将被执行,否则就执行默认的处理函数

信号处理函数可以通过 signal() 系统调用来设置。如果没有为一个信号设置对应的处理函数,就会使用默认的处理函数,否则信号就被进程截获并调用相应的处理函数。在没有处理函数的情况下,程序可以指定两种行为:忽略这个信号 SIG_IGN 或者用默认的处理函数 SIG_DFL 。但是有两个信号是无法被截获并处理的: SIGKILL、SIGSTOP

( SignalHandler不要在debug环境下测试。因为系统的debug会优先去拦截。我们要运行一次后,关闭debug状态。应该直接在模拟器上点击我们build上去的app去运行。而UncaughtExceptionHandler可以在调试状态下捕捉
虽然Xcode屏蔽了signal的回调,我们只要在lldb中输入以下命令,signal的回调就可以进来了
pro hand -p true -s false SIGABRT )

Signal信号的类型

- SIGABRT--程序中止命令中止信号
- SIGALRM--程序超时信号
- SIGFPE--程序浮点异常信号
- SIGILL--程序非法指令信号
- SIGHUP--程序终端中止信号
- SIGINT--程序键盘中断信号
- SIGKILL--程序结束接收中止信号
- SIGTERM--程序kill中止信号
- SIGSTOP--程序键盘中止信号
- SIGSEGV--程序无效内存中止信号
- SIGBUS--程序内存字节未对齐中止信号
- SIGPIPE--程序Socket发送失败中止信号

5.关于符号表dSYM文件

1)符号表是什么

  • 符号表就是指在Xcode项目编译后,在编译成的二进制文件.app同级目录下生成的同名的.dSYM文件
  • .dSYM文件其实是一个目录,在子目录中包含了一个16进制的保存函数地址映射信息的中转文件,所有Debug的symbols都在这个文件中(包括文件名、函数名、行号等),所以也称之为调试符号信息文件
  • 一般地,Xcode项目每次编译后,都会生成一个新的.dSYM文件。因此,App的每一个发布版本,都需要备份一个对应的.dSYM文件,以便后续调试定位问题

注意:
项目每一次编译后,.app和.dSYM成对出现,并且二者有相同的UUID值,以标识是同一次编译的产物。
UUID值可以使用dwarfdump —uuid来检查

  • $ dwarfdump --uuid XX.app.dSYM
  • $ dwarfdump --uuid XX.app/XX
  1. 符号表怎么生成

一般,Xcode项目默认的配置是在编译后生成.dSYM,在Build Setting配置如下:

  • Generate Debug Symbols = Yes
  • Debug Information Format = DWARF with dSYM File   
    

下面是几种常用的编译打包方式:

  • 使用xcodebuild编译打包

在Xcode中编译项目后,会在工程目录下的build/ConfigurationName-iphoneos目录下生成.app和.app.dSYM文件。

如果使用xcodebuild命令进行编译打包,则可以指定编译结果的存储路径,同样会有.app和.app.dSYM生成。

一般地,我们推荐打包发布时,使用xcodebuild编译打包,方便.app和.app.dSYM的匹配存储,避免.app.dSYM文件丢失的情况。

  • 使用Xcode的Archive导出

如果开发者使用Xcode的Archive导出功能打包,可以切换到Organizer的Projects视图,查看对应项目的Derived Data路径,在其中可以找到当前导出过程产生的.app和.app.dSYM文件

  • 使用make编译打包

如果开发团队不使用Xcode编译打包,而是使用make编译生成.o文件,然后打包发布。此时,编译过程不会有.dSYM文件生成。开发者可以使用dsymutil工具从.o文件中提取符号信息

  1. dSYM 文件有什么作用

在前面的内容可以知道,符号表的作用是把崩溃中的函数地址解析为函数名等信息。 如果开发者能够获取到崩溃的函数地址信息,就可以利用符号表分析出具体的出错位置

例如,崩溃问题的函数地址堆栈如下:

错误地址堆栈

  • 3 CoreFoundation 0x254b5949 0x253aa000 + 1096008
  • 4 CoreFoundation 0x253e6b68 _CF_forwarding_prep_0 + 24
  • 5 SuperSDKTest 0x0010143b 0x000ef000 + 74808

符号化堆栈

  • 3 CoreFoundation 0x254b5949 <redacted> + 712
  • 4 CoreFoundation 0x253e6b68 _CF_forwarding_prep_0 + 24
  • 5 SuperSDKTest 0x0010143b -[ViewController didTriggerClick:] + 58

说明:

大部分情况下,开发者能获取到的都是错误地址堆栈,需要利用符号表进一步符号化才能分析定位问题。
部分情况下,开发者也可以利用backtrace看到符号化堆栈,可以大概定位出错的函数、但却不知道具体的位置。
通过利用符号表信息,也是可以进一步得到具体的出错位置的。
目前,许多崩溃监控服务都显示backtrace符号化堆栈,增加了可读性,但分析定位问题时,仍然要进一步符号化处理。

  1. 符号化

将这些十六进制地址转化成方法名称和行数的过程称之为符号化

(1)使用symbolicatecrash,Xcode自带的崩溃分析工具
symbolicatecrash是一个将堆栈地址符号化的脚本,输入参数是苹果官方格式的崩溃日志及本地的.dSYM文件,使用symbolicatecrash工具的限制就在于只能分析官方格式的崩溃日志,需要从具体的设备中导出,获取和操作都不是很方便,而且,符号化的结果也是没有具体的行号信息的,也经常会出现符号化失败的情况。
实际上,使用Xcode查看崩溃日志是已经符号化的,Xcode能够自动根据本地存储的.dSYM文件进行了符号化的操作,是因为内置了symbolicatecrash

(2)dSYM 工具

6.崩溃日志分析

crash日志,几个重要的关键词

关键词解释:

6.1、 进程信息
第一部分是闪退进程的相关信息:

Incident Identifier : 是崩溃报告的唯一标识符。

CrashReporter Key: 是与设备标识相对应的唯一键值。同一个设备上同一版本的App发生Crash时,该值都是一样的。虽然它不是真正的设备标识符,但也是一个非常有用的情报:如果你看到100个崩溃日志的CrashReporter Key值都是相同的,或者只有少数几个不同的CrashReport值,说明这不是一个普遍的问题,只发生在一个或少数几个设备上。

Hardware Model :标识设备类型。 如果很多崩溃日志都是来自相同的设备类型,说明应用只在某特定类型的设备上有问题。上面的日志里,崩溃日志产生的设备是iPhone 4s。

Process:代表Crash的进程名称,通常都是我们的App的名字, []里面是当时进程的ID

Path:崩溃文件的路径

Identifier:项目标识符,就是Bundle Id

Version:版本号

Code Type:当前App的CPU架构
Parent Process:当前进程的父进程,由于iOS中App通常都是单进程的,一般父进程都是launchd

.....等等.......

6.2、基本信息
这部分给出了一些基本信息,包括闪退发生的日期Date/Time和时间Launch Time,设备的iOS版本OS Version等。
Report Version-Crash日志的格式,目前基本上都是104,不同的version里面包含的字段可能有不同

6.3、异常信息
Exception Type:异常的类型。
Exception Codes :异常错误码
Termination Reason:闪退的原因,比如常见的数组越界啊,什么的。
Triggered by Thread:出现问题在哪个线程,这个比较重要,首先确定在哪个线程中出了问题,然后再去定位。

6.4、线程回溯
这部分提供应用中所有线程的回溯日志。回溯是闪退发生时所有活动帧清单。它包含闪退发生时调用函数的清单. 线程调用的一些堆栈信息,压根看不懂,所有需要进行符号化处理。看下面这行日志:

12  UIKit       0x00000001943bd7fc 0x194343000 + 501756
 
它包括四列:
帧编号—— 此处是12。
二进制库的名称 ——此处是 UIKit
调用方法的地址 ——此处是 0x00000001943bd7fc
第四列分为两个子列,一个基本地址和一个偏移量。此处是0x194343000 + 501756, 第一个数字指向文件,第二个数字指向文件中的代码行。

6.5 二进制映像(Binary Images)
这部分列出了当Crash发生时被装载进进程内存空间的依赖库或者模块

7.异常类型信息

Exception Type

1)EXC_BAD_ACCESS

此类型的Excpetion是我们最长碰到的Crash,通常用于访问了不改访问的内存导致。一般EXC_BAD_ACCESS后面的"()"还会带有补充信息。

SIGSEGV: 通常由于重复释放对象导致,这种类型在切换了ARC以后应该已经很少见到了。

SIGABRT: 收到Abort信号退出,SIGABRT 异常是由于某个对象接收到未实现的消息引起的。
SEGV:(Segmentation Violation),代表无效内存地址,比如空指针,未初始化指针,栈溢出等;

SIGBUS:总线错误,与 SIGSEGV 不同的是,SIGSEGV 访问的是无效地址,而 SIGBUS 访问的是有效地址,但总线访问异常(如地址对齐问题)

SIGILL:尝试执行非法的指令,可能不被识别或者没有权限

2)EXC_BAD_INSTRUCTION

此类异常通常由于线程执行非法指令导致

3)EXC_ARITHMETIC

除零错误会抛出此类异常

Exception Code 异常编码

前面日至的第3部分找到异常编码。有些编码比较常见。通常,异常编码以一些文字开头,紧接着是一个或多个十六进制值,此数值正是说明闪退根本性质的所在。 从这些编码中,可以区分出闪退是因为程序错误、非法内存访问或者是其他原因

0xbaaaaaad 此种类型的log意味着该Crash log并非一个真正的Crash,它仅仅只是包含了整个系统某一时刻的运行状态。通常可以通过同时按Home键和音量键,可能由于用户不小心触发

常见的异常编码:

0xbad22222 当VOIP(指在 IP 网络上使用 IP 协议以数据包的方式传输语音)程序在后台太过频繁的激活时,系统可能会终止此类程序

0x8badf00d 程序启动或者恢复时间过长被watch dog终止,通常是应用花费太多时间而无法启动、终止或响应用系统事件

0xc00010ff 程序执行大量耗费CPU和GPU的运算,导致设备过热,触发系统过热保护被系统终止

0xdead10cc 程序退到后台时还占用系统资源,如通讯录被系统终止

0xdeadfa11 程序无响应,用户强制关闭

(注意: 在后台任务列表中关闭已挂起的应用不会产生崩溃日志。 一旦应用被挂起,它何时被终止都是合理的。所以不会产生崩溃日志。)

低内存崩溃

低内存崩溃日志与其他类型的崩溃日志很不一样,它们不指向特定的文件和代码行。相反,它们画出了闪退时设备上的内存使用情况的图表

头部还是跟其他崩溃日志很像的: 提供了 Incident Identifier, CrashReporter Key, Hardware Model, OS Version等信息。

接下来部分是低内存崩溃日志特有的:
Free pages 指可用内存页数。每页大小约是4KB, 上面的日志中,可用内存约为3,872 KB (或者说 3.9 MB)。

Purgeable pages 是那部分可被清除或重用的内存。在上面的日志中,是0KB。

Largest process是闪退时使用大部分内存的应用名称,在上面的日志中,正是你的应用!

Processes显示了闪退时各进程列表,还包含内存使用量。包含进程名 (第一列), 进程唯一标识符(第二名), 进程使用的内存页数(第三列)。最后一列是每个应用的状态。通常,发生闪退的应用的状态是 frontmost。 这里是 Rage Masters, 使用28591 页 (or 114.364 MB) 内存——这内存太多了!

参考

iOS崩溃 捕获异常处理

iOS 异常处理

使用signal让app能够在从容崩溃

iOS调试之 crash log分析

iOS应用崩溃日志分析

iOS Crash文件的解析

内存警告

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

推荐阅读更多精彩内容