LLDB是个开源的内置于XCode的具有REPL(read-eval-print-loop)特征的Debugger,其可以安装C++或者Python插件。
本系列针对于已经知道何为debugger,且有gdb或者lldb使用经验的读者。对于lldb的娴熟使用者,也可以用来作为查阅手册。
一、lldb命令
lldb与gdb命令名的对照表:http://lldb.llvm.org/lldb-gdb.html
1.help命令
单单执行help命令会列出所有命令列表,用户加载的插件一般来说列在最后。
执行help 可以打印指定command的帮助信息,至于插件提供的命令,其帮助信息取决于插件本身的实现。
例如 help print会打印内建命令print的使用帮助。
2.print命令
print命令的简化方式有prin pri p,唯独pr不能用来作为检查,因为会和process混淆,幸运的是p被lldb实现为特指print。
实际上你会发现,lldb对于命令的简称,是头部匹配方式,只要不混淆,你可以随意简称某个命令。
例如:
值得说明的是,返回的$0是命令结果的引用名,使用$0可以进行print $0 + 7这样打印出106,当然,$0可以被用于任何其他表达式或者接收参数的命令。
3.expression命令
expression命令可以用来修改变量的值,当然大部分情况下,使用xcode提供的可视化编辑器更方便。
(lldb) p count
(NSUInteger) $4 = 12
(lldb)e count = 42
(NSUInteger) $5 = 42
实际上print相当于expression --,而--的意思是命令的参数终止,跟在--后面的都是命令的输入数据。
要打印一个对象,则需要使用e -O -- anObj,而e -O -- 的缩写正是我们常用的po命令:
(lldb) po $8
(
foo,
bar
)
要按特定格式来打印一个变量则使用p/
命令:
(lldb) p 16
16
(lldb)p/x 16
0x10
(lldb) p/t 16
0b00000000000000000000000000010000
(lldb) p/t (char)16
0b00010000
或者使用p/c打印字符,p/s打印c字符串,详细格式查阅这里。
既然我们已经知道print实际上是expression --的简写,那么在p(或者po)后面跟上复杂的表达式则可以先计算表达式再打印最后的计算结果。
(lldb) e int $a = 2
(lldb) p $a * 19
38
(lldb) e NSArray *$array = @[ @"Saturday", @"Sunday", @"Monday" ]
(lldb) p [$array count]
3
(lldb) po [[$array objectAtIndex:0] uppercaseString]
SATURDAY
(lldb) p [[$array objectAtIndex:$a] characterAtIndex:0]
error: no known method '-characterAtIndex:'; cast the message send to the method's return type
error: 1 errors parsing expression
值得注意的是,一旦出现了上述错误提示,说明lldb无法判定某一步的计算结果是什么数据类型,这时需要强制类型转换来告诉lldb:
(lldb) p (char)[[$array objectAtIndex:$a] characterAtIndex:0]
'M'
(lldb) p/d (char)[[$array objectAtIndex:$a] characterAtIndex:0]
77
4.流程控制命令
实际上使用xcode自带的可视化工具来控制“继续”“暂停”“下一步”“进入”“跳出”更简单,但这里还是列出其所对应的命令名:
继续:process continue, continue, c
下一步:thread step-over, next, n
进入:thread step-in, step, s
跳出:thread step-out, finish, f
5.thread return命令
执行thread return命令可以使得当前函数立即返回,也就是说,后续代码都不会执行了。当然执行此命令可能会使得arc的计数追踪出现错乱。
thread return命令需要一个参数来指明函数强制返回时的返回值。
6.断点命令
一般来说,在xcode中新建/删除“行断点”是很容易的,但是断点还有很多进阶使用方法:
条件断点、条件执行、记录日志、自动继续、重复断点跳过。
使用xcode提供的可视化工具来操作是很容易的:
7.在debugger中执行任意代码
(lldb) e char *$str = (char *)malloc(128)
(lldb) e (void)strcpy($str, "wxrld of warcraft")
(lldb) e $str[1] = 'o'
(char) $0 = 'o'
(lldb) p $str
(char *) $str = 0x00007fd04a900040 "world of warcraft"
(lldb) e (void)free($str)
所以,在debugger中可以修改view的颜色、尺寸、甚至创建controller来push。
8.watchpoint
watchpoint可以在某个变量被写入/读取时暂停程序运行:
(lldb) watchpoint set expression -- (int*)&_abc4
Watchpoint created: Watchpoint 7: addr = 0x15e36d3c size = 4 state = enabled type = w
new value: 0x00000000
(lldb) watchpoint set v -w read _abc4
Watchpoint created: Watchpoint 8: addr = 0x15e36d3c size = 4 state = enabled type = r
watchpoint spec = '_abc4'
new value: 0
(lldb) watchpoint set v -w read_write _abc3
Watchpoint created: Watchpoint 9: addr = 0x15e36d38 size = 4 state = enabled type = rw
watchpoint spec = '_abc3'
new value: 0
实际上可以使用watchpoint来监视任意一段内存的读写。
使用XCode也可以方便地创建watchpoint。
XCode的可视化debug工具中的watch是一个write类型watchpoint(也就是默认的)
另外,上述语句中 v是variable的简写,同样的,set可以简写为s,watch可以简写为wa,而-w后面的参数是不可以简写的必须为read、write或者read_write。
当前在arm和x86上,我们一次最多创建4个watchpoint,继续创建会提示错误。
9.符号断点
用xcode的可视化工具创建符号断点很简单,在Add symbolic breakpoint中填入符号名即可,这里给出objective-c的函数符号断点的写法:
-[MyViewController viewDidAppear:]
+[MyViewController sharedInstance]
10.查看内存
使用XCode的可视化工具来查看memory,要注意watch memory of "p" 和watch memory of "*p"的区别。
手动执行命令可以help x或者 help memory。
小结
lldb的用法很灵活多样,但是XCode的可视化调试工具覆盖面有限,一些高级用法仍需手动输入命令,甚至结合python脚本。
二、使用
开胃小菜--简单的断点调试
在xcode中打开一个app,在想要break的行号上单击,即可生成一个深色的箭头标识--断点。如下图,在viewDidLoad:中设置了断点。
运行app,等待。。。就可以看到xcode在断点处进入调试模式,现在让我们把视线移到xcode右下角的控制台,有木有看到(lldb)这样一行,鼠标移到此行,输入
po [self view]
回车,看看控制台上是不是多了一些view的信息,如下图:
po(print object)是LLDB的一个命令,其主要功能是输出objective-c中对象(objects)的信息,与之相似的另外一个命令是 p(print),其主要功能是输出原生类型(boolean、integer、float、etc)的信息。
控制台输入
p (int)[[[self view] subviews] count]
结果如下
(int) $2 = 2
注意这个使用了类型转换告知调试器应该如何处理返回值。
技巧一:运行时修改变量的值
你以前怎么验证是不是某个变量的值导致整段程序不能正常工作?修改代码中的变量的值,然后cmd+r重新启动app?现在你不需要这么做了,只需要设置一个断点,当程序在这进入调试模式后,使用expr命令即可在运行时修改变量的值。
假如有一个loginWithUsername:方法,需要两个参数:username,password。
首先设置好断点,如下图所示:
运行app,进入断点模式后,在(lldb)后输入
expr username = @"username"
expr password = @"badpassword"
控制台会返回以下信息
(NSString *) $0 = 0x3d3504c4 @"username"
(NSString *) $1 = 0x1d18ef60 @"badpassword"
现在跳出断点,执行断点之后的两条输出语句,控制台会有以下输出
(0x1c59aae0) A line for the breakpoint
(0x1c59aae0) Username and Password after: username:badpassword
看到看吧,我们在运行时修改了变量的值,事情还可以变的更简单一些,我们可以编辑断点,让它自动填充需要的修改的变量的值,并且可以选择在此断点处不进入断点模式,仅仅修改指定变量的值,然后自动执行后续代码。
右击断点选择“Edit Breakpoint...”(或者按住cmd+option,单击断点),然后如下图所示设置断点
注意选中了最后一行(“Automatically continue after evaluating”)的选择框,这就保证运行到这个断点的时,填充变量的值,然后继续运行,并不在此处断点进入调试模式。
运行app,你会得到和上述手动设置变量的值一样的输出。
接下来单击断点,使其处于禁用状态,现在箭头的颜色应该是浅蓝色的,重新运行app,你会发现username和password的值没有在运行时被改变了。
技巧二:设置断点触发条件
断点的另外一个重要作用,是可以设置触发断点生效的条件,这样我们就可以在运行时针对特定的数据进行分析,观察app是否运行在正确的轨道上。如下图:
上述截图可以看到如下语句
(BOOL)[(NSString*)[item valueForKey:@"ID"] isEqualToString:@"93306"]
通过这行语句,我们告诉编译器:当item中ID等于93306时,此断点生效,进入断点调试模式。
技巧三:格式化输出数据
如果你厌倦了代码里无穷无尽的NSLog,幸运的是我们可以在编辑断点使其输出格式化字符串就像平常编码时一样。不过有一点需要注意,平常编码时可能会使用NSString‘s stringWithFormat:输出格式化字符串,不过这个方法貌似在断点中木有效果,你需要使用alloc/init形式的方法,如下:
po [[NSString alloc] initWithFormat:@"Item index is: %d", index]
运行app,就能在控制台看到想要的输出啦!
简单!强大!这就是LLDB给你的选择,从此代码里可以不用再有NSLog满天飞的情况了,代码变得更干净了,心情变得更愉悦了!
参考:
http://www.cnblogs.com/xiaxianbing/p/5621925.html
https://my.oschina.net/notting/blog/115294