LLDB详解
LLDB的Xcode默认的调试器,它与LLVM编译器一起,带给我们更丰富的流程控制和数据检测的调试功能。平时用Xcode运行程序,实际走的都是LLDB。熟练使用LLDB,可以让你debug事半功倍
LLDB基础知识
LLDB控制台
Xcode中内嵌了LLDB控制台,在Xcode中代码的下方,我们可以看到LLDB控制台。

LLDB控制台平时会输出一些log信息。如果我们想输入命令调试,必须让程序进入暂停状态。让程序进入暂停状态的方式主要有2种:
- 断点或者watchpoint: 在代码中设置一个断点(watchpoint),当程序运行到断点位置的时候,会进入stop状态
- 直接暂停,控制台上方有一个暂停按钮,上图红框已标出,点击即可暂停程序
LLDB语法
在使用LLDB之前,我们来先看看LLDB的语法,了解语法可以帮助我们清晰的使用LLDB:
<command> [<subcommand> [<subcommand>...]] <action> [-options [option-value]] [argument [argument...]]
一眼看上去可能比较迷茫,给大家解释一下:
<command>(命令)和<subcommand>(子命令):LLDB调试命令的名称。命令和子命令按层级结构来排列:一个命令对象为跟随其的子命令对象创建一个上下文,子命令又为其子命令创建一个上下文,依此类推。<action>:执行命令的操作<options>:命令选项<arguement>:命令的参数[]:表示命令是可选的,可以有也可以没有
举个例子,假设我们给main方法设置一个断点,我们使用下面的命令:
breakpoint set -n main
这个命令对应到上面的语法就是:
command:breakpoint表示断点命令action:set表示设置断点option:-n表示根据方法name设置断点arguement:mian表示方法名为mian
原始(raw)命令
LLDB支持不带命令选项(options)的原始(raw)命令,原始命令会将命令后面的所有东西当做参数(arguement)传递。不过很多原始命令也可以带命令选项,当你使用命令选项的时候,需要在命令选项后面加--区分命令选项和参数。
e.g: 常用的expression就是raw命令,一般情况下我们使用expression打印一个东西是这样的:
(lldb) expression count
(int) $ =
当我们想打印一个对象的时候。需要使用-O命令选项,我们应该用--将命令选项和参数区分:
(lldb) expression -O -- self
<ViewController: 0x7f9000f17660>
唯一匹配原则
LLDB的命令遵循唯一匹配原则:假如根据前n个字母已经能唯一匹配到某个命令,则只写前n个字母等效于写下完整的命令。
e.g: 前面提到我设置断点的命令,我们可以使用唯一匹配原则简写,下面2条命令等效:
breakpoint set -n main
br s -n main
~/.lldbinit
LLDB有了一个启动时加载的文件~/.lldbinit,每次启动都会加载。所以一些初始化的事儿,我们可以写入~/.lldbinit中,比如给命令定义别名等。但是由于这时候程序还没有真正运行,也有部分操作无法在里面玩,比如设置断点。
LLDB命令
expression
expression命令的作用是执行一个表达式,并将表达式返回的结果输出。expression的完整语法是这样的:
expression <cmd-options> -- <expr>
<cmd-options>:命令选项,一般情况下使用默认的即可,不需要特别标明。--: 命令选项结束符,表示所有的命令选项已经设置完毕,如果没有命令选项,--可以省略<expr>: 要执行的表达式
说expression是LLDB里面最重要的命令都不为过。因为他能实现2个功能。
我们在代码运行过程中,可以通过执行某个表达式来动态改变程序运行的轨迹。
假如我们在运行过程中,突然想把self.view颜色改成红色,看看效果。我们不必写下代码,重新run,只需暂停程序,用expression改变颜色,再刷新一下界面,就能看到效果
// 改变颜色
(lldb) expression -- self.view.backgroundColor = [UIColor redColor]
// 刷新界面
(lldb) expression -- (void)[CATransaction flush]
也就是说我们可以通过expression来打印东西。
假如我们想打印self.view:
(lldb) expression -- self.view
(UIView *) $ = 0x00007fe322c18a10
p & print & call
一般情况下,我们直接用expression还是用得比较少的,更多时候我们用的是p、print、call。这三个命令其实都是expression --的别名(--表示不再接受命令选项,详情见前面原始(raw)命令这一节)
print: 打印某个东西,可以是变量和表达式p: 可以看做是print的简写call: 调用某个方法。
表面上看起来他们可能有不一样的地方,实际都是执行某个表达式(变量也当做表达式),将执行的结果输出到控制台上。所以你可以用p调用某个方法,也可以用call打印东西
e.g: 下面代码效果相同:
(lldb) expression -- self.view
(UIView *) $ = 0x00007fb2a40344a0
(lldb) p self.view
(UIView *) $ = 0x00007fb2a40344a0
(lldb) print self.view
(UIView *) $ = 0x00007fb2a40344a0
(lldb) call self.view
(UIView *) $ = 0x00007fb2a40344a0
(lldb) e self.view
(UIView *) $ = 0x00007fb2a40344a0
根据唯一匹配原则,如果你没有自己添加特殊的命令别名。e也可以表示expression的意思。原始命令默认没有命令选项,所以e也能带给你同样的效果
po
我们知道,OC里所有的对象都是用指针表示的,所以一般打印的时候,打印出来的是对象的指针,而不是对象本身。如果我们想打印对象。我们需要使用命令选项:-O。为了更方便的使用,LLDB为expression -O --定义了一个别名:po
(lldb) expression -- self.view
(UIView *) $ = 0x00007fb2a40344a0
(lldb) expression -O -- self.view
<UIView: 0x7fb2a40344a0; frame = ( ; ); autoresize = W+H; layer = <CALayer: 0x7fb2a4018c80>>
(lldb) po self.view
<UIView: 0x7fb2a40344a0; frame = ( ; ); autoresize = W+H; layer = <CALayer: 0x7fb2a4018c80>>
thread
thread backtrace & bt
有时候我们想要了解线程堆栈信息,可以使用thread backtracethread backtrace作用是将线程的堆栈打印出来。我们来看看他的语法
thread backtrace [-c <count>] [-s <frame-index>] [-e <boolean>]
thread backtrace后面跟的都是命令选项:
-c:设置打印堆栈的帧数(frame)-s:设置从哪个帧(frame)开始打印-e:是否显示额外的回溯
实际上这些命令选项我们一般不需要使用。
e.g: 当发生crash的时候,我们可以使用thread backtrace查看堆栈调用
(lldb) thread backtrace
* thread #: tid = 0xdd42, 0x000000010afb380b libobjc.A.dylib`objc_msgSend + , queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=EXC_I386_GPFLT)
frame #: 0x000000010afb380b libobjc.A.dylib`objc_msgSend +
* frame #: 0x000000010aa9f75e TLLDB`-[ViewController viewDidLoad](self=0x00007fa270e1f440, _cmd="viewDidLoad") + at ViewController.m:
frame #: 0x000000010ba67f98 UIKit`-[UIViewController loadViewIfRequired] +
frame #: 0x000000010ba682e7 UIKit`-[UIViewController view] +
frame #: 0x000000010b93eab0 UIKit`-[UIWindow addRootViewControllerViewIfPossible] +
frame #: 0x000000010b93f199 UIKit`-[UIWindow _setHidden:forced:] +
frame #: 0x000000010b950c2e UIKit`-[UIWindow makeKeyAndVisible] +
我们可以看到crash发生在-[ViewController viewDidLoad]中的第23行,只需检查这行代码是不是干了什么非法的事儿就可以了。
LLDB还为backtrace专门定义了一个别名:bt,他的效果与thread backtrace相同,如果你不想写那么长一串字母,直接写下bt即可
(lldb) bt
thread return
Debug的时候,也许会因为各种原因,我们不想让代码执行某个方法,或者要直接返回一个想要的值。这时候就该thread return上场了。
thread return可以接受一个表达式,调用命令之后直接从当前的frame返回表达式的值。
e.g: 我们有一个someMethod方法,默认情况下是返回YES。我们想要让他返回NO

