iOS调试 - LLDB

开发iOS的时候常常会用到调试跟踪,如何正确的使用调试器来debug十分重要。
Xcode里有内置的Debugger,老版使用的是GDB,Xcode自4.3之后默认使用的就是LLDB了。

GDB: UNIX及UNIX-like下的调试工具。

LLDB: 开源的内置于XCode的具有REPL(read-eval-print-loop)特征的Debugger,其可以安装C++或者Python插件。

两个都是调试用的Debugger,只是LLDB是比较高级的版本,或者在调试开发iOS应用时比较好用,lldb与gdb命令名的对照表:http://lldb.llvm.org/lldb-gdb.html

开始使用LLDB

在什么地方可以输入这个命令?
首先, 在程序里你需要的地方设置断点。
当断点断住的时候你就能看到我们进入LLDB调试器了。

iOS调试 - LLDB_第1张图片

这时就可以使用一些LLDB命令来进行一些调试了。

一些Xcode调试快捷键:
command+shift+Y 打开调试窗口
command+Y 调试运行程序
command+option+P 继续
command+shift+O 跳过
command+shift+I 进入
command+shift+T 跳出

常用命令

  • help
  • 最简单命令 help 会列举出所有的命令。如果你忘记了一个命令是做什么的,或者想知道更多的话,你可以通过 help 来了解更多细节,例如 help print 或者 help thread。

    iOS调试 - LLDB_第2张图片

    iOS调试 - LLDB_第3张图片

  • print
  • 试试 print 命令:


    iOS调试 - LLDB_第4张图片

    LLDB 实际上会作前缀匹配。所以你也可以使用 prin,pri,或者 p。但你不能使用 pr,因为 LLDB 不能消除和 process 的歧义。

    结果中有个 $0。实际上你可以使用它来指向这个结果。试试 print $0 + 7,你会看到 106。任何以美元符开头的东西都是存在于 LLDB 的命名空间的,它们是为了帮助你进行调试而存在的。

    输出view 下 subview 的数量

    //由于 subview 的数量是一个 int 类型的值,所以我们使用命令p:
    (lldb)p (int)[[[self view] subviews] count]
    

    直接调用方法改变背景颜色之类

    其实使用p,po,call都可以调用方法,只是p和po都是用于输出的有返回值的。call一般只在不需要显示输出,或是方法无返回值时使用。
    (lldb)p [self.view setBackgroundColor:[UIColor redColor]]
    (lldb)p (void)[CATransaction flush]
    上述的p一般使用call比较好,因为方法是没有返回值的。

  • p objects
  • 命令p objects跟p很像。p输出的是基本类型,po输出的Objective-C对象。调试器会输出这个 object 的 description。
    po (print object 的缩写):


    iOS调试 - LLDB_第5张图片

  • expression
  • 如果想改变一个值怎么办?其实这时候我们要用到的是 expression 这个方便的命令。
    expression的简写就是e。可以用expression来声明新的变量,也可以改变已有变量的值。我们看到e声明的都是$开头的变量。我们在使用时也需要加上$符号。

    创建新的变量示例:


    iOS调试 - LLDB_第6张图片

    注意:如果上面这里输入以下命令,会发生错误。说明lldb无法判定某一步的计算结果是什么数据类型,这时需要强制类型转换来告诉lldb。

    (lldb) p [[$array objectAtIndex:0] characterAtIndex:0]
    error: no known method '-characterAtIndex:'; cast the message send to the method's return type
    error: 1 errors parsing expression
    
    (lldb) p (char)[[$array objectAtIndex:0] characterAtIndex:0]
    'o'
    

    修改已有变量示例:


    iOS调试 - LLDB_第7张图片

  • image
  • image 命令可用于寻址,有多个组合命令。
    比较实用的用法是用于寻找栈地址对应的代码位置。
    如:

    NSArray *arr=[[NSArray alloc] initWithObjects:@"1",@"2", nil];
    NSLog(@"%@",arr[2]);
    

    这段代码有明显的错误,程序运行这段代码后会抛出下面的异常:

     *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 2 beyond bounds [0 .. 1]'
    *** First throw call stack:
    (
     0   CoreFoundation                      0x0000000101951495 __exceptionPreprocess + 165
     1   libobjc.A.dylib                     0x00000001016b099e objc_exception_throw + 43
    2   CoreFoundation                      0x0000000101909e3f -[__NSArrayI objectAtIndex:] + 175
    3   ControlStyleDemo                    0x0000000100004af8 -[RootViewController viewDidLoad] + 312
    4   UIKit                               0x000000010035359e -[UIViewController loadViewIfRequired] + 562
    5   UIKit                               0x0000000100353777 -[UIViewController view] + 29
    6   UIKit                               0x000000010029396b -[UIWindow addRootViewControllerViewIfPossible] + 58
    7   UIKit                               0x0000000100293c70 -[UIWindow _setHidden:forced:] + 282
    8   UIKit                               0x000000010029cffa -[UIWindow makeKeyAndVisible] + 51
    9   ControlStyleDemo                    0x00000001000045e0 -[AppDelegate application:didFinishLaunchingWithOptions:] + 672
    10  UIKit                               0x00000001002583d9 -[UIApplication _handleDelegateCallbacksWithOptions:isSuspended:restoreState:] + 264
    11  UIKit                               0x0000000100258be1 -[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 1605
    12  UIKit                               0x000000010025ca0c -[UIApplication _runWithURL:payload:launchOrientation:statusBarStyle:statusBarHidden:] + 660
    13  UIKit                               0x000000010026dd4c -[UIApplication handleEvent:withNewEvent:] + 3189
    14  UIKit                               0x000000010026e216 -[UIApplication sendEvent:] + 79
    15  UIKit                               0x000000010025e086 _UIApplicationHandleEvent + 578
    16  GraphicsServices                    0x0000000103aca71a _PurpleEventCallback + 762
    17  GraphicsServices                    0x0000000103aca1e1 PurpleEventCallback + 35
    18  CoreFoundation                      0x00000001018d3679 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 41
    19  CoreFoundation                      0x00000001018d344e __CFRunLoopDoSource1 + 478
    20  CoreFoundation                      0x00000001018fc903 __CFRunLoopRun + 1939
    21  CoreFoundation                      0x00000001018fbd83 CFRunLoopRunSpecific + 467
    22  UIKit                               0x000000010025c2e1 -[UIApplication _run] + 609
    23  UIKit                               0x000000010025de33 UIApplicationMain + 1010
    24  ControlStyleDemo                    0x0000000100006b73 main + 115
    25  libdyld.dylib                       0x0000000101fe95fd start + 1
    26  ???                                 0x0000000000000001 0x0 + 1
    )
    libc++abi.dylib: terminating with uncaught exception of type NSException
    

    现在,我们怀疑出错的地址是0x0000000100004af8(可以根据执行文件名判断,或者最小的栈地址)。为了进一步精确定位,我们可以输入以下的命令:

    (lldb)image lookup --address 0x0000000100004af8
    (lldb)im loo -a 0x0000000100004af8
    

    命令执行后返回:

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

    可以看到,出错的位置是RootViewController.m的第53行。

  • call
  • call即是调用的意思。其实上述的po和p也有调用的功能。
    因此一般只在不需要显示输出,或是方法无返回值时使用call。
    和上面的命令一样,我们在viewDidLoad:里面设置断点,然后在程序中断的时候输入下面的命令:

    call [self.view setBackgroundColor:[UIColor redColor]]
    

    继续运行程序,看看view的背景颜色是不是变成红色的了!
    在调试的时候灵活运用call命令可以起到事半功倍的作用。

  • bt
  • 打印调用堆栈,加all可打印所有thread的堆栈。

  • 流程控制命令
  • 实际上使用xcode自带的可视化工具来控制“继续”“暂停”“下一步”“进入”“跳出”更简单,但这里还是列出其所对应的命令名:

    继续:process continue, continue, c

    下一步:thread step-over, next, n

    进入:thread step-in, step, s

    跳出:thread step-out, finish, f

  • thread return
  • 执行thread return命令可以使得当前函数立即返回,也就是说,后续代码都不会执行了。当然执行此命令可能会使得arc的计数追踪出现错乱。

    thread return命令需要一个参数来指明函数强制返回时的返回值。

  • 断点命令
  • 断点有很多进阶使用方法:条件断点、条件执行、记录日志、自动继续、重复断点跳过。
    使用xcode提供的可视化工具来操作是很容易的:


    iOS调试 - LLDB_第8张图片

  • 调试中执行任意代码
  • (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。

  • watchpoint
  • watchpoint可以在某个变量被写入/读取时暂停程序运行:

    (lldb) watchpoint set expression -- (int*)&_abc4
    Watchpoint created: Watchpoint 7: addr = 0x15e36d3c size = 4 state = enabled type = w
        new value: 0x00000000
    (lldb) watchpoint set v -w read _abc4
    Watchpoint created: Watchpoint 8: addr = 0x15e36d3c size = 4 state = enabled type = r
        watchpoint spec = '_abc4'
        new value: 0
    (lldb) watchpoint set v -w read_write _abc3
    Watchpoint created: Watchpoint 9: addr = 0x15e36d38 size = 4 state = enabled type = rw
        watchpoint spec = '_abc3'
        new value: 0
    

    实际上可以使用watchpoint来监视任意一段内存的读写。
    使用XCode也可以方便地创建watchpoint。

    XCode的可视化debug工具中的watch是一个write类型watchpoint(也就是默认的)

    另外,上述语句中 v是variable的简写,同样的,set可以简写为s,watch可以简写为wa,而-w后面的参数是不可以简写的必须为read、write或者read_write。

    当前在arm和x86上,我们一次最多创建4个watchpoint,继续创建会提示错误。

  • 查看内存
  • 使用XCode的可视化工具来查看memory,要注意watch memory of "p" 和watch memory of "*p"的区别。

    手动执行命令可以help x或者 help memory。

    你可能感兴趣的:(iOS调试 - LLDB)