zoukankan      html  css  js  c++  java
  • LLDB (转发)

    原文:LLDB详解

    LLDB的Xcode默认的调试器,它与LLVM编译器一起,带给我们更丰富的流程控制和数据检测的调试功能。平时用Xcode运行程序,实际走的都是LLDB。熟练使用LLDB,可以让你debug事半功倍

    LLDB基础知识

    LLDB控制台

    Xcode中内嵌了LLDB控制台,在Xcode中代码的下方,我们可以看到LLDB控制台。


    LLDB控制台平时会输出一些log信息。如果我们想输入命令调试,必须让程序进入暂停状态。让程序进入暂停状态的方式主要有2种:

      1. 断点或者watchpoint: 在代码中设置一个断点(watchpoint),当程序运行到断点位置的时候,会进入stop状态
      2. 直接暂停,控制台上方有一个暂停按钮,上图红框已标出,点击即可暂停程序

    LLDB语法

    在使用LLDB之前,我们来先看看LLDB的语法,了解语法可以帮助我们清晰的使用LLDB:

    <command> [<subcommand> [<subcommand>...]] <action> [-options [option-value]] [argument [argument...]]

    一眼看上去可能比较迷茫,给大家解释一下:

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

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

    breakpoint set -n main

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

    1. commandbreakpoint 表示断点命令
    2. actionset 表示设置断点
    3. option-n 表示根据方法name设置断点
    4. arguementmian 表示方法名为mian

    原始(raw)命令

    LLDB支持不带命令选项(options)的原始(raw)命令,原始命令会将命令后面的所有东西当做参数(arguement)传递。不过很多原始命令也可以带命令选项,当你使用命令选项的时候,需要在命令选项后面加--区分命令选项和参数。

    e.g: 常用的expression就是raw命令,一般情况下我们使用expression打印一个东西是这样的:

    (lldb) expression count
    (int) $2 = 4

    当我们想打印一个对象的时候。需要使用-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>
    1. <cmd-options>:命令选项,一般情况下使用默认的即可,不需要特别标明。
    2. --: 命令选项结束符,表示所有的命令选项已经设置完毕,如果没有命令选项,--可以省略
    3. <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 *) $1 = 0x00007fe322c18a10

    p & print & call

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

    1. print: 打印某个东西,可以是变量和表达式
    2. p: 可以看做是print的简写
    3. 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
    复制代码
    根据唯一匹配原则,如果你没有自己添加特殊的命令别名。e也可以表示expression的意思。原始命令默认没有命令选项,所以e也能带给你同样的效果

     

    po

    我们知道,OC里所有的对象都是用指针表示的,所以一般打印的时候,打印出来的是对象的指针,而不是对象本身。如果我们想打印对象。我们需要使用命令选项:-O。为了更方便的使用,LLDB为expression -O --定义了一个别名:po

    复制代码
    (lldb) expression -- self.view
    (UIView *) $13 = 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

    thread backtrace & bt

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

    复制代码
    (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
    复制代码

    我们可以看到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命令:

    1. ccontinuethread continue: 这三个命令效果都等同于上图中第一个按钮的。表示程序继续运行
    2. nnextthread step-over: 这三个命令效果等同于上图第二个按钮。表示单步运行
    3. sstepthread step-in: 这三个命令效果等同于上图第三个按钮。表示进入某个方法
    4. finishstep-out: 这两个命令效果等同于第四个按钮。表示直接走完当前方法,返回到上层frame

    thread其他不常用的命令

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

    1. thread jump: 直接让程序跳到某一行。由于ARC下编译器实际插入了不少retain,release命令。跳过一些代码不执行很可能会造成对象内存混乱发生crash。
    2. thread list: 列出所有的线程
    3. thread select: 选择某个线程
    4. thread until: 传入一个line的参数,让程序执行到这行的时候暂停
    5. thread info: 输出当前线程的信息

    frame

    前面我们提到过很多次frame(帧)。可能有的朋友对frame这个概念还不太了解。随便打个断点

    我们在控制台上输入命令bt,可以打印出来所有的frame。如果仔细观察,这些frame和左边红框里的堆栈是一致的。平时我们看到的左边的堆栈就是frame。

    rame variable

    平时Debug的时候我们经常做的事就是查看变量的值,通过frame variable命令,可以打印出当前frame的所有变量

    (lldb) frame variable
    (ViewController *) self = 0x00007fa158526e60
    (SEL) _cmd = "text:"
    (BOOL) ret = YES
    (int) a = 3

    可以看到,他将self,_cmd,ret,a等本地变量都打印了出来

    如果我们要需要打印指定变量,也可以给frame variable传入参数:

    (lldb) frame variable self->_string
    (NSString *) self->_string = nil

    不过frame variable只接受变量作为参数,不接受表达式,也就是说我们无法使用frame variable self.string,因为self.string是调用stringgetter方法。所以一般打印指定变量,我更喜欢用p或者po

    其他不常用命令

    一般frame variable打印所有变量用得比较多,frame还有2个不怎么常用的命令:

    frame info: 查看当前frame的信息

    (lldb) frame info
    frame #0: 0x0000000101bf87d5 TLLDB`-[ViewController text:](self=0x00007fa158526e60, _cmd="text:", ret=YES) + 37 at 

    frame select: 选择某个frame

    复制代码
    (lldb) frame select 1
    frame #1: 0x0000000101bf872e TLLDB`-[ViewController viewDidLoad](self=0x00007fa158526e60, _cmd="viewDidLoad") + 78 at ViewController.m:23
       20      
       21      - (void)viewDidLoad {
       22          [super viewDidLoad];
    -> 23          [self text:YES];
       24          NSLog(@"1");
       25          NSLog(@"2");
       26          NSLog(@"3");
    复制代码

    当我们选择frame 1的时候,他会把frame1的信息和代码打印出来。不过一般我都是直接在Xcode左边点击某个frame,这样更方便

    breakpoint

    调试过程中,我们用得最多的可能就是断点了。LLDB中的断点命令也非常强大

    breakpoint set

    breakpoint set命令用于设置断点,LLDB提供了很多种设置断点的方式:

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

    e.g: 我们想给所有类中的viewWillAppear:设置一个断点:

       (lldb) breakpoint set -n viewWillAppear:
        Breakpoint 13: 33 locations.

    使用-f指定文件

    e.g: 我们只需要给ViewController.m文件中的viewDidLoad设置断点:

     (lldb) breakpoint set -f ViewController.m -n viewDidLoad
        Breakpoint 22: where = TLLDB`-[ViewController viewDidLoad] + 20 at ViewController.m:22, address = 0x000000010272a6f4

    这里需要注意,如果方法未写在文件中(比如写在category文件中,或者父类文件中),指定文件之后,将无法给这个方法设置断点。

    使用-l指定文件某一行设置断点

    e.g: 我们想给ViewController.m第38行设置断点

    (lldb) breakpoint set -f ViewController.m -l 38
    Breakpoint 23: where = TLLDB`-[ViewController text:] + 37 at ViewController.m:38, address = 0x000000010272a7d5

    使用-c设置条件断点

    e.g: 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
    使用-o设置单次断点
    
    e.g: 如果刚刚那个断点我们只想让他中断一次:
    (lldb) breakpoint set -n text: -o
    'breakpoint 3': where = TLLDB`-[ViewController text:] + 30 at ViewController.m:37, address = 0x000000010b6f97ce

     

    breakpoint command

    有的时候我们可能需要给断点添加一些命令,比如每次走到这个断点的时候,我们都需要打印self对象。我们只需要给断点添加一个po self命令,就不用每次执行断点再自己输入po self

    breakpoint command add

    breakpoint command add命令就是给断点添加命令的命令。

    e.g: 假设我们需要在ViewControllerviewDidLoad中查看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

    (lldb) breakpoint command add -o "po self.view" 3

    -o完整写法是--one-liner,表示增加一条命令。3表示对id为3breakpoint增加命令。
    添加完命令之后,每次程序执行到这个断点就可以自动打印出self.view的值了

    如果我们一下子想增加多条命令,比如我想在viewDidLoad中打印当前frame的所有变量,但是我们不想让他中断,也就是在打印完成之后,需要继续执行。我们可以这样玩:

    (lldb) breakpoint command add 3
    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已有的命令

     实践记录:https://www.cnblogs.com/panpanwelcome/p/14263459.html

  • 相关阅读:
    May 1 2017 Week 18 Monday
    April 30 2017 Week 18 Sunday
    April 29 2017 Week 17 Saturday
    April 28 2017 Week 17 Friday
    April 27 2017 Week 17 Thursday
    April 26 2017 Week 17 Wednesday
    【2017-07-04】Qt信号与槽深入理解之一:信号与槽的连接方式
    April 25 2017 Week 17 Tuesday
    April 24 2017 Week 17 Monday
    为什么丑陋的UI界面却能创造良好的用户体验?
  • 原文地址:https://www.cnblogs.com/panpanwelcome/p/14263454.html
Copyright © 2011-2022 走看看