一、前言

  LLDB是个开源的内置于XCode的具有REPL(read-eval-print-loop)特征的Debugger,其可以安装C++或者Python插件。在日常的开发和调试过程中给开发人员带来了非常多的帮助。了解并熟练掌握LLDB的使用是非常有必要的。这篇文章将会带着大家一起了解在iOS开发中LLDB调试器的使用。

二、LLDB基础

2.1 LLDB基本语法

  LLDB的基本语法如下

<command> [<subcommand> [<subcommand>...]] <action> [-options [option-value]] [argument [argument...]]
  • <command>(命令)和<subcommand>(子命令):LLDB调试命令的名称。命令和子命令按层级结构来排列:一个命令对象为跟随其的子命令对象创建一个上下文,子命令又为其子命令创建一个上下文,依此类推。
  • <action>:执行命令的操作
  • <options>:命令选项
  • <arguement>:命令的参数
  • []:表示命令是可选的,可以有也可以没有

  举个例子,假设我们给main方法设置一个断点,我们使用下面的命令:

  这个命令对应到上面的语法就是:

1. command: breakpoint 表示断点命令 
2. action: set 表示设置断点 
3. option: -n 表示根据方法name设置断点 
4. arguement: mian 表示方法名为mian
  

2.2 LLDB的基本使用

2.2.1 Help命令  

  LLDB其中内置了非常多的功能,选择去硬背每一条指令并不是一个明智的选择。我们只需要记住一些常用的指令,在需要的时候通过help命令来查看相关的描述即可。

