iOS开发——测试篇&breakpoints、lldb 和 chisel 的详解

时间:2023-02-07 20:59:31

breakpoints、lldb 和 chisel 的详解

iOS开发——测试篇&breakpoints、lldb 和 chisel 的详解

Breakpoints

BreakPoint分类

breakpoint也是有分类的,我这里的文章内大致按使用的方式分为了

  • Normal Breakpoint,
  • Exception Breakpoint,
  • OpenGL ES Error breakpoint,
  • Symbolic Breakpoint,
  • Test Failure Breakpoint,
  • WatchPoints。

可以按具体的情景使用不同类型的breakpoint,解决问题为根本。

Normal Breakpoint

添加普通断点就不多说了,在源代码的右侧点击一下即可。或者,使用快捷键:command + \ 来添加和删除。这两种方式添加的breakpoints在Xcode上面是可以通过UI看到的。

还有可以通过下面两个LLDB命令直接在运行时添加断点,但是这种方式需要注意的是一方面无法通过UI直接看到断点,另外一方面只存在于本次运行,下一次启动模拟器重新运行的时候,这些断点就不生效了。

iOS开发——测试篇&breakpoints、lldb 和 chisel 的详解

如上图,通过“br li”命令打印所有的breakpoint,可以看到一共有3个breakpoint,第一个是通过Xcode的UI添加的,后面两个分别是通过下面两个命令添加的:

“breakpoint set -f XXX.m -l XX” 和  “b XXX.m:XX”。

Exception Breakpoint

可以通过下图中Xcode的UI添加Exception Breakpoint。有时候,比如数组越界或者设置一个空对象等问题,都会抛出一个异常,但是这种类型的错误非常难以定位,这个时候就可以使用Exception Breakpoint来进行调试,在异常发生时可以捕捉到并停止程序的执行。OC中的异常是一个常被忽略的地方,但实际上系统框架内这个使用非常广泛,大部分这种错误信息,系统框架都会以异常的形式throw出来,所以善用这种breakpoint的话,我们能大大减少查找错误的时间。

iOS开发——测试篇&breakpoints、lldb 和 chisel 的详解

例如,当我们添加如下Exception Breakpoint之后(bt 命令后文中会讲解,这个命令的作用是在断点触发时,打印回调栈信息):

iOS开发——测试篇&breakpoints、lldb 和 chisel 的详解

类似下面这样的数组越界的问题,我们可以很容易就定位到问题所在,不用再毫无头绪找来找去了:

iOS开发——测试篇&breakpoints、lldb 和 chisel 的详解

当断点暂停执行时,我们可以通过Xcode的UI中查看调用栈信息:

iOS开发——测试篇&breakpoints、lldb 和 chisel 的详解

或者查看bt命令打印的调用栈信息:

iOS开发——测试篇&breakpoints、lldb 和 chisel 的详解

还有类似如下的错误可以通过这种断点很容易定位到:

iOS开发——测试篇&breakpoints、lldb 和 chisel 的详解

,不过这种问题,可以通过使用setValue:forKey:代替来避免。

OpenGL ES Error Breakpoint

同上图中,在Xcode的breakpoint navigator的下部添加按钮,选择”Add OpenGL ES Error Breakpoint”即可。这个breakpoint主要是用来在OpenGL ES发生错误时停止程序的运行。

Symbolic Breakpoint

通过Xcode的UI添加symbolic breakpoint的方式同exception breakpoint,弹出框如下:

iOS开发——测试篇&breakpoints、lldb 和 chisel 的详解

Symbolic breakpoints 在某个特定的函数或者方法开始执行的时候,暂停程序的执行,通过这种方式添加断点,我们就不需要知道在源文件中添加,也不需要知道断点设置在文件的第几行。

上图中,最主要的设置是Symbol的内容,可以有如下几种:

  • 1. A method name,方法名称,例如 pathsMatchingExtensions: 这样的方法名称,会对所有类的这个方法都起作用。
  • 2. A method of a particular class. 特定类的某个方法。例如 ,[SKLine drawHandlesInView],或者 people::Person::name()
  • 3. A function name。函数名称。例如 ,_objc_msgForward 这样C函数。

另外,也可以通过命令行的方式添加 Symbolic breakpoints。对C函数添加断点:

iOS开发——测试篇&breakpoints、lldb 和 chisel 的详解

对OC的方法添加断点:

iOS开发——测试篇&breakpoints、lldb 和 chisel 的详解

常用的这个类型的断点有,objc_exception_throw可以用来代替 Exception Breakpoint,还有一个-[NSObject doesNotRecognizeSelector:] 也比较常用,用于检测方法调用失败。

Test Failure Breakpoint

通过Xcode的UI添加方法同上。这个类型的break point 会在 test assertion 失败的时候暂停程序的执行。

Watchpoints

Watuchpoints是一个用来监听变量的值的变化或者内存地址的变化的工具,发生变化时会在debugger中触发一个暂停。对于那些不知道如何准确跟踪的状态问题,可以利用这个工具来解决。要设置watchpoint的话,在程序运行到stack frame包含有你想观察的变量时,让debugger暂停运行,这个时候变量在当前stack frame的scope内,这个时候才能对该变量设置watchpoint。

