Web调试技术详解

avatar
SugarTurboS Club @SugarTurboS
  • 苏格团队
  • 作者:Jonny

一、调试技术的起源

对于每位开发者而言,bug已经是不能再寻常的东西,debug也是家常便饭的事情。然而寻根溯源起来,还得从上个世纪五十年代讲起。

1947年9月9日,哈佛大学在测试马克II型艾肯中继器计算机的时候,一只飞蛾粘在一个继电器上,导致计算机无法正常工作,操作员把飞蛾移除之后,计算机又恢复了正常运转。于是他们将这只飞蛾贴在了他们当时记录的日志上,并在日志最后写了这样一句话:First actual case of bug being found。这是他们发现的第一个真正意义上的 bug,这也是人类计算机软件历史上发现的第一个 bug。他们也提出了一个词,“debug(调试)”了机器,由此引出了计算机调试技术的发展。

二、Chrome 开发者工具

对于web前端而言,我们每天都在使用开发调试工具进行查看dom结构、js断点和查看分析登录请求等操作,最熟悉的开发调试工具 应该莫过于Chrome的Devtools和Firefox的FireBug了。

Chrome 开发者工具其实是一个用 HTML,JavaScript 和 CSS 写的 Web 应用程序,被集成在浏览器中。其基于远程调试协议,与浏览器架构关系如下:

由图可知:

  • 远程调试协议基于 WebSocket,利用 WebSocket 建立连接 DevTools 和浏览器内核的快速数据通道
  • 浏览器拥有多个 Tab,并为每个 Tab 单独提供 Websocket 的 Endpoint URI
  • 每个 DevTool 实例只能检视一个 Tab,即只能与一个 Tab 保持通讯

事实上,Chrome开发者工具不仅只是可以在当前的浏览器页面直接打开,它作为一个客户端(开源),也可以用来调试任何支持远程调试协议的浏览器。

使用文档:developers.google.com/web/tools/c…

github仓库:github.com/ChromeDevTo…

三、远程调试协议(remote debugging protocol)

远程调试协议Webkit 在 2012 年就已经引入,目前所有 Webkit 内核的浏览器都支持这一特性。远程调试协议基于 WebSocket,利用 WebSocket 建立连接 客户端(如DevTools) 和浏览器内核的快速数据通道,Chrome的Devtools仅仅只是Webkit远程调试协议的一个应用案例。

  1. 通讯模式和消息结构: 对于每个页面的所有操作,远程调试协议将其划分成了不同的命令域,如Browser、Dom、Debugger和Network等等,每个域定义了不同的命令和事件。在开发调试过程中,浏览器内核和远程客户端通过WebSocket发送消息进行通信,消息基本上分两种格式,一种是含有 message id 的,另一种是不含 message id 的,分别代表俩种通讯模式:
  • request/response:就如同一个异步调用,通过请求的信息,获取相应的返回结果。这样的通讯必然有一个 message id,否则两方都无法正确的判断请求和返回的匹配状况。

  • notification:和第一种不同,这种模式用于由一方单方面的通知另一方某个信息。和 “事件” 的概念类似。

  1. Chrome的上层封装: 为了更加方便Chrome extension的开发,Chrome调试器扩展API提供了更高级别的API ——Chrome Debugger API。此API隐藏请求ID并处理请求与其响应的绑定,因此允许sendCommand在回调函数调用中处理结果。每个 command 包含 request 和 response 两部分,request 部分指定所要进行的操作以及操作说要的参数,response 部分表明操作状态,成功或失败。

以Debugger Domain为例,

  • command结构如下:

  • 事件结构如下:

协议文档:chromedevtools.github.io/devtools-pr…

Debugger API: developer.chrome.com/extensions/…

我们可以通过devtools来查看Devtools Extension与浏览器内核实际通信的数据情况,步骤如下:

1、开启开发者工具实验模式:

  • 浏览器进入chrome://flags
  • 找到Developer Tools Experiments
  • 状态改为enable
  • 重启浏览器

