美文网首页代码人生
Xcode调试之LLDB介绍

Xcode调试之LLDB介绍

作者: 木夜溯 | 来源:发表于2016-01-19 23:11 被阅读200次

    一直以来在console中使用最基本的print与po命令来调试程序,通过这两个命令可以应付大多数的情况下的调试需求,但是有时候需要知道更多地信息就需要lldb的更多命令。

    LLDB介绍

    随着Xcode 5的发布,LLDB调试器已经取代了GDB,成为了Xcode工程中默认的调试器。它与LLVM编译器一起,带给我们更丰富的流程控制和数据检测的调试功能。LLDB为Xcode提供了底层调试环境,其中包括内嵌在Xcode IDE中的位于调试区域的控制面板,在这里我们可以直接调用LLDB命令。


    Xcode 控制台Xcode 控制台

    LLDB命令结构

    LLDB命令的语法有其通用结构,通常是以下形式的:
    <command> [<subcommand> [<subcommand>...]] <action> [-options [option-value]] [argument [argument...]]

    其中:
    (command)和(subcommand):LLDB调试命令的名称。命令和子命令按层级结构来排列:一个命令对象为跟随其的子命令对象创建一个上下文,子命令又为其子命令创建一个上下文,依此类推。
    (action):我们想在前面的命令序列的上下文中执行的一些操作。
    options:行为修改器(action modifiers)。通常带有一些值。
    argument:根据使用的命令的上下文来表示各种不同的东西。

    LLBD命令行的解析操作在执行命令之前完成。上面的这些元素之间通过空格来分割,如果某一元素自身含有空格,则可以使用双引用。而如果元素中又包含双引号,则可以使用反斜杠;或者元素使用单引号。如下所示:
    (lldb) command [subcommand] -option "some \"quoted\" string"
    也可以表示为:
    (lldb) command [subcommand] -option 'some "quoted" string'

    这种命令解析设计规范了LLDB命令语法,并对所有命令做了个统一。
    例如一个简单的LLDB命令,设置文件test.c的断点在第12行输入

    (lldb) breakpoint set --file test.c --line 12

    命令选项

    LLDB中的命令选项有规范形式和缩写形式两种格式。以设置断点的命令breakpoint set为例,以下列表了其部分选项的格式,其中括号中的是规范形式:

    breakpoint set
          -M <method> ( --method <method> )
          -S <selector> ( --selector <selector> )
          -b <function-name> ( --basename <function-name> )
          -f <filename> ( --file <filename> )
          -l <linenum> ( --line <linenum> )
         -n <function-name> ( --name <function-name> )
          …
    

    各选项的顺序是任意的。如果后面的参数是以”–“开头的,则在选项后面添加”—“作为选项的终止信号,以告诉LLDB我们处理的选项的正确位置。如下命令所示:
    (lldb) process launch --stop-at-entry -- -program_arg_1 value -program_arg_2 value
    如上所示,命令的选项是—stop-at-entry,参数是-program_arg_1和-program_arg_2,我们使用”—“将选项与参数作一下区分。

    原始命令

    LLDB命令解析器支持”原始(raw)“命令,即没有命令选项,命令字符串的剩余部分未经解析就传递给命令。例如,expression就是一个原始命令。
    不过原始命令也可以有选项,如果命令字符串中有虚线,则在命令名与命令字符串之间放置一个选项结束符(—)来表明没有命令标记。
    我们可以通过help命令的输出来查看一个命令是否是原始命令。

    命令补全(Command Completion)

    LLDB支持源文件名,符号名,文件名,等等的命令补全(Commmand Completion)。终端窗口中的补全是通过在命令行中输入一个制表符来初始化的。Xcode控制台中的补全与在源码编辑器中的补全方式是一样的:补全会在第三个字符被键入时自动弹出,或者通过Esc键手动弹出。
    一个命令中的私有选项可以有不同的完成者(completers)。如breakpoint中的—file 选项作为源文件的完成者,—shlib 选项作为当前加载的库的完成者,等等。这些行为是特定的,例如,如果指定—shlib ,且以—file 结尾,则LLDB只会列出由—shlib 指定的共享类库。

    Python脚本

    对于高级用户来说,LLDB有一个内置的Python解析器,可以通过脚本命令来访问。调试器中的所有特性在Python解析器中都可以作为类来访问。这样,我们就可以使用LLDB-Python库来写Python函数,并通过脚本将其加载到运行会话中,以执行一些更复杂的调试操作。

    在命令行中调试程序

    通常我们都是在Xcode中直接使用LLDB调试器,Xcode会帮我们完成很多操作。当然,如果我们想让自己看着更Bigger,或者想了解下调试器具体的一些流程,就可以试试直接在终端使用LLDB命令来调试程序。在终端中使用LLDB调试器,我们需要了解以下内容:

    1.加载程序以备调试
    2.将一个运行的程序绑定到LLDB
    3.设置断点和观察点
    4.控制程序的执行
    5.在调试的程序中导航
    6.检查状态和值的变量
    7.执行替代代码
    

    了解在终端中这些操作是如何进行的,可以帮助我们更深入的了解调试器在Xcode中是如何运作的。

    LLDB常用命令

    expr
     可以在调试时动态执行指定表达式,并将结果打印出来。常用于在调试过程中修改变量的值。
     在断点的控制台中使用:expr var=value 来对表达式做动态改变
     这个命令也可以新声明一个变量对象,
    例如: expr int $b=2    p $b
    
    call
    call即是调用的意思。一般只在不需要显示输出,或是方法无返回值时使用call。在ViewController里设置断点,然后在程序中断的时候输入下面的命令:
    
    call  [self.view setBackgroundColor:[UIColor redColor]]
    
    继续运行程序,看看view的背景颜色是不是变成红色的了!在调试的时候灵活运用call命令可以起到事半功倍的作用。
    
    bt
    打印调用堆栈,加all可打印所有thread的堆栈。
    
    image
    image 命令可用于寻址,有多个组合命令。比较实用的用法是用于寻找栈地址对应的代码位置。
    

    我们还可以用它来查找可执行文件或共享库的原始地址,这一点还是很有用的,当我们的程序崩溃时,我们可以使用这条命令来查找崩溃所在的具体位置,如下所示:

    NSArray *array = @[@1, @2];
    NSLog(@"item 3: %@", array[2]);
    

    代码在运行后会抛出如下异常:

     test[18122:76474] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 2 beyond bounds [0 .. 1]'
    *** First throw call stack:
    (
        0   CoreFoundation                      0x00007fff8e06f66c __exceptionPreprocess + 172
        1   libobjc.A.dylib                     0x00007fff886ad76e objc_exception_throw + 43
        2   CoreFoundation                      0x00007fff8df487de -[__NSArrayI objectAtIndex:] + 190
        3   test                                0x0000000100000de0 main + 384
        4   libdyld.dylib                       0x00007fff8f1b65c9 start + 1
    )
    libc++abi.dylib: terminating with uncaught exception of type NSException
    

    根据以上信息,我们可以判断崩溃位置是在main.m文件中,要想知道具体在哪一行,可以使用以下命令:

    (lldb) image lookup --address 0x0000000100000de0
          Address: test[0x0000000100000de0] (test.__TEXT.__text + 384)
          Summary: test`main + 384 at main.m:23```
    image命令还有许多其它功能,使用help来查看其他用法。
    
    ##### print命令
    简写形式:prin / pri / p。但是不能用pr表示,因为会和process混淆。
    实际上,如果在console中输入“help print”,就会得到*’print’ is an abbreviation for ‘expression --‘* 这句话,也就是说print实际上就相当于 expression -- 。这里就很容易理解下面命令是如何工作的,p _lastPoiID=20,这行命令表达式会被执行。
    打印变量的值可以使用print命令,该命令如果打印的是简单类型,则会列出简单类型的类型和值。如果是对象,还会打印出对象指针地址,如下所示:
    

    (lldb) print a
    (NSInteger) $0 = 0
    (lldb) print b
    (NSInteger) $1 = 0
    (lldb) print str
    (NSString *) $2 = 0x0000000100001048 @"abc"
    (lldb) print url
    (NSURL *) $3 = 0x0000000100206cc0 @"abc"

    在输出结果中我们还能看到类似于$0,$1这样的符号,我们可以将其看作是指向对象的一个引用,我们在控制面板中可以直接使用这个符号来操作对应的对象,这些东西存在于LLDB的全名空间中,目的是为了辅助调试。
    
    上面的print命令会打印出对象的很多信息,如果我们只想查看对象的值的信息,则可以使用po(print object的缩写)命令,如下所示:
    

    (lldb) po str
    abc

    当然,po命令是”exp -O —“命令的别名,使用”exp -O —”能达到同样的效果。
    
    ###简称和别名
    很多时候,*LLDB*完整的命令是很长的。比如前面所说的image lookup --address
    这个组合命令。为了方便日常的使用,提高效率,*LLDB*命令也提供通过简称的方式调用命令。还是这个命令,我们用简称就可以写为im loo -a,是不是简单多了。
    如果你是从gdb时代就开始使用调试器的,你会发现,有些命令如p、call等命令和*gdb*下是一致的。其实这些命令是*LLDB*一些命令的别名,比如p是frame variable的别名,p view实际上是frame variable view。除了系统自建的*LLDB*别名,你也可以自定义别名。比如下面这个命令
        
        command alias ioa image lookup --address %1
    
    将前面所介绍过的一个命令image lookup --address添加了一个ioa
    的别名。然后执行下面的命令:
    

    (lldb) ioa 0x0000000100004af8
    Address: ControlStyleDemo[0x0000000100004af8] (ControlStyleDemo.__TEXT.__text + 13288)
    Summary: ControlStyleDemo`-[RootViewController viewDidLoad] + 312 at RootViewController.m:53

    #####帮助系统
    LLDB帮助系统让我们可以了解LLDB提供了哪些功能,并可以查看LLDB命令结构的详细信息。熟悉帮助系统可以让我们访问帮助系统中中命令文档。
    
    我们可以简单地调用help命令来列出LLDB所有的顶层命令。如下所示:
    

    (lldb) help
    The following is a list of built-in, permanent debugger commands:
    _regexp-attach -- Attach to a process id if in decimal, otherwise treat the
    argument as a process name to attach to.
    _regexp-break -- Set a breakpoint using a regular expression to specify the
    location, where <linenum> is in decimal and <address> is
    in hex.
    _regexp-bt -- Show a backtrace. An optional argument is accepted; if
    that argument is a number, it specifies the number of
    frames to display. If that argument is 'all', full
    backtraces of all threads are displayed.
    … and so forth …</address></linenum>```

    如果help后面跟着某个特定的命令,则会列出该命令相关的所有信息,我们以breakpoint set为例,输出信息如下:

    (lldb) help breakpoint set
         Sets a breakpoint or set of breakpoints in the executable.
    Syntax: breakpoint set <cmd-options>
    Command Options Usage:
      breakpoint set [-Ho] -l <linenum> [-s <shlib-name>] [-i <count>] [-c <expr>] [-x <thread-index>] [-t <thread-id>] [-T <thread-name>] [-q <queue-name>] [-f <filename>] [-K <boolean>]
      breakpoint set [-Ho] -a <address-expression> [-s <shlib-name>] [-i <count>] [-c <expr>] [-x <thread-index>] [-t <thread-id>] [-T <thread-name>] [-q <queue-name>]
      breakpoint set [-Ho] -n <function-name> [-s <shlib-name>] [-i <count>] [-c <expr>] [-x <thread-index>] [-t <thread-id>] [-T <thread-name>] [-q <queue-name>] [-f <filename>] [-K <boolean>] [-L <language>]
      breakpoint set [-Ho] -F <fullname> [-s <shlib-name>] [-i <count>] [-c <expr>] [-x <thread-index>] [-t <thread-id>] [-T <thread-name>] [-q <queue-name>] [-f <filename>] [-K <boolean>]
     … and so forth …</boolean></filename></queue-name></thread-name></thread-id></thread-index></expr></count></shlib-name></fullname></language></boolean></filename></queue-name></thread-name></thread-id></thread-index></expr></count></shlib-name></function-name></queue-name></thread-name></thread-id></thread-index></expr></count></shlib-name></address-expression></boolean></filename></queue-name></thread-name></thread-id></thread-index></expr></count></shlib-name></linenum></cmd-options>```
    
    帮助系统能让我们快速地了解一个LLDB命令的使用方法。经常使用它,可以让我们更快地熟悉LLDB的各项功能。
    
    参考:
    [About LLDB and Xcode](https://developer.apple.com/library/ios/documentation/IDEs/Conceptual/gdb_to_lldb_transition_guide/document/Introduction.html)
    [浅谈LLDB调试器](http://www.cocoachina.com/ios/20150126/11021.html)
    [LLDB调试命令初探](http://www.starfelix.com/blog/2014/03/17/lldbdiao-shi-ming-ling-chu-tan/)

    相关文章

      网友评论

      • 李国安:大神, 问你个问题, 使用 image lookup --address 0x0000000100000de0 指令, 我依然没看出来崩溃在哪一行啊 -.-!

      本文标题:Xcode调试之LLDB介绍

      本文链接:https://www.haomeiwen.com/subject/unyhkttx.html