你可以在Xcode的GUI中设置watchpoint,在xcode的 Variables View中,把你想观察的变量保留出来,然后右键设置“Watch XXX”。例如下图,观察self的title变量,点击 Watch “_button1ClickCount” 即可。

iOS开发——测试篇&breakpoints、lldb 和 chisel 的详解

命令行

或者也可以通过命令行来设置watchpoint:watch set variable _button1ClickCount,详细命令可以参考:http://lldb.llvm.org/lldb-gdb.html,有好几种命令可以达到同样的效果。

上面是对变量进行观察,实际上我们可以对任意内存地址进行观察,命令如下:watchpoint set expression — 0x123456,参考:http://*.com/questions/21063995/watch-points-on-memory-address

需要注意的是,watchpoint是分类型的,包括read,write或者read_write类型,这个非常容易理解,在读,写或者读写变量或内存的时候,watchpoint是否被触发。read,write或read_write跟着-w参数后面表示类型。另外,命令行中,watchpoint还有一些简写,set简写为s,watch简写为wa,variable简写为v。

下面的示例是来自 http://www.dreamingwish.com/article/lldb-usage-a.html 网站的几个命令:

iOS开发——测试篇&breakpoints、lldb 和 chisel 的详解

第一个命令是监听_abc4变量的内存地址write的变化,第二个是监听_abc4变量read的变化,第三个是监听_abc3变量read_write的变化。

需要注意的是,通过Xcode的GUI添加的watchpoint为默认类型,即write类型,如果想要添加读写都watch的watchpoint,则只能通过命令行工具进行添加了。

使用watchpoint modify -c ‘(XXX==XX)’,则修改watchpoint之后在某个值的时候才会监听。

编辑选项

BreakPoint Condition

当我们通过Xcode对breakpoint进行编辑时,可以发现normal breakpoint和symbolic breakpoint都有一个”Condition”输入选项,这个的作用很容易理解,只有在设置的condition表达式为YES的情况下这些断点才会起作用。

例如,下图中的breakpoint在判断字符串相等的时候才会停止运行:

iOS开发——测试篇&breakpoints、lldb 和 chisel 的详解

可以注意到这里使用stringWithUTF8Stirng:方法,原因在于lldb的expression parser有一个bug,不兼容非ASCII字符,需要处理一下才行,否则会报错“An Objective-C constant string's string initializer is not an array”,参考:http://*.com/questions/17192505/error-in-breakpoint-condition

更加简单一些的例子就不说了,比如 i == 99之类的简单比较,只要表达式的结果为BOOL类型即可。

Breakpoint Actions

可以看到上面的每种breakpoint编辑选项中基本上都有“Add Action”选项,当breakpoint被触发时,都首先会执行我们设置的这些action,然后我们才能得到控制权,即Xcode上面才会显示程序停止执行的UI。这个Action通过例子比较好理解,我们通过上面那个setObject:forKey:的异常来说明。代码如下:

iOS开发——测试篇&breakpoints、lldb 和 chisel 的详解

设置Breakpoint:

iOS开发——测试篇&breakpoints、lldb 和 chisel 的详解

可以看到上图中,我们一共设置了3个action。第一个action,用来打印exception的详细信息,用法参考:http://*.com/questions/17238673/xcode-exception-breakpoint-doesnt-print-details-of-the-exception-being-thrown

第二个action,我们使用shell命令“say”,让电脑发声,把一段文字读出来。

第三个action,我们使用“bt”命令来打印调用栈信息

设置完成之后,当异常发生时,我们会听到电脑发声念上图中的英文,然后在log中可以看到如下信息,第一行是Exception的描述信息,下面是调用堆栈:

iOS开发——测试篇&breakpoints、lldb 和 chisel 的详解

Continuing after Evaluation

看一下breakpoint的编辑弹窗,我们可以发现有一个 “Automatically continue after evaluation actions” checkbox选项。当我们勾选这个checkbox之后,debugger会执行breakpoint中添加的所有的actions,然后继续执行程序。对于我们来说,除了触发一大堆command并且执行时间很长的情况之外,程序会很快跳过这个breakpoint,所以我们可能根本不会注意到这个breakpoint的存在。所以,这个选项的功能相当于在执行的最后一个action之后,直接输入continue命令继续执行。

有了这个很强大的功能,我们可以直接通过breakpoints来单独对我们的程序进行修改。在某行代码时停止执行,使用”expression”命令来直接修改程序的某个变量设置直接修改UI,然后继续执行。expression / call 配合这个选项的时候,会非常强大,可以很方便实现很多很强大的功能。

例如,我们实现一个如下的功能,把tableview的第一个cell的selectBackgroundView的背景色改为红色:

iOS开发——测试篇&breakpoints、lldb 和 chisel 的详解

action的内容为“expression [[cell selectedBackgroundView] setBackgroundColor:[UIColor redColor]]”,这里的表达式先不用关心,我们后面LLDB章节会讲到,修改之后,当我们点击cell的时候,cell的背景就会如下图一样变红:

iOS开发——测试篇&breakpoints、lldb 和 chisel 的详解

使用这种方式,我们在不需要修改一行代码的情况下,只需要通过修改breakpoint,就可以实现对UI的各种调试效果。