iOS 调试

调试
对于开发人员来说调试是不可少的。iOS开发目前用的调试器是LLDB,其是用LLVM中可重用组件构建的下一代高性能调试器,包括完成的LLVM编译器。对于我们开发人员来说,这就意味着LLDB能理解编译器所能理解的语法。

在用LLDB之前我们再来看看还有哪些调试的方法。

dSYM(调试信息文件)

dSYM中存储着和目标有关的调试信息。任何一种编程语言写的代码都需要一个编译器,将这些代码翻译成可被运行时环境理解的某种中间语言,或者是可在机器的体系结构上直接运行的原生机器码。
调试器一般在集成开发环境中,开发环境通常支持放置断点使应用停止运行,从而查看变量的值。一般有两类重要的调试器:符号调试器和机器调试器。前者能够在调试代码时显示应用中使用的符号或变量。后者则能够在运行断点时显示逆向过来的汇编代码。符号调试器允许观察代码中的符号而不是寄存器和内存地址。

Xcode的调试信息是dSYM。
iOS 调试_第1张图片

Build Settings -> Build Options->debug Information Format那栏,我们还可以用命令工具dsymutil创建dSYM。

符号化

很多编译器都是用来将源代码转换成汇编代码。所有汇编代码都有一个基地址,学过汇编语言的同学应该都清楚这个过程,而我们用到的变量,栈和堆都是依赖这个基地址。每次运行,基地址都会变,我已经测试过了,大家也可以写一个小程序测试一下,看看地址。符号化使用方法名或者变量名(符号)来替换基地址的过程。基地址是应用的入口地址,通常都是main方法。除了是一个静态库。方法是计算它们的相对基地址偏移,然后将它们映射到dSYM文件,符号化过程在用Xcode调试应用时才会进行,或者用Instruments做性能分析时进行。

xcarchiver内部机制
xcarchiver包含目录:dSYMs,Products以及一个Info.plist文件。dSYMs包含工程中包含的目标/静态库对应的所有dSYM文件。Products包含所有可执行的二进制文件。Info.plist文件与工程中的plist文件相同,对于识别xcarchiver中的target/dsym版本很重要。我们可以将从iTunes Connect中得到的.crash文件拖到Xcode中时,Xcode内部会查找归档文件,找出与崩溃报告匹配的Info.plist文件,然后从那个归档文件的dSYMs目录获取.dSYM 文件。这也是为什么我们不能删除已提交归档文件的原因。

断点
像上面提到的符号化,dSYM,xcarchiver 我在项目开发中很少用到,而断点是我调试程序离不开的。

异常断点
代码抛出异常是很常见的事,特别是做一个企业级的APP,因为逻辑业务的复杂,数据量较大,在开发过程中,难免有考虑不到的地方,异常也是在所难免的。我们导入的库的一些方法会在不能满足特定条件的情况下抛出异常。像数组越界,返回值为NULL等。调试异常是比较容易的,但是查找原因,有时候是相当复杂的,一般应用崩溃的时候可能只会在日志文件中显示造成崩溃的异常,如果我们不设置断点,仅仅依靠看日志文件发现根源是很困难。

首先我们打开断点导航版,然后选择Add Exception Breakpoint

这里写图片描述
点击‘+’

iOS 调试_第2张图片

可以看在已经添加了一个异常。当运行我们的项目时,应用程序就停在抛出异常的那行。

符号异常

符号断点会在执行到特定符号时暂停程序。符号可能是一个方法名,类方法,或者C方法(objc_msg_Send)。

iOS 调试_第3张图片
跟添加异常断点类似,但是选择的是符号断点。

iOS 调试_第4张图片
编辑我们关注的符号。

不管是异常还是符号断点,我们都可以进行编辑,图片也显示出编辑的效果,大家可以自己尝试一下。

iOS 调试_第5张图片
我们不仅可以编辑还可以选择共享。点击Share。我们设置的断点就会保存到工程问价报的xcshareddata目录。将该目录提交到版本控制系统,就可以跟团队的所有人共享断点。

观察点

断点可以帮助我们暂停程序的执行,而观察点则帮助我们在某个变量中保存的值发生变化时暂停程序的执行。帮助我们解决了与全局变量有关的问题。可以追踪到具体是哪个方法改变了特定的全局变量。跟断点工作原理类似,不同的是,是在数据被修改时停止执行。

做法如下:

设置观察点

在初始化变量的时候,打一个断点,来初始化这个观察点
当程序运行到这个断点时,我们通过 lldb 命令 watchpoint set v string_weak_ 设置观察点,其中 string_weak_ 是变量的名字。观察点设置成功之后,可以看到相关的日志提示。

观察点设置成功之后,当观察的变量的值发生变化之后,xcode 就会自动断点到修改的位置,暂停执行,可以捕获到该事件,进行相应的分析。
iOS 调试_第6张图片
配合断点,选择编辑
iOS 调试_第7张图片
填上我们的触发条件即可。注意不能在程序还没开始运行时添加观察点。

LLDB

Xcode的调试控制台窗口是一个功能完备的LLDB调试控制台。当暂停应用时调试控制台会显示在LLDB命令提示符
iOS 调试_第8张图片

