- 语言运行时
- 编译器
- 移动开发
【免费下载链接】hermes
A JavaScript engine optimized for running React Native.
导读
hcdp(Hermes CDP)是 Hermes 仓库中一个独立的命令行调试工具,它把 Hermes 引擎的 CDP(Chrome DevTools Protocol)调试能力包装成一个可直接运行的进程:用户既可以用它调试本地的 JavaScript 脚本,也可以借助 Chrome / Edge DevTools 界面进行可视化调试。本文以 tools/hcdp/README.md 为核心,结合 tools/hcdp/hcdp.cpp、tools/hcdp/hcdp.js 等源码,完整讲解 hcdp 的构建、运行方式、双组件架构与 IPC 消息协议,帮助你快速上手并理解 Hermes CDP 调试链路的工作原理。
一、hcdp 是什么
Hermes 是一个针对 React Native 场景优化的 JavaScript 引擎,其调试能力基于 CDP(Chrome DevTools Protocol)实现。hcdp正是围绕这一能力打造的命令行工具,它的核心目标是把“Hermes 运行时 + CDP 调试 API + 调试客户端”串成一条可独立运行的调试链路:
- 它承载一个真实的 Hermes Runtime,并执行你指定的 JS 脚本;
- 它对外暴露一个 WebSocket 服务,接收调试客户端(如 Chrome DevTools)发来的 CDP 消息;
- 它把 CDP 消息转化为内部 IPC 消息,交给 C++ 组件中的 CDP Agent 处理,再将 Agent 产生的响应与通知转发回客户端。
从源码结构看,整个工具由两大部分组成:hcdp.cpp(C++ 组件,负责运行时与调试内核)和hcdp.js(JavaScript 组件,负责网络与交互),二者通过标准输入输出上的行式 IPC 协议通信,详见 tools/hcdp/ 目录。
二、快速上手:构建与运行
1. 构建 hcdp 二进制
进入tools/hcdp目录后,使用 BUCK 或 CMake 构建hcdp可执行文件。CMake 构建入口为 tools/hcdp/CMakeLists.txt,它依赖仓库根目录的 CMakeLists.txt 中定义的add_hermes_tool与hermesvm_a目标。
值得注意的是,构建行为与HERMES_ENABLE_DEBUGGER编译选项强相关:
- 若该选项未开启,构建出的
hcdp只是一个打印提示后以失败退出的占位程序(stub),源码见 tools/hcdp/hcdp.cpp:hcdp compiled without Hermes debugger enabled. - 若该选项开启,才会编译完整的调试实现,并链接
hermesvm_a静态库。同时 CMake 会强制为hcdp打开 RTTI 与异常支持(set(HERMES_ENABLE_RTTI ON)、set(HERMES_ENABLE_EH ON)),并为JSONHelpers.cpp单独附加-fno-exceptions -fno-rtti(MSVC 下对应/EHs-c- /GR-)编译选项,以隔离差异,见 tools/hcdp/CMakeLists.txt。
构建产物默认通过install(TARGETS hcdp RUNTIME DESTINATION bin)安装到bin目录。
2. 安装 npm 依赖
JavaScript 组件依赖ws、chrome-launcher、chromium-edge-launcher三个包(声明见 tools/hcdp/package.json),在tools/hcdp目录下执行:
npm install3. 运行
运行hcdp.js,依次传入两个参数:hcdp二进制的路径,以及待调试脚本的路径。例如:
node ./hcdp.js ~/hcdp ~/loop.js其中:
~/hcdp指向构建好的hcdp二进制;~/loop.js是你要调试的脚本。
hcdp.js在启动时会做参数与文件存在性校验:缺少参数会打印Usage: node <path to hcdp binary> <path to script to debug>并退出,路径非法会分别报Binary not found at .../Script not found at ...(见 tools/hcdp/hcdp.js)。校验通过后,它会以子进程方式拉起 C++ 二进制,并把脚本内容读入内存备用。
启动成功后终端会打印提示:按下o键会用本机 Chrome(或 Edge)打开 DevTools 界面,按下x键退出。也可以直接访问提示中给出的 URL,其形式为:
https://chrome-devtools-frontend.appspot.com/serve_file/@<devtools版本>/js_app.html?ws=127.0.0.1%3A9999可见调试端口固定为9999,DevTools 前端版本号定义在 tools/hcdp/hcdp.js;浏览器启动依赖chrome-launcher/chromium-edge-launcher,若两者都不可用则抛出“Supported browsers: Google Chrome, Microsoft Edge”的错误。
三、整体架构:双组件协作
原文档明确给出了工具的两个组成部分,这里结合源码进一步展开其职责边界:
| 组件 | 文件 | 职责 |
|---|---|---|
| C++ 组件 | tools/hcdp/hcdp.cpp、tools/hcdp/IPC.cpp、tools/hcdp/JSONHelpers.cpp | 通过 Hermes API 承载调试会话所需对象(Hermes Runtime、CDP Debug API、CDP Agent);从 stdin 接收 IPC 消息,按需创建/销毁 CDP Agent 或处理消息;把 Agent 产生的响应与通知通过 stdout 以 IPC 消息发出 |
| JavaScript 组件 | tools/hcdp/hcdp.js | 启动 WebSocket 服务与调试客户端通信;原样打印流经的 CDP 消息;处理用户键盘输入;在客户端与 C++ 组件之间做消息格式转换 |
消息流转方向是双向的:
- 客户端 → JS → C++:调试客户端通过 WebSocket 发送 CDP 消息,
hcdp.js将其封装成 IPC 消息写入子进程 stdin,C++ 组件解析后交给对应 CDP Agent 处理; - C++ → JS → 客户端:CDP Agent 产生的响应与通知由 C++ 组件写到 stdout,
hcdp.js逐行解析、转发回 WebSocket 客户端,并同步打印到终端。
四、C++ 组件:运行时与 CDP Agent 管理
1. RuntimeInstance:承载可调试的 Hermes 运行时
hcdp/hcdp.cpp 中的RuntimeInstance类负责在一个独立线程上运行待调试脚本,其构造过程清晰地展示了 Hermes 调试 API 的标准用法:
- 用
fbhermes::makeHermesRuntime(...)创建运行时,配置中显式开启了采样性能分析(withEnableSampleProfiling(true)); - 调用
cdp::CDPDebugAPI::create(*runtime_)为运行时挂接 CDP 调试接口,这是之后创建 CDP Agent 的前提; - 注入
console.log:通过 JSI 的Object/Function::createFromHostFunction构造宿主函数,把参数收集后以cdpDebugAPI->addConsoleMessage(...)上报为 CDP 控制台消息(类型为ConsoleAPIType::kLog),并附带当前调用栈(hermesRt.getDebugger().captureStackTrace()),这样 DevTools 的 Console 面板才能看到脚本输出; - 通过
SerialExecutor将runtime_->debugJavaScript(source, url, flags)排入运行时线程执行,保证脚本执行与调试任务串行化。
析构顺序同样讲究:先销毁 executor 等待任务结束,再依次销毁 CDP Debug API 与运行时。
2. debugScript:IPC 事件循环与 Agent 生命周期
hcdp/hcdp.cpp 的debugScript是 C++ 侧的主循环,结构为while (std::optional<IPCCommand> ipc = receiveIPC()),对三类 IPC 消息分别处理:
C(Connect):为clientID创建新 Agent,调用cdp::CDPAgent::create(...),并传入两个关键回调:- 运行时任务回调:把 CDP 内部产生的、需要独占运行时执行的任务通过
RuntimeInstance::addTask排到运行时线程,确保“在两次 JS 执行间隙”执行; - 出站消息回调:把 Agent 产生的响应/通知封装为
M类型 IPC 消息发回 stdout。
- 运行时任务回调:把 CDP 内部产生的、需要独占运行时执行的任务通过
M(Message):从agents表按clientID查找 Agent,把客户端消息解析为message::Request后交给agent->second->handleCommand(...)处理;若找不到对应 Agent 则抛出No such agent。D(Disconnect):从agents表移除并销毁该客户端对应的 Agent。
3. 两个值得注意的实现细节
executionContextCreated通知的拦截注入。为了让 DevTools 的 Console 面板正常工作,运行时必须发出Runtime.executionContextCreated通知。hcdp 的实现方式是:当收到客户端的Runtime.enable命令时,记录其消息id(见 tools/hcdp/hcdp.cpp);随后在出站消息回调里,若发现某条响应的id恰好等于该记录值,就紧接着补发一条Runtime.executionContextCreated通知(desc.name = "main",执行上下文 id 由全局计数器nextExecutionContextId分配),再转发原响应,见 tools/hcdp/hcdp.cpp。响应id的提取由 tools/hcdp/JSONHelpers.cpp 的getResponseId完成,它借助 Hermes 自身的JSONParser与valueFromJson<long long>解析。
stdout 缓冲处理。main入口调用setbuf(stdout, nullptr)关闭输出缓冲,避免大体积输出(例如性能分析结果)被积压在缓冲区,见 tools/hcdp/hcdp.cpp。
五、IPC 消息协议
C++ 与 JavaScript 组件之间的通信采用行式文本协议,定义于 tools/hcdp/IPC.h 与 tools/hcdp/IPC.cpp。每条消息由三部分组成:
| 字段 | 说明 | 取值 |
|---|---|---|
type | 消息类型(单个字符) | C/M/D |
agentId(源码中为clientID) | 目标 Agent 的唯一数字 ID,类型为uint32_t | 非负整数 |
message | 可选的 CDP 消息体(JSON 字符串) | C与D时留空 |
三种消息类型的语义如下(沿用原文档定义):
C— Connect:连接。agentId表示要创建的新 Agent 的唯一 ID,message不使用。对应源码常量kConnectIPCType = 'C'。M— Message:消息。agentId表示此前已创建、应处理message的那个 Agent。对应kMessageIPCType = 'M'。D— Disconnect:断开。agentId表示要销毁的 Agent 的 ID,message不使用。对应kDisconnectIPCType = 'D'。
线格式示例(C++ 侧发送,见 tools/hcdp/IPC.cpp):
M3{"id":5,"method":"Debugger.enable"}即:type紧随agentId,随后是消息体(若无消息体则省略),最后以换行符\n结束整条 IPC 消息。
解析侧(tools/hcdp/IPC.cpp)先std::getline读一行,再用istringstream依次提取type与agentId,行尾剩余部分作为message。
JavaScript 侧的发送函数与之一一对应:sendConnectIPC/sendMessageIPC/sendDisconnectIPC最终都调用sendIPC(type, clientId, message),拼出<type><clientId><message>\n写入子进程 stdin(见 tools/hcdp/hcdp.js)。
六、JavaScript 组件:WebSocket 服务与消息桥
1. WebSocket 服务与客户端管理
hcdp.js使用ws库在9999端口启动 WebSocket 服务(tools/hcdp/hcdp.js)。每个客户端连接会获得一个自增的数字 ID(clientIdCounter++),并立即向 C++ 组件发送C连接 IPC;收到客户端消息则发送M消息 IPC;连接关闭则发送D断开 IPC,同时清理本地clients表(tools/hcdp/hcdp.js)。因此该服务天然支持多个调试客户端同时连接,每个客户端对应一个独立的 CDP Agent。
2. stdout 行解析与消息转发
C++ 组件通过 stdout 输出两类内容:IPC 消息与脚本自身的输出。hcdp.js按行累积缓冲(lineBuffer),对每一行:
- 定位首个
{,若无则视为脚本输出,直接打印到终端; - 首字符必须是
M(即messageIPCType),否则也视为普通输出打印; - 解析
M与{之间的数字作为clientID,做合法性校验(非数字、越界、客户端已断开等情况都有对应处理——例如客户端在 CDP 消息到达前就已断开时,会直接跳过该消息); - 对剩余 JSON 做
JSON.parse校验,非法消息抛出Malformed message; - 转发给对应 WebSocket 客户端,并记录日志(tools/hcdp/hcdp.js)。
3. 本地拦截Debugger.getScriptSource
一个典型的“JS 侧智能”是:当客户端请求Debugger.getScriptSource(获取脚本源码)时,hcdp.js并不把它转发给 C++,而是直接在本地生成响应——因为脚本源码本来就是由hcdp.js自己读入内存的。实现方式为:监听Debugger.scriptParsed通知记录当前脚本的scriptId(inspectScriptParsed),收到getScriptSource请求时,若请求的scriptId匹配则返回result.scriptSource,否则返回-32602(Invalid params)错误(tools/hcdp/hcdp.js)。这既减少了 IPC 往返,也避免把整个脚本源码通过子进程管道传回。
4. 终端交互与事件日志
hcdp.js将进程 stdin 设为 raw 模式,实现两键快捷键:o打开 DevTools、x退出并杀掉子进程(tools/hcdp/hcdp.js)。终端日志以“图标 + 客户端 ID + 消息”的格式打印所有流经事件,图标含义如下:
| 图标 | 含义 |
|---|---|
| ⚡ | 客户端连接(connect) |
| → | 客户端发来的命令(command from client) |
| ← | 发往客户端的响应/通知(response/notification to client) |
| ✕ | 客户端断开(disconnect) |
七、CDP Agent 与调试 API 的源码依据
hcdp 依赖的调试内核位于 API/hermes/cdp/:
- CDPDebugAPI:管理运行时级调试状态,
CDPDebugAPI::create以 Hermes 运行时为参数创建,hcdp 在RuntimeInstance构造时调用(见 API/hermes/cdp/CDPDebugAPI.h); - CDPAgent:处理 Debugger、Runtime、Profiler、HeapProfiler 域的 CDP 消息。其公开接口
CDPAgent::create(executionContextID, cdpDebugAPI, enqueueRuntimeTaskFunc, messageCallback, state)与 hcdp 的调用方式完全对应(API/hermes/cdp/CDPAgent.h);注释明确要求集成方维护一个“独占访问运行时”的任务队列,即 hcdp 中SerialExecutor+addTask的职责来源。handleCommand(std::string json)可被任意线程调用,这与 hcdp 在 IPC 主循环中直接调用它相印证。
此外,hcdp 使用的消息类型(如message::Request::fromJson、Runtime.executionContextCreated)由 API/hermes/cdp/MessageTypes.h 等头文件定义,JSON 解析则复用 Hermes 自身的 lib/Parser/JSONParser.cpp。
八、小结与适用场景
通过本文可以梳理出 hcdp 的完整工作链条:
调试客户端(Chrome/Edge DevTools) │ WebSocket (127.0.0.1:9999) ▼ hcdp.js(WebSocket 服务、日志、键盘交互、脚本源码本地应答) │ IPC 行协议(stdin/stdout):C/M/D + agentId + message ▼ hcdp.cpp(Hermes Runtime + CDPDebugAPI + 每客户端一个 CDPAgent) │ debugJavaScript 执行脚本 ▼ 待调试的 JS 脚本hcdp 的适用场景包括:快速验证 Hermes 的 CDP 调试行为、在没有完整 React Native 宿主环境的情况下调试单个 JS 脚本、以及作为研究 Hermes 调试 API(CDPDebugAPI/CDPAgent/debugJavaScript)如何集成的参考实现。需要特别说明的是,hcdp是仓库tools目录下的独立开发调试工具,若需在应用内集成 CDP 调试,官方路径仍是直接使用 API/hermes/cdp/ 中的公开 API(可参考 API/hermes/DebuggerAPI.h 与 API/hermes/cdp/CDPAgent.h 的接口注释)。
- 语言运行时
- 编译器
- 移动开发
【免费下载链接】hermes
A JavaScript engine optimized for running React Native.
相关推荐
NumPy 高级调试工具实战指南:Python 调试构建、Valgrind、C 调试器与编译器 Sanitizer
NumPy 高级调试工具实战指南:Python 调试构建、Valgrind、C 调试器与编译器 Sanitizer 导读 本文是 NumPy 开发者文档中 de
科学计算数据分析Chokidar 如何做到事件不重复触发?文件监听 5 层节流去重机制完整指南
Chokidar 如何做到事件不重复触发?文件监听 5 层节流去重机制完整指南 Chokidar 是 Node.js 生态中最流行的跨平台文件监听(file w
开发工具Koop核心功能解析:从数据转换到FeatureServer查询的完整流程
Koop核心功能解析:从数据转换到FeatureServer查询的完整流程 Koop是一个强大的JavaScript工具包,专为在Web上转换、查询和下载地理空
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考