breakpoints、lldb 和 chisel 的使用

李富强Jason的博客 2015-08-03 09:12:29 4312

startup-photos-large.jpg

作者:@李富强Jason 授权本站转载。

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直接看到断点,另外一方面只存在于本次运行,下一次启动模拟器重新运行的时候,这些断点就不生效了。

blob.png

如上图,通过“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的话,我们能大大减少查找错误的时间。

blob.png

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

blob.png

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

blob.png

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

blob.png

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

blob.png

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

blob.png

,不过这种问题,可以通过使用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,弹出框如下:

blob.png

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函数添加断点:

blob.png

对OC的方法添加断点:

blob.png

常用的这个类型的断点有,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” 即可。

blob.png

命令行

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

上面是对变量进行观察,实际上我们可以对任意内存地址进行观察,命令如下:watchpoint set expression — 0x123456,参考:http://stackoverflow.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 网站的几个命令:

blob.png

第一个命令是监听_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在判断字符串相等的时候才会停止运行:

blob.png

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

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

Breakpoint Actions

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

blob.png

设置Breakpoint:

blob.png

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

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

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

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

blob.png

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的背景色改为红色:

blob.png

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

blob.png

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

参考:

LLDB

常用命令

help

直接输入help命令,列出所有可用的commands。

使用 help来获得某个command的具体用法

expression

简写形式:expr / e。在运行调试时执行表达式,用来直接修改程序的变量的值,或者使用这个命令声明一个变量对象。

例如:修改变量的值:expression count = 20

或者声明一个新变量a:

blob.png

expression命令可以带有参数,但也会带来一些问题,例如: e -h +17 命令,这个命令就容易产生混淆,到底-h是参数flag,然后+17是输入变量,还是说要计算 -h+17 表达式的最终值。LLDB提供的解决方式非常简单,使用“ -- ”来指定命令参数的终止,命令输入的开始。例如,e -h -- +17表示前文中命令的第一个解释,e -- -h+17 表示表示前文中命令的第二个解释。

print

简写形式:prin / pri / p。但是不能用pr表示,因为会和process混淆。实际上,你会发现,lldb对于命令的简称,是头部匹配方式,只要不相互混淆,可以随意简称某个命令。

实际上,如果在console中输入“help print”,就会得到’print’ is an abbreviation for ‘expression --‘ 这句话,也就是说print实际上就相当于 expression -- 。这里就很容易理解下面命令是如何工作的,p _lastPoiID=20,这行命令表达式会被执行。

blob.png

打印一个对象的话,使用 po 命令,使用expression表示的话就是 expression -O -- XXX

使用 print/或者 p/来格式化打印变量,可以参考对应的格式列表:https://sourceware.org/gdb/onlinedocs/gdb/Output-Formats.html。p/x使用十六进制打印一个变量,p/t 使用二进制打印整数 ,p/c 打印字符,p/s 打印c字符串。

既然我们可以print对象和简单类型,并且在debugger中通过expression命令直接修改他们,那么我们可以通过使用一些变量来减轻我们的工作量。前面讲过我们可以直接使用expression命令来声明变量,但需要注意的是,新声明的变量必须以 $ 符号开始。

blob.png

最后一个命令报错了,是因为LLDB没法确定结果的类型,需要强制转换一下来告诉LLDB。

blob.png

call

调用命令,类似expression,一般用户不需要打印结果或者无返回值的地方

bt

打印调用堆栈信息,使用 bt all命令可以打印出所有thread的调用栈信息

比如说:call [self.view setBackgroundColor:[UIColor redColor]],使用这个命令来设置view controller的背景色为红色

image

image命令可用于寻址,有多个组合命令。比较实用的用法是用于寻找栈地址对应的代码位置。详细用法参考:http://www.starfelix.com/blog/2014/03/17/lldbdiao-shi-ming-ling-chu-tan/http://blog.csdn.net/hursing/article/details/8745334

常见问题

对中文的兼容

前面文章中讲过,有中文时必须使用 [NSString stringWithUTF8String:] 方法,原因在于lldb的expression parser有一个bug,不兼容非ASCII字符,需要处理一下才行,否则会报错“An Objective-C constant string's string initializer is not an array”,参考:http://stackoverflow.com/questions/17192505/error-in-breakpoint-condition

类型的问题

出现类型不确定或类型不匹配的时候,就会报错,这个时候必须强制转换才可以。

比如前文中获取indexPath的row的方法的表达式:(int)[indexPath row],或者类似下面这种干脆无返回值 p (void)NSLog(@"%@",[self.view  viewWithTag:1001]),或者上文中的 p (char)[[$array objectAtIndex:$a] characterAtIndex:0],这些场景中都需要明确输出的类型。

找不到方法

注意,使用系统框架对象的属性时不能使用dot语法,比如下图中的问题。

blob.png

blob.png改成如下的格式才行:

blob.png

Chisel

基于LLDB对Python插件的支持,http://lldb.llvm.org/python-reference.html,facebook开发开源了一套LLDB命令库,https://github.com/facebook/chisel,里面包含了很多很有意思的命令工具。安装方式很简单,使用brew工具,详细参考官方网站,不多说。

安装之后,使用help命令,在下面可以 user-defined commands 可以找到这个框架提供的一些自定义命令。Chisel提供了非常多很有用的命令行工具,调试UI的时候非常方便,具体可以参考官方网站。

参考

版权声明:本文为博主原创文章,未经博主允许不得转载。