zoukankan      html  css  js  c++  java
  • Swift 使用 LLDB 调试命令


    打印和赋值,观察数值变量和view对象属性

    • p指令可打印其对象类型、内存地址以及该对象的值等具体信息,
    • po指令则是打印其调用description方法得到的值。
    • e 赋值指令(后面有例子详解)

    流程控制

    image_1bpvqsf71os1t5e64h16mm4lm9.png-61.6kB

    n 命令,代表 Step Over 操作。
    s 命令,代表 Step Into 操作。
    finish 命令,代表 Step Out 操作。
    c 命令,代表恢复程序执行操作。

    修改指针变量的值,观测程序不同变化

    程序中: testThreadReturn = “testThreadReturn”
    
    (lldb) p testThreadReturn
    (String) $R2 = "testTheardReturn"
    (lldb) e testThreadReturn = "zaozuo"
    (lldb) p testThreadReturn 
    (String) $R4 = "zaozuo"
    

    动态修改view的属性

    image_1bpvvlkmfdnjgq0t4toj41fodm.png-45.4kB

    Thread Return,函数设置断点返回值(swift不兼容)

    • 这个在OC一切ok,在swift还不能用会有以下错误
    (lldb) thread return "zaozuo-return"
    error: Error returning from frame 0 of thread 1: We only support setting simple integer and float return types at present..
    

    调试时,还有一个很棒的函数可以用来控制程序流程:thread return 。它有一个可选参数,在执行时它会把可选参数加载进返回寄存器里,然后立刻执行返回命令,跳出当前栈帧。这意味这函数剩余的部分不会被执行。这会给 ARC 的引用计数造成一些问题,或者会使函数内的清理部分失效。但是在函数的开头执行这个命令,是个非常好的隔离这个函数,伪造返回值的方式 。

    直接设置返回值,不用写死代码破坏代码结构

    (五星推荐):编辑断点

    设置断点进入条件,condition

    image_1blat7pff10qmu251thl1c29fs8g.png-182.4kB

    断点行为 (Action):

    可以设置或不设置,断点进入的条件,设置进入条件后还可以,自定义后续的动作,比如,打印值,修改值,执行shell命令,执行lldb命令,打印log等等很多自定义的动作,这一点确实很强大,这里就可以和shell 脚本联动,测试一些不好测试的点,和需要提前处理外部因素的案例

    (五星推荐):在lldb中初始化值,动态赋值,执行多行语句

    • 简单变量赋值,仅仅在调试环境赋值了一个值,是局部变量,可以用print打印
    (lldb) e let hello = "Hello"
    you can simply use:
    (lldb) p let hello = "Hello"
    
    • 使用"$"符号定义全局常量和变量
    OC中为:
    (lldb) e NSString *$a = @"c" (lldb) po $a c 
    
    Swift中变为:
    (lldb) e let $a = "a" 
    (lldb) po $a "a"  
    
    • Swift中多行语句:输入完成敲一个空行即可开始执行
    (lldb) p
    Enter expressions, then terminate with an empty line to evaluate:
    struct compass{var direction = "N"; var angle = 16.5}
    var c = compass()
    print(c)
    (lldb)
    
    • 同样的你也可以向当前view添加一个layer
    (lldb) p
    Enter expressions, then terminate with an empty line to evaluate:
    1 let layer = CALayer()
    2 layer.backgroundColor = UIColor.yellow.cgColor
    3 layer.bounds = CGRect(x:0, y:0, 100, height:100)
    4 layer.position = CGPoint(x:250, y:300)
    5 layer.cornerRadius = 12.0
    6 view.layer.addSublayer(layer)
    7 
    (lldb)
    

    image_1bq027mrc1hea136n116b1vj617m91a.png-18.3kB

    • 修改函数中成员变量的值
      image_1bnnq8f3i155b1k2aaov108dkv19.png-106.9kB

    引入模块 Importing modules

    (lldb) p import MapKit
    

    打印UI层级关系及更新UI

    • oc,试了下这个在swift也可以运行:
    (lldb) po [[[UIApplication sharedApplication] keyWindow] recursiveDescription]
    <UIWindow: 0x7ffcd2f0f1e0; frame = (0 0; 375 667); gestureRecognizers = <NSArray: 0x7ffcd2f10170>; layer = <UIWindowLayer: 0x7ffcd2f0ea80>>
       | <UIView: 0x7ffcd2c6dc10; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7ffcd2c17f10>>
       |    | <UIButton: 0x7ffcd2c6dfc0; frame = (20 62; 78 30); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x7ffcd2c6bc10>>
       |    |    | <UIButtonLabel: 0x7ffcd2f15af0; frame = (16 6; 46 18); text = 'Button'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x7ffcd2f16120>>
       |    | <_UILayoutGuide: 0x7ffcd2c6fae0; frame = (0 0; 0 20); hidden = YES; layer = <CALayer: 0x7ffcd2c6faa0>>
       |    | <_UILayoutGuide: 0x7ffcd2c70740; frame = (0 667; 0 0); hidden = YES; layer = <CALayer: 0x7ffcd2c6d3d0>>
    
    • swift:
    expr -l objc++ -O -- [[[UIWindow keyWindow] rootViewController] _printHierarchy]
    
    • view指针赋值
    (lldb) e id $view = (id) 0x7fbd71432590 
    
    • 使用指针进行操作
    (lldb) e (void) [$view setBackgroundColor:[UIColor redColor]]
    
    • 当然,我们运行完这条命令,界面上不会马上反应出来,我们还需要
      调用这个命令刷新一下:
    (lldb) e (void)[CATransaction flush]
    

    Push 一个 View Controller

    • 第一步我本地实验了下没有成功,报错了就,可能我代码是swift的所以得用swift 代码,下面仅仅oc 可以
    (lldb) e navigationController?.pushViewController($vcc, animated: true)
    MainNavigationController.swift[36], pushViewController: [pushViewController=====><zaozuo.ProductCategoryController: 0x7ffce5147600>]
    (Swift.Void?) $R4 = nil
    
    然后执行:
    (lldb) caflush // e (void)[CATransaction flush]
    或是
    (lldb) c
    都可以
    

    查找按钮的 target(这个方法还未尝试,留在这里开阔下思路)

    想象你在调试器中有一个 $myButton 的变量,可以是创建出来的,也可以是从 UI 上抓取出来的,或者是你停止在断点时的一个局部变量。你想知道,按钮按下的时候谁会接收到按钮发出的 action。非常简单:

    (lldb) po [$myButton allTargets]
    {(
        <MagicEventListener: 0x7fb58bd2e240>
    )}
    (lldb) po [$myButton actionsForTarget:(id)0x7fb58bd2e240 forControlEvent:0]
    <__NSArrayM 0x7fb58bd2aa40>(
    _handleTap:
    )
    

    观察实例变量的变化

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

    首先,我们需要找到 _layer 这个变量在对象上的相对位置:

    (lldb) p (ptrdiff_t)ivar_getOffset((struct Ivar *)class_getInstanceVariable([MyView class], "_layer"))
    (ptrdiff_t) $0 = 8
    现在我们知道 ($myView + 8) 是被写入的内存地址:
    
    (lldb) watchpoint set expression -- (int *)$myView + 8
    Watchpoint created: Watchpoint 3: addr = 0x7fa554231340 size = 8 state = enabled type = w
        new value: 0x0000000000000000
    这被以 wivar $myView _layer 加入到 Chisel 中。
    

    非重写方法的符号断点

    假设你想知道 -[MyViewController viewDidAppear:] 什么时候被调用。如果这个方法并没有在MyViewController 中实现,而是在其父类中实现的,该怎么办呢?试着设置一个断点,会出现以下结果:

    (lldb) b -[MyViewController viewDidAppear:]
    Breakpoint 1: no locations (pending).
    WARNING:  Unable to resolve breakpoint to any actual locations.
    

    因为 LLDB 会查找一个符号,但是实际在这个类上却找不到,所以断点也永远不会触发。你需要做的是为断点设置一个条件 [self isKindOfClass:[MyViewController class]],然后把断点放在 UIViewController 上。正常情况下这样设置一个条件可以正常工作。但是这里不会,因为我们没有父类的实现。

    viewDidAppear: 是苹果实现的方法,因此没有它的符号;在方法内没有 self 。如果想在符号断点上使用 self,你必须知道它在哪里 (它可能在寄存器上,也可能在栈上;在 x86 上,你可以在 $esp+4 找到它)。但是这是很痛苦的,因为现在你必须至少知道四种体系结构 (x86,x86-64,armv7,armv64)。想象你需要花多少时间去学习命令集以及它们每一个的调用约定,然后正确的写一个在你的超类上设置断点并且条件正确的命令。
    幸运的是,这个在 Chisel 被解决了。这被成为

    bmessage:
    (lldb) bmessage -[MyViewController viewDidAppear:]
    Setting a breakpoint at -[UIViewController viewDidAppear:] with condition (void*)object_getClass((id)$rdi) == 0x000000010e2f4d28
    Breakpoint 1: where = UIKit`-[UIViewController viewDidAppear:], address = 0x000000010e11533c
    

    让代码停在Swift Error 或者Objective C异常

    • 停在Objective C异常
    (lldb) br s -E  objc
    Breakpoint 6: where = libobjc.A.dylib`objc_exception_throw, address = 0x000000010dededbb
    
    • 停在Swift Error
    (lldb) br s -E swift
    Breakpoint 7: where = libswiftCore.dylib`swift_willThrow, address = 0x000000010e55ccc0
    
    • 停在某一种类型的Swift Error
    (lldb) br s -E swift -O EnumError
    Breakpoint 8: where = libswiftCore.dylib`swift_willThrow, address = 0x000000010e55
    

    参考
    http://lldb.llvm.org/
    https://lldb.llvm.org/lldb-gdb.html
    https://www.invasivecode.com/weblog/lldb-expression-for-code-injection-in-xcode
    http://www.theappbusiness.com/blog/debugging-in-swift
    iOS 开发者旅途中的指南针 - LLDB 调试技术,swiftcafe
    与调试器共舞 - LLDB 的华尔兹
    Chisel-LLDB命令插件,让调试更Easy
    Chisel常用命令总结
    Swift 代码调试核武-LLDB调试基础
    Basic LLDB tips
    udacity 的视频教程

    结尾

    LLDB 调试命令强大,但是有些语法不大好书写,Chisel 做了封装和一些扩展,使得调试App 不在打印各种log,我们可以快捷的查看或是修改代码内的变量,常量,数组,对象,在断点位置执行log,shell等但这都是调试代码内部信息,如何更加灵活的调试UIView和约束信息呢?

    这就有了下文:

    Reveal 9 iOS 界面调试

  • 相关阅读:
    静态代码块执行时机
    java中的命令
    JDBC
    final, finally, finalize
    面向对象
    线程中常用的一些方法
    线程中的yield()
    线程中的join()
    Thread对象 既传入了Runnable对象又重写了run()方法
    Python 绘制词云
  • 原文地址:https://www.cnblogs.com/buoge/p/9343337.html
Copyright © 2011-2022 走看看