什么是LLDB
LLDB是XCode内置的为我们开发者提供的调试工具,它与LLVM编译器一起,存在于主窗口底部的控制台中,能够带给我们更丰富的流程控制和数据检测的调试功能。在调试过程中熟练使用LLDB,可以让你debug事半功倍。
LLDB可以带来以下体验:
- 允许你在程序运行的特定时暂停它
- 查看变量的值
- 执行自定的指令
- 按照你所认为合适的步骤来操作程序的进展
常用基本命令介绍
- expr
- 可以在调试时动态执行指定表达式,并将结果打印出来。常用于在调试过程中修改变量的值。
第一次正常打印, 使用expr 修改之后,在运行就已经是修改后的值了.
-
bt
- 打印调用堆栈,加all可打印所有thread的堆栈 , 调试多线程的时候很有用
设置观察点
作为断点的补充,LLDB支持观察点以在不中断程序运行的情况下监测一些变量。例如,我们可以使用以下命令来监测名为global的变量的写操作,并在(global==5)为真时停止监测:
(lldb) watch set var global
Watchpoint created: Watchpoint 1: addr = 0x100001018 size = 4 state = enabled type = w
declare @ '/Volumes/data/lldb/svn/ToT/test/functionalities/watchpoint/watchpoint_commands/condition/main.cpp:12'
(lldb) watch modify -c '(global==5)'
(lldb) watch list
Current watchpoints:
Watchpoint 1: addr = 0x100001018 size = 4 state = enabled type = w
declare @ '/Volumes/data/lldb/svn/ToT/test/functionalities/watchpoint/watchpoint_commands/condition/main.cpp:12'
condition = '(global==5)'
检查帧参数和本地变量的最简便的方式是使用frame variable命令:
(lldb) frame variable
self = (SKTGraphicView *) 0x0000000100208b40
_cmd = (struct objc_selector *) 0x000000010001bae1
sender = (id) 0x00000001001264e0
selection = (NSArray *) 0x00000001001264e0
i = (NSUInteger) 0x00000001001264e0
c = (NSUInteger) 0x00000001001253b0
(lldb) frame variable self
(SKTGraphicView *) self = 0x0000000100208b40
如果没有指定任何变量名,则会显示所有参数和本地变量。如果指定参数名或变量名,则只打印指定的值。如:
frame variable命令不是一个完全的表达式解析器,但它支持一些简单的操作符,如&,*,–>,[]。这个数组括号可用于指针,以将指针作为数组处理。如下所示:
(lldb) frame variable *self
(SKTGraphicView *) self = 0x0000000100208b40
(NSView) NSView = {
(NSResponder) NSResponder = {
...
(lldb) frame variable &self
(SKTGraphicView **) &self = 0x0000000100304ab
(lldb) frame variable argv[0]
(char const *) argv[0] = 0x00007fff5fbffaf8 "/Projects/Sketch/build/Debug/Sketch.app/Contents/MacOS/Sketch"
如果想查看另外一帧,可以使用frame select命令,如下所示:frame variable命令会在变量上执行”对象打印”操作。目前,LLDB只支持Objective-C打印,使用的是对象的description方法。
(lldb) frame select 9
frame #9: 0x0000000100015ae3, where = Sketch`function1 + 33 at /Projects/Sketch/SKTFunctions.m:11
在Xcode中调试程序
打印
打印变量的值可以使用print命令,print命令的简化方式有prin pri p,幸运的是p被lldb实现为特指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的全名空间中,目的是为了辅助调试。直接使用$1就当一个对象使用,但是后面的方式是不会提示的,得硬敲上去.对于隐藏比较深的对象,使用$0,$1这样调试就好多了
另外$后面的数值是递增的,每打印一个与对象相关的命令,这个值都会加1。
上面的print命令会打印出对象的很多信息,如果我们只想查看对象的值的信息,则可以使用po(print object的缩写)命令,如下所示:
当然,po命令是”exp -O —“命令的别名,使用”exp -O —”能达到同样的效果。
对于简单类型,我们还可以为其指定不同的打印格式,其命令格式是print/,如下所示:
1 2 | (lldb) p/x a (NSInteger) $13 = 0x0000000000000064 |
expression
在开发中,我们经常会遇到这样一种情况:我们设置一个视图的背景颜色,运行后发现颜色不好看。嗯,好吧,在代码里面修改一下,再编译运行一下,嗯,还是不好看,然后再修改吧~~这样无形中浪费了我们大把的时间。在这种情况下,expression命令强大的功能就能体现出来了,它不仅会改变调试器中的值,还改变了程序中的实际值。我们先来看看实际效果,如下所示:
1 2 3 4 5 | (lldb) exp a = 10 (NSInteger) $0 = 10 (lldb) exp b = 100 (NSInteger) $1 = 100 2015-01-25 14:00:41.313 test[18064:71466] a + b = 110, abc |
expression命令的功能不仅于此,正如上面的po命令,其实际也是”expression -O —“命令的别名。更详细使用可以参考Evaluating Expressions。
image
image命令的用法也挺多,首先可以用它来查看工程中使用的库,如下所示:
1 2 3 4 5 6 7 8 9 | (lldb) image list [ 0] 432A6EBF-B9D2-3850-BCB2-821B9E62B1E0 0x0000000100000000 /Users /**/ Library/Developer/Xcode/DerivedData/test-byjqwkhxixddxudlnvqhrfughkra/Build/Products/Debug/test [ 1] 65DCCB06-339C-3E25-9702-600A28291D0E 0x00007fff5fc00000 /usr/lib/dyld [ 2] E3746EDD-DFB1-3ECB-88ED-A91AC0EF3AAA 0x00007fff8d324000 /System/Library/Frameworks/Foundation.framework/Versions/C/Foundation [ 3] 759E155D-BC42-3D4E-869B-6F57D477177C 0x00007fff8869f000 /usr/lib/libobjc.A.dylib [ 4] 5C161F1A-93BA-3221-A31D-F86222005B1B 0x00007fff8c75c000 /usr/lib/libSystem.B.dylib [ 5] CBD1591C-405E-376E-87E9-B264610EBF49 0x00007fff8df0d000 /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation [ 6] A260789B-D4D8-316A-9490-254767B8A5F1 0x00007fff8de36000 /usr/lib/libauto.dylib ...... |
我们还可以用它来查找可执行文件或共享库的原始地址,这一点还是很有用的,当我们的程序崩溃时,我们可以使用这条命令来查找崩溃所在的具体位置,如下所示:
1 2 | NSArray *array = @[@1, @2]; NSLog(@ "item 3: %@" , array[2]); |
这段代码在运行后会抛出如下异常:
1 2 3 4 5 6 7 8 9 10 | 2015-01-25 14:12:01.007 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文件中,要想知道具体在哪一行,可以使用以下命令:
1 2 3 | (lldb) image lookup --address 0x0000000100000de0 Address: test[0x0000000100000de0] (test.__TEXT.__text + 384) Summary: test`main + 384 at main.m:23 |
可以看到,最后定位到了main.m文件的第23行,正是我们代码所在的位置。
我们还可以使用image lookup命令来查看具体的类型,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | (lldb) image lookup --type NSURL Best match found in /Users /**/ Library/Developer/Xcode/DerivedData/test-byjqwkhxixddxudlnvqhrfughkra/Build/Products/Debug/test: id = {0x100000157}, name = "NSURL" , byte-size = 40, decl = NSURL.h:17, clang_type = "@interface NSURL : NSObject{ NSString * _urlString; NSURL * _baseURL; void * _clients; void * _reserved; } @property ( readonly,getter = absoluteString,setter = < null selector>,nonatomic ) NSString * absoluteString; @property ( readonly,getter = relativeString,setter = < null selector>,nonatomic ) NSString * relativeString; @property ( readonly,getter = baseURL,setter = < null selector>,nonatomic ) NSURL * baseURL; @property ( readonly,getter = absoluteURL,setter = < null selector>,nonatomic ) NSURL * absoluteURL; @property ( readonly,getter = scheme,setter = < null selector>,nonatomic ) NSString * scheme; @property ( readonly,getter = resourceSpecifier,setter = < null selector>,nonatomic ) NSString * resourceSpecifier; @property ( readonly,getter = host,setter = < null selector>,nonatomic ) NSString * host; @property ( readonly,getter = port,setter = < null selector>,nonatomic ) NSNumber * port; @property ( readonly,getter = user,setter = < null selector>,nonatomic ) NSString * user; @property ( readonly,getter = password,setter = < null selector>,nonatomic ) NSString * password; @property ( readonly,getter = path,setter = < null selector>,nonatomic ) NSString * path; @property ( readonly,getter = fragment,setter = < null selector>,nonatomic ) NSString * fragment; @property ( readonly,getter = parameterString,setter = < null selector>,nonatomic ) NSString * parameterString; @property ( readonly,getter = query,setter = < null selector>,nonatomic ) NSString * query; @property ( readonly,getter = relativePath,setter = < null selector>,nonatomic ) NSString * relativePath; @property ( readonly,getter = fileSystemRepresentation,setter = < null selector> ) const char * fileSystemRepresentation; @property ( readonly,getter = isFileURL,setter = < null selector>,readwrite ) BOOL fileURL; @property ( readonly,getter = standardizedURL,setter = < null selector>,nonatomic ) NSURL * standardizedURL; @property ( readonly,getter = filePathURL,setter = < null selector>,nonatomic ) NSURL * filePathURL; @end"</ null selector></ null selector></ null selector></ null selector></ null selector></ null selector></ null selector></ null selector></ null selector></ null selector></ null selector></ null selector></ null selector></ null selector></ null selector></ null selector></ null selector></ null selector></ null selector> |
可以看到,输出结果中列出了NSURL的一些成员变量及属性信息。 和 command 进去差不多 .
image命令还有许多其它功能,具体可以参考Executable and Shared Library Query Commands。
查看线程状态
在进程停止后,LLDB会选择一个当前线程和线程中当前帧(frame)。很多检测状态的命令可以用于这个线程或帧。
为了检测进程的当前状态,可以从以下命令开始:
1 2 3 4 5 | (lldb) thread list Process 46915 state is Stopped * thread #1: tid = 0x2c03, 0x00007fff85cac76a, where = libSystem.B.dylib`__getdirentries64 + 10, stop reason = signal = SIGSTOP, queue = com.apple.main-thread thread #2: tid = 0x2e03, 0x00007fff85cbb08a, where = libSystem.B.dylib`kevent + 10, queue = com.apple.libdispatch-manager thread #3: tid = 0x2f03, 0x00007fff85cbbeaa, where = libSystem.B.dylib`__workq_kernreturn + 10 |
星号(*)表示thread #1为当前线程。为了获取线程的跟踪栈,可以使用以下命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 | (lldb) thread backtrace thread #1: tid = 0x2c03, stop reason = breakpoint 1.1, queue = com.apple.main-thread frame #0: 0x0000000100010d5b, where = Sketch`-[SKTGraphicView alignLeftEdges:] + 33 at /Projects/Sketch/SKTGraphicView.m:1405 frame #1: 0x00007fff8602d152, where = AppKit`-[NSApplication sendAction:to:from:] + 95 frame #2: 0x00007fff860516be, where = AppKit`-[NSMenuItem _corePerformAction] + 365 frame #3: 0x00007fff86051428, where = AppKit`-[NSCarbonMenuImpl performActionWithHighlightingForItemAtIndex:] + 121 frame #4: 0x00007fff860370c1, where = AppKit`-[NSMenu performKeyEquivalent:] + 272 frame #5: 0x00007fff86035e69, where = AppKit`-[NSApplication _handleKeyEquivalent:] + 559 frame #6: 0x00007fff85f06aa1, where = AppKit`-[NSApplication sendEvent:] + 3630 frame #7: 0x00007fff85e9d922, where = AppKit`-[NSApplication run] + 474 frame #8: 0x00007fff85e965f8, where = AppKit`NSApplicationMain + 364 frame #9: 0x0000000100015ae3, where = Sketch`main + 33 at /Projects/Sketch/SKTMain.m:11 frame #10: 0x0000000100000f20, where = Sketch`start + 52 |
如果想查看所有线程的调用栈,则可以使用以下命令:
1 | (lldb) thread backtrace all |
6.断点命令
一般来说,在xcode中新建/删除“行断点”是很容易的,但是断点还有很多进阶使用方法:
条件断点、条件执行、记录日志、自动继续、重复断点跳过。
使用xcode提供的可视化工具来操作是很容易的:
7.在debugger中执行任意代码
1 2 3 4 5 6 7 | (lldb) e char *$str = ( char *)malloc( 128 ) (lldb) e ( void )strcpy($str, "wxrld of warcraft" ) (lldb) e $str[ 1 ] = 'o' ( char ) $ 0 = 'o' (lldb) p $str ( char *) $str = 0x00007fd04a900040 "world of warcraft" (lldb) e ( void )free($str) |
所以,在debugger中可以修改view的颜色、尺寸、甚至创建controller来push。
命令别名及帮助系统
LLDB有两个非常有用的特性,即命令别名及帮助。
命令别名
我们可以使用LLDB的别名机制来为常用的命令创建一个别名,以方便我们的使用,如下命令:
1 | (lldb) breakpoint set --file foo.c --line 12 |
如果在我们的调试中需要经常用到这条命令,则每次输入这么一长串的字符一定会很让人抓狂。此时,我们就可以为这条命令创建一个别名,如下所示:
1 | (lldb) command alias bfl breakpoint set -f %1 -l %2 |
这样,我们只需要按如下方式来使用它即可:
是不是简单多了?
我们可以自由地创建LLDB命令的别名集合。LLDB在启动时会读取~/.lldbinit文件。这个文件中存储了command alias命令创建的别名。LLDB帮助系统会读取这个初始化文件并会列出这些别名,以让我们了解自己所设置的别名。我们可以使用”help -a”命令并在输出的后面来查看这边别名,其以下面这行开始:
1 2 | ... The following is a list of your current command abbreviations (see 'help command alias' for more info): ... |
如果我们不喜欢已有命令的别名,则可以使用以下命令来取消这个别名:
1 | (lldb) command unalias b |
帮助系统
LLDB帮助系统让我们可以了解LLDB提供了哪些功能,并可以查看LLDB命令结构的详细信息。熟悉帮助系统可以让我们访问帮助系统中中命令文档。
我们可以简单地调用help命令来列出LLDB所有的顶层命令。如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 | (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为例,输出信息如下:
1 2 3 4 5 6 7 8 9 | (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有哪些功能,即使用apropos命令:它会根据关键字来搜索LLDB帮助文档,并为每个命令选取一个帮助字符串,我们以apropos file为例,其输出如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | (lldb) apropos file The following commands may relate to 'file' : … log enable -- Enable logging for a single log channel. memory read -- Read from the memory of the process being debugged. memory write -- Write to the memory of the process being debugged. platform process launch -- Launch a new process on a remote platform. platform select -- Create a platform if needed and select it as the current platform. plugin load -- Import a dylib that implements an LLDB plugin. process launch -- Launch the executable in the debugger. process load -- Load a shared library into the current process. source -- A set of commands for accessing source file information … and so forth … |
我们还可以使用help来了解一个命令别名的构成。如:
1 2 3 | (lldb) help b … 'b' is an abbreviation for '_regexp-break' |
help命令的另一个特性是可以查看某个具体参数的使用,我们以”break command add”命令为例:
1 2 3 4 | (lldb) help break command add Add a set of commands to a breakpoint, to be executed whenever the breakpoint is hit. Syntax: breakpoint command add <cmd-options> <breakpt-id> etc...</breakpt-id></cmd-options> |
如果想了解以上输出的参数的作用,我们可以在help后面直接指定这个参数(将其放在尖括号内)来查询它的详细信息,如下所示:
1 2 3 | (lldb) help <breakpt-id> <breakpt-id> -- Breakpoint IDs consist major and minor numbers; the major etc...</breakpt-id></breakpt-id> |
帮助系统能让我们快速地了解一个LLDB命令的使用方法。经常使用它,可以让我们更快地熟悉LLDB的各项功能,所以建议多使用它。