下面那栏,我这里忘记设置断点,程序运行成功,所以没有打印错误信息。

打印变量

po(print object)是LLDB的一个命令,其主要功能是输出objective-c中对象(objects)的信息,与之相似的另外一个命令是 p(print),其主要功能是输出原生类型(boolean、integer、float、etc)的信息。

控制台输入

p (int)[[[self view] subviews] count]
结果如下
(int) $2 = 1

注意这个使用了类型转换告知调试器应该如何处理返回值。

打印寄存器

在控制台输入:
register read
即可

另外,LLDB调试器的设计由底至上都支持API和插件接口。支持导入Phython脚本调试。

NSZombieEnable标志

这个变量用来调试与内存相关的问题。跟踪对象的释放过程。启用这个变量,会用一个僵尸实现替换默认的dealloc,在引用计数降到0时,该僵尸实现会将该对象转换成僵尸对象,僵尸对象的作用是向它发送消息时,会显示一段日志并自动跳入调试器。

Xcode崩溃类型

1)EXC_BAD_ACCESS
在访问一个已经释放的对象或向它发送消息时,EXC_BAD_ACCESS就会出现。造成EXC_BAD_ACCESS最常见的原因是,在初始化方法中初始化变量时用错了所有权修饰符,这会导致对象被释放。举个例子,在 viewDidLoad 方法中 UITableViewController 创建了一个包含元素的 NSMutableArray,却将该数组的所有权修饰符设成了 unsafe_unretained 或 assign 而不是 strong 。现在在 cellForRowAtIndexPath: 中,若要访问已经释放掉的对象时,就会得到名为 EXC_BAD_ACCESS 的崩溃。

2)SIGSEGV
段错误信号(SIGSEGV) 是操作系统产生的一个更严重的问题。当硬件出现错误、访问不可读的内存地址或向受保护的内存地址写入数据时,就会发生这个错误。
硬件错误这一情况并不常见。当要读取保存在RAM中的数据,而该位置的RAM硬件有问题时,你就会收到SIGSEGV。SIGSEGV更多是出现在后两种情况。默认情况下,代码页不允许进行写操作,而数据页不允许进行执行操作。当应用中的某个指针指向代码页并试图修改指向位置的值时,你就会受到SIGSEGV。当要读取一个指针的值,而它被初始化成指向无效内存地址的垃圾值时,你也会收到SIGSEGV。
SIGSEGV 错误调试起来更困难,而导致SIGSEGV的最常见原因是不正确的类型转换。要避免过度使用指针或尝试手动修改指针来读取私有数据结构。如果你那样做了,而在修改指针时没有注意内存对齐和填充问题,就会收到SIGSEGV。

3)SIGBUS
总线错误信号(SIGBUS)代表无效内存访问,即访问的内存是一个无效的内存地址。也就是说,那个地址指向的位置根本不是物理内存地址(他可能是某个硬件芯片的地址)。SIGBUS 和 SIGSEGV 都属于 EXC_BAD_ACCESS 的子类型。

4)SIGTRAP
SIGTRAP 代表陷阱信号。它并不是一个真正的崩溃信号。它会在处理器执行 trap 指令时发送。LLDB调试器通常会处理此信号,并在指定的断点处停止运行。如果你收到了原因不明的 SIGTRAP,先清除上次的输出,然后重新进行构建通常能解决这个问题。

5)EXC_ARITHMETIC
当要除零时,应用会收到 EXC_ARITHMETIC 信号。这个错误应该很容易解决

6)SIGILL
SIGILL 代表 SIGILL ILLEGAL INSTRUCTION (非法指令信号)。 当在处理器上执行非法指令时,它就会发生。执行非法指令是指,将函数指针传给另外一个函数时,该函数指针由于某种原因是坏的,指向了一段已经释放的内存或是一个数据段。有时你收到的是 EXC_BAD_INSTRUCTION 而不是 SIGILL 。虽然它们是一回事,不过 EXC_* 等同于此信号不依赖体系结构。

7)SIGABRT
SIGABRT代表 SIGNAL ABORT (中止信号)。当操作系统发现不安全的情况时,它能够对这种情况进行更多的控制,必要的话,它能要求进程进行清理工作。在调试造成此信号的底层错误时,并没有什么妙招。 cocos2d 或 UIKit 等框架通常会在特定的前提条件没有满足或一些糟糕的情况出现时调用 C 函数 abort (由它来发送此信号)。当 SIGABRT 出现时,控制台通常会输出大量的信息,说明具体哪里出错了。由于它是可控制的崩溃,所以可以在LLDB控制台上键入 bt命令打印出回溯信息。

8)0x8badf00d (看门狗超时)
这种崩溃通常比较容易分辨,因为错误码是固定的 0x8badf00d。在iOS上,他经常出现在执行一个同步网络调用而阻塞主线程的情况。因此,永远不要进行同步网络调用。

9)自定义错误信号处理程序
大家可以参考这个大神:
http://www.cocoawithlove.com/2010/05/handling-unhandled-exceptions-and.html

渣渣水平,我也云里雾里的。

关于调试还有怎样收集崩溃报告,第三方崩溃报告服务等,我做项目的时候用过Bugly。

你可能感兴趣的:(ios,iOS)