浅谈跨端调试

背景

在当前跨端技术流行的大背景下,背后的研发配套能力也需要不断进步,以提高跨端开发的研发效率,调试就是其中重要的能力之一。
调试能力的建设可以帮助开发者提高研发效率,降低在多平台多设备场景下排查问题的成本。
本文主要介绍一下笔者对于建设跨端调试方案的经验。

常见跨端调试方案

跨端调试场景最多的情况往往是开发者的研发设备和运行设备不是同一台设备。所以在多数场景下需要建立必要的调试信道服务用于调试端设备端的通信。如果提供的是平台型服务,就要考虑将信道服务器置于双端均能访问的公网环境下。

小程序真机调试

小程序的调试能力相信是很多同学最先接触到的跨端的调试方案,通过小程序的真机调试能力开发者可以Inspect代码在真机上运行情况。虽然每个平台的小程序方案略有不同,但是大多都提供了控制台,视图,源码,网络,存储的调试能力。而他们的调试流程基本都是设备扫码=>弹起调试面板。
下面我们来介绍腾讯&阿里的小程序平台为开发者提供的调试方案。后文将深入探讨这些能力背后的技术方案。

视图调试

首先看到这个调试面板第一印象都觉得是Chrome DevTools,其实通过逆向两者的开发者工具去看确实都是从[Chrome DevTools](https://github.com/ChromeDevTools/devtools-frontend)项目中改造过来。
微信调试面板

支付宝
image.png
可以看到这些调试面板上显示出小程序的DSL层接结构,属性面板上更是可以查看节点绑定的数据值运行时的情况。同时选中这些节点,真机上也会有高亮快Overlay在对应的显示渲染区域。两家大厂的在视图调试体验可以说都做的相当优秀,非常接近日常在浏览器中的调试体验。

源码调试

image.png

两个小程序在源码调试能力的差别不大,基本和原生的 Chrome DevTools 差距不大,支持添加断点,单步调试,异常断点等操作。

Flutter Dart DevTools

Flutter 开发了一个非常全面的调试工具,可以 Inspect 看组件树信息,也可以 Debug 代码和查看网络请求,性能相关信息。

ReactNative DevTools

ReactNative 选择 React Devtools(提供inspect 组件树的能力) + Chrome Devtools (Safari Devtools)(调试 JavaScript 能力) 结合的调试方案。

方案设计

今天笔者也正在建设一套基于类小程序框架方案( X-Widget 旨在让开发者可以使用小程序语法来完成多平台多系统上的视图开发,想要将小程序生态继续衍生到多终端的设备上,让小程序开发者可以用最熟悉的语法开发跨平台的应用)的跨端调试方案。

目标功能

跨端开发中,开发者最关心的莫过于能够感知和参与进入远程设备中的 节点布局渲染和代码执行逻辑 过程。在 Web 开发中,我们通常会用 DevTools 中的 Elements/Console/Sources 三个Tab来完成。

Elements

Elements Tab 我们可以查看当前 渲染树(跨端渲染过程中一个重要中间信息的名称)的结构,附属信息和在远程设备的中的定位和大小,并且可以在一定程度上修改渲染树的信息同步给远程设备更新渲染,在跨端开发过程中,开发者最关心的莫过于 渲染一致性,样式信息是否正确命中到渲染节点上是一个判断问题所属边界的重要信息。所以在 Elements Tab 中我们需要将渲染节点的附着到的样式信息也一并计算出来并进行展示,同时选中节点也需要在真机上有布局区域Overlay 的效果。

Console

Console Tab中我们主要提供查看 JS 打印 log 信息和执行相应作用域代码的能力,查看log对于初阶开发者这是一个感知代码在远程设备上执行逻辑过程的好办法。

Sources

Sources Tab 中我们提供查看 JS 代码和 代码断点 能力,支持真实运行在跨端机器上的单步调试能力,

能力建设

面板选型

在调试终端面板的选型上,为了让开发者更加贴近 Web生态并且和小程序一样的调试体验,笔者选择了同目前绝大数小程序平台一样基于Chrome Frontend DevTools 和背后的 Chrome Debug Protocol 进行二次开发。

项目构建

1
2
3
4
5
6
7
8
9
## Google 开源的一些工具链 
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
## 添加到全局Path中

## 构建
gclient config https://chromium.googlesource.com/devtools/devtools-frontend.git
gclient sync
cd devtools-frontend
npm run build

调试链路

下图是一个调试链路简单的示意图。

构建过程

调试运行的代码相对于生产环境的代码有两点不同

  • 植入Agent

Agent是我们调试能力的主要载体,我们选择在构建时和业务代码一起植入进去。
这里我们利用 Webpack Plugin 开放的插件体系在对应时机进行植入,为了避免开发者看到 Agent 代码和调试行号错位,我们必须要在 Webpack 完成 Sourcemap 合成过程和优化之后再进行插入。

  • 开启和优化 SourceMap

我们的云构建器可以感知调试信息,在感知到是调试构件时,会开启 SourceMap 插件,这里我们还是利用 SourceMapDevToolPlugin 来实现,由于框架实现逻辑中会塞入很多临时的中间态文件(internal module),然而我们只想让开发者看到原本对应的文件体系,利用 SourceMapDevToolPluginmoduleFilenameTemplate Function 进行文件路径优化和中间态文件路径归档。例如我们会将开发者写的跨端代码统一在 app:// 下, 临时中间态文件归档在 internal:// 中,方面开发者在调试时快速找到对应的文件体系。

视图调试

视图调试对应的Elements面板的实现,对应在Chrome Devtools Protocol Domains中的DOM / CSS / Overlay下的相关方法。为了满足CDP协议的交互,我们需要对于渲染树有完整的掌控能力,一方面我们需要有序列化渲染树到 CDP 协议树的能力,一方面我们需要对两棵树中的节点建立关联关系。为了能够观察到渲染树,并且渲染树节点的状态信息也需要感知到。我们需要对跨端的前端运行时框架有足够的掌握程度,并且还需要在渲染树改动时做相关劫持,以便能够将渲染树改动同步到CDP协议树上。这里我们需要明确CDP协议树对比于渲染树要更加贴近用户开发的DSL结构,这是为了方便开发者更好把渲染视图情况和代码进行对应。

image.png

微信的做法

通过逆向微信小程序模拟器 RenderWebView加载的专门为Debug的模块文件(类似笔者在上文中提到的Agent)

1
/__pageframe__/__dev__/WARemoteDebug.js

我们可以清晰的看到微信也在框架内部透出一些能力,让RemoteDebug中可以通过一些方法获取和观察到DSL结构和节点属性的变动。

解释执行

面对调试JavaScript的需求,我们迎来了更加复杂的场景和业界难题。一方面对于 iOS 设备因为苹果安全限制在 Release 包中无法转发桥接 JavaScriptCore的调试协议,另一方面目前对于使用到的执行引擎还没有完成对于调试能力的支持,如QuickJs,所以为了能够为了在跨技术栈的设备上进行 JS 调试,我们采用 采用了 JS eval JS 的解释执行调试方案,和执行引擎相关特性进行解耦,恰到好处的解决了我们所面对的难题。下面一图简单介绍了解释执行的原理。
js-interpreterdrawio.png
解释执行调试的核心就是一方面掌控了 JS 执行的控制权可以模拟执行引擎的中断行为,一方面又可以获取执行时的上下文信息。执行过程中去完成对于CDP协议的实现。因此面向这样的技术方案,我们对于执行引擎容器的建设成本就会大大降低,只需要提供一些基础的通信能力即可。

总结

本文简单介绍了目前关于跨端调试相关能力的技术选型和原理。跨端的场景会越加的丰富,未来可能还会面向更多跨技术栈的设备场景(如IOT场景),优秀的调试工具是提升跨端研发效率的必备神器。对于远程设备应用开发,研发侧还有更多工具和链路需要建设,欢迎一起讨论。


浅谈跨端调试
https://disheng.site/2023/04/21/cross-end-debugging/
作者
笛声
发布于
2023年4月21日
许可协议