我们只需在方法的开始位置加一个断点,当程序中断的时候,输入命令即可:
(lldb) thread return NO
效果相当于在断点位置直接调用return NO;,不会执行断点后面的代码
c & n & s & finish
一般在调试程序的时候,我们经常用到下面这4个按钮:

用触摸板的孩子们可能会觉得点击这4个按钮比较费劲。其实LLDB命令也可以完成上面的操作,而且如果不输入命令,直接按Enter键,LLDB会自动执行上次的命令。按一下Enter就能达到我们想要的效果,有木有顿时感觉逼格满满的!!!
我们来看看对应这4个按钮的LLDB命令:
c/continue/thread continue: 这三个命令效果都等同于上图中第一个按钮的。表示程序继续运行n/next/thread step-over: 这三个命令效果等同于上图第二个按钮。表示单步运行s/step/thread step-in: 这三个命令效果等同于上图第三个按钮。表示进入某个方法finish/step-out: 这两个命令效果等同于第四个按钮。表示直接走完当前方法,返回到上层frame
thread其他不常用的命令
thread 相关的还有其他一些不常用的命令,这里就简单介绍一下即可,如果需要了解更多,可以使用命令help thread查阅
thread jump: 直接让程序跳到某一行。由于ARC下编译器实际插入了不少retain,release命令。跳过一些代码不执行很可能会造成对象内存混乱发生crash。thread list: 列出所有的线程thread select: 选择某个线程thread until: 传入一个line的参数,让程序执行到这行的时候暂停thread info: 输出当前线程的信息
frame
前面我们提到过很多次frame(帧)。可能有的朋友对frame这个概念还不太了解。随便打个断点

