
bytenode .jsc(V8 코드 캐시)를 JavaScript로 리버스 — 정적, 순수 Rust, 패치된 V8/Node 불필요. Node 8→26 / V8 5.8–14.6; 25k .jsc 테스트, 0 실패.
bytenode로 컴파일된 .jsc 파일을 JavaScript로 역컴파일합니다.
정적 파싱만 사용: 순수 Rust 바이너리 하나 — 패치된 V8도, Node 런타임도, 네트워크도 필요 없습니다.
Node 8.0.0 → 26.10.0 (510개 릴리스 / 36개 V8 마이너)을 지원하며, 약 25k개의 .jsc 파일이
0개의 실패로 테스트를 통과합니다 (31개 Node 라인 × 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 in this transcript
$ 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
처음 바이트는 리틀 엔디언 방식의 코드 캐시 매직이다 (cc 05 de c0 = 0xc0de05cc);
jscd info hello.jsc는 전체 헤더를 디코딩한다 (Usage에 샘플 있음).
위 출력은 두 최적화 계층을 거친 후 나오는 결과이다. JSCD_NO_OPT=1 jscd hello.jsc는
대신 원시 변환 결과를 보여준다 — V8의 레지스터 셔플링을 한 명령어씩 보여준다.
Node 22+ (V8 12.4+)에서는 일부 내장 프로퍼티 이름이 읽기 전용 힙에 존재한다. 동작 매트릭스가
다루는 29개의 V8 마이너 버전에 대해 jscd는 이름 테이블(tables/ro_map_*)을 제공하고
자동으로 해석한다 — macOS에서, 해당 테이블이 추출된 플랫폼이기 때문이다: 읽기 전용 힙
인덱스는 플랫폼별로 다르므로, 다른 시스템에서는 jscd ro-map으로 직접 빌드한 후 --ro-map을
전달해야 한다 (JSCD_NO_RO_MAP=1은 내장 테이블을 비활성화한다). 테이블이 없으면 그러한
이름은 <ro0_…> 플레이스홀더로 표시된다 — 절대 잘못된 이름으로 표시되지 않는다.
macOS / Linux — Homebrew
brew install ejfkdev/tap/jscd
Windows — Scoop
scoop bucket add ejfkdev https://github.com/ejfkdev/scoop-bucket
scoop install jscd
매니페스트 URL에서 바로, 버킷을 먼저 추가하지 않고:
scoop install https://raw.githubusercontent.com/ejfkdev/scoop-bucket/main/bucket/jscd.json
scoop install ejfkdev/scoop-bucket/jscd는 작동하지 않습니다: Scoop은bucket/app을 이미 추가한 bucket에 대해 해석하며,owner/repo경로에 대해서는 해석하지 않습니다.
미리 빌드된 바이너리 — 태그가 지정된 모든 릴리스에는 바로 실행 가능한 실행 파일이 그대로 포함됩니다 (Linux / macOS / Windows × x64 / arm64; Linux amd64는 정적 musl 빌드이며, Linux와 Windows는 UPX로 압축됨):
릴리스 페이지에서 바로 받으세요 (아카이브도, 설치 프로그램도 필요 없습니다 — 그 파일이 곧 실행 파일입니다):
curl -fLO https://github.com/ejfkdev/jscd/releases/download/v0.1.0/jscd-v0.1.0-linux-amd64
chmod +x jscd-v0.1.0-linux-amd64
./jscd-v0.1.0-linux-amd64 --version
cargo (Rust 1.96+가 설치된 모든 플랫폼)
cargo install jscd # build from crates.io
cargo binstall jscd # or fetch the release binary instead of compiling (cargo-binstall)
출처에서
cargo install --git https://github.com/ejfkdev/jscd # latest main
git clone https://github.com/ejfkdev/jscd && cd jscd
cargo build --release # -> target/release/jscd
cargo install --path . # ...or install that build into ~/.cargo/bin
Rust 1.96+가 필요합니다(swc, JS 옵티마이저는 최신 rustc가 필요합니다). 시스템 의존성은 없습니다.
scripts/release.sh vX.Y.Z는 릴리스 게이트(cargo test --release,
clippy --all-targets -- -D warnings, 그리고 엔드투엔드 스모크 scripts/ci_smoke.sh — 픽스처를
실제 .jsc로 컴파일하고, 디컴파일하고, 구문 검증하고, 실행 비교)를 실행한 다음, 메시지가 GitHub Release 설명이 되는 주석이 달린 태그를 푸시합니다. 이 태그는
.github/workflows/release.yml을 트리거하여 위에 나열된 여섯 개의 베어 바이너리를 다시 빌드합니다. 그런 다음
crates.io에 cargo publish(release.sh는 태그와 Cargo.toml 버전을 동기화 상태로 유지)하고,
python3 scripts/update_readme_help.py로 아래의 CLI 도움말을 새로 고칩니다;
Homebrew tap과
Scoop bucket은 일일
자동 업데이트 실행에서 새 릴리스를 가져갑니다.
jscd app.jsc # decompile one file to stdout
jscd app.jsc app.js # ...to a file
jscd dist/ # every .jsc under dist/ → dist-out/ (tree mirrored)
jscd dist/ out/ # ...into out/ instead
jscd info app.jsc # header fields, detected Node/V8 version
jscd disasm --filter main app.jsc
jscd ro-map probe.jsc > m.json # build a read-only-heap name table
jscd --help # bilingual help (-h, `help`, `help <SUBCOMMAND>`)
.jsc 파일이거나 *.jsc를 재귀적으로 스캔한 디렉터리입니다. 출력은 파일인 경우 기본적으로 stdout으로, 디렉터리인 경우 <INPUT>-out/(트리 미러링)으로 설정됩니다. -o / OUTPUT 인자는 다른 위치에 쓰며, -는 stdout을 의미합니다..jsc는 감지된 버전과 지원 범위를 메시지에 포함하여 거부됩니다(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 <PATH> | 모든 형식 | 출력 파일 / 디렉터리 / - (OUTPUT 인자와 동일) |
--quiet | 디렉터리 입력 | 파일별 진행 상황 숨김 |
--json | 모든 하위 명령 | 기계 판독 가능 출력 |
--filter <SUBSTR> | disasm | 이름에 SUBSTR이 포함된 함수만 출력 |
--ro-map <PATH> | decompile | jscd ro-map으로 생성된 읽기 전용 힙 이름 테이블 |
--verify | decompile | 구문 검사: 결과를 node --check로 실행 (PATH에 node 필요) |
--runtime | decompile | 실행 가능한 프리앰블 유지 (__runtime 스텁, 평탄화된 context 변수, 이름 별칭). 기본값은 꺼짐 — node로 결과물을 실행하려면 추가하세요 |
-h, --help | 모든 형식 | 도움말 출력 (단일 명령은 jscd help <SUBCOMMAND>) |
-v, -V, --version | 모든 형식 | 이름, 버전, 저장소 출력 |
CLI가 --help와 help <SUBCOMMAND>에 대해 출력하는 모든 내용을 그대로 옮긴 것입니다(python3 scripts/update_readme_help.py로 재생성).
jscd --help 및 모든 하위 명령, 그대로jscd --help
$ jscd --help
jscd v0.1.0 — V8 code cache → JavaScript
https://github.com/ejfkdev/jscd (MIT license)
Reconstructs JavaScript from bytenode-compiled .jsc files (V8 code caches).
Static parsing — no patched V8, no Node runtime. Directories are scanned
recursively.
Usage: jscd [OPTIONS] <INPUT> [OUTPUT] # decompile (default action)
jscd <SUBCOMMAND> [ARGS...] # stage-by-stage analysis
jscd help [SUBCOMMAND] | version | -h | -v | -V