Skip to content

Latest commit

 

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

鸿蒙 PC Node JIT 补丁(wxjit · 进程内签名快版)

让 HarmonyOS PC 上 Harmonybrew 的 node 摆脱 --jitless,恢复 V8 真实 JIT, 并且交互式 TUI(kimi 等)全速可用。 实测:热循环稳态 约 9 倍提速(5–10ms vs 80–110ms),MAGLEV 编译并执行; 页面翻转开销从 ~60–80ms/页 降到 ~1ms/页,键入无卡顿。 设备:MOR-W52 / HarmonyOS 6.1.0.135 / HongMeng Kernel 1.12(底座 Linux 5.10) 日期:2026-08-10


1. 这是什么

鸿蒙 PC 的 HiShell 沙箱强制 XPM 代码签名校验,任何进程无法通过 mmap(PROT_EXEC) / mprotect(...→RX) 创建可执行内存。V8 的 JIT 需要 RWX 代码页,因此被卡死,Harmonybrew 只能给 node 打 --jitless 纯解释器。

wxjit 是一个 LD_PRELOAD shim,不改 node/V8 一行代码,用用户态手段恢复 JIT:

  • 拦截 mmap/mprotect/munmap,接管 V8 对代码段的 RWX 请求(落地为 RW)。
  • V8 执行到 RW 代码页触发 SIGSEGV(W^X 在取指层强制)。
  • shim 在信号处理里:把该页代码补丁进预签名 ELF 模板 → 进程内自签名 → dlopen 让 ld-musl 建立合法 RX 映射 → mremap 搬回 V8 期望的原地址 → 重新执行。
  • V8 后续要写该页时触发写 SIGSEGV,shim 把页换回 RW(memfd 预填内容,消除零页竞态), 写完再翻回 RX。

与旧版(2026-08-10 上午版)的关键区别

旧版每次 RW→RX 翻转都要 fork+exec binary-sign-tool(~60–80ms/页), 批处理负载没问题,但 TUI/REPL 里 V8 持续打补丁,每次按键都在为签名付费, 交互不可用,只能回退 --jitless。

本版(fast edition)解决了两点:

  1. 进程内签名:binary-sign-tool sign -selfSign 1 被完整逆向并重实现 (见第 6 节)。自签名的"签名"只是 fs-verity descriptor 的 SHA-256, 无任何非对称密钥,进程内算一次不到 0.1ms。翻转总开销 ~1ms/页。 模板缺 .codesign 段时自动回退外部工具(需 binary-sign-tool 可用)。
  2. 关闭并发撕裂竞态:旧版靠 ~80ms 签名延迟"碰巧"躲过 V8 并发编译器 在快照与 mremap 之间写页的问题(实测快签名下 20% 崩溃率:SIGILL 死循环 → SIGSEGV)。本版翻转期间把页面降级为 PROT_READ 并把 pagemap 置为 状态 3(翻转中),任何并发写/执行都会立即缺页并排队等待,从结构上 消除了撕裂快照。压测 20/20 通过(修复前 8/20 失败)。

2. 目录内容

文件 说明
src/wxjit.c shim 源码(fast edition,LD_PRELOAD 库本体)
prebuilt/wxjit.so 预编译 shim(aarch64-linux-ohos,已自签)
tools/gen_templates.sh 生成 4 档 RX 代码池模板(64K/256K/1M/4M)到 $HOME/.tmp/wxjit
tools/selfsign.py 自签名算法的 Python 参考实现(与官方工具输出字节级一致)
tests/test_shim.c 独立自测(不依赖 node,验证翻转机制)
tests/rxread.c 诊断:验证 RX 页读回内容与 RW 恢复一致性
tests/bench.js / tests/bench2.js 性能对比脚本
tests/kimi_pty_test.py TUI 交互延迟测试(PTY 内驱动 kimi 键入并测回显延迟)
prebuilt/test_shim / prebuilt/rxread 预编译测试二进制(install.sh 会在设备上重新编译)
install.sh 一键安装(见下)

3. 部署到另一台鸿蒙 PC

前置条件:目标机器已装 Harmonybrew + openssh(能 ssh ohos@设备 -p 8022), 并装有 ohos-sdk(提供 clang/lld;brew install node 时通常会带上)。

# 主机侧
scp -P 8022 -r ohos-node-jit-patch ohos@127.0.0.1:/storage/Users/currentUser/Download/

# 设备侧
cd ~/Download/ohos-node-jit-patch
zsh install.sh

install.sh 做的事:生成模板到 $HOME/.tmp/wxjit → 编译 shim(失败则用预编译) → 跑 test_shim + rxread 自测 → 有 node 则跑 bench.js → 输出启用方法。

shim 不依赖任何硬编码用户路径:模板目录默认 $HOME/.tmp/wxjit (可用 WXJIT_DIR 覆盖),外部签名工具默认 $HOME/.harmonybrew/bin/binary-sign-tool (可用 WXJIT_SIGN_TOOL 覆盖,仅回退路径用)。

4. 启用方法

