LLDB
是XCode内置的为我们开发者提供的调试工具,可以在设置断点的时候在控制台中输入相关的lldb命令进行调试。
help
- help :列出所有的命令
- help
: 列出某个命令更多的细节,例如 help print
- print
: 打印需要查看的变量,简写 p - call 与 print 命令功能一样
- po
: 打印对象的 description 方法的结果 - 打印不同格式可以用 p/x number 打印十六进制,p/t number 打印二进制,p/c char 打印字符。这里是完整清单 https://sourceware.org/gdb/onlinedocs/gdb/Output-Formats.html
(lldb) p (int)[[[self view] subviews] count]
最后你看到的输出会是:
(int) $2 = 2
你会发现输出的信息中带有$1、$2的字样。实际上,lldb 的每次查询结果会保存在一些持续变量中,($[0-9]+),这样你可以在后面的查询中直接使用这些值。比如现在我接下来要重新取回$1的值:
(lldb) po $1
(UIView *) $1 = 0x0824c800 ; layer = ; contentOffset: {0, 0}>
expression
- expression
-- : 声明一个临时变量或改变一个变量的值,简写 expr 或 e
// 把self.str 的值改为 "111"
(lldb) e self.str = @"111";
// 声明一个临时变量str2
(lldb) e NSString *$str2 = @"222"`
注意:要使用
$
符号
image
image 命令可用于寻址,有多个组合命令。比较实用的用法是用于寻找栈地址对应的代码位置。比如下面一段代码:
NSArray *array = @[@"1", @"2"];
NSLog(@"%@", array[2]);
这段代码有明显的错误,程序运行这段代码后会抛出下面的异常:
2017-07-14 22:26:40.836 octest[34972:1928364] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 2 beyond bounds [0 .. 1]'
*** First throw call stack:
(
0 CoreFoundation 0x000000010d0c6b0b __exceptionPreprocess + 171
1 libobjc.A.dylib 0x000000010cb2b141 objc_exception_throw + 48
2 CoreFoundation 0x000000010d00438b -[__NSArrayI objectAtIndex:] + 155
3 octest 0x000000010c55ac29 -[ViewController buttonDidClick:] + 153
4 UIKit 0x000000010d4ebd82 -[UIApplication sendAction:to:from:forEvent:] + 83
5 UIKit 0x000000010d6705ac -[UIControl sendAction:to:forEvent:] + 67
6 UIKit 0x000000010d6708c7 -[UIControl _sendActionsForEvents:withEvent:] + 450
7 UIKit 0x000000010d66f802 -[UIControl touchesEnded:withEvent:] + 618
8 UIKit 0x000000010d5597ea -[UIWindow _sendTouchesForEvent:] + 2707
9 UIKit 0x000000010d55af00 -[UIWindow sendEvent:] + 4114
10 UIKit 0x000000010d507a84 -[UIApplication sendEvent:] + 352
11 UIKit 0x000000010dceb5d4 __dispatchPreprocessedEventFromEventQueue + 2926
12 UIKit 0x000000010dce3532 __handleEventQueue + 1122
13 CoreFoundation 0x000000010d06cc01 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
14 CoreFoundation 0x000000010d0520cf __CFRunLoopDoSources0 + 527
15 CoreFoundation 0x000000010d0515ff __CFRunLoopRun + 911
16 CoreFoundation 0x000000010d051016 CFRunLoopRunSpecific + 406
17 GraphicsServices 0x0000000110e9ba24 GSEventRunModal + 62
18 UIKit 0x000000010d4ea134 UIApplicationMain + 159
19 octest 0x000000010c55b6df main + 111
20 libdyld.dylib 0x000000010ff3065d start + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException
我们可以看到出错是在堆栈信息的第3行提示的 buttonDidClick
方法中,我们怀疑出错的地址是 0x000000010c55ac29,可以根据执行文件名判断,或者最小的栈地址)。为了进一步精确定位,我们可以输入以下的命令:
(lldb)image lookup --address 0x000000010c55ac29
命令执行后返回:
Address: octest[0x0000000100001c29] (octest.__TEXT.__text + 473)
Summary: octest`-[ViewController buttonDidClick:] + 153 at ViewController.m:61
我们可以看到,出错的位置是 ViewController.m 的第61行。
无法确定类型
(lldb) e NSArray *$array = @[ @"Saturday", @"Sunday", @"Monday" ]
(lldb) p [$array count]
2
(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) p (char)[[$array objectAtIndex:$a] characterAtIndex:0]
'M'
断点管理
- breakpoint list : 可以查看所有断点, 简写 br li
设置断点触发条件
断点可以设置条件,只有当条件满足时,才会进入断点,并且可以设置进入断点时执行某些操作,比如打印log,执行lldb命令等。
这种应用场景主要是在循环遍历时,想要断点跟踪就只能通过这种方式了,除非添加NSLog打印,但是这种需要手动添加代码,在调试时才想到要添加一些打印语句,这时候又得重新运行,这太慢了,所以懂得设置断点触发条件将会大大提高效率。
操作如下:
断点触发条件(打印log)
断点触发条件(执行lldb命令)
打印视图层次结构
(lldbd) po [self.view recursiveDescription]
更新UI
(lldb) e ((UIButton *)sender).backgroundColor = [UIColor yellowColor]
(UICachedDeviceRGBColor *) $0 = 0x000061800007e280
(lldb) e (void)[CATransaction flush]
流程控制
- continue : 继续执行下去到达下一个断点(process continue),或者使用缩写 c
- next : 单步执行到下一个语句(process step-over),缩写 n
- step : 跳进一个函数调试(process step-into),缩写 s
- finish : 继续执行到下一个断点或返回语句,然后再次停止(process step-out)
- return
: 会在当前断点处直接返回出函数,函数剩余部分不会被执行(process )
查找Button的target
查看按钮按下后谁会接收到按钮发出的action
(lldb) po [sender allTargets]
{(
)}
(lldb) po [sender actionsForTarget:(id)0x7fa6415083f0 forControlEvent:UIControlEventTouchUpInside]
<__NSArrayM 0x600000056e60>(
buttonDidClick:
)
更多命令参考:The LLDB Debugger
另外,facebook 开源了一个扩展的 LLDB命令库,有兴趣可以看看。
参考文档:
LLDB调试命令初探
与调试器共舞 - LLDB 的华尔兹