Xcode调试之LLDB介绍

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

LLDB介绍

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


Xcode调试之LLDB介绍_第1张图片
Xcode 控制台

LLDB命令结构

LLDB命令的语法有其通用结构,通常是以下形式的:
[ [...]] [-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  )
      -S  ( --selector  )
      -b  ( --basename  )
      -f  ( --file  )
      -l  ( --line  )
     -n  ( --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 is in decimal and

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 …
```

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

(lldb) help breakpoint set
     Sets a breakpoint or set of breakpoints in the executable.
Syntax: breakpoint set 
Command Options Usage:
  breakpoint set [-Ho] -l  [-s ] [-i ] [-c ] [-x ] [-t ] [-T ] [-q ] [-f ] [-K ]
  breakpoint set [-Ho] -a  [-s ] [-i ] [-c ] [-x ] [-t ] [-T ] [-q ]
  breakpoint set [-Ho] -n  [-s ] [-i ] [-c ] [-x ] [-t ] [-T ] [-q ] [-f ] [-K ] [-L ]
  breakpoint set [-Ho] -F  [-s ] [-i ] [-c ] [-x ] [-t ] [-T ] [-q ] [-f ] [-K ]
 … and so forth …```

帮助系统能让我们快速地了解一个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/)

你可能感兴趣的:(Xcode调试之LLDB介绍)