#LLDB调试器
###简介
你是否曾经苦恼于理解你的代码,而去尝试打印一个变量的值?
```
NSLog(@"%@", password);
```
或者跳过一个函数调用来简化程序的行为?
实际应该调用这个函数:Foo()
```
NSNumber *n = @7; //complexCalculate() ;
```
或者伪造一个函数实现?
```
int complexCalculate {
return 9;
/*
万行代码.
...
}
```
并且每次必须重新编译,从头开始?
构建软件是复杂的,并且 Bug 总会出现。一个常见的修复周期就是修改代码,编译,重新运行,并且祈祷出现最好的结果。
但是不一定要这么做。你可以使用调试器。而且即使你已经知道如何使用调试器检查变量,它可以做的还有很多。
这篇文章将试图挑战你对调试的认知,并详细地解释一些你可能还不了解的基本原理,然后展示一系列有趣的例子。
###基础
如图所示,当我们在程序的301行打上断点的时候,触发这段代码,程序就会进入lldb的调试模式.运行我们和调试器进行交互.这时候我们可以做些什么呢?
###help
最简单命令是 help,它会列举出所有的命令。如果你忘记了一个命令是做什么的,或者想知道更多的话,你可以通过 help来了解更多细节,例如 help print 或者 help thread。如果你甚至忘记了 help 命令是做什么的,你可以试试 help help。不过你如果知道这么做,那就说明你大概还没有忘光这个命令。??
###po
使用"help po" 我们能简单的看到,官方对这个命令的简单描述.
- 在当前上下文能评估一个表达式(OC对象或者Swift),这个对象可以是用户自己定义的或者是在当前区域内有效的变量.
#####使用场景
1. 基本用法,打印OC对象.
2. po [searchBar recursiveDescription];
利用这个命令,我们可以清楚看到某个控件的内部结构....非常利于我们去修改和调整他们.(比如说tabbar,还有自定义控件,等等);
当然,现在我们也可以通过Xcode的层级化视图来看...这个只能说各有千秋吧.
3. po [UIViewController _printHierarchy]
苹果在IOS8也默默的添加了 [UIViewController _printHierarchy] 这个命令,利用它我们可以对viewController的结构一目了然。这对帮我们了解程序基本结构是非常有帮助的.
4. 如何控制po在打印对象的时候,所打印出来的信息格式呢?
打印对象,会调用对象description方法。是print-object的简写
#####常见小问题
1. 在po一个带宏的值的时候,由于LLDB调试器是不认识定义的宏的,这个时候,我们通常为宏取名的时候,key和value是相同的....给宏加上@""就可以正常使用了.![Alt text](./1447984218129.png)
这是时候,去lldb调试里面去打宏的时候,是还有智能提示的.但是他最终却不认识他...这个时候,我们就需要这样做.![Alt text](./1447984280359.png)
2. po其实是print object的简写...它有点奇怪的就是,拒绝打印结构体的样子..
```
// po对于返回值类型不确定的东西,将无法打印.
(lldb) po cell0.frame
error: property 'frame' not found on object of type 'IMYAccountCell *'
error: 1 errors parsing expression
(lldb) po [cell0 frame]
error: Execution was interrupted, reason: EXC_BAD_ACCESS (code=EXC_I386_GPFLT).
The process has been returned to the state before expression evaluation.
(lldb) po (CGRect)[cell0 frame]
(origin = (x = 0, y = 249), size = (width = 375, height = 45))
(origin = (x = 0, y = 249), size = (width = 375, height = 45))
(lldb) po self.navigationController.navigationBar
error: property 'navigationBar' not found on object of type 'id'
error: 1 errors parsing expression
// 有时候用点语法无效,尝试用[]来获取
(lldb) po [self.navigationController navigationBar]
<UINavigationBar: 0x7fbe627d9a20; frame = (0 20; 375 44); opaque = NO; autoresize = W; gestureRecognizers = <NSArray: 0x7fbe627d83e0>; layer = <CALayer: 0x7fbe627d7dd0>>
```
###expression
#####使用场景
1. 在开发中,我们经常会遇到这样一种情况:我们设置一个视图的背景颜色,运行后发现颜色不好看。嗯,好吧,在代码里面修改一下,再编译运行一下,嗯,还是不好看,然后再修改吧~~这样无形中浪费了我们大把的时间。在这种情况下,expression命令强大的功能就能体现出来了,它不仅会改变调试器中的值,还改变了程序中的实际值。我们先来看看实际效果,如下所示
但是只有程序继续运行之后才会看到界面的变化。因为改变的内容必须被发送到渲染服务中,然后显示才会被更新。
渲染服务实际上是一个另外的进程 (被称作 backboardd)。这就是说即使我们正在调试的内容所在的进程被打断了,backboardd 也还是继续运行着的。
这意味着你可以运行下面的命令,而不用继续运行程序:
```
(lldb) e (void)[CATransaction flush]
```
2. 这点就比较实用了.也许我们在程序运行时去改变视图的一些信息,用户并不是非常大,可是,如果是运行时去改变程序里面的一些变量值呢?个人觉得还是比较有用的.(脑洞越大,这个命令的功能就越大.)
###断点配合lldb
###流程控制命令
实际上使用xcode自带的可视化工具来控制“继续”“暂停”“下一步”“进入”“跳出”更简单,但这里还是列出其所对应的命令名:
继续:process continue, continue, c
下一步:thread step-over, next, n
进入:thread step-in, step, s
跳出:thread step-out, finish, f
###watchpoint
watchpoint可以在某个变量被写入/读取时暂停程序运行:
###命令补全(Command Completion)
LLDB支持源文件名,符号名,文件名,等等的命令补全(Commmand Completion)。终端窗口中的补全是通过在命令行中输入一个制表符来初始化的。Xcode控制台中的补全与在源码编辑器中的补全方式是一样的:补全会在第三个字符被键入时自动弹出,或者通过Esc键手动弹出。
###其他功能
1. 设置断点
2. 查看内存
3. 查看线程状态
4. 查看调用栈状态
5. 命令别名
###Python脚本
对于高级用户来说,LLDB有一个内置的Python解析器,可以通过脚本命令来访问。调试器中的所有特性在Python解析器中都可以作为类来访问。这样,我们就可以使用LLDB-Python库来写Python函数,并通过脚本将其加载到运行会话中,以执行一些更复杂的调试操作。
#Chisel-LLDB命令插件,让调试更Easy(江大大推荐)
###1.安装Chisel
源码地址: [Chisel](https://github.com/facebook/chisel)
Chisel 使用 homebrew 来安装,如果你没有安装homebrew, 参考 [homebrew](http://brew.sh/)。
```
brew update
brew install chisel
```
安装完成按照安装日志上的提示,在~/.lldbinit文件中添加一行,没有则新建。 提示类似如下:
```
Add the following line to ~/.lldbinit to load chisel when Xcode launches:
command script import /usr/local/opt/chisel/libexec/fblldb.py
```
做好上面的步骤,然后重启Xcode就可以尝试下了。
###2.内置命令
Chisel 为lldb提供了新增的便捷命令,是非常实用的命令
#####2.1 pviews
这个命令可以递归打印所有的view,并能标示层级,相当于 UIView 的私有辅助方法 [view recursiveDescription] 。 善用使用这个功能会让你在调试定位问题时省去很多麻烦。
使用示例:
```
(lldb) pviews view
<TestView: 0x18df8070; baseClass = UIControl; frame = (144 9; 126 167); layer = <CALayer: 0x18df8150>>
| <UIView: 0x18df81d0; frame = (0 0; 126 126); userInteractionEnabled = NO; layer = <CALayer: 0x18df8240>>
| <UIImageView: 0x18df8330; frame = (0 0; 126 126); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x18df83b0>>
| <UILabel: 0x18df8460; frame = (0 135; 126 14); text = 'haha'; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x18df7fb0>>
| | <_UILabelContentLayer: 0x131a3d50> (layer)
| <UILabel: 0x18df8670; frame = (0 155; 126 12); text = 'hahaha'; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x18df8730>>
| | <_UILabelContentLayer: 0x131bea10> (layer)
| <UIImageView: 0x18df88d0; frame = (0 9; 28 27); hidden = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x18df8ba0>>
```
#####2.2 pvc
这个命令也是递归打印层级,但是不是view,而是viewController。利用它我们可以对viewController的结构一目了然。 其实苹果在IOS8也默默的添加了 UIViewController 的一个私有辅助方法 [UIViewController _printHierarchy] 同样的效果。
预览效果:
```
<UITabBarController 0x7fa789429fc0>, state: appeared, view: <UILayoutContainerView 0x7fa78961b760>
| <SYYellowViewController 0x7fa78942a610>, state: appeared, view: <UIView 0x7fa789404570>
| <SYRedViewController 0x7fa78942c960>, state: disappeared, view: (view not loaded)
```
是不是方便很多呢,而且还可以看到 viewController 是否已经 viewDidLoad .
#####2.3 visualize
这是个很有意思的功能,它可以让你使用Mac的预览打开一个 UIImage, CGImageRef, UIView, 或 CALayer。 这个功能或许可以帮我们用来截图、用来定位一个view的具体内容。 但是在我试用了一下,发现暂时还是只能在模拟器时使用,真机还不行。
使用简单:
```
(lldb) visualize imageView
```
#####2.4 fv & fvc
fv 和 fvc 这两个命令是用来通过类名搜索当前内存中存在的view和viewController实例的命令,支持正则搜索。
```
(lldb) fv scrollView
0x18d3b8c0 UIScrollView
0x137d0c50 UIScrollView
0x131b1580 UIScrollView
0x131b2070 UIScrollView
(lldb) fvc Home
0x1393fe00 HomeFeedsViewController
0x138a8e00 HomeFeedsViewController
(lldb)
```
#####2.5 show & hide
这两个命令用来显示和隐藏一个指定的 UIView . 你甚至不需要Continue Progress. 就可以看到效果。
#####2.6 caflush
这个命令会重新渲染,即可以重新绘制界面, 相当于执行了 [CATransaction flush] 方法,要注意如果在动画过程中执行这个命令,就直接渲染出动画结束的效果。
当你想在调试界面颜色、坐标之类的时候,可以直接在控制台修改属性,然后caflush就可以看到效果啦,是不是要比改代码,然后重新build省事多了呢。
例, 其中 $122 即是目标UIView:
```
(lldb) p view
(long) $122 = 140718754142192
(lldb) e (void)[$122 setBackgroundColor:[UIColor greenColor]]
(lldb) caflush
```
#一些po不正常的情况(传法推荐)
1. 在使用Xcode6beta时(以下全部简称x6b),发现设置断点,中断后直接跳到汇编视图,这根本不是一般人需要的哦!其实这个是可以设定的哦,在断点断下之后,菜单中依次选择Debug->Debug Workflow后,将总是显示汇编行勾去掉即可:
2.
```
(lldb) po date
error: Couldn't materialize: couldn't get the value of variable date: no location, value may have been optimized out
Errored out in Execute, couldn't PrepareToExecuteJITExpression
```
备注:本例子结合loginBottomButtonAction方法进行讲解.
来源:https://www.cnblogs.com/carrotlsp/p/4995209.html