LLDB 是一个有着 REPL 的特性和 C++ ,Python 插件的开源调试器。LLDB 绑定在 Xcode 内部,存在于主窗口底部的控制台中。调试器允许你在程序运行的特定时暂停它,你可以查看变量的值,执行自定的指令,并且按照你所认为合适的步骤来操作程序的进展。(这里有一个关于调试器如何工作的总体的解释。)
基础
help <变量>
//了解命令的更多细节p <变量>
//p是print的简写 用来打印值类型
另外,结果中有个
$0
。实际上你可以使用它来指向这个结果。试试print $0 + 7
,你会看到106
。任何以美元符开头的东西都是存在于 LLDB 的命名空间的,它们是为了帮助你进行调试而存在的。po <变量>
//po是print object的简写 用来打印对象类型尝试输入
p ocValue
输出结果比较繁琐
(__NSCFConstantString *) $1 = 0x0000000100001038 @"Hellow World"
如果是复杂的对象则更糟
(lldb) p @[ @"foo", @"bar" ] (__NSArrayI *) $4 = 0x0000000100600020 @"2 objects"
通常,我们想看的是对象的description方法的结果,可以
(lldb) po ocValue Hellow World
e <赋值表达式>
//e是expression的简写(lldb) e ocValue=@"new Value" (NSTaggedPointerString *) $7 = 0x0b0243f720240095 @"new Value" (lldb) po ocValue new Value (lldb) e count=3 (NSUInteger) $9 = 3 (lldb) po count 3
thread return <可选参数>
//在执行时它会把可选参数加载进返回寄存器里,然后立刻执行返回命令,跳出当前栈帧。这意味这函数剩余的部分不会被执行。起到的作用类似
int calculateTheTrickyValue { return 9; /* some code ... }
想象把断点放在函数的开头,然后用
thread return
命令重写函数的行为,然后继续。想象一下让这个过程自动化,听起来不错,不是吗?br li
//列出所有的断点(lldb) br list Current breakpoints: 1: file = '/Users/terry/Desktop/LLDBTest/LLDBTest/main.m', line = 15, locations = 1, resolved = 1, hit count = 1 1.1: where = LLDBTest`main + 65 at main.m:15, address = 0x0000000100000ef1, resolved, hit count = 1 (lldb)
br enable <breakpointID> 或者 br disable <breakpointID>
//断点的开启(lldb) br enable 1 1 breakpoints enabled. (lldb) br disable 1 1 breakpoints disabled. (lldb)
高级技巧
创建普通断点(指定某一行的断点)和符号断点(根据调用的方法指定断点)
b main.m:17
//b 是_regexp-break 断点定位到main.m文件的17行b add
//断点打在方法的开始
通过Xcode的UI创建符号断点
在Symbol
对应的输入框中输入-[NSArray objectAtIndex:]
,这样每次调用这个函数的时候,程序都会停止,不管是你调用还是苹果调用。
配置断点
使用Xcode UI
输出结果如下
(NSUInteger) $1 = 3
"测试ShellCommand"
count的结果:3
(lldb)
并弹出对话框,发出声音
Conditon
//断点生效的条件ignore
//告诉断点最初的 n 次调用 (并且条件为真的时候) 的时候不要停止。Action
//触发断点后的行为Debugger Command
//使用lldb命令 示例见上图AppleScript
//使用AppleScript,可操作本地应用Shell Command
//使用Shell命令Log Message
//类似NSLogAutomatically continue after evaluation actions.
//选中它,调试器会运行你所有的命令,然后继续运行。看起来就像没有执行任何断点一样 。
使用LLDB命令 配置断点
(lldb) b add
Breakpoint 2: 77 locations.
(lldb) br modify -c 'a == 3' 2
(lldb) br command add 2
Enter your debugger command(s). Type 'DONE' to end.
> p a
> DONE
(lldb) br list 2
2: name = 'add', locations = 77, resolved = 77, hit count = 0
Breakpoint commands:
p a
Condition: a == 3
2.1: where = LLDBTest`add + 10 at main.m:12, address = 0x0000000100000e8a, resolved, hit count = 0
运行到断点的结果
p a
(int) $4 = 3
(lldb)
FaceBook插件Chisel
安装
brew update
brew install chisel
安装完成按照安装日志上的提示,在~/.lldbinit
文件中添加一行,没有则新建。 提示类似如下:
command script import /usr/local/opt/chisel/libexec/fblldb.py
备注:每次Xcode启动,都会加载~/.lldbinit
文件
常用命令
pviews
//递归打印所有的view,并能标示层级,等价于[view recursiveDescription]
(lldb) pviews self.view <UIView: 0x12ee993b0; frame = (0 0; 320 568); autoresize = W+H; layer = <CALayer: 0x12ee98690>> | <_UILayoutGuide: 0x12ee99760; frame = (0 0; 0 0); hidden = YES; layer = <CALayer: 0x12ee8f2c0>> | <_UILayoutGuide: 0x12ee9a190; frame = (0 0; 0 0); hidden = YES; layer = <CALayer: 0x12ee98580>>
pvc
//递归打印所有的viewController,并能标示层级,等价于[UIViewController _printHierarchy]
(lldb) pvc <TabBarController: 0x13772fd0; view = <UILayoutContainerView; 0x151b3a30>; frame = (0, 0; 414, 736)> | <UINavigationController: 0x1602b800; view = <UILayoutContainerView; 0x1b00aca0>; frame = (0, 0; 414, 736)> | | <FirstViewController: 0x16029c00; view = <UIView; 0x1b01e1c0>; frame = (0, 0; 414, 736)> | <UINavigationController: 0x138c5200; view = <UILayoutContainerView; 0x1316a080>; frame = (0, 0; 414, 736)> | | <SecondViewController: 0x16030400; view = <UIView; 0x2094b370>; frame = (0, 0; 414, 736)> | | | <SecondChildViewController: 0x15af6000; view = <UIView; 0x18d4e650>; frame = (0, 64; 414, 628)> | <UINavigationController: 0x1383ca00; view = <UILayoutContainerView; 0x13180070>; frame = (0, 0; 414, 736)> | | <ThirdViewController: 0x138ddc00; view = <UIView; 0x18df6650>; frame = (0, 0; 414, 736)> | | | <ThirdChild1ViewController: 0x1393fe00; view = <UIView; 0x131ec000>; frame = (0, 0; 414, 672)> | | | <ThirdChild2ViewController: 0x138dce00; view = <UIView; 0x204075a0>; frame = (414, 0; 414, 672)> | | | <ThirdChild3ViewController: 0x138a8e00; view = <UIView; 0x20426250>; frame = (828, 0; 414, 672)> | <UINavigationController: 0x160eca00; view = <UILayoutContainerView; 0x152f7d90>; frame = (0, 0; 414, 736)> | | <FourViewController: 0x13157cc0; view not loaded>
visualize
//可以让你使用Mac的预览打开一个 UIImage, CGImageRef, UIView, 或 CALayer。 这个功能或许可以帮我们用来截图、用来定位一个view的具体内容(lldb) visualize imageView
fv & fvc
//通过类名搜索当前内存中存在的view和viewController实例的命令,支持正则搜索(lldb) fv scrollView 0x18d3b8c0 UIScrollView (lldb) fvc Home 0x1393fe00 HomeFeedsViewController 0x138a8e00 HomeFeedsViewController
show & hide <view or layer>
//显示和隐藏一个指定的 UIView . 你甚至不需要Continue Progress. 就可以看到效果mask/unmask <view or layer> //border/unborder <view or layer>
//添加蒙版 添加边框,用来表示view或layer的位置calfush
//重新绘制界面 等价于[CATransaction flush]
,要注意如果在动画过程中执行这个命令,就直接渲染出动画结束的效果;当你想在调试界面颜色、坐标之类的时候,可以直接在控制台修改属性,然后caflush就可以看到效果啦,是不是要比改代码,然后重新build省事多了呢。(lldb) e (void)[self.label setBackgroundColor:[UIColor greenColor]] (lldb) caflush
bmessage
//这个命令就是用来打断点用的了,虽然大家断点可能都喜欢在图形界面里面打,但是考虑一种情况:我们想在 [MyViewController viewWillAppear:] 里面打断点,但是 MyViewController并没有实现 viewWillAppear: 方法, 以往的作法可能就是在子类中实现下viewWillAppear:,然后打断点,然后rebuild。
那么幸好有了 bmessage命令。我们可以不用这样就可以打这个效果的断点: (lldb) bmessage -[ViewController viewWillAppear:] 上面命令会在其父类的 viewWillAppear: 方法中打断点,并添加上了条件:[self isKindOfClass:[ViewController class]]//创建断点 (lldb) bmessage -[ViewController viewDidAppear:] Setting a breakpoint at -[ViewController viewDidAppear:] with condition (void*)object_getClass((id)$x0) == 0x00000001000acd58 Breakpoint 2: where = ChiselTest`-[ViewController viewDidAppear:] at ViewController.m:31, address = 0x00000001000aa6a0 //添加条件 (lldb) breakpoint modify -c '[self isKindOfClass:[ViewController class]]' 2
wivar <Object> <示例变量>
\观察实例变量的变化假设你有一个 ViewController,不知道为什么它的 _label2 实例变量被重写了 (糟糕)。因为有可能并不涉及到方法,我们不能使用符号断点。相反的,我们想监视什么时候这个地址被写入。
自定义命令
//我们也可以自定义插件,不过前提是要懂一些 python。 比如设计一个打印keyWindow的windowLevel的命令创建python脚本文件 /magical/commands/example.py :
#!/usr/bin/python # Example file with custom commands, located at /magical/commands/example.py import lldb import fblldbbase as fb def lldbcommands(): return [ PrintKeyWindowLevel() ] class PrintKeyWindowLevel(fb.FBCommand): def name(self): return 'pkeywinlevel' def description(self): return 'An incredibly contrived command that prints the window level of the key window.' def run(self, arguments, options): # It's a good habit to explicitly cast the type of all return # values and arguments. LLDB can't always find them on its own. lldb.debugger.HandleCommand('p (CGFloat)[(id)[(id)[UIApplication sharedApplication] keyWindow] windowLevel]')
其中定义了PrintKeyWindowLevel的类,需要实现 name description run 方法来分别告诉名称、描述、和执行实体。
创建好脚本后,然后在前面安装时创建的 ~/.lldbinit文件中添加一行:
script fblldb.loadCommandsInDirectory('/magical/commands/')
然后重启Xcode之后就可以使用自定义的命令啦。
LLDB和Python
LLDB 有内建的,完整的 Python 支持。在LLDB中输入 script,会打开一个 Python REPL。你也可以输入一行 python 语句作为 script 命令 的参数,这可以运行 python 语句而不进入REPL:
(lldb) script Python Interactive Interpreter. To exit, type 'quit()', 'exit()'. >>> import os >>> os.system("open http://www.objc.io/") 0
等价于
(lldb) script Python Interactive Interpreter. To exit, type 'quit()', 'exit()'. >>> import os >>> os.system("open http://www.objc.io/") 0
结果:在默认浏览器中打开网址:http://www.objc.io/
这就是Chisel的实现原理。
用LLDB优化调试
- 配置断点
Log Message
代替NSLog
- 使用
expression
改变变量,继续运行,代替修改代码,改变变量值,重新编译 - 使用
thread return <可选参数>
动态添加return
代码, 代替代码添加return
,重新编译
常见错误整理
不明类型或者类型不匹配
(lldb) p NSLog(@"%@",[self.view viewWithTag:1001]) error: 'NSLog' has unknown return type; cast the call to its declared return type error: 1 errors parsing expression
多见于id类型,比如NSArray中某个值),那我们就必须显式声明类型
p (void)NSLog(@"%@",[self.view viewWithTag:1001])
找不到方法
(lldb) po self.view.frame error: unsupported expression with unknown type error: unsupported expression with unknown type error: 2 errors parsing expression
这似乎是lldb的一个bug,无法通过点属性访问的方法打印framework里面的对象,但是自己在app里面定义的就可以。我们把上面的命令改动一下:
(lldb) p (CGRect)[self.view frame] (CGRect) $0 = origin=(x=0, y=0) size=(width=320, height=480)
或者
(lldb) po self.view <UIView: 0x15fe67a10; frame = (0 0; 320 568); autoresize = W+H; layer = <CALayer: 0x15fe67d80>>