大多数浏览器和
Developer App 均支持流媒体播放。
-
LLDB:不限于“po”
LLDB 是一个功能强大的工具,可用于在运行时浏览和调试您的 app。探索在 app 中显示值的各种方式,如何格式化自定数据类型,以及如何利用自己的 Python 3 脚本来扩展 LLDB。
资源
相关视频
WWDC22
WWDC21
WWDC18
-
下载
(LLDB:不止“PO”) 大家好 我是Davide 我是一名工程师 来自Apple调试技术团队 今天我将与我的同事 Jonas共同进行本次演讲 你们可能很熟悉po了 它是一种 在LLDB中打印变量的方式 今天我们就来谈谈po及其运行方式 我们也会介绍其他方法 来查看源代码中的变量 以及进行格式化输出的机制
LLDB是为Xcode中的 变量视图提供支持的调试工具 你可以看到定义的变量及其类型 在Xcode进行调试时 你可以在窗口右下方 直接发送命令 并通过控制台与LLDB进行交互 它可以你检查app中的错误时 同时打印出定义 源代码的变量值 LLDB可以有多种方式 来完成这一任务 而每种方式都有不同的权衡取舍 我们来看 在这个例子中 假设我们有一个代表Trip的结构 由名和目的站组成 接下来让我们仔细看看
我们要探究的第一个命令是po 你可以把它看作是打印对象描述 当我们运用这个命令时 我们得到的返回就是对象描述 也就是你打印实例的文本表达 系统运行时会提供一个默认值 但也是可以自定义的 我们可以将一致性 加入至CustomDebugStringConvertible 协议中 这需要一种叫 debugDescription的属性 现在如果我们在调试工具中 打印对象描述 我们就可以看到我们规定的描述 而不是默认描述了 此项更改仅影响顶级描述
如果你需要修改子结构 就查看CustomerReflectable 协议的文档 也可以通过 Objective-C对象完成 实现描述方法
但是po能做的不仅仅是打印变量 比如 你可以得到漫游名称 并计算出它的大写版本 或者是按字母排序的漫游目的站 一般来说 它可以评估任何表达方式 所以 任意给定命令提示符中 可以编译的东西 都可以作为参数传递给注释
实际上 po是一个名为表达的命令的别名 含有能够打印对象描述的参数 LECC和LLDB都是让你 不用敲那么多下键盘的简便方式 比如说 你想自己实现po 你就可以用alias命令 将你的命令名称指定为第一个参数 再按照你想进行 alias的命令进行操作 确定好之后 你就可以像LLDB中其他命令一样 使用自己的命令了
现在我们了解了po可以做什么 我们再来深入看看它是怎么运行的 让我们来看看po要传输一个值时 所要经历的步骤 为了让你现在使用的语言 能够完整表达 LLDB不会解析和评估表达本身 相反 它一开始会生成一串源代码 这些代码是由你所给定的表达编译的 跟这里展示的片段很像 之后它会运用嵌入式Swift 并声明编译器 来编译代码 之后会在你 调试程序的context中执行 一旦执行完成 LLDB就要访问结果值
从结果值 你要得到对象描述 为此 LLDB将前一结果 写入另一段源代码中 这也会被编译并在 调试过程的context里执行 执行结果是LLDB 作为po命令的结果 显示的字符串 po只是三种方式中的一种 我们要介绍 如何在LLDB中打印变量 我们再来看看其他方式 第二种在LLDB中打印变量的方式 叫p命令 可以把它想象成是一种 不用对象描述就能够打印的方法 让我们来看看它的输出 要注意的第一点是 它与在po中的表达 略有不同之处 但它们包含的是同样的信息 需要注意的第二点 是结果值已被命名为$R0 这是LLDB中的特殊约定 每个表达的结果都要被赋予增值名称 比如$R1和$R2 这些名称在LLDB 之后的表达中要用到 你可以参考$R0 与你的项目中其他变量西欧昂通 比如说 你可以打印损坏字段 与po类似 P在LLDB中也并不是顶级命令 它只是表达命令的别名 但是后面不用加--对象描述 就像刚刚的po一样 我们具体来看p是如何运作的
因为p不需要获取表述 它也就没有那么复杂 你可以从之前我对po的介绍中 回忆起这个图 实际上 第一部分 编译和评估表达 对于这两种命令是完全一样的 但当它得到结果后 LLDB会执行一项 叫动态类型解析的步骤 说得更具体一些 为了做到这一点 我们要对例子进行一些修改 我们来看看是怎么做的 我们更改Trip结构 以符合协议名称活动 在Swift中 源代码中的静态表达 和运行时的动态类型 不一定是相同的 例如 变量可声明使用该种类型协议 在该例中 变量是静态的漫游活动 但在运行时 该变量又会具有Trip类型实例 既动态
如果我们打印漫游值 我们会得到Trip类型对象 因为LLDB重新生成了结果元数据 在给定的程序点显示 给定变量的最准确类型 这也就是我们所说的动态类型解析 在p命令里 动态类型解析仅会对表达结果执行 假设我们要访问一个漫游字段 当LLDB想通过p 对该表达进行评估时 我们看到漫游是一个活动类型对象 也没有叫name的成员 出现错误 评估失败 原因是 如果你还记得的话 LLDB在p命令运行位置编译代码 且唯一可识别的代码类型 就是你源代码中的类型 也就是静态类型 如果你在源代码中打出 cruise.name的 命令也是一样 静态编译器会报错并拒绝执行 如果你想评估表达且不被报错 首先你需要将对象转化为其动态类型 然后再访问结果字段 这在调试工具和你的源代码中都一样
到这里p命令还没有结束 在结果中执行动态解析后 LLDB将生成的对象传递给 格式化程序子系统 这也是LLDB要做的事 打印出人能够看懂的对象描述 让我们来细看
为了展示格式化是如何进行的 我将展示它的输入和输出 如果没有格式化 字符串就会是这样的 如果你想自己尝试 你可以给p命令传递--原始选项 标准库类型 即使是像字符串和整数 这样简单的类型 都有复杂的表达 因为它们针对速度和大小 进行了高度优化
在进行格式化操作之后 字符串看起来就如你所想 一串字母 LLDB了解许多常用类型 也可以将它们格式化 你还可以自己编写自定义格式化程序 我们简单说说 我们刚刚介绍了po和p 现在来介绍LLDB中 打印变量的第三种方式 v命令
v命令的输出 和p是完全一样的 因为它靠的也是 我们刚才说过的格式化程序
像其他两个命令一样 v也是我们在Xcode 10.2 介绍过的 帧变量命令的一个别名 但他不像刚刚两个命令 v命令并不编译和执行代码 所以它非常快 由于v命令不编译代码 它有自己的语法 它的语法可能和你在调试中 所使用的语言并不相同 例如 它用点和下标符 来访问字段 但他在解析时并不会过载运行 也无法评估计算属性 由于它要执行代码 你可以用p或po 你可能也想到了 v命令与其他两种打印变量的 运行方式大相径庭 我们详细来看 在图中 这是v命令 我们要打印变量 为此 v命令会首先查询程序状态 以在内存中定位变量 之后 它在内存中读取变量值 然后对其进行动态类型解析 如果用户要求访问子字段 它会在子字段中重复上述步骤 每次都会进行动态变量解析 完成之后 它就会传递数据格式化子系统的结果 v命令可能多次进行 动态类型解析 这是我们需要记住的一点 也是p命令和v命令运行中 非常不同的一点 格式化程序只执行一次动态类型解析 我们来看这些情况
回到我们的例子 p命令无法访问漫游成员的地方 通过在解释的每个步骤中 执行动态类型解析 v命令知道漫游是 Trip类型的一个对象 能够从内存中访问它的字段
这是它比P命令要好的一点 它可以让你查看 p命令没有转化的类型 我们说完了这三种 在LLDB中打印变量的方式
我们来回顾一下 以及对比po、p和v命令的不同 要说的第一点是 对象是如何呈现的 po命令运用的是对象描述 而p和v命令 运用的是格式化程序来呈现对象 我们也要记住计算结果的方式 po和p都会编译表达 并且可以访问完整的语言 而v命令有自己的语法 解释和表达 并在解释的每步中 都会进行动态类型解析
我们刚才也提过 LLDB格式化是可以自定义的 接下来我们也会详细讲到 有请我的同事Jonas 大家好 我是Jonas 我也是调试技术团队的一名工程师 在LLDB中 在调试工具中的数据格式化程序 定义了变量的显示方式
建立格式化程序有几种常见的类型 在实例中 当我们运用v命令时 我们可以打印漫游的目的地 以及以可读形式显示的数组元素 通常 默认的格式化程序在 用户定义类型 和标准库中的类型里都能运行 但有时 你可能要调整 现有的格式化程序 或者自己找一个
你也可以做到 因为LLDB中的数据格式化程序 是完全可扩展的 每种类型都有自己的表达 为了帮助你自定义这些表达 LLDB会提供过滤器 摘要和合成子项
过滤器用于限制 现有格式化程序的输出 现在我们的Trip只有几个目的地 但随着数量的增加 输出可能变得很乱 通过过滤器 我们就能指定只显示Trip名称 这不仅只影响控制台 格式化程序的输出 同时也影响Xcode中的变量 在我们继续之前先把过滤器去掉
摘要能够提供一种类型的字符串表达 为了一目了然地显示 有关该类型的信息 他们的数据格式化程序 和你在用po的时候是一样的 加上过滤器之后 它也可以影响Xcode中 显示的变量 Trip种的所有成员都是有摘要的 但Trip本身是没有的 我们改一下
一个好的摘要应该是 最初和最终的目的地 摘要字符串包括常规文本 和特殊变量 这些内容能够访问正在打印的字段 该变量以美元标志开始 用花括号括起来 它们的语法和v命令一致 摘要定义的当前类型 以var形式进行访问 该摘要以var.name 来访问Trip名称 以var.destinations 来访问目的地 但这样的摘要有一个问题 它只有在包含这三种特定目的地的 Trip中才能运行 因为格式化程序无法访问计算变量 比如一个数组 我们必须对最后一个元素的 索引进行硬编码
幸运的是 我们还有其他有用的工具 我们可以在Python里定义摘要 Python的格式化程序 可以进行任意计算 而且它可以完全访问 LLDB的脚本桥API 还可以提供用于 访问当前调试会话状态的对象
目标是当前正在调试的程序 进程 线程和框架 都为对应运行时信息提供访问
变量 寄存器和表达的值 都由SB值类进行表示 这对数据格式化程序来说十分有趣 因为其可以用来定位 各种类型及对应的值 更多详细信息请查看在线文档
从Xcode 11开始 脚本使用的是Python 3 如果你用的是 现有Python 2脚本 请查看Xcode的发行说明 上面有如何转换到 Python 3的详细信息
让我们来探究LDB API 执行脚本命令 将其放入交互式 Python解释器中 lldb.frame变量 可以访问当前框架 返回为一个SBFrame实例 我们知道当前框架 包含一个叫漫游的变量 因此我们可以用 FindVariable来获取其SB值 由于这些对象为 数据格式化程序提供支持 所以将它们打印出来 看起来与其对应的 数据格式化程序相同也就不足为奇了
我们也知道该漫游有一个叫 目的地的成员 我们可以通过调用 GetChildMemberWithName来访问它 结果是另一个表示目标数组的SB值
让我们用Python 来模拟之前的格式化程序 这一次最后一个元素的索引 不需要进行硬编码 我们可以在SB值目的地 使用GetNumChildren 来获取元素数量 通过GetChildAtIndex 我们可以访问第一和最后一个元素 请注意 打印值 是context相关的 它们包含了数组中的索引 SB值实例维持着 其父关系的context
现在 我们可以将所有东西 都一起放进一个字符串中 然而结果并不是我们想要的 通过打印开始和结束 我们获取到了SB值对象的描述 但是我们想要的是它们的摘要 我们可以使用GetSummary 来检索格式化之后的值 并使用它们 现在的结果 只有目标字符串本身
现在我们把所有都综合到一起 格式化程序可以在 调试器控制台中直接定义 或者你可以创建一个文件 并将其加载进LLDB内 我们创建一个 叫Trip.py的文件 当在文件中定义提供程序时 无需使用当前框架 来访问我们想要显示的变量 SB值会作为输入参数传递给函数 接下来的步骤 就和之前的类似 用Python来定义 格式化程序的另一个优点是 我们有控制流程 如果该Trip没有目的地 我们就可以打印为空Trip 我们可以获取第一和最后一个目的地 并返回为摘要字符串
现在 我们要将新的摘要提供程序 加载到LLDB中 可以使用脚本导入命令完成 接下来 我们要指定新的格式化程序为 之前用于Trip类型的那个 使用类型摘要添加 并提供要格式化的类型 及要使用的提供程序函数 在这里 使用完全限定类型非常重要 一切都搞定之后 v命令会用Python 摘要提供程序来打印漫游对象
该摘要不仅会在控制台显示 也会也显示在Xcode变量视图中
我们讲到了过滤器和摘要 最后一种自定义类型显示的方法 就是将合成子项 这可以使你自定义类型的子项 例如当你在Xcode中 输入变量视图时 在Python中 各子项都有一个 SB值 并且都有自己的摘要
定义自己的合成子项提供程序 与定义摘要提供程序类似 但是我们不定义函数 而是定义特定实现方法的类 除了init之外 你还提供了获取子项总数的方法 合成子项本身 及给定名称的索引 此期的资源链接中有完整示例
和刚才一样 使用脚本导入命令将Python 源代码加载到LLDB中 在这个例子中 我们在刚刚已经加载了过滤器 再次运行该命令会重新加载文件
指定用于合成子项的格式化程序 运用类型合成添加 并提供要格式化的类型和要使用的类
在定义好我们自定义的提供程序后 我们不希望在 调试会话结束时丢失它们 你在控制台的所有命令 可以永久保存在主目录中的 .lldbinit文件中 在调试会话开始时 该文件会自动加载
LLDB具有多种功能 可助你在调试时查看程序的状态 运用v、p还是po命令来打印变量 取决于你是否需要只显示其值 要执行命令还是获取对象描是 自定义或定义 你自己的数据格式化程序 可以使用过滤器 摘要和合成子项 最后 如果你用的是 Python 2写的脚本 请更新 以便与Python 3兼容 LLDB使用的版本 是从Xcode 11开始的
欲知更多信息 请登录developer.apple.com 查看本次演讲
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。