Xcode调试之LLDB介绍

一直以来在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/)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • [转]浅谈LLDB调试器文章来源于:http://www.cocoachina.com/ios/20150126/...
    loveobjc阅读 2,410评论 2 6
  • 随着Xcode 5的发布,LLDB调试器已经取代了GDB,成为了Xcode工程中默认的调试器。它与LLVM编译器一...
    随风飘荡的小逗逼阅读 1,344评论 0 0
  • LLDB的Xcode默认的调试器,它与LLVM编译器一起,带给我们更丰富的流程控制和数据检测的调试功能。平时用Xc...
    CoderSC阅读 1,304评论 0 2
  • Xcode 上的lldb LLDB 调试工具,gdb替代品;LLVM : Low Level Virtual Ma...
    helinyu阅读 858评论 0 2
  • iOS调试之LLDB Xcode内嵌了LLDB控制台,在Xcode代码编辑区的下方。shift + cmd + y...
    comst阅读 1,424评论 0 3