作者:黄俊铭,抖音小程序前端开发工程师
基础概念
首先需要提到的是,chrome 与 V8 本身实现了一套调试协议:chromedevtools.github.io/devtools-pr…
比如我们在 chrome devtool 打了一个断点,devtool 会自动产生协议消息,V8 获得协议消息之后也能对该协议消息进行解析产生相应的反馈,产生协议消息再给 chrome devtool, chrome devtool 再展示相应的变量信息等。
如下图,拦截了 ws 消息,可以看到协议中产生的消息自动产生了 id 序列号码以及方法(绿色的是 chrome devtool 发出的消息,红色是从 V8 发来,chrome devtool 接收到的消息)。
本地调试
小程序/小游戏在内部包支持本地直接连接 USB 进行本地调试,在没有配置远程调试链接模式下,默认在初始化V8引擎后,内部会在本地开启一个websocket server,端口为9229。
同时这个websocket server需要支持/json(更多可以参考:chromedevtools.github.io/devtools-pr… ),这样chrome就能在 chrome://inspect 界面找到该调试。
具体操作就是,手机设备先连接USB线,然后执行 adb forward tcp:9229 tcp:9229 转发端口到电脑设备,这时我们可以访问 localhost:9229,可以看到如下数据,描述了调试调试链接等信息。
[
{
"description": "helium v8 inspect",
"devtoolsFrontendUrl": "chrome-devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=127.0.0.1:9229/helium",
"title": "helium",
"id": "helium",
"type": "node",
"url": "file://",
"webSocketDebuggerUrl": "ws://127.0.0.1:9229/helium"
}
]
打开 chrome://inspect,可以配置寻找的相应端口号
最后从 chrome://inspect 可以看到
点击 inspect 则打开 json 结构中的 devtoolsFrontendUrl
chrome-devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=127.0.0.1:9229/helium
可以观察发现ws=127.0.0.1:9229/helium 其实就是告诉chrome devtool去连接该链接的websocket server,这样的话就 websocket server 就能收到 devtool 传来的调试协议消息,然后传给 v8,v8 产生协议消息后再给websocket server向devtool发送消息,这样就产生了整个调试通路。
远程调试
本地调试是我们的内部调试方式,并且使用 USB 调试比较麻烦,为了解决开发者的调试,我们加了一个远程调试逻辑,其实原理也很简单,我们通过一个远程的中转 websocket 服务器,客户端不再作为 server,而是作为 client,然后 client 连接远程服务端,devtool 也连接远程服务端,devtool 发送断点调试信息给远程服务端,远程服务端将该信息透传给手机中的 v8,这样就形成了通路。
其实对于抖音小程序小游戏场景,其中还有一个角色是开发者工具IDE(electron),IDE包含了chrome devtool,整体的运行流程如下:
- 开发者开发完成,点击开发者工具的真机调试
- 开发者工具连接远程服务端,获取调试房间id,将房间id带入了二维码信息中,产生了二维码
- 带有小程序的手机扫码,获取调试房间id后连接到远程服务器的同一个房间中
- 这时候设备中的V8就可以与chrome devtool通过远程服务器的房间进行转发通信消息,也就形成了整个调试通路
小程序 webview 调试
对于小程序场景还有一个 webview 的调试,那我们也要一起支持远程调试,怎么做呢? 其实从 chrome://inspect 本身我们可以看到开了debug设置的webview本身就是可以调试的,其实我们就可以猜想到 webview 本身在手机就起了服务,因此我们只要同样连上这个服务,就可以往里面收发消息完成远程调试了。
如下,chrome for android其实开启了unix domain socket。
所以只要连接上它们就行了,然后再新增一个websocket client,将我们从原本的远程Websocket client收到的 DOM 调试信息转发给该unix domain socket,client从该unix domain socket 收到消息转发给远程服务器,最后devtool就可以收到 webview DOM的调试信息。
可以看到上面的示意图,相比前一节新增了一个 WebSocket client(webview)以及 Webview Unix Socket 的模块,通过它们的转发通信,我们就实现了远程调试小程序的页面元素。
V8 调试模块
下述讲解 V8 调试模块接入的一些核心类,包含 v8_inspector::V8InspectorClient、v8_inspector::V8Inspector、v8_inspector::V8InspectorSession、v8_inspector::V8Inspector::Channel等,有兴趣接入 V8 调试模块业务可以作为参考,整体工作流程如下所示: 代码示例:
// 创建 Channel
// 需要自己实现 Channel
// class ChannelImpl final: public v8_inspector::V8Inspector::Channel
v8_inspector::V8Inspector::Channel channel_ = new ChannelImpl();
v8_inspector::StringView view( ... )
// 创建 session
// 连接 inspector 和 channel
v8_inspector::V8InspectorSession session_ =
inspector_.connect(
1,
channel,
view);
// 需要调用 contextCreated
// 将上下文信息传入 inspector
// ctx_name 为上下文名字
v8_inspector::StringView ctx_name( /*ctx_name*/ )
inspector_->contextCreated(、
v8_inspector::V8ContextInfo(
context,
1,
ctx_name);
v8_inspector::V8InspectorClient
class V8_EXPORT V8InspectorClient {
public:
virtual ~V8InspectorClient() = default;
virtual void runMessageLoopOnPause(int contextGroupId) {}
virtual void quitMessageLoopOnPause() {}
};
这个类我们主要需要实现两个函数,一个是runMessageLoopOnPause,一个是quitMessageLoopOnPause
- runMessageLoopOnPause
在断点的时候,v8 会触发 runMessageLoopOnPause,这时候需要接入方同步的消费所有来源于 devtool 消息(消费即传入 v8 驱动运行),我们需要开启自己的messageLoop去消费消息,否则会导致丢失所有 devtool 发来的消息的作用,现象就是拿不到上下文信息,console 控制台将无效没有反应
- quitMessageLoopOnPause
将结束断点的时候,就会自动触发 quitMessageLoopOnPause,这时候我们可以停止 MessageLoop,不需要再继续进行同步地消费消息了
v8_inspector::V8Inspector
这个类构造时需要传入两个参数,一个是isolate,一个是上述我们所讲的V8InspectorClient实例,首先需要说的是,一个v8Inspector对应一个isolate,一个isolate下有多个context,因此其实一个inspector可以调试多个context。
v8_inspector::V8Inspector::Channel
// Connection.
class V8_EXPORT Channel {
public:
virtual ~Channel() = default;
virtual void sendResponse(int callId,
std::unique_ptr<StringBuffer> message) = 0;
virtual void sendNotification(std::unique_ptr<StringBuffer> message) = 0;
virtual void flushProtocolNotifications() = 0;
};
channel是跟v8inspector的通道,可以获取到v8内部产生的协议消息,需要做的事情就将它转发给 devtool,主要需要实现sendResponse和sendNotification,其实很简单直接将消息转发给devtool就行了
void sendResponse(int callId, std::unique_ptr<v8_inspector::StringBuffer> message) override {
sendToDevtool(message->string());
}
void sendNotification(std::unique_ptr<v8_inspector::StringBuffer> message) override {
sendToDevtool(message->string());
}
v8_inspector::V8InspectorSession
通过连接channel和inspect产生调试session,这个session主要用来接收devtool传来的消息,然后将消息通过方法dispatchProtocolMessage 可以将协议消息传给v8,从而驱动v8断点等行为,然后v8会调用channel方法再将内部协议消息传达出来,整个链路就走通了。
std::unique_ptr<StringBuffer> &str = messages.front();
session->dispatchProtocolMessage(str->string());
参考资料
- v8.dev/docs/inspec…
- chromedevtools.github.io/devtools-pr…
- medium.com/@hyperandro…
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!