LLDB调试器简介
对于LLDB调试相信很多开发者都不陌生但是也仅仅停留在下断点看数据的阶段,使用最多的命令也就是po甚至包括我在内的一部分开发者也就会使用这一条命令,因为在平时的开发中这已经能够满足我们的使用要求了,有一次在听唐巧大神的分享会的时候当时大神在LLDB里面进行各种操作简直是亮瞎了我的双眼这不才在晚上搜索了一下这个东西才发现原来这里暗藏玄机,其实网上的帖子都已经讲的很清晰了可是为什么自己还要自己写一遍呢?说白了就是想着自己再加深一遍印象以后再回来看的时候能快速的找到地方。
下面来切入正题:
1.LLDB简介
LLDB 是一个有着 REPL 的特性和 C++ ,Python 插件的开源调试器。LLDB 绑定在 Xcode 内部,存在于主窗口底部的控制台中。调试器允许你在程序运行的特定时暂停它,你可以查看变量的值,执行自定的指令,并且按照你所认为合适的步骤来操作程序的进展。
你以前有可能已经使用过调试器,即使只是在 Xcode 的界面上加一些断点。但是通过一些小的技巧,你就可以做一些非常酷的事情。GDB to LLDB 参考是一个非常好的调试器可用命令的总览。你也可以安装 Chisel,它是一个开源的 LLDB 插件合辑,这会使调试变得更加有趣。
来自 与调试器共舞 - LLDB 的华尔兹
2. 简单命令的讲解
先来一段代码片段,下面的讲解大都是基于此
片段- help
见文识意,可以通过输入help
命令来了解全部的lldb的指令也可以输入help <command>
来获取某一个特定指令的详细注解 - print 简写为 p
print命令
细心的人可能会注意到在输出结果中有$0
,任何以美元符开头的东西都是存在于 LLDB 的命名空间的,试试print $0 + 7
,你会看到106
,实际上$0
是指向这个结果的 - po
po指令
相对print
指令po
指令仅仅是打印变量的description
方法 - breakpoint
a 给某个文件的某一行下断点breakpoint set --file ViewController.m --line 20
或者简写为breakpoint set -f ViewControlloer.m -l 20
断点成功后的提示:
设置某一行的断点
b 给某个函数下断点breakpoint set -n -[ViewController testFunction]
c 给指定的动态库中指定的方法添加断点breakpoint set -s libate.dylib -n freePartitionTables2D
d 查看断点的列表breakpoint list
e 删除断点breakpoint delete
- watchPoint - 观测某个变量值的具体变化
比如我们要观测变量i
的变化watchpoint set variable i
,只要被检测的变量值发生改变即会被检测到。成功检测后的结果如下:
成功检测某个变量后的结果
也可以在这里检测:
甚至可以设置变量i
触发的条件:watchpoint modify -c '(i=40)'
当i
的值变化到40时触发
我们可以看一下具体断点的参数,使用watchpoint list
命令
此时可以看到观察的变量的地址、声明变量的代码所在的行数、当前变量的值以及触发所需要的条件 - image
该命令用于寻址,假如程序由于某个原因崩溃掉了然而崩溃并没有给你定位到具体的信息而是直接怵在了main
函数里边,此时image
指令将极大的帮助你。使用指令:image lookup --address
,在这里笔者举个简单的数组越界的定位例子:
不要问我崩溃地址从哪看这个这个应该能找到吧😑 ,行数都已经显示出来了还有什么解决不了的呢 - thread
athread list
查看当前进程状态,前面带有 * 号的那条线程代表的就是当前线程
bthread backtrace
作用是打印出当前线程的堆栈信息。当程序发生了crash后,我们可以用该命令打印出发生crash的当前的程序堆栈,查询出发生crash的调用路径。由于比较常用,所以LLDB直接给它一个特殊的bt
别名。
cthread
另一个比较常用的用法是thread return
,调试的时候,我们希望在当前执行的程序堆栈直接返回一个自己想要的值,可以执行该命令直接返回。thread return
带有一个可选的返回值。 - frame
frame
即是帧,其实就是当前的程序堆栈,我们使用bt
打印出来的是当前线程的frame
,而我们关心的是当前堆栈的变量值,我们可以使用frame variable
获取全部变量值。也可以通过frame variable self-> count
来获取某个特定的变量的值。 - expression - (e)
个人感觉这条命令比较强大,因为什么呢?马上就说。因为它能改变一个变量在调试器以及程序当中的值,而当程序继续执行时会使用改变过后的值。
命令的使用方法:expression count = 5
,从此以后变量count
的值就变成了5
当然了有人会有这样的疑问:我打个断点直接利用xcode就能打上何必要用这么复杂难记得命令行呢?其实在实际开发中除非遇到难以解决的问题会用到lldb的命令行以外一般确实用不到,就比如说要为.a
文件里的某个方法里打断点,此时命令行可能就是你的救星了。然而xcode其实已经在自己的GUI界面里面为我们实现了一部分的命令只是在平时开发中并没有注意到接下来就为了我方便记忆的原则来简单写一下。
3.XcodeGUI界面介绍
-
先来一发图
相信大家看着这张图并不陌生,但是呢可能会对后面的两个按钮并没有那么的关注,只要你将鼠标放到这四个按钮上其实就明白了但是还有在这里简单说一下,从左到右一次为:continue,step over,step into,step out
- 第一个,
continue
按钮,会取消程序的暂停,允许程序正常执行 (要么一直执行下去,要么到达下一个断点)。在 LLDB 中,你可以使用process continue
命令来达到同样的效果,它的别名为continue
,或者也可以缩写为c
。 - 第二个,
step over
按钮,会以黑盒的方式执行一行代码。如果所在这行代码是一个函数调用,那么就不会跳进这个函数,而是会执行这个函数,然后继续。LLDB 则可以使用thread step-over
,next
,或者n
命令。 - 如果你确实想跳进一个函数调用来调试或者检查程序的执行情况,那就用第三个按钮,
step in
,或者在LLDB中使用thread step in
,step
,或者s
命令。注意,当前行不是函数调用时,next
和step
效果是一样的。 - 大多数人知道
c
,n
和s
,但是其实还有第四个按钮,step out
。如果你曾经不小心跳进一个函数,但实际上你想跳过它,常见的反应是重复的运行n
直到函数返回。其实这种情况,step out
按钮是你的救世主。它会继续执行到下一个返回语句 (直到一个堆栈帧结束) 然后再次停止。
- 再来一发图
相信大家对于上图中第二个并不陌生,那么在这里也就不叙述了,现在说一下第三个选项:
Symbolic Breakpoint
你可以在Symbol选项里面添加例如
-[NSArray objectAtIndex:]
这样的符号断点,这样当这个方法被调用的时候程序就会停止。当然了当程序停止时你也可以自定义一些信息比如打印一些信息或者念一段语音之类的。。。
同样的对于如下面普通断点其实也有一些比较吸引人的功能:
condition里面可以设置触发此断点的条件,ignore选项可以设置前n次运行不触发该断点,Action同样的可以设置触发断点后的一些个操作。
此时你可能会发现下面有这么一个选择框:
Automatically continue after evaluation actions.
,选中它,调试器会运行你所有的命令,然后继续运行。看起来就像没有执行任何断点一样。
4.举个例子
1.解决NSLog(@"%@",string);
繁琐的打印log
小伙伴们在平时的开发中有没有为了想看某个变量的值而NSLog(@"%@",string);
偶尔一个还好可是项目大了每每运行起来的时候下面就无数个log信息,自己看的也心里烦而且在release版本下还会消耗一本分的性能,所以在调试的时候可以选择为为某个变量添加特定的条件以及Action来观测该变量值的变化。
2.解决跳过一个函数调用来简化程序的行为
if (1 || condition) {
}
对于上面简化判断的方式是可取的然而智者千虑必有一失,在进行判断时如果使用expression
命令将条件改为YES
或者在复杂的判断中修改为任何你想要的值,无疑是极为方便的
-(void)testFunction {
return;
//已经存在的一些代码
}
比如已经运行起来的程序突然想跳过某个方法,那么就必须像上面那样写然后再重新编译,对于大工程来说编译时间无疑是个漫长的等待,so你或许可以机制的利用thread return
来直接返回
5.更新UI
有时候为了调试一个简单的布局而来回修改编译,那么漫长的编译时间无疑会浪费掉你宝贵的开发时间,在xcode8以前似乎有这么一个插件可以适时将修改反应在界面上,可惜xcode8以后就没用过了,那么在这里通过LLDB调试器也可以更加炫酷的实现这一功能。
前面说过在LLDB中的变量都要在变量名前加上 $
1.假如我们要修改一个view的颜色,那么可以通过po
命令来获取到你想要你的view的信息(假设给view开辟的内存地址为:0x7f82b2601fd0)
首先获取这个view
(lldb) e id $myView = (id)0x7f82b2601fd0
在调试器中改变这个view的颜色
(lldb) e (void)[$myView setBackgroundColor:[UIColor redColor]]
最后刷新UI即可
(lldb) e (void)[CATransaction flush]
此时你就可以看到界面上view的颜色被修改为了红色,因为即使你仍然在调试器中,渲染服务实际上是一个另外的进程 (被称作 backboardd)。这就是说即使我们正在调试的内容所在的进程被打断了,backboardd 也还是继续运行着的。
2.push一个ViewController
想象一个以 UINavigationController
为 root ViewController
的应用。你可以通过下面的命令,轻松地获取它:
(lldb) e id $nvc = [[[UIApplication sharedApplication] keyWindow] rootViewController]
然后 push 一个 child view controller或者是已经存在的特定的view controller
(lldb) e id $vc = [UIViewController new]
(lldb) e (void)[[$vc view] setBackgroundColor:[UIColor yellowColor]]
(lldb) e (void)[$vc setTitle:@"Yay!"]
(lldb) e (void)[$nvc pushViewContoller:$vc animated:YES]
最后运行下面的命令:
(lldb) e (void)[CATransaction flush]
navigation Controller 就会立刻就被 push 到你眼前。
写了这么多除了让我自己能忘记的时候回来看看同时也希望对有所需要的小伙伴有所帮助!