# 方法 A:临时,单条命令
NODE_OPTIONS= LD_PRELOAD=~/.tmp/wxjit/wxjit.so node yourscript.js

# 方法 B:固化进 ~/.zshrc(覆盖交互 shell 的 node 与 kimi)
export LD_PRELOAD=$HOME/.tmp/wxjit/wxjit.so
export NODE_OPTIONS=     # 删除/清空原有的 --jitless

注意:改动前已打开的终端仍带旧环境变量,需重开终端生效。

验证 JIT 编译:

node --trace-opt -e 'function h(n){let a=0;for(let i=0;i<n;i++)a=(a+i*i)^(i*3);return a;}h(2000000);console.log("ok")'
# 看到 "[completed compiling ... (target MAGLEV) ...]" 即 JIT 已生效

性能对比:

NODE_OPTIONS=--jitless LD_PRELOAD= node tests/bench2.js      # 基线(解释器)
NODE_OPTIONS= LD_PRELOAD=~/.tmp/wxjit/wxjit.so node tests/bench2.js   # JIT

TUI 交互验证(需要已安装 kimi):

python3 tests/kimi_pty_test.py ~/.tmp/wxjit/wxjit.so 40
# PTY-TEST PASS;本机实测 median 0ms / p90 34ms / max 291ms(40 次键入)

5. 环境变量

变量 作用
WXJIT_DBG 设为非空即在 stderr 输出完整翻转日志([t] exec-flip 等);不设则完全安静(TUI 安全)
WXJIT_DIR 模板/临时文件目录,默认 $HOME/.tmp/wxjit
WXJIT_SIGN_TOOL 外部签名工具路径(仅模板无 .codesign 段的回退路径用),默认 $HOME/.harmonybrew/bin/binary-sign-tool

罕见错误事件(翻转失败、越界 SEGV、SIGILL 详情)始终追加到 $WXJIT_DIR/fault.log,不污染 stderr。该文件不存在 = 无错误事件。

6. 自签名格式(进程内实现依据)

逆向自 binary-sign-tool sign -selfSign 1,并经字节级比对验证 (4 档模板 × 随机内容,输出与官方工具逐字节一致)。参考: 鸿蒙PC 底层开发技术详解(七):二进制自签名算法的实现。

  • ohos-sdk 的 lld 链接产物自带 4K 全零 .codesign 段(页对齐)。
  • 签名 = 填充该段前 296 字节:ElfSignInfo 头 8B(type=1, length=288)
    • fs-verity descriptor 256B + "signature" 32B。
  • descriptor 关键字段:version=1, hashAlgorithm=1(SHA-256), logBlockSize=12, signSize(算摘要时 0,落盘时 32), dataSize=文件总大小, rootHash=Merkle 树根, flags=0x10(FLAG_SELF_SIGN), csVersion=3(@offset 255)。
  • "signature" = SHA-256(descriptor with signSize=0)。无私钥、无证书。
  • Merkle 树:文件按 4K 分页(末页补零),.codesign 所在页叶哈希置零, 其余页 SHA-256;每 128 个哈希(4K)聚一层;根页 ≤4K 时对其补零后再哈希。
  • 官方工具签名前会把 PT_GNU_RELRO 程序头的 p_align 改成 8(参与哈希), 本实现同样处理。
  • 段内 296B 之后全零即可(官方工具实测也不写 Merkle tree bytes)。

验签失败排查:hilog -t kmsg | grep -E "xpm|unsigned file"。

7. 注意事项 / 已知边界

  • 冷启动:JIT 编译本身有开销,首轮含翻转约 40–60ms;短任务可能不如 jitless,长跑/TUI/服务型负载收益大(稳态 9x)。
  • node 退出时可能打一条无关紧要的段错误(musl 清理被搬移的映射), 不影响退出码与结果。
  • 仅适用 Harmonybrew 用户态 node;对系统 ArkCompiler 无效也无必要。
  • 旧终端记得重开;WXJIT_DBG=1 可用于诊断但不要在 TUI 里开(会刷屏)。

8. 复现/验证清单(换机器自检)

  1. test_shim 打印 HARNESS PASS → 翻转机制 OK
  2. rxread 打印 RXREAD DONE 且两行 OK → RX 读回/RW 恢复一致
  3. node --trace-opt 出现 completed compiling ... MAGLEV → JIT 编译 OK
  4. bench2.js 后几轮显著低于 jitless 基线 → JIT 执行 OK
  5. python3 tests/kimi_pty_test.py ~/.tmp/wxjit/wxjit.so → TUI 交互 OK

9. 性能数据(MOR-W52)

模式 bench.js 5 轮 (ms) 说明
--jitless 81 / 83 / 99 / 111 / 105 解释执行基线
旧版 wxjit(外部签名) 261 / 6 / 7 / 6 / 5 稳态同 JIT,但每次翻转 60–80ms,TUI 卡顿不可用
本版 wxjit 41–62 / ~10 / ~10 / 5–7 / 6–9 翻转 ~1ms,TUI 流畅;20/20 压测无崩溃

About

鸿蒙 PC Node JIT 补丁(wxjit · 进程内签名)

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages