A7a6f385196eee6812bd1ab97f7a7850
Session 429 Beyond po

导读

session video link:LLDB: Beyond "po"

阅读时长:10分钟

难度:易

环境:10.15 Beta ,Xcode11 Beta

微博:NinthDay

概要:LLDB是一个强大的工具,可以在运行时调试应用程序。Session 429 将帮助你了解在应用程序中显示值的各种方法,如何格式化自定义数据类型,以及如何使用自定义的Python 3脚本扩展LLDB;除了上述调试值的技巧之外,本文还会额外做一些lldb的扩展阅读,有兴趣的读者可跳至扩展阅读章节点击阅读。

popv 三大命令

日常开发调试过程中,不可避免地会遇到打印变量值的情况,最简单的方式莫过于使用 NSLogprint 方法打印值。本文以一个乘船游览的例子开篇,使用print调试输出变量值如下:

po命令

首先介绍 lldb 最常用的 po 命令,在终端调试界面键入如下命令打印 cruise 对象值:

如果想要输出结果中更为详细地显示 Trip 对象的描述信息,可以通过让 Trip 类实现 CustomDebugStringConvertible 协议,可自定义debugDescription 返回字符串值,代码实现如下图:

注意红色框标识的信息已变更为自定义调试信息。除此之外,Swift 还提供了 CustomReflectable 反射协议帮助我们进一步定制输出信息。实现代码如下:

Mirror 中的 children 是个字典类型,我们以key-value形式输出对象信息,注意我故意只输出了 destinations[0] 和 destination[1] ,所以 po 输出也仅限于此,更多请见 CustomReflectable 协议说明。如果你想要了解 Mirror 基础知识和使用姿势,可以点击这篇文章了解。

lldb 调试中, po 命令不仅限直接输出对象的内容,还可以做一些操作后再进行输出,比如对cruise对象的 name 字符串进行大写变换,以及对 destinations 数组进行排序:

如上图所示,为什么 po 命令可以输出一个表达式的结果值?这就要说说它的本质了。po 实际上是 expression --object-description 的缩写,你可以直接在 lldb 调试工具用完整命令去输出 cruise 对象:

(lldb) expression --object-description -- cruise
"Mediterranean Cruise"

如果你乐意,同样可以为它再取个别名:

(lldb) command alias my_po expression --object-description
(lldb) my_po cruise

po 命令的本质

其中 Create compilable code 阶段就是将要打印的 view 对象赋值给 __lldb_res

func __lldb_expr(){
        __lldb_res = view
}

Create code to access description 阶段调用实例对象的 description 方法:

func __lldb_expr2 -> String() {
  return __lldb_res.description
}

p命令

po 命令顾名思义就是打印对象信息,而 p 命令则使用范围更广一些,接着上面的例子讲解。

其中 $R0 寄存器存储了 cruise 实例,所以我们也可通过 $R0来访问 cruise 实例对象属性,e.g. p $R0.destinations

p 命令的本质

v 命令

pop 都是经过了数据格式化后输出信息,如果想要数据以"原始"形态展现,那么我们就需要使用 expression 命令了。

再用 p cruise 对比下输出:

上述是日常开发中频繁被用到的几个命令,那么v命令就相对"冷门"一些,它是 frame variable 的别名,使用姿势如下:

看到这,大部分读者应该会有这样的疑问:v 命令和 po 命令输出几乎一模一样,它有啥用?

我们通过一个简单Demo来回答这个疑问。如下图所示,我们声明了一个 Activity 协议,同时定义一个 Trip 类遵循它,注意 cruise 变量类型为 Activity,而非 Trip,尝试使用 pov命令打印name属性值:

而输入 po cruise.name 则会报错!

(lldb) po cruise.name
error: <EXPR>:3:1: error: value of type 'Activity' has no member 'name'
cruise.name
^~~~~~ ~~~~

两者的区别主要还是在实现上,如下图"v"命令的本质,中间有一环是 Dynamic type resolution 帮我们处理动态对象的问题。

自定义数据格式器

自定义数据格式器有如下几种:数据过滤器(Filter)、对象概述(String summaries),数组对象输出(Synthetic children)以及自定义 Python Formatter。

数据过滤器(Filter)

日常开发中我们想要 po 一个对象,但是想要过滤其中的一部分,这时候就需要一个 filter 过滤器了。

这里我们为 Travel.Trip 类添加了一个 filter,仅输出Trip类的属性 name 变量值。如果想要移除过滤器,调用如下命令即可:

(lldb) type filter delete Travel.Trip

对象概述(String summaries)

这里说的对象概述有三种:

  • String representation
  • Xcode 面板显示的变量值
  • 其他
top Created with Sketch.