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直接看到断点。另外一方面仅仅存在于本次执行,下一次启动模拟器又一次执行的时候,这些断点就不生效了。
如上图,通过“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的话,我们能大大降低查找错误的时间。
比如,当我们加入例如以下Exception Breakpoint之后(bt 命令后文中会解说。这个命令的作用是在断点触发时。打印回调栈信息):
类似以下这种数组越界的问题。我们能够非常easy就定位到问题所在。不用再毫无头绪找来找去了:
当断点暂停运行时,我们能够通过Xcode的UI中查看调用栈信息:
或者查看bt命令打印的调用栈信息:
还有类似例如以下的错误能够通过这样的断点非常easy定位到:
,只是这样的问题,能够通过使用setValue:forKey:取代来避免。
OpenGL ES Error Breakpoint
同上图中,在Xcode的breakpoint navigator的下部加入button。选择”Add OpenGL ES Error Breakpoint”就可以。
这个breakpoint主要是用来在OpenGL ES错误发生时停止程序的执行。
Symbolic Breakpoint
通过Xcode的UI加入symbolic breakpoint的方式同exception breakpoint,弹出框例如以下:
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函数加入断点:
对OC的方法加入断点:
经常使用的这个类型的断点有,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” 就可以。
命令行
或者也能够通过命令行来设置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类型,这个很easy理解,在读,写或者读写变量或内存的时候,watchpoint是否被触发。
read,write或read_write跟着-w參数后面表示类型。
另外。命令行中。watchpoint另一些简写,set简写为s。watch简写为wa,variable简写为v。
以下的演示样例是来自 http://www.dreamingwish.com/article/lldb-usage-a.html 站点的几个命令:
第一个命令是监听_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”输入选项,这个的作用非常easy理解,仅仅有在设置的condition表达式为YES的情况下这些断点才会起作用。
比如,下图中的breakpoint在推断字符串相等的时候才会停止执行:
能够注意到这里使用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:的异常来说明。代码例如以下:
设置Breakpoint:
能够看到上图中。我们一共设置了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的描写叙述信息,以下是调用堆栈:
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的背景色改为红色:
action的内容为“expression [[cell selectedBackgroundView] setBackgroundColor:[UIColor redColor]]”,这里的表达式先不用关心。我们后面LLDB章节会讲到,改动之后,当我们点击cell的时候,cell的背景就会例如以下图一样变红:
使用这样的方式,我们在不须要改动一行代码的情况下,仅仅须要通过改动breakpoint,就能够实现对UI的各种调试效果。
參考:
- https://developer.apple.com/library/ios/recipes/xcode_help-breakpoint_navigator/articles/about_breakpoint_navigator.html
- http://www.objc.io/issues/19-debugging/lldb-debugging/
- http://blog.csdn.net/freshforiphone/article/details/8546677
- https://www.bignerdranch.com/blog/xcode-breakpoint-wizardry/
- http://lldb.llvm.org/tutorial.html, LLDB命令大全
LLDB
经常使用命令
help
直接输入help命令,列出全部可用的commands。
使用 help <command> 来获得某个command的详细使用方法
expression
简写形式:expr / e。在执行调试时执行表达式,用来直接改动程序的变量的值,或者使用这个命令声明一个变量对象。
比如:改动变量的值:expression count = 20
或者声明一个新变量a:
expression命令能够带有參数。但也会带来一些问题。比如: e -h +17 命令,这个命令就easy产生混淆,究竟-h是參数flag,然后+17是输入变量。还是说要计算 -h+17 表达式的终于值。
LLDB提供的解决方案很easy,使用“ -- ”来指定命令參数的终止,命令输入的開始。比如,e -h -- +17表示前文中命令的第一个解释,e -- -h+17
表示表示前文中命令的第二个解释。
print
简写形式:prin / pri / p。可是不能用pr表示。由于会和process混淆。实际上。你会发现。lldb对于命令的简称,是头部匹配方式,仅仅要不相互混淆,能够任意简称某个命令。
实际上,假设在console中输入“help print”,就会得到
’print’ is an abbreviation for ‘expression --‘ 这句话,也就是说print实际上就相当于 expression -- 。
这里就非常easy理解以下命令是怎样工作的。p _lastPoiID=20。这行命令表达式会被运行。
打印一个对象的话,使用 po 命令,使用expression表示的话就是 expression -O -- XXX
使用 print/<fmt> 或者 p/<fmt> 来格式化打印变量,能够參考相应的格式列表:https://sourceware.org/gdb/onlinedocs/gdb/Output-Formats.html。p/x使用十六进制打印一个变量,p/t
使用二进制打印整数 ,p/c 打印字符,p/s 打印c字符串。
既然我们能够print对象和简单类型,而且在debugger中通过expression命令直接改动他们。那么我们能够通过使用一些变量来减轻我们的工作量。前面讲过我们能够直接使用expression命令来声明变量。但须要注意的是,新声明的变量必须以 $ 符号開始。
最后一个命令报错了,是由于LLDB没法确定结果的类型。须要强制转换一下来告诉LLDB。
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语法。比方下图中的问题。
改成例如以下的格式才行:
Chisel
基于LLDB对Python插件的支持,http://lldb.llvm.org/python-reference.html。facebook开发开源了一套LLDB命令库。https://github.com/facebook/chisel,里面包括了非常多非常有意思的命令工具。
安装方式非常easy。使用brew工具。具体參考官方站点,不多说。
安装之后,使用help命令。在以下能够 user-defined commands 能够找到这个框架提供的一些自己定义命令。Chisel提供了许多很实用的命令行工具,调试UI的时候很方便,详细能够參考官方站点。
參考
- http://www.objc.io/issues/19-debugging/lldb-debugging/
- http://www.starfelix.com/blog/2014/03/17/lldbdiao-shi-ming-ling-chu-tan/
- http://www.dreamingwish.com/article/lldb-usage-a.html
- https://developer.apple.com/library/ios/documentation/IDEs/Conceptual/gdb_to_lldb_transition_guide/document/Introduction.html
- http://lldb.llvm.org/lldb-gdb.html
- http://lldb.llvm.org/tutorial.html
- https://github.com/facebook/chisel