Xcode 命令和断点(LLDB调试命令技巧)
绪论
Xcode 中的调试技巧与我们的日常开发息息相关,而这些调试技巧在我们解决Bug时,常常有事半功倍的效果。
LLDB全称Low Level Debugger ,并不是低水平的调试器,而是轻量级的高性能调试器,默认内置于Xcode中。LLDBDebug在编译后就是一个Macho的可执行文件,也可以理解为镜像文件,image并不是图像的意思,而是代表镜像。这里跟上我们自己的工程名,即用image定位寻址才是寻找我们自己的代码。
image.png
在上图中,右侧黑色区域就是Log 输出区,在 Log 输出区可以使用一些命令,来辅助调试。接下来将讲解lldb常用命令及一些高级用法。
一: 常用的一般调试命令
//
1.p命令
p 命令是 print 命令的简写,查看基本数据类型的值,但是如果使用p命令查看的是对象,那么只会返回对象的指针地址。
p 命令后面除了可以接 变量、常量,还可以接 表达式。(❌但是不可以使用宏❌)
2.po 命令
po 命令可以理解为打印对象的description。可以打印 常量、变量,打印表达式返回的对象等。
(❌也不可以打印宏❌)
p 和po命令的演示
image.gif
p命令修改变量的演示
image.gif
3.expr 命令
使用e来给给具体的变量赋值
if(self.dataArray.count = 0)
{
NSLog(@“修改成功了!”);
};
//若想让self.dataArray.count = 0进入if判断句。那就要当断点到if的时候。
//在控制台输入e self.dataArray.count = 0回车,继续运行就可以。
4.cell 命令
Xcode 还支持动态调用函数。在控制台执行该命令,可以在不修改代码,不重新编译的情况下,修改界面上的视图。
这里有一个动态将cell 的某个子视图移除的范
5.image 命令
image list 命令可以列出当前App中的所有module(这个module 在后面符号断点时有用到),可以查看某一个地址
对应的代码位置。 除了 image list 还有 image add、image lookup等命令,可以自行查看。
当遇到crash 时,查看线程栈,只能看到栈帧的地址,使用 image lookup –address 地址可以方便
的定位到这个地址对应的代码行。
image lookup -a用于寻找栈地址对应的代码位置,演示如下:
image.gif
从上图中我们可以看到当程序崩溃时并不能定位到指定的代码位置,使用image寻址命令可以定位到具体的崩溃位置在viewDidLoad方法中的第51行。
image.png
//
6.bt命令
bt 命令 可以打印出线程的堆栈信息,该信息比左侧的Debug Navigator 看到的还要详细一些。
bt 命令是打印当前线程的堆栈信息。
bt All输出所有的信息。
7.x 命令
x命令是查看地址在内存的情况 ,一般查看对象地址和对象的属性地址。
x/4gx命令:打印4个16进制地址。x/4gx student
x/8gx命令:打印8个16进制地址。x/8gx student
例如:x student
比如:
x 0x600002bfd110 按回车 会出现下面的这些
0x600002bfd110: e8 06 62 0c 01 00 00 00 00 00 00 00 00 00 00 00 ..b...........
0x600002bfd120: 38 e0 61 0c 01 00 00 00 58 e0 61 0c 01 00 00 00 8.a.....x.a.......
使用bt 命令可以查看函数调用堆栈,然后用命令frame select即可查看对应函数详细,演示如下:
image.gif
上面函数执行的顺序如下:点击登录按钮--验证手机号--验证密码--开始登录。
- (IBAction)login:(UIButton *)sender {
[self validationPhone];
}
#pragma mark --验证手机号
-(void)validationPhone{
[self validationPwd];
}
#pragma mark --验证密码
-(void)validationPwd{
[self startLogin];
}
#pragma mark --开始登陆
-(void)startLogin{
NSLog(@"------开始登录...------");
}
从bt命令的打印信息中,我们可以很清楚看到函数调用顺序,如下图:
image.png
接下来我们执行frame select命令即可以查看函数相关信息,同时配合up和down命令追踪函数的调用和被调用关系,演示如下:
image.gif
同时可以frame variable使用很方便的查方法的调用者及方法名称,如下图:
image.png
breakpoint命令
b命令给函数下断点,演示如下图:
image.gif
当我们的断点下成功后,控制台会打印如下信息:
Breakpoint 1: where = LLDBDebug-[ViewController login:] at ViewController.m:53, address = 0x00000001034fb0a0
我们可以看到断点的位置在.m文件的53行,Breakpoint 1这里的1代表的是编号为1的组断点。
使用breakpoint list我们可以看到断点的数量,同时使用breakpoint delete后面跟上组号,即可删除,演示如下:
image.png
使用breakpoint的c,n,s以及fininsh的命令,对应演示如下:
image.png
我们执行c,n,s以及fininsh的命令,对应演示
image.gif
target stop-hook add -o "frame variable"每次进入断点都会自动打印详细的参数信息,演示如下:
image.gif
二: 特殊自定义断点
符号断点就是 Symbolic Breakpoint,其实是针对某一个特定函数的断点,可以是一个OC函数,也可以是C++函数。
添加的地方如下:
image.png
image.png
Symbol 栏可以填 [类名 方法名]或者 直接单个方法名,module也是选填项,它就是上面image命令中列出来的module。
例如 ,我们如果只填一个viewDidLoad,那么就会在所有类(包括第三方库)的viewDidLoad 处打断点。
符号断点在调试一些没有源码的模块时比较有用,比如调试一个第三方提供的Lib库,或者系统的模块,可以在相应函数处下断点,可以大概调试清楚程序的运行流程,也可以在断点的时候查看到参数信息。
三: LLDB高级用法
我们先来简单看下menthods和pviews命令的执行效果,演示如下图:
image.gif
menthods命令可以打印当前对象的属性和方法,如下所示:
(lldb) methods p1
<Person: 0x60000003eac0>:
in Person:
Properties:
@property (copy, nonatomic) NSString* name; (@synthesize name = _name;)
@property (nonatomic) long age; (@synthesize age = _age;)
Instance Methods:
- (void) eat; (0x1098bf3e0)
- (void) .cxx_destruct; (0x1098bf4f0)
- (id) description; (0x1098bf410)
- (id) name; (0x1098bf430)
- (void) setName:(id)arg1; (0x1098bf460)
- (void) setAge:(long)arg1; (0x1098bf4c0)
- (long) age; (0x1098bf4a0)
(NSObject ...)
pviews命令可以打印当前视图的层级结构,如下所示:
(lldb) pviews
<UIWindow: 0x7fd1719060a0; frame = (0 0; 414 736); gestureRecognizers = <NSArray: 0x60c000058660>; layer = <UIWindowLayer: 0x60c0000364c0>>
| <UIView: 0x7fd16fc06d10; frame = (0 0; 414 736); alpha = 0.8; autoresize = W+H; layer = <CALayer: 0x60000003e7e0>>
| | <UIButton: 0x7fd16fe0b520; frame = (54 316; 266 53); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x60400003b040>>
| | | <UIButtonLabel: 0x7fd16fe023f0; frame = (117.667 17.6667; 30.6667 18); text = '登录'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x60400008ac80>>
| | | | <_UILabelContentLayer: 0x600000220260> (layer)
| | <UILabel: 0x7fd16fc04a60; frame = (164 225; 80 47); text = 'Qinz'; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x600000088fc0>>
(lldb)
注释:如果你在原生的XCode中,是敲不出这些命令的,上面只是演示了两个常见的LLDB插件命令的用法,更加高级的用法下面会详细说明。不过在这之前,我们要安装两个插件,接下来先讲解环境的配置。
2.1 chisel是Facebook开源的一款LLDB插件,里面封装了很多好用的命令,当然这些命令都是基于苹果提供的api。chisel下载*
2.2 这里建议使用包管理工具Homebrew来安装,然后配置脚本路径,演示如下:
image.gif
2.3 然后在lldb窗口执行命令,演示如下:
image.gif
2.4 看到输出"command script import /usr/local/opt/chisel/libexec/fblldb.py"即代表安装成功,这里还会看到一个"command script import /opt/LLDB/lldb_commands/dslldb.py "路径,这是我们接下来要安装的第二个插件。
Executing commands in '/Users/Qinz/.lldbinit'.
command script import /usr/local/opt/chisel/libexec/fblldb.py
command script import /opt/LLDB/lldb_commands/dslldb.py
(lldb)
2.5 这个插件的名称也叫LLDB,LLDB下载。我们先clone文件,我这里放置在opt文件夹下,你可以选择自己的文件目录放置,然后依次找到dslldb文件,在~/.initlldb文件中配置路径,演示如下:
image.gif
2.6 接下来依然在lldb窗口执行command source ~/.lldbinit命令。到此LLDB插件的配置环境完成,接下来我们讲解这些插件的实用命令。
lldb高级用法
taplog搭配flicker,让你快速找准控件,演示如下:
image.gif
taplog是点击控件,会打印控件的地址,大小及透明度等信息,我们拿到地址后执行flicker 0x7fd321e09710命令,此时控件会进行闪烁,这里动态图显示的闪烁效果明显。
show和hide显示和隐藏控件,演示如下:
image.gif
vs命令方便动态查看控件的层级关系,演示如下:
image.gif
当我们执行vs命令后会进入动态调试阶段,会出现以下五个命令,每个命令我做了详细注释如下:
(lldb) vs 0x7fe73550a090
Use the following and (q) to quit.
(w) move to superview //移动到父视图
(s) move to first subview //移动到第一个子视图
(a) move to previous sibling //移动上一个兄弟视图
(d) move to next sibling //移动下一个兄弟视图
(p) print the hierarchy //打印视图层级结构
pachtions直接打印对象调用者及方法,演示如下:
image.gif
border&unborder直接打印对象调用者及方法,演示如下:
image.gif
这里的-c即是color,-w即设置边框的宽度。通过这个命令我们可以很方便的查看边框的边缘的问题,而不需要每次重启运行。
pclass打印对象的继承关系,演示如下图:
image.gif
presponder命令打印响应链,演示如下图:
image.gif
caflush这个命令会重新渲染,即可以重新绘制界面, 相当于执行了 [CATransaction flush] 方法,演示如下:
image.gif
search搜索已经存在于栈中的控件及其子控件,演示如下:
image.gif
lookup搜索,可执行正则表达式。演示如下:
image.gif
上面的搜索会搜索所用镜像模块,我们重点看与我们工程名字相同的模块,即可查看哪些地方调用了这些方法。
pbundlepath打印app路径及pdocspath打印文档路径,演示如下:
image.gif
总结:上面详细讲解了LLDB常用命令及高级命令的用法,熟练掌握可大幅度提高Debug能力和开发效率。
image.png
image.gif
image.gif
image.gif
image.png
image.gif
image.png
image.gif
image.png
image.gif
image.png
image.png
image.gif
image.gif
image.png
image.png
image.gif
image.gif
image.gif
image.gif
image.gif
image.gif
image.gif
image.gif
image.gif
image.gif
image.gif
image.gif
image.gif
image.gif
image.gif