把 bytenode 编译的 .jsc 还原成 JavaScript。纯静态解析:
纯 Rust 单二进制,不需要打过补丁的 V8,也不需要 Node 运行时、不联网。
支持 Node 8.0.0 → 26.10.0(510 个发布 / 36 个 V8 minor),≈ 25k 份 .jsc 通过测试、0 失败
(31 条版本线 × 41 份用例,按 bytenode 1.7.0 的编译参数);两层优化(寄存器折叠 → swc 复制传播)
提升反编译源码的可读性。
English · 中文
目录:特性 · 快速上手 · 安装 · 用法 · CLI 参考 · 支持的版本 · 实现 · 验证 · 仓库结构 · 已知限制 · 参与贡献
--runtime 补上 __runtime 桩,反编译结果直接 node 可跑;--verify 再过一遍
node --check。JSCD_NO_OPT=1 可对照原样。ReferenceError 的标识符。$ cat hello.js
function greet(name) {
return "hello " + name;
}
console.log(greet("world"));
$ npm i -g bytenode && bytenode -c hello.js # 本文用 Node 20.20.2
$ xxd -l 48 hello.jsc
00000000: cc05 dec0 0bc2 e400 9200 0000 e521 2eaa .............!..
00000010: f002 0000 0000 0000 011c 5401 2006 a860 ..........T. ..`
00000020: 0000 0000 0600 0000 010c 4c60 0000 0000 ..........L`....
$ jscd hello.jsc
console.log(greet("world"));
function greet(a0) {
return "hello " + a0;
}
$ jscd hello.jsc --runtime > hello.out.js && node hello.out.js
hello world
jscd info hello.jsc 能解出上面的头部(样例见用法);JSCD_NO_OPT=1 则可看优化前的原样。
macOS / Linux —— Homebrew
brew install ejfkdev/tap/jscd
Windows —— Scoop
scoop bucket add ejfkdev https://github.com/ejfkdev/scoop-bucket
scoop install jscd
不先加 bucket、直接按清单 URL 装也可以:
scoop install https://raw.githubusercontent.com/ejfkdev/scoop-bucket/main/bucket/jscd.json
预编译产物 —— 见 Releases。
cargo(任何有 Rust 1.96+ 的平台)
cargo install jscd # 从 crates.io 源码构建
cargo binstall jscd # 或者:直接拉 release 里的可执行文件,不编译(cargo-binstall)
源码构建
cargo install --git https://github.com/ejfkdev/jscd # 直接装 main
git clone https://github.com/ejfkdev/jscd && cd jscd
cargo build --release # -> target/release/jscd
cargo install --path . # ……或把这份构建装进 ~/.cargo/bin
需要 Rust 1.96+(swc 优化器要求较新的 rustc),无系统依赖。
scripts/release.sh vX.Y.Z 先过发版门禁(cargo test --release、
clippy --all-targets -- -D warnings,以及端到端冒烟 scripts/ci_smoke.sh —— fixture 真编译成
.jsc → 反编译 → 语法门禁 + 整脚本行为对拍),再推一个带说明的 annotated tag;tag 说明就是
GitHub Release 的描述。tag 一推即触发 .github/workflows/release.yml,在上面那六个平台重建裸
二进制。之后 cargo publish 发到 crates.io(release.sh 保证 tag 与 Cargo.toml 版本一致),
再跑一次 python3 scripts/update_readme_help.py 刷新下面的 CLI 帮助;
Homebrew tap 与
Scoop bucket 每天自动更新时会带上新版本。
jscd app.jsc # 单文件反编译到 stdout
jscd app.jsc app.js # ……写到文件
jscd dist/ # dist/ 下所有 .jsc → dist-out/(保持层级)
jscd dist/ out/ # ……改写到 out/
jscd info app.jsc # 头部字段与识别出的 Node/V8 版本
jscd disasm --filter main app.jsc
jscd ro-map probe.jsc > m.json # 生成只读堆名表
jscd --help # 双语帮助(-h、`help`、`help <子命令>`)
.jsc 文件,或目录(递归找 *.jsc);输出默认:文件 → stdout,目录 → 输入同级的
<输入名>-out/(保持层级)—— -o / 位置参数 OUTPUT 可指定文件或目录,- 表示 stdout。jscd info 的 supported: yes|no
同理),不会静默吐一份只有运行时前导的文件。jscd ro-map 自己建一张再 --ro-map 传入(JSCD_NO_RO_MAP=1 可关掉内嵌表)。对不上的表会被忽略:
宁可留 <ro0_…> 占位,也不给错名字。JSCD_LANG(再依次看 LC_ALL / LC_MESSAGES / LANGUAGE / LANG / LC_CTYPE):
zh* 中文、其余英文,JSCD_LANG=zh|en 可强制。| 旗标 | 适用 | 作用 |
|---|---|---|
-o, --output <路径> | 全部形态 | 输出文件 / 目录 / -(与位置参数 OUTPUT 等价) |
--quiet | 目录输入 | 不打印逐文件进度 |
--json | 全部子命令 | 机器可读输出 |
--filter <子串> | disasm | 只输出名字含该子串的函数 |
--ro-map <路径> | decompile | 只读堆名表,由 jscd ro-map 生成 |
--verify | decompile | 语法门禁:产物过一遍 node --check(需要 PATH 里有 node) |
--runtime | decompile | 保留可运行前导(__runtime 占位实现 + 上下文变量提升 + 别名块)。默认不带;想让产物能 node 跑就加上 |
-h, --help | 全部形态 | 打印帮助(jscd help <子命令> 看单个命令) |
-v, -V, --version | 全部形态 | 打印名字、版本与仓库地址 |
--help 与 help <子命令> 的输出原样收在这里(刷新用 python3 scripts/update_readme_help.py)。
jscd --help 与每个子命令,原样输出jscd --help
$ jscd --help
jscd v0.1.0 — V8 代码缓存 → JavaScript
https://github.com/ejfkdev/jscd (MIT license)
把 bytenode 编译的 .jsc(V8 代码缓存)还原成 JavaScript。纯静态解析 ——
不需要打过补丁的 V8,也不需要 Node 运行时;目录输入会递归处理。
用法:jscd [选项] <输入> [输出] # 默认动作就是反编译
jscd <子命令> [参数...] # 分阶段分析
jscd help [子命令] | version | -h | -v | -V
输入 .jsc 文件,或目录(递归找 *.jsc)。
输出 文件、目录,或 `-` 表示 stdout:
<文件.js> 单个输入文件时写这个文件
<目录> 目录输入时写入该目录,保持层级
默认:文件输入 → stdout;目录输入 → 输入同级的 <输入名>-out/
选项:
-o, --output <路径> 输出文件 / 目录 / -(与位置参数 OUTPUT 等价)
--ro-map <路径> 只读堆名表(decompile 用,还原内建属性名)
--verify 用 `node --check` 实编译校验产物(decompile)
--runtime 保留可运行前导(`__runtime` 占位实现),产物能直接 `node` 跑;
默认**不带**,只输出还原出来的代码本身
--filter <子串> 只输出名字含该子串的函数(disasm)
--json 机器可读输出(info/strings/functions/disasm/ro-map)
--quiet 不打印逐文件进度(目录输入时)
-h, --help 打印本帮助
-v, -V, --version 打印名字、版本与仓库地址
JSCD_LANG=zh|en 强制帮助/报错的语言(默认自动识别)
子命令:
decompile 重建 JavaScript(默认动作,等价于直接 `jscd <输入>`)
info 头部字段与识别出的 Node/V8 版本
strings 常量池里的字符串与符号
functions SharedFunctionInfo 树(名字、形参、字节码长度)
disasm 字节码反汇编(View8 兼容文本)
ro-map 从探针缓存生成只读堆名表
help 打印帮助(可跟子命令名)
version 打印名字、版本与仓库地址
示例:
jscd app.jsc 反编译并打到 stdout
jscd app.jsc app.js 反编译到文件
jscd dist/ dist/**.jsc → dist-out/**.js
jscd dist/ out/ ……改写到 out/
jscd decompile --ro-map m.json a.jsc 带只读堆名表
jscd info app.jsc 头部字段与 V8 版本
jscd disasm --filter main app.jsc 只看某个函数的字节码
jscd help disasm 单个子命令的详细帮助(带示例)
jscd ro-map probe.jsc -o names.json
生成只读堆名表(见 `jscd help ro-map`)
jscd help decompile
$ jscd help decompile
jscd decompile — 重建 JavaScript(默认动作,等价于直接 `jscd <输入>`)
https://github.com/ejfkdev/jscd (MIT license)
用法:jscd decompile [选项] <输入> [输出]
jscd [选项] <输入> [输出] # 等价写法
输入是 .jsc 文件,或目录(递归找 *.jsc)。输出是文件、目录或 `-`(stdout)。
默认:文件输入 → stdout;目录输入 → 输入同级的 <输入名>-out/。