资讯详情

Chrome 远程调试

揭秘浏览器远程调试技术 作者: 肖焉 发表于: 2016-10-19

揭秘浏览器远程调试技术

调试技术的起源 1947 年 9 月 9 美国科学家格蕾丝.霍普和她的同伴在对 Mark II 研究计算机时,发现一只飞蛾粘在继电器上,导致计算机无法正常工作。当他们移除飞蛾时,计算机恢复正常运行。于是他们把飞蛾贴在当时记录的日志上,详细记录了这件事,并在日志结尾写了这样一句话:First actual case of bug being found。这是他们发现的第一个真实意义 bug,这也是人类计算机软件历史上发现的第一个 bug,他们找到飞蛾的方法和过程是 debugging 调试技术。

History of Debug

第一次从格蕾丝调试 bug 到现在,69 在计算机领域,硬件和软件的各种调试技术都在不断发展和发展。调试技术对日新月异的前端尤为重要。淘宝前端团队也在使用一些创新的技术和手段来解决无线页面调试的问题。今天,我将与大家分享浏览器远程调试技术 Chrome/Webview 作为案例。

调试原理 调试方法和权限管理

常规浏览器调试目标有两种:Chrome PC 浏览器和 Chrome Mobile(Android 4.4 以后,Android WebView 其实就是 Chromium WebView)。

Chrome PC 浏览器 对于调试 Chrome PC 浏览器,也许你经常使用鼠标右键或快速方法(mac:option command J),唤起 Chrome 调试当前页面的控制台。其实还有一种方法,就是用一个 Chrome 另一个浏览器调试 Chrome 浏览器。Chrome 启动时默认关闭调试端口。如果你想瞄准一个目标 Chrome PC 如果浏览器调试,启动时可以通过传输参数打开 Chrome 调试开关

for mac

sudo /Applications/GoogleChrome.app/Contents/MacOS/GoogleChrome --remote-debugging-port=9222 Chrome Android 浏览器 对于调试 Android 上的 Chrome 或者 WebView 需要连接 USB 线路。打开调试端口的方法如下:

adb forward tcp:9222 localabstract:chrome_devtools_remote 跟 Chrome PC 不同的浏览器是,对于 Chrome Android 浏览器是通过数据传输的 USB 线而不是 WIFI,实际上 Chrome Android 创建的一个 chrome_devtools_remote 这个 path 的 domain socket。因此,上述命令是通过的 Android 的 adb 将 PC 的端口 9222 通过 USB 线与 chrome_devtools_remote 这个 domain socket 建立了端口映射。

权限管理 Google 为限制调试端口的接入范围, Chrome PC 浏览器只接受调试端口 127.0.0.1 或者 localhost 数据请求,所以你不能通过你的本地机器 IP 来调试 Chrome。对于 Android Chrome/WebView,调试端口只接受 shell 这个用户数据请求只能通过 USB 调试不能通过 WIFI。

开始调试 此时在浏览器中输入上述调试方法的接入和调试端口的打开:

http://127.0.0.1:9222/json 您将看到以下内容:

[ { "description": "", "devtoolsFrontendUrl": "/devtools/inspector.html?ws=127.0.0.1:9222/devtools/page/ebdace60-d482-4340-b622-a6198e7aad6e", "id": "ebdace60-d482-4340-b622-a6198e7aad6e", "title": "揭秘浏览器远程调试技术.mdown—/Users/harlen/Documents", "type": "page", "url": "http://127.0.0.1:51004/view/61", "webSocketDebuggerUrl": "ws://127.0.0.1:9222/devtools/page/ebdace60-d482-4340-b622-a6198e7aad6e" }] 其中,最重要的 2 参数分别是 id 和 webSocketDebuggerUrl。Chrome 它将为每个页面分配一个唯一的 id,作为页面的唯一标识符。几乎所有的目标浏览器操作都需要这个 id。

Chrome 提供以下内容 http 接口控制目标浏览器

获取所有可调页面信息

http://127.0.0.1:9222/json

获取调试目标 WebView/blink 的版本号

http://127.0.0.1:9222/json/version

创建新的 tab,并加载 url

http://127.0.0.1:9222/json/new?url

关闭 id 对应的 tab

http://127.0.0.1:9222/json/close/id webSocketDebuggerUrl 调试页面需要使用的一个 WebSocket 连接。chrome 的 devtool 所有的调试功能都是基于 Remote Debugging Protocol 使用 WebSocket 数据传输。那么这个 WebSocket,我们从上面走 http://127.0.0.1:9222/json 获取的 webSocketDebuggerUrl,每个页面都有自己的不同 webSocketDebuggerUrl。这个 webSocketDebuggerUrl是通过 url 的 query 参数传递给 chrome devtool 的。

chrome 的 devtool 可以从 Chrome 从浏览器中提取 devtool 源码或者从 blink 获取源代码。部署好自己 chrome devtool 代码完成后,下面可以开始 Chrome 进行调试, 浏览器输入内容:

http://path_to_your_devtool/devtool.html?ws=127.0.0.1:9222/devtools/page/ebdace60-d482-4340-b622-a6198e7aad6e 其中 ws 这个参数的值出现在上面 webSocketDebuggerUrl。Chrome 的 devtool 会使用这个 url 创建 WebSocket 调试页面。

如何实现 JavaScript 调试 在进入 Chrome 的 devtool 之后可以调出控制台查看 devtool 的 WebSocket 数据。这个里面有很多数据,我这里只讲跟 JavaScript 调试相关。

图中,对于 JavaScript 调试,有一个很重要的消息,我选择蓝色的消息:

{"id":6,"method":"Debugger.enable"} 然后选择要调试的 JavaScript 我们来看看文件,然后设置断点。 WebSocket 消息:

devtool 像目标 Chrome 发送了 2 条消息

{ "id": 23, "method": "Debugger.getScriptSource", "params": { "scriptId": "103" } } { "id": 24, "method": "Debugger.setBreakpointByUrl", "params": { "lineNumber": 2, "url": "https://g.alicdn.com/alilog/wlog/0.2.10/??aplus_wap.js,spm_wap.js,spmact_wap.js", "columnNumber": 0, "condition": "" } } 收到这些消息后,V8 你做了什么? 先简单看一下 V8 一小段源码片段:

// V8 Debugger.cpp DispatcherImpl(FrontendChannel* frontendChannel, Backend* backend) : DispatcherBase(frontendChannel), m_backend(backend) { m_dispatchMap["Debugger.enable"] = &DispatcherImpl::enable; m_dispatchMap["Debugger.disable"] = &DispatcherImpl::disable; m_dispatchMap["Debugger.setBreakpointsActive"] = &DispatcherImpl::setBreakpointsActive; m_dispathMap["Debugger.setSkipAllPauses"] = &DispatcherImpl::setSkipAllPauses; m_dispatchMap["Debugger.setBreakpointByUrl"] = &DispatcherImpl::setBreakpointByUrl; m_dispatchMap["Debugger.setBreakpoint"] = &DispatcherImpl::setBreakpoint; m_dispatchMap["Debugger.removeBreakpoint"] = &DispatcherImpl::removeBreakpoint; m_dispatchMap["Debugger.continueToLocation"] = &DispatcherImpl::continueToLocation; m_dispatchMap["Debugger.stepOver"] = &DispatcherImpl::stepOver; m_dispatchMap["Debugger.stepInto"] = &DispatcherImpl::stepInto; m_dispatchMap["Debugger.stepOut"] = &DispatcherImpl::stepOut; m_dispatchMap["Debugger.pause"] = &DispatcherImpl::pause; m_dispatchMap["Debugger.resume"] = &DispatcherImpl::resume; m_dispatchMap["Debugger.searchInContent"] = &DispatcherImpl::searchInContent; m_dispatchMap["Debugger.setScriptSource"] = &DispatcherImpl::setScriptSource; m_dispatchMap["Debugger.restartFrame"] = &DispatcherImpl::restartFrame; m_dispatchMap["Debugger.getScriptSource"] = &DispatcherImpl::getScriptSource; m_dispatchMap["Debugger.setPauseOnExceptions"] = &DispatcherImpl::setPauseOnExceptions; m_dispatchMap["Debugger.evaluateOnCallFrame"] = &DispatcherImpl::evaluateOnCallFrame; m_dispatchMap["Debugger.setVariableValue"] = &DispatcherImpl::setVariableValue; m_dispatchMap["Debugger.setAsyncCallStackDepth"] = &DispatcherImpl::setAsyncCallStackDepth; m_dispatchMap["Debugger.setBlackboxPatterns"] = &DispatcherImpl::setBlackboxPatterns; m_dispatchMap["Debugger.setBlackboxedRanges"] = &DispatcherImpl::setBlackboxedRanges; } 你会发现,V8 有 m_dispatchMap 这样一个 Map。专门用来处理所有 JavaScript 调试相关的处理。 其中就有本文即将重点讲述的:

Debuggger.enable Debugger.getScriptSource setBreakpointByUrl 这些都需要在 V8 的源码中找到答案。顺便给大家推荐一个查看 Chromium/V8 最正确的方式是使用 https://cs.chromium.org,比 SourceInsight 还要方便。

Debugger.enable void V8Debugger::enable() { if (m_enableCount++) return; DCHECK(!enabled()); v8::HandleScope scope(m_isolate); v8::Debug::SetDebugEventListener(m_isolate, &V8Debugger::v8DebugEventCallback, v8::External::New(m_isolate, this)); m_debuggerContext.Reset(m_isolate, v8::Debug::GetDebugContext(m_isolate)); compileDebuggerScript(); } 这个接口的名称叫 Debugger.enable,但是收到这条消息,V8 其实就干了两件事情事情:

SetDebugEventListener: 给 JavaScript 调试安装监听器,并设置 v8DebugEventCallback 这个回调函数。JavaScript 所有的调试事件,都会被这个监听器捕获,包括:JavaScript 异常停止,断点停止,单步调试等等。

compileDebuggerScript: 编译 V8 内置的 JavaScript 文件 debugger-script.js。由于这文件比较长,我这里就不贴出来了,感兴趣的同学点击这个链接进行查看源码。debugger-script.js 主要是定义了一些针对 JavaScript 断点进行操作的函数,例如设置断点、查找断点以及单步调试相关的函数。那么这个 debugger-script.js 文件,被 V8 进行编译之后,保存在 global 对象上,等待对 JavaScript 进行调试的时候,被调用。

Debugger.getScriptSource 在 Chrome 解析引擎解析到

标签: m048dc2继电器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台