2、打开协议监控tab

  • 点击devtools工具右上角菜单图标,进入“settings”,左边选择“Experiments”tab,将“Protocol Monitor”打上勾
  • 关闭devtools后重新打开,点击devtools工具右上角菜单图标,再进入“More Tools”,选择“Protocol monitor"

之后我们便可以看到:

四、Chrome的远程调试模式:

从上面我们已经知道,Devtools是如何基于远程调试协议与浏览器内核进行交互的了,然而,不仅仅如此,Chrome还可以开启远程调试模式,允许外部客户端(支持远程调试协议)对其进行调试。

  1. 协议Server端:

首先,以远程调试模式打开Chrome:

./chrome --remote-debugging-port=9222

之后调试数据会转发到本地9222端口,浏览器输入localhost:9222/json可以看到:

其中我们可以看到每个tab页面对应的websocket url,通过该url建立连接便可以和系统内核进行通信了。

  1. 协议客户端:

我们可以采用Chrome内置的工具与其建立连接进行调试,步骤如下:

1)打开Chrome 内置客户端,在浏览器输入:

http://localhost:9222

可以看到以下界面:

2)或者在chrome://inspect界面,我们可以发现,此时本地浏览器也可以被作为一个remote device来调试了。

3)我们也可以采用一个外部的调试工具,如node程序,vscode插件等,通过从9222端口获取到各个页面的json数据,然后进行websocket连接进行通信,进行实现各种丰富的开发调试功能。

五、移动端远程调试技术:

从上面看,chrome等浏览器的远程调试模式似乎有点鸡肋,我都有Devtools工具了,何必还要多此一举去开启远程调试模式用其他工具来调试。别急,远程调试模式真正发挥作用的还是移动端设备的调试场景。移动端设备有运行环境,却没有合适的开发和调试环境,借助于webkit的远程调试模式,可以使得我们很方便的查看和调试移动端设备。

移动端web远程调试技术很多种,像weinre等在代码中嵌入脚本的就不说了,我们以基于webkit远程调试协议的方式来讲解。 从上面我们已经知道,移动端chrome开启远程调试模式后json数据会被打到debugging端口(localhost:9222),因为安全考虑,chrome限制了只能是本地localhost的,不能打到指定ip之上。此时,需要通过某种方式,将移动端9222端口的数据绑定到pc端,之后才能PC端便可以通过PC端本地端口与移动端页面进行调试。

1、端口绑定方式:

  • 有线(USB线):以webkit为内核的移动端浏览器,开启浏览器远程调试功能之后通过usb连接到pc端,之后通过adb进行端口绑定:

adb forward tcp:9222 localabstract:chrome_devtools_remote

  • 无线(network):通过ssh 进行端口转发,本方式适合移动端支持ssh连接登录。

#在本地主机A1登陆远程云主机B1,并进行本地端口转发。2000端口绑定本地所有网卡 ssh -L 2000:localhost:3000 root@192.168.*.*

六、调试服务器脚本(node程序):

2016年,Node将 Chrome浏览器的"开发者工具"作为官方的调试工具,使得 Node 脚本也可以使用图形界面调试,这大大方便了开发者。开启调试方式如下:

node --inspect --debug-brk index.js

然后通过chrome://inspect可以看到app.js的调试入口,打开之后出现Devtools的定制版,只有四个Tab,移除了和服务器脚本调试无关的部分。

可以发现,node脚本调试的原理与chrome js脚本相似,其内置V8支持远程调试协议,开启调试后作为一个server端,devtools客户端通过websocket与其建立连接进行通信。

六、总结

至此,我们已经介绍了本地调试和远程调试已经隐藏在其背后的通讯原理。远程调试协议作为一个强大的通用的协议,支持了不同server或客户端的通信。

浏览器的调试,其实最后都落脚到引擎:渲染引擎和 JavaScipt 引擎。对于css的修改、js的断点等,如何落实到渲染引擎上,上下文环境切换,函数调用栈追踪等等,还有更多东西值得挖掘。