(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[]
expr -f bin -- (index * ) +
expr unsigned int $foo =
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.

2.2.2 expression命令

  expression命令的作用是执行一个表达式,并将表达式返回的结果输出。expression的完整语法是这样的 :

expression <cmd-options> -- <expr>

//<cmd-options>:命令选项,一般情况下使用默认的即可,不需要特别标明。
//--: 命令选项结束符,表示所有的命令选项已经设置完毕,如果没有命令选项,--可以省略
//<expr>: 要执行的表达式

  说expression是LLDB里面最重要的命令都不为过。因为他能实现2个功能。

  1. 执行某个表达式。 我们在代码运行过程中,可以通过执行某个表达式来动态改变程序运行的轨迹。 假如我们在运行过程中,突然想把self.view颜色改成红色,看看效果。我们不必写下代码,重新run,只需暂停程序,用expression改变颜色,再刷新一下界面,就能看到效果

    // 改变颜色
    (lldb) expression -- self.view.backgroundColor = [UIColor redColor]
    // 刷新界面
    (lldb) expression -- (void)[CATransaction flush]
  2. 将返回值输出。 也就是说我们可以通过expression来打印东西。 假如我们想打印self.view

    (lldb) expression self.view
    (UIView *) $ = 0x00007f8ed7418480
    (lldb) expression -- self.view
    (UIView *) $ = 0x00007f8ed7418480

2.2.3 p & print & call & po 命令

  一般情况下,我们直接用expression还是用得比较少的,更多时候我们用的是p、print、call。这三个命令其实都是 expression -- 的别名(--表示不再接受命令选项,详情见前面原始(raw)命令这一节):

  • print: 打印某个东西,可以是变量和表达式

  • p: 可以看做是print的简写

  • po:OC里所有的对象都是用指针表示的,所以一般打印的时候,打印出来的是对象的指针,而不是对象本身。如果我们想打印对象。我们需要使用命令选项:-O。为了更方便的使用,LLDB为expression -O –定义了一个别名:po。p打印的是当前对象的地址而po则会调用对象的description方法,做法和NSLog是一致的
  • 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
(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>>

2.2.4 thread backtrace命令

  有时候我们想要了解线程堆栈信息,可以使用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 #: 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] +

此外,LLDB还为backtrace专门定义了一个别名:bt,他的效果与thread backtrace相同,如果你不想写那么长一串字母,直接写下bt即可

2.2.5 thread return命令

  Debug的时候,也许会因为各种原因,我们不想让代码执行某个方法,或者要直接返回一个想要的值。这时候就该thread return上场了。thread return可以接受一个表达式,调用命令之后直接从当前的frame返回表达式的值。

thread return [<expr>]

  e.g: 我们有一个someMethod方法,默认情况下是返回YES。我们想要让他返回NO。我们只需在方法的开始位置加一个断点,当程序中断的时候,输入命令即可,效果相当于在断点位置直接调用return NO;,不会执行断点后面的代码。

(lldb) thread return NO

2.2.6 thread其他不常用的命令

  thread 相关的还有其他一些不常用的命令,这里就简单介绍一下即可,如果需要了解更多,可以使用命令help thread查阅

  • thread jump: 直接让程序跳到某一行。由于ARC下编译器实际插入了不少retain,release命令。跳过一些代码不执行很可能会造成对象内存混乱发生crash。

  • thread list: 列出所有的线程

  • thread select: 选择某个线程

  • thread until: 传入一个line的参数,让程序执行到这行的时候暂停

  • thread info: 输出当前线程的信息

2.2.7 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

2.2.8 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 =
  • frame variable 参数:如果我们要需要打印指定变量,也可以给frame variable传入参数。不过frame variable只接受变量作为参数,不接受表达式,也就是说我们无法使用frame variable self.string,因为self.string是调用string的getter方法。所以一般打印指定变量,我更喜欢用p或者po。
    (lldb) frame variable self->_string
    (NSString *) self->_string = nil
  • frame info: 查看当前frame的信息
    (lldb) frame info
    frame #: 0x0000000101bf87d5 TLLDB`-[ViewController text:](self=0x00007fa158526e60, _cmd="text:", ret=YES) + at ViewController.m:
  • frame select: 选择某个frame,当我们选择frame 1的时候,他会把frame1的信息和代码打印出来。不过一般我都是直接在Xcode左边点击某个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(@"");

2.2.9 breakpoint命令

  • breakpoint set:设置断点,LLDB提供了很多种设置断点的方式

    • 使用-n根据方法名设置断点

      //我们想给所有类中的viewWillAppear:设置一个断点
      (lldb) breakpoint set -n viewWillAppear:
      Breakpoint : locations.
    • 使用-f指定文件
      // 我们只需要给ViewController.m文件中的viewDidLoad设置断点
      (lldb) breakpoint set -f ViewController.m -n viewDidLoad
      Breakpoint : where = TLLDB`-[ViewController viewDidLoad] + at ViewController.m:, address = 0x000000010272a6f4
    • 使用-l指定文件某一行设置断点
      //我们想给ViewController.m第38行设置断点
      (lldb) breakpoint set -f ViewController.m -l
      Breakpoint : where = TLLDB`-[ViewController text:] + at ViewController.m:, address = 0x000000010272a7d5
    • 使用-c设置条件断点
      //text:方法接受一个ret的参数,我们想让ret == YES的时候程序中断
      (lldb) breakpoint set -n text: -c ret == YES
      Breakpoint : where = TLLDB`-[ViewController text:] + at ViewController.m:, address = 0x0000000105ef37ce
    • 使用-o设置单次断点
      //如果刚刚那个断点我们只想让他中断一次
      (lldb) breakpoint set -n text: -o
      'breakpoint 3': where = TLLDB`-[ViewController text:] + at ViewController.m:, address = 0x000000010b6f97ce 
  • breakpoint command add:就是给断点添加命令的命令。多次对同一个断点添加命令,后面命令会将前面命令覆盖
     //假设我们需要在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
    -o完整写法是--one-liner,表示增加一条命令。3表示对id为3的breakpoint增加命令。 添加完命令之后,每次程序执行到这个断点就可以自动打印出self.view的值了
    */
    (lldb) breakpoint command add -o "po self.view" /*
    如果我们一下子想增加多条命令,比如我想在viewDidLoad中打印当前frame的所有变量,但是我们不想让他中断,也就是在打印完成之后,需要继续执行。我们可以这样玩
    输入breakpoint command add 3对断点3增加命令。他会让你输入增加哪些命令,输入’DONE’表示结束。这时候你就可以输入多条命令了
    */
    (lldb) breakpoint command add
    Enter your debugger command(s). Type 'DONE' to end.
    > frame variable
    > continue
    > DONE
  • breakpoint command list:查看某个断点已有的命令
    //我们查看一下刚刚的断点3已有的命令
    (lldb) breakpoint command list
    'breakpoint 3':
    Breakpoint commands:
    frame variable
    continue
  • breakpoint command delete:有增加就有删除,breakpoint command delete可以让我们删除某个断点的命令
  • breakpoint list:查看已经设置了哪些断点
  • breakpoint disable/enable:有的时候我们可能暂时不想要某个断点,可以使用breakpoint disable让某个断点暂时失效,使用breakpoint enable再次让他生效。
  • breakpoint delete:如果我们觉得这个断点以后再也用不上了,可以用breakpoint delete直接删除断点
    //删除断点4
    (lldb) breakpoint delete
    breakpoints deleted; 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. ( breakpoint) //删除的时候他会提示你,是不是真的想删除所有断点,需要你再次输入Y确认。如果想直接删除,不需要他的提示,使用-f命令选项即可
    (lldb) breakpoint delete -f
    All breakpoints removed. ( breakpoint)

  实际平时我们真正使用breakpoint命令反而比较少,因为Xcode已经内置了断点工具。我们可以直接在代码上打断点,可以在断点工具栏里面查看编辑断点,这比使用LLDB命令方便很多。不过了解LLDB相关命令可以让我们对断点理解更深刻。 如果你想了解怎么使用Xcode设置断点,可以阅读这篇文章《Xcode中断点的威力》

2.2.10 watchpoint命令

  breakpoint有一个孪生兄弟watchpoint。如果说breakpoint是对方法生效的断点,watchpoint就是对地址生效的断点。如果我们想要知道某个属性什么时候被篡改了,我们该怎么办呢?有人可能会说对setter方法打个断点不就行了么?但是如果更改的时候没调用setter方法呢? 这时候最好的办法就是用watchpoint。我们可以用他观察这个属性的地址。如果地址里面的东西改变了,就让程序中断

  • watchpoint set:用于添加一个watchpoint。只要这个地址中的内容变化了,程序就会中断。
  • watchpoint set variable:一般情况下,要观察变量或者属性,使用watchpoint set variable命令即可。watchpoint set variable传入的是变量名。需要注意的是,这里不接受方法,所以不能使用watchpoint set variable self.string,因为self.string调用的是string的getter方法
    (lldb) watchpoint set variable self->_string
    Watchpoint created: Watchpoint : addr = 0x7fcf3959c418 size = state = enabled type = w
    watchpoint spec = 'self->_string'
    new value: 0x0000000000000000
  • watchpoint set expression:如果我们想直接观察某个地址,可以使用watchpoint set expression

    //我们先拿到_model的地址,然后对地址设置一个watchpoint
    (lldb) p &_model
    (Modek **) $ = 0x00007fe0dbf23280
    (lldb) watchpoint set expression 0x00007fe0dbf23280
    Watchpoint created: Watchpoint : addr = 0x7fe0dbf23280 size = state = enabled type = w
    new value:
  • watchpoint command add:和breakpoint一样给watchpoint添加命令

    //设置一个watchpoint
    (lldb) watchpoint set variable _string
    Watchpoint created: Watchpoint : addr = 0x7fe4e1444760 size = state = enabled type = w
    watchpoint spec = '_string'
    new value: 0x0000000000000000 //可以看到这个watchpoint的id是1。我们可以用watchpoint command add -o添加单条命令
    watchpoint command add -o 'bt' //我们也可以一次添加多条命令
    (lldb) watchpoint command add
    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

iOS——调试工具LLDB学习的更多相关文章

  1. iOS调试-LLDB学习总结

    from:http://www.jianshu.com/p/d6a0a5e39b0e LLDB阐述 LLDB 是一个有着 REPL 的特性和 C++ ,Python 插件的开源调试器.LLDB 绑定在 ...

  2. iOS开发如何学习前端(2)

    iOS开发如何学习前端(2) 上一篇成果如下. 实现的效果如下. 实现了一个横放的<ul>,也既iOS中的UITableView. 实现了当鼠标移动到列表中的某一个<li>,也 ...

  3. iOS开发如何学习前端(1)

    iOS开发如何学习前端(1) 我为何学前端?因为无聊. 概念 前端大概三大块. HTML CSS JavaScript 基本上每个概念在iOS中都有对应的.HTML请想象成只能拉Autolayout或 ...

  4. 移动开发iOS&Android对比学习--异步处理

    在移动开发里很多时候需要用到异步处理.Android的主线程如果等待超过一定时间的时候直接出现ANR(对不熟悉Android的朋友这里需要解释一下什么叫ANR.ANR就是Application Not ...

  5. 关于iOS开发的学习

    关于iOS开发的学习,打个比方就像把汽车分解:    最底层的原料有塑料,钢铁    再用这些底层的东西造出来发动机,座椅    最后再加上写螺丝,胶水等,把汽车就拼起来了 iOS基本都是英文的资料, ...

  6. iOS 调试工具

    仪表  xcode5 引入了调试仪表,通过仪表可以直观的看出应用的CPU和内存占用量.运行一个程序,点击仪表栏.可以发现当程序处于运行状态时,调试导航面板会以柱状图显示CPU和内存占用量,并随着应用实 ...

  7. iOS核心动画学习整理

    最近利用业余时间终于把iOS核心动画高级技巧(https://zsisme.gitbooks.io/ios-/content/chapter1/the-layer-tree.html)看完,对应其中一 ...

  8. iOS CoreData技术学习资源汇总

    一.CoreData学习指引 1. 苹果官方:Core Data Programming Guide 什么是CoreData? 创建托管对象模型 初始化Core Data堆栈 提取对象 创建和修改自定 ...

  9. IOS内存管理学习笔记

    内存管理作为iOS中非常重要的部分,每一个iOS开发者都应该深入了解iOS内存管理,最近在学习iOS中整理出了一些知识点,先从MRC开始说起. 1.当一个对象在创建之后它的引用计数器为1,当调用这个对 ...

随机推荐

  1. xss攻击与防御

    一.XSS攻击 Cross Site Scripting跨站脚本攻击 利用js和DOM攻击. 盗用cookie,获取敏感信息 破坏正常页面结构,插入恶意内容(广告..) 劫持前端逻辑 DDos攻击效果 ...

  2. JMeter3.0启动日志报错WARN - org.jmeterplugins.repository.Plugin: Unable to load class解决方法

    解决方法: 通过sh find-in-jars 'HlsSampler' -d /data/apache-jmeter-3.0/lib/ext/确定这个class文件在哪个jar包 由于find-in ...

  3. UOJ#206. 【APIO2016】Gap 构造 交互题

    原文链接www.cnblogs.com/zhouzhendong/p/UOJ206.html 题解 T = 1 的情况直接大力从两边向中间询问即可. T = 2 的情况挺妙的,我没想到. 考虑首先花费 ...

  4. 修改 salt-minion 的 ID 后报错解决方法

    当在搭建 Saltstack 集中化管理平台配置完毕时,启动服务时,不知道是否你也越到过如下报错的现象呢? 报错问题如下: [root@saltstack_web1group_1 ~]# vim /e ...

  5. 动态DP之全局平衡二叉树

    目录 前置知识 全局平衡二叉树 大致介绍 建图过程 修改过程 询问过程 时间复杂度的证明 板题 前置知识 在学习如何使用全局平衡二叉树之前,你首先要知道如何使用树链剖分解决动态DP问题.这里仅做一个简 ...

  6. Dancing Links 学习笔记

    Dancing Links 本周的AI引论作业布置了一道数独 加了奇怪剪枝仍然TLE的Candy?不得不去学了dlx dlxnb! Exact cover 设全集X,X的若干子集的集合为S.精确覆盖是 ...

  7. Python 3.5 filter

    filter(F, L) F: 函数.L:范围 filter的功能是:用函数F把L范围内的参数做过滤 通常和list一起使用,把过滤后的参数做成列表 list(filter(lambda n:not ...

  8. Python爬虫开发与项目实战

    Python爬虫开发与项目实战(高清版)PDF 百度网盘 链接:https://pan.baidu.com/s/1MFexF6S4No_FtC5U2GCKqQ 提取码:gtz1 复制这段内容后打开百度 ...

  9. tomcat安装启动startup.bat文件命令行界面出现乱码的问题解决

    进入tomcat安装界面,进入conf文件夹,找打logging.properties,打开进行编辑,在最后添加一句 java.util.logging.ConsoleHandler.encoding ...

  10. 201771010118 马昕璐 《面向对象程序设计(java)》第十三周学习总结

    第一部分:理论知识学习部分 事件处理基础 1.事件源(event source):能够产生事件的对象都可以成为事件源.一个事件源是一个能够注册监听器并向监听器发送事件对象的对象. 2.事件监听器(ev ...