[关闭]
@buoge 2017-09-14T19:53:08.000000Z 字数 5544 阅读 2357

Swift 使用 LLDB 调试命令

iOS


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

流程控制

image_1bpvqsf71os1t5e64h16mm4lm9.png-61.6kB

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

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

  1. 程序中: testThreadReturn = testThreadReturn
  2. (lldb) p testThreadReturn
  3. (String) $R2 = "testTheardReturn"
  4. (lldb) e testThreadReturn = "zaozuo"
  5. (lldb) p testThreadReturn
  6. (String) $R4 = "zaozuo"

动态修改view的属性

image_1bpvvlkmfdnjgq0t4toj41fodm.png-45.4kB

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

  1. (lldb) thread return "zaozuo-return"
  2. 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中初始化值,动态赋值,执行多行语句

  1. (lldb) e let hello = "Hello"
  2. you can simply use:
  3. (lldb) p let hello = "Hello"
  1. OC中为:
  2. (lldb) e NSString *$a = @"c" (lldb) po $a c
  3. Swift中变为:
  4. (lldb) e let $a = "a"
  5. (lldb) po $a "a"
  1. (lldb) p
  2. Enter expressions, then terminate with an empty line to evaluate:
  3. struct compass{var direction = "N"; var angle = 16.5}
  4. var c = compass()
  5. print(c)
  6. (lldb)
  1. (lldb) p
  2. Enter expressions, then terminate with an empty line to evaluate:
  3. 1 let layer = CALayer()
  4. 2 layer.backgroundColor = UIColor.yellow.cgColor
  5. 3 layer.bounds = CGRect(x:0, y:0, width:100, height:100)
  6. 4 layer.position = CGPoint(x:250, y:300)
  7. 5 layer.cornerRadius = 12.0
  8. 6 view.layer.addSublayer(layer)
  9. 7
  10. (lldb)

image_1bq027mrc1hea136n116b1vj617m91a.png-18.3kB

引入模块 Importing modules

(lldb) p import MapKit

打印UI层级关系及更新UI

  1. (lldb) po [[[UIApplication sharedApplication] keyWindow] recursiveDescription]
  2. <UIWindow: 0x7ffcd2f0f1e0; frame = (0 0; 375 667); gestureRecognizers = <NSArray: 0x7ffcd2f10170>; layer = <UIWindowLayer: 0x7ffcd2f0ea80>>
  3. | <UIView: 0x7ffcd2c6dc10; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7ffcd2c17f10>>
  4. | | <UIButton: 0x7ffcd2c6dfc0; frame = (20 62; 78 30); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x7ffcd2c6bc10>>
  5. | | | <UIButtonLabel: 0x7ffcd2f15af0; frame = (16 6; 46 18); text = 'Button'; opaque = NO; userInteractionEnabled = NO; layer = <_UILabelLayer: 0x7ffcd2f16120>>
  6. | | <_UILayoutGuide: 0x7ffcd2c6fae0; frame = (0 0; 0 20); hidden = YES; layer = <CALayer: 0x7ffcd2c6faa0>>
  7. | | <_UILayoutGuide: 0x7ffcd2c70740; frame = (0 667; 0 0); hidden = YES; layer = <CALayer: 0x7ffcd2c6d3d0>>
  1. expr -l objc++ -O -- [[[UIWindow keyWindow] rootViewController] _printHierarchy]
  1. (lldb) e id $view = (id) 0x7fbd71432590
  1. (lldb) e (void) [$view setBackgroundColor:[UIColor redColor]]
  1. (lldb) e (void)[CATransaction flush]

Push 一个 View Controller

  1. (lldb) e navigationController?.pushViewController($vcc, animated: true)
  2. MainNavigationController.swift[36], pushViewController: [pushViewController=====><zaozuo.ProductCategoryController: 0x7ffce5147600>]
  3. (Swift.Void?) $R4 = nil
  4. 然后执行:
  5. (lldb) caflush // e (void)[CATransaction flush]
  6. 或是
  7. (lldb) c
  8. 都可以

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

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

  1. (lldb) po [$myButton allTargets]
  2. {(
  3. <MagicEventListener: 0x7fb58bd2e240>
  4. )}
  5. (lldb) po [$myButton actionsForTarget:(id)0x7fb58bd2e240 forControlEvent:0]
  6. <__NSArrayM 0x7fb58bd2aa40>(
  7. _handleTap:
  8. )

观察实例变量的变化

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

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

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

非重写方法的符号断点

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

  1. (lldb) b -[MyViewController viewDidAppear:]
  2. Breakpoint 1: no locations (pending).
  3. 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 被解决了。这被成为

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

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

  1. (lldb) br s -E objc
  2. Breakpoint 6: where = libobjc.A.dylib`objc_exception_throw, address = 0x000000010dededbb
  1. (lldb) br s -E swift
  2. Breakpoint 7: where = libswiftCore.dylib`swift_willThrow, address = 0x000000010e55ccc0
  1. (lldb) br s -E swift -O EnumError
  2. 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 界面调试

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注