我们在控制台上输入命令bt,可以打印出来所有的frame。如果仔细观察,这些frame和左边红框里的堆栈是一致的。平时我们看到的左边的堆栈就是frame。
frame variable
平时Debug的时候我们经常做的事就是查看变量的值,通过frame variable命令,可以打印出当前frame的所有变量
(lldb) frame variable
(ViewController *) self = 0x00007fa158526e60
(SEL) _cmd = "text:"
(BOOL) ret = YES
(int) a =
可以看到,他将self,_cmd,ret,a等本地变量都打印了出来
如果我们要需要打印指定变量,也可以给frame variable传入参数:
(lldb) frame variable self->_string
(NSString *) self->_string = nil
不过frame variable只接受变量作为参数,不接受表达式,也就是说我们无法使用frame variable self.string,因为self.string是调用string的getter方法。所以一般打印指定变量,我更喜欢用p或者po。
其他不常用命令
一般frame variable打印所有变量用得比较多,frame还有2个不怎么常用的命令:
frame info: 查看当前frame的信息
(lldb) frame info
frame #: 0x0000000101bf87d5 TLLDB`-[ViewController text:](self=0x00007fa158526e60, _cmd="text:", ret=YES) + at
frame select: 选择某个frame
(lldb) frame select
frame #: 0x0000000101bf872e TLLDB`-[ViewController viewDidLoad](self=0x00007fa158526e60, _cmd="viewDidLoad") + at ViewController.m: - (void)viewDidLoad {
[super viewDidLoad];
-> [self text:YES];
NSLog(@"");
NSLog(@"");
NSLog(@"");
当我们选择frame 1的时候,他会把frame1的信息和代码打印出来。不过一般我都是直接在Xcode左边点击某个frame,这样更方便
breakpoint
调试过程中,我们用得最多的可能就是断点了。LLDB中的断点命令也非常强大
breakpoint set
breakpoint set命令用于设置断点,LLDB提供了很多种设置断点的方式:
使用-n根据方法名设置断点:
e.g: 我们想给所有类中的viewWillAppear:设置一个断点:
(lldb) breakpoint set -n viewWillAppear:
Breakpoint : locations.
使用-f指定文件
e.g: 我们只需要给ViewController.m文件中的viewDidLoad设置断点:
(lldb) breakpoint set -f ViewController.m -n viewDidLoad
Breakpoint : where = TLLDB`-[ViewController viewDidLoad] + at ViewController.m:, address = 0x000000010272a6f4
这里需要注意,如果方法未写在文件中(比如写在category文件中,或者父类文件中),指定文件之后,将无法给这个方法设置断点。
使用-l指定文件某一行设置断点
e.g: 我们想给ViewController.m第38行设置断点
(lldb) breakpoint set -f ViewController.m -l
Breakpoint : where = TLLDB`-[ViewController text:] + at ViewController.m:, address = 0x000000010272a7d5
使用-c设置条件断点
e.g: text:方法接受一个ret的参数,我们想让ret == YES的时候程序中断:
(lldb) breakpoint set -n text: -c ret == YES
Breakpoint : where = TLLDB`-[ViewController text:] + at ViewController.m:, address = 0x0000000105ef37ce
使用-o设置单次断点 e.g: 如果刚刚那个断点我们只想让他中断一次:
(lldb) breakpoint set -n text: -o
'breakpoint 3': where = TLLDB`-[ViewController text:] + at ViewController.m:, address = 0x000000010b6f97ce
breakpoint command
有的时候我们可能需要给断点添加一些命令,比如每次走到这个断点的时候,我们都需要打印self对象。我们只需要给断点添加一个po self命令,就不用每次执行断点再自己输入po self了
breakpoint command add
breakpoint command add命令就是给断点添加命令的命令。
e.g: 假设我们需要在ViewController的viewDidLoad中查看self.view的值
我们首先给-[ViewController viewDidLoad]添加一个断点
(lldb) breakpoint set -n "-[ViewController viewDidLoad]"
'breakpoint 3': where = TLLDB`-[ViewController viewDidLoad] + at ViewController.m:, address = 0x00000001055e6004
可以看到添加成功之后,这个breakpoint的id为3,然后我们给他增加一个命令:po self.view
(lldb) breakpoint command add -o "po self.view"
-o完整写法是--one-liner,表示增加一条命令。3表示对id为3的breakpoint增加命令。
添加完命令之后,每次程序执行到这个断点就可以自动打印出self.view的值了
如果我们一下子想增加多条命令,比如我想在viewDidLoad中打印当前frame的所有变量,但是我们不想让他中断,也就是在打印完成之后,需要继续执行。我们可以这样玩:
(lldb) breakpoint command add
Enter your debugger command(s). Type 'DONE' to end.
> frame variable
> continue
> DONE
输入breakpoint command add 3对断点3增加命令。他会让你输入增加哪些命令,输入'DONE'表示结束。这时候你就可以输入多条命令了
breakpoint command list
如果想查看某个断点已有的命令,可以使用breakpoint command list。
e.g: 我们查看一下刚刚的断点3已有的命令
LLDB详解的更多相关文章
- iOS开发——开发技巧&LLDB详解
开胃小菜--简单的断点调试 在xcode中打开一个app,在想要break的行号上单击,即可生成一个深色的箭头标识--断点.如下图,在viewDidLoad:中设置了断点. 运行app,等待...就可 ...
- iOS开发——测试篇&breakpoints、lldb 和 chisel 的详解
breakpoints.lldb 和 chisel 的详解 Breakpoints BreakPoint分类 breakpoint也是有分类的,我这里的文章内大致按使用的方式分为了 Normal Br ...
- 《招一个靠谱的移动开发》iOS面试题及详解(下篇)
iOS面试知识点 现在进入本篇的正题.本篇的面试题是我认为比较好的iOS开发基础知识点,希望大家看过这后在理解的基础上掌握而不是死记硬背.死记硬背很快也会忘记的. 1 iOS基础 1.1 父类实现深拷 ...
- Linq之旅:Linq入门详解(Linq to Objects)
示例代码下载:Linq之旅:Linq入门详解(Linq to Objects) 本博文详细介绍 .NET 3.5 中引入的重要功能:Language Integrated Query(LINQ,语言集 ...
- 架构设计:远程调用服务架构设计及zookeeper技术详解(下篇)
一.下篇开头的废话 终于开写下篇了,这也是我写远程调用框架的第三篇文章,前两篇都被博客园作为[编辑推荐]的文章,很兴奋哦,嘿嘿~~~~,本人是个很臭美的人,一定得要截图为证: 今天是2014年的第一天 ...
- EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用详解
前言 我比较喜欢安静,大概和我喜欢研究和琢磨技术原因相关吧,刚好到了元旦节,这几天可以好好学习下EF Core,同时在项目当中用到EF Core,借此机会给予比较深入的理解,这里我们只讲解和EF 6. ...
- Java 字符串格式化详解
Java 字符串格式化详解 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 文中如有纰漏,欢迎大家留言指出. 在 Java 的 String 类中,可以使用 format() 方法 ...
- Android Notification 详解(一)——基本操作
Android Notification 详解(一)--基本操作 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/Notification 文中如有纰 ...
- Android Notification 详解——基本操作
Android Notification 详解 版权声明:本文为博主原创文章,未经博主允许不得转载. 前几天项目中有用到 Android 通知相关的内容,索性把 Android Notificatio ...
随机推荐
- [Xcode 实际操作]五、使用表格-(8)自定义UITableView单元格Accessory样式(附件图标)
目录:[Swift]Xcode实际操作 本文将演示如何自定义单元格的附件图标. 在项目导航区,打开视图控制器的代码文件[ViewController.swift] import UIKit //首先添 ...
- appium服务——封装生成可用端口
一.判断端口是否可用 1.在windows中判断端口是否可用,使用dos命令"netstat -ano| findstr 8080".运行结果有如下两种 如果没有被占用,就是结果为 ...
- C - Distinct Substrings (模板)
https://vjudge.net/problem/SPOJ-DISUBSTR 有两种方式来求去除重读的子串 #include <bits/stdc++.h> using namespa ...
- 小技巧(updating)
小技巧 我们要算一个点集中所有点到另一个点集中所有点的一些量的时候,可以建立一个超级源点和超级汇点,从多->多变成单->单 整体二分的时候,操作要可以撤销,才能保证复杂度,每一层到左边区间 ...
- Netty(1-1)Discard
一.DiscardServerHandler import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext ...
- 常见的HTTP状态码说明
1.说明 HTTP服务器状态代码定义(Status Code Definitions) 做测试的时候,会产生比较多的HTTP错误,查看其错误,有超时的,链接不到图片的,连接不到服务器等等,很多人经常忘 ...
- NET Core 事件总线
NET Core 事件总线,分布式事务解决方案:CAP 背景 相信前面几篇关于微服务的文章也介绍了那么多了,在构建微服务的过程中确实需要这么一个东西,即便不是在构建微服务,那么在构建分布式应用的过程中 ...
- Ubuntu搭建WordPress-MySQL-Apache
目标 技术博客www.xifarm.com有5年时间了. 原来在虚拟机/VPS上搭建,不过都是Windows系统下的. 最近突发奇想,试试迁移到Linux的Unbuntu下.说干就干,抽空用了大约3天 ...
- AJPFX辨析continue与break的区别
1.break : (1).结束当前整个循环,执行当前循环下边的语句.忽略循环体中任何其它语句和循环条件测试.(2).只能跳出一层循环,如果你的循环是嵌套循环,那么你需要按照你嵌套的层次,逐步使用br ...
- 1169 传纸条 2008年NOIP全国联赛提高组 个人博客:attack.cf
1169 传纸条 2008年NOIP全国联赛提高组 时间限制: 1 s 空间限制: 128000 KB 题目等级 : 钻石 Diamond 题目描述 Description 小渊和小轩 ...