LLDB是个开源的内置于XCode的具备REPL(read-eval-print-loop)特征的Debugger,其能够安装C++或者Python插件。在平常的开发和调试过程当中给开发人员带来了很是多的帮助。了解并熟练掌握LLDB的使用是很是有必要的。这篇文章将会带着你们一块儿了解在iOS开发中LLDB调试器的使用。express
LLDB的基本语法以下sass
<command> [<subcommand> [<subcommand>...]] <action> [-options [option-value]] [argument [argument...]]
举个例子,假设咱们给main方法设置一个断点,咱们使用下面的命令:
这个命令对应到上面的语法就是:markdown
1. command: breakpoint 表示断点命令
app
2. action: set 表示设置断点
3. option: -n 表示根据方法name设置断点
4. arguement: mian 表示方法名为mian
LLDB其中内置了很是多的功能,选择去硬背每一条指令并非一个明智的选择。咱们只须要记住一些经常使用的指令,在须要的时候经过help命令来查看相关的描述便可。dom
(lldb) help Debugger commands: apropos -- List debugger commands related to a word or subject. breakpoint -- Commands for operating on breakpoints (see 'help b' for shorthand.) bugreport -- Commands for creating domain-specific bug reports. command -- Commands for managing custom LLDB commands. disassemble -- Disassemble specified instructions in the current target. Defaults to the current function for the current thread and stack frame. expression -- Evaluate an expression on the current thread. Displays any returned value with LLDB's default formatting. frame -- Commands for selecting and examing the current thread's stack frames. gdb-remote -- Connect to a process via remote GDB server. If no host is specifed, localhost is assumed. gui -- Switch into the curses based GUI mode. help -- Show a list of all debugger commands, or give details about a specific command. ......
咱们要查看某一个命令改如何使用时,可使用 help <command> 来获取对应命令的使用方法。工具
(lldb) help expression Evaluate an expression on the current thread. Displays any returned value with LLDB's default formatting. Expects 'raw' input (see 'help raw-input'.) Syntax: expression <cmd-options> -- <expr> Command Options Usage: expression [-AFLORTgp] [-f <format>] [-G <gdb-format>] [-a <boolean>] [-i <boolean>] [-t <unsigned-integer>] [-u <boolean>] [-l <source-language>] [-X <boolean>] [-v[<description-verbosity>]] [-j <boolean>] [-d <none>] [-S <boolean>] [-D <count>] [-P <count>] [-Y[<count>]] [-V <boolean>] [-Z <count>] -- <expr> expression [-AFLORTgp] [-a <boolean>] [-i <boolean>] [-t <unsigned-integer>] [-u <boolean>] [-l <source-language>] [-X <boolean>] [-j <boolean>] [-d <none>] [-S <boolean>] [-D <count>] [-P <count>] [-Y[<count>]] [-V <boolean>] [-Z <count>] -- <expr> expression [-r] -- <expr> expression <expr> -A ( --show-all-children ) Ignore the upper bound on the number of children to show. -D <count> ( --depth <count> ) Set the max recurse depth when dumping aggregate types (default is infinity). 。。。。。。 Examples: expr my_struct->a = my_array[3] expr -f bin -- (index * 8) + 5 expr unsigned int $foo = 5 expr char c[] = \"foo\"; c[0] Important Note: Because this command takes 'raw' input, if you use any command options you must use ' -- ' between the end of the command options and the beginning of the raw input.
expression命令的做用是执行一个表达式,并将表达式返回的结果输出。expression的完整语法是这样的 :oop
expression <cmd-options> -- <expr> //<cmd-options>:命令选项,通常状况下使用默认的便可,不须要特别标明。 //--: 命令选项结束符,表示全部的命令选项已经设置完毕,若是没有命令选项,--能够省略 //<expr>: 要执行的表达式
说expression是LLDB里面最重要的命令都不为过。由于他能实现2个功能。ui
执行某个表达式。 咱们在代码运行过程当中,能够经过执行某个表达式来动态改变程序运行的轨迹。 假如咱们在运行过程当中,忽然想把self.view颜色改为红色,看看效果。咱们没必要写下代码,从新run,只需暂停程序,用expression改变颜色,再刷新一下界面,就能看到效果this
// 改变颜色 (lldb) expression -- self.view.backgroundColor = [UIColor redColor] // 刷新界面 (lldb) expression -- (void)[CATransaction flush]
将返回值输出。 也就是说咱们能够经过expression来打印东西。 假如咱们想打印self.viewlua
(lldb) expression self.view (UIView *) $0 = 0x00007f8ed7418480 (lldb) expression -- self.view (UIView *) $1 = 0x00007f8ed7418480
通常状况下,咱们直接用expression仍是用得比较少的,更多时候咱们用的是p、print、call。这三个命令其实都是 expression -- 的别名(--表示再也不接受命令选项,详情见前面原始(raw)命令这一节):
print: 打印某个东西,能够是变量和表达式
p: 能够看作是print的简写
p
打印的是当前对象的地址而po
则会调用对象的description方法,作法和NSLog是一致的call: 调用某个方法
表面上看起来他们可能有不同的地方,实际都是执行某个表达式(变量也当作表达式),将执行的结果输出到控制台上。因此你能够用p调用某个方法,也能够用call打印东西 e.g: 下面代码效果相同:
(lldb) expression -- self.view (UIView *) $5 = 0x00007fb2a40344a0 (lldb) p self.view (UIView *) $6 = 0x00007fb2a40344a0 (lldb) print self.view (UIView *) $7 = 0x00007fb2a40344a0 (lldb) call self.view (UIView *) $8 = 0x00007fb2a40344a0 (lldb) e self.view (UIView *) $9 = 0x00007fb2a40344a0
(lldb) expression -O -- self.view <UIView: 0x7fb2a40344a0; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7fb2a4018c80>> (lldb) po self.view <UIView: 0x7fb2a40344a0; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7fb2a4018c80>>
有时候咱们想要了解线程堆栈信息,可使用thread backtrace thread backtrace做用是将线程的堆栈打印出来。咱们来看看他的语法 :
thread backtrace [-c <count>] [-s <frame-index>] [-e <boolean>] /* * thread backtrace后面跟的都是命令选项,实际上这些命令选项咱们通常不须要使用。 -c:设置打印堆栈的帧数(frame) -s:设置从哪一个帧(frame)开始打印 -e:是否显示额外的回溯 */
e.g: 当发生crash的时候,咱们可使用thread backtrace查看堆栈调用。从下面的结果中,咱们能够看到crash发生在-[ViewController viewDidLoad]中的第23行,只需检查这行代码是否是干了什么非法的事儿就能够了。
(lldb) thread backtrace * thread #1: tid = 0xdd42, 0x000000010afb380b libobjc.A.dylib`objc_msgSend + 11, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=EXC_I386_GPFLT) frame #0: 0x000000010afb380b libobjc.A.dylib`objc_msgSend + 11 * frame #1: 0x000000010aa9f75e TLLDB`-[ViewController viewDidLoad](self=0x00007fa270e1f440, _cmd="viewDidLoad") + 174 at ViewController.m:23 frame #2: 0x000000010ba67f98 UIKit`-[UIViewController loadViewIfRequired] + 1198 frame #3: 0x000000010ba682e7 UIKit`-[UIViewController view] + 27 frame #4: 0x000000010b93eab0 UIKit`-[UIWindow addRootViewControllerViewIfPossible] + 61 frame #5: 0x000000010b93f199 UIKit`-[UIWindow _setHidden:forced:] + 282 frame #6: 0x000000010b950c2e UIKit`-[UIWindow makeKeyAndVisible] + 42
此外,LLDB还为backtrace专门定义了一个别名:bt,他的效果与thread backtrace相同,若是你不想写那么长一串字母,直接写下bt便可
Debug的时候,也许会由于各类缘由,咱们不想让代码执行某个方法,或者要直接返回一个想要的值。这时候就该thread return上场了。thread return能够接受一个表达式,调用命令以后直接从当前的frame返回表达式的值。
thread return [<expr>]
e.g: 咱们有一个someMethod方法,默认状况下是返回YES。咱们想要让他返回NO。咱们只需在方法的开始位置加一个断点,当程序中断的时候,输入命令便可,效果至关于在断点位置直接调用return NO;,不会执行断点后面的代码。
(lldb) thread return NO
thread 相关的还有其余一些不经常使用的命令,这里就简单介绍一下便可,若是须要了解更多,可使用命令help thread查阅
thread jump: 直接让程序跳到某一行。因为ARC下编译器实际插入了很多retain,release命令。跳过一些代码不执行极可能会形成对象内存混乱发生crash。
thread list: 列出全部的线程
thread select: 选择某个线程
thread until: 传入一个line的参数,让程序执行到这行的时候暂停
thread info: 输出当前线程的信息
通常在调试程序的时候,咱们常常用到下面这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
前面咱们提到过不少次frame(帧)。可能有的朋友对frame这个概念还不太了解。随便打个断点,咱们在控制台上输入命令bt,能够打印出来全部的frame。若是仔细观察,这些frame和左边红框里的堆栈是一致的。平时咱们看到的左边的堆栈就是frame。
(lldb) frame variable (ViewController *) self = 0x00007fa158526e60 (SEL) _cmd = "text:" (BOOL) ret = YES (int) a = 3
(lldb) frame variable self->_string
(NSString *) self->_string = nil
(lldb) frame info frame #0: 0x0000000101bf87d5 TLLDB`-[ViewController text:](self=0x00007fa158526e60, _cmd="text:", ret=YES) + 37 at ViewController.m:38
(lldb) frame select 1 frame #1: 0x0000000101bf872e TLLDB`-[ViewController viewDidLoad](self=0x00007fa158526e60, _cmd="viewDidLoad") + 78 at ViewController.m:23 - (void)viewDidLoad { [super viewDidLoad]; [self text:YES]; NSLog(@"1"); NSLog(@"2"); NSLog(@"3");
//咱们想给全部类中的viewWillAppear:设置一个断点 (lldb) breakpoint set -n viewWillAppear: Breakpoint 13: 33 locations.
// 咱们只须要给ViewController.m文件中的viewDidLoad设置断点 (lldb) breakpoint set -f ViewController.m -n viewDidLoad Breakpoint 22: where = TLLDB`-[ViewController viewDidLoad] + 20 at ViewController.m:22, address = 0x000000010272a6f4
//咱们想给ViewController.m第38行设置断点 (lldb) breakpoint set -f ViewController.m -l 38 Breakpoint 23: where = TLLDB`-[ViewController text:] + 37 at ViewController.m:38, address = 0x000000010272a7d5
//text:方法接受一个ret的参数,咱们想让ret == YES的时候程序中断 (lldb) breakpoint set -n text: -c ret == YES Breakpoint 7: where = TLLDB`-[ViewController text:] + 30 at ViewController.m:37, address = 0x0000000105ef37ce
//若是刚刚那个断点咱们只想让他中断一次 (lldb) breakpoint set -n text: -o 'breakpoint 3': where = TLLDB`-[ViewController text:] + 30 at ViewController.m:37, address = 0x000000010b6f97ce
//假设咱们须要在ViewController的viewDidLoad中查看self.view的值 咱们首先给-[ViewController viewDidLoad]添加一个断点 (lldb) breakpoint set -n "-[ViewController viewDidLoad]" 'breakpoint 3': where = TLLDB`-[ViewController viewDidLoad] + 20 at ViewController.m:23, address = 0x00000001055e6004 /* 能够看到添加成功以后,这个breakpoint的id为3,而后咱们给他增长一个命令:po self.view -o完整写法是--one-liner,表示增长一条命令。3表示对id为3的breakpoint增长命令。 添加完命令以后,每次程序执行到这个断点就能够自动打印出self.view的值了 */ (lldb) breakpoint command add -o "po self.view" 3 /* 若是咱们一会儿想增长多条命令,好比我想在viewDidLoad中打印当前frame的全部变量,可是咱们不想让他中断,也就是在打印完成以后,须要继续执行。咱们能够这样玩 输入breakpoint command add 3对断点3增长命令。他会让你输入增长哪些命令,输入’DONE’表示结束。这时候你就能够输入多条命令了 */ (lldb) breakpoint command add 3 Enter your debugger command(s). Type 'DONE' to end. > frame variable > continue > DONE
//咱们查看一下刚刚的断点3已有的命令 (lldb) breakpoint command list 3 'breakpoint 3': Breakpoint commands: frame variable continue
//删除断点4 (lldb) breakpoint delete 4 1 breakpoints deleted; 0 breakpoint locations disabled. //若是咱们想删除全部断点,只须要不指定breakpoint delete参数便可 (lldb) breakpoint delete About to delete all breakpoints, do you want to do that?: [Y/n] y All breakpoints removed. (1 breakpoint) //删除的时候他会提示你,是否是真的想删除全部断点,须要你再次输入Y确认。若是想直接删除,不须要他的提示,使用-f命令选项便可 (lldb) breakpoint delete -f All breakpoints removed. (1 breakpoint)
实际平时咱们真正使用breakpoint命令反而比较少,由于Xcode已经内置了断点工具。咱们能够直接在代码上打断点,能够在断点工具栏里面查看编辑断点,这比使用LLDB命令方便不少。不过了解LLDB相关命令可让咱们对断点理解更深入。 若是你想了解怎么使用Xcode设置断点,能够阅读这篇文章《Xcode中断点的威力》
breakpoint有一个孪生兄弟watchpoint。若是说breakpoint是对方法生效的断点,watchpoint就是对地址生效的断点。若是咱们想要知道某个属性何时被篡改了,咱们该怎么办呢?有人可能会说对setter方法打个断点不就好了么?可是若是更改的时候没调用setter方法呢? 这时候最好的办法就是用watchpoint。咱们能够用他观察这个属性的地址。若是地址里面的东西改变了,就让程序中断
(lldb) watchpoint set variable self->_string Watchpoint created: Watchpoint 1: addr = 0x7fcf3959c418 size = 8 state = enabled type = w watchpoint spec = 'self->_string' new value: 0x0000000000000000
watchpoint set expression:若是咱们想直接观察某个地址,可使用watchpoint set expression
//咱们先拿到_model的地址,而后对地址设置一个watchpoint (lldb) p &_model (Modek **) $3 = 0x00007fe0dbf23280 (lldb) watchpoint set expression 0x00007fe0dbf23280 Watchpoint created: Watchpoint 1: addr = 0x7fe0dbf23280 size = 8 state = enabled type = w new value: 0
watchpoint command add:和breakpoint同样,给watchpoint添加命令
//设置一个watchpoint (lldb) watchpoint set variable _string Watchpoint created: Watchpoint 1: addr = 0x7fe4e1444760 size = 8 state = enabled type = w watchpoint spec = '_string' new value: 0x0000000000000000 //能够看到这个watchpoint的id是1。咱们能够用watchpoint command add -o添加单条命令 watchpoint command add -o 'bt' 1 //咱们也能够一次添加多条命令 (lldb) watchpoint command add 1 Enter your debugger command(s). Type 'DONE' to end. > bt > continue > DONE
watchpoint command list:列出某个watchpoint全部的command
watchpoint command delete:删除某个watchpoint全部的command
watchpoint list:查看当前全部watchpoint
watchpoint disable/enable:使某个watchpoint失效/生效
watchpoint delete:删除watchpoint,删除单个或多个,用法同breakpoint delete