LLDB调试器总结

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/shenhualxt/article/details/50697245

LLDB 是一个有着 REPL 的特性和 C++ ,Python 插件的开源调试器。LLDB 绑定在 Xcode 内部,存在于主窗口底部的控制台中。调试器允许你在程序运行的特定时暂停它,你可以查看变量的值,执行自定的指令,并且按照你所认为合适的步骤来操作程序的进展。(这里有一个关于调试器如何工作的总体的解释。)

基础

  • help <变量> //了解命令的更多细节
  • p <变量> //p是print的简写 用来打印值类型
    p命令

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

  • po <变量> //po是print object的简写 用来打印对象类型

    尝试输入

    p ocValue

    输出结果比较繁琐

    (__NSCFConstantString *) $1 = 0x0000000100001038 @"Hellow World"
    

    如果是复杂的对象则更糟

    (lldb) p @[ @"foo", @"bar" ]
    (__NSArrayI *) $4 = 0x0000000100600020 @"2 objects"
    

    通常,我们想看的是对象的description方法的结果,可以

    (lldb) po ocValue
    Hellow World
    
  • e <赋值表达式> //e是expression的简写

    (lldb) e ocValue=@"new Value"
    (NSTaggedPointerString *) $7 = 0x0b0243f720240095 @"new Value"
    (lldb) po ocValue
    new Value
    
    (lldb) e count=3
    (NSUInteger) $9 = 3
    (lldb) po count
    3
  • thread return <可选参数> //在执行时它会把可选参数加载进返回寄存器里,然后立刻执行返回命令,跳出当前栈帧。这意味这函数剩余的部分不会被执行。

    起到的作用类似

    int calculateTheTrickyValue {
    return 9;
    
    /*
    some code
    ...
    }
    

    想象把断点放在函数的开头,然后用 thread return 命令重写函数的行为,然后继续。想象一下让这个过程自动化,听起来不错,不是吗?

  • br li //列出所有的断点

    (lldb) br list
    Current breakpoints:
    1: file = '/Users/terry/Desktop/LLDBTest/LLDBTest/main.m', line = 15, locations = 1, resolved = 1, hit count = 1
    
      1.1: where = LLDBTest`main + 65 at main.m:15, address = 0x0000000100000ef1, resolved, hit count = 1 
    
    (lldb) 
  • br enable <breakpointID> 或者 br disable <breakpointID> //断点的开启

    (lldb) br enable 1
    1 breakpoints enabled.
    (lldb) br disable 1
    1 breakpoints disabled.
    (lldb)

高级技巧

创建普通断点(指定某一行的断点)和符号断点(根据调用的方法指定断点)

  • b main.m:17 //b 是_regexp-break 断点定位到main.m文件的17行

  • b add //断点打在方法的开始
    创建普通断点

  • 通过Xcode的UI创建符号断点
    创建符号断点
    Symbol对应的输入框中输入-[NSArray objectAtIndex:],这样每次调用这个函数的时候,程序都会停止,不管是你调用还是苹果调用。

配置断点

使用Xcode UI

创建符号断点

输出结果如下

  (NSUInteger) $1 = 3
  "测试ShellCommand"
  count的结果:3
  (lldb) 

并弹出对话框,发出声音

创建符号断点

  • Conditon //断点生效的条件

  • ignore //告诉断点最初的 n 次调用 (并且条件为真的时候) 的时候不要停止。

  • Action //触发断点后的行为

    • Debugger Command //使用lldb命令 示例见上图
    • AppleScript //使用AppleScript,可操作本地应用
    • Shell Command //使用Shell命令
    • Log Message //类似NSLog
    • Automatically continue after evaluation actions. //选中它,调试器会运行你所有的命令,然后继续运行。看起来就像没有执行任何断点一样 。

使用LLDB命令 配置断点

(lldb) b add
Breakpoint 2: 77 locations.
(lldb) br modify -c 'a == 3' 2
(lldb) br command add 2
Enter your debugger command(s).  Type 'DONE' to end.
> p a
> DONE
(lldb) br list 2
2: name = 'add', locations = 77, resolved = 77, hit count = 0
    Breakpoint commands:
      p a

Condition: a == 3

  2.1: where = LLDBTest`add + 10 at main.m:12, address = 0x0000000100000e8a, resolved, hit count = 0 

运行到断点的结果

 p a
(int) $4 = 3
(lldb) 

FaceBook插件Chisel

安装

brew update
brew install chisel

安装完成按照安装日志上的提示,在~/.lldbinit文件中添加一行,没有则新建。 提示类似如下:

command script import /usr/local/opt/chisel/libexec/fblldb.py

备注:每次Xcode启动,都会加载~/.lldbinit文件

常用命令

  • pviews //递归打印所有的view,并能标示层级,等价于[view recursiveDescription]

    (lldb) pviews self.view
    <UIView: 0x12ee993b0; frame = (0 0; 320 568); autoresize = W+H; layer = <CALayer: 0x12ee98690>>
    | <_UILayoutGuide: 0x12ee99760; frame = (0 0; 0 0); hidden = YES; layer = <CALayer: 0x12ee8f2c0>>
    | <_UILayoutGuide: 0x12ee9a190; frame = (0 0; 0 0); hidden = YES; layer = <CALayer: 0x12ee98580>>
  • pvc //递归打印所有的viewController,并能标示层级,等价于[UIViewController _printHierarchy]

    (lldb) pvc
    <TabBarController: 0x13772fd0; view = <UILayoutContainerView; 0x151b3a30>; frame = (0, 0; 414, 736)>
    | <UINavigationController: 0x1602b800; view = <UILayoutContainerView; 0x1b00aca0>; frame = (0, 0; 414, 736)>
    |   | <FirstViewController: 0x16029c00; view = <UIView; 0x1b01e1c0>; frame = (0, 0; 414, 736)>
    | <UINavigationController: 0x138c5200; view = <UILayoutContainerView; 0x1316a080>; frame = (0, 0; 414, 736)>
    |   | <SecondViewController: 0x16030400; view = <UIView; 0x2094b370>; frame = (0, 0; 414, 736)>
    |   |   | <SecondChildViewController: 0x15af6000; view = <UIView; 0x18d4e650>; frame = (0, 64; 414, 628)>
    | <UINavigationController: 0x1383ca00; view = <UILayoutContainerView; 0x13180070>; frame = (0, 0; 414, 736)>
    |   | <ThirdViewController: 0x138ddc00; view = <UIView; 0x18df6650>; frame = (0, 0; 414, 736)>
    |   |   | <ThirdChild1ViewController: 0x1393fe00; view = <UIView; 0x131ec000>; frame = (0, 0; 414, 672)>
    |   |   | <ThirdChild2ViewController: 0x138dce00; view = <UIView; 0x204075a0>; frame = (414, 0; 414, 672)>
    |   |   | <ThirdChild3ViewController: 0x138a8e00; view = <UIView; 0x20426250>; frame = (828, 0; 414, 672)>
    | <UINavigationController: 0x160eca00; view = <UILayoutContainerView; 0x152f7d90>; frame = (0, 0; 414, 736)>
    |   | <FourViewController: 0x13157cc0; view not loaded>
  • visualize //可以让你使用Mac的预览打开一个 UIImage, CGImageRef, UIView, 或 CALayer。 这个功能或许可以帮我们用来截图、用来定位一个view的具体内容

    (lldb) visualize imageView
  • fv & fvc //通过类名搜索当前内存中存在的view和viewController实例的命令,支持正则搜索

    (lldb) fv scrollView
    0x18d3b8c0 UIScrollView
    (lldb) fvc Home
    0x1393fe00 HomeFeedsViewController
    0x138a8e00 HomeFeedsViewController
  • show & hide <view or layer> //显示和隐藏一个指定的 UIView . 你甚至不需要Continue Progress. 就可以看到效果

  • mask/unmask <view or layer> //border/unborder <view or layer> //添加蒙版 添加边框,用来表示view或layer的位置

  • calfush //重新绘制界面 等价于[CATransaction flush],要注意如果在动画过程中执行这个命令,就直接渲染出动画结束的效果;当你想在调试界面颜色、坐标之类的时候,可以直接在控制台修改属性,然后caflush就可以看到效果啦,是不是要比改代码,然后重新build省事多了呢。

    (lldb) e (void)[self.label setBackgroundColor:[UIColor greenColor]]
    (lldb) caflush
  • bmessage //这个命令就是用来打断点用的了,虽然大家断点可能都喜欢在图形界面里面打,但是考虑一种情况:我们想在 [MyViewController viewWillAppear:] 里面打断点,但是 MyViewController并没有实现 viewWillAppear: 方法, 以往的作法可能就是在子类中实现下viewWillAppear:,然后打断点,然后rebuild。
    那么幸好有了 bmessage命令。我们可以不用这样就可以打这个效果的断点: (lldb) bmessage -[ViewController viewWillAppear:] 上面命令会在其父类的 viewWillAppear: 方法中打断点,并添加上了条件:[self isKindOfClass:[ViewController class]]

    //创建断点
    (lldb) bmessage -[ViewController viewDidAppear:]
    Setting a breakpoint at -[ViewController viewDidAppear:] with condition (void*)object_getClass((id)$x0) == 0x00000001000acd58
    Breakpoint 2: where = ChiselTest`-[ViewController viewDidAppear:] at ViewController.m:31, address = 0x00000001000aa6a0
    //添加条件
    (lldb) breakpoint modify -c '[self isKindOfClass:[ViewController class]]' 2

    创建断点

  • wivar <Object> <示例变量> \观察实例变量的变化

    假设你有一个 ViewController,不知道为什么它的 _label2 实例变量被重写了 (糟糕)。因为有可能并不涉及到方法,我们不能使用符号断点。相反的,我们想监视什么时候这个地址被写入。
    创建断点

  • 自定义命令 //我们也可以自定义插件,不过前提是要懂一些 python。 比如设计一个打印keyWindow的windowLevel的命令

    创建python脚本文件 /magical/commands/example.py :

    
    #!/usr/bin/python
    
    
    # Example file with custom commands, located at /magical/commands/example.py
    
    
    import lldb
    import fblldbbase as fb
    
    def lldbcommands():
      return [ PrintKeyWindowLevel() ]
    
    class PrintKeyWindowLevel(fb.FBCommand):
      def name(self):
        return 'pkeywinlevel'
    
      def description(self):
        return 'An incredibly contrived command that prints the window level of the key window.'
    
      def run(self, arguments, options):
        # It's a good habit to explicitly cast the type of all return
        # values and arguments. LLDB can't always find them on its own.
        lldb.debugger.HandleCommand('p (CGFloat)[(id)[(id)[UIApplication sharedApplication] keyWindow] windowLevel]')
    

    其中定义了PrintKeyWindowLevel的类,需要实现 name description run 方法来分别告诉名称、描述、和执行实体。

    创建好脚本后,然后在前面安装时创建的 ~/.lldbinit文件中添加一行:

    script fblldb.loadCommandsInDirectory('/magical/commands/')
    

    然后重启Xcode之后就可以使用自定义的命令啦。

LLDB和Python

  • LLDB 有内建的,完整的 Python 支持。在LLDB中输入 script,会打开一个 Python REPL。你也可以输入一行 python 语句作为 script 命令 的参数,这可以运行 python 语句而不进入REPL:

    (lldb) script
    Python Interactive Interpreter. To exit, type 'quit()', 'exit()'.
    >>> import os
    >>> os.system("open http://www.objc.io/")
    0

    等价于

    (lldb) script
    Python Interactive Interpreter. To exit, type 'quit()', 'exit()'.
    >>> import os
    >>> os.system("open http://www.objc.io/")
    0

    结果:在默认浏览器中打开网址:http://www.objc.io/
    这就是Chisel的实现原理。

用LLDB优化调试

  • 配置断点Log Message 代替NSLog
  • 使用expression 改变变量,继续运行,代替修改代码,改变变量值,重新编译
  • 使用thread return <可选参数> 动态添加 return 代码, 代替代码添加return ,重新编译

常见错误整理

  • 不明类型或者类型不匹配

    (lldb) p NSLog(@"%@",[self.view  viewWithTag:1001])
    error: 'NSLog' has unknown return type; cast the call to its declared return type
    error: 1 errors parsing expression

    多见于id类型,比如NSArray中某个值),那我们就必须显式声明类型

    p (void)NSLog(@"%@",[self.view  viewWithTag:1001])
    
  • 找不到方法

    (lldb) po self.view.frame
    error: unsupported expression with unknown type
    error: unsupported expression with unknown type
    error: 2 errors parsing expression

    这似乎是lldb的一个bug,无法通过点属性访问的方法打印framework里面的对象,但是自己在app里面定义的就可以。我们把上面的命令改动一下:

    (lldb) p (CGRect)[self.view frame]
    (CGRect) $0 = origin=(x=0, y=0) size=(width=320, height=480)

    或者

    (lldb) po self.view
    <UIView: 0x15fe67a10; frame = (0 0; 320 568); autoresize = W+H; layer = <CALayer: 0x15fe67d80>>
展开阅读全文

没有更多推荐了,返回首页