大多数浏览器和
Developer App 均支持流媒体播放。
-
符号化:超越基础功能
探索如何通过您的 App 实现最高性能和明智调试。符号化是 Instruments 和 LLDB 等工具的中心,旨在帮助衔接应用程序运行时和源代码之间的层。了解此过程如何工作,以及您可以采取哪些步骤来最深入地了解 App。
资源
相关视频
WWDC22
-
下载
♪低音音乐播放♪ ♪ 亚雷汉德罗卢瑟纳:哈啰 各位 谢谢大家加入这个关于符号化的课程 尽管符号化听起来是个很模糊的术语 我们会认识它如何能帮助你 快速地辨识出程序错误、崩溃 和性能瓶颈的根本原因 我们会获得关于符号化如何运作 更深入的直观 以及介绍几项可以帮助你的工具 让大家可以直接跟着使用 过程中 我们也会讨论到 几种调试信息的来源 它们对于建立丰富的符号化体验 是必要的 以及大家能如何配置你的app 来好好利用这些信息 让我们先来熟悉一下 符号化的具体定义和范例 基本上 符号化是一种机制 将我们的装置在运行时 对应用程序的看法… 这里指的是内存地址和指令 转换或转译为 我们作为开发者对app的看法 这里是指函数、名称和文件 少了这个桥接层 它会将诊断程序错误异常地复杂化 即便只是几行编码 做为范例 让我们来研究 这个Swift编码 这里我有一个函数 generateMagicNumber 它会从一系列的候选数字中 选出一个特定的数字 要做这件事 我们要先把 numberChoices调用进来 那会回传一个由十个随机产生的数字 所组成的数组 接着我们会把那个数组传送到 selectMagicNumber 那会在特定的索引上回传数字 这听起来是个很合理的程序 不过当我第一次执行它时 我遇到程序崩溃 我做的第一个求助是检查崩溃日志… 这根本没有用 从线程的堆栈追踪中我只能知道 我的MagicNumbers app 在某个地址崩溃了 多谢了 这我早就知道了 我完全不知道 哪些缓存器涉及到这个错误 我可以尝试用调试程序 一步步去检查app 并且找出崩溃 可是万一这只发生在 某个我无法复制的特定环境下呢? 这种状况下 使用调试程序 不见得能帮我查出问题 或者我可以试着去看反组译 不过那更难搞清楚问题了 这绝对不是一个诊断问题的可行方法 更重要的是 有了符号化的帮忙 我们不需要从这个起始点开始调试 Xcode的建立者说我可以 为这个app下载dSYM 它会重新处理崩溃日志 这样做的时候 Xcode会运用符号化的概念 因此我可以用比较好的崩溃日志 来诊断问题 在里面我不只能看到我所有的函数 实际被调用的状况 还可以决定 我要回推到我的编码中的文件和行数 这个更新的崩溃日志让我知道 我们尝试存取超出范围的索引 或者 如果我已经有dSYM 我可以用atos命令来取得同样的信息 回头去看我的编码 我意识到 MAGIC_CHOICE远远超出我们 10元素数组的边界 糟糕 另一个例子中 我有兴趣对我的app 进行性能分析 来带出速度最快的用户体验 这里 Instruments告诉我 这个app在高使用率和低使用率之间 周期性的循环 如果我们想专注在低使用率的周期 Instruments告诉我们 这个app正在将一些内容写入文件中 不过 当我检查高使用率的周期 我得到完全相同的堆栈追踪 这怎么可能? 该不会是执行完全一样的编码? 我们待会看到这个Instruments追踪 只有部分被符号化 举例来说 我没有在堆栈追踪中 看到任何文件名称或行数 可是我在更新的崩溃日志中有看到 因此 它漏掉一些信息 知道这件事后 我可以同样地将我的dSYM 设置在Instruments中 完成之后 我新的Instruments追踪 显示说高使用率区域 确实有在写入文件 不过它们都很明确地 在我留在程序里的 调试编码途径中 低使用率区域会避开它 并且代表我的app在环境中的表现 就如同Xcode利用dSYM来符号化 一个相对信息量不足的崩溃日志 Instruments同样使用dSYM 来强化部分符号化的追踪 以及告诉我这个性能问题真正的原因 尽管这些工具使用符号化 来找出在我的编码中的问题区域 它很自然地会带出几个问题 这些都是怎么运作? 我还能把它应用到其他哪些地方? 这些都是和dSYM有关吗? 要回答这些问题 和开启符号化的潜力 我们需要更深入地去认识它 这可能会让人有点难承受 不过这些都是需要了解的重要概念 有很多能帮助调试和性能分析的工具 都是建立在符号化之上 atos本身已经告诉我们崩溃 确切的根本原因 还有很多其他建立在Xcode中的工具 进一步地说 我们指定像是o、l 和i的标记到atos上 可是它们是什么意思? 我们总是要用同一组标记吗? 如果我们没有其中一个值可以使用? 如同Instruments案例中看到的 大家也能奠定扎实的基础来认识 什么时候和为什么你的堆栈追踪 没有完全符号化 以及怎么修补它 最后 有几个建立设定 是你可以控制来影响 符号化的丰富度的 我们会浏览这些建立设定 让大家对于如何使用它们 产生很深厚的直觉 为了这个目的 我要介绍 符号化的两步骤程序 第一步是回到文件中 而第二步是参考调试信息 我们等一下会看到 回到文件中 就是将执行时期的内存地址 转换和转译成更稳定、更好用的格式 这让我们和我们的调试信息沟通 在原始内存地址和来源编码之间 制造出有意义的连结 让我们先来讨论第一步 回到文件中 这个步骤的最终目的 是将执行时期的内存地址 例如我们看到在原本崩溃日志中那些 转译为磁盘上 二进制文件的相对应地址 就像我们有执行时期地址 你的app和框架 也有一个地址空间在磁盘上! 磁盘地址空间和你的app 在执行时期占据的地址空间不同 而我们需要一个机制来厘清这些差异 首先 我们应该要知道 磁盘地址到底是什么 这些地址是在你建立你的app时 由链接器所指定的 具体来说 链接器将你的二进制文件 聚集成段 每一段都包含相关的数据 而且有一些属性 例如名称、尺寸 和它们被指定的地址 举例来说 你的二进制文件的__TEXT段 包含所有你写入的函数和方法 而__DATA段包含整个程序的状态 例如全局变量 每一个段都会被指定到不同的位置 因此它们不会重叠 链接器会在你执行档的一开头 记录这个信息 做为Mach-O标头的一部分 Mach-O是一种用于 可执行的二进制文件 和函数库的格式 而且系统知道 它必须读取这个标头来执行你的app 再更仔细来看 Mach-O标头包含许多 具有段属性的加载命令 系统利用这些加载命令 将段载入到内存中 要注意如果你的app属于Universal 2 那么你的app在每个架构上 会有一个标头和一组段 我们可以从使用otool -l命令中看到 那会替一个特定的文件 打印出加载命令 这里 我们看到一个段加载命令 等同于LC_SEGMENT_64 这个加载命令说 __TEXT段从vmaddr中的地址开始 而且是vmsize字节长 如果核心遵守这些加载命令 把段载入到内存上 那执行时期和链接器地址之间的差异 究竟是什么? 嗯 在核心实际加载段之前 它会初始化一个随机值 也称为ASLR偏移 接着核心会将ASLR偏移加到 加载命令中的地址上 因此核心不会在地址A加载__TEXT段 和地址B加载__DATA段 而是将它们加载A+S和B+S 其中S是ASLR偏移 因为A+S和B+S是系统使用的真实地址 因此它们也被称为加载地址 考虑到这一点 现在我们知道 执行时期地址 和链接器地址之间的差异 是ASLR偏移 我们可以用下面的方程序 来计算ASLR偏移: S=L-A 其中S是ASLR偏移 L是加载地址 而A是链接器地址 我们很快就会看到这个方程序的范例 不过关键是一旦我们知道ASLR偏移 我们就可以随时回去文件地址空间 ASLR偏移方程序需要两个地址 加载地址和链接器地址 我们该从哪里取得它们? 我们已经看过如何利用otool 查询加载命令来得知链接器地址 要知道执行时期地址 系统会在崩溃时查询你的app 或是在为了取得它的执行时期的 地址空间 通过Instruments来描绘时 这个信息会反映在 在你的崩溃日志中里面的 二进制图像列表中 你也可以用vmmap工具 交互地看到加载地址 这会枚举出你程序中的激活内存区域 让我们从原始崩溃日志 自己来计算ASLR偏移值 在二进制图像列表中 我有__TEXT段的加载地址 当我去看加载命令时 我也有磁盘上 二进制文件的链接器地址 这两个值相减会得到 0x45c000的ASLR偏移值 这代表在我程序中 执行时期 __TEXT段的每个地址 和链接器__TEXT段地址 相距0x45c000字节 因此 要知道崩溃日志中的 堆栈追踪地址 对应到文件中的什么内容 我可以把它减掉0x45c000 来取得地址磁盘 既然这个地址现在是 磁盘地址空间的一部分 我可以检查我的app看里面 存放什么内容 崩溃日志告诉我说 不管这个地址上的是什么 在执行它的时候 一个线程崩溃了 因此我们可以再用otool 来看看这个可疑的指令 这次 我把-tV标记指定到otool上 这样会打印出反组译 注意到我也把架构指定为arm64 因为这个app是根据 Universal 2建立的 这样做otool才知道 要考虑哪一个Mach-O标头和段 otool的输出显示在那个地址上的 brk指令 brk针对app中例外和问题发送信号 像是atos的工具也会利用 我们刚刚讲过的同一个技术 来计算ASLR偏移 atos会替文件读取标示为 -o标记的加载命令 然后我们可以告诉它 具有-l标记的加载地址 就像我刚刚说过的 vmmap也会告诉我们 一个执行中app的加载地址 让我们再试一次这个计算 不过这次我们会用vmmap 而不是二进制图像列表 来决定ASLR偏移 我再次执行MagicNumbers程序 并且在程序崩溃前 得到__TEXT段的加载地址 使用之前的公式 我可以推断这次 ASLR偏移值为0x104d14000 再一次 要回到文件 我们必须减掉ASLR偏移值 如果我从新的崩溃日志的 最顶层入口减掉4d14000 我会得到跟之前完全一样的文件地址 这不是巧合 核心挑了一个不同的ASLR值 因此我们的加载地址 在崩溃日志改变了 然而 我们还是可以判断 负责这个崩溃的文件地址 这里的重点是我们有一个机制 不论我们的app的执行时期地址 我们都能了解它到底在做什么 甚至到指令层面 有了那个定位 我们可以让调试信息来诊断 编译到那些指令上的来源编码 在我们往下之前 我想要回顾一下 我们介绍过的内容和用过的工具 App二进制文件和框架是Mach-O文件 这代表说它们在不同段里面 有相关的内容 这些段是由链接器产生的 Mach-O标头加载命令 描述了这些段的属性 包含地址 我们用有-l标记的otool 来印出加载命令 接着 我们学到核心会将一个随机值 也就是ASLR偏移 加到链接器地址 添加的ASLR偏移和链接器地址 就是加载地址 我们可以在崩溃日志中 检查二进制图像列表 来看崩溃事件中的加载地址 或者我们可以用vmmap 来查看执行中app的加载地址 最后 我们介绍了一些 计算ASLR偏移的范例 来回到文件地址空间 现在我们可以来谈谈调试信息 它包含在文件地址 和来源编码之间的关键链接 当你建立你的app时 Xcode会建立调试信息 并且直接将它嵌入在 你的app二进制文件中 或是将它存成独立的文件 例如dSYM 调试信息有几种类别或类型 每一种会针对特定的文件地址 提供不同程度的细节 今天我们要来看三种不同类型的 调试信息 首先 我们会介绍函数起始点 它本身不会加上太多值 不过这是一个常用的起点 接着我们会来看nlist符号表 它会加上函数和方法的名称 最后 我们会讲DWARF 这是从dSYM和静态函数库来的 DWARF会加上最多的细节 包含文件名称 行数和优化记录 因为DWARF会提供最多的细节 因此只要在允许的状况下 我们就会尽量取得这类型的调试信息 我们会认识每一种类型 以及如何用它们来建立 完全符号化的崩溃日志 让我们从函数起始点开始 大家可以从表格中看到 函数起始点给出最少的来源编码细节 就像它的名字一样 这类型的调试信息只会显示 我们的函数的第一个地址 或者就只是开始的位置 举例来说 它会告诉我们 有一个函数开始和存在于特定的地址 然而 它不会告诉我们哪一个函数 从那个地址开始 只会说它们在那里 函数起始点的调试信息会通过 在你的app的__LINKEDIT段 写入地址列表的编码来做到这件事 因为它是直接嵌入在你的app中 Mach-O标头也有一个加载命令 来通知我们可以在哪里找到它 那是LC_FUNCTION_STARTS 利用符号命令 和-onlyFuncStartsData标记 你自己看到这些 这里我们会得到一串地址 和空值占位符 理想上 这些占位符会有 函数和方法名称 而不是空值 不过函数起始点数据不会提供名称 再强调一次 这不是最叙述性的数据 不过 它确实能稍微更新崩溃日志 现在我们可以将文件地址视为 来自函数的偏移量 举例来说 首先我们减掉ASLR偏移值 回到文件中 接着我们找到能够包含文件地址的 函数起始点值 在这个案例中 只有第一个值 可以包含地址 因为其他所有的值都比地址大 最后 我们可以在这个函数上宣告 我们的是文件地址实际上是 264字节 这对于调试程序是很有用的 因为它们可以知道 这个函数是如何被设定的细节 和哪一个缓存器被修改过了 然而对你来说 这意味着 万一你遇到一个缺少函数名称的 崩溃日志 你大概得处理 这种最低层次的调试信息 这是好消息 因为这代表 还有很多机会能以更好的调试信息 来提升崩溃日志 我们想看到下一个层次的细节 理所当然会是函数名称 这是我们第一个真正有机会 用崩溃日志或Instruments追踪 来找出我们来源编码中的问题 这把我引导到nlist符号表 符号表建立在函数起始点的概念上 同样会在__LINKEDIT段中 编码一系列的信息 而且它也有自己的加载命令 然而 它不只是编码地址 它还编码了C结构 相较于函数起始点 这让我们在任何特定入口 都能加入更多细节 具体地说 它们会编码nlist_64结构 这边我们有那个结构的定义 大致看上去 上面显示我们可以 取用名称和几个属性 这些结构字段的值 是由nlist的n_type决定的 我们感兴趣的主要n_type一共有三种 不过目前我们会先专注于其中两种 第一个是所谓的直接符号 这些是你在你的app和框架里面 已经完全定义的函数和方法 直接符号在nlist_64结构中 有一个名称和地址 此外 它们是由在n_type字段内的 特定位元型样来呈现的 具体地说 n_type会有第二、第三 和第四 最低有效位元集合 这些位元也就是N_SECT 我们可以用nm查看这些 并且指定--定义的-only 和--numeric-sort标记 这里 nm详细说明了 MagicNumbers程序中定义的符号 并且依照地址顺序列出它们 我们得到的名称是加密的 那是因为这些实际上储存在 符号表中的名称 是修饰的名称 这些修饰的名称能 帮助编译器和链接器 独一无二地辨识出函数 不过它们不容易理解 除非它们经过编译还原 要取得比较浅显易懂的名称 我将输出传送给 swift-demangle还原函数 现在我们有比较熟悉的名称了 例如主函数和numberChoices 因为这些是直接从我的app中定义的 同样地 符号工具有一个选项 来显示NList数据 而且它也会自动编译还原这些名称 现在我们可以将函数名称 连结到地址上 这让我们再一次更新崩溃日志 这里 我们可以观察到 我们从函数起始点数据取得的 偏移量表达式 同样符合直接符号中的入口 而且那个入口有一个名称 把这两个放在一起 我们可以说我们的崩溃发生在 进入主函数的264字节位置 尽管如此 还是有一些 我们想得到的细节 因为我们很清楚知道 主函数不是唯一参与的函数 而且能有确切的行数也会很有用 我们在Instruments追踪的范例中 有遇到类似的状况 我们有一些可用的函数名称 可是它还是缺少其他的名称 其中一个原因是符号表 只有参与在连结的函数的 直接符号入口 这些是你从你的框架输出 并且在模块和函数之间使用的函数 这让它很适合用来辨识API边界 这也代表它具有 利用像是dlsym和dladdr这些函数 驱动动态加载的必要数据 不过有一个缺点是 区域或静态函数 不会呈现在符号表中 因为它们没有参照到它们外面的模块 这会导致省略那些在app逻辑中 占了很大一部分的实作函数 更进一步来说 在释出版本中建立的二进制文件 通常都会裁剪它们的符号表 意思是说不必要的入口 会从符号表上被移除 这能帮你减少app的尺寸 不过大家想一下 我们app的主要驱动程序 到处去输出功能 这不太寻常 这样我们就是浪费空间 来保留这些符号表 在我们的框架和函数库中 当然我们有客户应该拿来使用的 输出的函数 不过却没有必要 留下区域共享的函数 因为它们不会被用在其他地方 裁剪我们主要的app执行档 会让符号表几乎随时是空的 裁剪我们的框架函数库 只留下输出的函数 你可能已经用过Xcode中的建立设定 例如Strip Linked Product Strip Style和Strip Swift Symbols 这些建立设定控制在建立的时候 你的app是如何被裁剪的 如果开启Strip Linked Product 那么二进制文件 就会根据Strip Style来裁剪 举例来说 所有符号都会执行 最具侵入性的移除 只留下最基本的必要内容 非全局会移除 用在你的app不同模块中 但是不是输出给其他app使用的 直接符号 调试符号会移除nlist的第三种类型 我们待会在讲DWARF的时候 会讨论到这部分 然而 这个Strip Style 的确会保留直接符号 举例来说 这里我有一个框架 它会定义两个公用界面 和一个内部共享的实作函数 因为这些函数全部都参与在连结中 它们都有直接符号的入口 如果我裁剪了非全局 那我就只剩下我的界面 共享实作函数只能用在我的框架中 所以它不算是全局 同样地 裁剪全部的符号 还是会留下界面 因为这些界面对于其他app 要使用框架来说 是必要的 你也可以注意到在符号 --onlyNListData输出里面 有函数起始点地址 穿插在直接符号之间 这些地址代表从来不在直接符号 或者没有被裁剪的函数 你可以让这些裁剪设定 符合你想要的符号表可见性的程度 有了这些信息 我们可以决定 我们什么时候要使用直接符号 它的一些警报信号是有函数名称 但是没有行数或文件名称 或者是混合函数名称 和函数起始点地址 就像我们这里的框架范例一样 我们要分析的第二种nlist结构 是所谓的动态符号 和直接符号相反 这是在n_type只符合N_EXT 位元型样的时候 这些是你从其他框架和函数库 使用的函数和方法 例如打印 你可以用nm来查看这些 只是这次我们会指定 --un定义的-only 而不是--定义的-only 我们也会加上一个-m标记 它会显示这些函数应该在 哪个框架或函数库中被找到 举例来说 MagicNumbers app 依赖各种从libSwiftCore定义的 Swift函数 现在我们介绍了三种调试信息的 其中两种 我们要确定我们知道它们的属性 函数起始点是一系列的地址 所以它们缺少名称 不过可以让我们推断出偏移量 nlist符号表编码整个信息的结构 而且可以将名称连结到地址上 它们描述了那些直接在你的app中 被定义的直接符号 以及由相依性提供的 动态符号 直接符号通常会保留给 参与连结的函数 而裁剪建立设定会影响 哪一个直接符号可以使用 最后 函数起始点 和nlist符号表都是 直接嵌入在你的app中 我们还没讲到的是细节更丰富的层次 例如文件名称和行数 这是DWARF提供给我们的 DWARF将nlist符号表的概念 带到一个完全不同的层次 DWARF不只是保留函数的子集 而是努力描述所有的内容 我们看到相较于函数起始点 nlist符号表加入了更大量的信息 它是通过增加维度来做到这件事的 要记得 当我们在看函数起始点时 我们是从单一维度开始的 也就是地址 接着我们通过编码一个 充满nlist符号表信息的结构 提升到二维 DWARF加入了第三个维度 那是关于关系 DWARF会辨认出不是孤立的函数 它们会调用函数 它们有参数 回传有意义的数据 而且是定义在特定的文件中 编码这些关系 开启了符号化最强大的层面 当我们在分析DWARF时 我们在说的主要是dSYM包裹 除了其他元数据外 例如plists dSYM包裹包含一个 有DWARF的二进制文件 是什么让这个二进制文件这么特别? 二进制文件将它的数据放在一个 特殊的DWARF段中 DWARF规格提到三个位于 我们关注的段里面的数据流 debug_info包含原始文件数据 debug_abbrev将结构指定到数据上 以及debug_line包含文件名称和行数 DWARF还定义了两个词汇类型 编译单位和子程序 我们会先研究这两种 晚一点再介绍第三种 编译单位代表一个参与在建立产物的 单一源文件 例如 你可以预期在我们项目的 每一个Swift文件中 都有一个编译单位 DWARF将属性指定到编译单位上 例如文件的名称、软件开发工具包 它的函数占据的__TEXT段的部分 其他还有很多 main.swift编译单位将这些属性 放在左边的debug_info串流内 而它在右边的调试_abbrev串流内 有一个相对应的入口 告诉我们这些值代表什么 这里我们会看到文件名称 它写入的语言 以及代表__TEXT段范围的低/高组合 子程序代表一个定义的函数 我们已经在nlist符号表中 看到定义的函数 不过子程序还可以描述 静态和区域函数 子程序也有名称 和它的__TEXT段地址范围 一个在编译单位和子程序之间 基本的关系是 子程序是在编译单位中被定义的 DWARF以树状结构呈现这部分 编译单位位于树状图的根部 它有一些子程序入口做为产物 跟随它们的地址范围 可以搜寻到这些产物 我们可以用dwarfdump命令 来检视更详细的内容 首先 我们要看编译单位 它符合一些我前面提到过的 编译单位的属性 dwarfdump很有效的结合 debug_info和debug_abbrev内容 来显示在你的dSYM中数据的 结构和内容 如果我们把输出往下滑 我们会遇到一个子程序产物 它占据的地址范围 是在编译单位的边界内 我们也可以看到函数的名称 我说过DWARF会以极致的细节 来描述它的数据 我们不会花太多时间来讲全部的细节 不过我认为像是函数参数 这样的细节很有趣 它们有自己的词汇类型 那会描述名称和参数的类型 跟着树状模型 参数是一个子程序的产物 这里可以看到由我们提供给函数的 选择参数的入口 接着 会从debug_line串流 得到文件名称和行数 这个串流没有树状结构 而是会定义一个行表程序 在这里个别的文件地址 可以对照回确切的编码行 这样产生一系列的来源编码细节 让我们用来搜寻到文件和编码行 如果我们分析debug_info树状图 并且产出debug_line清单 我们会得到像这样的结构 因此如果想要比对出文件地址 我们可以穿越这个树状图 首先 我们从编译单位开始 然后跟着分支 接着挑出所有符合的debug_line入口 我们可以再用atos让它自动化 不过这次我要明确地排除-i标记 有注意到什么奇怪的地方吗? 嗯 我们有函数名称和行数 我们当然要用DWARF 除此之外 它和nlist符号表更新 没有太大的差异 事实上 把它跟我们 第一次用atos做比较 它依旧看起来像是 我们少了很多珍贵的函数和细节 这里发生了什么事? 唯一改变的是这次我们没有 把-i指定到atos上 那个标记代表“内嵌函数” 内嵌是编译器执行的 常规优化程序 它牵扯到以函数主体 直接取代函数调用 它的一个很酷的效果 是让编码看起来消失了 我们可以把它想成 不是调用numberChoices 而是numberChoices的整体编码 被放进来了 突然间 没有numberChoices的 函数调用了! DWARF以一个内嵌子程序来呈现它 这是我们今天会讨论的第三个 也是最后一个DWARF词汇类型 内嵌子程序是一个子程序 所以它是一个 内嵌到另一个子程序的函数 因为内嵌函数在关系树状图中 完全被另一个节点吞没 因此内嵌子程序是那个节点的产物 这个定义也可以递归地套用 意思是内嵌子程序 可以有另一个内嵌产物 再强调一次 有了dwarfdump 我们可以寻求内嵌子程序 它们被列成其他节点的产物 而且具有和子程序相似的属性 例如名称和地址 然而 在DWARF中 这些属性 经常通过共同节点来存取 也就是抽象源头 如果某个特定函数有许多内嵌副本 那么它们的共同、共享属性 就会留在抽象源头内 如此一来它们不会到处被复制 内嵌子程序有一个 独特的属性是调用位置 这是在来源编码中 我们写入真正的函数调用 不过被优化器取代的位置 例如在这里 我们调用 main.swift文件 第36行上的generateANumber 这让我们用新的子节点 来更新我们的树状图 现在 这看起来是我们程序的 一个更容易理解的图示 内嵌函数的优化细节 是让我们得到完全符号化的 崩溃日志的关键细节 atos的-i标记会指示工具 在符号化期间考虑它们 它们也是在我们的 Instruments追踪中 遗漏的细节 我们之所以在Instruments 和崩溃日志都需要dSYM的原因 就是要让我们取出这些所有的内容 还有一个你可以找到DWARF的来源 那就是从静态函数库和目标文件中 在没有dSYM的状况下 你依然可以替你从静态函数库 或是目标文件链接到的函数 聚集DWARF 在这些案例中 你会找到 调试符号nlist类型 这是其中一种可以被裁剪的符号类型 不过它们本身不具有DWARF 而是它们会将一个函数 连结回它们原本的文件中 如果这个函数库是以调试信息建立的 那么nlist入口可以把我们 带去那个DWARF 这种类型的nlist入口 因为有dsymutil --dump-debug-map 看起来很冗长 这里我们有一个不同函数的清单 和它们是从哪里取得的 我们可以为DWARF 扫描和处理那些位置 总而言之 DWARF是一个深入的符号化数据的 虚拟来源 DWARF揭露在函数和文件之间的 重要关系 像是函数内嵌的优化 对于符号化的质量有很大的影响 而且DWARF可以很清楚的表达它 我们也看到dSYM和静态函数库 包含DWARF 不过 请尽量选择dSYM 因为你可以轻易地将它们 转移到其他位置 而且有来自几个工具的内建支持 最后 我想要分享一些你可以用来 辅助符号化的工具和诀窍 对于区域开发建立 如果你在调试模式下建立 通常你可以有大量的调试信息 对于释出版本 通过检查 调试信息格式建立设定 你可以确保Xcode产生一个dSYM 要确认释出被设定成 DWARF以及dSYM文件 对于提交到App Store的app 你可以通过App Store Connect 来下载你的dSYM 这也包括任何启用位元编码的app 如果你想检查某个dSYM 是否已经在你的装置上 你可以用mdfind命令 这里的字母数字字符串是你的 二进制文件的UUID 它是一个在加载命令中被定义的 独特标识符 你可以看到你的dSYM的UUID 具有符号-uuid 有时候 工具链会产生无效的DWARF 你可以用dwarfdump --verify 来检查它 如果你看到任何回报的错误 麻烦提交程序错误! DWARF数据有一个每个二进制文件 最大是四千兆位组的限制 如果你在dSYM遇到一些问题 看到它们超过四千兆位组 考虑将项目拆成不同的组件 这样每一个本身都会有比较小的dSYM 通过比较UUID 你可以确保你使用的dSYM 符合你的app中你感兴趣的特定构建 app的UUID位在崩溃报告的 二进制图像列表部分 你也可以用符号命令来查看它 你应该要确保你的app和你的dSYM 都有相同的UUID 符号工具也让你检查 你的app可以使用的调试信息类型 我们已经看过这个的范例了 不过这些告诉你信息来源 位在方括号中的标签 是很有用的提示 如果你不确定你在处理的 是哪一个调试信息 它就很有用 如果你很肯定你的dSYM可以使用 但是依旧没有在Instruments追踪中 得到你函数的名称 请检查你的权限和编码签名 具体地说 通过codesign命令 你可以确认你有适当的编码签名 你也应该确认在本机建立 用于开发的app 拥有get-task-allow权限 这个权限会允许 例如Instruments的工具 来符号化你的app Xcode应该以概述动作 自动设定这个权限 不过去确认它是好的 如果你没有开启get-task-allow权限 你应该检查 你的编码签名注入基本权限的 建立设定 并且确保在你开发时 它是开启的 最后 针对Universal 2 app 你应该将你有兴趣的架构 指定到工具上 符号、otool和dwarfdump 都有一个-arch标记 因而只会在特定的架构片上运行 《符号化:在基础之外》 就介绍到这里 如果没有别的 我想要特别强调几个关键 UUID和文件地址是个 一致且可靠的方法 来确认你的app在做什么 因为它们独立于ASLR偏移 它们也是我们去查询调试信息的关键 可能的话 你也应该尽量使用dSYM dSYM以DWARF的形式 包含最丰富的调试信息 Xcode和Instruments都支持dSYM 最后 我们介绍了几个工具 你可以直接在Xcode中使用这些工具 而且它们提供强大的诊断和洞察 你应该努力将它们加入 你在调试和优化的工作流程中 如果你想要了解更多 我建议大家去看WWDC18这两个课程 来学习如何让你的app在启动时 就活跃起来 《优化app的启动时间》 以及《App启动时间 过去、现在和未来》 谢谢大家跟我一起 学习符号化! 希望大家接下来的时间过得愉快 ♪
-
-
1:11 - MagicNumbers
func selectMagicNumber(choices: [Int]) -> Int { return choices[MAGIC_CHOICE] } func randomValue() -> Int { return Int.random(in: 1...100) } func numberChoices() -> [Int] { var choices = [Int]() for _ in 1...10 { choices.append(randomValue()) } return choices } func generateMagicNumber() -> Int { let numbers = numberChoices() let magic = selectMagicNumber(choices: numbers) return magic } print("The magic number is: \(generateMagicNumber())")
-
2:51 - atos symbolication
atos -o MagicNumbers.dSYM/Contents/Resources/DWARF/MagicNumbers -arch arm64 -l 0x10045c000 -i 0x10045fb70
-
7:34 - Load commands
otool -l MagicNumbers | grep LC_SEGMENT -A8
-
10:31 - Disassembly
otool -tV MagicNumbers -arch arm64
-
11:32 - vmmap
vmmap MagicNumbers | grep __TEXT
-
15:09 - Function starts
symbols -onlyFuncStartsData -arch arm64 MagicNumbers
-
17:06 - nlist_64
struct nlist_64 { union { uint32_t n_strx; } n_un; uint8_t n_type; uint8_t n_sect; uint16_t n_desc; uint64_t n_value; };
-
17:59 - Direct symbols with nm
nm -arch arm64 —defined-only --numeric-sort MagicNumbers
-
18:30 - Demangled direct symbols with nm
nm -arch arm64 —defined-only --numeric-sort MagicNumbers | xcrun swift-demangle
-
18:43 - Demangled direct symbols with the symbols tool
symbols -arch arm64 -onlyNListData MagicNumbers
-
23:06 - Indirect symbols with nm
nm -m —arch arm64 --undefined-only --numeric-sort MagicNumbers
-
27:16 - Examining dSYMs with dwarfdump
dwarfdump -v -debug-info -arch arm64 MagicNumbers.dSYM
-
29:25 - atos symbolication without inlined functions
atos -o MagicNumbers.dSYM/Contents/Resources/DWARF/MagicNumbers -arch arm64 —l 0x10045c000 0x10045fb70
-
32:29 - Examining debugging symbols
dsymutil --dump-debug-map -arch arm64 MagicNumbers
-
33:59 - Examining dSYM UUIDs
symbols -uuid MagicNumbers.dSYM
-
34:03 - Verifying DWARF
dwarfdump —verify MagicNumbers.dSYM
-
35:09 - Verifying entitlements and codesigning
codesign --display -v --entitlements :- MagicApp.app
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。