Compare commits

..
90 Commits
Author SHA1 Message Date
pancake f5a033b45e Release 1.3.2 2026-06-11 16:22:12 +02:00
pancake 78eaad9e55 Fold try_get getters into their consumer 2026-06-11 13:43:01 +02:00
pancake 6eb777ae11 Forward-substitute register copies with value-safety analysis 2026-06-11 13:15:15 +02:00
pancake d23ec88b12 Cut more decompiler clutter for single-use temporary assignments 2026-06-11 12:27:57 +02:00
pancake 0e49c04565 Render array literals inline to cut AST clutter 2026-06-11 12:13:10 +02:00
pancake 84abea0501 Bound closure inlining to stop module-graph explosion 2026-06-11 12:06:07 +02:00
pancake b84cc1a37c Fix truncation noise, closure terminator, and token spacing in output 2026-06-11 11:51:23 +02:00
pancake 1489606813 Drop the dead materialized compare after a while fold 2026-06-11 11:36:38 +02:00
pancake 41df2c071f Fold materialized-guard loops into while in the decompiler 2026-06-11 11:20:57 +02:00
pancake 5d0fab69c1 Reconstruct break statements for loop-exit jumps in the decompiler 2026-06-11 11:05:25 +02:00
pancake 47f864ae72 Reconstruct if/else, short-circuit, while and for(;;) in the decompiler 2026-06-11 10:36:27 +02:00
pancake 93a276b894 Structure backward conditional jumps as do-while loops in the decompiler 2026-06-11 09:34:45 +02:00
pancake 4818337181 Support multiple nested trycatch 2026-06-11 01:29:22 +02:00
pancake b557b3a00a Move pd:hi into r2hermes-H for the headers 2026-06-11 00:24:49 +02:00
pancake 6d99e666d9 Move pd:he into r2hermes-E 2026-06-11 00:24:09 +02:00
pancake 9ee9be2b53 Structure try/catch for return-terminated try bodies 2026-06-10 23:53:06 +02:00
pancake 01f049a7c4 Recovers nested if-block structuring in catch functions 2026-06-10 23:36:32 +02:00
pancake f5f2efd37c Fold negation into conditions to drop double negatives 2026-06-10 23:13:55 +02:00
pancake 49282811c0 Support case fallthrough grouping in the decompiler 2026-06-10 23:00:17 +02:00
pancake 2781a9e7f9 Structure simple for-in loops with continue instead of gotos 2026-06-10 21:33:20 +02:00
pancake af303a0a52 Name captured closure vars instead of raw env_load/env_store 2026-06-10 21:16:51 +02:00
pancake 1c4714eb71 Fix StoreNPToEnvironment statements silently dropped 2026-06-10 19:11:21 +02:00
pancake 8a53361ff1 Implement -Lr to list literals as r2 commands 2026-06-10 18:55:43 +02:00
pancake 4c25c3d680 Rename -Lr to -LR 2026-06-10 18:48:40 +02:00
pancake fac40c417c Implement r2hermes-Lx to show xrefs to slps 2026-06-10 18:46:32 +02:00
pancake 5d5a5e0a67 Decompile SwitchImm jump tables as real switch/case dispatch 2026-06-10 18:43:04 +02:00
pancake 1623480b8b Implement -Lq to quietly list vaddr for SLPs 2026-06-10 18:41:48 +02:00
pancake eab7978191 Fix parameter calls for generic method calls 2026-06-10 18:32:37 +02:00
pancake d06b250947 Expose real string size into the RAnalOp.ptrSize 2026-06-10 18:14:43 +02:00
pancake 67bfe95cfa Implicit scan for literals, remove -Ls flag 2026-06-10 18:13:30 +02:00
pancake 33ce3f5f5b Wrap license 2026-06-10 17:53:36 +02:00
pancake 8049145c71 Fix try/catch/throw/typeof/instanceof with constructor calls 2026-06-10 17:37:48 +02:00
pancake d9567ea001 Fix parameter loading arguments in the decompiler 2026-06-10 17:25:28 +02:00
pancake 0b17938421 Remove dead and duplicated code fixing memleaks in the way 2026-06-10 17:15:12 +02:00
pancake 5334a8f048 Rename all the hbc. vars to r2hermes. 2026-06-10 16:44:11 +02:00
pancake 9984f7800e pd:hL is now fully moved to r2hermes-L 2026-06-10 16:42:41 +02:00
pancake 3676c6af71 Emit unindented labels and add api helpers for goto/braces 2026-06-10 16:35:50 +02:00
dependabot[bot] 67f1680668 ##build Bump actions/download-artifact from 4 to 8
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 8.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v4...v8)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '8'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <[email protected]>
2026-06-09 23:02:34 +02:00
pancake 3e020c5892 Fix bytecode parser for switch statements and fix dead code elimination in decompiler 2026-06-09 21:19:25 +02:00
pancake f30fc67a56 Remove deadcode from cfg in the decompiler 2026-06-09 20:40:18 +02:00
pancake 6dda1f2a05 Fix address of function problem when decompiling 2026-06-09 19:44:02 +02:00
pancake 6904cc5a46 Handle invalid r2hermes commands 2026-06-09 19:32:54 +02:00
pancake 06104892e4 Implement -Sr to show the raw input used for SBOM 2026-06-09 19:24:10 +02:00
pancake 4e98c0e3bf Allow spaces after 'r2hermes' and '-' 2026-06-09 19:20:25 +02:00
pancake 4c0de18d7c Implement r2hermes-S for SBOM (CycloneDX format with -Sj) 2026-06-09 19:20:22 +02:00
pancake 2b50f46206 Rewrite the esil printer with macros and save -800LOC 2026-06-09 19:06:52 +02:00
pancake 753e01fdda Add help for r2hermes-h 2026-06-09 18:59:21 +02:00
pancake 007cdc403e Reduce LOC cleaning up printable utf16/ascii strings 2026-06-09 18:52:28 +02:00
pancake ea6d64532c Deduplicate decode_operands into a separate helper 2026-06-09 18:47:37 +02:00
pancake b55860249c Introduce 'r2hermes' command as a hint for pd:h for now 2026-06-09 18:44:25 +02:00
pancake bad277e5de u16le/u32le helpers to avoid some inline code smells 2026-06-09 18:42:39 +02:00
pancake 65b2677053 Improve stringbuffer growing algorithm and cleanup dupe 2026-06-09 18:30:32 +02:00
pancake 90d1d692e0 Enforce asm.cpu parsing always starting with 'v' 2026-06-09 18:28:07 +02:00
pancake 4cca427d1b Create unpack_function_flags helper to reduce more dupcode 2026-06-09 18:19:16 +02:00
pancake c35f58f6d9 Simplify some BufferReader decissions 2026-06-09 18:08:16 +02:00
pancake c1bdf1bb1f Format code and sanitize identifiers once 2026-06-09 17:56:36 +02:00
pancake 535611437d Extract hbc_operand_value into a helper to avoid dupcode 2026-06-09 17:51:50 +02:00
pancake 3696b78774 Implement hbc_operand_name helper and use it to avoid duplicate code 2026-06-09 17:35:42 +02:00
pancake 953d6b9d7b Fix missing license headers 2026-06-09 17:07:18 +02:00
pancake fd948123a2 Remove some hardcoded warnings from the library code 2026-06-09 10:11:00 +02:00
pancake 822beaf2f7 Release 1.3.0 2026-06-03 10:07:28 +02:00
pancake 9d7efe34f9 Fix build for r2-6.1.6 2026-06-03 10:07:25 +02:00
Abhi b131b263b6 Fix license identifier to BSD-3-Clause in r2 plugin 2026-05-13 03:18:27 +05:30
pancake 35f2231e0a Use the radareorg/github-actions 2026-05-01 09:24:15 +02:00
pancake b18aeb94c7 Add dist/debian and build package in the ci 2026-04-30 18:39:44 +02:00
pancake 9c57cba8b5 Release 1.2.8 2026-04-28 16:33:08 +02:00
pancake 9fb8ec1393 Add ih/iH header dumping support 2026-04-24 01:55:48 +02:00
pancake 02ee8c6dea Bottleneck found and got 26x speedup loading times 2026-04-24 01:46:00 +02:00
pancake c9384a7f3c Add pd:he to list all functions with evals 2026-04-24 01:31:52 +02:00
pancake d73ec29dc8 Add the ability to list cjs modules and show them as il libs 2026-04-24 01:24:05 +02:00
pancake bfae644555 SourceLine exposure and strip details in rbininfo
* Honors bin.dbginfo for loding faster
* Uses this info to set the stripped field
2026-04-24 01:12:41 +02:00
pancake 56c943a4ca Add support for imports and exports via opcode scan 2026-04-24 00:01:56 +02:00
pancake 21231841f5 Release 1.2.6 2026-04-21 17:08:40 +02:00
dependabot[bot] e7985ec5e4 GhActionRelease from 261 to 300 2026-04-21 01:27:08 +02:00
pancake 5f830f3dc8 Simplify xps integration with r2 2026-04-20 19:19:10 +02:00
pancake 74acfedc8c Fix map permissions for the bytecode section 2026-04-20 15:20:32 +02:00
pancake 9fd443304a Refactor the flag parser for the cli tool (-200LOC) 2026-04-20 14:56:25 +02:00
pancake 55460d4253 Use base address for the analysis info to match the virtual mapping 2026-04-20 14:54:34 +02:00
pancake 55eb2dd38d Redeisgned support for buffer literals in cli, api and r2 plugin 2026-04-20 14:22:27 +02:00
pancake 57a6817d9c Make fmt 2026-04-20 00:16:42 +02:00
pancake 2a0ca11c6b Ass maxast option for the decompiler 2026-04-20 00:16:06 +02:00
pancake 55f1658b23 Use r2-6.1.4 in the ci 2026-04-19 23:46:55 +02:00
pancake 40cec248c0 Fix vaddr addressing for sections and symbols 2026-04-19 20:45:53 +02:00
pancake e21259b873 Add codemeta json support for the decompiler output 2026-04-18 11:06:29 +02:00
pancake 04750b84cb Fix call operand semantics and decompiler UAFs 2026-04-17 18:46:28 +02:00
pancake 11284e7081 Release 1.2.4 2026-04-12 23:09:53 +02:00
Ahmeth4n 5ff2a61c2c Operand handling bugs fixed 2026-04-12 14:04:00 +02:00
Ahmethan G. 29c1cadb98 Fix operand order bug in the decompiler 2026-04-12 10:52:14 +02:00
pancake a1d520c845 Reject unexpected extra operands in encoder parser + tests 2026-04-10 16:24:26 +02:00
pancake 40be994134 Bound checks for strings and bigint enumerations 2026-04-10 15:38:11 +02:00
89 changed files with 8530 additions and 3934 deletions

No files matched your search

+43 -14
View File
@@ -1,8 +1,5 @@
name: CI
env:
R2V: 6.0.8
permissions:
contents: write
@@ -26,12 +23,9 @@ jobs:
run: sudo apt-get update -y && sudo apt-get install -y build-essential autoconf automake libtool pkg-config
- name: Build library
run: make
- name: Downloading Radare2 Debian packages
run: |
wget -O r2.deb https://github.com/radareorg/radare2/releases/download/${{env.R2V}}/radare2_${{env.R2V}}_amd64.deb
wget -O r2-dev.deb https://github.com/radareorg/radare2/releases/download/${{env.R2V}}/radare2-dev_${{env.R2V}}_amd64.deb
- name: Installing Radare2
run: sudo dpkg -i r2.deb r2-dev.deb
- name: Install radare2
id: r2
uses: radareorg/github-actions@v1
- name: Build r2 plugin
run: make r2
- name: Run testsuite
@@ -52,10 +46,37 @@ jobs:
echo "tag=" >> $GITHUB_OUTPUT
fi
release:
name: Create release and upload tarball
debian:
name: Build Debian package
runs-on: ubuntu-latest
needs: [build]
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Install build deps
run: sudo apt-get update -y && sudo apt-get install -y build-essential autoconf automake libtool pkg-config
- name: Install radare2
id: r2
uses: radareorg/github-actions@v1
- name: Build Debian package
run: make -C dist/debian SUDO=
- name: Upload Debian package artifact
uses: actions/upload-artifact@v7
with:
name: debian-package
path: dist/debian/*.deb
if-no-files-found: error
release:
name: Create release and upload artifacts
runs-on: ubuntu-latest
needs: [build, debian]
if: needs.build.outputs.tag != ''
steps:
@@ -71,11 +92,19 @@ jobs:
git archive --format=tar.gz --prefix=libhbc-$TAG/ $TAG -o libhbc-$TAG.tgz
ls -l libhbc*
- name: Download Debian package artifact
uses: actions/download-artifact@v8
with:
name: debian-package
path: release-artifacts
- name: Create GitHub Release
uses: softprops/action-gh-release@v2.6.1
uses: softprops/action-gh-release@v3.0.0
with:
tag_name: ${{ needs.build.outputs.tag }}
files: ./libhbc-${{ needs.build.outputs.tag }}.tgz
files: |
./libhbc-${{ needs.build.outputs.tag }}.tgz
./release-artifacts/*.deb
make_latest: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+5 -15
View File
@@ -1,8 +1,5 @@
name: Meson Build
env:
R2V: 6.0.8
on:
push:
workflow_dispatch: {}
@@ -21,16 +18,9 @@ jobs:
- name: Install build deps
run: sudo apt-get update -y && sudo apt-get install -y build-essential meson ninja-build pkg-config
- name: Build library
- name: Install radare2
id: r2
uses: radareorg/github-actions@v1
- name: Build library and r2 plugin with meson
run: meson setup builddir && meson compile -C builddir
- name: Downloading Radare2 Debian packages
run: |
wget -O r2.deb https://github.com/radareorg/radare2/releases/download/${{env.R2V}}/radare2_${{env.R2V}}_amd64.deb
wget -O r2-dev.deb https://github.com/radareorg/radare2/releases/download/${{env.R2V}}/radare2-dev_${{env.R2V}}_amd64.deb
- name: Installing Radare2
run: sudo dpkg -i r2.deb r2-dev.deb
- name: Build r2 plugin with meson
run: meson compile -C builddir
-3
View File
@@ -1,8 +1,5 @@
name: CI
env:
R2V: 6.0.8
permissions:
contents: write
+4 -12
View File
@@ -1,8 +1,5 @@
name: Windows Build
env:
R2V: 6.0.8
on:
push:
workflow_dispatch: {}
@@ -29,16 +26,11 @@ jobs:
- name: Install dependencies
shell: bash
run: |
pip install meson ninja wget
choco install wget
pip install meson ninja
- name: Download Radare2 release
shell: bash
run: |
python -m wget https://github.com/radareorg/radare2/releases/download/${{env.R2V}}/radare2-${{env.R2V}}-w64.zip -o r2.zip
unzip r2.zip
mv radare2-${{env.R2V}}-w64 radare2 || mv radare2-w64-${{env.R2V}} radare2
cp -f radare2/include/libr/r_util/r_event.h radare2/include/libr/r_event.h || true
- name: Install radare2
id: r2
uses: radareorg/github-actions@v1
- name: Build with Meson
shell: cmd
+1 -1
View File
@@ -1,5 +1,5 @@
build/
bin/libhbctool
bin/
third_party/
*.d
*.o
+22 -5
View File
@@ -1,11 +1,28 @@
Copyright 2025-2026 pancake <pancake@nopcode.org>
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
3. Neither the name of the copyright holder nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS”
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+13 -3
View File
@@ -18,8 +18,9 @@ DISASM_SRC = $(wildcard $(SRC_DIR)/disassembly/*.c)
DECOMPILE_SRC = $(wildcard $(SRC_DIR)/decompilation/*.c)
OPCODES_SRC = $(filter-out $(SRC_DIR)/opcodes/%.inc.c,$(wildcard $(SRC_DIR)/opcodes/*.c))
LIB_SRC = $(UTILS_SRC) $(PARSERS_SRC) $(DISASM_SRC) $(DECOMPILE_SRC) $(OPCODES_SRC) \
$(SRC_DIR)/hbc.c $(SRC_DIR)/opcodes/encoder.c $(SRC_DIR)/opcodes/decoder.c $(SRC_DIR)/r2.c
$(SRC_DIR)/hbc.c $(SRC_DIR)/literals_api.c $(SRC_DIR)/opcodes/encoder.c $(SRC_DIR)/opcodes/decoder.c $(SRC_DIR)/r2.c
MAIN_SRC = src/tool/libhbctool.c
HBC_HEADERS = $(shell find include -name '*.h')
## Object files
LIB_OBJ = $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/%.o,$(LIB_SRC))
@@ -55,11 +56,11 @@ $(STATIC_LIB): $(LIB_OBJ)
$(BIN_FILE): $(STATIC_LIB) $(MAIN_OBJ) | $(shell mkdir -p $(BIN_DIR))
$(CC) $(CFLAGS) -o $@ $(MAIN_OBJ) -L$(BUILD_DIR) -lhbc
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c | $(VH)
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c $(HBC_HEADERS) | $(VH)
@mkdir -p $(dir $@)
$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@
$(MAIN_OBJ): $(MAIN_SRC) | $(VH)
$(MAIN_OBJ): $(MAIN_SRC) $(HBC_HEADERS) | $(VH)
$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@
clean:
@@ -72,6 +73,15 @@ TEST_DIR = tests
TEST_SRC = $(wildcard $(TEST_DIR)/*.c)
TEST_BIN = $(BIN_DIR)/run_tests
$(TEST_BIN): $(STATIC_LIB) $(TEST_SRC) | $(shell mkdir -p $(BIN_DIR))
$(CC) $(CFLAGS) $(INCLUDES) -o $@ $(TEST_SRC) -L$(BUILD_DIR) -lhbc
test: $(BIN_FILE) $(TEST_BIN)
$(TEST_BIN)
$(BIN_FILE) sl test/bins/hbc/bespoke_eval.hbc | grep -q '0x000000b4 f=0 +0x4 yes.js:1:5 stmt=0'
$(BIN_FILE) h test/bins/hbc/bespoke_eval.hbc | grep -q 'Source Locations: 13 bytes'
$(BIN_FILE) h test/bins/hbc/index.android.bundle | grep -q 'Source Locations: 0 bytes'
$(BIN_FILE) libs test/bins/hbc/index.android.bundle | grep -q 'react-native'
r2:
$(MAKE) clean
+19 -10
View File
@@ -70,7 +70,6 @@ Usage: pd:h[subcommand]
pd:hc [id] - Decompile function by id
pd:ha - Decompile all functions
pd:hf - List all functions
pd:hi - Show file information and hash status
pd:hj [id] - JSON output for function
pd:ho [id] - Decompile with offsets (addresses) per statement
pd:hoa - Decompile all with offsets
@@ -78,6 +77,16 @@ Usage: pd:h[subcommand]
.(fix-hbc) - Fix/update footer hash (for binary patching)
```
```
[0x00000000]> r2hermes-?
Usage: r2hermes[-arg] # see also pd:h for decompilation
r2hermes-h - help message (same as r2hermes-?, see pd:h? too)
r2hermes-E - List functions containing eval instructions
r2hermes-H - Show file information and hash status
r2hermes-L[?] - SLP literal cache: list/scan/reset/format/toggle
r2hermes-S[jr?] - emit SBOM from SLP literals (j=CycloneDX JSON, r=raw input)
```
### Examples
```bash
@@ -94,7 +103,7 @@ Usage: pd:h[subcommand]
[0x00000000]> pd:hf
# Show file info and hash status
[0x00000000]> pd:hi
[0x00000000]> r2hermes-H
# Fix footer hash (for binary patching)
[0x00000000]> .(fix-hbc)
@@ -106,7 +115,7 @@ Hermes validates a SHA1 footer hash at runtime. When patching binaries:
```bash
# Check hash status
r2 file.hbc -qc 'pd:hi'
r2 file.hbc -qc 'r2hermes-H'
# Fix/add footer hash (works with or without existing footer)
r2 -wqc '.(fix-hbc)' file.hbc
@@ -116,19 +125,19 @@ r2 -wqc '.(fix-hbc)' file.hbc
```bash
# Pretty-print literals (objects, arrays)
e hbc.pretty_literals=true
e r2hermes.pretty_literals=true
# Suppress comments in output
e hbc.suppress_comments=false
e r2hermes.suppress_comments=false
# Show bytecode offsets per statement
e hbc.show_offsets=false
e r2hermes.show_offsets=false
# Skip decompiler passes (for debugging)
e hbc.skip_pass1=false # Metadata collection
e hbc.skip_pass2=false # Code transformation
e hbc.skip_pass3=false # For-in loop parsing
e hbc.skip_pass4=false # Closure variable naming
e r2hermes.skip_pass1=false # Metadata collection
e r2hermes.skip_pass2=false # Code transformation
e r2hermes.skip_pass3=false # For-in loop parsing
e r2hermes.skip_pass4=false # Closure variable naming
```
## Dependencies
+20
View File
@@ -0,0 +1,20 @@
PACKAGE=r2hermes
VERSION=$(shell grep version ../../meson.build | head -1 | cut -d "'" -f 2)
DEPENDS=radare2
SECTION=user/shell
PRIORITY=optional
MAINTAINER=pancake <[email protected]>
UNAMEM=$(shell uname -m)
DPKG_ARCH=$(shell command -v dpkg >/dev/null 2>&1 && dpkg --print-architecture)
ifneq ($(DPKG_ARCH),)
ARCH=$(DPKG_ARCH)
else ifeq ($(UNAMEM),x86_64)
ARCH=amd64
else ifeq ($(UNAMEM),aarch64)
ARCH=arm64
else ifeq ($(UNAMEM),arm64)
ARCH=arm64
else
ARCH=$(UNAMEM)
endif
+2
View File
@@ -0,0 +1,2 @@
Hermes bytecode tools and radare2 plugins
r2hermes provides libhbctool, libhbc, and radare2 plugins to inspect, disassemble, assemble, and decompile React Native Hermes bytecode files.
+42
View File
@@ -0,0 +1,42 @@
include ./CONFIG
UNAME=$(shell uname)
SUDO?=sudo
PWD=$(shell pwd)
PACKAGE_DIR?=$(PWD)
R2_VERSION=$(shell r2 -qv 2>/dev/null || echo last)
R2PLUGDIR?=/usr/lib/radare2/$(R2_VERSION)
R2_PLUGINS=arch_hbc.so asm_hbc.so bin_hbc.so core_hbc.so
all: root
$(SUDO) rm -rf control data
$(MAKE) clean
mkdir -p data
cp -rf root/* data
$(MAKE) control
$(MAKE) deb
root:
ifneq ($(UNAME),Linux)
@echo "Debian packages must be built on Linux so radare2 plugins are built as .so files."
@false
else
cd ../.. && $(MAKE) clean && $(MAKE) && $(MAKE) -C src/r2
rm -rf root
mkdir -p root/usr/bin
cp -f ../../bin/libhbctool root/usr/bin/
mkdir -p root/usr/lib
cp -f ../../build/libhbc.a root/usr/lib/
mkdir -p root/usr/include
cp -rf ../../include/hbc root/usr/include/
mkdir -p root/$(R2PLUGDIR)
for plugin in $(R2_PLUGINS); do cp -f ../../src/r2/$$plugin root/$(R2PLUGDIR)/; done
endif
purge: clean
rm -rf root
summary:
echo $(VERSION)
include deb.mk
+85
View File
@@ -0,0 +1,85 @@
# Create .deb without using dpkg tools.
#
# Author: Tim Wegener <[email protected]>
#
# Use 'include deb.mk' after defining the user variables in a local
# makefile.
ifeq ($(shell uname),Darwin)
MD5SUM=md5
else
MD5SUM=md5sum
endif
GAWK=awk
PACKAGE_DIR=$(shell pwd)
CONTROL_EXTRAS ?= ${wildcard preinst postinst prerm postrm}
${PACKAGE_DIR}/control: ${PACKAGE_DIR}/data ${CONTROL_EXTRAS} DESCR \
${ICON_SOURCE}
mkdir -p $@
ifneq (${CONTROL_EXTRAS},)
cp ${CONTROL_EXTRAS} $@
endif
echo "Package: ${PACKAGE}" > $@/control
echo "Version: ${VERSION}" >> $@/control
echo "Section: ${SECTION}" >> $@/control
echo "Priority: ${PRIORITY}" >> $@/control
echo "Architecture: ${ARCH}" >> $@/control
ifneq (${DEPENDS},)
echo "Depends: ${DEPENDS}" >> $@/control
endif
echo "Installed-Size: ${shell du -s ${PACKAGE_DIR}/data|cut -f1}" \
>> $@/control
echo "Maintainer: ${MAINTAINER}" >> $@/control
printf "Description:" >> $@/control
cat DESCR | ${GAWK} '{print " "$$0;}' >> $@/control
cd ${PACKAGE_DIR}/data && find . -type f -exec ${MD5SUM} {} \; \
| sed -e 's| \./||' \
> $@/md5sums
${PACKAGE_DIR}/debian-binary:
echo "2.0" > $@
${PACKAGE_DIR}/clean:
rm -rf ${PACKAGE_DIR}/data ${PACKAGE_DIR}/control ${PACKAGE_DIR}/build *.deb
${PACKAGE_DIR}/build: ${PACKAGE_DIR}/debian-binary ${PACKAGE_DIR}/control \
${PACKAGE_DIR}/data
rm -rf $@
mkdir $@
cp ${PACKAGE_DIR}/debian-binary $@/
cd ${PACKAGE_DIR}/control && tar czvf $@/control.tar.gz *
cd ${PACKAGE_DIR}/data && \
COPY_EXTENDED_ATTRIBUTES_DISABLE=true \
COPYFILE_DISABLE=true \
tar cpzvf $@/data.tar.gz *
${PACKAGE_DIR}/${PACKAGE}_${VERSION}_${ARCH}.deb: ${PACKAGE_DIR}/build
ar -rc $@ $</debian-binary $</control.tar.gz $</data.tar.gz
.PHONY: data
data: ${PACKAGE_DIR}/data
.PHONY: control
control: ${PACKAGE_DIR}/control
.PHONY: build
build: ${PACKAGE_DIR}/build
.PHONY: clean
clean: ${PACKAGE_DIR}/clean $(EXTRA_CLEAN)
rm -f debian-binary
.PHONY: deb
deb: ${PACKAGE_DIR}/${PACKAGE}_${VERSION}_${ARCH}.deb
clobber::
rm -rf ${PACKAGE_DIR}/debian_binary ${PACKAGE_DIR}/control \
${PACKAGE_DIR}/data ${PACKAGE_DIR}/build
push:
scp *.deb radare.org:/srv/http/radareorg/cydia/debs
mrproper: clean
rm -rf root
-339
View File
@@ -1,339 +0,0 @@
add=binary add of two registers (numeric or string concatenation)
add_empty_string=convert a value to string by concatenating with the empty string
add_n=numeric add (operands known to be numeric)
add_own_private_by_sym=add a private own property using a symbol key
add_s=string add (operands known to be strings)
add32=32-bit integer add
async_break_check=check if execution should break asynchronously
bit_and=bitwise AND of two registers
bit_not=bitwise NOT of a register
bit_or=bitwise OR of two registers
bit_xor=bitwise XOR of two registers
call=call a function with a given number of arguments
call_builtin=call a builtin function by id
call_builtin_long=call a builtin function by id (long form)
call_direct=call a function whose code address is statically known
call_direct_long_index=call a function with a long function index
call_long=call a function with a long argument count
call_require=call the require builtin to import a module
call_with_new_target=call a function passing an explicit new.target
call_with_new_target_long=call with new.target (long form)
call1=call a function with 1 argument
call2=call a function with 2 arguments
call3=call a function with 3 arguments
call4=call a function with 4 arguments
catch=mark catch handler entry, receives the thrown exception
coerce_this_n_s=coerce 'this' to an object in non-strict mode
coerce_this_ns=coerce 'this' to an object in non-strict mode
complete_generator=mark generator as completed
construct=invoke a constructor with a given number of arguments
construct_long=invoke a constructor (long form)
create_async_closure=create an async closure
create_async_closure_long_index=create an async closure (long function index)
create_base_class=create a class with no superclass
create_base_class_long_index=create a base class (long function index)
create_closure=create a closure from a function
create_closure_long_index=create a closure (long function index)
create_derived_class=create a class that extends a superclass
create_derived_class_long_index=create a derived class (long function index)
create_environment=create a new lexical environment for the current function
create_function_environment=create the function's lexical environment
create_generator=create a generator function instance
create_generator_closure=create a generator closure
create_generator_closure_long_index=create a generator closure (long function index)
create_generator_long_index=create a generator (long function index)
create_inner_environment=create an inner (nested) lexical environment
create_private_name=create a unique private name symbol
create_reg_exp=create a RegExp object from a pattern and flags
create_this=create the 'this' object for a constructor call
create_this_for_new=allocate 'this' for a 'new' expression
create_this_for_super=allocate 'this' for a super constructor call
create_top_level_environment=create the top-level lexical environment
debugger=trigger the JavaScript debugger statement
debugger_check=check if a debugger break is requested
debugger_check_break=check and yield to debugger if breakpoint is set
dec=decrement a register by 1
declare_global_var=declare a variable on the global object
define_own_by_id=define an own property by string id
define_own_by_id_long=define an own property by string id (long form)
define_own_by_index=define an own indexed property
define_own_by_index_l=define an own indexed property (long form)
define_own_by_val=define an own property by computed value
define_own_getter_setter_by_val=define accessor (getter/setter) property
define_own_in_dense_array=define an element in a dense array literal
define_own_in_dense_array_l=define an element in a dense array literal (long)
del_by_id=delete a property by string id
del_by_id_long=delete a property by string id (long form)
del_by_id_loose=delete a property by id (loose mode)
del_by_id_loose_long=delete a property by id (loose, long form)
del_by_id_strict=delete a property by id (strict mode)
del_by_id_strict_long=delete a property by id (strict, long form)
del_by_val=delete a property by computed value
del_by_val_loose=delete a property by computed value (loose mode)
del_by_val_strict=delete a property by computed value (strict mode)
direct_eval=invoke direct eval()
div=divide two registers
div_n=numeric divide (operands known to be numeric)
divi32=signed 32-bit integer divide
divu32=unsigned 32-bit integer divide
eq=loose equality comparison (==)
fast_array_append=append all elements of a fast array to another
fast_array_length=read the length of a fast array
fast_array_load=load element from a fast array by index
fast_array_push=push a value into a fast array
fast_array_store=store value into a fast array at index
get_arguments_length=read 'arguments.length' lazily
get_arguments_prop_by_val=read an element from 'arguments' lazily
get_arguments_prop_by_val_loose=read 'arguments' element (loose mode)
get_arguments_prop_by_val_strict=read 'arguments' element (strict mode)
get_builtin_closure=get a closure for a builtin function
get_by_id=load a property by string id
get_by_id_long=load a property by string id (long form)
get_by_id_short=load a property by string id (short cache)
get_by_id_with_receiver_long=load a property with explicit receiver (long form)
get_by_index=load an element by integer index
get_by_val=load a property by computed value
get_by_val_with_receiver=load a property by computed value with receiver
get_closure_environment=get the environment of an enclosing closure
get_environment=get the lexical environment at a given depth
get_global_object=get the global object
get_new_target=read the new.target meta-property
get_next_p_name=advance a for-in property iterator
get_next_pname=advance a for-in property iterator
get_own_by_slot_idx=load own property by inline cache slot index
get_own_by_slot_idx_long=load own property by slot index (long form)
get_own_private_by_sym=load an own private property by symbol
get_p_name_list=initialize a for-in property name list
get_parent_environment=get the parent of a lexical environment
get_pname_list=initialize a for-in property name list
greater=greater than comparison (>)
greater_eq=greater than or equal comparison (>=)
inc=increment a register by 1
instance_of=instanceof operator
is_in='in' operator (property exists in object)
iterator_begin=start iteration over an iterable
iterator_close=close (cleanup) an iterator
iterator_next=advance an iterator and read the next value
j_equal=branch if loose equal
j_equal_long=branch if loose equal (long offset)
j_greater=branch if greater than
j_greater_equal=branch if greater than or equal
j_greater_equal_long=branch if greater than or equal (long offset)
j_greater_equal_n=branch if greater or equal (numeric)
j_greater_equal_n_long=branch if greater or equal (numeric, long offset)
j_greater_long=branch if greater than (long offset)
j_greater_n=branch if greater than (numeric)
j_greater_n_long=branch if greater than (numeric, long offset)
j_less=branch if less than
j_less_equal=branch if less than or equal
j_less_equal_long=branch if less than or equal (long offset)
j_less_equal_n=branch if less than or equal (numeric)
j_less_equal_n_long=branch if less than or equal (numeric, long offset)
j_less_long=branch if less than (long offset)
j_less_n=branch if less than (numeric)
j_less_n_long=branch if less than (numeric, long offset)
j_not_equal=branch if loose not equal
j_not_equal_long=branch if loose not equal (long offset)
j_not_greater=branch if not greater than
j_not_greater_equal=branch if not greater than or equal
j_not_greater_equal_long=branch if not greater than or equal (long offset)
j_not_greater_equal_n=branch if not greater than or equal (numeric)
j_not_greater_equal_n_long=branch if not greater than or equal (numeric, long offset)
j_not_greater_long=branch if not greater than (long offset)
j_not_greater_n=branch if not greater than (numeric)
j_not_greater_n_long=branch if not greater than (numeric, long offset)
j_not_less=branch if not less than
j_not_less_equal=branch if not less than or equal
j_not_less_equal_long=branch if not less than or equal (long offset)
j_not_less_equal_n=branch if not less than or equal (numeric)
j_not_less_equal_n_long=branch if not less than or equal (numeric, long offset)
j_not_less_long=branch if not less than (long offset)
j_not_less_n=branch if not less than (numeric)
j_not_less_n_long=branch if not less than (numeric, long offset)
j_strict_equal=branch if strict equal (===)
j_strict_equal_long=branch if strict equal (long offset)
j_strict_not_equal=branch if strict not equal (!==)
j_strict_not_equal_long=branch if strict not equal (long offset)
jequal=branch if loose equal
jequal_long=branch if loose equal (long offset)
jgreater=branch if greater than
jgreater_equal=branch if greater than or equal
jgreater_equal_long=branch if greater than or equal (long offset)
jgreater_equal_n=branch if greater or equal (numeric)
jgreater_equal_nlong=branch if greater or equal (numeric, long offset)
jgreater_long=branch if greater than (long offset)
jgreater_n=branch if greater than (numeric)
jgreater_nlong=branch if greater than (numeric, long offset)
jless=branch if less than
jless_equal=branch if less than or equal
jless_equal_long=branch if less than or equal (long offset)
jless_equal_n=branch if less than or equal (numeric)
jless_equal_nlong=branch if less than or equal (numeric, long offset)
jless_long=branch if less than (long offset)
jless_n=branch if less than (numeric)
jless_nlong=branch if less than (numeric, long offset)
jmp=unconditional branch
jmp_builtin_is=branch if value is a specific builtin
jmp_builtin_is_long=branch if value is a specific builtin (long offset)
jmp_builtin_is_not=branch if value is not a specific builtin
jmp_builtin_is_not_long=branch if value is not a specific builtin (long offset)
jmp_false=branch if value is falsy
jmp_false_long=branch if value is falsy (long offset)
jmp_long=unconditional branch (long offset)
jmp_true=branch if value is truthy
jmp_true_long=branch if value is truthy (long offset)
jmp_type_of_is=branch if typeof value matches a tag
jmp_undefined=branch if value is undefined
jmp_undefined_long=branch if value is undefined (long offset)
jnot_equal=branch if loose not equal
jnot_equal_long=branch if loose not equal (long offset)
jnot_greater=branch if not greater than
jnot_greater_equal=branch if not greater than or equal
jnot_greater_equal_long=branch if not greater than or equal (long offset)
jnot_greater_equal_n=branch if not greater than or equal (numeric)
jnot_greater_equal_nlong=branch if not greater than or equal (numeric, long offset)
jnot_greater_long=branch if not greater than (long offset)
jnot_greater_n=branch if not greater than (numeric)
jnot_greater_nlong=branch if not greater than (numeric, long offset)
jnot_less=branch if not less than
jnot_less_equal=branch if not less than or equal
jnot_less_equal_long=branch if not less than or equal (long offset)
jnot_less_equal_n=branch if not less than or equal (numeric)
jnot_less_equal_nlong=branch if not less than or equal (numeric, long offset)
jnot_less_long=branch if not less than (long offset)
jnot_less_n=branch if not less than (numeric)
jnot_less_nlong=branch if not less than (numeric, long offset)
jstrict_equal=branch if strict equal (===)
jstrict_equal_long=branch if strict equal (long offset)
jstrict_not_equal=branch if strict not equal (!==)
jstrict_not_equal_long=branch if strict not equal (long offset)
l_shift=left shift (<<)
less=less than comparison (<)
less_eq=less than or equal comparison (<=)
load_const_big_int=load a BigInt constant
load_const_big_int_long_index=load a BigInt constant (long index)
load_const_double=load a double-precision float constant
load_const_empty=load the 'empty' sentinel value
load_const_false=load the boolean false constant
load_const_int=load an integer constant
load_const_null=load the null constant
load_const_string=load a string constant by id
load_const_string_long_index=load a string constant by id (long index)
load_const_true=load the boolean true constant
load_const_u_int8=load an 8-bit unsigned integer constant
load_const_uint8=load an 8-bit unsigned integer constant
load_const_undefined=load the undefined constant
load_const_zero=load the integer zero constant
load_from_environment=read a variable from a lexical environment slot
load_from_environment_l=read from environment slot (long index)
load_param=read a function parameter into a register
load_param_long=read a function parameter (long index)
load_parent_no_traps=load object's prototype skipping proxy traps
load_this_n_s=load 'this' in non-strict mode
load_this_ns=load 'this' in non-strict mode
loadi16=load a signed 16-bit integer
loadi32=load a signed 32-bit integer
loadi8=load a signed 8-bit integer
loadu16=load an unsigned 16-bit integer
loadu32=load an unsigned 32-bit integer
loadu8=load an unsigned 8-bit integer
lshift=left shift (<<)
mod=modulo (%) operation
mov=copy a register into another register
mov_long=copy a register (long form)
mul=multiply two registers
mul_n=numeric multiply (operands known to be numeric)
mul32=32-bit integer multiply
name=get the name (string id) of an object
negate=arithmetic negation (unary minus)
neq=loose inequality comparison (!=)
new_array=create an empty array of given size
new_array_with_buffer=create an array initialized from a literal buffer
new_array_with_buffer_long=create an array from a literal buffer (long form)
new_fast_array=create a new fast (dense, integer-indexed) array
new_object=create an empty object
new_object_with_buffer=create an object initialized from a literal buffer
new_object_with_buffer_and_parent=create an object from a buffer with a given prototype
new_object_with_buffer_long=create an object from a literal buffer (long form)
new_object_with_parent=create an empty object with a given prototype
new_typed_object_with_buffer=create a typed object from a literal buffer
not=logical NOT (!)
private_is_in=test if a private name is present in an object
profile_point=marker used by the profiler to record execution
put_by_id=store a property by string id
put_by_id_long=store a property by string id (long form)
put_by_id_loose=store a property by id (loose mode)
put_by_id_loose_long=store a property by id (loose, long form)
put_by_id_strict=store a property by id (strict mode)
put_by_id_strict_long=store a property by id (strict, long form)
put_by_val=store a property by computed value
put_by_val_loose=store a property by computed value (loose mode)
put_by_val_strict=store a property by computed value (strict mode)
put_by_val_with_receiver=store a property by computed value with receiver
put_new_own_by_id=define a new own property by id
put_new_own_by_id_long=define a new own property by id (long form)
put_new_own_by_id_short=define a new own property by id (short form)
put_new_own_n_eby_id=define a new own non-enumerable property by id
put_new_own_n_eby_id_long=define a new own non-enumerable property by id (long form)
put_new_own_ne_by_id=define a new own non-enumerable property by id
put_new_own_ne_by_id_long=define a new own non-enumerable property by id (long form)
put_own_by_index=define an own property by integer index
put_own_by_index_l=define an own property by integer index (long form)
put_own_by_slot_idx=store an own property by inline cache slot
put_own_by_slot_idx_long=store an own property by slot index (long form)
put_own_by_val=define an own property by computed value
put_own_getter_setter_by_val=define an own accessor (getter/setter) property
put_own_private_by_sym=store an own private property by symbol
r_shift=signed right shift (>>)
reify_arguments=create the actual 'arguments' object lazily
reify_arguments_loose=reify 'arguments' object in loose mode
reify_arguments_strict=reify 'arguments' object in strict mode
resume_generator=resume a suspended generator
ret=return from the current function
rshift=signed right shift (>>)
save_generator=suspend the current generator and yield
save_generator_long=suspend the current generator (long offset)
select_object=pick the object operand among two values
start_generator=initialize execution of a generator body
store_n_pto_environment=store non-pointer value to environment slot
store_n_pto_environment_l=store non-pointer value to environment slot (long index)
store_np_to_environment=store non-pointer value to environment slot
store_np_to_environment_l=store non-pointer value to environment slot (long index)
store_to_environment=store a variable into a lexical environment slot
store_to_environment_l=store into environment slot (long index)
store16=store a 16-bit value to memory
store32=store a 32-bit value to memory
store8=store an 8-bit value to memory
strict_eq=strict equality comparison (===)
strict_neq=strict inequality comparison (!==)
string_switch_imm=switch on a string with an inline jump table
sub=subtract two registers
sub_n=numeric subtract (operands known to be numeric)
sub32=32-bit integer subtract
switch_imm=switch on an integer with an inline jump table
throw=throw an exception
throw_if_empty=throw a ReferenceError if value is the empty sentinel (TDZ)
throw_if_has_restricted_global_property=throw if a restricted global property already exists
throw_if_this_initialized=throw if 'this' has already been initialized in a derived ctor
throw_if_undefined=throw a TypeError if the value is undefined
throw_if_undefined_inst=throw TypeError if the value is undefined (instance form)
to_int32=convert a value to a signed 32-bit integer (ToInt32)
to_number=convert a value to a number (ToNumber)
to_numeric=convert a value to numeric (ToNumeric, supports BigInt)
to_property_key=convert a value to a property key (ToPropertyKey)
to_uint32=convert a value to an unsigned 32-bit integer (ToUint32)
try_get_by_id=load a property by id, throwing if missing
try_get_by_id_long=load a property by id, throwing if missing (long form)
try_put_by_id=store a property by id, throwing if missing
try_put_by_id_long=store a property by id, throwing if missing (long form)
try_put_by_id_loose=store a property by id, throwing if missing (loose)
try_put_by_id_loose_long=store a property by id, throwing if missing (loose, long form)
try_put_by_id_strict=store a property by id, throwing if missing (strict)
try_put_by_id_strict_long=store a property by id, throwing if missing (strict, long form)
type_of=typeof operator
type_of_is=test if typeof value equals a specific tag
typed_load_parent=load the prototype of a typed object
u_int_switch_imm=switch on an unsigned integer with an inline jump table
u_rshift=unsigned right shift (>>>)
unreachable=trap; this point should never be reached
urshift=unsigned right shift (>>>)
+13
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#ifndef LIBHBC_BYTECODE_H
#define LIBHBC_BYTECODE_H
@@ -23,6 +25,7 @@ typedef struct {
u32 original_pos;
u32 next_pos;
u32 function_offset;
u32 frame_size; /* register count of the owning function (for call args) */
HBCReader *hbc_reader;
} ParsedInstruction;
@@ -42,6 +45,16 @@ Result _hbc_parse_instruction(HBCReader *reader, FunctionHeader *function_header
Result _hbc_parse_function_bytecode(HBCReader *reader, u32 function_id, ParsedInstructionList *out_instructions, HBCISA isa);
Result _hbc_instruction_to_string(ParsedInstruction *instruction, StringBuffer *out_string);
/* Raw value of the i-th operand (0..5); 0 when the index is out of range. */
u32 hbc_operand_value(const ParsedInstruction *insn, int i);
/* True when operand `idx` (0..5) is a relative jump/branch address. */
bool _hbc_operand_is_addr(const Instruction *inst, int idx);
/* Absolute (function-relative) branch target of operand `op_index`.
* Hermes offsets are relative to the start of the instruction. */
u32 _hbc_compute_target_address(const ParsedInstruction *insn, int op_index);
/* Bytecode version module getters */
BytecodeModule *_hbc_get_bytecode_module(u32 bytecode_version);
const char **_hbc_get_builtin_functions(BytecodeModule *module, u32 *out_count);
+2 -1
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#ifndef LIBHBC_COMMON_H
#define LIBHBC_COMMON_H
@@ -9,7 +11,6 @@
#include <string.h>
#include <errno.h>
#include <stdarg.h>
#include "version.h"
#ifndef R2_VERSION
#define R_RETURN_VAL_IF_FAIL(x, y) \
+41
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#ifndef LIBHBC_DECOMPILER_H
#define LIBHBC_DECOMPILER_H
@@ -54,6 +56,10 @@ struct Environment {
char **slot_index_to_varname;
int var_count;
int slot_capacity;
/* >=0: synthetic stand-in for a get_environment captured scope at this
* nesting level (used when the real parent chain is unknown, i.e. the
* function is decompiled standalone). -1: a real created environment. */
int captured_level;
};
/* Address to strings mapping */
@@ -122,6 +128,38 @@ struct DecompiledFunctionBody {
u32 nested_frames_count;
u32 nested_frames_capacity;
/* for-in / loop top addresses: a jump here is a `continue`, not a `goto`,
* and the address gets no label (the loop body brace stands in). */
u32 *forin_continue_targets;
u32 forin_continue_targets_count;
u32 forin_continue_targets_capacity;
/* do-while loops: `do {` opens at `top`, `} while (cond);` replaces the
* back-edge conditional jump at `back_edge`. When an entry guard at
* `guard_pos` (jumping to `exit_addr`) tests the same condition, the loop is
* `promoted` to `while (cond) {` (guard + do/while folded into one header). */
struct {
u32 top;
u32 back_edge;
u32 guard_pos;
u32 exit_addr;
bool promoted;
/* For a materialized guard (`r=cmp; if(r){do}while(cmp)`), the while
* header renders this condition instead of the guard register. NULL when
* the guard already spells the condition (fused compare-jump). */
void *while_cond;
bool while_cond_invert;
} *dowhile_loops;
u32 dowhile_loops_count;
u32 dowhile_loops_capacity;
/* Infinite loops: a backward unconditional `goto top` is the back-edge of a
* `for (;;) {` opened at `top`; the enclosing if's false path exits at
* `exit`, where a `break;` and the loop close are emitted. */
struct { u32 top; u32 exit; } *forever_loops;
u32 forever_loops_count;
u32 forever_loops_capacity;
/* Generated code */
TokenString *statements;
u32 statements_count;
@@ -144,6 +182,9 @@ typedef struct HermesDecompiler {
bool *function_in_progress; /* Track functions currently on the emit stack */
int indent_level;
bool inlining_function; /* True when outputting a nested function inline */
int inline_depth; /* Current nesting depth of inlined closures */
bool output_truncated; /* Sticky flag: budget exceeded; stop emitting new content */
bool truncation_marker_emitted; /* Sticky flag: the truncation comment was already written */
HBCDecompOptions options;
StringBuffer output;
} HermesDecompiler;
+2
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#ifndef LIBHBC_LITERALS_H
#define LIBHBC_LITERALS_H
+2
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#ifndef LIBHBC_TOKEN_H
#define LIBHBC_TOKEN_H
+2
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#ifndef LIBHBC_TRANSLATOR_H
#define LIBHBC_TRANSLATOR_H
+2
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#ifndef LIBHBC_DISASSEMBLER_H
#define LIBHBC_DISASSEMBLER_H
+97 -1
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#ifndef LIBHBC_API_H
#define LIBHBC_API_H
@@ -63,6 +65,34 @@ typedef struct {
u32 param_count; // Number of parameters
} HBCFunc;
typedef struct {
u32 function_id;
u32 address;
u32 function_address;
u32 line;
u32 column;
u32 statement;
const char *filename;
} HBCSourceLine;
typedef struct {
HBCSourceLine *lines;
u32 count;
} HBCSourceLineArray;
typedef struct {
bool has_debug_info;
u32 filename_count;
u32 filename_storage_size;
u32 file_region_count;
u32 source_locations_size;
u32 scope_desc_data_size;
u32 textified_data_size;
u32 string_table_size;
u32 debug_data_size;
u32 functions_with_debug_info;
} HBCDebugInfo;
/* String table data */
typedef struct {
u32 string_count;
@@ -103,7 +133,14 @@ typedef struct {
/* Control flow rendering options */
bool force_dispatch; /* Force switch/case dispatch loop for linear functions */
bool inline_closures; /* Inline closure definitions (default: true) */
int inline_threshold; /* Max instruction count to inline (0 = no limit, -1 = no inline) */
int inline_threshold; /* Max closure bytecode size to inline (0 = no limit, -1 = no inline) */
int inline_max_depth; /* Max nested inline depth (0 = no limit). Bounds the
* recursive closure expansion that otherwise inlines a
* whole module graph into one function. */
/* Early-stop budgets (0 = unlimited). max_ast_statements caps pass2 AST
* growth per function; max_output_bytes caps total emitted output. */
int max_ast_statements;
int max_output_bytes;
} HBCDecompOptions;
/* Per-instruction details */
@@ -216,6 +253,14 @@ Result hbc_get_string_tables(HBC *hbc, HBCStrs *out);
*/
Result hbc_get_function_source(HBC *hbc, u32 function_id, const char **out_src);
/**
* Get decoded source-line records from debug information.
*/
Result hbc_get_source_lines(HBC *hbc, HBCSourceLineArray *out);
void hbc_free_source_lines(HBCSourceLineArray *arr);
bool hbc_has_source_lines(HBC *hbc);
Result hbc_get_debug_info(HBC *hbc, HBCDebugInfo *out);
/**
* Get the raw bytecode bytes for a function.
*/
@@ -284,11 +329,62 @@ typedef struct {
u32 count;
} HBCInsns;
typedef enum {
HBC_BINDING_IMPORT = 0,
HBC_BINDING_EXPORT = 1
} HBCBindingType;
typedef struct {
HBCBindingType type;
const char *kind; /* js, native, cjs, global, module */
char *name;
char *module;
u32 function_id;
u32 offset;
u32 string_id;
} HBCBinding;
typedef struct {
HBCBinding *bindings;
u32 count;
} HBCBindings;
typedef struct {
char *name;
char *path;
char *version;
const char *kind; /* cjs, package, js, native, global */
u32 function_id;
u32 offset;
u32 string_id;
bool inferred;
} HBCModule;
typedef struct {
HBCModule *modules;
u32 count;
} HBCModules;
/**
* Free instruction array.
*/
void hbc_free_insns(HBCInsn *insns, u32 count);
/**
* Scan bytecode/string references for probable JS/native import/export
* boundaries. Hermes has no native import table, so these entries are derived
* from bytecode operands and CJS metadata.
*/
Result hbc_scan_bindings(HBC *hbc, HBCBindings *out);
void hbc_free_bindings(HBCBindings *bindings);
/**
* List probable bundled modules/libraries. Hermes bytecode has no SBOM table,
* so versions are only populated when they are explicitly preserved in strings.
*/
Result hbc_list_modules(HBC *hbc, HBCModules *out);
void hbc_free_modules(HBCModules *modules);
/**
* Encoding buffer and functions
*/
+2
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#ifndef LIBHBC_ENCODER_H
#define LIBHBC_ENCODER_H
+110
View File
@@ -0,0 +1,110 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#ifndef LIBHBC_LITERALS_PUBLIC_H
#define LIBHBC_LITERALS_PUBLIC_H
#include <hbc/common.h>
#include <hbc/hbc.h>
/* ============================================================================
* Literals API (public)
*
* Covers the buffer-backed literals produced by:
* OP_NewArrayWithBuffer / OP_NewArrayWithBufferLong
* OP_NewObjectWithBuffer / OP_NewObjectWithBufferLong
*
* Responsibilities:
* - Format a literal from opcode immediates into a JS-like string.
* - Maintain a lazy cache of distinct literals so the disassembler and
* decompiler can look up previously-formatted literals in O (1) and avoid
* re-walking the SLP buffer on every call.
* - Track the set of instruction addresses that reference each literal.
* - Enumerate literals from the pool side (scan SLP stream) and from the
* code side (walk function bodies).
* - Allow resetting the cache to recover from a bad scan.
*
* Caching policy: entries are keyed by (kind, num_items, primary_id,
* secondary_id); formatting happens at registration time and is memoized.
* The cache can be cleared at any time.
* ============================================================================ */
typedef enum {
HBC_LIT_ARRAY = 0,
HBC_LIT_OBJECT = 1
} HBCLiteralKind;
/* One entry in the literal cache. The strings / xref array are owned by the
* HBC and freed on hbc_close () or hbc_literals_reset (). */
typedef struct {
HBCLiteralKind kind;
u32 num_items;
u32 primary_id; /* array_id (array) or keys/shape id (object) */
u32 secondary_id; /* values_id for objects, 0 for arrays */
u32 paddr; /* absolute file offset of the literal data in the SLP pool (0 if unknown) */
char *formatted; /* cached JS literal text; NULL if not yet formatted */
u32 *xref_addrs; /* instruction addresses that construct this literal */
u32 xref_count;
} HBCLiteralEntry;
/* Format a literal directly from raw parameters. Does not touch the cache.
* Returned string is heap-allocated; caller frees with free (). */
Result hbc_literals_format_raw(HBC *hbc, HBCLiteralKind kind, u32 num_items, u32 primary_id, u32 secondary_id, char **out);
/* Convenience wrapper taking opcode + raw arg3/arg4/arg5 as decoded by
* hbc_dec (). Knows the per-opcode mapping; useful as the "give me the string
* for these parameters" helper. */
Result hbc_literals_format_for_opcode(HBC *hbc, u8 opcode, u32 arg3, u32 arg4, u32 arg5, char **out);
/* Look up (or create) a cache entry for a literal and return the formatted
* text. The returned pointer is owned by the cache — do not free it. */
Result hbc_literals_get(HBC *hbc, HBCLiteralKind kind, u32 num_items, u32 primary_id, u32 secondary_id, const char **out_formatted);
/* Register that `from_addr` references the literal described by the key.
* Creates the cache entry (and formats) on first reference. */
Result hbc_literals_register(HBC *hbc, HBCLiteralKind kind, u32 num_items, u32 primary_id, u32 secondary_id, u32 from_addr);
/* Full-binary scan: walk every function body; record every
* New{Array,Object}WithBuffer[Long] call site into the cache (with xrefs).
* Returns the number of distinct literals found in *out_count. */
Result hbc_literals_scan_code(HBC *hbc, u32 *out_count);
/* Pool-side scan: enumerate group boundaries in the SLP pool for the given
* kind. For arrays and object-values it walks the byte stream linearly.
* For object-keys on v97+ it enumerates the shape table instead.
* Does not populate the cache — this is an inspection helper. Caller frees
* *out via free (). */
typedef struct {
u32 paddr; /* absolute file offset */
u32 pool_offset; /* byte offset within the pool */
u32 num_items; /* number of values in this group (or shape prop_count) */
u8 tag; /* SLP tag 0..7 (for arrays/values); 0xff if not applicable */
} HBCPoolGroup;
Result hbc_literals_scan_pool(HBC *hbc, HBCLiteralKind kind, HBCPoolGroup **out, u32 *out_count);
/* Enumerate the current cache contents. The returned array is owned by the
* HBC and valid until the next cache mutation. */
Result hbc_literals_list(HBC *hbc, const HBCLiteralEntry **out, u32 *out_count);
/* Drop every cached entry and every xref. Fast. Does not affect the SLP
* pools themselves. */
void hbc_literals_reset(HBC *hbc);
/* Number of cached entries. 0 before any register / scan call. */
u32 hbc_literals_count(HBC *hbc);
/* Absolute file offset of the named pool, or 0 if the pool is empty/absent. */
u32 hbc_get_pool_paddr(HBC *hbc, HBCLiteralKind kind);
u32 hbc_get_pool_size(HBC *hbc, HBCLiteralKind kind);
/* Value pool paddr/size for objects (arrays & object-keys reuse the kind-based
* accessors; object values is a distinct pool). */
u32 hbc_get_object_values_paddr(HBC *hbc);
u32 hbc_get_object_values_size(HBC *hbc);
/* Global toggle: whether hbc_dec () should inline the formatted literal as a
* trailing " ; <text>" comment on the disasm line. Default: off. */
void hbc_set_inline_literals(bool on);
bool hbc_get_inline_literals(void);
#endif /* LIBHBC_LITERALS_PUBLIC_H */
+17 -11
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#ifndef LIBHBC_HERMES_OPCODES_H
#define LIBHBC_HERMES_OPCODES_H
@@ -5,16 +7,16 @@
/* Operand type enum */
typedef enum {
OPERAND_TYPE_NONE,
OPERAND_TYPE_REG8,
OPERAND_TYPE_REG32,
OPERAND_TYPE_UINT8,
OPERAND_TYPE_UINT16,
OPERAND_TYPE_UINT32,
OPERAND_TYPE_ADDR8,
OPERAND_TYPE_ADDR32,
OPERAND_TYPE_IMM32,
OPERAND_TYPE_DOUBLE
OPERAND_TYPE_NONE, // 0
OPERAND_TYPE_REG8, // 'r'
OPERAND_TYPE_REG32, // 'R'
OPERAND_TYPE_UINT8, // 'b'
OPERAND_TYPE_UINT16, // 'u'
OPERAND_TYPE_UINT32, // 'U'
OPERAND_TYPE_ADDR8, // 'a'
OPERAND_TYPE_ADDR32, // 'A'
OPERAND_TYPE_IMM32, // 'i'
OPERAND_TYPE_DOUBLE // 'd'
} OperandType;
/* Operand meaning enum */
@@ -42,7 +44,7 @@ typedef struct {
u32 binary_size; /* Total size in bytes */
} Instruction;
/* Hermes opcodes based on hbc95.py (version 95) - Complete list */
/* Hermes opcodes based on v95 */
enum HermesOpcodes {
OP_Unreachable = 0,
OP_NewObjectWithBuffer = 1,
@@ -265,4 +267,8 @@ typedef struct {
HBCISA hbc_isa_getv(int version);
u8 hbc_canonical_opcode_from_name(const char *name);
/* Human-readable label for an operand, derived from its meaning or type.
* Never returns NULL; falls back to "Unknown" for unexpected values. */
const char *hbc_operand_name(const InstructionOperand *operand);
#endif /* LIBHBC_HERMES_OPCODES_H */
+19 -1
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#ifndef LIBHBC_FILE_PARSER_H
#define LIBHBC_FILE_PARSER_H
@@ -239,6 +241,11 @@ struct HBCReader {
ShapeTableEntry *object_shapes;
size_t object_shape_count;
/* Absolute file offsets of the SLP pools (0 if the pool is absent). */
u32 arrays_paddr;
u32 object_keys_paddr;
u32 object_values_paddr;
/* BigInt data */
i64 *bigint_values;
size_t bigint_count;
@@ -258,11 +265,21 @@ struct HBCReader {
FunctionSourceEntry *function_sources;
size_t function_source_count;
/* Debug info */
/* Debug info — split into two phases:
* debug_info_header_loaded: cheap parse of the DebugInfoHeader fields
* only (a handful of u32s), populated eagerly at load time so callers
* can report accurate "has source lines" / "stripped" without paying
* for the full parse.
* debug_info_loaded: full parse of filenames, file regions, and the
* sources / scope / textified / string-table storage blobs. Populated
* lazily on first hbc_get_source_lines / hbc_get_debug_info call. */
bool debug_info_header_loaded;
bool debug_info_loaded;
DebugInfoHeader debug_info_header;
OffsetLengthPair *debug_string_table;
u8 *debug_string_storage;
size_t debug_string_storage_size;
char **debug_filenames;
DebugFileRegion *debug_file_regions;
size_t debug_file_region_count;
u8 *sources_data_storage;
@@ -293,6 +310,7 @@ Result _hbc_reader_read_bigints(HBCReader *reader);
Result _hbc_reader_read_regexp(HBCReader *reader);
Result _hbc_reader_read_cjs_modules(HBCReader *reader);
Result _hbc_reader_read_function_sources(HBCReader *reader);
Result _hbc_reader_read_debug_info_header(HBCReader *reader);
Result _hbc_reader_read_debug_info(HBCReader *reader);
Result _hbc_reader_read_whole_file(HBCReader *reader, const char *filename);
+2 -1
View File
@@ -1,7 +1,7 @@
project(
'r2hermes',
'c',
version: '1.2.2',
version: '1.4.0',
license: 'MIT',
default_options: [
'c_std=c11',
@@ -67,6 +67,7 @@ lib_opcodes_src = files(
lib_core_src = files(
'src/lib/hbc.c',
'src/lib/literals_api.c',
'src/lib/r2.c'
)
+2
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#ifdef R2_CORE_H
extern RCorePlugin r_core_plugin_r2hermes;
#endif
+7 -47
View File
@@ -1,66 +1,26 @@
# Version
# XXX reuse from parent meson.build or wtf
version = '1.0.0'
# r2hermes XPS integration for radare2.
# Included from libr/xps/meson.build when r2hermes sources live under
# libr/xps/p/r2hermes/. Appends sources and include paths to the r_core
# aggregate so the static plugin is linked into libr_core.
# Version extraction
version_parts = version.split('.')
version_major = version_parts[0]
version_minor = version_parts.length() > 1 ? version_parts[1] : '0'
version_patch = version_parts.length() > 2 ? version_parts[2] : '0'
# Generate version.h
configure_file(
input: '../include/hbc/version.h.in',
output: 'version.h',
configuration: {
'VERSION': version,
'VERSION_MAJOR': version_major,
'VERSION_MINOR': version_minor,
'VERSION_PATCH': version_patch,
},
install_dir: 'libr/hbc',
install: true,
)
# Register plugins in the static plugin lists
core_plugins += ['r2hermes']
# Include directories (relative to this meson.build file location)
include_dirs = include_directories(join_paths('..', 'include'))
r_core_additional_inc += include_dirs
r_core_additional_inc += include_directories(join_paths('..', 'include'))
# Library source files
lib_utils_src = files(
r_core_additional_sources += files(
'../src/lib/utils/string_buffer.c',
'../src/lib/utils/buffer_reader.c',
)
lib_parsers_src = files(
'../src/lib/parsers/hbc_file_parser.c',
'../src/lib/parsers/hbc_bytecode_parser.c',
)
lib_disasm_src = [] # Disassembly sources (if any)
lib_decompile_src = files(
'../src/lib/decompilation/translator.c',
'../src/lib/decompilation/token.c',
'../src/lib/decompilation/literals.c',
'../src/lib/decompilation/decompiler.c',
)
lib_opcodes_src = files(
'../src/lib/opcodes/isa.c',
'../src/lib/opcodes/encoder.c',
'../src/lib/opcodes/decoder.c',
)
lib_core_src = files(
'../src/lib/hbc.c',
'../src/lib/literals_api.c',
'../src/lib/r2.c',
'../src/r2/core_hbc_one.c',
)
lib_src = lib_utils_src + lib_parsers_src + lib_disasm_src + lib_decompile_src + lib_opcodes_src + lib_core_src
r_core_additional_sources += lib_src
+2
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#ifdef R2_CORE_H
#include "core/deps.h"
#endif
File diff suppressed because it is too large. Load diff
+11 -7
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/decompilation/literals.h>
#include <ctype.h>
#include <math.h>
@@ -91,7 +93,7 @@ static bool slp_read_u16(const u8 *base, size_t size, size_t *pos, u16 *out) {
if (*pos + 2 > size) {
return false;
}
*out = (u16) (base[*pos] | (base[*pos + 1] << 8));
*out = (u16) ((u16)base[*pos] | ((u16)base[*pos + 1] << 8));
(*pos) += 2;
return true;
}
@@ -99,7 +101,7 @@ static bool slp_read_u32(const u8 *base, size_t size, size_t *pos, u32 *out) {
if (*pos + 4 > size) {
return false;
}
*out = (u32) (base[*pos] | (base[*pos + 1] << 8) | (base[*pos + 2] << 16) | (base[*pos + 3] << 24));
*out = (u32)base[*pos] | ((u32)base[*pos + 1] << 8) | ((u32)base[*pos + 2] << 16) | ((u32)base[*pos + 3] << 24);
(*pos) += 4;
return true;
}
@@ -216,8 +218,8 @@ static Result slp_append_value_js(HBCReader *r, const SLPValue *v, bool for_key,
case SLP_ShortStringTag:
case SLP_ByteStringTag:
{
const char *s = (r && r->strings && v->v.str_id < r->header.stringCount)? r->strings[v->v.str_id]: NULL;
if (for_key && s && _hbc_is_js_identifier (s)) {
const char *s = (r && r->strings && v->v.str_id < r->header.stringCount)? r->strings[v->v.str_id]: NULL;
if (for_key && s && _hbc_is_js_identifier (s)) {
return _hbc_string_buffer_append (out, s);
}
if (s) {
@@ -311,9 +313,11 @@ Result _hbc_format_object_literal(HBCReader *r, u32 key_count, u32 value_count,
}
Result _hbc_format_array_literal(HBCReader *r, u32 value_count, u32 array_id, StringBuffer *out, LiteralsPrettyPolicy policy, bool suppress_comments) {
/* Apply policy */
bool pretty = (policy == LITERALS_PRETTY_ALWAYS) || (policy == LITERALS_PRETTY_AUTO && (value_count > 0));
bool multiline = pretty && (value_count > 0);
/* Apply policy. AUTO renders arrays inline (`[1, 2, 3]`): these are data
* (e.g. module dependency lists) often hundreds long, and one-element-per-
* line dominates the output while its embedded newlines ignore the statement
* indent. Only ALWAYS expands them vertically. */
bool multiline = (policy == LITERALS_PRETTY_ALWAYS) && (value_count > 0);
RETURN_IF_ERROR (_hbc_string_buffer_append (out, multiline? "[\n": "["));
if (!r || !r->arrays || value_count == 0) {
if (suppress_comments) {
+10 -21
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/decompilation/token.h>
#include <hbc/decompilation/decompiler.h>
#include <hbc/common.h>
@@ -326,10 +328,10 @@ Result _hbc_token_to_string(Token *token, StringBuffer *buffer) {
{
FunctionTableIndexToken *t = (FunctionTableIndexToken *)token;
/* Try to resolve name if possible */
if (t->state && t->state->hbc_reader && t->function_id < t->state->hbc_reader->header.functionCount) {
if (t->state && t->state->hbc_reader && t->function_id < t->state->hbc_reader->header.functionCount) {
u32 name_id = t->state->hbc_reader->function_headers[t->function_id].functionName;
const char *name = (name_id < t->state->hbc_reader->header.stringCount && t->state->hbc_reader->strings)? t->state->hbc_reader->strings[name_id]: NULL;
if (name && *name) {
const char *name = (name_id < t->state->hbc_reader->header.stringCount && t->state->hbc_reader->strings)? t->state->hbc_reader->strings[name_id]: NULL;
if (name && *name) {
RETURN_IF_ERROR (_hbc_string_buffer_append (buffer, name));
return SUCCESS_RESULT ();
}
@@ -443,10 +445,6 @@ Result _hbc_token_to_string(Token *token, StringBuffer *buffer) {
}
}
static bool is_punct(TokenType t) {
return t == TOKEN_TYPE_LEFT_PARENTHESIS || t == TOKEN_TYPE_RIGHT_PARENTHESIS || t == TOKEN_TYPE_DOT_ACCESSOR;
}
Result _hbc_token_string_to_string(TokenString *ts, StringBuffer *out) {
if (!ts || !out) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "_hbc_token_string_to_string: NULL");
@@ -454,24 +452,15 @@ Result _hbc_token_string_to_string(TokenString *ts, StringBuffer *out) {
Token *cur = ts->head;
TokenType prev = (TokenType) (-1);
while (cur) {
/* spacing rules: add space before '(', no space after '(' or before ')', add spaces around '=' */
/* spacing rules: space before '(', none after '(' or '.' nor before ')' or '.' */
if (cur != ts->head) {
bool need_space = true;
/* Space before '(' for readability */
bool need_space;
if (cur->type == TOKEN_TYPE_LEFT_PARENTHESIS) {
need_space = true;
}
/* No space before other punctuation */
else if (is_punct (cur->type) || cur->type == TOKEN_TYPE_RIGHT_PARENTHESIS || cur->type == TOKEN_TYPE_DOT_ACCESSOR) {
} else if (cur->type == TOKEN_TYPE_RIGHT_PARENTHESIS || cur->type == TOKEN_TYPE_DOT_ACCESSOR) {
need_space = false;
}
/* No space after '(' or after '.' */
else if (prev == TOKEN_TYPE_LEFT_PARENTHESIS || prev == TOKEN_TYPE_DOT_ACCESSOR) {
need_space = false;
}
/* Always space around '=' */
else if (prev == TOKEN_TYPE_ASSIGNMENT || cur->type == TOKEN_TYPE_ASSIGNMENT) {
need_space = true;
} else {
need_space = prev != TOKEN_TYPE_LEFT_PARENTHESIS && prev != TOKEN_TYPE_DOT_ACCESSOR;
}
if (need_space) {
RETURN_IF_ERROR (_hbc_string_buffer_append_char (out, ' '));
+179 -298
View File
@@ -1,8 +1,9 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/decompilation/translator.h>
#include <hbc/opcodes.h>
#include <hbc/parser.h>
#include <stdbool.h>
#include <ctype.h>
#include <string.h>
#include <hbc/decompilation/literals.h>
@@ -155,7 +156,6 @@ static const char *lookup_op_table(const OpTableEntry *table, u8 opcode) {
static Token *reg_l_safe(const ParsedInstruction *insn, int idx);
static Token *reg_r_safe(const ParsedInstruction *insn, int idx);
static Token *num_token_u32(u32 v);
static u32 compute_target_address(const ParsedInstruction *insn, int op_index);
static Result emit_literal_assign(TokenString *out, const ParsedInstruction *insn, const char *literal) {
RETURN_IF_ERROR (add (out, reg_l_safe (insn, 0)));
@@ -220,8 +220,10 @@ static Result emit_call_fixed(TokenString *out, const ParsedInstruction *insn, i
RETURN_IF_ERROR (add (out, reg_l_safe (insn, 0))); /* dest */
RETURN_IF_ERROR (add (out, create_assignment_token ()));
RETURN_IF_ERROR (add (out, reg_r_safe (insn, 1))); /* callee */
/* Call1..Call4 encode an explicit `this` register before the user arguments. */
RETURN_IF_ERROR (add (out, create_raw_token (".call")));
RETURN_IF_ERROR (add (out, create_left_parenthesis_token ()));
for (int i = 2; i < operand_count; i++) { /* args start at operand 2 */
for (int i = 2; i < operand_count; i++) {
if (i > 2) {
RETURN_IF_ERROR (add (out, create_raw_token (",")));
}
@@ -230,20 +232,47 @@ static Result emit_call_fixed(TokenString *out, const ParsedInstruction *insn, i
return add (out, create_right_parenthesis_token ());
}
static Result emit_call_with_argc(TokenString *out, const ParsedInstruction *insn) {
/* Hermes reserves this many frame-header registers at the very top of a
* function's register frame; an outgoing call's `this`+args sit just below. */
#define HBC_CALL_FRAME_RESERVED 6
/* Variable-arity Call/Construct. Hermes places the call's argument registers at
* the top of the caller's frame, below the reserved header: `this` at
* frame_size-1-RESERVED, the explicit arguments descending below it.
* Reconstruct them as fn.call (this, a1, ...) (or new fn (a1, ...) for construct).
* Falls back to an argc note when the frame layout is unknown or too small. */
static Result emit_call_with_argc(TokenString *out, const ParsedInstruction *insn, bool is_construct) {
const u32 argc = insn->arg3;
RETURN_IF_ERROR (add (out, reg_l_safe (insn, 0)));
RETURN_IF_ERROR (add (out, create_assignment_token ()));
RETURN_IF_ERROR (add (out, reg_r_safe (insn, 0)));
RETURN_IF_ERROR (add (out, create_left_parenthesis_token ()));
if (is_construct) {
RETURN_IF_ERROR (add (out, create_raw_token ("new")));
}
RETURN_IF_ERROR (add (out, reg_r_safe (insn, 1)));
char buf2[32];
snprintf (buf2, sizeof (buf2), ", /*argc:%u*/", (unsigned)insn->arg3);
RETURN_IF_ERROR (add (out, create_raw_token (buf2)));
if (!is_construct) {
RETURN_IF_ERROR (add (out, create_raw_token (".call")));
}
RETURN_IF_ERROR (add (out, create_left_parenthesis_token ()));
const int base = (int)insn->frame_size - 1 - HBC_CALL_FRAME_RESERVED;
if (argc == 0 || base < 0 || (int)argc > base + 1) {
char buf2[32];
snprintf (buf2, sizeof (buf2), "/*argc:%u*/", (unsigned)argc);
RETURN_IF_ERROR (add (out, create_raw_token (buf2)));
return add (out, create_right_parenthesis_token ());
}
/* Construct omits the implicit `this` (the freshly created object). */
const u32 first = is_construct? 1: 0;
for (u32 i = first; i < argc; i++) {
if (i > first) {
RETURN_IF_ERROR (add (out, create_raw_token (",")));
}
RETURN_IF_ERROR (add (out, create_right_hand_reg_token (base - (int)i)));
}
return add (out, create_right_parenthesis_token ());
}
static Result emit_simple_jump(TokenString *out, const ParsedInstruction *insn, bool positive) {
u32 target = compute_target_address (insn, 0);
u32 target = _hbc_compute_target_address (insn, 0);
if (positive) {
RETURN_IF_ERROR (add (out, create_jump_condition_token (target)));
return add (out, create_raw_token ("true"));
@@ -277,34 +306,6 @@ static bool is_operand_register(const Instruction *inst, int idx) {
return t == OPERAND_TYPE_REG8 || t == OPERAND_TYPE_REG32;
}
static bool is_operand_addr(const Instruction *inst, int idx) {
if (!inst || idx < 0 || idx >= 6) {
return false;
}
OperandType t = inst->operands[idx].operand_type;
return t == OPERAND_TYPE_ADDR8 || t == OPERAND_TYPE_ADDR32;
}
static u32 get_operand_value(const ParsedInstruction *insn, int idx) {
switch (idx) {
case 0: return insn->arg1;
case 1: return insn->arg2;
case 2: return insn->arg3;
case 3: return insn->arg4;
case 4: return insn->arg5;
default: return insn->arg6;
}
}
static u32 compute_target_address(const ParsedInstruction *insn, int op_index) {
u32 v = get_operand_value (insn, op_index);
u32 base = insn->original_pos;
if (_hbc_is_jump_instruction (insn->opcode)) {
base += insn->inst->binary_size;
}
return base + v;
}
static const char *jump_cmp_operator(u8 op) {
switch (op) {
case OP_JEqual:
@@ -359,7 +360,7 @@ static Token *reg_l_safe(const ParsedInstruction *insn, int idx) {
if (!is_operand_register (insn->inst, idx)) {
return create_raw_token ("/*not_reg*/");
}
u32 r = get_operand_value (insn, idx);
u32 r = hbc_operand_value (insn, idx);
if (!is_valid_register (r, insn)) {
/* Out-of-bounds register: show normally but warn in debug mode.
* This is a rare error condition indicating bytecode corruption
@@ -376,7 +377,7 @@ static Token *reg_r_safe(const ParsedInstruction *insn, int idx) {
if (!is_operand_register (insn->inst, idx)) {
return create_raw_token ("/*not_reg*/");
}
u32 r = get_operand_value (insn, idx);
u32 r = hbc_operand_value (insn, idx);
if (!is_valid_register (r, insn)) {
/* Out-of-bounds register: show normally but warn in debug mode.
* This is a rare error condition indicating bytecode corruption
@@ -451,6 +452,54 @@ static Token *unquoted_string(HBCReader *r, u32 sid) {
return create_raw_token ("unknown");
}
/* Emit `.name` when the property is a valid identifier, `["name"]` otherwise */
static Result emit_property_access(TokenString *out, const ParsedInstruction *insn, u32 sid) {
HBCReader *r = insn->hbc_reader;
const char *s = (r && r->strings && sid < r->header.stringCount)? r->strings[sid]: NULL;
if (s && _hbc_is_js_identifier (s)) {
RETURN_IF_ERROR (add (out, create_dot_accessor_token ()));
return add (out, create_raw_token (s));
}
RETURN_IF_ERROR (add (out, create_raw_token ("[")));
RETURN_IF_ERROR (add (out, quoted_string (r, sid)));
return add (out, create_raw_token ("]"));
}
/* Emit a compare-and-jump as `if (rA cmp rB)` or its negated goto form */
static Result emit_cmp_jump(TokenString *out, const ParsedInstruction *insn_c, const char *cmp) {
i32 rel = (i32)hbc_operand_value (insn_c, 0);
u32 target = _hbc_compute_target_address (insn_c, 0);
if (rel > 0) {
RETURN_IF_ERROR (add (out, create_jump_not_condition_token (target)));
RETURN_IF_ERROR (add (out, create_raw_token ("!")));
RETURN_IF_ERROR (add (out, create_left_parenthesis_token ()));
} else {
RETURN_IF_ERROR (add (out, create_jump_condition_token (target)));
}
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 1)));
RETURN_IF_ERROR (add (out, create_raw_token (cmp)));
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 2)));
if (rel > 0) {
RETURN_IF_ERROR (add (out, create_right_parenthesis_token ()));
}
return SUCCESS_RESULT ();
}
/* Emit `rD = <function table entry>` with environment and closure kind flags */
static Result emit_closure_token(TokenString *out, const ParsedInstruction *insn_c, bool is_closure, bool is_generator, bool is_async) {
RETURN_IF_ERROR (add (out, reg_l_safe (insn_c, 0)));
RETURN_IF_ERROR (add (out, create_assignment_token ()));
FunctionTableIndexToken *t = (FunctionTableIndexToken *)create_function_table_index_token (hbc_operand_value (insn_c, 2), NULL);
if (!t) {
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "oom fti");
}
t->environment_id = (int)hbc_operand_value (insn_c, 1);
t->is_closure = is_closure;
t->is_generator = is_generator;
t->is_async = is_async;
return add (out, (Token *)t);
}
Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, TokenString *out) {
if (!insn_c || !out || !insn_c->inst) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "translate: bad args");
@@ -534,10 +583,12 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
case OP_LoadParam:
case OP_LoadParamLong:
{
/* LoadParam index maps directly to args[i]: index 0 is `this`,
* matching the a0..aN-1 names emitted in the function signature. */
RETURN_IF_ERROR (add (out, reg_l_safe (insn_c, 0)));
RETURN_IF_ERROR (add (out, create_assignment_token ()));
char buf[32];
snprintf (buf, sizeof (buf), "args[%u]", insn->arg2 > 0? insn->arg2 - 1: 0);
snprintf (buf, sizeof (buf), "args[%u]", insn->arg2);
RETURN_IF_ERROR (add (out, create_raw_token (buf)));
break;
}
@@ -557,7 +608,7 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
{
/* catch (rN) marker */
if (is_operand_register (insn->inst, 0)) {
RETURN_IF_ERROR (add (out, create_catch_block_start_token ((int)get_operand_value (insn, 0))));
RETURN_IF_ERROR (add (out, create_catch_block_start_token ((int)hbc_operand_value (insn, 0))));
} else {
RETURN_IF_ERROR (add (out, create_raw_token ("/*catch non-reg*/")));
}
@@ -567,7 +618,7 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
{
/* Create a new environment object in rD */
if (is_operand_register (insn->inst, 0)) {
RETURN_IF_ERROR (add (out, create_new_environment_token ((int)get_operand_value (insn_c, 0))));
RETURN_IF_ERROR (add (out, create_new_environment_token ((int)hbc_operand_value (insn_c, 0))));
} else {
RETURN_IF_ERROR (add (out, create_raw_token ("/*new_env_invalid*/")));
}
@@ -575,8 +626,8 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
}
case OP_CreateInnerEnvironment:
{
if (is_operand_register (insn->inst, 0) && is_operand_register (insn->inst, 1)) {
RETURN_IF_ERROR (add (out, create_new_inner_environment_token ((int)get_operand_value (insn_c, 0), (int)get_operand_value (insn_c, 1), (int)get_operand_value (insn_c, 2))));
if (is_operand_register (insn->inst, 0) && is_operand_register (insn->inst, 1)) {
RETURN_IF_ERROR (add (out, create_new_inner_environment_token ((int)hbc_operand_value (insn_c, 0), (int)hbc_operand_value (insn_c, 1), (int)hbc_operand_value (insn_c, 2))));
} else {
RETURN_IF_ERROR (add (out, create_raw_token ("/*new_inner_env_invalid*/")));
}
@@ -584,47 +635,16 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
}
case OP_CreateClosure:
case OP_CreateClosureLongIndex:
{
RETURN_IF_ERROR (add (out, reg_l_safe (insn_c, 0)));
RETURN_IF_ERROR (add (out, create_assignment_token ()));
FunctionTableIndexToken *t = (FunctionTableIndexToken *)create_function_table_index_token (get_operand_value (insn_c, 2), NULL);
if (!t) {
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "oom fti");
}
t->environment_id = (int)get_operand_value (insn_c, 1);
t->is_closure = true;
RETURN_IF_ERROR (add (out, (Token *)t));
break;
}
RETURN_IF_ERROR (emit_closure_token (out, insn_c, true, false, false));
break;
case OP_CreateGeneratorClosure:
case OP_CreateGeneratorClosureLongIndex:
{
RETURN_IF_ERROR (add (out, reg_l_safe (insn_c, 0)));
RETURN_IF_ERROR (add (out, create_assignment_token ()));
FunctionTableIndexToken *t = (FunctionTableIndexToken *)create_function_table_index_token (get_operand_value (insn_c, 2), NULL);
if (!t) {
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "oom fti");
}
t->environment_id = (int)get_operand_value (insn_c, 1);
t->is_closure = true;
t->is_generator = true;
RETURN_IF_ERROR (add (out, (Token *)t));
break;
}
RETURN_IF_ERROR (emit_closure_token (out, insn_c, true, true, false));
break;
case OP_CreateGenerator:
case OP_CreateGeneratorLongIndex:
{
RETURN_IF_ERROR (add (out, reg_l_safe (insn_c, 0)));
RETURN_IF_ERROR (add (out, create_assignment_token ()));
FunctionTableIndexToken *t = (FunctionTableIndexToken *)create_function_table_index_token (get_operand_value (insn_c, 2), NULL);
if (!t) {
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "oom fti");
}
t->environment_id = (int)get_operand_value (insn_c, 1);
t->is_generator = true;
RETURN_IF_ERROR (add (out, (Token *)t));
break;
}
RETURN_IF_ERROR (emit_closure_token (out, insn_c, false, true, false));
break;
case OP_NewArray:
{
/* rD = new Array (count) */
@@ -642,30 +662,7 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
RETURN_IF_ERROR (add (out, reg_l_safe (insn_c, 0)));
RETURN_IF_ERROR (add (out, create_assignment_token ()));
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 1)));
u32 sid = insn->arg4;
const char *s = NULL;
if (insn->hbc_reader && insn->hbc_reader->strings && sid < insn->hbc_reader->header.stringCount) {
s = insn->hbc_reader->strings[sid];
}
bool ident = true;
if (!s || !*s) {
ident = false;
} else {
if (! (isalpha ((unsigned char)*s) || *s == '_' || *s == '$')) {
ident = false;
}
for (const char *p = s + 1; ident && *p; p++) {
ident = (isalnum ((unsigned char)*p) || *p == '_' || *p == '$');
}
}
if (ident) {
RETURN_IF_ERROR (add (out, create_dot_accessor_token ()));
RETURN_IF_ERROR (add (out, create_raw_token (s)));
} else {
RETURN_IF_ERROR (add (out, create_raw_token ("[")));
RETURN_IF_ERROR (add (out, quoted_string (insn->hbc_reader, sid)));
RETURN_IF_ERROR (add (out, create_raw_token ("]")));
}
RETURN_IF_ERROR (emit_property_access (out, insn_c, insn->arg4));
break;
}
case OP_GetByVal:
@@ -685,12 +682,12 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
RETURN_IF_ERROR (add (out, reg_l_safe (insn_c, 0)));
RETURN_IF_ERROR (add (out, create_assignment_token ()));
StringBuffer sb;
_hbc_string_buffer_init (&sb, 48);
RETURN_IF_ERROR (_hbc_string_buffer_init (&sb, 48));
_hbc_string_buffer_append (&sb, "try_get(");
char nb[16];
snprintf (nb, sizeof (nb), "r%u", (unsigned)insn->arg2);
_hbc_string_buffer_append (&sb, nb);
_hbc_format_property_from_string_id (insn->hbc_reader, (op == OP_TryGetById)? insn->arg4: insn->arg4, &sb);
_hbc_format_property_from_string_id (insn->hbc_reader, insn->arg4, &sb);
_hbc_string_buffer_append (&sb, ")");
Token *t = create_raw_token (sb.data? sb.data: "try_get(r0)");
_hbc_string_buffer_free (&sb);
@@ -703,20 +700,15 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
case OP_PutNewOwnByIdShort:
case OP_PutNewOwnById:
case OP_PutNewOwnByIdLong:
case OP_TryPutById:
case OP_TryPutByIdLong:
case OP_PutNewOwnNEById:
case OP_PutNewOwnNEByIdLong:
{
/* obj.name = value (prefer dot accessor) */
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 0)));
u32 sid = (op == OP_PutNewOwnByIdShort)? insn->arg3: (op == OP_PutNewOwnById)? insn->arg3
: insn->arg4;
const char *s = (insn->hbc_reader && insn->hbc_reader->strings && sid < insn->hbc_reader->header.stringCount)? insn->hbc_reader->strings[sid]: NULL;
if (s && _hbc_is_js_identifier (s)) {
RETURN_IF_ERROR (add (out, create_dot_accessor_token ()));
RETURN_IF_ERROR (add (out, create_raw_token (s)));
} else {
RETURN_IF_ERROR (add (out, create_raw_token ("[")));
RETURN_IF_ERROR (add (out, quoted_string (insn->hbc_reader, sid)));
RETURN_IF_ERROR (add (out, create_raw_token ("]")));
}
u32 sid = (op == OP_PutNewOwnByIdLong || op == OP_PutNewOwnNEByIdLong)? insn->arg4: insn->arg3;
RETURN_IF_ERROR (emit_property_access (out, insn_c, sid));
RETURN_IF_ERROR (add (out, create_assignment_token ()));
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 1)));
break;
@@ -724,13 +716,13 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
case OP_PutOwnByIndex:
case OP_PutOwnByIndexL:
{
/* obj[idx] = value */
/* obj[idx] = value; operands: obj reg, value reg, literal idx */
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 0)));
RETURN_IF_ERROR (add (out, create_raw_token ("[")));
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 1)));
RETURN_IF_ERROR (add (out, num_token_u32 (insn->arg3)));
RETURN_IF_ERROR (add (out, create_raw_token ("]")));
RETURN_IF_ERROR (add (out, create_assignment_token ()));
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 2)));
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 1)));
break;
}
case OP_PutByVal:
@@ -747,12 +739,9 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
case OP_PutById:
case OP_PutByIdLong:
{
/* rObj["name"] = rVal */
/* rObj.name = rVal (prefer dot accessor, matching GetById) */
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 0)));
RETURN_IF_ERROR (add (out, create_raw_token ("[")));
u32 sid = (op == OP_PutById)? insn->arg4: insn->arg4;
RETURN_IF_ERROR (add (out, quoted_string (insn->hbc_reader, sid)));
RETURN_IF_ERROR (add (out, create_raw_token ("]")));
RETURN_IF_ERROR (emit_property_access (out, insn_c, insn->arg4));
RETURN_IF_ERROR (add (out, create_assignment_token ()));
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 1)));
break;
@@ -810,7 +799,7 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
case OP_ResumeGenerator:
{
/* args: result_out, return_bool_out */
if (is_operand_register (insn->inst, 0) && is_operand_register (insn->inst, 1)) {
if (is_operand_register (insn->inst, 0) && is_operand_register (insn->inst, 1)) {
RETURN_IF_ERROR (add (out, create_resume_generator_token ((int)insn->arg1, (int)insn->arg2)));
} else {
RETURN_IF_ERROR (add (out, create_raw_token ("/*resume_gen_invalid*/")));
@@ -820,14 +809,14 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
case OP_SaveGenerator:
case OP_SaveGeneratorLong:
{
u32 target = compute_target_address (insn, 0);
u32 target = _hbc_compute_target_address (insn, 0);
RETURN_IF_ERROR (add (out, create_save_generator_token (target)));
break;
}
case OP_GetEnvironment:
{
if (is_operand_register (insn->inst, 0)) {
RETURN_IF_ERROR (add (out, create_get_environment_token ((int)get_operand_value (insn_c, 0), (int)get_operand_value (insn_c, 1))));
RETURN_IF_ERROR (add (out, create_get_environment_token ((int)hbc_operand_value (insn_c, 0), (int)hbc_operand_value (insn_c, 1))));
} else {
RETURN_IF_ERROR (add (out, create_raw_token ("/*get_env_invalid*/")));
}
@@ -835,24 +824,17 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
}
case OP_StoreToEnvironment:
case OP_StoreToEnvironmentL:
{
if (is_operand_register (insn->inst, 0) && is_operand_register (insn->inst, 2)) {
RETURN_IF_ERROR (add (out, create_store_to_environment_token ((int)get_operand_value (insn_c, 0), (int)get_operand_value (insn_c, 1), (int)get_operand_value (insn_c, 2))));
} else {
RETURN_IF_ERROR (add (out, create_raw_token ("/*env_store_invalid*/")));
}
break;
}
/* The NP ("non-pointer") variant has identical operands and decompiled
* semantics; route it through the same closure-var path so it is named
* consistently and not misread as a plain register write by DCE. */
case OP_StoreNPToEnvironment:
case OP_StoreNPToEnvironmentL:
{
/* env[slot] = value (NP variant) */
RETURN_IF_ERROR (add (out, reg_l_safe (insn_c, 0)));
RETURN_IF_ERROR (add (out, create_raw_token ("[")));
RETURN_IF_ERROR (add (out, num_token_u32 (insn->arg2)));
RETURN_IF_ERROR (add (out, create_raw_token ("]")));
RETURN_IF_ERROR (add (out, create_assignment_token ()));
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 2)));
if (is_operand_register (insn->inst, 0) && is_operand_register (insn->inst, 2)) {
RETURN_IF_ERROR (add (out, create_store_to_environment_token ((int)hbc_operand_value (insn_c, 0), (int)hbc_operand_value (insn_c, 1), (int)hbc_operand_value (insn_c, 2))));
} else {
RETURN_IF_ERROR (add (out, create_raw_token ("/*env_store_invalid*/")));
}
break;
}
case OP_LoadFromEnvironment:
@@ -862,7 +844,7 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
RETURN_IF_ERROR (add (out, reg_l_safe (insn_c, 0)));
RETURN_IF_ERROR (add (out, create_assignment_token ()));
if (is_operand_register (insn->inst, 1)) {
RETURN_IF_ERROR (add (out, create_load_from_environment_token ((int)get_operand_value (insn_c, 1), (int)get_operand_value (insn_c, 2))));
RETURN_IF_ERROR (add (out, create_load_from_environment_token ((int)hbc_operand_value (insn_c, 1), (int)hbc_operand_value (insn_c, 2))));
} else {
RETURN_IF_ERROR (add (out, create_raw_token ("/*env_load_invalid*/")));
}
@@ -879,7 +861,7 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
return r;
}
r = _hbc_format_object_literal (insn->hbc_reader, insn->arg2, insn->arg3, insn->arg4, insn->arg5, &sb, LITERALS_PRETTY_AUTO, false);
Token *t = create_raw_token ((r.code == RESULT_SUCCESS && sb.data)? sb.data: "{ /*object*/ }");
Token *t = create_raw_token ((r.code == RESULT_SUCCESS && sb.data)? sb.data: "{ /*object*/ }");
_hbc_string_buffer_free (&sb);
if (!t) {
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "oom");
@@ -898,7 +880,7 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
return r;
}
r = _hbc_format_array_literal (insn->hbc_reader, insn->arg3, insn->arg4, &sb, LITERALS_PRETTY_AUTO, false);
Token *t = create_raw_token ((r.code == RESULT_SUCCESS && sb.data)? sb.data: "[ /*array*/ ]");
Token *t = create_raw_token ((r.code == RESULT_SUCCESS && sb.data)? sb.data: "[ /*array*/ ]");
_hbc_string_buffer_free (&sb);
if (!t) {
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "oom");
@@ -916,9 +898,10 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
return emit_call_fixed (out, insn_c, 6); /* dest, callee, arg1, arg2, arg3, arg4 */
case OP_Call:
case OP_CallLong:
return emit_call_with_argc (out, insn_c, false);
case OP_Construct:
case OP_ConstructLong:
return emit_call_with_argc (out, insn_c);
return emit_call_with_argc (out, insn_c, true);
/* Delete operations */
case OP_DelById:
case OP_DelByIdLong:
@@ -927,10 +910,10 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 0)));
u32 sid = insn->arg2;
const char *s = NULL;
if (insn->hbc_reader && insn->hbc_reader->strings && sid < insn->hbc_reader->header.stringCount) {
if (insn->hbc_reader && insn->hbc_reader->strings && sid < insn->hbc_reader->header.stringCount) {
s = insn->hbc_reader->strings[sid];
}
if (s && _hbc_is_js_identifier (s)) {
if (s && _hbc_is_js_identifier (s)) {
RETURN_IF_ERROR (add (out, create_dot_accessor_token ()));
RETURN_IF_ERROR (add (out, create_raw_token (s)));
} else {
@@ -973,12 +956,12 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
/* Jump instructions */
case OP_Jmp:
case OP_JmpLong:
return emit_simple_jump (out, insn_c, (i32)get_operand_value (insn_c, 0) <= 0);
return emit_simple_jump (out, insn_c, (i32)hbc_operand_value (insn_c, 0) <= 0);
case OP_JmpTrue:
case OP_JmpTrueLong:
{
i32 rel = (i32)get_operand_value (insn_c, 0);
u32 target = compute_target_address (insn, 0);
i32 rel = (i32)hbc_operand_value (insn_c, 0);
u32 target = _hbc_compute_target_address (insn, 0);
if (rel > 0) {
RETURN_IF_ERROR (add (out, create_jump_not_condition_token (target)));
RETURN_IF_ERROR (add (out, create_raw_token ("!")));
@@ -992,8 +975,8 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
case OP_JmpFalse:
case OP_JmpFalseLong:
{
i32 rel = (i32)get_operand_value (insn_c, 0);
u32 target = compute_target_address (insn, 0);
i32 rel = (i32)hbc_operand_value (insn_c, 0);
u32 target = _hbc_compute_target_address (insn, 0);
if (rel > 0) {
RETURN_IF_ERROR (add (out, create_jump_not_condition_token (target)));
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 1)));
@@ -1007,8 +990,8 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
case OP_JmpUndefined:
case OP_JmpUndefinedLong:
{
i32 rel = (i32)get_operand_value (insn_c, 0);
u32 target = compute_target_address (insn, 0);
i32 rel = (i32)hbc_operand_value (insn_c, 0);
u32 target = _hbc_compute_target_address (insn, 0);
if (rel > 0) {
RETURN_IF_ERROR (add (out, create_jump_not_condition_token (target)));
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 1)));
@@ -1039,27 +1022,30 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
case OP_JStrictEqualLong:
case OP_JStrictNotEqual:
case OP_JStrictNotEqualLong:
/* Conditional jump instructions (N variants) */
case OP_JLessN:
case OP_JLessNLong:
case OP_JNotLessN:
case OP_JNotLessNLong:
case OP_JGreaterN:
case OP_JGreaterNLong:
case OP_JNotGreaterN:
case OP_JNotGreaterNLong:
case OP_JLessEqualN:
case OP_JLessEqualNLong:
case OP_JNotLessEqualN:
case OP_JNotLessEqualNLong:
case OP_JGreaterEqualN:
case OP_JGreaterEqualNLong:
case OP_JNotGreaterEqualN:
case OP_JNotGreaterEqualNLong:
{
const char *cmp = jump_cmp_operator (op);
if (!cmp) {
RETURN_IF_ERROR (add (out, create_raw_token ("/*jump_cmp*/")));
break;
}
i32 rel = (i32)get_operand_value (insn_c, 0);
u32 target = compute_target_address (insn, 0);
if (rel > 0) {
RETURN_IF_ERROR (add (out, create_jump_not_condition_token (target)));
RETURN_IF_ERROR (add (out, create_raw_token ("!")));
RETURN_IF_ERROR (add (out, create_left_parenthesis_token ()));
} else {
RETURN_IF_ERROR (add (out, create_jump_condition_token (target)));
}
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 1)));
RETURN_IF_ERROR (add (out, create_raw_token (cmp)));
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 2)));
if (rel > 0) {
RETURN_IF_ERROR (add (out, create_right_parenthesis_token ()));
}
RETURN_IF_ERROR (emit_cmp_jump (out, insn_c, cmp));
break;
}
/* Built-in calls */
@@ -1083,7 +1069,7 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
{
RETURN_IF_ERROR (add (out, reg_l_safe (insn_c, 0)));
RETURN_IF_ERROR (add (out, create_assignment_token ()));
FunctionTableIndexToken *t = (FunctionTableIndexToken *)create_function_table_index_token (get_operand_value (insn_c, 1), NULL);
FunctionTableIndexToken *t = (FunctionTableIndexToken *)create_function_table_index_token (hbc_operand_value (insn_c, 1), NULL);
if (!t) {
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "oom fti");
}
@@ -1097,11 +1083,14 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
{
RETURN_IF_ERROR (add (out, reg_l_safe (insn_c, 0)));
RETURN_IF_ERROR (add (out, create_assignment_token ()));
RETURN_IF_ERROR (add (out, create_raw_token ("fn_")));
RETURN_IF_ERROR (add (out, num_token_u32 ((op == OP_CallDirect)? insn->arg4: insn->arg4)));
Token *t = create_function_table_index_token (hbc_operand_value (insn_c, 2), NULL);
if (!t) {
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "oom fti");
}
RETURN_IF_ERROR (add (out, t));
RETURN_IF_ERROR (add (out, create_left_parenthesis_token ()));
RETURN_IF_ERROR (add (out, create_raw_token ("/*argc:")));
RETURN_IF_ERROR (add (out, num_token_u32 (insn->arg3)));
RETURN_IF_ERROR (add (out, num_token_u32 (hbc_operand_value (insn_c, 1))));
RETURN_IF_ERROR (add (out, create_raw_token ("*/")));
RETURN_IF_ERROR (add (out, create_right_parenthesis_token ()));
break;
@@ -1151,53 +1140,10 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
RETURN_IF_ERROR (add (out, create_raw_token ("switch(")));
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 0)));
RETURN_IF_ERROR (add (out, create_raw_token (") /*tableSize:")));
RETURN_IF_ERROR (add (out, num_token_u32 (insn->arg2)));
RETURN_IF_ERROR (add (out, num_token_u32 (insn->switch_jump_table_size)));
RETURN_IF_ERROR (add (out, create_raw_token ("*/")));
break;
}
/* Property operations (variants) */
case OP_TryPutById:
case OP_TryPutByIdLong:
{
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 0)));
u32 sid = insn->arg3;
const char *s = NULL;
if (insn->hbc_reader && insn->hbc_reader->strings && sid < insn->hbc_reader->header.stringCount) {
s = insn->hbc_reader->strings[sid];
}
if (s && _hbc_is_js_identifier (s)) {
RETURN_IF_ERROR (add (out, create_dot_accessor_token ()));
RETURN_IF_ERROR (add (out, create_raw_token (s)));
} else {
RETURN_IF_ERROR (add (out, create_raw_token ("[")));
RETURN_IF_ERROR (add (out, quoted_string (insn->hbc_reader, sid)));
RETURN_IF_ERROR (add (out, create_raw_token ("]")));
}
RETURN_IF_ERROR (add (out, create_assignment_token ()));
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 1)));
break;
}
case OP_PutNewOwnNEById:
case OP_PutNewOwnNEByIdLong:
{
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 0)));
u32 sid = (op == OP_PutNewOwnNEById)? insn->arg3: insn->arg4;
const char *s = NULL;
if (insn->hbc_reader && insn->hbc_reader->strings && sid < insn->hbc_reader->header.stringCount) {
s = insn->hbc_reader->strings[sid];
}
if (s && _hbc_is_js_identifier (s)) {
RETURN_IF_ERROR (add (out, create_dot_accessor_token ()));
RETURN_IF_ERROR (add (out, create_raw_token (s)));
} else {
RETURN_IF_ERROR (add (out, create_raw_token ("[")));
RETURN_IF_ERROR (add (out, quoted_string (insn->hbc_reader, sid)));
RETURN_IF_ERROR (add (out, create_raw_token ("]")));
}
RETURN_IF_ERROR (add (out, create_assignment_token ()));
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 1)));
break;
}
case OP_PutOwnByVal:
{
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 0)));
@@ -1224,9 +1170,9 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
/* Property list operations */
case OP_GetPNameList:
{
if (is_operand_register (insn->inst, 0) && is_operand_register (insn->inst, 1) &&
is_operand_register (insn->inst, 2) && is_operand_register (insn->inst, 3)) {
RETURN_IF_ERROR (add (out, create_for_in_loop_init_token ((int)get_operand_value (insn_c, 0), (int)get_operand_value (insn_c, 1), (int)get_operand_value (insn_c, 2), (int)get_operand_value (insn_c, 3))));
if (is_operand_register (insn->inst, 0) && is_operand_register (insn->inst, 1) &&
is_operand_register (insn->inst, 2) && is_operand_register (insn->inst, 3)) {
RETURN_IF_ERROR (add (out, create_for_in_loop_init_token ((int)hbc_operand_value (insn_c, 0), (int)hbc_operand_value (insn_c, 1), (int)hbc_operand_value (insn_c, 2), (int)hbc_operand_value (insn_c, 3))));
} else {
RETURN_IF_ERROR (add (out, create_raw_token ("/*forin_init_invalid*/")));
}
@@ -1234,10 +1180,10 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
}
case OP_GetNextPName:
{
if (is_operand_register (insn->inst, 0) && is_operand_register (insn->inst, 1) &&
is_operand_register (insn->inst, 2) && is_operand_register (insn->inst, 3) &&
if (is_operand_register (insn->inst, 0) && is_operand_register (insn->inst, 1) &&
is_operand_register (insn->inst, 2) && is_operand_register (insn->inst, 3) &&
is_operand_register (insn->inst, 4)) {
RETURN_IF_ERROR (add (out, create_for_in_loop_next_iter_token ((int)get_operand_value (insn_c, 0), (int)get_operand_value (insn_c, 1), (int)get_operand_value (insn_c, 2), (int)get_operand_value (insn_c, 3), (int)get_operand_value (insn_c, 4))));
RETURN_IF_ERROR (add (out, create_for_in_loop_next_iter_token ((int)hbc_operand_value (insn_c, 0), (int)hbc_operand_value (insn_c, 1), (int)hbc_operand_value (insn_c, 2), (int)hbc_operand_value (insn_c, 3), (int)hbc_operand_value (insn_c, 4))));
} else {
RETURN_IF_ERROR (add (out, create_raw_token ("/*forin_next_invalid*/")));
}
@@ -1246,62 +1192,11 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
/* Async closures */
case OP_CreateAsyncClosure:
case OP_CreateAsyncClosureLongIndex:
{
RETURN_IF_ERROR (add (out, reg_l_safe (insn_c, 0)));
RETURN_IF_ERROR (add (out, create_assignment_token ()));
FunctionTableIndexToken *t = (FunctionTableIndexToken *)create_function_table_index_token (get_operand_value (insn_c, 2), NULL);
if (!t) {
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "oom fti");
}
t->environment_id = (int)get_operand_value (insn_c, 1);
t->is_async = true;
t->is_closure = true;
RETURN_IF_ERROR (add (out, (Token *)t));
break;
}
/* Conditional jump instructions (N variants) */
case OP_JLessN:
case OP_JLessNLong:
case OP_JNotLessN:
case OP_JNotLessNLong:
case OP_JGreaterN:
case OP_JGreaterNLong:
case OP_JNotGreaterN:
case OP_JNotGreaterNLong:
case OP_JLessEqualN:
case OP_JLessEqualNLong:
case OP_JNotLessEqualN:
case OP_JNotLessEqualNLong:
case OP_JGreaterEqualN:
case OP_JGreaterEqualNLong:
case OP_JNotGreaterEqualN:
case OP_JNotGreaterEqualNLong:
{
const char *cmp = jump_cmp_operator (op);
if (!cmp) {
RETURN_IF_ERROR (add (out, create_raw_token ("/*jump_cmp*/")));
break;
}
i32 rel = (i32)get_operand_value (insn_c, 0);
u32 target = compute_target_address (insn, 0);
if (rel > 0) {
RETURN_IF_ERROR (add (out, create_jump_not_condition_token (target)));
RETURN_IF_ERROR (add (out, create_raw_token ("!")));
RETURN_IF_ERROR (add (out, create_left_parenthesis_token ()));
} else {
RETURN_IF_ERROR (add (out, create_jump_condition_token (target)));
}
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 1)));
RETURN_IF_ERROR (add (out, create_raw_token (cmp)));
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 2)));
if (rel > 0) {
RETURN_IF_ERROR (add (out, create_right_parenthesis_token ()));
}
break;
}
RETURN_IF_ERROR (emit_closure_token (out, insn_c, true, false, true));
break;
default:
{
if (!insn->inst->name || strcmp (insn->inst->name, "Unknown") == 0) {
if (!insn->inst->name || strcmp (insn->inst->name, "Unknown") == 0) {
char buf[96];
u32 skipped = insn->arg1;
if (skipped) {
@@ -1319,28 +1214,14 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
/* Generic compare-jump fallback (covers variants not explicitly handled above). */
const char *cmp = jump_cmp_operator (op);
if (cmp && is_operand_addr (insn->inst, 0) && is_operand_register (insn->inst, 1) && is_operand_register (insn->inst, 2)) {
i32 rel = (i32)get_operand_value (insn_c, 0);
u32 target = compute_target_address (insn, 0);
if (rel > 0) {
RETURN_IF_ERROR (add (out, create_jump_not_condition_token (target)));
RETURN_IF_ERROR (add (out, create_raw_token ("!")));
RETURN_IF_ERROR (add (out, create_left_parenthesis_token ()));
} else {
RETURN_IF_ERROR (add (out, create_jump_condition_token (target)));
}
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 1)));
RETURN_IF_ERROR (add (out, create_raw_token (cmp)));
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 2)));
if (rel > 0) {
RETURN_IF_ERROR (add (out, create_right_parenthesis_token ()));
}
if (cmp && _hbc_operand_is_addr (insn->inst, 0) && is_operand_register (insn->inst, 1) && is_operand_register (insn->inst, 2)) {
RETURN_IF_ERROR (emit_cmp_jump (out, insn_c, cmp));
break;
}
/* Fallback: emit a comment-like raw token with mnemonic */
StringBuffer sb;
_hbc_string_buffer_init (&sb, 64);
RETURN_IF_ERROR (_hbc_string_buffer_init (&sb, 64));
_hbc_string_buffer_append (&sb, "/* ");
_hbc_string_buffer_append (&sb, insn->inst->name);
_hbc_string_buffer_append (&sb, " */");
+675 -60
View File
@@ -1,8 +1,11 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/hbc.h>
#include <hbc/common.h>
#include <hbc/disasm.h>
#include <hbc/decompilation/decompiler.h>
#include <hbc/decompilation/literals.h>
#include <hbc/literals.h>
#include <hbc/hermes_encoder.h>
#include <hbc/parser.h>
#include <hbc/bytecode.h>
@@ -117,7 +120,16 @@ Result hbc_open_from_memory(const u8 *data, size_t size, HBC **out) {
hbc_close (hbc);
return res;
}
(void)_hbc_reader_read_debug_info;
/* Read only the DebugInfoHeader eagerly — a handful of u32s — so
* hbc_has_source_lines () can answer accurately without committing to the
* full body parse. The filename, file region and sources/scope/textified
* storage blobs are read lazily on first hbc_get_source_lines /
* hbc_get_debug_info call. */
res = _hbc_reader_read_debug_info_header (&hbc->reader);
if (res.code != RESULT_SUCCESS) {
hbc_close (hbc);
return res;
}
*out = hbc;
return SUCCESS_RESULT ();
@@ -127,6 +139,7 @@ void hbc_close(HBC *hbc) {
if (!hbc) {
return;
}
hbc_literals_reset (hbc);
_hbc_reader_cleanup (&hbc->reader);
free (hbc);
}
@@ -237,10 +250,232 @@ Result hbc_get_function_source(HBC *hbc, u32 function_id, const char **out_src)
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Function ID out of range");
}
*out_src = NULL;
if (hbc->reader.function_sources && function_id < hbc->reader.header.functionSourceCount) {
u32 string_id = hbc->reader.function_sources[function_id].string_id;
if (string_id < hbc->reader.header.stringCount) {
*out_src = hbc->reader.strings[string_id];
if (hbc->reader.function_sources) {
for (u32 i = 0; i < hbc->reader.header.functionSourceCount; i++) {
if (hbc->reader.function_sources[i].function_id != function_id) {
continue;
}
u32 string_id = hbc->reader.function_sources[i].string_id;
if (string_id < hbc->reader.header.stringCount) {
*out_src = hbc->reader.strings[string_id];
}
break;
}
}
return SUCCESS_RESULT ();
}
static bool read_sleb128(const u8 *data, size_t size, size_t *pos, int64_t *out) {
uint64_t result = 0;
unsigned shift = 0;
u8 byte = 0;
do {
if (*pos >= size || shift > 63) {
return false;
}
byte = data[(*pos)++];
if (shift == 63 && (byte & 0x7e)) {
return false;
}
result |= ((uint64_t) (byte & 0x7f)) << shift;
shift += 7;
} while (byte & 0x80);
if ((shift < 64) && (byte & 0x40)) {
result |= (~0ULL) << shift;
}
*out = (int64_t)result;
return true;
}
static const char *debug_filename_at(HBCReader *reader, u32 index) {
const u32 count = reader->debug_info_header.filename_count;
if (index >= count || !reader->debug_string_table || !reader->debug_string_storage) {
return NULL;
}
if (!reader->debug_filenames) {
reader->debug_filenames = (char **)calloc (count, sizeof (char *));
if (!reader->debug_filenames) {
return NULL;
}
}
if (!reader->debug_filenames[index]) {
const OffsetLengthPair *entry = &reader->debug_string_table[index];
const size_t storage_size = reader->debug_string_storage_size;
if ((size_t)entry->offset > storage_size || (size_t)entry->length > storage_size - entry->offset) {
return NULL;
}
char *filename = (char *)malloc ((size_t)entry->length + 1);
if (!filename) {
return NULL;
}
memcpy (filename, reader->debug_string_storage + entry->offset, entry->length);
filename[entry->length] = '\0';
reader->debug_filenames[index] = filename;
}
return reader->debug_filenames[index];
}
static const char *debug_filename_for_source_offset(HBCReader *reader, u32 source_offset) {
const DebugFileRegion *best = NULL;
for (u32 i = 0; i < reader->debug_file_region_count; i++) {
const DebugFileRegion *region = &reader->debug_file_regions[i];
if (region->from_address <= source_offset && (!best || region->from_address >= best->from_address)) {
best = region;
}
}
return best? debug_filename_at (reader, best->filename_id): NULL;
}
static Result append_source_line(HBCSourceLineArray *arr, u32 *cap, const HBCSourceLine *line) {
if (arr->count >= *cap) {
u32 next = *cap? (*cap * 2): 64;
HBCSourceLine *lines = (HBCSourceLine *)realloc (arr->lines, (size_t)next * sizeof (HBCSourceLine));
if (!lines) {
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "Failed to allocate source lines");
}
arr->lines = lines;
*cap = next;
}
arr->lines[arr->count++] = *line;
return SUCCESS_RESULT ();
}
#define PARSE_FAIL(msg) \
do { \
res = ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, msg); \
goto fail; \
} while (0)
Result hbc_get_source_lines(HBC *hbc, HBCSourceLineArray *out) {
if (!hbc || !out) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Invalid arguments");
}
memset (out, 0, sizeof (*out));
HBCReader *reader = &hbc->reader;
RETURN_IF_ERROR (_hbc_reader_read_debug_info (reader));
const u8 *data = reader->sources_data_storage;
const size_t size = reader->sources_data_storage_size;
if (!data || !size) {
return SUCCESS_RESULT ();
}
Result res = SUCCESS_RESULT ();
u32 cap = 0;
size_t pos = 0;
while (pos < size) {
const size_t source_offset = pos;
int64_t function_id = 0, line = 0, column = 0;
if (source_offset > UINT32_MAX || !read_sleb128 (data, size, &pos, &function_id) || !read_sleb128 (data, size, &pos, &line) || !read_sleb128 (data, size, &pos, &column)) {
PARSE_FAIL ("Invalid source location header");
}
if (function_id < 0 || function_id >= (int64_t)reader->header.functionCount || line < 0 || line > UINT32_MAX || column < 0 || column > UINT32_MAX) {
PARSE_FAIL ("Source location header out of range");
}
HBCFunc fi = { 0 };
(void)hbc_get_function_info (hbc, (u32)function_id, &fi);
HBCSourceLine sl = {
.function_id = (u32)function_id,
.address = fi.offset,
.line = (u32)line,
.column = (u32)column,
.filename = debug_filename_for_source_offset (reader, (u32)source_offset)
};
res = append_source_line (out, &cap, &sl);
if (res.code != RESULT_SUCCESS) {
goto fail;
}
int64_t address = 0, statement = 0;
for (;;) {
int64_t adelta = 0;
if (!read_sleb128 (data, size, &pos, &adelta)) {
PARSE_FAIL ("Invalid source location address delta");
}
if (adelta == -1) {
break;
}
int64_t ldelta = 0, cdelta = 0, scope = 0, env = 0, sdelta = 0;
if (adelta < 0 || !read_sleb128 (data, size, &pos, &ldelta) || !read_sleb128 (data, size, &pos, &cdelta) || !read_sleb128 (data, size, &pos, &scope) || !read_sleb128 (data, size, &pos, &env)) {
PARSE_FAIL ("Invalid source location entry");
}
if ((ldelta & 1) && !read_sleb128 (data, size, &pos, &sdelta)) {
PARSE_FAIL ("Invalid source location statement delta");
}
ldelta = (ldelta - (ldelta & 1)) / 2;
address += adelta;
line += ldelta;
column += cdelta;
statement += sdelta;
if (address < 0 || address > UINT32_MAX || (uint64_t)fi.offset + (uint64_t)address > UINT32_MAX || line < 0 || line > UINT32_MAX || column < 0 || column > UINT32_MAX || statement < 0 || statement > UINT32_MAX) {
PARSE_FAIL ("Source location entry out of range");
}
sl.address = fi.offset + (u32)address;
sl.function_address = (u32)address;
sl.line = (u32)line;
sl.column = (u32)column;
sl.statement = (u32)statement;
res = append_source_line (out, &cap, &sl);
if (res.code != RESULT_SUCCESS) {
goto fail;
}
}
}
return SUCCESS_RESULT ();
fail:
hbc_free_source_lines (out);
return res;
}
#undef PARSE_FAIL
void hbc_free_source_lines(HBCSourceLineArray *arr) {
if (!arr) {
return;
}
free (arr->lines);
memset (arr, 0, sizeof (*arr));
}
/* Describes the file, not the runtime preference: true iff the file carries
* a non-empty source-locations blob. Relies on the DebugInfoHeader parsed at
* load time, so callers pay no body-parse cost for this query. */
bool hbc_has_source_lines(HBC *hbc) {
if (!hbc || hbc->reader.header.debugInfoOffset == 0) {
return false;
}
if (_hbc_reader_read_debug_info_header (&hbc->reader).code != RESULT_SUCCESS) {
return false;
}
return hbc->reader.debug_info_header.scope_desc_data_offset > 0;
}
Result hbc_get_debug_info(HBC *hbc, HBCDebugInfo *out) {
if (!hbc || !out) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Invalid arguments");
}
memset (out, 0, sizeof (*out));
HBCReader *reader = &hbc->reader;
out->has_debug_info = reader->header.debugInfoOffset != 0;
if (!out->has_debug_info) {
return SUCCESS_RESULT ();
}
RETURN_IF_ERROR (_hbc_reader_read_debug_info (reader));
out->filename_count = reader->debug_info_header.filename_count;
out->filename_storage_size = reader->debug_info_header.filename_storage_size;
out->file_region_count = reader->debug_info_header.file_region_count;
out->source_locations_size = (u32)reader->sources_data_storage_size;
out->scope_desc_data_size = (u32)reader->scope_desc_data_storage_size;
out->textified_data_size = (u32)reader->textified_data_storage_size;
out->string_table_size = (u32)reader->string_table_storage_size;
out->debug_data_size = reader->debug_info_header.debug_data_size;
if (reader->function_headers) {
for (u32 i = 0; i < reader->header.functionCount; i++) {
if (reader->function_headers[i].hasDebugInfo) {
out->functions_with_debug_info++;
}
}
}
return SUCCESS_RESULT ();
@@ -387,6 +622,10 @@ static char *build_literal_comment(HBCReader *reader, const char *mnemonic, cons
return result;
}
/* Read each operand by type into values[6], little-endian with sign-extension
* for relative addresses; returns total instruction size, or 0 if truncated. */
static size_t decode_operands(const Instruction *inst, const u8 *bytes, size_t len, u32 values[6]);
Result hbc_dec(const HBCDecodeCtx *ctx, HBCInsnInfo *out) {
if (!ctx || !out) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Invalid context");
@@ -412,7 +651,7 @@ Result hbc_dec(const HBCDecodeCtx *ctx, HBCInsnInfo *out) {
u8 opcode = ctx->bytes[0];
/* Get instruction definition */
const Instruction *inst = (opcode < isa.count) ? &isa.instructions[opcode] : NULL;
const Instruction *inst = (opcode < isa.count)? &isa.instructions[opcode]: NULL;
if (!inst || !inst->name) {
out->text = strdup ("unk");
out->size = 1;
@@ -426,59 +665,8 @@ Result hbc_dec(const HBCDecodeCtx *ctx, HBCInsnInfo *out) {
}
/* Parse operands */
u32 operand_values[6] = { 0 };
size_t pos = 1; /* Start after opcode */
for (int i = 0; i < 6 && inst->operands[i].operand_type != OPERAND_TYPE_NONE; i++) {
OperandType operand_type = inst->operands[i].operand_type;
/* Check if we have enough bytes */
size_t need = 0;
switch (operand_type) {
case OPERAND_TYPE_REG8:
case OPERAND_TYPE_UINT8:
case OPERAND_TYPE_ADDR8: need = 1; break;
case OPERAND_TYPE_UINT16: need = 2; break;
case OPERAND_TYPE_REG32:
case OPERAND_TYPE_UINT32:
case OPERAND_TYPE_IMM32:
case OPERAND_TYPE_ADDR32: need = 4; break;
case OPERAND_TYPE_DOUBLE: need = 8; break;
default: need = 0; break;
}
if (pos + need > ctx->len) {
break;
}
/* Read operand value */
switch (operand_type) {
case OPERAND_TYPE_REG8:
case OPERAND_TYPE_UINT8:
case OPERAND_TYPE_ADDR8:
operand_values[i] = ctx->bytes[pos];
pos += 1;
break;
case OPERAND_TYPE_UINT16:
operand_values[i] = ctx->bytes[pos] | (ctx->bytes[pos + 1] << 8);
pos += 2;
break;
case OPERAND_TYPE_REG32:
case OPERAND_TYPE_UINT32:
case OPERAND_TYPE_IMM32:
case OPERAND_TYPE_ADDR32:
operand_values[i] = ctx->bytes[pos] | (ctx->bytes[pos + 1] << 8) |
(ctx->bytes[pos + 2] << 16) | (ctx->bytes[pos + 3] << 24);
pos += 4;
break;
case OPERAND_TYPE_DOUBLE:
/* Skip for now */
pos += 8;
break;
default:
break;
}
}
u32 operand_values[6];
size_t pos = decode_operands (inst, ctx->bytes, ctx->len, operand_values);
/* Format output string */
char buf[512];
@@ -561,7 +749,7 @@ Result hbc_dec(const HBCDecodeCtx *ctx, HBCInsnInfo *out) {
!strncmp (mnemonic, "new_array_with_buffer", strlen ("new_array_with_buffer"));
if (is_literal_op) {
char *with_comment = build_literal_comment (&ctx->hbc->reader, mnemonic, operand_values, buf);
out->text = with_comment ? with_comment : strdup (buf);
out->text = with_comment? with_comment: strdup (buf);
} else {
out->text = strdup (buf);
}
@@ -589,6 +777,433 @@ Result hbc_dec(const HBCDecodeCtx *ctx, HBCInsnInfo *out) {
return SUCCESS_RESULT ();
}
typedef struct {
const char *name; /* exact match, else match prefix */
const char *prefix;
const char *kind;
const char *module; /* NULL for exports */
HBCBindingType type;
} BindingHint;
static const BindingHint g_hints[] = {
{ "NativeModules", NULL, "native", "react-native", HBC_BINDING_IMPORT },
{ "TurboModuleRegistry", NULL, "native", "react-native", HBC_BINDING_IMPORT },
{ "__turboModuleProxy", NULL, "native", "react-native", HBC_BINDING_IMPORT },
{ "requireNativeComponent", NULL, "native", "react-native", HBC_BINDING_IMPORT },
{ "codegenNativeComponent", NULL, "native", "react-native", HBC_BINDING_IMPORT },
{ "NativeEventEmitter", NULL, "native", "react-native", HBC_BINDING_IMPORT },
{ "RCTDeviceEventEmitter", NULL, "native", "react-native", HBC_BINDING_IMPORT },
{ "HermesInternal", NULL, "native", "react-native", HBC_BINDING_IMPORT },
{ "require", NULL, "js", "react-native", HBC_BINDING_IMPORT },
{ "metroRequire", NULL, "js", "react-native", HBC_BINDING_IMPORT },
{ "__r", NULL, "js", "react-native", HBC_BINDING_IMPORT },
{ "importScripts", NULL, "js", "react-native", HBC_BINDING_IMPORT },
{ "global", NULL, "global", "react-native", HBC_BINDING_IMPORT },
{ "globalThis", NULL, "global", "react-native", HBC_BINDING_IMPORT },
{ "exports", NULL, "module", NULL, HBC_BINDING_EXPORT },
{ "module", NULL, "module", NULL, HBC_BINDING_EXPORT },
{ "__esModule", NULL, "module", NULL, HBC_BINDING_EXPORT },
{ "default", NULL, "module", NULL, HBC_BINDING_EXPORT },
{ NULL, "RCT", "native", "react-native", HBC_BINDING_IMPORT },
{ NULL, "Native", "native", "react-native", HBC_BINDING_IMPORT },
};
#define HINTS_N (sizeof (g_hints) / sizeof (g_hints[0]))
/* Per-sid dedup flags for hbc_scan_bindings; avoids O (N^2) strcmp dedup. */
#define SEEN_CJS 0x1
#define SEEN_IMP 0x2
#define SEEN_EXP 0x4
static bool is_export_marker(const char *s) {
return !strcmp (s, "exports") || !strcmp (s, "module");
}
static bool is_export_store(const char *name) {
return strstr (name, "put") && strstr (name, "by_id");
}
static Result binding_add(HBCBindings *out, HBCBindingType type, const char *kind, const char *name, const char *module, u32 function_id, u32 offset, u32 string_id) {
HBCBinding *nb = realloc (out->bindings, (out->count + 1) * sizeof (HBCBinding));
if (!nb) {
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "OOM");
}
out->bindings = nb;
HBCBinding *b = &nb[out->count++];
*b = (HBCBinding){
.type = type,
.kind = kind,
.name = strdup (name),
.module = module? strdup (module): NULL,
.function_id = function_id,
.offset = offset,
.string_id = string_id,
};
if (!b->name || (module && !b->module)) {
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "OOM");
}
return SUCCESS_RESULT ();
}
static size_t decode_operands(const Instruction *inst, const u8 *bytes, size_t len, u32 values[6]) {
size_t pos = 1;
memset (values, 0, 6 * sizeof (u32));
for (int i = 0; i < 6 && inst->operands[i].operand_type != OPERAND_TYPE_NONE; i++) {
OperandType t = inst->operands[i].operand_type;
size_t need;
switch (t) {
case OPERAND_TYPE_REG8:
case OPERAND_TYPE_UINT8:
need = 1;
if (pos + need > len) {
return 0;
}
values[i] = bytes[pos];
break;
case OPERAND_TYPE_ADDR8:
need = 1;
if (pos + need > len) {
return 0;
}
values[i] = (u32) (i32) (i8)bytes[pos];
break;
case OPERAND_TYPE_UINT16:
need = 2;
if (pos + need > len) {
return 0;
}
values[i] = (u32)bytes[pos] | ((u32)bytes[pos + 1] << 8);
break;
case OPERAND_TYPE_REG32:
case OPERAND_TYPE_UINT32:
case OPERAND_TYPE_IMM32:
case OPERAND_TYPE_ADDR32:
need = 4;
if (pos + need > len) {
return 0;
}
values[i] = (u32)bytes[pos] | ((u32)bytes[pos + 1] << 8) | ((u32)bytes[pos + 2] << 16) | ((u32)bytes[pos + 3] << 24);
break;
case OPERAND_TYPE_DOUBLE:
need = 8;
if (pos + need > len) {
return 0;
}
break;
default:
return 0;
}
pos += need;
}
return pos;
}
static Result scan_hint_matches(HBCBindings *out, const char *s, u32 fid, u32 off, u32 sid, u8 *flag) {
/* Dedup identical (kind,module) across hints (pointers are stable literals).
* Set SEEN_EXP when adding an EXPORT so caller's put_by_id skips this sid. */
const void *added[HINTS_N * 2];
u32 added_n = 0;
for (size_t h = 0; h < HINTS_N; h++) {
const BindingHint *hint = &g_hints[h];
bool match = hint->name? !strcmp (s, hint->name): !strncmp (s, hint->prefix, strlen (hint->prefix));
if (!match) {
continue;
}
bool dup = false;
for (u32 a = 0; a < added_n; a += 2) {
if (added[a] == hint->kind && added[a + 1] == hint->module) {
dup = true;
break;
}
}
if (dup) {
continue;
}
added[added_n++] = hint->kind;
added[added_n++] = hint->module;
if (hint->type == HBC_BINDING_EXPORT) {
*flag |= SEEN_EXP;
}
RETURN_IF_ERROR (binding_add (out, hint->type, hint->kind, s, hint->module, fid, off, sid));
}
return SUCCESS_RESULT ();
}
Result hbc_scan_bindings(HBC *hbc, HBCBindings *out) {
if (!hbc || !out) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "invalid args");
}
memset (out, 0, sizeof (*out));
HBCReader *r = &hbc->reader;
HBCISA isa = hbc_isa_getv (r->header.version);
if (!isa.instructions) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "no isa for version");
}
u32 sc = r->header.stringCount;
u8 *seen = sc? calloc (sc, 1): NULL;
if (sc && !seen) {
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "OOM");
}
Result res = SUCCESS_RESULT ();
if (r->header.cjsModuleCount > 0 && r->header.version >= 77 && r->cjs_modules) {
for (u32 i = 0; i < r->header.cjsModuleCount; i++) {
u32 sid = r->cjs_modules[i].symbol_id;
const char *name = (sid < sc && r->strings)? r->strings[sid]: NULL;
if (!name || !*name || (seen[sid] & SEEN_CJS)) {
continue;
}
seen[sid] |= SEEN_CJS;
res = binding_add (out, HBC_BINDING_IMPORT, "cjs", name, "commonjs", 0, r->cjs_modules[i].offset, sid);
if (res.code != RESULT_SUCCESS) {
goto done;
}
}
}
struct {
u32 sid, off;
} *cand = NULL;
u32 cand_cap = 0;
for (u32 fid = 0; fid < r->header.functionCount; fid++) {
const u8 *code = NULL;
u32 size = 0;
if (hbc_get_function_bytecode (hbc, fid, &code, &size).code != RESULT_SUCCESS || !code || !size) {
continue;
}
HBCFunc fi = { 0 };
(void)hbc_get_function_info (hbc, fid, &fi);
u32 cand_n = 0;
bool has_marker = false;
for (u32 pc = 0; pc < size;) {
u8 op = code[pc];
const Instruction *inst = (op < isa.count)? &isa.instructions[op]: NULL;
if (!inst || !inst->name || !inst->binary_size) {
break;
}
u32 values[6];
if (!decode_operands (inst, code + pc, size - pc, values)) {
break;
}
bool is_store = is_export_store (inst->name);
for (int i = 0; i < 6 && inst->operands[i].operand_type != OPERAND_TYPE_NONE; i++) {
if (inst->operands[i].operand_meaning != OPERAND_MEANING_STRING_ID) {
continue;
}
u32 sid = values[i];
if (sid >= sc || !r->strings) {
continue;
}
const char *s = r->strings[sid];
if (!s || !*s) {
continue;
}
bool marker = is_export_marker (s);
if (marker) {
has_marker = true;
}
if (! (seen[sid] & SEEN_IMP)) {
seen[sid] |= SEEN_IMP;
res = scan_hint_matches (out, s, fid, fi.offset + pc, sid, &seen[sid]);
if (res.code != RESULT_SUCCESS) {
goto done_scan;
}
}
if (is_store && !marker && ! (seen[sid] & SEEN_EXP)) {
if (cand_n == cand_cap) {
u32 nc = cand_cap? cand_cap * 2: 16;
void *nb = realloc (cand, nc * sizeof (*cand));
if (!nb) {
res = ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "OOM");
goto done_scan;
}
cand = nb;
cand_cap = nc;
}
cand[cand_n].sid = sid;
cand[cand_n].off = fi.offset + pc;
cand_n++;
}
}
pc += inst->binary_size;
}
if (!has_marker) {
continue;
}
for (u32 k = 0; k < cand_n; k++) {
u32 sid = cand[k].sid;
if (seen[sid] & SEEN_EXP) {
continue;
}
seen[sid] |= SEEN_EXP;
res = binding_add (out, HBC_BINDING_EXPORT, "module", r->strings[sid], NULL, fid, cand[k].off, sid);
if (res.code != RESULT_SUCCESS) {
goto done_scan;
}
}
}
done_scan:
free (cand);
done:
free (seen);
if (res.code != RESULT_SUCCESS) {
hbc_free_bindings (out);
}
return res;
}
void hbc_free_bindings(HBCBindings *bindings) {
if (!bindings || !bindings->bindings) {
return;
}
for (u32 i = 0; i < bindings->count; i++) {
free (bindings->bindings[i].name);
free (bindings->bindings[i].module);
}
free (bindings->bindings);
bindings->bindings = NULL;
bindings->count = 0;
}
static char *dup_range(const char *s, size_t n) {
char *ret = (char *)malloc (n + 1);
if (!ret) {
return NULL;
}
memcpy (ret, s, n);
ret[n] = 0;
return ret;
}
static char *package_from_path(const char *s) {
const char *nm = strstr (s, "node_modules/");
if (nm) {
const char *p = nm + strlen ("node_modules/");
const char *end;
if (*p == '@') {
const char *scope_end = strchr (p, '/');
if (!scope_end) {
return strdup (p);
}
end = strchr (scope_end + 1, '/');
} else {
end = strchr (p, '/');
}
return end? dup_range (p, (size_t) (end - p)): strdup (p);
}
if (s[0] == '@') {
const char *scope_end = strchr (s, '/');
if (scope_end) {
const char *end = strchr (scope_end + 1, '/');
return end? dup_range (s, (size_t) (end - s)): strdup (s);
}
}
return NULL;
}
static bool module_eq(const HBCModule *m, const char *kind, const char *name) {
return m->kind && kind && !strcmp (m->kind, kind) && m->name && name && !strcmp (m->name, name);
}
static Result module_add(HBCModules *out, const char *kind, const char *name, const char *path, const char *version, u32 function_id, u32 offset, u32 string_id, bool inferred) {
if (!name || !*name) {
return SUCCESS_RESULT ();
}
for (u32 i = 0; i < out->count; i++) {
if (module_eq (&out->modules[i], kind, name)) {
return SUCCESS_RESULT ();
}
}
HBCModule *nm = realloc (out->modules, (out->count + 1) * sizeof (HBCModule));
if (!nm) {
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "OOM");
}
out->modules = nm;
HBCModule *m = &nm[out->count++];
*m = (HBCModule){
.name = strdup (name),
.path = path? strdup (path): NULL,
.version = version? strdup (version): NULL,
.kind = kind,
.function_id = function_id,
.offset = offset,
.string_id = string_id,
.inferred = inferred,
};
if (!m->name || (path && !m->path) || (version && !m->version)) {
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "OOM");
}
return SUCCESS_RESULT ();
}
Result hbc_list_modules(HBC *hbc, HBCModules *out) {
if (!hbc || !out) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "invalid args");
}
memset (out, 0, sizeof (*out));
HBCReader *r = &hbc->reader;
if (r->header.cjsModuleCount > 0 && r->header.version >= 77 && r->cjs_modules) {
for (u32 i = 0; i < r->header.cjsModuleCount; i++) {
u32 sid = r->cjs_modules[i].symbol_id;
const char *s = (sid < r->header.stringCount && r->strings)? r->strings[sid]: NULL;
if (!s || !*s) {
continue;
}
char *pkg = package_from_path (s);
if (pkg) {
Result res = module_add (out, "package", pkg, s, NULL, 0, r->cjs_modules[i].offset, sid, true);
free (pkg);
if (res.code != RESULT_SUCCESS) {
hbc_free_modules (out);
return res;
}
} else {
Result res = module_add (out, "cjs", s, s, NULL, 0, r->cjs_modules[i].offset, sid, false);
if (res.code != RESULT_SUCCESS) {
hbc_free_modules (out);
return res;
}
}
}
}
HBCBindings bindings = { 0 };
Result res = hbc_scan_bindings (hbc, &bindings);
if (res.code != RESULT_SUCCESS) {
hbc_free_modules (out);
return res;
}
for (u32 i = 0; i < bindings.count; i++) {
HBCBinding *b = &bindings.bindings[i];
if (b->type != HBC_BINDING_IMPORT) {
continue;
}
const char *name = b->module? b->module: b->name;
if (!name || !*name || !strcmp (name, "commonjs")) {
name = b->name;
}
res = module_add (out, b->kind? b->kind: "js", name, b->name, NULL, b->function_id, b->offset, b->string_id, true);
if (res.code != RESULT_SUCCESS) {
hbc_free_bindings (&bindings);
hbc_free_modules (out);
return res;
}
}
hbc_free_bindings (&bindings);
return SUCCESS_RESULT ();
}
void hbc_free_modules(HBCModules *modules) {
if (!modules || !modules->modules) {
return;
}
for (u32 i = 0; i < modules->count; i++) {
free (modules->modules[i].name);
free (modules->modules[i].path);
free (modules->modules[i].version);
}
free (modules->modules);
modules->modules = NULL;
modules->count = 0;
}
/* Encoding functions */
Result hbc_enc(
+31
View File
@@ -1,8 +1,38 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#ifndef HBC_INTERNAL_H
#define HBC_INTERNAL_H
#include <hbc/hbc.h>
#include <hbc/parser.h>
#include <hbc/literals.h>
#include <string.h>
/* Grow a dynamic array (doubling) so it has room for one more element.
* arrp points at the array pointer (e.g. &vec->data); *cap is the capacity in
* elements; initial is the capacity to allocate when growing from empty. The
* stored pointer is read/written via memcpy so no aliasing cast is needed. */
static inline Result grow_array(void *arrp, u32 *cap, u32 count, size_t elem_size, u32 initial) {
if (count < *cap) {
return SUCCESS_RESULT ();
}
u32 new_cap = *cap? *cap * 2: initial;
void *cur;
memcpy (&cur, arrp, sizeof (cur));
void *grown = realloc (cur, (size_t)new_cap * elem_size);
if (!grown) {
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "out of memory");
}
memcpy (arrp, &grown, sizeof (grown));
*cap = new_cap;
return SUCCESS_RESULT ();
}
typedef struct {
HBCLiteralEntry *entries;
u32 count;
u32 cap;
} HBCLiteralCache;
struct HBC {
HBCReader reader;
@@ -11,6 +41,7 @@ struct HBC {
u32 string_count;
const char **strings;
u32 function_count;
HBCLiteralCache lit_cache;
};
#endif /* HBC_INTERNAL_H */
+489
View File
@@ -0,0 +1,489 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
/* Public literals API: lazy cache, formatting, scanning. See
* include/hbc/literals.h for the contract. */
#include <hbc/literals.h>
#include <hbc/opcodes.h>
#include <hbc/bytecode.h>
#include <hbc/decompilation/literals.h>
#include "hbc_internal.h"
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
/* Little-endian readers for raw bytecode operands. */
static inline u32 rd_u16le(const u8 *p) {
return (u32)p[0] | ((u32)p[1] << 8);
}
static inline u32 rd_u32le(const u8 *p) {
return (u32)p[0] | ((u32)p[1] << 8) | ((u32)p[2] << 16) | ((u32)p[3] << 24);
}
/* ---------- Global toggles ---------- */
static bool g_inline_literals = false;
void hbc_set_inline_literals(bool on) {
g_inline_literals = on;
}
bool hbc_get_inline_literals(void) {
return g_inline_literals;
}
/* ---------- Pool accessors ---------- */
u32 hbc_get_pool_paddr(HBC *hbc, HBCLiteralKind kind) {
if (!hbc) {
return 0;
}
const HBCReader *r = &hbc->reader;
switch (kind) {
case HBC_LIT_ARRAY:
return r->arrays? r->arrays_paddr: 0;
case HBC_LIT_OBJECT:
return r->object_keys? r->object_keys_paddr: 0;
}
return 0;
}
u32 hbc_get_pool_size(HBC *hbc, HBCLiteralKind kind) {
if (!hbc) {
return 0;
}
const HBCReader *r = &hbc->reader;
switch (kind) {
case HBC_LIT_ARRAY: return r->header.arrayBufferSize;
case HBC_LIT_OBJECT: return r->header.objKeyBufferSize;
}
return 0;
}
u32 hbc_get_object_values_paddr(HBC *hbc) {
return (hbc && hbc->reader.object_values)? hbc->reader.object_values_paddr: 0;
}
u32 hbc_get_object_values_size(HBC *hbc) {
return hbc? hbc->reader.header.objValueBufferSize: 0;
}
/* ---------- Formatting ---------- */
static Result format_raw_impl(HBCReader *r, HBCLiteralKind kind, u32 num_items, u32 primary_id, u32 secondary_id, char **out) {
*out = NULL;
StringBuffer sb;
Result ir = _hbc_string_buffer_init (&sb, 128);
if (ir.code != RESULT_SUCCESS) {
return ir;
}
Result fr;
if (kind == HBC_LIT_ARRAY) {
fr = _hbc_format_array_literal (r, num_items, primary_id, &sb, LITERALS_PRETTY_NEVER, true);
} else {
/* For v97+ primary_id is the shape index; the internal formatter
* expects that convention already (first arg is "key_count" which is
* reinterpreted as shape id when version >= 97). */
fr = _hbc_format_object_literal (r, num_items, num_items, primary_id, secondary_id, &sb, LITERALS_PRETTY_NEVER, true);
}
if (fr.code == RESULT_SUCCESS && sb.data && sb.length > 0) {
*out = strdup (sb.data);
if (!*out) {
fr = ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "oom");
}
}
_hbc_string_buffer_free (&sb);
return fr;
}
Result hbc_literals_format_raw(HBC *hbc, HBCLiteralKind kind, u32 num_items, u32 primary_id, u32 secondary_id, char **out) {
if (!hbc || !out) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "invalid args");
}
return format_raw_impl (&hbc->reader, kind, num_items, primary_id, secondary_id, out);
}
Result hbc_literals_format_for_opcode(HBC *hbc, u8 opcode, u32 arg3, u32 arg4, u32 arg5, char **out) {
if (!hbc || !out) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "invalid args");
}
switch (opcode) {
case OP_NewArrayWithBuffer:
case OP_NewArrayWithBufferLong:
return hbc_literals_format_raw (hbc, HBC_LIT_ARRAY, arg3, arg4, 0, out);
case OP_NewObjectWithBuffer:
case OP_NewObjectWithBufferLong:
return hbc_literals_format_raw (hbc, HBC_LIT_OBJECT, arg3, arg4, arg5, out);
}
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT,
"opcode is not a buffer-literal constructor");
}
/* ---------- Cache ---------- */
static HBCLiteralEntry *cache_find(HBCLiteralCache *c, HBCLiteralKind kind, u32 num_items, u32 primary_id, u32 secondary_id) {
for (u32 i = 0; i < c->count; i++) {
HBCLiteralEntry *e = &c->entries[i];
if (e->kind == kind && e->num_items == num_items && e->primary_id == primary_id && e->secondary_id == secondary_id) {
return e;
}
}
return NULL;
}
static HBCLiteralEntry *cache_alloc(HBCLiteralCache *c) {
if (grow_array (&c->entries, &c->cap, c->count, sizeof (HBCLiteralEntry), 64).code != RESULT_SUCCESS) {
return NULL;
}
HBCLiteralEntry *e = &c->entries[c->count++];
memset (e, 0, sizeof (*e));
return e;
}
/* Compute paddr for an entry based on its kind/primary_id. For v97+ objects,
* primary_id is a shape index — resolve via the shape table. */
static u32 entry_paddr(HBC *hbc, HBCLiteralKind kind, u32 primary_id) {
const HBCReader *r = &hbc->reader;
if (kind == HBC_LIT_ARRAY) {
if (!r->arrays) {
return 0;
}
if (primary_id >= r->header.arrayBufferSize) {
return 0;
}
return r->arrays_paddr + primary_id;
}
/* object */
if (r->header.version >= 97 && r->object_shapes && primary_id < r->object_shape_count) {
u32 key_off = r->object_shapes[primary_id].key_buffer_offset;
if (r->object_keys && key_off < r->header.objKeyBufferSize) {
return r->object_keys_paddr + key_off;
}
return 0;
}
if (!r->object_keys || primary_id >= r->header.objKeyBufferSize) {
return 0;
}
return r->object_keys_paddr + primary_id;
}
static Result cache_get_or_create(HBC *hbc, HBCLiteralKind kind, u32 num_items, u32 primary_id, u32 secondary_id, HBCLiteralEntry **out_entry) {
HBCLiteralEntry *e = cache_find (&hbc->lit_cache, kind, num_items, primary_id, secondary_id);
if (e) {
*out_entry = e;
return SUCCESS_RESULT ();
}
e = cache_alloc (&hbc->lit_cache);
if (!e) {
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "oom");
}
e->kind = kind;
e->num_items = num_items;
e->primary_id = primary_id;
e->secondary_id = secondary_id;
e->paddr = entry_paddr (hbc, kind, primary_id);
/* Format now (lazy across calls, but eager per key once we've decided to
* cache it). The formatted string is small and reused on every access. */
char *txt = NULL;
Result fr = format_raw_impl (&hbc->reader, kind, num_items, primary_id, secondary_id, &txt);
if (fr.code == RESULT_SUCCESS) {
e->formatted = txt;
}
*out_entry = e;
return SUCCESS_RESULT ();
}
Result hbc_literals_get(HBC *hbc, HBCLiteralKind kind, u32 num_items, u32 primary_id, u32 secondary_id, const char **out_formatted) {
if (!hbc || !out_formatted) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "invalid args");
}
HBCLiteralEntry *e;
Result r = cache_get_or_create (hbc, kind, num_items, primary_id, secondary_id, &e);
if (r.code != RESULT_SUCCESS) {
return r;
}
*out_formatted = e->formatted;
return SUCCESS_RESULT ();
}
static void xref_add(HBCLiteralEntry *e, u32 from_addr) {
/* Keep xrefs as a small sorted-by-insertion array. Linear dedup. */
for (u32 i = 0; i < e->xref_count; i++) {
if (e->xref_addrs[i] == from_addr) {
return;
}
}
u32 *na = realloc (e->xref_addrs, (e->xref_count + 1) * sizeof (u32));
if (!na) {
return;
}
e->xref_addrs = na;
e->xref_addrs[e->xref_count++] = from_addr;
}
Result hbc_literals_register(HBC *hbc, HBCLiteralKind kind, u32 num_items, u32 primary_id, u32 secondary_id, u32 from_addr) {
if (!hbc) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "invalid args");
}
HBCLiteralEntry *e;
Result r = cache_get_or_create (hbc, kind, num_items, primary_id, secondary_id, &e);
if (r.code != RESULT_SUCCESS) {
return r;
}
if (from_addr) {
xref_add (e, from_addr);
}
return SUCCESS_RESULT ();
}
Result hbc_literals_list(HBC *hbc, const HBCLiteralEntry **out, u32 *out_count) {
if (!hbc || !out || !out_count) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "invalid args");
}
*out = hbc->lit_cache.entries;
*out_count = hbc->lit_cache.count;
return SUCCESS_RESULT ();
}
u32 hbc_literals_count(HBC *hbc) {
return hbc? hbc->lit_cache.count: 0;
}
void hbc_literals_reset(HBC *hbc) {
if (!hbc) {
return;
}
HBCLiteralCache *c = &hbc->lit_cache;
for (u32 i = 0; i < c->count; i++) {
free (c->entries[i].formatted);
free (c->entries[i].xref_addrs);
}
free (c->entries);
c->entries = NULL;
c->count = 0;
c->cap = 0;
}
/* ---------- Code-side scan ---------- */
static bool is_buffer_op(u8 opcode, HBCLiteralKind *out_kind) {
switch (opcode) {
case OP_NewArrayWithBuffer:
case OP_NewArrayWithBufferLong:
*out_kind = HBC_LIT_ARRAY;
return true;
case OP_NewObjectWithBuffer:
case OP_NewObjectWithBufferLong:
*out_kind = HBC_LIT_OBJECT;
return true;
}
return false;
}
/* Decode opcode immediates into (num_items, primary, secondary), version-aware.
* Returns false if the opcode is not a buffer-literal constructor or if the
* buffer would be read out of bounds. */
static bool decode_buffer_op(u8 op, u32 version, const u8 *code, u32 size, u32 pc, u32 inst_size, HBCLiteralKind *kind, u32 *num_items, u32 *primary, u32 *secondary) {
if (pc + inst_size > size) {
return false;
}
*secondary = 0;
if (op == OP_NewArrayWithBuffer) {
/* reg8 + u16 prealloc + u16 num + u16 id (v<97 AND v97+) */
*kind = HBC_LIT_ARRAY;
*num_items = rd_u16le (code + pc + 4);
*primary = rd_u16le (code + pc + 6);
return true;
}
if (op == OP_NewArrayWithBufferLong) {
/* reg8 + u16 prealloc + u16 num + u32 id */
*kind = HBC_LIT_ARRAY;
*num_items = rd_u16le (code + pc + 4);
*primary = rd_u32le (code + pc + 6);
return true;
}
if (op == OP_NewObjectWithBuffer) {
*kind = HBC_LIT_OBJECT;
if (version >= 97) {
/* reg8 + u16 shape_id + u16 values_id */
*primary = rd_u16le (code + pc + 2);
*secondary = rd_u16le (code + pc + 4);
*num_items = 0; /* resolved via shape table by the formatter */
} else {
/* reg8 + u16 prealloc + u16 num + u16 keys + u16 vals */
*num_items = rd_u16le (code + pc + 4);
*primary = rd_u16le (code + pc + 6);
*secondary = rd_u16le (code + pc + 8);
}
return true;
}
if (op == OP_NewObjectWithBufferLong) {
*kind = HBC_LIT_OBJECT;
if (version >= 97) {
/* reg8 + u32 shape_id + u32 values_id */
*primary = rd_u32le (code + pc + 2);
*secondary = rd_u32le (code + pc + 6);
*num_items = 0;
} else {
/* reg8 + u16 prealloc + u16 num + u32 keys + u32 vals */
*num_items = rd_u16le (code + pc + 4);
*primary = rd_u32le (code + pc + 6);
*secondary = rd_u32le (code + pc + 10);
}
return true;
}
return false;
}
Result hbc_literals_scan_code(HBC *hbc, u32 *out_count) {
if (!hbc) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "invalid args");
}
u32 nfuncs = hbc->reader.header.functionCount;
u32 version = hbc->reader.header.version;
HBCISA isa = hbc_isa_getv (version);
if (!isa.instructions) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "no isa for version");
}
for (u32 fid = 0; fid < nfuncs; fid++) {
const u8 *code = NULL;
u32 size = 0;
if (hbc_get_function_bytecode (hbc, fid, &code, &size).code != RESULT_SUCCESS || !code || size == 0) {
continue;
}
HBCFunc fi;
u32 fn_paddr = 0;
if (hbc_get_function_info (hbc, fid, &fi).code == RESULT_SUCCESS) {
fn_paddr = fi.offset;
}
u32 pc = 0;
while (pc < size) {
u8 op = code[pc];
const Instruction *ins = (op < isa.count)? &isa.instructions[op]: NULL;
if (!ins || !ins->name || ins->binary_size == 0) {
break;
}
HBCLiteralKind kind;
u32 num_items = 0, primary = 0, secondary = 0;
if (decode_buffer_op (op, version, code, size, pc, ins->binary_size, &kind, &num_items, &primary, &secondary)) {
u32 call_addr = fn_paddr + pc;
hbc_literals_register (hbc, kind, num_items, primary, secondary, call_addr);
}
(void)is_buffer_op; /* retained for tools that need the predicate */
pc += ins->binary_size;
}
}
if (out_count) {
*out_count = hbc->lit_cache.count;
}
return SUCCESS_RESULT ();
}
/* ---------- Pool-side scan ---------- */
static bool slp_skip_group(const u8 *base, size_t size, size_t *pos, u8 *out_tag, u32 *out_length) {
if (*pos >= size) {
return false;
}
u8 taglen = base[(*pos)++];
u8 tag = (taglen >> 4) & 0x7;
u32 length = taglen & 0x0F;
if (taglen & 0x80) {
if (*pos >= size) {
return false;
}
length = ((u32) (taglen & 0x0F) << 8) | base[(*pos)++];
}
size_t per_value = 0;
switch (tag) {
case 0:
case 1:
case 2: per_value = 0; break; /* null/true/false */
case 3: per_value = 8; break; /* number */
case 4:
case 7: per_value = 4; break; /* longstr/int */
case 5: per_value = 2; break; /* shortstr */
case 6: per_value = 1; break; /* bytestr */
default: return false;
}
if (*pos + per_value * length > size) {
return false;
}
*pos += per_value * length;
if (out_tag) {
*out_tag = tag;
}
if (out_length) {
*out_length = length;
}
return true;
}
Result hbc_literals_scan_pool(HBC *hbc, HBCLiteralKind kind, HBCPoolGroup **out, u32 *out_count) {
if (!hbc || !out || !out_count) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "invalid args");
}
*out = NULL;
*out_count = 0;
/* Object-keys on v97+: enumerate shape table instead of the raw pool. */
if (kind == HBC_LIT_OBJECT && hbc->reader.header.version >= 97 && hbc->reader.object_shapes && hbc->reader.object_shape_count > 0) {
size_t n = hbc->reader.object_shape_count;
HBCPoolGroup *arr = calloc (n, sizeof (HBCPoolGroup));
if (!arr) {
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "oom");
}
for (size_t i = 0; i < n; i++) {
arr[i].pool_offset = hbc->reader.object_shapes[i].key_buffer_offset;
arr[i].num_items = hbc->reader.object_shapes[i].prop_count;
arr[i].paddr = hbc->reader.object_keys_paddr + arr[i].pool_offset;
arr[i].tag = 0xff; /* N/A for a shape row */
}
*out = arr;
*out_count = (u32)n;
return SUCCESS_RESULT ();
}
const u8 *base;
u32 size;
u32 paddr;
if (kind == HBC_LIT_ARRAY) {
base = hbc->reader.arrays;
size = hbc->reader.header.arrayBufferSize;
paddr = hbc->reader.arrays_paddr;
} else {
base = hbc->reader.object_keys;
size = hbc->reader.header.objKeyBufferSize;
paddr = hbc->reader.object_keys_paddr;
}
if (!base || !size) {
return SUCCESS_RESULT ();
}
u32 cap = 64;
HBCPoolGroup *arr = calloc (cap, sizeof (HBCPoolGroup));
if (!arr) {
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "oom");
}
u32 n = 0;
size_t pos = 0;
while (pos < size) {
size_t group_start = pos;
u8 tag = 0;
u32 length = 0;
if (!slp_skip_group (base, size, &pos, &tag, &length)) {
break;
}
if (grow_array (&arr, &cap, n, sizeof (HBCPoolGroup), 64).code != RESULT_SUCCESS) {
break;
}
arr[n].pool_offset = (u32)group_start;
arr[n].paddr = paddr + (u32)group_start;
arr[n].num_items = length;
arr[n].tag = tag;
n++;
}
*out = arr;
*out_count = n;
return SUCCESS_RESULT ();
}
+7 -100
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/disasm.h>
#include <stdio.h>
#include <stdlib.h>
@@ -135,15 +137,7 @@ Result _hbc_print_function_header(Disassembler *disassembler, FunctionHeader *fu
static Result format_operand_asm(Disassembler *d, ParsedInstruction *ins, int idx, StringBuffer *out) {
OperandType t = ins->inst->operands[idx].operand_type;
OperandMeaning m = ins->inst->operands[idx].operand_meaning;
u32 v = 0;
switch (idx) {
case 0: v = ins->arg1; break;
case 1: v = ins->arg2; break;
case 2: v = ins->arg3; break;
case 3: v = ins->arg4; break;
case 4: v = ins->arg5; break;
case 5: v = ins->arg6; break;
}
u32 v = hbc_operand_value (ins, idx);
HBCReader *r = d->reader;
FunctionHeader *fh = &r->function_headers[d->current_function_id];
@@ -263,82 +257,13 @@ Result _hbc_print_instruction(Disassembler *disassembler, ParsedInstruction *ins
}
first = false;
/* Get operand name */
const char *operand_name = "Unknown";
switch (instruction->inst->operands[i].operand_meaning) {
case OPERAND_MEANING_NONE:
switch (operand_type) {
case OPERAND_TYPE_REG8:
operand_name = "Reg8";
break;
case OPERAND_TYPE_REG32:
operand_name = "Reg32";
break;
case OPERAND_TYPE_UINT8:
operand_name = "UInt8";
break;
case OPERAND_TYPE_UINT16:
operand_name = "UInt16";
break;
case OPERAND_TYPE_UINT32:
operand_name = "UInt32";
break;
case OPERAND_TYPE_ADDR8:
operand_name = "Addr8";
break;
case OPERAND_TYPE_ADDR32:
operand_name = "Addr32";
break;
case OPERAND_TYPE_IMM32:
operand_name = "Imm32";
break;
case OPERAND_TYPE_DOUBLE:
operand_name = "Double";
break;
break;
default:
operand_name = "Unknown";
break;
}
break;
case OPERAND_MEANING_STRING_ID:
operand_name = "string_id";
break;
case OPERAND_MEANING_BIGINT_ID:
operand_name = "bigint_id";
break;
case OPERAND_MEANING_FUNCTION_ID:
operand_name = "function_id";
break;
case OPERAND_MEANING_BUILTIN_ID:
operand_name = "builtin_id";
break;
case OPERAND_MEANING_ARRAY_ID:
operand_name = "array_id";
break;
case OPERAND_MEANING_OBJ_KEY_ID:
operand_name = "obj_key_id";
break;
case OPERAND_MEANING_OBJ_VAL_ID:
operand_name = "obj_val_id";
break;
}
/* Print operand name and value */
const char *operand_name = hbc_operand_name (&instruction->inst->operands[i]);
RETURN_IF_ERROR (_hbc_string_buffer_append (output, operand_name));
RETURN_IF_ERROR (_hbc_string_buffer_append (output, ": "));
/* Get operand value */
u32 value;
switch (i) {
case 0: value = instruction->arg1; break;
case 1: value = instruction->arg2; break;
case 2: value = instruction->arg3; break;
case 3: value = instruction->arg4; break;
case 4: value = instruction->arg5; break;
case 5: value = instruction->arg6; break;
default: value = 0;
}
u32 value = hbc_operand_value (instruction, i);
/* Print operand value */
if (instruction->inst->operands[i].operand_type != OPERAND_TYPE_DOUBLE) {
@@ -362,16 +287,7 @@ Result _hbc_print_instruction(Disassembler *disassembler, ParsedInstruction *ins
continue;
}
u32 value;
switch (i) {
case 0: value = instruction->arg1; break;
case 1: value = instruction->arg2; break;
case 2: value = instruction->arg3; break;
case 3: value = instruction->arg4; break;
case 4: value = instruction->arg5; break;
case 5: value = instruction->arg6; break;
default: value = 0;
}
u32 value = hbc_operand_value (instruction, i);
switch (operand_meaning) {
case OPERAND_MEANING_STRING_ID:
@@ -437,16 +353,7 @@ Result _hbc_print_instruction(Disassembler *disassembler, ParsedInstruction *ins
continue;
}
u32 value;
switch (i) {
case 0: value = instruction->arg1; break;
case 1: value = instruction->arg2; break;
case 2: value = instruction->arg3; break;
case 3: value = instruction->arg4; break;
case 4: value = instruction->arg5; break;
case 5: value = instruction->arg6; break;
default: value = 0;
}
u32 value = hbc_operand_value (instruction, i);
RETURN_IF_ERROR (_hbc_string_buffer_append (output, " # Address: "));
+102 -98
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/hermes_encoder.h>
#include <hbc/opcodes.h>
#include <hbc/bytecode.h>
@@ -131,7 +133,7 @@ Result hbc_encoder_parse_instruction(HBCEncoder *encoder, const char *asm_line,
}
/* Parse operands separated by commas */
while (*line && operand_count < 6) {
while (*line) {
/* Skip whitespace */
while (*line && isspace (*line)) {
line++;
@@ -177,104 +179,106 @@ Result hbc_encoder_parse_instruction(HBCEncoder *encoder, const char *asm_line,
}
*trim_end = '\0';
/* Parse operand based on expected type */
if (operand_count < 6 && inst->operands[operand_count].operand_type != OPERAND_TYPE_NONE) {
OperandType expected_type = inst->operands[operand_count].operand_type;
char *endptr;
bool parse_success = false;
switch (expected_type) {
case OPERAND_TYPE_REG8:
case OPERAND_TYPE_REG32:
/* Register: rN or RN */
if (trim_start[0] == 'r' || trim_start[0] == 'R') {
operand_values[operand_count] = (u32)strtoul (trim_start + 1, &endptr, 10);
if (*endptr == '\0') {
parse_success = true;
/* Validate register range */
if (expected_type == OPERAND_TYPE_REG8 && operand_values[operand_count] > 255) {
return ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, "Register number out of range for REG8");
}
}
} else {
/* Allow bare numbers for registers too */
operand_values[operand_count] = (u32)strtoul (trim_start, &endptr, 10);
if (*endptr == '\0') {
parse_success = true;
/* Validate register range */
if (expected_type == OPERAND_TYPE_REG8 && operand_values[operand_count] > 255) {
return ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, "Register number out of range for REG8");
}
}
}
break;
case OPERAND_TYPE_UINT8:
case OPERAND_TYPE_UINT16:
case OPERAND_TYPE_UINT32:
case OPERAND_TYPE_IMM32:
case OPERAND_TYPE_ADDR8:
case OPERAND_TYPE_ADDR32:
/* Immediate or address */
if (trim_start[0] == '0' && (trim_start[1] == 'x' || trim_start[1] == 'X')) {
/* Hex */
operand_values[operand_count] = (u32)strtoul (trim_start, &endptr, 16);
if (*endptr == '\0') {
parse_success = true;
}
} else {
/* Decimal */
operand_values[operand_count] = (u32)strtoul (trim_start, &endptr, 10);
if (*endptr == '\0') {
parse_success = true;
}
}
break;
case OPERAND_TYPE_DOUBLE:
/* Parse as double literal */
{
char *endptr_double;
double dval = strtod (trim_start, &endptr_double);
if (*endptr_double == '\0') {
/* Convert double to IEEE 754 bits */
u64 bits;
memcpy (&bits, &dval, sizeof (double));
operand_values[operand_count] = bits;
parse_success = true;
} else if (trim_start[0] == '0' && (trim_start[1] == 'x' || trim_start[1] == 'X')) {
/* Allow hex for bit patterns */
operand_values[operand_count] = (u64)strtoull (trim_start, &endptr_double, 16);
if (*endptr_double == '\0') {
parse_success = true;
}
}
}
break;
default:
/* Unknown type, try to parse as number */
if (trim_start[0] == '0' && (trim_start[1] == 'x' || trim_start[1] == 'X')) {
operand_values[operand_count] = (u32)strtoul (trim_start, &endptr, 16);
if (*endptr == '\0') {
parse_success = true;
}
} else {
operand_values[operand_count] = (u32)strtoul (trim_start, &endptr, 10);
if (*endptr == '\0') {
parse_success = true;
}
}
break;
}
if (!parse_success) {
return ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, "Invalid operand format");
}
operand_count++;
if (operand_count >= 6 || inst->operands[operand_count].operand_type == OPERAND_TYPE_NONE) {
return ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, "Incorrect number of operands");
}
/* Parse operand based on expected type */
OperandType expected_type = inst->operands[operand_count].operand_type;
char *endptr;
bool parse_success = false;
switch (expected_type) {
case OPERAND_TYPE_REG8:
case OPERAND_TYPE_REG32:
/* Register: rN or RN */
if (trim_start[0] == 'r' || trim_start[0] == 'R') {
operand_values[operand_count] = (u32)strtoul (trim_start + 1, &endptr, 10);
if (*endptr == '\0') {
parse_success = true;
/* Validate register range */
if (expected_type == OPERAND_TYPE_REG8 && operand_values[operand_count] > 255) {
return ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, "Register number out of range for REG8");
}
}
} else {
/* Allow bare numbers for registers too */
operand_values[operand_count] = (u32)strtoul (trim_start, &endptr, 10);
if (*endptr == '\0') {
parse_success = true;
/* Validate register range */
if (expected_type == OPERAND_TYPE_REG8 && operand_values[operand_count] > 255) {
return ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, "Register number out of range for REG8");
}
}
}
break;
case OPERAND_TYPE_UINT8:
case OPERAND_TYPE_UINT16:
case OPERAND_TYPE_UINT32:
case OPERAND_TYPE_IMM32:
case OPERAND_TYPE_ADDR8:
case OPERAND_TYPE_ADDR32:
/* Immediate or address */
if (trim_start[0] == '0' && (trim_start[1] == 'x' || trim_start[1] == 'X')) {
/* Hex */
operand_values[operand_count] = (u32)strtoul (trim_start, &endptr, 16);
if (*endptr == '\0') {
parse_success = true;
}
} else {
/* Decimal */
operand_values[operand_count] = (u32)strtoul (trim_start, &endptr, 10);
if (*endptr == '\0') {
parse_success = true;
}
}
break;
case OPERAND_TYPE_DOUBLE:
/* Parse as double literal */
{
char *endptr_double;
double dval = strtod (trim_start, &endptr_double);
if (*endptr_double == '\0') {
/* Convert double to IEEE 754 bits */
u64 bits;
memcpy (&bits, &dval, sizeof (double));
operand_values[operand_count] = bits;
parse_success = true;
} else if (trim_start[0] == '0' && (trim_start[1] == 'x' || trim_start[1] == 'X')) {
/* Allow hex for bit patterns */
operand_values[operand_count] = (u64)strtoull (trim_start, &endptr_double, 16);
if (*endptr_double == '\0') {
parse_success = true;
}
}
}
break;
default:
/* Unknown type, try to parse as number */
if (trim_start[0] == '0' && (trim_start[1] == 'x' || trim_start[1] == 'X')) {
operand_values[operand_count] = (u32)strtoul (trim_start, &endptr, 16);
if (*endptr == '\0') {
parse_success = true;
}
} else {
operand_values[operand_count] = (u32)strtoul (trim_start, &endptr, 10);
if (*endptr == '\0') {
parse_success = true;
}
}
break;
}
if (!parse_success) {
return ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, "Invalid operand format");
}
operand_count++;
/* Move to next operand */
line = operand_end;
if (*line == ',') {
@@ -338,7 +342,7 @@ Result hbc_encoder_encode_instruction(HBCEncoder *encoder, const HBCEncodedInstr
/* Write operands based on their types */
for (int i = 0; i < 6 && inst->operands[i].operand_type != OPERAND_TYPE_NONE; i++) {
OperandType type = inst->operands[i].operand_type;
u32 value = operand_values[i];
u64 value = operand_values[i];
switch (type) {
case OPERAND_TYPE_REG8:
+31
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/opcodes.h>
#include <string.h>
@@ -99,6 +101,35 @@ static const IsaVersion k_isa_versions[] = {
{ 99, &k_isa_v99 },
};
const char *hbc_operand_name(const InstructionOperand *operand) {
if (!operand) {
return "Unknown";
}
switch (operand->operand_meaning) {
case OPERAND_MEANING_STRING_ID: return "string_id";
case OPERAND_MEANING_BIGINT_ID: return "bigint_id";
case OPERAND_MEANING_FUNCTION_ID: return "function_id";
case OPERAND_MEANING_BUILTIN_ID: return "builtin_id";
case OPERAND_MEANING_ARRAY_ID: return "array_id";
case OPERAND_MEANING_OBJ_KEY_ID: return "obj_key_id";
case OPERAND_MEANING_OBJ_VAL_ID: return "obj_val_id";
case OPERAND_MEANING_NONE: break;
}
switch (operand->operand_type) {
case OPERAND_TYPE_REG8: return "Reg8";
case OPERAND_TYPE_REG32: return "Reg32";
case OPERAND_TYPE_UINT8: return "UInt8";
case OPERAND_TYPE_UINT16: return "UInt16";
case OPERAND_TYPE_UINT32: return "UInt32";
case OPERAND_TYPE_ADDR8: return "Addr8";
case OPERAND_TYPE_ADDR32: return "Addr32";
case OPERAND_TYPE_IMM32: return "Imm32";
case OPERAND_TYPE_DOUBLE: return "Double";
case OPERAND_TYPE_NONE: break;
}
return "Unknown";
}
u8 hbc_canonical_opcode_from_name(const char *name) {
if (!name) {
return HBC_CANONICAL_OPCODE_UNKNOWN;
+2
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/opcodes.h>
static const Instruction k_instructions_v51[167] = {
+2
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/opcodes.h>
static const Instruction k_instructions_v58[176] = {
+2
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/opcodes.h>
static const Instruction k_instructions_v59[177] = {
+2
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/opcodes.h>
static const Instruction k_instructions_v61[177] = {
+2
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/opcodes.h>
static const Instruction k_instructions_v62[177] = {
+2
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/opcodes.h>
static const Instruction k_instructions_v68[177] = {
+2
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/opcodes.h>
static const Instruction k_instructions_v69[177] = {
+2
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/opcodes.h>
static const Instruction k_instructions_v70[177] = {
+2
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/opcodes.h>
static const Instruction k_instructions_v72[177] = {
+2
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/opcodes.h>
static const Instruction k_instructions_v73[180] = {
+2
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/opcodes.h>
static const Instruction k_instructions_v76[256] = {
+2
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/opcodes.h>
static const Instruction k_instructions_v80[181] = {
+2
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/opcodes.h>
static const Instruction k_instructions_v81[183] = {
+2
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/opcodes.h>
static const Instruction k_instructions_v82[185] = {
+2
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/opcodes.h>
static const Instruction k_instructions_v83[185] = {
+2
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/opcodes.h>
static const Instruction k_instructions_v84[256] = {
+2
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/opcodes.h>
static const Instruction k_instructions_v85[201] = {
+2
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/opcodes.h>
static const Instruction k_instructions_v86[201] = {
+2
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/opcodes.h>
static const Instruction k_instructions_v87[204] = {
+2
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/opcodes.h>
static const Instruction k_instructions_v89[256] = {
+2
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/opcodes.h>
static const Instruction k_instructions_v90[256] = {
+2
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/opcodes.h>
static const Instruction k_instructions_v92[206] = {
+2
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/opcodes.h>
static const Instruction k_instructions_v93[256] = {
+2
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/opcodes.h>
static const Instruction k_instructions_v94[256] = {
+2
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/opcodes.h>
static const Instruction k_instructions_v95[256] = {
+2
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/opcodes.h>
static const Instruction k_instructions_v96[256] = {
+2
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/opcodes.h>
static const Instruction k_instructions_v97[200] = {
+2
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/opcodes.h>
static const Instruction k_instructions_v99[223] = {
+54 -106
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/bytecode.h>
#include <hbc/opcodes.h>
@@ -116,16 +118,10 @@ Result _hbc_parse_function_bytecode(HBCReader *reader, u32 function_id, ParsedIn
/* Initialize instruction list */
RETURN_IF_ERROR (_hbc_parsed_instruction_list_init (out_instructions, 32)); /* Start with space for 32 instructions */
/* Create a fresh bytecode buffer similar to the Python BytesIO approach */
BufferReader bytecode_buffer;
Result result = _hbc_buffer_reader_init_from_memory (&bytecode_buffer,
function_header->bytecode,
function_header->bytecodeSizeInBytes);
if (result.code != RESULT_SUCCESS) {
_hbc_parsed_instruction_list_free (out_instructions);
return result;
}
/* Read directly from the function bytecode: the parser never writes and
* the header-owned buffer outlives the parse, so no owning copy is needed */
BufferReader bytecode_buffer = { function_header->bytecode, function_header->bytecodeSizeInBytes, 0 };
Result result;
/* Parse instructions in a loop similar to Python's implementation */
while (bytecode_buffer.position < bytecode_buffer.size) {
@@ -174,6 +170,7 @@ Result _hbc_parse_function_bytecode(HBCReader *reader, u32 function_id, ParsedIn
instruction.original_pos = original_pos;
instruction.next_pos = original_pos + (u32) (remaining > UINT32_MAX? UINT32_MAX: remaining);
instruction.function_offset = function_header->offset;
instruction.frame_size = function_header->frameSize;
instruction.hbc_reader = reader;
instruction.switch_jump_table = NULL;
instruction.switch_jump_table_size = 0;
@@ -192,6 +189,7 @@ Result _hbc_parse_function_bytecode(HBCReader *reader, u32 function_id, ParsedIn
instruction.opcode = hbc_canonical_opcode_from_name (inst->name);
instruction.original_pos = original_pos;
instruction.function_offset = function_header->offset;
instruction.frame_size = function_header->frameSize;
instruction.hbc_reader = reader;
/* Set the expected next position based on instruction size */
@@ -368,7 +366,13 @@ Result _hbc_parse_function_bytecode(HBCReader *reader, u32 function_id, ParsedIn
candidates[1] = function_header->offset + base; /* function-relative */
candidates[2] = base; /* absolute */
size_t func_start = function_header->offset;
size_t func_end = func_start + function_header->bytecodeSizeInBytes;
size_t table_limit = fsz;
if (function_id + 1 < reader->header.functionCount) {
size_t next_func = reader->function_headers[function_id + 1].offset;
if (next_func > func_start && next_func < table_limit) {
table_limit = next_func;
}
}
/* Save file position */
size_t saved_pos = reader->file_buffer.position;
@@ -398,8 +402,8 @@ Result _hbc_parse_function_bytecode(HBCReader *reader, u32 function_id, ParsedIn
if (table_end > fsz) {
continue;
}
/* Also require the table to lie within the function's bytecode region */
if (! (reader->file_buffer.position >= func_start && table_end <= func_end)) {
/* Jump tables live after the bytecode body, before the next function. */
if (! (reader->file_buffer.position >= func_start && table_end <= table_limit)) {
continue;
}
@@ -446,6 +450,38 @@ Result _hbc_parse_function_bytecode(HBCReader *reader, u32 function_id, ParsedIn
return SUCCESS_RESULT ();
}
u32 hbc_operand_value(const ParsedInstruction *insn, int i) {
if (!insn) {
return 0;
}
switch (i) {
case 0: return insn->arg1;
case 1: return insn->arg2;
case 2: return insn->arg3;
case 3: return insn->arg4;
case 4: return insn->arg5;
case 5: return insn->arg6;
default: return 0;
}
}
bool _hbc_operand_is_addr(const Instruction *inst, int idx) {
if (!inst || idx < 0 || idx >= 6) {
return false;
}
OperandType t = inst->operands[idx].operand_type;
return t == OPERAND_TYPE_ADDR8 || t == OPERAND_TYPE_ADDR32;
}
u32 _hbc_compute_target_address(const ParsedInstruction *insn, int op_index) {
if (!insn) {
return 0;
}
/* Hermes jump and address offsets are relative to the start of the
* instruction (original_pos), matching the disassembler and the VM. */
return insn->original_pos + hbc_operand_value (insn, op_index);
}
/* Convert instruction to string */
Result _hbc_instruction_to_string(ParsedInstruction *instruction, StringBuffer *out_string) {
if (!instruction || !out_string || !instruction->inst) {
@@ -476,80 +512,10 @@ Result _hbc_instruction_to_string(ParsedInstruction *instruction, StringBuffer *
first = false;
/* Get operand value */
u32 value;
switch (i) {
case 0: value = instruction->arg1; break;
case 1: value = instruction->arg2; break;
case 2: value = instruction->arg3; break;
case 3: value = instruction->arg4; break;
case 4: value = instruction->arg5; break;
case 5: value = instruction->arg6; break;
default: value = 0;
}
u32 value = hbc_operand_value (instruction, i);
/* Get operand name */
const char *operand_name;
if (instruction->inst->operands[i].operand_meaning != OPERAND_MEANING_NONE) {
switch (instruction->inst->operands[i].operand_meaning) {
case OPERAND_MEANING_STRING_ID:
operand_name = "string_id";
break;
case OPERAND_MEANING_BIGINT_ID:
operand_name = "bigint_id";
break;
case OPERAND_MEANING_FUNCTION_ID:
operand_name = "function_id";
break;
case OPERAND_MEANING_BUILTIN_ID:
operand_name = "builtin_id";
break;
case OPERAND_MEANING_ARRAY_ID:
operand_name = "array_id";
break;
case OPERAND_MEANING_OBJ_KEY_ID:
operand_name = "obj_key_id";
break;
case OPERAND_MEANING_OBJ_VAL_ID:
operand_name = "obj_val_id";
break;
default:
operand_name = "unknown";
break;
}
} else {
switch (instruction->inst->operands[i].operand_type) {
case OPERAND_TYPE_REG8:
operand_name = "Reg8";
break;
case OPERAND_TYPE_REG32:
operand_name = "Reg32";
break;
case OPERAND_TYPE_UINT8:
operand_name = "UInt8";
break;
case OPERAND_TYPE_UINT16:
operand_name = "UInt16";
break;
case OPERAND_TYPE_UINT32:
operand_name = "UInt32";
break;
case OPERAND_TYPE_ADDR8:
operand_name = "Addr8";
break;
case OPERAND_TYPE_ADDR32:
operand_name = "Addr32";
break;
case OPERAND_TYPE_IMM32:
operand_name = "Imm32";
break;
case OPERAND_TYPE_DOUBLE:
operand_name = "Double";
break;
default:
operand_name = "Unknown";
break;
}
}
const char *operand_name = hbc_operand_name (&instruction->inst->operands[i]);
/* Print operand name and value */
RETURN_IF_ERROR (_hbc_string_buffer_append (out_string, operand_name));
@@ -573,16 +539,7 @@ Result _hbc_instruction_to_string(ParsedInstruction *instruction, StringBuffer *
continue;
}
u32 value;
switch (i) {
case 0: value = instruction->arg1; break;
case 1: value = instruction->arg2; break;
case 2: value = instruction->arg3; break;
case 3: value = instruction->arg4; break;
case 4: value = instruction->arg5; break;
case 5: value = instruction->arg6; break;
default: value = 0;
}
u32 value = hbc_operand_value (instruction, i);
switch (operand_meaning) {
case OPERAND_MEANING_STRING_ID:
@@ -638,16 +595,7 @@ Result _hbc_instruction_to_string(ParsedInstruction *instruction, StringBuffer *
continue;
}
u32 value;
switch (i) {
case 0: value = instruction->arg1; break;
case 1: value = instruction->arg2; break;
case 2: value = instruction->arg3; break;
case 3: value = instruction->arg4; break;
case 4: value = instruction->arg5; break;
case 5: value = instruction->arg6; break;
default: value = 0;
}
u32 value = hbc_operand_value (instruction, i);
/* For addresses, calculate absolute target address */
u32 absolute_address = instruction->function_offset + instruction->original_pos + value;
@@ -658,7 +606,7 @@ Result _hbc_instruction_to_string(ParsedInstruction *instruction, StringBuffer *
}
/* Add jump table for switch instructions */
if (strcmp (instruction->inst->name, "SwitchImm") == 0 &&
if (instruction->opcode == OP_SwitchImm &&
instruction->switch_jump_table && instruction->switch_jump_table_size > 0) {
RETURN_IF_ERROR (_hbc_string_buffer_append (out_string, " # Jump table: ["));
+132 -116
View File
@@ -1,8 +1,20 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/parser.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* Unpack the function-header flags byte into the header struct. */
static void unpack_function_flags(FunctionHeader *h, u8 flags) {
h->prohibitInvoke = flags & 0x3u;
h->strictMode = (flags >> 2) & 0x1u;
h->hasExceptionHandler = (flags >> 3) & 0x1u;
h->hasDebugInfo = (flags >> 4) & 0x1u;
h->overflowed = (flags >> 5) & 0x1u;
h->unused = (flags >> 6) & 0x3u;
}
/* Initialize HBC reader */
Result _hbc_reader_init(HBCReader *reader) {
R_RETURN_VAL_IF_FAIL (reader,
@@ -81,6 +93,12 @@ Result _hbc_reader_cleanup(HBCReader *reader) {
/* Clean up debug info */
free (reader->debug_string_table);
free (reader->debug_string_storage);
if (reader->debug_filenames) {
for (u32 i = 0; i < reader->debug_info_header.filename_count; i++) {
free (reader->debug_filenames[i]);
}
free (reader->debug_filenames);
}
free (reader->debug_file_regions);
free (reader->sources_data_storage);
free (reader->scope_desc_data_storage);
@@ -136,8 +154,10 @@ Result _hbc_reader_read_file(HBCReader *reader, const char *filename) {
}
}
#if 0
fprintf (stderr, "Warning: File does not start with Hermes bytecode magic number (found 0x%016llx, expected 0x%016llx).\n", (unsigned long long)magic, (unsigned long long)HEADER_MAGIC);
fprintf (stderr, "This file may not be a Hermes bytecode file or might be corrupted.\n");
#endif
}
return SUCCESS_RESULT ();
@@ -167,6 +187,45 @@ static Result align_over_padding(BufferReader *reader, size_t padding_amount) {
return SUCCESS_RESULT ();
}
static Result read_exception_handlers(HBCReader *reader, u32 function_id, u32 info_offset) {
RETURN_IF_ERROR (_hbc_buffer_reader_seek (&reader->file_buffer, info_offset));
RETURN_IF_ERROR (align_over_padding (&reader->file_buffer, 4));
u32 count;
RETURN_IF_ERROR (_hbc_buffer_reader_read_u32 (&reader->file_buffer, &count));
size_t remaining = reader->file_buffer.size - reader->file_buffer.position;
if (count > remaining / (3 * sizeof (u32))) {
return ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, "Exception handler table exceeds file size");
}
ExceptionHandlerInfo *handlers = NULL;
if (count) {
handlers = (ExceptionHandlerInfo *)malloc (count * sizeof (ExceptionHandlerInfo));
if (!handlers) {
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "Failed to allocate exception handlers");
}
}
for (u32 j = 0; j < count; j++) {
Result res = _hbc_buffer_reader_read_u32 (&reader->file_buffer, &handlers[j].start);
if (res.code == RESULT_SUCCESS) {
res = _hbc_buffer_reader_read_u32 (&reader->file_buffer, &handlers[j].end);
}
if (res.code == RESULT_SUCCESS) {
res = _hbc_buffer_reader_read_u32 (&reader->file_buffer, &handlers[j].target);
}
if (res.code != RESULT_SUCCESS) {
free (handlers);
return res;
}
}
ExceptionHandlerList *list = &reader->function_id_to_exc_handlers[function_id];
free (list->handlers);
list->handlers = handlers;
list->count = count;
return SUCCESS_RESULT ();
}
/* Read and validate the header */
Result _hbc_reader_read_header(HBCReader *reader) {
if (!reader) {
@@ -346,15 +405,7 @@ static Result hbc_reader_read_functions_modern(HBCReader *reader) {
header->highestReadCacheIndex = (raw3 >> 8) & 0xFFu;
header->highestWriteCacheIndex = (reader->header.version >= 99)? ((raw3 >> 16) & 0x7Fu): ((raw3 >> 16) & 0xFFu);
header->environmentSize = (reader->header.version >= 99)? ((raw3 >> 23) & 0x1u): 0;
{
u8 flags = (raw3 >> 24) & 0xFFu;
header->prohibitInvoke = flags & 0x3u;
header->strictMode = (flags >> 2) & 0x1u;
header->hasExceptionHandler = (flags >> 3) & 0x1u;
header->hasDebugInfo = (flags >> 4) & 0x1u;
header->overflowed = (flags >> 5) & 0x1u;
header->unused = (flags >> 6) & 0x3u;
}
unpack_function_flags (header, (raw3 >> 24) & 0xFFu);
header->infoOffset = 0;
if (header->overflowed) {
@@ -376,12 +427,7 @@ static Result hbc_reader_read_functions_modern(HBCReader *reader) {
{
u8 flags = 0;
RETURN_IF_ERROR (_hbc_buffer_reader_read_u8 (&reader->file_buffer, &flags));
header->prohibitInvoke = flags & 0x3u;
header->strictMode = (flags >> 2) & 0x1u;
header->hasExceptionHandler = (flags >> 3) & 0x1u;
header->hasDebugInfo = (flags >> 4) & 0x1u;
header->overflowed = (flags >> 5) & 0x1u;
header->unused = (flags >> 6) & 0x3u;
unpack_function_flags (header, flags);
}
} else {
RETURN_IF_ERROR (_hbc_buffer_reader_read_u32 (&reader->file_buffer, &header->bytecodeSizeInBytes));
@@ -392,12 +438,7 @@ static Result hbc_reader_read_functions_modern(HBCReader *reader) {
{
u8 flags = 0;
RETURN_IF_ERROR (_hbc_buffer_reader_read_u8 (&reader->file_buffer, &flags));
header->prohibitInvoke = flags & 0x3u;
header->strictMode = (flags >> 2) & 0x1u;
header->hasExceptionHandler = (flags >> 3) & 0x1u;
header->hasDebugInfo = (flags >> 4) & 0x1u;
header->overflowed = (flags >> 5) & 0x1u;
header->unused = (flags >> 6) & 0x3u;
unpack_function_flags (header, flags);
}
}
RETURN_IF_ERROR (_hbc_buffer_reader_seek (&reader->file_buffer, header_pos + 12));
@@ -569,13 +610,6 @@ Result _hbc_reader_read_functions(HBCReader *reader) {
small_header.highestReadCacheIndex = (u8) ((raw_data[3] >> 8) & 0xFF);
small_header.highestWriteCacheIndex = (u8) ((raw_data[3] >> 16) & 0xFF);
small_header.prohibitInvoke = (raw_data[3] >> 24) & 0x3;
small_header.strictMode = (raw_data[3] >> 26) & 0x1;
small_header.hasExceptionHandler = (raw_data[3] >> 27) & 0x1;
small_header.hasDebugInfo = (raw_data[3] >> 28) & 0x1;
small_header.overflowed = (raw_data[3] >> 29) & 0x1;
small_header.unused = (raw_data[3] >> 30) & 0x3;
/* Store the function header */
FunctionHeader *header = &reader->function_headers[i];
header->offset = small_header.offset;
@@ -587,18 +621,13 @@ Result _hbc_reader_read_functions(HBCReader *reader) {
header->environmentSize = small_header.environmentSize;
header->highestReadCacheIndex = small_header.highestReadCacheIndex;
header->highestWriteCacheIndex = small_header.highestWriteCacheIndex;
header->prohibitInvoke = small_header.prohibitInvoke;
header->strictMode = small_header.strictMode;
header->hasExceptionHandler = small_header.hasExceptionHandler;
header->hasDebugInfo = small_header.hasDebugInfo;
header->overflowed = small_header.overflowed;
header->unused = small_header.unused;
unpack_function_flags (header, (u8) (raw_data[3] >> 24));
/* Save current position */
size_t current_pos = reader->file_buffer.position;
/* Handle overflowed header */
if (small_header.overflowed) {
if (header->overflowed) {
/* Calculate the absolute offset of the large header */
u32 large_header_offset = (small_header.infoOffset << 16) | small_header.offset;
@@ -625,13 +654,6 @@ Result _hbc_reader_read_functions(HBCReader *reader) {
u8 flags;
RETURN_IF_ERROR (_hbc_buffer_reader_read_u8 (&reader->file_buffer, &flags));
large_header.prohibitInvoke = flags & 0x3;
large_header.strictMode = (flags >> 2) & 0x1;
large_header.hasExceptionHandler = (flags >> 3) & 0x1;
large_header.hasDebugInfo = (flags >> 4) & 0x1;
large_header.overflowed = (flags >> 5) & 0x1;
large_header.unused = (flags >> 6) & 0x3;
/* Copy to the combined header */
header->offset = large_header.offset;
header->paramCount = large_header.paramCount;
@@ -642,12 +664,7 @@ Result _hbc_reader_read_functions(HBCReader *reader) {
header->environmentSize = (u8)large_header.environmentSize; /* Cast from u32 to u8 */
header->highestReadCacheIndex = large_header.highestReadCacheIndex;
header->highestWriteCacheIndex = large_header.highestWriteCacheIndex;
header->prohibitInvoke = large_header.prohibitInvoke;
header->strictMode = large_header.strictMode;
header->hasExceptionHandler = large_header.hasExceptionHandler;
header->hasDebugInfo = large_header.hasDebugInfo;
header->overflowed = large_header.overflowed;
header->unused = large_header.unused;
unpack_function_flags (header, flags);
}
/* Read the function bytecode */
@@ -668,34 +685,8 @@ Result _hbc_reader_read_functions(HBCReader *reader) {
/* Store the bytecode pointer */
header->bytecode = bytecode;
/* Read exception handlers if present */
if (header->hasExceptionHandler) {
/* Seek to the exception handler info */
RETURN_IF_ERROR (_hbc_buffer_reader_seek (&reader->file_buffer, header->infoOffset));
/* Align buffer */
RETURN_IF_ERROR (align_over_padding (&reader->file_buffer, 4));
/* Read the number of exception handlers */
u32 exc_handler_count;
RETURN_IF_ERROR (_hbc_buffer_reader_read_u32 (&reader->file_buffer, &exc_handler_count));
/* Allocate exception handlers */
ExceptionHandlerInfo *handlers = (ExceptionHandlerInfo *)malloc (exc_handler_count * sizeof (ExceptionHandlerInfo));
if (!handlers) {
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "Failed to allocate exception handlers");
}
/* Read each exception handler */
for (u32 j = 0; j < exc_handler_count; j++) {
RETURN_IF_ERROR (_hbc_buffer_reader_read_u32 (&reader->file_buffer, &handlers[j].start));
RETURN_IF_ERROR (_hbc_buffer_reader_read_u32 (&reader->file_buffer, &handlers[j].end));
RETURN_IF_ERROR (_hbc_buffer_reader_read_u32 (&reader->file_buffer, &handlers[j].target));
}
/* Store the exception handlers */
reader->function_id_to_exc_handlers[i].handlers = handlers;
reader->function_id_to_exc_handlers[i].count = exc_handler_count;
RETURN_IF_ERROR (read_exception_handlers (reader, i, header->infoOffset));
}
/* Read debug information if present */
@@ -984,12 +975,9 @@ Result _hbc_reader_read_string_tables(HBCReader *reader) {
is_utf16 = reader->small_string_table[i].isUTF16;
}
size_t decoded_length = (size_t)length;
size_t available = (size_t)offset > string_storage_size? 0: string_storage_size - (size_t)offset;
if ((size_t)offset > string_storage_size ||
decoded_length == SIZE_MAX ||
(is_utf16 && decoded_length > available / 2) ||
(!is_utf16 && decoded_length > available)) {
/* Overflow-safe bounds check via subtraction */
const u32 bpc = is_utf16? 2: 1;
if (offset > string_storage_size || length > (string_storage_size - offset) / bpc) {
fprintf (stderr, "Warning: String %u offset/length out of bounds, treating as empty string\n", i);
reader->strings[i] = strdup ("");
if (!reader->strings[i]) {
@@ -1000,9 +988,7 @@ Result _hbc_reader_read_string_tables(HBCReader *reader) {
continue;
}
/* Allocate string buffer (+1 for null terminator) */
size_t buffer_size = decoded_length + 1;
char *str = (char *)malloc (buffer_size);
char *str = (char *)malloc (length * bpc + 1);
if (!str) {
free (string_storage);
reader->string_table_storage = NULL;
@@ -1082,6 +1068,7 @@ Result _hbc_reader_read_arrays(HBCReader *reader) {
/* Read array buffer */
if (reader->header.arrayBufferSize > 0) {
reader->arrays_paddr = (u32)reader->file_buffer.position;
reader->arrays = (u8 *)malloc (reader->header.arrayBufferSize);
if (!reader->arrays) {
hbc_debug_printf ("Failed to allocate %u bytes for array buffer\n", reader->header.arrayBufferSize);
@@ -1113,6 +1100,7 @@ Result _hbc_reader_read_arrays(HBCReader *reader) {
reader->header.objKeyBufferSize = 0;
} else {
/* Safe to read object keys */
reader->object_keys_paddr = (u32)reader->file_buffer.position;
reader->object_keys = (u8 *)malloc (reader->header.objKeyBufferSize);
if (!reader->object_keys) {
fprintf (stderr, "Failed to allocate %u bytes for object key buffer\n", reader->header.objKeyBufferSize);
@@ -1154,6 +1142,7 @@ Result _hbc_reader_read_arrays(HBCReader *reader) {
}
reader->literal_values = reader->arrays;
reader->object_values = reader->arrays;
reader->object_values_paddr = reader->arrays_paddr;
return SUCCESS_RESULT ();
}
@@ -1169,6 +1158,7 @@ Result _hbc_reader_read_arrays(HBCReader *reader) {
reader->header.objValueBufferSize = 0;
} else {
/* Safe to read object values */
reader->object_values_paddr = (u32)reader->file_buffer.position;
reader->object_values = (u8 *)malloc (reader->header.objValueBufferSize);
if (!reader->object_values) {
fprintf (stderr, "Failed to allocate %u bytes for object value buffer\n", reader->header.objValueBufferSize);
@@ -1339,10 +1329,8 @@ Result _hbc_reader_read_bigints(HBCReader *reader) {
u32 offset = bigint_table[i].offset;
u32 length = bigint_table[i].length;
/* Check bounds */
size_t bigint_available = (size_t)offset > reader->header.bigIntStorageSize? 0:
(size_t)reader->header.bigIntStorageSize - (size_t)offset;
if ((size_t)offset > reader->header.bigIntStorageSize || (size_t)length > bigint_available) {
/* Overflow-safe bounds check via subtraction */
if (offset > reader->header.bigIntStorageSize || length > reader->header.bigIntStorageSize - offset) {
fprintf (stderr, "Warning: BigInt %u has invalid offset/length (%u+%u exceeds %u)\n", i, offset, length, reader->header.bigIntStorageSize);
/* Use zero for this value */
reader->bigint_values[i] = 0;
@@ -1581,22 +1569,31 @@ Result _hbc_reader_read_function_sources(HBCReader *reader) {
return SUCCESS_RESULT ();
}
/* Read debug information */
Result _hbc_reader_read_debug_info(HBCReader *reader) {
/* Size of the on-disk DebugInfoHeader block, in bytes. */
static u32 debug_info_header_bytes(u32 version) {
/* v<91: 5 u32s (filename_count, filename_storage_size, file_region_count,
* scope_desc_data_offset, debug_data_size).
* v>=91: 7 u32s (adds textified_data_offset, string_table_offset). */
return (version >= 91? 7u: 5u) * sizeof (u32);
}
/* Read just the DebugInfoHeader fields (no storage blobs, no allocations).
* Cheap enough to run eagerly at load time so "has source lines" is knowable
* without committing to a full debug-info parse. Idempotent. */
Result _hbc_reader_read_debug_info_header(HBCReader *reader) {
if (!reader) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Reader is NULL");
}
if (reader->header.debugInfoOffset == 0) {
if (reader->debug_info_header_loaded) {
return SUCCESS_RESULT ();
}
if (reader->header.version >= 97) {
if (reader->header.debugInfoOffset == 0) {
reader->debug_info_header_loaded = true;
return SUCCESS_RESULT ();
}
/* Seek to debug info offset */
RETURN_IF_ERROR (_hbc_buffer_reader_seek (&reader->file_buffer, reader->header.debugInfoOffset));
/* Read debug info header */
RETURN_IF_ERROR (_hbc_buffer_reader_read_u32 (&reader->file_buffer, &reader->debug_info_header.filename_count));
RETURN_IF_ERROR (_hbc_buffer_reader_read_u32 (&reader->file_buffer, &reader->debug_info_header.filename_storage_size));
RETURN_IF_ERROR (_hbc_buffer_reader_read_u32 (&reader->file_buffer, &reader->debug_info_header.file_region_count));
@@ -1609,6 +1606,30 @@ Result _hbc_reader_read_debug_info(HBCReader *reader) {
RETURN_IF_ERROR (_hbc_buffer_reader_read_u32 (&reader->file_buffer, &reader->debug_info_header.debug_data_size));
reader->debug_info_header_loaded = true;
return SUCCESS_RESULT ();
}
/* Read the full debug-info body (filenames, file regions, sources/scope/
* textified/string-table storage blobs). Builds on the cheap header parse.
* Idempotent: subsequent calls after a successful load are no-ops. */
Result _hbc_reader_read_debug_info(HBCReader *reader) {
if (!reader) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Reader is NULL");
}
if (reader->debug_info_loaded) {
return SUCCESS_RESULT ();
}
RETURN_IF_ERROR (_hbc_reader_read_debug_info_header (reader));
if (reader->header.debugInfoOffset == 0) {
reader->debug_info_loaded = true;
return SUCCESS_RESULT ();
}
/* Seek past the already-parsed header to the start of the body. */
RETURN_IF_ERROR (_hbc_buffer_reader_seek (&reader->file_buffer,
reader->header.debugInfoOffset + debug_info_header_bytes (reader->header.version)));
/* Allocate debug string table */
if (reader->debug_info_header.filename_count > 0) {
reader->debug_string_table = (OffsetLengthPair *)malloc (
@@ -1710,6 +1731,7 @@ Result _hbc_reader_read_debug_info(HBCReader *reader) {
reader->string_table_storage_size = string_table_size;
}
reader->debug_info_loaded = true;
return SUCCESS_RESULT ();
}
@@ -1823,12 +1845,6 @@ Result _hbc_reader_read_functions_robust(HBCReader *reader) {
const u8 small_environmentSize = (u8) (raw_data[3] & 0xFF);
const u8 small_highestReadCacheIndex = (u8) ((raw_data[3] >> 8) & 0xFF);
const u8 small_highestWriteCacheIndex = (u8) ((raw_data[3] >> 16) & 0xFF);
const u8 small_prohibitInvoke = (raw_data[3] >> 24) & 0x3;
const u8 small_strictMode = (raw_data[3] >> 26) & 0x1;
const u8 small_hasExceptionHandler = (raw_data[3] >> 27) & 0x1;
const u8 small_hasDebugInfo = (raw_data[3] >> 28) & 0x1;
const u8 small_overflowed = (raw_data[3] >> 29) & 0x1;
const u8 small_unused = (raw_data[3] >> 30) & 0x3;
/* Initialize header with small header values */
header->offset = small_offset;
@@ -1840,15 +1856,10 @@ Result _hbc_reader_read_functions_robust(HBCReader *reader) {
header->environmentSize = small_environmentSize;
header->highestReadCacheIndex = small_highestReadCacheIndex;
header->highestWriteCacheIndex = small_highestWriteCacheIndex;
header->prohibitInvoke = small_prohibitInvoke;
header->strictMode = small_strictMode;
header->hasExceptionHandler = small_hasExceptionHandler;
header->hasDebugInfo = small_hasDebugInfo;
header->overflowed = small_overflowed;
header->unused = small_unused;
unpack_function_flags (header, (u8) (raw_data[3] >> 24));
/* Try to read large header if needed and override values */
if (small_overflowed || small_bytecodeSizeInBytes == 0) {
if (header->overflowed || small_bytecodeSizeInBytes == 0) {
const u32 large_header_offset = (small_infoOffset << 16) | small_offset;
const Result sr = _hbc_buffer_reader_seek (&reader->file_buffer, large_header_offset);
@@ -1877,12 +1888,7 @@ Result _hbc_reader_read_functions_robust(HBCReader *reader) {
header->environmentSize = (u8)large_header.environmentSize;
header->highestReadCacheIndex = large_header.highestReadCacheIndex;
header->highestWriteCacheIndex = large_header.highestWriteCacheIndex;
header->prohibitInvoke = flags & 0x3;
header->strictMode = (flags >> 2) & 0x1;
header->hasExceptionHandler = (flags >> 3) & 0x1;
header->hasDebugInfo = (flags >> 4) & 0x1;
header->overflowed = (flags >> 5) & 0x1;
header->unused = (flags >> 6) & 0x3;
unpack_function_flags (header, flags);
}
}
}
@@ -1914,6 +1920,13 @@ Result _hbc_reader_read_functions_robust(HBCReader *reader) {
/* For robustness, we don't load bytecode here */
header->bytecode = NULL;
if (header->hasExceptionHandler) {
Result eh_res = read_exception_handlers (reader, i, header->infoOffset);
if (eh_res.code != RESULT_SUCCESS) {
hbc_debug_printf ("Warning: Skipping exception handlers for function %u: %s\n", i, eh_res.error_message);
}
}
successful_functions++;
/* Ensure position is at next small header (16 bytes per entry) */
@@ -1952,7 +1965,9 @@ Result _hbc_reader_read_whole_file(HBCReader *reader, const char *filename) {
/* Read header */
result = _hbc_reader_read_header (reader);
if (result.code != RESULT_SUCCESS) {
#if 0
fprintf (stderr, "Error reading header: %s\n", result.error_message);
#endif
return result;
}
@@ -2032,10 +2047,11 @@ Result _hbc_reader_read_whole_file(HBCReader *reader, const char *filename) {
}
}
/* Read debug info */
result = _hbc_reader_read_debug_info (reader);
/* Read only the DebugInfoHeader eagerly (cheap). The body is parsed lazily
* via _hbc_reader_read_debug_info () on first API use. */
result = _hbc_reader_read_debug_info_header (reader);
if (result.code != RESULT_SUCCESS) {
fprintf (stderr, "Error reading debug info: %s\n", result.error_message);
fprintf (stderr, "Error reading debug info header: %s\n", result.error_message);
return result;
}
+26 -68
View File
@@ -1,7 +1,18 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/hbc.h>
#include <hbc/parser.h>
#include <ctype.h>
static void sanitize_identifier(const char *src, char *dst, size_t dst_size) {
size_t out = 0;
for (size_t j = 0; src[j] && out + 1 < dst_size; j++) {
char c = src[j];
dst[out++] = (isalnum ((unsigned char)c) || c == '_')? c: '_';
}
dst[out] = '\0';
}
/* Generate r2 script with function flags - robust version inspired by the Python implementation */
Result _hbc_generate_r2_script(const char *input_file, const char *output_file) {
if (!input_file) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Input file is NULL");
@@ -209,21 +220,7 @@ Result _hbc_generate_r2_script(const char *input_file, const char *output_file)
/* Generate sanitized name for r2 */
char sanitized_name[256] = { 0 };
size_t name_len = strlen (function_name);
size_t sanitized_idx = 0;
for (size_t j = 0; j < name_len && sanitized_idx < sizeof (sanitized_name) - 1; j++) {
char c = function_name[j];
if ((c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') ||
c == '_') {
sanitized_name[sanitized_idx++] = c;
} else {
sanitized_name[sanitized_idx++] = '_';
}
}
sanitized_name[sanitized_idx] = '\0';
sanitize_identifier (function_name, sanitized_name, sizeof (sanitized_name));
/* If sanitized name is empty, use a default */
if (sanitized_name[0] == '\0') {
@@ -363,11 +360,9 @@ Result _hbc_generate_r2_script(const char *input_file, const char *output_file)
u32 length = string_infos[i].length;
bool isUTF16 = string_infos[i].isUTF16;
/* Skip strings with obviously invalid offsets/lengths */
size_t available = (size_t)offset > string_storage_size? 0: string_storage_size - (size_t)offset;
if ((size_t)offset > string_storage_size ||
(isUTF16 && (size_t)length > available / 2) ||
(!isUTF16 && (size_t)length > available)) {
/* Overflow-safe bounds check via subtraction */
const u32 bpc = isUTF16? 2: 1;
if (offset > string_storage_size || length > (string_storage_size - offset) / bpc) {
continue;
}
@@ -377,65 +372,28 @@ Result _hbc_generate_r2_script(const char *input_file, const char *output_file)
display_length = 32;
}
/* Generate a string value for the flag name */
/* Generate a string value for the flag name: decode each char (1 or
* 2 bytes, LE) and keep only ASCII-printable bytes, else '_'. */
char str_value[64] = { 0 };
if (isUTF16) {
/* UTF-16 string - simplified handling */
u32 chars_added = 0;
for (u32 j = 0; j < display_length && chars_added < sizeof (str_value) - 1; j++) {
/* Read 16-bit character */
u16 c = (u16) (string_storage[offset + (j * 2)] | (string_storage[offset + (j * 2) + 1] << 8));
/* Only add ASCII-printable characters to name */
if (c >= 32 && c < 127) {
str_value[chars_added++] = (char)c;
} else {
str_value[chars_added++] = '_';
}
}
str_value[chars_added] = '\0';
} else {
/* ASCII string */
u32 chars_added = 0;
for (u32 j = 0; j < display_length && chars_added < sizeof (str_value) - 1; j++) {
char c = (char)string_storage[offset + j];
/* Only add printable characters to name */
if (c >= 32 && c < 127) {
str_value[chars_added++] = c;
} else {
str_value[chars_added++] = '_';
}
}
str_value[chars_added] = '\0';
u32 chars_added = 0;
for (u32 j = 0; j < display_length && chars_added < sizeof (str_value) - 1; j++) {
const u8 *p = string_storage + offset + (j * bpc);
u32 c = isUTF16? ((u32)p[0] | ((u32)p[1] << 8)): p[0];
str_value[chars_added++] = (c >= 32 && c < 127)? (char)c: '_';
}
str_value[chars_added] = '\0';
/* Generate sanitized name for r2 - only include alphanumeric and underscores */
char sanitized_name[64] = { 0 };
size_t name_len = strlen (str_value);
size_t sanitized_idx = 0;
for (size_t j = 0; j < name_len && sanitized_idx < sizeof (sanitized_name) - 1; j++) {
char c = str_value[j];
if ((c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') ||
c == '_') {
sanitized_name[sanitized_idx++] = c;
} else {
sanitized_name[sanitized_idx++] = '_';
}
}
sanitized_name[sanitized_idx] = '\0';
sanitize_identifier (str_value, sanitized_name, sizeof (sanitized_name));
/* Add index to name to ensure uniqueness */
char unique_name[96];
snprintf (unique_name, sizeof (unique_name), "%s_%u", sanitized_name[0]? sanitized_name: "str", i);
/* Write the string flag - offset is relative to string storage base */
size_t size = isUTF16? (size_t)length * 2: (size_t)length;
uint32_t addr = (unsigned long) (reader.file_buffer.position - string_storage_size + offset);
fprintf (out, "'f str.%s %zu 0x%08x\n", unique_name, size, addr);
fprintf (out, "'f str.%s %u 0x%08x\n", unique_name, length * bpc, addr);
#if 0
/* Add flag for string length */
if (isUTF16) {
+37 -117
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/common.h>
Result _hbc_buffer_reader_init_from_file(BufferReader *reader, const char *filename) {
@@ -61,141 +63,59 @@ Result _hbc_buffer_reader_init_from_memory(BufferReader *reader, const u8 *data,
return SUCCESS_RESULT ();
}
Result _hbc_buffer_reader_read_u8(BufferReader *reader, u8 *out_value) {
if (!reader || !out_value) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Invalid arguments for _hbc_buffer_reader_read_u8");
/* Shared validity + bounds check for a read of `need` bytes at the cursor. */
static Result buffer_reader_check(const BufferReader *reader, size_t need) {
if (!reader || !reader->data) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Invalid buffer reader");
}
/* Safety check - is the reader data valid? */
if (!reader->data) {
return ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, "BufferReader has no data");
}
/* Extra validation for position */
if (reader->position >= reader->size) {
return ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, "Buffer position at or beyond buffer size");
}
/* Now check if we can read a byte */
if (reader->position + sizeof (u8) > reader->size) {
/* Provide a default but warn */
hbc_debug_printf ("Warning: Buffer overflow prevented in read_u8 at position %zu of %zu bytes\n",
if (reader->position + need > reader->size) {
hbc_debug_printf ("Warning: Buffer overflow prevented reading %zu bytes at position %zu of %zu\n",
need,
reader->position,
reader->size);
*out_value = 0;
return ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, "Buffer overflow in read_u8");
return ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, "Buffer overflow");
}
/* All good, proceed with read */
*out_value = reader->data[reader->position];
reader->position += sizeof (u8);
return SUCCESS_RESULT ();
}
Result _hbc_buffer_reader_read_u16(BufferReader *reader, u16 *out_value) {
if (!reader || !out_value) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Invalid arguments for _hbc_buffer_reader_read_u16");
/* Read `n` (1..8) little-endian bytes at the cursor, advancing the position.
* *out is zeroed on any failure. */
static Result buffer_reader_read_le(BufferReader *reader, size_t n, u64 *out) {
*out = 0;
RETURN_IF_ERROR (buffer_reader_check (reader, n));
u64 v = 0;
for (size_t i = 0; i < n; i++) {
v |= (u64)reader->data[reader->position + i] << (8 * i);
}
/* Safety check - is the reader data valid? */
if (!reader->data) {
return ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, "BufferReader has no data");
}
/* Extra validation for position */
if (reader->position >= reader->size) {
return ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, "Buffer position at or beyond buffer size");
}
/* Now check if we can read 2 bytes */
if (reader->position + sizeof (u16) > reader->size) {
/* Provide a default but warn */
hbc_debug_printf ("Warning: Buffer overflow prevented in read_u16 at position %zu of %zu bytes\n",
reader->position,
reader->size);
*out_value = 0;
return ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, "Buffer overflow in read_u16");
}
/* Read in little-endian format */
*out_value = (u16)reader->data[reader->position] |
((u16)reader->data[reader->position + 1] << 8);
reader->position += sizeof (u16);
reader->position += n;
*out = v;
return SUCCESS_RESULT ();
}
Result _hbc_buffer_reader_read_u32(BufferReader *reader, u32 *out_value) {
if (!reader || !out_value) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Invalid arguments for _hbc_buffer_reader_read_u32");
/* Define the fixed-width little-endian readers; all share buffer_reader_read_le. */
#define HBC_DEFINE_READ(bits) \
Result _hbc_buffer_reader_read_u ## bits (BufferReader *reader, u ## bits *out_value) { \
if (!out_value) { \
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "read out_value is NULL"); \
} \
u64 v; \
Result r = buffer_reader_read_le (reader, sizeof (u ## bits), &v); \
*out_value = (u ## bits)v; \
return r; \
}
/* Safety check - is the reader data valid? */
if (!reader->data) {
return ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, "BufferReader has no data");
}
/* Extra validation for position */
if (reader->position >= reader->size) {
return ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, "Buffer position at or beyond buffer size");
}
/* Now check if we can read 4 bytes */
if (reader->position + sizeof (u32) > reader->size) {
/* Provide a default but warn */
hbc_debug_printf ("Warning: Buffer overflow prevented in read_u32 at position %zu of %zu bytes\n",
reader->position,
reader->size);
*out_value = 0;
return ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, "Buffer overflow in read_u32");
}
/* Read in little-endian format */
*out_value = (u32)reader->data[reader->position] |
((u32)reader->data[reader->position + 1] << 8) |
((u32)reader->data[reader->position + 2] << 16) |
((u32)reader->data[reader->position + 3] << 24);
reader->position += sizeof (u32);
return SUCCESS_RESULT ();
}
Result _hbc_buffer_reader_read_u64(BufferReader *reader, u64 *out_value) {
if (!reader || !out_value) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Invalid arguments for _hbc_buffer_reader_read_u64");
}
if (reader->position + sizeof (u64) > reader->size) {
return ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, "Buffer overflow in read_u64");
}
/* Read in little-endian format */
*out_value = (u64)reader->data[reader->position] |
((u64)reader->data[reader->position + 1] << 8) |
((u64)reader->data[reader->position + 2] << 16) |
((u64)reader->data[reader->position + 3] << 24) |
((u64)reader->data[reader->position + 4] << 32) |
((u64)reader->data[reader->position + 5] << 40) |
((u64)reader->data[reader->position + 6] << 48) |
((u64)reader->data[reader->position + 7] << 56);
reader->position += sizeof (u64);
return SUCCESS_RESULT ();
}
HBC_DEFINE_READ(8)
HBC_DEFINE_READ(16)
HBC_DEFINE_READ(32)
HBC_DEFINE_READ(64)
#undef HBC_DEFINE_READ
Result _hbc_buffer_reader_read_bytes(BufferReader *reader, u8 *out_buffer, size_t length) {
if (!reader || !out_buffer) {
if (!out_buffer) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Invalid arguments for _hbc_buffer_reader_read_bytes");
}
if (reader->position + length > reader->size) {
return ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, "Buffer overflow in read_bytes");
}
RETURN_IF_ERROR (buffer_reader_check (reader, length));
memcpy (out_buffer, reader->data + reader->position, length);
reader->position += length;
return SUCCESS_RESULT ();
}
+28 -28
View File
@@ -1,5 +1,30 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/common.h>
/* Grow the buffer (doubling) so it can hold at least required_capacity bytes. */
static Result string_buffer_ensure(StringBuffer *buffer, size_t required_capacity) {
if (required_capacity <= buffer->capacity) {
return SUCCESS_RESULT ();
}
size_t new_capacity = buffer->capacity? buffer->capacity: 64;
while (new_capacity < required_capacity) {
size_t doubled = new_capacity * 2;
if (doubled <= new_capacity) { /* overflow: just take what's needed */
new_capacity = required_capacity;
break;
}
new_capacity = doubled;
}
char *new_data = (char *)realloc (buffer->data, new_capacity);
if (!new_data) {
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "Failed to resize string buffer");
}
buffer->data = new_data;
buffer->capacity = new_capacity;
return SUCCESS_RESULT ();
}
Result _hbc_string_buffer_init(StringBuffer *buffer, size_t initial_capacity) {
if (!buffer) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Buffer is NULL");
@@ -32,21 +57,7 @@ Result _hbc_string_buffer_append(StringBuffer *buffer, const char *str) {
}
/* Ensure enough capacity */
size_t required_capacity = buffer->length + str_len + 1;
if (required_capacity > buffer->capacity) {
size_t new_capacity = buffer->capacity * 2;
while (new_capacity < required_capacity) {
new_capacity *= 2;
}
char *new_data = (char *)realloc (buffer->data, new_capacity);
if (!new_data) {
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "Failed to resize string buffer");
}
buffer->data = new_data;
buffer->capacity = new_capacity;
}
RETURN_IF_ERROR (string_buffer_ensure (buffer, buffer->length + str_len + 1));
/* Append the string */
memcpy (buffer->data + buffer->length, str, str_len);
@@ -61,19 +72,8 @@ Result _hbc_string_buffer_append_char(StringBuffer *buffer, char c) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Buffer is NULL");
}
/* Ensure enough capacity */
size_t required_capacity = buffer->length + 2; /* +1 for the char, +1 for null terminator */
if (required_capacity > buffer->capacity) {
size_t new_capacity = buffer->capacity * 2;
char *new_data = (char *)realloc (buffer->data, new_capacity);
if (!new_data) {
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "Failed to resize string buffer");
}
buffer->data = new_data;
buffer->capacity = new_capacity;
}
/* Ensure enough capacity (+1 for the char, +1 for null terminator) */
RETURN_IF_ERROR (string_buffer_ensure (buffer, buffer->length + 2));
/* Append the character */
buffer->data[buffer->length] = c;
+6 -5
View File
@@ -33,6 +33,7 @@ BIN_SRC = bin_hbc.c
ASM_SRC = asm_hbc.c
CORE_SRC = core_hbc.c
CORE_ONE_SRC = core_hbc_one.c
HBC_HEADERS = $(shell find ../../include -name '*.h') utils.inc.c sbom.inc.c
# Object files
ARCH_OBJ = arch_hbc.o
@@ -107,19 +108,19 @@ $(CORE_ONE_PLUGIN): $(CORE_ONE_OBJ) $(HERMES_LIB)
$(CC) $(LDFLAGS) -o $@ $(CORE_ONE_OBJ) $(HERMES_LIB)
# Compile object files
$(ARCH_OBJ): $(ARCH_SRC)
$(ARCH_OBJ): $(ARCH_SRC) $(HBC_HEADERS)
$(CC) $(CFLAGS) -c $< -o $@
$(BIN_OBJ): $(BIN_SRC)
$(BIN_OBJ): $(BIN_SRC) $(HBC_HEADERS)
$(CC) $(CFLAGS) -c $< -o $@
$(ASM_OBJ): $(ASM_SRC)
$(ASM_OBJ): $(ASM_SRC) $(HBC_HEADERS)
$(CC) $(CFLAGS) -c $< -o $@
$(CORE_OBJ): $(CORE_SRC)
$(CORE_OBJ): $(CORE_SRC) $(HBC_HEADERS)
$(CC) $(CFLAGS) -c $< -o $@
$(CORE_ONE_OBJ): $(CORE_ONE_SRC)
$(CORE_ONE_OBJ): $(CORE_ONE_SRC) $(HBC_HEADERS)
$(CC) $(CFLAGS) -c $< -o $@
+52 -26
View File
@@ -1,18 +1,20 @@
/* radare2 - LGPL - Copyright 2025-2026 - pancake */
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <r_anal.h>
#include <hbc/hbc.h>
#include <hbc/literals.h>
#include <hbc/opcodes.h>
#include <hbc/bytecode.h>
#include <hbc/parser.h>
#define MAX_OP_SIZE 16
#define MAX_OP_SIZE 32
#define HBC_VADDR_BASE 0x10000000
#ifndef R_ARCH_SYNTAX_CAMEL
#define R_ARCH_SYNTAX_CAMEL 6
#endif
typedef struct {
ut32 bytecode_version; /* cached from RBinInfo->cpu if available */
ut32 bytecode_version; /* cached effective HBC bytecode version */
HBC *hbc; /* Hermes state for direct access */
u32 string_count;
const void *small_string_table;
@@ -20,25 +22,41 @@ typedef struct {
u64 string_storage_offset;
} HermesArchSession;
static ut32 parse_cpu_version(const char *cpu) {
if (!cpu || !*cpu) {
return 0;
}
const char *p = cpu;
if (p[0] != 'v') {
return 0;
}
unsigned long v = strtoul (p + 1, NULL, 10);
return v > 0 && v <= UT32_MAX? (ut32)v: 0;
}
static ut32 detect_version_from_bin(RArchSession *s) {
if (!s || !s->arch || !s->arch->binb.bin) {
return 96; /* sane default */
return 0;
}
RBin *bin = s->arch->binb.bin;
RBinInfo *bi = r_bin_get_info (bin);
if (bi && bi->cpu && *bi->cpu) {
const char *p = bi->cpu;
/* Accept optional leading 'v' or 'V' (e.g., "v76") */
if (p[0] == 'v' || p[0] == 'V') {
p++;
}
/* cpu holds the version string set by bin plugin */
ut32 v = (ut32)strtoul (p, NULL, 10);
ut32 v = parse_cpu_version (bi->cpu);
if (v > 0) {
return v;
}
}
return 96;
return 0;
}
static ut32 detect_version(RArchSession *s) {
ut32 v = parse_cpu_version (s && s->config? s->config->cpu: NULL);
if (v > 0) {
return v;
}
v = detect_version_from_bin (s);
return v > 0? v: 96; /* sane default */
}
#include "esil.inc.c"
@@ -169,7 +187,12 @@ static void parse_operands_and_set_ptr(RAnalOp *op, const ut8 *bytes, ut32 size,
HBCStringMeta meta;
Result meta_result = hbc_get_string_meta (hs->hbc, string_id, &meta);
if (meta_result.code == RESULT_SUCCESS) {
op->ptr = (st64)meta.offset;
op->ptr = (st64) (HBC_VADDR_BASE + meta.offset);
/* Expose the string byte length (size of the pointed
* contents) so the disassembler caps r2's string read and
* picks the right flag among overlapping, non-null-terminated
* Hermes strings that share this storage offset. */
op->ptrsize = meta.isUTF16? (int) (meta.length * 2): (int)meta.length;
}
}
}
@@ -185,7 +208,7 @@ static void parse_operands_and_set_ptr(RAnalOp *op, const ut8 *bytes, ut32 size,
offset = fi.offset;
(void)fi.size;
(void)fi.param_count;
op->ptr = (st64)offset;
op->ptr = (st64) (HBC_VADDR_BASE + offset);
}
}
}
@@ -205,13 +228,12 @@ static bool decode(RArchSession *s, RAnalOp *op, RArchDecodeMask mask) {
return false;
}
if (!hs->bytecode_version) {
hs->bytecode_version = detect_version_from_bin (s);
}
hs->bytecode_version = detect_version (s);
/* Load string tables if not already loaded */
if (!hs->hbc) {
load_string_tables (hs, s);
hs->bytecode_version = detect_version (s);
}
/* Build decode context */
@@ -231,8 +253,12 @@ static bool decode(RArchSession *s, RAnalOp *op, RArchDecodeMask mask) {
.resolve_string_ids = true,
.string_tables = &string_tables,
.hbc = hs->hbc,
};
if (mask & R_ARCH_OP_MASK_ESIL && mask & R_ARCH_OP_MASK_DISASM) {
};
/* Inlining of buffer-literal contents into the disasm line is opt-in —
* it makes arch.decode O (n) in literal size and breaks r2's O(1) per-op
* disasm assumption. Toggle via `e r2hermes.inline_buffer_literals=true`
*(core plugin updates a process-wide flag). */
if ((mask & R_ARCH_OP_MASK_DISASM) && hbc_get_inline_literals ()) {
ctx.build_objects = true;
}
@@ -527,7 +553,10 @@ static int archinfo(RArchSession *s, ut32 q) {
static char *mnemonics(RArchSession *s, int id, bool json) {
HermesArchSession *hs = s? (HermesArchSession *)s->data: NULL;
ut32 version = (hs && hs->bytecode_version)? hs->bytecode_version: 96;
ut32 version = detect_version (s);
if (hs) {
hs->bytecode_version = version;
}
HBCISA isa = hbc_isa_getv (version);
if (!isa.instructions || isa.count == 0) {
return NULL;
@@ -565,12 +594,7 @@ static bool encode(RArchSession *s, RAnalOp *op, RArchEncodeMask mask) {
return false;
}
if (!hs->bytecode_version) {
hs->bytecode_version = detect_version_from_bin (s);
if (!hs->bytecode_version) {
hs->bytecode_version = 96;
}
}
hs->bytecode_version = detect_version (s);
const char *asm_line = op->mnemonic;
@@ -661,7 +685,9 @@ const RArchPlugin r_arch_plugin_r2hermes = {
},
.arch = "hbc",
.bits = R_SYS_BITS_PACK1 (64),
.cpus = "v76,v90,v91,v92,v93,v94,v95,v96,v97,v98,v99",
.cpus = "v51,v58,v59,v61,v62,v68,v69,v70,v72,v73,v76,"
"v80,v81,v82,v83,v84,v85,v86,v87,v88,v89,"
"v90,v91,v92,v93,v94,v95,v96,v97,v98,v99",
.decode = &decode,
.encode = &encode,
.regs = regs,
+171 -171
View File
@@ -1,210 +1,210 @@
/* radare2 - LGPL - Copyright 2025-2026 - pancake */
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <r_asm.h>
/* Pseudo transformation rules for Hermes bytecode instructions */
static const char *pseudo_rules[] = {
/* Mov/Load operations */
"mov/2/$1 = $2",
"mov_long/2/$1 = $2",
"loadi8/2/$1 = (int8)$2",
"loadu8/2/$1 = (uint8)$2",
"loadi16/2/$1 = (int16)$2",
"loadu16/2/$1 = (uint16)$2",
"loadi32/2/$1 = (int32)$2",
"loadu32/2/$1 = (uint32)$2",
"load_const_uint8/2/$1 = $2",
"load_const_u_int8/2/$1 = $2",
"load_const_int/2/$1 = $2",
"load_const_double/2/$1 = $2",
"load_const_bigint/2/$1 = $2",
"load_const_string/2/$1 = $2",
"load_const_string_long_index/2/$1 = $2",
"load_const_empty/1/$1 = empty",
"load_const_undefined/1/$1 = undefined",
"load_const_null/1/$1 = null",
"load_const_true/1/$1 = true",
"load_const_false/1/$1 = false",
"load_const_zero/1/$1 = 0",
"mov/2/$1 = $2",
"mov_long/2/$1 = $2",
"loadi8/2/$1 = (int8)$2",
"loadu8/2/$1 = (uint8)$2",
"loadi16/2/$1 = (int16)$2",
"loadu16/2/$1 = (uint16)$2",
"loadi32/2/$1 = (int32)$2",
"loadu32/2/$1 = (uint32)$2",
"load_const_uint8/2/$1 = $2",
"load_const_u_int8/2/$1 = $2",
"load_const_int/2/$1 = $2",
"load_const_double/2/$1 = $2",
"load_const_bigint/2/$1 = $2",
"load_const_string/2/$1 = $2",
"load_const_string_long_index/2/$1 = $2",
"load_const_empty/1/$1 = empty",
"load_const_undefined/1/$1 = undefined",
"load_const_null/1/$1 = null",
"load_const_true/1/$1 = true",
"load_const_false/1/$1 = false",
"load_const_zero/1/$1 = 0",
/* Property access - get_by_id has 4 args: dest, obj, cache_idx, prop_name */
"get_by_id/4/$1 = $2.$4",
"get_by_id_long/4/$1 = $2.$4",
"get_by_id_short/4/$1 = $2.$4",
"get_by_val/3/$1 = $2[$3]",
"put_by_id/4/$1.$4 = $3",
"put_by_id_long/4/$1.$4 = $3",
"put_by_val/3/$1[$2] = $3",
"put_new_own_by_id/3/$1.$3 = $2",
"put_new_own_by_id_short/3/$1.$3 = $2",
"put_new_own_by_id_long/3/$1.$3 = $2",
"put_new_own_ne_by_id/3/$1.$3 = $2",
"put_new_own_ne_by_id_long/3/$1.$3 = $2",
"try_get_by_id/4/$1 = try_get($2, $4)",
"try_get_by_id_long/4/$1 = try_get($2, $4)",
"try_put_by_id/4/try_put($1, $4, $3)",
"try_put_by_id_long/4/try_put($1, $4, $3)",
"del_by_id/3/delete $1.$3",
"del_by_id_long/3/delete $1.$3",
"del_by_val/3/delete $1[$2]",
"get_by_id/4/$1 = $2.$4",
"get_by_id_long/4/$1 = $2.$4",
"get_by_id_short/4/$1 = $2.$4",
"get_by_val/3/$1 = $2[$3]",
"put_by_id/4/$1.$4 = $3",
"put_by_id_long/4/$1.$4 = $3",
"put_by_val/3/$1[$2] = $3",
"put_new_own_by_id/3/$1.$3 = $2",
"put_new_own_by_id_short/3/$1.$3 = $2",
"put_new_own_by_id_long/3/$1.$3 = $2",
"put_new_own_ne_by_id/3/$1.$3 = $2",
"put_new_own_ne_by_id_long/3/$1.$3 = $2",
"try_get_by_id/4/$1 = try_get($2, $4)",
"try_get_by_id_long/4/$1 = try_get($2, $4)",
"try_put_by_id/4/try_put($1, $4, $3)",
"try_put_by_id_long/4/try_put($1, $4, $3)",
"del_by_id/3/delete $1.$3",
"del_by_id_long/3/delete $1.$3",
"del_by_val/3/delete $1[$2]",
/* Arithmetic operations */
"add/3/$1 = $2 + $3",
"add_n/3/$1 = $2 + $3",
"add_32/3/$1 = (int32)($2 + $3)",
"add_empty_string/2/$1 = \"\" + $2",
"sub/3/$1 = $2 - $3",
"sub_n/3/$1 = $2 - $3",
"sub_32/3/$1 = (int32)($2 - $3)",
"mul/3/$1 = $2 * $3",
"mul_n/3/$1 = $2 * $3",
"mul_32/3/$1 = (int32)($2 * $3)",
"div/3/$1 = $2 / $3",
"div_n/3/$1 = $2 / $3",
"divi32/3/$1 = (int32)($2 / $3)",
"divu32/3/$1 = (uint32)($2 / $3)",
"mod/3/$1 = $2 % $3",
"inc/2/$1 = $2 + 1",
"dec/2/$1 = $2 - 1",
"negate/2/$1 = -$2",
"add/3/$1 = $2 + $3",
"add_n/3/$1 = $2 + $3",
"add_32/3/$1 = (int32)($2 + $3)",
"add_empty_string/2/$1 = \"\" + $2",
"sub/3/$1 = $2 - $3",
"sub_n/3/$1 = $2 - $3",
"sub_32/3/$1 = (int32)($2 - $3)",
"mul/3/$1 = $2 * $3",
"mul_n/3/$1 = $2 * $3",
"mul_32/3/$1 = (int32)($2 * $3)",
"div/3/$1 = $2 / $3",
"div_n/3/$1 = $2 / $3",
"divi32/3/$1 = (int32)($2 / $3)",
"divu32/3/$1 = (uint32)($2 / $3)",
"mod/3/$1 = $2 % $3",
"inc/2/$1 = $2 + 1",
"dec/2/$1 = $2 - 1",
"negate/2/$1 = -$2",
/* Bitwise operations */
"bit_and/3/$1 = $2 & $3",
"bit_or/3/$1 = $2 | $3",
"bit_xor/3/$1 = $2 ^ $3",
"bit_not/2/$1 = ~$2",
"lshift/3/$1 = $2 << $3",
"rshift/3/$1 = $2 >> $3",
"urshift/3/$1 = (unsigned)$2 >> $3",
"bit_and/3/$1 = $2 & $3",
"bit_or/3/$1 = $2 | $3",
"bit_xor/3/$1 = $2 ^ $3",
"bit_not/2/$1 = ~$2",
"lshift/3/$1 = $2 << $3",
"rshift/3/$1 = $2 >> $3",
"urshift/3/$1 = (unsigned)$2 >> $3",
/* Logical operations */
"not/2/$1 = !$2",
"eq/3/$1 = $2 == $3",
"strict_eq/3/$1 = $2 === $3",
"neq/3/$1 = $2 != $3",
"strict_neq/3/$1 = $2 !== $3",
"less/3/$1 = $2 < $3",
"greater/3/$1 = $2 > $3",
"less_eq/3/$1 = $2 <= $3",
"greater_eq/3/$1 = $2 >= $3",
"not/2/$1 = !$2",
"eq/3/$1 = $2 == $3",
"strict_eq/3/$1 = $2 === $3",
"neq/3/$1 = $2 != $3",
"strict_neq/3/$1 = $2 !== $3",
"less/3/$1 = $2 < $3",
"greater/3/$1 = $2 > $3",
"less_eq/3/$1 = $2 <= $3",
"greater_eq/3/$1 = $2 >= $3",
/* Type operations */
"typeof/2/$1 = typeof($2)",
"is_in/3/$1 = ($2 in $3)",
"instanceof/3/$1 = ($2 instanceof $3)",
"to_number/2/$1 = Number($2)",
"to_numeric/2/$1 = Numeric($2)",
"to_int32/2/$1 = (int32)$2",
"typeof/2/$1 = typeof($2)",
"is_in/3/$1 = ($2 in $3)",
"instanceof/3/$1 = ($2 instanceof $3)",
"to_number/2/$1 = Number($2)",
"to_numeric/2/$1 = Numeric($2)",
"to_int32/2/$1 = (int32)$2",
/* Object/Array creation */
"new_object/1/$1 = {}",
"new_object_with_parent/2/$1 = Object.create($2)",
"new_object_with_buffer/5/$1 = {/*buffer*/}",
"new_object_with_buffer_long/5/$1 = {/*buffer*/}",
"new_array/2/$1 = []",
"new_array_with_buffer/4/$1 = [/*buffer*/]",
"new_array_with_buffer_long/4/$1 = [/*buffer*/]",
"create_closure/3/$1 = closure($3)",
"create_closure_long_index/3/$1 = closure($3)",
"create_generator_closure/3/$1 = gen_closure($3)",
"create_generator/3/$1 = generator($3)",
"create_regexp/3/$1 = regexp($3)",
"new_object/1/$1 = {}",
"new_object_with_parent/2/$1 = Object.create($2)",
"new_object_with_buffer/5/$1 = {/*buffer*/}",
"new_object_with_buffer_long/5/$1 = {/*buffer*/}",
"new_array/2/$1 = []",
"new_array_with_buffer/4/$1 = [/*buffer*/]",
"new_array_with_buffer_long/4/$1 = [/*buffer*/]",
"create_closure/3/$1 = closure($3)",
"create_closure_long_index/3/$1 = closure($3)",
"create_generator_closure/3/$1 = gen_closure($3)",
"create_generator/3/$1 = generator($3)",
"create_regexp/3/$1 = regexp($3)",
/* Control flow */
"unreachable/0/unreachable",
"jmp/1/goto $1",
"jmp_long/1/goto $1",
"name/3/if ($2) goto $1",
"name##_long/2/if ($2) goto $1",
"jmp_true/2/if ($2) goto $1",
"jmp_true_long/2/if ($2) goto $1",
"jmp_false/2/if (!$2) goto $1",
"jmp_false_long/2/if (!$2) goto $1",
"jmp_undefined/2/if ($2 === undefined) goto $1",
"jmp_undefined_long/2/if ($2 === undefined) goto $1",
"jless/3/if ($1 < $2) goto $3",
"jless_long/3/if ($1 < $2) goto $3",
"jless_eq/3/if ($1 <= $2) goto $3",
"jless_eq_long/3/if ($1 <= $2) goto $3",
"jgreater/3/if ($1 > $2) goto $3",
"jgreater_long/3/if ($1 > $2) goto $3",
"jgreater_eq/3/if ($1 >= $2) goto $3",
"jgreater_eq_long/3/if ($1 >= $2) goto $3",
"jequal/3/if ($1 == $2) goto $3",
"jequal_long/3/if ($1 == $2) goto $3",
"jstrict_equal/3/if ($1 === $2) goto $3",
"jstrict_equal_long/3/if ($1 === $2) goto $3",
"jnot_equal/3/if ($1 != $2) goto $3",
"jnot_equal_long/3/if ($1 != $2) goto $3",
"jstrict_not_equal/3/if ($1 !== $2) goto $3",
"jstrict_not_equal_long/3/if ($1 !== $2) goto $3",
"unreachable/0/unreachable",
"jmp/1/goto $1",
"jmp_long/1/goto $1",
"name/3/if ($2) goto $1",
"name##_long/2/if ($2) goto $1",
"jmp_true/2/if ($2) goto $1",
"jmp_true_long/2/if ($2) goto $1",
"jmp_false/2/if (!$2) goto $1",
"jmp_false_long/2/if (!$2) goto $1",
"jmp_undefined/2/if ($2 === undefined) goto $1",
"jmp_undefined_long/2/if ($2 === undefined) goto $1",
"jless/3/if ($1 < $2) goto $3",
"jless_long/3/if ($1 < $2) goto $3",
"jless_eq/3/if ($1 <= $2) goto $3",
"jless_eq_long/3/if ($1 <= $2) goto $3",
"jgreater/3/if ($1 > $2) goto $3",
"jgreater_long/3/if ($1 > $2) goto $3",
"jgreater_eq/3/if ($1 >= $2) goto $3",
"jgreater_eq_long/3/if ($1 >= $2) goto $3",
"jequal/3/if ($1 == $2) goto $3",
"jequal_long/3/if ($1 == $2) goto $3",
"jstrict_equal/3/if ($1 === $2) goto $3",
"jstrict_equal_long/3/if ($1 === $2) goto $3",
"jnot_equal/3/if ($1 != $2) goto $3",
"jnot_equal_long/3/if ($1 != $2) goto $3",
"jstrict_not_equal/3/if ($1 !== $2) goto $3",
"jstrict_not_equal_long/3/if ($1 !== $2) goto $3",
/* Function calls and returns */
/* call: result = callee (this, args...) */
"call/2/$1($2)",
"call1/3/$1 = $2($3)",
"call2/4/$1 = $2($4)",
"call3/5/$1 = $2($4, $5)",
"call4/6/$1 = $2($4, $5, $6)",
"call_long/2/$1($2)",
"call_builtin/3/$1 = builtin.$2($3)",
"call_builtin_long/3/$1 = builtin.$2($3)",
"call_direct/3/$1 = $2($3)",
"call_direct_long_index/3/$1 = $2($3)",
"construct/3/$1 = new $2($3)",
"construct_long/3/$1 = new $2($3)",
"ret/1/return $1",
"ret_undefined/0/return",
"call/2/$1($2)",
"call1/3/$1 = $2($3)",
"call2/4/$1 = $2($4)",
"call3/5/$1 = $2($4, $5)",
"call4/6/$1 = $2($4, $5, $6)",
"call_long/2/$1($2)",
"call_builtin/3/$1 = builtin.$2($3)",
"call_builtin_long/3/$1 = builtin.$2($3)",
"call_direct/3/$1 = $2($3)",
"call_direct_long_index/3/$1 = $2($3)",
"construct/3/$1 = new $2($3)",
"construct_long/3/$1 = new $2($3)",
"ret/1/return $1",
"ret_undefined/0/return",
/* Exception handling */
"throw/1/throw $1",
"throw_if_empty/2/if (empty($1)) throw $2",
"catch/2/$1 = catch $2",
"throw/1/throw $1",
"throw_if_empty/2/if (empty($1)) throw $2",
"catch/2/$1 = catch $2",
/* Parameter handling */
"load_param/2/$1 = param[$2]",
"load_param_long/2/$1 = param[$2]",
"reify_arguments/1/arguments = reify($1)",
"get_arguments_length/2/$1 = arguments.length",
"get_arguments_prop_by_val/3/$1 = arguments[$2]",
"load_param/2/$1 = param[$2]",
"load_param_long/2/$1 = param[$2]",
"reify_arguments/1/arguments = reify($1)",
"get_arguments_length/2/$1 = arguments.length",
"get_arguments_prop_by_val/3/$1 = arguments[$2]",
/* Environment operations */
"get_environment/2/$1 = env[$2]",
"load_from_environment/3/$1 = $2[$3]",
"load_from_environment_l/3/$1 = $2[$3]",
"store_to_environment/3/$2[$3] = $1",
"store_to_environment_l/3/$2[$3] = $1",
"store_np_to_environment/3/$2[$3] = $1",
"store_np_to_environment_l/3/$2[$3] = $1",
"create_environment/1/$1 = new_env()",
"create_inner_environment/1/$1 = new_inner_env()",
"get_environment/2/$1 = env[$2]",
"load_from_environment/3/$1 = $2[$3]",
"load_from_environment_l/3/$1 = $2[$3]",
"store_to_environment/3/$2[$3] = $1",
"store_to_environment_l/3/$2[$3] = $1",
"store_np_to_environment/3/$2[$3] = $1",
"store_np_to_environment_l/3/$2[$3] = $1",
"create_environment/1/$1 = new_env()",
"create_inner_environment/1/$1 = new_inner_env()",
/* Generator operations */
"start_generator/0/start_generator()",
"resume_generator/2/$1 = resume_generator($2)",
"save_generator/1/save_generator($1)",
"complete_generator/0/complete_generator()",
"start_generator/0/start_generator()",
"resume_generator/2/$1 = resume_generator($2)",
"save_generator/1/save_generator($1)",
"complete_generator/0/complete_generator()",
/* Miscellaneous */
"debugger/0/debugger",
"profile_point/1/profile($1)",
"select_object/3/$1 = select($2, $3)",
"catch/2/$1 = catch_value($2)",
"get_builtin_closure/2/$1 = builtin_closure($2)",
"get_global_object/1/$1 = global",
"get_new_target/1/$1 = new.target",
"get_this_ns/1/$1 = this",
"coerce_this_ns/1/$1 = coerce_this()",
"debugger/0/debugger",
"profile_point/1/profile($1)",
"select_object/3/$1 = select($2, $3)",
"catch/2/$1 = catch_value($2)",
"get_builtin_closure/2/$1 = builtin_closure($2)",
"get_global_object/1/$1 = global",
"get_new_target/1/$1 = new.target",
"get_this_ns/1/$1 = this",
"coerce_this_ns/1/$1 = coerce_this()",
/* Iterator operations */
"iterator_begin/2/$1 = iterator_begin($2)",
"iterator_next/3/$1 = iterator_next($2, $3)",
"iterator_close/2/iterator_close($1, $2)",
"get_pname_list/2/$1 = property_names($2)",
"get_next_pname/4/$1 = next_property_name($2, $3, $4)",
"iterator_begin/2/$1 = iterator_begin($2)",
"iterator_next/3/$1 = iterator_next($2, $3)",
"iterator_close/2/iterator_close($1, $2)",
"get_pname_list/2/$1 = property_names($2)",
"get_next_pname/4/$1 = next_property_name($2, $3, $4)",
/* Switch */
"switch_imm/3/switch($1) { cases in $2 }",
"switch_imm/3/switch($1) { cases in $2 }",
NULL
};
@@ -219,7 +219,7 @@ const RAsmPlugin r_asm_plugin_r2hermes = {
.name = "hbc",
.desc = "Hermes bytecode pseudo syntax",
.author = "pancake",
.license = "LGPL-3.0-only",
.license = "BSD-3-Clause",
},
.parse = parse,
};
+582 -19
View File
@@ -1,7 +1,9 @@
/* radare2 - LGPL - Copyright 2025-2026 - pancake */
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <r_bin.h>
#include <r_bin_dwarf.h>
#include <hbc/hbc.h>
#include <hbc/literals.h>
#include "utils.inc.c"
#define HEADER_MAGIC 0x1f1903c103bc1fc6ULL
@@ -131,7 +133,7 @@ static ut64 resolve_entrypoint(RBinFile *bf, HBC *hbc) {
return 0;
}
static void fill_info(RBinInfo *ret, const char *file_path, bool has_version, ut32 version) {
static void fill_info(RBinInfo *ret, const char *file_path, bool has_version, ut32 version, bool has_source_lines) {
ret->file = file_path? strdup (file_path): NULL;
ret->bclass = strdup ("Hermes bytecode");
ret->rclass = strdup ("hermes");
@@ -140,13 +142,16 @@ static void fill_info(RBinInfo *ret, const char *file_path, bool has_version, ut
ret->bits = 32;
ret->type = strdup ("Hermes bytecode");
ret->machine = strdup ("Hermes VM");
ret->cpu = has_version? r_str_newf ("%u", version): strdup ("unknown");
ret->cpu = has_version? r_str_newf ("v%u", version): strdup ("unknown");
ret->has_va = true;
ret->dbg_info = has_source_lines? R_BIN_DBG_LINENUMS: R_BIN_DBG_STRIPPED;
}
static RBinInfo *bininfo(RBinFile *bf) {
RBinInfo *ret = R_NEW0 (RBinInfo);
bool has_version = false;
ut32 version = 0;
bool has_source_lines = false;
HBC *hbc = get_hbc (bf);
if (hbc) {
@@ -155,6 +160,10 @@ static RBinInfo *bininfo(RBinFile *bf) {
has_version = true;
version = hh.version;
}
/* Cheap: answered from the DebugInfoHeader parsed at load time.
* Reflects file truth (does this file carry source lines?) regardless
* of bin.dbginfo — that setting only gates whether we decode them. */
has_source_lines = hbc_has_source_lines (hbc);
}
/* Fallback: parse version directly from buffer */
@@ -167,10 +176,30 @@ static RBinInfo *bininfo(RBinFile *bf) {
}
}
fill_info (ret, bf->file, has_version, version);
fill_info (ret, bf->file, has_version, version, has_source_lines);
return ret;
}
/* Helper: append a section if the pool is present. Owns no strings — `name`
* is duplicated into a heap-allocated section name via strdup. */
static void add_pool_section(RList *list, const char *name, ut64 paddr, ut64 size) {
if (!size || !paddr) {
return;
}
RBinSection *s = R_NEW0 (RBinSection);
if (!s) {
return;
}
s->name = strdup (name);
s->size = size;
s->vsize = size;
s->paddr = paddr;
s->vaddr = HBC_VADDR_BASE + paddr;
s->perm = R_PERM_R;
s->add = true;
r_list_append (list, s);
}
static RList *sections(RBinFile *bf) {
RList *sections = r_list_newf ((RListFree)free);
if (!sections) {
@@ -183,9 +212,19 @@ static RList *sections(RBinFile *bf) {
section->vsize = section->size;
section->paddr = 0;
section->vaddr = HBC_VADDR_BASE;
section->perm = R_PERM_R;
section->perm = R_PERM_RX;
section->add = true;
r_list_append (sections, section);
/* SLP pool subsections — gives `iS` a quick map of where the buffer
* literals live, and lets users `s` / `pd @` straight into them. */
HBC *hbc = get_hbc (bf);
if (hbc) {
add_pool_section (sections, "slp.arrays", hbc_get_pool_paddr (hbc, HBC_LIT_ARRAY), hbc_get_pool_size (hbc, HBC_LIT_ARRAY));
add_pool_section (sections, "slp.object_keys", hbc_get_pool_paddr (hbc, HBC_LIT_OBJECT), hbc_get_pool_size (hbc, HBC_LIT_OBJECT));
add_pool_section (sections, "slp.object_values", hbc_get_object_values_paddr (hbc), hbc_get_object_values_size (hbc));
}
return sections;
}
@@ -210,6 +249,33 @@ static ut64 baddr(RBinFile *bf R_UNUSED) {
return HBC_VADDR_BASE;
}
static void append_binding_symbols(RList *symbols, HBC *hbc) {
HBCBindings bindings = { 0 };
if (hbc_scan_bindings (hbc, &bindings).code != RESULT_SUCCESS) {
return;
}
for (u32 i = 0; i < bindings.count; i++) {
HBCBinding *b = &bindings.bindings[i];
if (b->type != HBC_BINDING_EXPORT) {
continue;
}
RBinSymbol *sym = R_NEW0 (RBinSymbol);
char *nm = r_str_newf ("hbc.export.%s.%s", b->kind? b->kind: "js", b->name);
sym->name = r_bin_name_new (nm);
r_bin_name_filtered (sym->name, nm);
sym->paddr = b->offset;
sym->vaddr = HBC_VADDR_BASE + b->offset;
sym->size = 1;
sym->ordinal = b->string_id;
sym->type = R_BIN_TYPE_OBJECT_STR;
sym->bind = R_BIN_BIND_GLOBAL_STR;
sym->bits = 32;
r_list_append (symbols, sym);
free (nm);
}
hbc_free_bindings (&bindings);
}
static RList *symbols(RBinFile *bf) {
RList *symbols = r_list_newf ((RListFree)r_bin_symbol_free);
HBC *hbc = get_hbc (bf);
@@ -263,10 +329,149 @@ static RList *symbols(RBinFile *bf) {
free (san);
}
/* SLP pool groups as symbols — one per group discovered by the
* pool-side linear scan. Lets users see `lit.arr.* / lit.obj.*` in `is`
* without needing to run r2hermes-Lp. The scan is linear in pool size and
* runs at RBin load time. */
for (int k = 0; k < 2; k++) {
HBCLiteralKind kind = k? HBC_LIT_OBJECT: HBC_LIT_ARRAY;
const char *prefix = k? "lit.obj.": "lit.arr.";
HBCPoolGroup *groups = NULL;
u32 n = 0;
if (hbc_literals_scan_pool (hbc, kind, &groups, &n).code != RESULT_SUCCESS || !groups) {
continue;
}
for (u32 i = 0; i < n; i++) {
RBinSymbol *sym = R_NEW0 (RBinSymbol);
char *nm = r_str_newf ("%s0x%x", prefix, groups[i].paddr);
sym->name = r_bin_name_new (nm);
r_bin_name_filtered (sym->name, nm);
sym->paddr = groups[i].paddr;
sym->vaddr = HBC_VADDR_BASE + groups[i].paddr;
sym->size = 1;
sym->type = R_BIN_TYPE_OBJECT_STR;
sym->bits = 32;
r_list_append (symbols, sym);
free (nm);
}
free (groups);
}
append_binding_symbols (symbols, hbc);
return symbols;
}
static RList *strings(RBinFile *bf) {
static RList *imports(RBinFile *bf) {
RList *imports = r_list_newf ((RListFree)r_bin_import_free);
HBC *hbc = get_hbc (bf);
if (!imports || !hbc) {
return imports;
}
HBCBindings bindings = { 0 };
if (hbc_scan_bindings (hbc, &bindings).code != RESULT_SUCCESS) {
return imports;
}
for (u32 i = 0; i < bindings.count; i++) {
HBCBinding *b = &bindings.bindings[i];
if (b->type != HBC_BINDING_IMPORT) {
continue;
}
RBinImport *imp = R_NEW0 (RBinImport);
char *nm = r_str_newf ("%s.%s", b->kind? b->kind: "js", b->name);
imp->name = r_bin_name_new (nm);
r_bin_name_filtered (imp->name, nm);
imp->libname = strdup (b->module? b->module: "hermes");
imp->bind = "NONE";
imp->type = "FUNC";
imp->ordinal = b->string_id;
imp->is_imported = true;
r_list_append (imports, imp);
free (nm);
}
hbc_free_bindings (&bindings);
return imports;
}
static bool list_has_string(RList *list, const char *name) {
RListIter *iter;
const char *it;
r_list_foreach (list, iter, it) {
if (it && name && !strcmp (it, name)) {
return true;
}
}
return false;
}
static RList *libs(RBinFile *bf) {
RList *libs = r_list_newf (free);
HBC *hbc = get_hbc (bf);
if (!libs || !hbc) {
return libs;
}
HBCModules modules = { 0 };
if (hbc_list_modules (hbc, &modules).code != RESULT_SUCCESS) {
return libs;
}
for (u32 i = 0; i < modules.count; i++) {
HBCModule *m = &modules.modules[i];
if (m->name && *m->name && !list_has_string (libs, m->name)) {
r_list_append (libs, strdup (m->name));
}
}
hbc_free_modules (&modules);
return libs;
}
static R_UNOWNED RList *lines(RBinFile *bf) {
HBC *hbc = get_hbc (bf);
if (!hbc) {
return NULL;
}
/* Respect bin.dbginfo=false — return early so the lazy debug-info parse
*(triggered by hbc_get_source_lines) never runs for users who opted out. */
if (bf->rbin && !bf->rbin->want_dbginfo) {
return NULL;
}
HBCSourceLineArray lines = { 0 };
RList *ret = NULL;
if (hbc_get_source_lines (hbc, &lines).code != RESULT_SUCCESS || !lines.count) {
goto done;
}
ret = r_list_newf (free);
if (!ret) {
goto done;
}
const bool has_al = bf->addrline.al_add && bf->addrline.al_get;
for (u32 i = 0; i < lines.count; i++) {
HBCSourceLine *sl = &lines.lines[i];
ut64 addr = HBC_VADDR_BASE + sl->address;
const char *file = sl->filename? sl->filename: "";
if (has_al) {
bf->addrline.al_add (&bf->addrline, addr, file, NULL, sl->line, sl->column);
}
RBinAddrline *row = R_NEW0 (RBinAddrline);
if (!row) {
continue;
}
const RBinAddrline *stored = has_al? bf->addrline.al_get (&bf->addrline, addr): NULL;
if (stored) {
*row = *stored;
} else {
row->addr = addr;
row->file = UT32_MAX;
row->path = UT32_MAX;
row->line = sl->line;
row->column = sl->column;
}
r_list_append (ret, row);
}
done:
hbc_free_source_lines (&lines);
return ret;
}
static RList *strings_list(RBinFile *bf) {
RList *ret = r_list_newf ((RListFree)r_bin_string_free);
HBC *hbc = get_hbc (bf);
if (!hbc) {
@@ -285,21 +490,18 @@ static RList *strings(RBinFile *bf) {
if (hbc_get_string_meta (hbc, i, &meta).code != RESULT_SUCCESS) {
continue;
}
if (meta.length == 0) {
const size_t str_len = strlen (str);
if (str_len == 0) {
continue;
}
RBinString *ptr = R_NEW0 (RBinString);
if (meta.length >= R_BIN_SIZEOF_STRINGS) {
ptr->string = r_str_ndup (str, R_BIN_SIZEOF_STRINGS - 4);
if (ptr->string) {
char *tmp = r_str_newf ("%s...", ptr->string);
free (ptr->string);
ptr->string = tmp;
}
if (str_len >= R_BIN_SIZEOF_STRINGS) {
char *trunc = r_str_ndup (str, R_BIN_SIZEOF_STRINGS - 4);
ptr->string = r_str_newf ("%s...", trunc? trunc: "");
free (trunc);
} else {
ptr->string = r_str_ndup (str, meta.length);
ptr->string = r_str_ndup (str, str_len);
}
if (!ptr->string) {
free (ptr);
@@ -307,7 +509,7 @@ static RList *strings(RBinFile *bf) {
}
ptr->paddr = meta.offset;
ptr->vaddr = meta.offset;
ptr->vaddr = HBC_VADDR_BASE + meta.offset;
ptr->size = meta.isUTF16? (meta.length * 2): meta.length;
ptr->length = meta.length;
ptr->ordinal = i;
@@ -317,6 +519,355 @@ static RList *strings(RBinFile *bf) {
return ret;
}
#if R2_ABIVERSION >= 104
static bool sections_vec(RBinFile *bf) {
if (!bf || !bf->bo) {
return false;
}
if (!RVecRBinSection_empty (&bf->bo->sections_vec)) {
return true;
}
RList *list = sections (bf);
if (!list) {
return false;
}
bool ok = true;
RListIter *iter;
RBinSection *section;
r_list_foreach (list, iter, section) {
RBinSection *dst = RVecRBinSection_emplace_back (&bf->bo->sections_vec);
if (!dst) {
ok = false;
break;
}
*dst = *section;
memset (section, 0, sizeof (*section));
}
if (!ok) {
RVecRBinSection_clear (&bf->bo->sections_vec);
}
list->free = (RListFree)r_bin_section_free;
r_list_free (list);
return ok;
}
static bool symbols_vec(RBinFile *bf) {
if (!bf || !bf->bo) {
return false;
}
if (!RVecRBinSymbol_empty (&bf->bo->symbols_vec)) {
return true;
}
RList *list = symbols (bf);
if (!list) {
return false;
}
bool ok = true;
RListIter *iter;
RBinSymbol *symbol;
r_list_foreach (list, iter, symbol) {
RBinSymbol *dst = RVecRBinSymbol_emplace_back (&bf->bo->symbols_vec);
if (!dst) {
ok = false;
break;
}
*dst = *symbol;
memset (symbol, 0, sizeof (*symbol));
}
if (!ok) {
RVecRBinSymbol_clear (&bf->bo->symbols_vec);
}
r_list_free (list);
return ok;
}
static bool imports_vec(RBinFile *bf) {
if (!bf || !bf->bo) {
return false;
}
if (!RVecRBinImport_empty (&bf->bo->imports_vec)) {
return true;
}
RList *list = imports (bf);
if (!list) {
return false;
}
bool ok = true;
RListIter *iter;
RBinImport *imp;
r_list_foreach (list, iter, imp) {
RBinImport *dst = RVecRBinImport_emplace_back (&bf->bo->imports_vec);
if (!dst) {
ok = false;
break;
}
*dst = *imp;
memset (imp, 0, sizeof (*imp));
}
if (!ok) {
RVecRBinImport_clear (&bf->bo->imports_vec);
}
r_list_free (list);
return ok;
}
static RVecRBinString *strings_vec(RBinFile *bf) {
RList *list = strings_list (bf);
if (!list) {
return NULL;
}
RVecRBinString *ret = RVecRBinString_new ();
if (!ret) {
r_list_free (list);
return NULL;
}
bool ok = true;
RListIter *iter;
RBinString *str;
r_list_foreach (list, iter, str) {
RBinString *dst = RVecRBinString_emplace_back (ret);
if (!dst) {
ok = false;
break;
}
*dst = *str;
memset (str, 0, sizeof (*str));
}
r_list_free (list);
if (!ok) {
RVecRBinString_free (ret);
return NULL;
}
return ret;
}
#endif
static void hbc_source_hash_hex(const HBCHeader *hh, char *dst, size_t dstsz) {
if (!dst || dstsz < 41) {
return;
}
r_hex_bin2str (hh->sourceHash, 20, dst);
}
static void hbc_header_print_u32(RStrBuf *sb, ut64 off, const char *name, ut32 value) {
r_strbuf_appendf (sb, "0x%08" PFMT64x " %-29s 0x%08x\n", off, name, value);
}
static void hbc_header_print_bool(RStrBuf *sb, ut64 off, const char *name, bool value) {
r_strbuf_appendf (sb, "0x%08" PFMT64x " %-29s %s\n", off, name, value? "true": "false");
}
static char *header(RBinFile *bf, int mode) {
HBC *hbc = get_hbc (bf);
if (!hbc) {
return NULL;
}
HBCHeader hh;
if (hbc_get_header (hbc, &hh).code != RESULT_SUCCESS) {
return NULL;
}
char hash[41] = { 0 };
hbc_source_hash_hex (&hh, hash, sizeof (hash));
RStrBuf *sb = r_strbuf_new ("");
if (!sb) {
return NULL;
}
if (mode == R_MODE_EQUAL) {
r_strbuf_appendf (sb, "magic=0x%016" PFMT64x "\n", hh.magic);
r_strbuf_appendf (sb, "version=%u\n", hh.version);
r_strbuf_appendf (sb, "sourceHash=%s\n", hash);
r_strbuf_appendf (sb, "fileLength=%u\n", hh.fileLength);
r_strbuf_appendf (sb, "globalCodeIndex=%u\n", hh.globalCodeIndex);
r_strbuf_appendf (sb, "functionCount=%u\n", hh.functionCount);
r_strbuf_appendf (sb, "stringKindCount=%u\n", hh.stringKindCount);
r_strbuf_appendf (sb, "identifierCount=%u\n", hh.identifierCount);
r_strbuf_appendf (sb, "stringCount=%u\n", hh.stringCount);
r_strbuf_appendf (sb, "overflowStringCount=%u\n", hh.overflowStringCount);
r_strbuf_appendf (sb, "stringStorageSize=%u\n", hh.stringStorageSize);
r_strbuf_appendf (sb, "bigIntCount=%u\n", hh.bigIntCount);
r_strbuf_appendf (sb, "bigIntStorageSize=%u\n", hh.bigIntStorageSize);
r_strbuf_appendf (sb, "regExpCount=%u\n", hh.regExpCount);
r_strbuf_appendf (sb, "regExpStorageSize=%u\n", hh.regExpStorageSize);
r_strbuf_appendf (sb, "literalValueBufferSize=%u\n", hh.literalValueBufferSize);
r_strbuf_appendf (sb, "arrayBufferSize=%u\n", hh.arrayBufferSize);
r_strbuf_appendf (sb, "objKeyBufferSize=%u\n", hh.objKeyBufferSize);
r_strbuf_appendf (sb, "objValueBufferSize=%u\n", hh.objValueBufferSize);
r_strbuf_appendf (sb, "objShapeTableCount=%u\n", hh.objShapeTableCount);
r_strbuf_appendf (sb, "numStringSwitchImms=%u\n", hh.numStringSwitchImms);
r_strbuf_appendf (sb, "segmentID=%u\n", hh.segmentID);
r_strbuf_appendf (sb, "cjsModuleCount=%u\n", hh.cjsModuleCount);
r_strbuf_appendf (sb, "functionSourceCount=%u\n", hh.functionSourceCount);
r_strbuf_appendf (sb, "debugInfoOffset=%u\n", hh.debugInfoOffset);
r_strbuf_appendf (sb, "staticBuiltins=%s\n", hh.staticBuiltins? "true": "false");
r_strbuf_appendf (sb, "cjsModulesStaticallyResolved=%s\n", hh.cjsModulesStaticallyResolved? "true": "false");
r_strbuf_appendf (sb, "hasAsync=%s\n", hh.hasAsync? "true": "false");
return r_strbuf_drain (sb);
}
if (mode == R_MODE_JSON) {
PJ *pj = pj_new ();
if (!pj) {
r_strbuf_free (sb);
return NULL;
}
pj_o (pj);
pj_kn (pj, "magic", hh.magic);
pj_kn (pj, "version", hh.version);
pj_ks (pj, "sourceHash", hash);
pj_kn (pj, "fileLength", hh.fileLength);
pj_kn (pj, "globalCodeIndex", hh.globalCodeIndex);
pj_kn (pj, "functionCount", hh.functionCount);
pj_kn (pj, "stringKindCount", hh.stringKindCount);
pj_kn (pj, "identifierCount", hh.identifierCount);
pj_kn (pj, "stringCount", hh.stringCount);
pj_kn (pj, "overflowStringCount", hh.overflowStringCount);
pj_kn (pj, "stringStorageSize", hh.stringStorageSize);
pj_kn (pj, "bigIntCount", hh.bigIntCount);
pj_kn (pj, "bigIntStorageSize", hh.bigIntStorageSize);
pj_kn (pj, "regExpCount", hh.regExpCount);
pj_kn (pj, "regExpStorageSize", hh.regExpStorageSize);
pj_kn (pj, "literalValueBufferSize", hh.literalValueBufferSize);
pj_kn (pj, "arrayBufferSize", hh.arrayBufferSize);
pj_kn (pj, "objKeyBufferSize", hh.objKeyBufferSize);
pj_kn (pj, "objValueBufferSize", hh.objValueBufferSize);
pj_kn (pj, "objShapeTableCount", hh.objShapeTableCount);
pj_kn (pj, "numStringSwitchImms", hh.numStringSwitchImms);
pj_kn (pj, "segmentID", hh.segmentID);
pj_kn (pj, "cjsModuleCount", hh.cjsModuleCount);
pj_kn (pj, "functionSourceCount", hh.functionSourceCount);
pj_kn (pj, "debugInfoOffset", hh.debugInfoOffset);
pj_kb (pj, "staticBuiltins", hh.staticBuiltins);
pj_kb (pj, "cjsModulesStaticallyResolved", hh.cjsModulesStaticallyResolved);
pj_kb (pj, "hasAsync", hh.hasAsync);
pj_end (pj);
r_strbuf_free (sb);
return pj_drain (pj);
}
ut64 off = 0;
r_strbuf_append (sb, "Hermes bytecode header:\n");
r_strbuf_appendf (sb, "0x%08" PFMT64x " %-29s 0x%016" PFMT64x "\n", off, "magic", hh.magic);
off += 8;
hbc_header_print_u32 (sb, off, "version", hh.version);
off += 4;
r_strbuf_appendf (sb, "0x%08" PFMT64x " %-29s %s\n", off, "sourceHash", hash);
off += 20;
hbc_header_print_u32 (sb, off, "fileLength", hh.fileLength);
off += 4;
hbc_header_print_u32 (sb, off, "globalCodeIndex", hh.globalCodeIndex);
off += 4;
hbc_header_print_u32 (sb, off, "functionCount", hh.functionCount);
off += 4;
hbc_header_print_u32 (sb, off, "stringKindCount", hh.stringKindCount);
off += 4;
hbc_header_print_u32 (sb, off, "identifierCount", hh.identifierCount);
off += 4;
hbc_header_print_u32 (sb, off, "stringCount", hh.stringCount);
off += 4;
hbc_header_print_u32 (sb, off, "overflowStringCount", hh.overflowStringCount);
off += 4;
hbc_header_print_u32 (sb, off, "stringStorageSize", hh.stringStorageSize);
off += 4;
if (hh.version >= 87) {
hbc_header_print_u32 (sb, off, "bigIntCount", hh.bigIntCount);
off += 4;
hbc_header_print_u32 (sb, off, "bigIntStorageSize", hh.bigIntStorageSize);
off += 4;
}
hbc_header_print_u32 (sb, off, "regExpCount", hh.regExpCount);
off += 4;
hbc_header_print_u32 (sb, off, "regExpStorageSize", hh.regExpStorageSize);
off += 4;
hbc_header_print_u32 (sb, off, "literalValueBufferSize", hh.literalValueBufferSize);
off += 4;
hbc_header_print_u32 (sb, off, "objKeyBufferSize", hh.objKeyBufferSize);
off += 4;
if (hh.version >= 97) {
hbc_header_print_u32 (sb, off, "objShapeTableCount", hh.objShapeTableCount);
off += 4;
} else {
hbc_header_print_u32 (sb, off, "objValueBufferSize", hh.objValueBufferSize);
off += 4;
}
if (hh.version >= 99) {
hbc_header_print_u32 (sb, off, "numStringSwitchImms", hh.numStringSwitchImms);
off += 4;
}
hbc_header_print_u32 (sb, off, "segmentID", hh.segmentID);
off += 4;
hbc_header_print_u32 (sb, off, "cjsModuleCount", hh.cjsModuleCount);
off += 4;
if (hh.version >= 84) {
hbc_header_print_u32 (sb, off, "functionSourceCount", hh.functionSourceCount);
off += 4;
}
hbc_header_print_u32 (sb, off, "debugInfoOffset", hh.debugInfoOffset);
off += 4;
hbc_header_print_bool (sb, off, "staticBuiltins", hh.staticBuiltins);
hbc_header_print_bool (sb, off, "cjsModulesStaticallyResolved", hh.cjsModulesStaticallyResolved);
hbc_header_print_bool (sb, off, "hasAsync", hh.hasAsync);
return r_strbuf_drain (sb);
}
static void hbc_fields_append_u32(RList *ret, ut64 *off, const char *name, ut32 value) {
r_list_append (ret, r_bin_field_new (*off, *off, value, 4, name, NULL, "x", false));
*off += 4;
}
static RList *fields(RBinFile *bf) {
RList *ret = r_list_new ();
HBC *hbc = get_hbc (bf);
if (!ret || !hbc) {
return ret;
}
HBCHeader hh;
if (hbc_get_header (hbc, &hh).code != RESULT_SUCCESS) {
return ret;
}
char hash[41] = { 0 };
hbc_source_hash_hex (&hh, hash, sizeof (hash));
ut64 off = 0;
r_list_append (ret, r_bin_field_new (off, off, hh.magic, 8, "magic", NULL, "q", false));
off += 8;
hbc_fields_append_u32 (ret, &off, "version", hh.version);
r_list_append (ret, r_bin_field_new (off, off, 0, 20, "sourceHash", hash, "s", false));
off += 20;
hbc_fields_append_u32 (ret, &off, "fileLength", hh.fileLength);
hbc_fields_append_u32 (ret, &off, "globalCodeIndex", hh.globalCodeIndex);
hbc_fields_append_u32 (ret, &off, "functionCount", hh.functionCount);
hbc_fields_append_u32 (ret, &off, "stringKindCount", hh.stringKindCount);
hbc_fields_append_u32 (ret, &off, "identifierCount", hh.identifierCount);
hbc_fields_append_u32 (ret, &off, "stringCount", hh.stringCount);
hbc_fields_append_u32 (ret, &off, "overflowStringCount", hh.overflowStringCount);
hbc_fields_append_u32 (ret, &off, "stringStorageSize", hh.stringStorageSize);
if (hh.version >= 87) {
hbc_fields_append_u32 (ret, &off, "bigIntCount", hh.bigIntCount);
hbc_fields_append_u32 (ret, &off, "bigIntStorageSize", hh.bigIntStorageSize);
}
hbc_fields_append_u32 (ret, &off, "regExpCount", hh.regExpCount);
hbc_fields_append_u32 (ret, &off, "regExpStorageSize", hh.regExpStorageSize);
hbc_fields_append_u32 (ret, &off, "literalValueBufferSize", hh.literalValueBufferSize);
hbc_fields_append_u32 (ret, &off, "objKeyBufferSize", hh.objKeyBufferSize);
if (hh.version >= 97) {
hbc_fields_append_u32 (ret, &off, "objShapeTableCount", hh.objShapeTableCount);
} else {
hbc_fields_append_u32 (ret, &off, "objValueBufferSize", hh.objValueBufferSize);
}
if (hh.version >= 99) {
hbc_fields_append_u32 (ret, &off, "numStringSwitchImms", hh.numStringSwitchImms);
}
hbc_fields_append_u32 (ret, &off, "segmentID", hh.segmentID);
hbc_fields_append_u32 (ret, &off, "cjsModuleCount", hh.cjsModuleCount);
if (hh.version >= 84) {
hbc_fields_append_u32 (ret, &off, "functionSourceCount", hh.functionSourceCount);
}
hbc_fields_append_u32 (ret, &off, "debugInfoOffset", hh.debugInfoOffset);
ut32 flags = (hh.staticBuiltins? 1: 0) | (hh.cjsModulesStaticallyResolved? 2: 0) | (hh.hasAsync? 4: 0);
r_list_append (ret, r_bin_field_new (off, off, flags, 1, "flags", NULL, "b", false));
r_list_append (ret, r_bin_field_new (off, off, hh.staticBuiltins? 1: 0, 1, "staticBuiltins", NULL, "b", false));
r_list_append (ret, r_bin_field_new (off, off, hh.cjsModulesStaticallyResolved? 1: 0, 1, "cjsModulesStaticallyResolved", NULL, "b", false));
r_list_append (ret, r_bin_field_new (off, off, hh.hasAsync? 1: 0, 1, "hasAsync", NULL, "b", false));
return ret;
}
const RBinPlugin r_bin_plugin_r2hermes = {
.meta = {
.name = "hbc.bin",
@@ -329,10 +880,22 @@ const RBinPlugin r_bin_plugin_r2hermes = {
.destroy = &destroy,
.check = &check,
.entries = &entries,
#if R2_ABIVERSION >= 104
.sections_vec = &sections_vec,
.symbols_vec = &symbols_vec,
.imports_vec = &imports_vec,
.strings = &strings_vec,
#else
.sections = &sections,
.baddr = &baddr,
.symbols = &symbols,
.strings = &strings,
.imports = &imports,
.strings = &strings_list,
#endif
.baddr = &baddr,
.libs = &libs,
.lines = &lines,
.header = &header,
.fields = &fields,
};
#ifndef R2_PLUGIN_INCORE
+828 -105
View File
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -1,4 +1,4 @@
/* radare2 - LGPL - Copyright 2025-2026 - pancache */
/* radare2 - BSD - Copyright 2025-2026 - pancake */
/* Core HBC plugin with plugin registration enabled for r2one */
+272 -926
View File
File diff suppressed because it is too large. Load diff
+301
View File
@@ -0,0 +1,301 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
/* CycloneDX SBOM generation for Hermes bytecode.
*
* Strategy:
* - Force a code scan so the SLP literal cache is populated.
* - Walk every cached object literal; keep the ones whose formatted JS
* text mentions "typescript" — that catches the package.json-style
* dependency maps (dependencies / devDependencies / peerDependencies).
* - The literal formatter prints valid JS identifiers as bare keys
*({foo: "1.2.3"}), which is not strict JSON. We rewrite bare keys
* into quoted ones, then parse with r_json so we don't hand-roll a
* key/value parser.
* - For each name -> string-version pair, emit a CycloneDX component.
*
* Output formats:
* r2hermes-S plaintext (one "name version" per line)
* r2hermes-Sj CycloneDX JSON (built via PJ)
*/
#include <hbc/literals.h>
typedef struct {
char *name;
char *version;
} SbomComp;
typedef struct {
SbomComp *items;
size_t count;
size_t cap;
} SbomCompList;
static bool sbom_push(SbomCompList *list, const char *name, const char *version) {
if (!name || !version) {
return false;
}
if (list->count == list->cap) {
size_t ncap = list->cap? list->cap * 2: 16;
SbomComp *grown = realloc (list->items, ncap * sizeof (SbomComp));
if (!grown) {
return false;
}
list->items = grown;
list->cap = ncap;
}
list->items[list->count].name = strdup (name);
list->items[list->count].version = strdup (version);
if (!list->items[list->count].name || !list->items[list->count].version) {
free (list->items[list->count].name);
free (list->items[list->count].version);
return false;
}
list->count++;
return true;
}
static void sbom_free(SbomCompList *list) {
for (size_t i = 0; i < list->count; i++) {
free (list->items[i].name);
free (list->items[i].version);
}
free (list->items);
list->items = NULL;
list->count = 0;
list->cap = 0;
}
/* Quote bare identifier keys so the result is parsable as JSON.
* Skips over double-quoted strings (with escape handling) so we never
* touch content inside string literals. Bare 'true'/'false'/'null' are
* left alone since they may legitimately appear as values. */
static char *jsobj_to_json(const char *src) {
if (!src) {
return NULL;
}
RStrBuf *sb = r_strbuf_new ("");
if (!sb) {
return NULL;
}
const char *p = src;
while (*p) {
if (*p == '"') {
const char *start = p++;
while (*p && *p != '"') {
if (*p == '\\' && p[1]) {
p += 2;
} else {
p++;
}
}
if (*p == '"') {
p++;
}
r_strbuf_append_n (sb, start, (int) (p - start));
continue;
}
unsigned char c = (unsigned char)*p;
if (isalpha (c) || c == '_' || c == '$') {
const char *start = p;
while (*p && (isalnum ((unsigned char)*p) || *p == '_' || *p == '$')) {
p++;
}
int len = (int) (p - start);
const char *q = p;
while (*q == ' ' || *q == '\t' || *q == '\n' || *q == '\r') {
q++;
}
bool is_key = (*q == ':');
bool is_literal = (len == 4 && !memcmp (start, "true", 4)) || (len == 5 && !memcmp (start, "false", 5)) || (len == 4 && !memcmp (start, "null", 4));
if (is_key && !is_literal) {
r_strbuf_append (sb, "\"");
r_strbuf_append_n (sb, start, len);
r_strbuf_append (sb, "\"");
} else {
r_strbuf_append_n (sb, start, len);
}
continue;
}
char one[2] = { *p, 0 };
r_strbuf_append (sb, one);
p++;
}
return r_strbuf_drain (sb);
}
/* Extract every name -> string-version pair from one candidate literal. */
static void sbom_harvest(const char *formatted, SbomCompList *out) {
char *jsonstr = jsobj_to_json (formatted);
if (!jsonstr) {
return;
}
RJson *js = r_json_parsedup (jsonstr);
free (jsonstr);
if (!js) {
return;
}
if (js->type == R_JSON_OBJECT) {
for (RJson *kid = js->children.first; kid; kid = kid->next) {
if (kid->type == R_JSON_STRING && kid->key && kid->str_value) {
sbom_push (out, kid->key, kid->str_value);
}
}
}
r_json_free (js);
}
static int sbom_comp_cmp(const void *a, const void *b) {
const SbomComp *ca = a;
const SbomComp *cb = b;
int r = strcmp (ca->name, cb->name);
return r? r: strcmp (ca->version, cb->version);
}
/* Drop duplicate (name,version) pairs in-place. List must be sorted. */
static void sbom_dedup(SbomCompList *list) {
if (list->count < 2) {
return;
}
size_t w = 1;
for (size_t i = 1; i < list->count; i++) {
if (sbom_comp_cmp (&list->items[w - 1], &list->items[i]) == 0) {
free (list->items[i].name);
free (list->items[i].version);
} else {
list->items[w++] = list->items[i];
}
}
list->count = w;
}
static void sbom_emit_text(RCore *core, const SbomCompList *list) {
if (list->count == 0) {
r_cons_println (core->cons, "(no SBOM candidates: no SLP object literal mentions \"typescript\")");
return;
}
r_cons_printf (core->cons, "# SBOM (%zu components)\n", list->count);
for (size_t i = 0; i < list->count; i++) {
r_cons_printf (core->cons, "%s %s\n", list->items[i].name, list->items[i].version);
}
}
static void sbom_emit_json(RCore *core, const SbomCompList *list) {
PJ *pj = r_core_pj_new (core);
if (!pj) {
return;
}
pj_o (pj);
pj_ks (pj, "bomFormat", "CycloneDX");
pj_ks (pj, "specVersion", "1.5");
pj_ki (pj, "version", 1);
pj_ka (pj, "components");
for (size_t i = 0; i < list->count; i++) {
const SbomComp *c = &list->items[i];
pj_o (pj);
pj_ks (pj, "type", "library");
pj_ks (pj, "name", c->name);
pj_ks (pj, "version", c->version);
char *purl = r_str_newf ("pkg:npm/%s@%s", c->name, c->version);
if (purl) {
pj_ks (pj, "purl", purl);
free (purl);
}
pj_end (pj);
}
pj_end (pj);
pj_end (pj);
char *s = pj_drain (pj);
if (s) {
r_cons_println (core->cons, s);
free (s);
}
}
static const char SBOM_HELP[] =
"Usage: r2hermes-S[jr?]\n"
" r2hermes-S Emit SBOM as plaintext (name + version per line)\n"
" r2hermes-Sj Emit CycloneDX 1.5 SBOM as JSON\n"
" r2hermes-Sr Dump the raw matched SLP literal text (pre-JSON)\n"
" r2hermes-S? Show this help\n";
typedef enum {
SBOM_FMT_TEXT,
SBOM_FMT_JSON,
SBOM_FMT_RAW
} SbomFormat;
static void cmd_sbom(HbcContext *ctx, RCore *core, const char *arg) {
while (*arg && isspace ((unsigned char)*arg)) {
arg++;
}
if (*arg == '?') {
r_cons_print (core->cons, SBOM_HELP);
return;
}
SbomFormat fmt = SBOM_FMT_TEXT;
if (*arg == 'j') {
fmt = SBOM_FMT_JSON;
} else if (*arg == 'r') {
fmt = SBOM_FMT_RAW;
}
HBC *hbc = NULL;
Result r = ensure_hbc_loaded (ctx, core, &hbc);
if (r.code != RESULT_SUCCESS) {
R_LOG_ERROR ("%s", safe_errmsg (r.error_message));
return;
}
u32 nscanned = 0;
r = hbc_literals_scan_code (hbc, &nscanned);
if (r.code != RESULT_SUCCESS) {
R_LOG_ERROR ("literal scan failed: %s", safe_errmsg (r.error_message));
return;
}
const HBCLiteralEntry *arr = NULL;
u32 n = 0;
if (hbc_literals_list (hbc, &arr, &n).code != RESULT_SUCCESS || !arr) {
R_LOG_ERROR ("literal list unavailable");
return;
}
SbomCompList comps = { 0 };
u32 matched = 0;
for (u32 i = 0; i < n; i++) {
const HBCLiteralEntry *e = &arr[i];
if (e->kind != HBC_LIT_OBJECT || !e->formatted) {
continue;
}
if (!strstr (e->formatted, "typescript")) {
continue;
}
matched++;
if (fmt == SBOM_FMT_RAW) {
r_cons_printf (core->cons, "# paddr=0x%08x xrefs=%u\n%s\n", e->paddr, e->xref_count, e->formatted);
} else {
sbom_harvest (e->formatted, &comps);
}
}
if (fmt == SBOM_FMT_RAW) {
if (matched == 0) {
r_cons_println (core->cons, "(no SLP object literal mentions \"typescript\")");
}
return;
}
if (comps.count > 1) {
qsort (comps.items, comps.count, sizeof (SbomComp), sbom_comp_cmp);
sbom_dedup (&comps);
}
if (fmt == SBOM_FMT_JSON) {
sbom_emit_json (core, &comps);
} else {
sbom_emit_text (core, &comps);
}
sbom_free (&comps);
}
+1 -1
View File
@@ -1,4 +1,4 @@
/* radare2 - LGPL - Copyright 2025-2026 - pancake */
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#ifndef R2HERMES_UTILS_INC_C
#define R2HERMES_UTILS_INC_C
+664 -400
View File
File diff suppressed because it is too large. Load diff
+18 -2
View File
@@ -1,4 +1,4 @@
NAME=r2hermes test
NAME=r2hermes test (va)
FILE=bins/hbc/bespoke_eval.hbc
ARGS=-e asm.sub.varmin=16
CMDS=<<EOF
@@ -9,7 +9,23 @@ EXPECT=<<EOF
hbc
;-- entry0:
;-- global_0x000000b0:
0x000000b0 73000100 load_const_string r0, str.print_123__ ; 0xa2 ; "print(123);"
0x100000b0 73000100 load_const_string r0, 0xa2 ; yes.js:1:1 ; str.print_123__
; 0x100000a2 ; "print(123);"
0x100000b4 5e000000 direct_eval r0, r0, 0 ; yes.js:1:5
EOF
RUN
NAME=r2hermes test (no va)
FILE=bins/hbc/bespoke_eval.hbc
ARGS=-e asm.sub.varmin=16 -e io.va=false
CMDS=<<EOF
-a
pd 2 @ 0xb0
EOF
EXPECT=<<EOF
hbc
0x000000b0 73000100 load_const_string r0, 0xa2 ; str.print_123__
; 0x100000a2
0x000000b4 5e000000 direct_eval r0, r0, 0
EOF
RUN
+254 -17
View File
@@ -18,70 +18,84 @@ EXPECT=<<EOF
EOF
RUN
NAME=pd:hi file info small
NAME=r2hermes-? help eval command
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
pd:hi~Version
r2hermes-?~r2hermes-E
EOF
EXPECT=<<EOF
r2hermes-E - List functions containing eval instructions
EOF
RUN
NAME=r2hermes-H file info small
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
r2hermes-H~Version
EOF
EXPECT=<<EOF
Version: 96
EOF
RUN
NAME=pd:hi file length small
NAME=r2hermes-H file length small
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
pd:hi~File Length
r2hermes-H~File Length
EOF
EXPECT=<<EOF
File Length: 292 bytes
EOF
RUN
NAME=pd:hi function count small
NAME=r2hermes-H function count small
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
pd:hi~Functions
r2hermes-H~Functions
EOF
EXPECT=<<EOF
Functions: 1
Functions With Debug Info: 1
EOF
RUN
NAME=pd:hi string count small
NAME=r2hermes-H string count small
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
pd:hi~Strings
r2hermes-H~Strings
EOF
EXPECT=<<EOF
Strings: 2
Overflow Strings: 0
EOF
RUN
NAME=pd:hi file info large
NAME=r2hermes-H file info large
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
pd:hi~Functions
r2hermes-H~Functions
EOF
EXPECT=<<EOF
Functions: 5388
Functions With Debug Info: 0
EOF
RUN
NAME=pd:hi strings large
NAME=r2hermes-H strings large
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
pd:hi~Strings
r2hermes-H~Strings
EOF
EXPECT=<<EOF
Strings: 6170
Overflow Strings: 7
EOF
RUN
NAME=pd:hi identifiers large
NAME=r2hermes-H identifiers large
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
pd:hi~Identifiers
r2hermes-H~Identifiers
EOF
EXPECT=<<EOF
Identifiers: 3449
@@ -122,6 +136,26 @@ EXPECT=<<EOF
EOF
RUN
NAME=r2hermes-E list eval sites
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
r2hermes-E
EOF
EXPECT=<<EOF
0x100000b4 f=0 +0x4 func=0x100000b0 name=global
EOF
RUN
NAME=r2hermes-E list eval sites large
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
r2hermes-E
EOF
EXPECT=<<EOF
0x100d166b f=4953 +0x28 func=0x100d1643 name=onPress
EOF
RUN
NAME=pd:hc decompile function return
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
@@ -178,13 +212,25 @@ EXPECT=<<EOF
EOF
RUN
NAME=pd:hj json output
NAME=pd:hj json code field
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
pd:hj 0~function_id
pd:hj 0~{code}
EOF
EXPECT=<<EOF
{"function_id":0,"decompilation":"function global() {\n r0 = \"print(123);\";\n r0 = eval (r0);\n return r0;\n}\n\n\n"}
function global() {\n r0 = \"print(123);\";\n r0 = eval (r0);\n return r0;\n}\n\n\n
EOF
RUN
NAME=pd:hj json annotation offsets
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
pd:hj 0~{annotations[0].type}
pd:hj 0~{annotations[0].offset}
EOF
EXPECT=<<EOF
offset
176
EOF
RUN
@@ -198,3 +244,194 @@ EXPECT=<<EOF
r0 = eval (r0);
EOF
RUN
NAME=pd:hc for-in loop structuring
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
pd:hc 6~for (,continue
EOF
EXPECT=<<EOF
for (r6 in r5)
if (!r11) continue;
EOF
RUN
NAME=pd:hc for-in no back-edge goto or loop-top label
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
pd:hc 6~for (r6 in,goto loc_0003e68d,loc_0003e68d:
EOF
EXPECT=<<EOF
for (r6 in r5)
EOF
RUN
NAME=pd:hc switch fallthrough case grouping
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
pd:hc 562~case 26,case 27
EOF
EXPECT=<<EOF
case 26:
case 27: goto loc_0004d144;
EOF
RUN
NAME=pd:hc comparison condition goto-form no double parens
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
pd:hc 5~if (r3!==r1)
EOF
EXPECT=<<EOF
if (r3!==r1) goto loc_0003e637;
EOF
RUN
NAME=pd:hc comparison condition structured-if negated operator
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
pd:hc 46~if (r5!==r0)
EOF
EXPECT=<<EOF
if (r5!==r0) {
EOF
RUN
NAME=pd:hc if-blocks structured in catch-containing function
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
pd:hc 12~if (r7) {
EOF
EXPECT=<<EOF
if (r7) {
EOF
RUN
NAME=pd:hc structured try/catch for return-terminated try
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
pd:hc 189~try {,} catch (r0) {
EOF
EXPECT=<<EOF
try {
} catch (r0) {
EOF
RUN
NAME=pd:hc multiple independent try/catch blocks structured
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
pd:hc 344~} catch
EOF
EXPECT=<<EOF
} catch (r0) {
} catch (r1) {
} catch (r1) {
EOF
RUN
NAME=pd:hc do-while loop structuring with no back-edge goto or loop-top label
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
pd:hc 513~do {,while (r1,goto
EOF
EXPECT=<<EOF
do {
} while (r1>r2);
EOF
RUN
NAME=pd:hc materialized guard folds into a while loop
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
pd:hc 199~while (r2,do {,goto
EOF
EXPECT=<<EOF
while (r2<r1) {
EOF
RUN
NAME=pd:hc new Array call has no stray space after the parenthesis
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
pd:hc 513~new Array(0
EOF
EXPECT=<<EOF
r0 = new Array(0);
EOF
RUN
NAME=pd:hc select_object args have single spacing
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
pd:hc 310~select_object(r
EOF
EXPECT=<<EOF
r2 = select_object(r3, r2);
r1 = select_object(r2, r1);
EOF
RUN
NAME=pd:hc forward substitution folds single-use temp into call
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
pd:hc 199~.call (undefined
EOF
EXPECT=<<EOF
r0 = r2 .call (undefined, r1);
EOF
RUN
NAME=pd:hc if-else reconstruction with no trailing goto or join label
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
pd:hc 311~if (!r0) {,} else {,goto,loc_
EOF
EXPECT=<<EOF
if (!r0) {
} else {
EOF
RUN
NAME=pd:hc short-circuit OR merges consecutive same-target conditional gotos
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
pd:hc 730~ || r2===r20 ||
EOF
EXPECT=<<EOF
if (r1===r20 || r2===r20 || r3===r20 || r4===r20) goto loc_00055f28;
EOF
RUN
NAME=pd:hc while loop folds entry guard and do-while into a while header
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
pd:hc 2303~while (r2,do {,goto
EOF
EXPECT=<<EOF
while (r2!=r1) {
EOF
RUN
NAME=pd:hc infinite loop structures as for(;;) with continue and break
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
pd:hc 3896~for (,continue,break
EOF
EXPECT=<<EOF
for (;;) {
continue;
break;
EOF
RUN
NAME=pd:hc break reconstruction renders loop-exit goto as break
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
pd:hc 3406~while (r0,break,goto loc
EOF
EXPECT=<<EOF
while (r0<r2) {
if (r6>=r5) break;
EOF
RUN
+616 -30
View File
@@ -1,4 +1,4 @@
NAME=hbc disasm basic instructions
NAME=hbc disasm basic instructions (va)
FILE=bins/hbc/bespoke_eval.hbc
ARGS=-e asm.sub.varmin=16
CMDS=<<EOF
@@ -7,7 +7,22 @@ EOF
EXPECT=<<EOF
;-- entry0:
;-- global_0x000000b0:
0x000000b0 73000100 load_const_string r0, str.print_123__ ; 0xa2 ; "print(123);"
0x100000b0 73000100 load_const_string r0, 0xa2 ; yes.js:1:1 ; str.print_123__
; 0x100000a2 ; "print(123);"
0x100000b4 5e000000 direct_eval r0, r0, 0 ; yes.js:1:5
0x100000b8 5c00 ret r0
EOF
RUN
NAME=hbc disasm basic instructions (no va)
FILE=bins/hbc/bespoke_eval.hbc
ARGS=-e asm.sub.varmin=16 -e io.va=false
CMDS=<<EOF
pd 3 @ 0xb0
EOF
EXPECT=<<EOF
0x000000b0 73000100 load_const_string r0, 0xa2 ; str.print_123__
; 0x100000a2
0x000000b4 5e000000 direct_eval r0, r0, 0
0x000000b8 5c00 ret r0
EOF
@@ -43,36 +58,70 @@ CMDS=<<EOF
i~cpu
EOF
EXPECT=<<EOF
cpu 96
cpu v96
EOF
RUN
NAME=hbc symbols list
NAME=hbc symbols list (va)
FILE=bins/hbc/bespoke_eval.hbc
ARGS=-e asm.sub.varmin=16
CMDS=<<EOF
is~FUNC
EOF
EXPECT=<<EOF
0 0x000000b0 0x100000b0 NONE FUNC 10 global_0x000000b0
EOF
RUN
NAME=hbc symbols list (no va)
FILE=bins/hbc/bespoke_eval.hbc
ARGS=-e asm.sub.varmin=16 -e io.va=false
CMDS=<<EOF
is~FUNC
EOF
EXPECT=<<EOF
0 0x000000b0 0x000000b0 NONE FUNC 10 global_0x000000b0
EOF
RUN
NAME=hbc entry point
NAME=hbc entry point (va)
FILE=bins/hbc/bespoke_eval.hbc
ARGS=-e asm.sub.varmin=16
CMDS=<<EOF
ie~program
EOF
EXPECT=<<EOF
0x000000b0 0x100000b0 ---------- ---------- program
EOF
RUN
NAME=hbc entry point (no va)
FILE=bins/hbc/bespoke_eval.hbc
ARGS=-e asm.sub.varmin=16 -e io.va=false
CMDS=<<EOF
ie~program
EOF
EXPECT=<<EOF
0x000000b0 0x000000b0 ---------- ---------- program
EOF
RUN
NAME=hbc disasm ret instruction
NAME=hbc disasm ret instruction (va)
FILE=bins/hbc/bespoke_eval.hbc
ARGS=-e asm.sub.varmin=16
CMDS=<<EOF
s 0x100000b8
pd 1
EOF
EXPECT=<<EOF
0x100000b8 5c00 ret r0
EOF
RUN
NAME=hbc disasm ret instruction (no va)
FILE=bins/hbc/bespoke_eval.hbc
ARGS=-e asm.sub.varmin=16 -e io.va=false
CMDS=<<EOF
s 0xb8
pd 1
EOF
@@ -81,24 +130,50 @@ EXPECT=<<EOF
EOF
RUN
NAME=hbc disasm load_const_string
NAME=hbc disasm load_const_string (va)
ARGS=-e asm.sub.varmin=16
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
s 0x100000b0
pd 1
EOF
EXPECT=<<EOF
;-- entry0:
;-- global_0x000000b0:
0x100000b0 73000100 load_const_string r0, 0xa2 ; yes.js:1:1 ; str.print_123__
; 0x100000a2 ; "print(123);"
EOF
RUN
NAME=hbc disasm load_const_string (no va)
ARGS=-e asm.sub.varmin=16 -e io.va=false
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
s 0xb0
pd 1
EOF
EXPECT=<<EOF
;-- entry0:
;-- global_0x000000b0:
0x000000b0 73000100 load_const_string r0, str.print_123__ ; 0xa2 ; "print(123);"
0x000000b0 73000100 load_const_string r0, 0xa2 ; str.print_123__
; 0x100000a2
EOF
RUN
NAME=hbc disasm direct_eval
NAME=hbc disasm direct_eval (va)
FILE=bins/hbc/bespoke_eval.hbc
ARGS=-e asm.sub.varmin=16
CMDS=<<EOF
s 0x100000b4
pd 1
EOF
EXPECT=<<EOF
0x100000b4 5e000000 direct_eval r0, r0, 0 ; yes.js:1:5
EOF
RUN
NAME=hbc disasm direct_eval (no va)
FILE=bins/hbc/bespoke_eval.hbc
ARGS=-e asm.sub.varmin=16 -e io.va=false
CMDS=<<EOF
s 0xb4
pd 1
EOF
@@ -107,18 +182,29 @@ EXPECT=<<EOF
EOF
RUN
NAME=hbc sections
NAME=hbc sections (va)
FILE=bins/hbc/bespoke_eval.hbc
ARGS=-e asm.sub.varmin=16
CMDS=<<EOF
iS~hermes
EOF
EXPECT=<<EOF
0 0x00000000 0x124 0x00000000 0x124 -r-- 0x0 ---- hermes_bytecode
0 0x00000000 0x124 0x10000000 0x124 -r-x 0x0 ---- hermes_bytecode
EOF
RUN
NAME=hbc seek and disasm
NAME=hbc sections (no va)
FILE=bins/hbc/bespoke_eval.hbc
ARGS=-e asm.sub.varmin=16 -e io.va=false
CMDS=<<EOF
iS~hermes
EOF
EXPECT=<<EOF
0 0x00000000 0x124 0x00000000 0x124 -r-x 0x0 ---- hermes_bytecode
EOF
RUN
NAME=hbc seek and disasm (va)
FILE=bins/hbc/bespoke_eval.hbc
ARGS=-e asm.sub.varmin=16
CMDS=<<EOF
@@ -128,16 +214,32 @@ EOF
EXPECT=<<EOF
;-- entry0:
;-- global_0x000000b0:
0x000000b0 73000100 load_const_string r0, str.print_123__ ; 0xa2 ; "print(123);"
0x000000b4 5e000000 direct_eval r0, r0, 0
0x100000b0 73000100 load_const_string r0, 0xa2 ; yes.js:1:1 ; str.print_123__
; 0x100000a2 ; "print(123);"
0x100000b4 5e000000 direct_eval r0, r0, 0 ; yes.js:1:5
EOF
RUN
NAME=hbc print hex bytes
NAME=hbc print hex bytes (va)
FILE=bins/hbc/bespoke_eval.hbc
ARGS=-e asm.sub.varmin=16
CMDS=<<EOF
s 0x100000b0
-e hex.header=true
px 10
EOF
EXPECT=<<EOF
- offset - B0B1 B2B3 B4B5 B6B7 B8B9 BABB BCBD BEBF 0123456789ABCDEF
0x100000b0 7300 0100 5e00 0000 5c00 s...^...\.
EOF
RUN
NAME=hbc print hex bytes (no va)
FILE=bins/hbc/bespoke_eval.hbc
ARGS=-e asm.sub.varmin=16 -e io.va=false
CMDS=<<EOF
s 0xb0
-e hex.header=true
px 10
EOF
EXPECT=<<EOF
@@ -146,10 +248,23 @@ EXPECT=<<EOF
EOF
RUN
NAME=hbc unreachable padding
NAME=hbc unreachable padding (va)
FILE=bins/hbc/bespoke_eval.hbc
ARGS=-e asm.sub.varmin=16
CMDS=<<EOF
s 0x100000ba
pd 2
EOF
EXPECT=<<EOF
0x100000ba 00 unreachable
0x100000bb 00 unreachable
EOF
RUN
NAME=hbc unreachable padding (no va)
FILE=bins/hbc/bespoke_eval.hbc
ARGS=-e asm.sub.varmin=16 -e io.va=false
CMDS=<<EOF
s 0xba
pd 2
EOF
@@ -169,21 +284,82 @@ EXPECT=<<EOF
EOF
RUN
NAME=hbc get_global_object instruction
NAME=hbc imports js require
FILE=bins/hbc/index.android.bundle
ARGS=-e asm.sub.varmin=16
CMDS=<<EOF
ii~js.__r
ii~?
EOF
EXPECT=<<EOF
3048 0x00000000 NONE FUNC react-native js.__r
58
EOF
RUN
NAME=hbc imports native module
FILE=bins/hbc/index.android.bundle
ARGS=-e asm.sub.varmin=16
CMDS=<<EOF
ii~native.NativeModules
EOF
EXPECT=<<EOF
3894 0x00000000 NONE FUNC react-native native.NativeModules
EOF
RUN
NAME=hbc exports module symbol
FILE=bins/hbc/index.android.bundle
ARGS=-e asm.sub.varmin=16
CMDS=<<EOF
iE~hbc.export.module.RN$AppRegistry
iE~?
EOF
EXPECT=<<EOF
3630 0x00071961 0x10071961 GLOBAL OBJ 1 hbc.export.module.RN$AppRegistry
451
EOF
RUN
NAME=hbc get_global_object instruction (va)
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
s 0x1003e510
pd 1
EOF
EXPECT=<<EOF
;-- clear_0x0003e510:
0x1003e510 3000 get_global_object r0
EOF
RUN
NAME=hbc get_global_object instruction (no va)
FILE=bins/hbc/index.android.bundle
ARGS=-e io.va=false
CMDS=<<EOF
s 0x3e510
pd 1
EOF
EXPECT=<<EOF
;-- clear_0x0003e510:
0x0003e510 3000 get_global_object r0
EOF
RUN
NAME=hbc try_get_by_id instruction
NAME=hbc try_get_by_id instruction (va)
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
s 0x1003e512
ao~mnemonic
EOF
EXPECT=<<EOF
mnemonic: try_get_by_id
EOF
RUN
NAME=hbc try_get_by_id instruction (no va)
FILE=bins/hbc/index.android.bundle
ARGS=-e io.va=false
CMDS=<<EOF
s 0x3e512
ao~mnemonic
EOF
@@ -192,9 +368,21 @@ mnemonic: try_get_by_id
EOF
RUN
NAME=hbc get_by_id_short instruction
NAME=hbc get_by_id_short instruction (va)
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
s 0x1003e518
ao~mnemonic
EOF
EXPECT=<<EOF
mnemonic: get_by_id_short
EOF
RUN
NAME=hbc get_by_id_short instruction (no va)
FILE=bins/hbc/index.android.bundle
ARGS=-e io.va=false
CMDS=<<EOF
s 0x3e518
ao~mnemonic
EOF
@@ -203,9 +391,21 @@ mnemonic: get_by_id_short
EOF
RUN
NAME=hbc create_this instruction
NAME=hbc create_this instruction (va)
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
s 0x1003e51d
pd 1
EOF
EXPECT=<<EOF
0x1003e51d 6a010100 create_this r1, r1, r0
EOF
RUN
NAME=hbc create_this instruction (no va)
FILE=bins/hbc/index.android.bundle
ARGS=-e io.va=false
CMDS=<<EOF
s 0x3e51d
pd 1
EOF
@@ -214,9 +414,21 @@ EXPECT=<<EOF
EOF
RUN
NAME=hbc mov instruction
NAME=hbc mov instruction (va)
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
s 0x1003e521
pd 1
EOF
EXPECT=<<EOF
0x1003e521 080201 mov r2, r1
EOF
RUN
NAME=hbc mov instruction (no va)
FILE=bins/hbc/index.android.bundle
ARGS=-e io.va=false
CMDS=<<EOF
s 0x3e521
pd 1
EOF
@@ -225,9 +437,21 @@ EXPECT=<<EOF
EOF
RUN
NAME=hbc construct instruction
NAME=hbc construct instruction (va)
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
s 0x1003e524
pd 1
EOF
EXPECT=<<EOF
0x1003e524 50000001 construct r0, r0, 1
EOF
RUN
NAME=hbc construct instruction (no va)
FILE=bins/hbc/index.android.bundle
ARGS=-e io.va=false
CMDS=<<EOF
s 0x3e524
pd 1
EOF
@@ -236,9 +460,21 @@ EXPECT=<<EOF
EOF
RUN
NAME=hbc select_object instruction
NAME=hbc select_object instruction (va)
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
s 0x1003e528
pd 1
EOF
EXPECT=<<EOF
0x1003e528 6b000100 select_object r0, r1, r0
EOF
RUN
NAME=hbc select_object instruction (no va)
FILE=bins/hbc/index.android.bundle
ARGS=-e io.va=false
CMDS=<<EOF
s 0x3e528
pd 1
EOF
@@ -247,9 +483,21 @@ EXPECT=<<EOF
EOF
RUN
NAME=hbc get_environment instruction
NAME=hbc get_environment instruction (va)
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
s 0x1003e52c
pd 1
EOF
EXPECT=<<EOF
0x1003e52c 290100 get_environment r1, 0
EOF
RUN
NAME=hbc get_environment instruction (no va)
FILE=bins/hbc/index.android.bundle
ARGS=-e io.va=false
CMDS=<<EOF
s 0x3e52c
pd 1
EOF
@@ -258,9 +506,21 @@ EXPECT=<<EOF
EOF
RUN
NAME=hbc store_to_environment instruction
NAME=hbc store_to_environment instruction (va)
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
s 0x1003e52f
pd 1
EOF
EXPECT=<<EOF
0x1003e52f 2a010100 store_to_environment r1, 1, r0
EOF
RUN
NAME=hbc store_to_environment instruction (no va)
FILE=bins/hbc/index.android.bundle
ARGS=-e io.va=false
CMDS=<<EOF
s 0x3e52f
pd 1
EOF
@@ -269,9 +529,25 @@ EXPECT=<<EOF
EOF
RUN
NAME=hbc complete function disasm
NAME=hbc complete function disasm (va)
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
s 0x1003e510
pd 3~get_global
pd 1 @ 0x1003e51d~create_this
pd 1 @ 0x1003e533~ret
EOF
EXPECT=<<EOF
0x1003e510 3000 get_global_object r0
0x1003e51d 6a010100 create_this r1, r1, r0
0x1003e533 5c00 ret r0
EOF
RUN
NAME=hbc complete function disasm (no va)
FILE=bins/hbc/index.android.bundle
ARGS=-e io.va=false
CMDS=<<EOF
s 0x3e510
pd 3~get_global
pd 1 @ 0x3e51d~create_this
@@ -284,7 +560,7 @@ EXPECT=<<EOF
EOF
RUN
NAME=hbc assembler
NAME=hbc pa ret
FILE=--
CMDS=<<EOF
-a hbc
@@ -294,3 +570,313 @@ EXPECT=<<EOF
5c00
EOF
RUN
NAME=hbc pa get_global_object
FILE=--
CMDS=<<EOF
-a hbc
pa get_global_object r0
EOF
EXPECT=<<EOF
3000
EOF
RUN
NAME=hbc pa mov two regs
FILE=--
CMDS=<<EOF
-a hbc
pa mov r2, r1
EOF
EXPECT=<<EOF
080201
EOF
RUN
NAME=hbc pa construct three operands
FILE=--
CMDS=<<EOF
-a hbc
pa construct r0, r0, 1
EOF
EXPECT=<<EOF
50000001
EOF
RUN
NAME=hbc pa select_object three regs
FILE=--
CMDS=<<EOF
-a hbc
pa select_object r0, r1, r0
EOF
EXPECT=<<EOF
6b000100
EOF
RUN
NAME=hbc pa store_to_environment
FILE=--
CMDS=<<EOF
-a hbc
pa store_to_environment r1, 1, r0
EOF
EXPECT=<<EOF
2a010100
EOF
RUN
NAME=hbc pa rejects extra operand
FILE=--
CMDS=<<EOF
-a hbc
"pa ret r0, r1"
EOF
EXPECT=<<EOF
EOF
RUN
NAME=hbc pa rejects multiple extra operands
FILE=--
CMDS=<<EOF
-a hbc
"pa ret r0, r1, r2"
EOF
EXPECT=<<EOF
EOF
RUN
NAME=hbc pa rejects extra operand on zero-arg instr
FILE=--
CMDS=<<EOF
-a hbc
"pa get_global_object r0, r1"
EOF
EXPECT=<<EOF
EOF
RUN
NAME=hbc pa rejects missing operand
FILE=--
CMDS=<<EOF
-a hbc
pa ret
EOF
EXPECT=<<EOF
EOF
RUN
NAME=hbc pa rejects truncated mov
FILE=--
CMDS=<<EOF
-a hbc
pa mov r2
EOF
EXPECT=<<EOF
EOF
RUN
NAME=hbc pa rejects unknown mnemonic
FILE=--
CMDS=<<EOF
-a hbc
pa bogus_op r0
EOF
EXPECT=<<EOF
EOF
RUN
NAME=hbc pad ret
FILE=--
CMDS=<<EOF
-a hbc
pad 5c00
EOF
EXPECT=<<EOF
ret r0
EOF
RUN
NAME=hbc pad get_global_object
FILE=--
CMDS=<<EOF
-a hbc
pad 3000
EOF
EXPECT=<<EOF
get_global_object r0
EOF
RUN
NAME=hbc pad mov
FILE=--
CMDS=<<EOF
-a hbc
pad 080201
EOF
EXPECT=<<EOF
mov r2, r1
EOF
RUN
NAME=hbc pad respects asm.cpu v99
FILE=--
CMDS=<<EOF
-a hbc
e asm.cpu=v99
pad 010203040506
EOF
EXPECT=<<EOF
new_object_with_buffer r2, 1027, 1541
EOF
RUN
NAME=hbc pa respects asm.cpu v99
FILE=--
CMDS=<<EOF
-a hbc
e asm.cpu=v99
pa new_object_with_buffer r2, 1027, 1541
EOF
EXPECT=<<EOF
010203040506
EOF
RUN
NAME=hbc pad construct
FILE=--
CMDS=<<EOF
-a hbc
pad 50000001
EOF
EXPECT=<<EOF
construct r0, r0, 1
EOF
RUN
NAME=hbc pad select_object
FILE=--
CMDS=<<EOF
-a hbc
pad 6b000100
EOF
EXPECT=<<EOF
select_object r0, r1, r0
EOF
RUN
NAME=hbc pad store_to_environment
FILE=--
CMDS=<<EOF
-a hbc
pad 2a010100
EOF
EXPECT=<<EOF
store_to_environment r1, 1, r0
EOF
RUN
NAME=hbc pa+pad roundtrip ret
FILE=--
CMDS=<<EOF
-a hbc
pad `pa ret r0`
EOF
EXPECT=<<EOF
ret r0
EOF
RUN
NAME=hbc pa+pad roundtrip mov
FILE=--
CMDS=<<EOF
-a hbc
pad `pa mov r2, r1`
EOF
EXPECT=<<EOF
mov r2, r1
EOF
RUN
NAME=hbc pa+pad roundtrip construct
FILE=--
CMDS=<<EOF
-a hbc
pad `pa construct r0, r0, 1`
EOF
EXPECT=<<EOF
construct r0, r0, 1
EOF
RUN
NAME=hbc pi ret after wx
FILE=malloc://16
CMDS=<<EOF
-a hbc
wx 5c00
pi 1
EOF
EXPECT=<<EOF
ret r0
EOF
RUN
NAME=hbc pi mov after wx
FILE=malloc://16
CMDS=<<EOF
-a hbc
wx 080201
pi 1
EOF
EXPECT=<<EOF
mov r2, r1
EOF
RUN
NAME=hbc pi construct after wx
FILE=malloc://16
CMDS=<<EOF
-a hbc
wx 50000001
pi 1
EOF
EXPECT=<<EOF
construct r0, r0, 1
EOF
RUN
NAME=hbc pa+wx+pi roundtrip ret
FILE=malloc://16
CMDS=<<EOF
-a hbc
wx `pa ret r0`
pi 1
EOF
EXPECT=<<EOF
ret r0
EOF
RUN
NAME=hbc pa+wx+pi roundtrip mov
FILE=malloc://16
CMDS=<<EOF
-a hbc
wx `pa mov r2, r1`
pi 1
EOF
EXPECT=<<EOF
mov r2, r1
EOF
RUN
NAME=hbc pa+wx+pi roundtrip store_to_environment
FILE=malloc://16
CMDS=<<EOF
-a hbc
wx `pa store_to_environment r1, 1, r0`
pi 1
EOF
EXPECT=<<EOF
store_to_environment r1, 1, r0
EOF
RUN
+83
View File
@@ -0,0 +1,83 @@
NAME=hbc ih header fields
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
ih~version
ih~functionCount
ih~debugInfoOffset
EOF
EXPECT=<<EOF
0x00000008 0x00000008 0x00000060 version
0x00000028 0x00000028 0x00000001 functionCount
0x00000068 0x00000068 0x000000c8 debugInfoOffset
EOF
RUN
NAME=hbc ihq header fields
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
ihq~version
ihq~functionCount
ihq~debugInfoOffset
EOF
EXPECT=<<EOF
0x00000008 0x00000060 version
0x00000028 0x00000001 functionCount
0x00000068 0x000000c8 debugInfoOffset
EOF
RUN
NAME=hbc ihj header fields
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
ihj~{}~version
ihj~{}~functionCount
ihj~{}~debugInfoOffset
EOF
EXPECT=<<EOF
"name": "version",
"name": "functionCount",
"name": "debugInfoOffset",
EOF
RUN
NAME=hbc iH ascii header
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
iH~version
iH~functionCount
iH~debugInfoOffset
EOF
EXPECT=<<EOF
0x00000008 version 0x00000060
0x00000028 functionCount 0x00000001
0x00000068 debugInfoOffset 0x000000c8
EOF
RUN
NAME=hbc iH key values
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
iH=~version
iH=~functionCount
iH=~debugInfoOffset
EOF
EXPECT=<<EOF
version=96
functionCount=1
debugInfoOffset=200
EOF
RUN
NAME=hbc iH json
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
iHj~version
iHj~functionCount
iHj~debugInfoOffset
EOF
EXPECT=<<EOF
{"magic":2240826417119764422,"version":96,"sourceHash":"a97c8302dab90bec7184a9183b22b43badd57a65","fileLength":292,"globalCodeIndex":0,"functionCount":1,"stringKindCount":1,"identifierCount":0,"stringCount":2,"overflowStringCount":0,"stringStorageSize":17,"bigIntCount":0,"bigIntStorageSize":0,"regExpCount":0,"regExpStorageSize":0,"literalValueBufferSize":0,"arrayBufferSize":0,"objKeyBufferSize":0,"objValueBufferSize":0,"objShapeTableCount":0,"numStringSwitchImms":0,"segmentID":0,"cjsModuleCount":0,"functionSourceCount":0,"debugInfoOffset":200,"staticBuiltins":false,"cjsModulesStaticallyResolved":false,"hasAsync":false}
{"magic":2240826417119764422,"version":96,"sourceHash":"a97c8302dab90bec7184a9183b22b43badd57a65","fileLength":292,"globalCodeIndex":0,"functionCount":1,"stringKindCount":1,"identifierCount":0,"stringCount":2,"overflowStringCount":0,"stringStorageSize":17,"bigIntCount":0,"bigIntStorageSize":0,"regExpCount":0,"regExpStorageSize":0,"literalValueBufferSize":0,"arrayBufferSize":0,"objKeyBufferSize":0,"objValueBufferSize":0,"objShapeTableCount":0,"numStringSwitchImms":0,"segmentID":0,"cjsModuleCount":0,"functionSourceCount":0,"debugInfoOffset":200,"staticBuiltins":false,"cjsModulesStaticallyResolved":false,"hasAsync":false}
{"magic":2240826417119764422,"version":96,"sourceHash":"a97c8302dab90bec7184a9183b22b43badd57a65","fileLength":292,"globalCodeIndex":0,"functionCount":1,"stringKindCount":1,"identifierCount":0,"stringCount":2,"overflowStringCount":0,"stringStorageSize":17,"bigIntCount":0,"bigIntStorageSize":0,"regExpCount":0,"regExpStorageSize":0,"literalValueBufferSize":0,"arrayBufferSize":0,"objKeyBufferSize":0,"objValueBufferSize":0,"objShapeTableCount":0,"numStringSwitchImms":0,"segmentID":0,"cjsModuleCount":0,"functionSourceCount":0,"debugInfoOffset":200,"staticBuiltins":false,"cjsModulesStaticallyResolved":false,"hasAsync":false}
EOF
RUN
+172
View File
@@ -0,0 +1,172 @@
NAME=pd:hs help entry
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
pd:h?~pd:hs
EOF
EXPECT=<<EOF
pd:hs [id] - List source-line information
EOF
RUN
NAME=pd:hs source lines small
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
pd:hs
EOF
EXPECT=<<EOF
0x000000b0 f=0 +0x0 yes.js:1:1 stmt=0
0x000000b4 f=0 +0x4 yes.js:1:5 stmt=0
EOF
RUN
NAME=pd:hs source lines filter
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
pd:hs 0
EOF
EXPECT=<<EOF
0x000000b0 f=0 +0x0 yes.js:1:1 stmt=0
0x000000b4 f=0 +0x4 yes.js:1:5 stmt=0
EOF
RUN
NAME=pd:hs source lines filter miss
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
pd:hs 99
EOF
EXPECT=<<EOF
EOF
RUN
NAME=pd:hs empty source lines
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
pd:hs
EOF
EXPECT=<<EOF
EOF
RUN
NAME=r2hermes-H debug info present
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
r2hermes-H~Debug Info Present
EOF
EXPECT=<<EOF
Debug Info Present: yes
EOF
RUN
NAME=r2hermes-H source locations size
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
r2hermes-H~Source Locations
EOF
EXPECT=<<EOF
Source Locations: 13 bytes
EOF
RUN
NAME=r2hermes-H debug files count
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
r2hermes-H~Debug Files
EOF
EXPECT=<<EOF
Debug Files: 1 (6 bytes)
EOF
RUN
NAME=iI linenum exposed (debug info)
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
iI~linenum
EOF
EXPECT=<<EOF
linenum true
EOF
RUN
NAME=iI linenum stripped
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
iI~linenum
EOF
EXPECT=<<EOF
linenum false
EOF
RUN
NAME=iI linenum honours file truth with bin.dbginfo=false
FILE=bins/hbc/bespoke_eval.hbc
ARGS=-e bin.dbginfo=false
CMDS=<<EOF
iI~linenum,stripped
EOF
EXPECT=<<EOF
linenum true
stripped false
EOF
RUN
NAME=iI stripped file still stripped with bin.dbginfo=false
FILE=bins/hbc/index.android.bundle
ARGS=-e bin.dbginfo=false
CMDS=<<EOF
iI~linenum,stripped
EOF
EXPECT=<<EOF
linenum false
stripped true
EOF
RUN
NAME=iI stripped file stripped with bin.dbginfo=true
FILE=bins/hbc/index.android.bundle
ARGS=-e bin.dbginfo=true
CMDS=<<EOF
iI~linenum,stripped
EOF
EXPECT=<<EOF
linenum false
stripped true
EOF
RUN
NAME=CL populated with bin.dbginfo=true
FILE=bins/hbc/bespoke_eval.hbc
ARGS=-e bin.dbginfo=true
CMDS=<<EOF
CL
EOF
EXPECT=<<EOF
file: yes.js
line: 1
colu: 1
addr: 0x100000b0
file: yes.js
line: 1
colu: 5
addr: 0x100000b4
EOF
RUN
NAME=CL empty with bin.dbginfo=false
FILE=bins/hbc/bespoke_eval.hbc
ARGS=-e bin.dbginfo=false
CMDS=<<EOF
CL
EOF
EXPECT=<<EOF
EOF
RUN
NAME=CL empty on stripped file
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
CL
EOF
EXPECT=<<EOF
EOF
RUN
+3 -2
View File
@@ -1,13 +1,14 @@
test_src = files(
# Add test files here as needed
'test_debug_info.c',
)
if test_src.length() > 0
executable(
run_tests = executable(
'run_tests',
test_src,
link_with: libhbc,
include_directories: include_dirs,
install: false,
)
test('debug-info', run_tests, args: [meson.project_source_root()])
endif
+83
View File
@@ -0,0 +1,83 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/hbc.h>
#include <stdio.h>
#include <string.h>
#define CHECK(x) do { \
if (!(x)) { \
fprintf (stderr, "check failed: %s:%d: %s\n", __FILE__, __LINE__, #x); \
return 1; \
} \
} while (0)
static int test_small_debug_info(const char *root) {
char path[512];
snprintf (path, sizeof (path), "%s/test/bins/hbc/bespoke_eval.hbc", root);
HBC *hbc = NULL;
CHECK (hbc_open (path, &hbc).code == RESULT_SUCCESS);
HBCDebugInfo di = { 0 };
CHECK (hbc_get_debug_info (hbc, &di).code == RESULT_SUCCESS);
CHECK (di.has_debug_info);
CHECK (di.filename_count == 1);
CHECK (di.filename_storage_size == 6);
CHECK (di.file_region_count == 1);
CHECK (di.functions_with_debug_info == 1);
CHECK (di.source_locations_size == 13);
CHECK (di.scope_desc_data_size == 3);
CHECK (di.textified_data_size == 1);
HBCSourceLineArray lines = { 0 };
CHECK (hbc_get_source_lines (hbc, &lines).code == RESULT_SUCCESS);
CHECK (hbc_has_source_lines (hbc));
CHECK (lines.count == 2);
CHECK (lines.lines[0].address == 0xb0);
CHECK (lines.lines[0].function_address == 0);
CHECK (lines.lines[0].line == 1);
CHECK (lines.lines[0].column == 1);
CHECK (!strcmp (lines.lines[0].filename, "yes.js"));
CHECK (lines.lines[1].address == 0xb4);
CHECK (lines.lines[1].function_address == 4);
CHECK (lines.lines[1].line == 1);
CHECK (lines.lines[1].column == 5);
hbc_free_source_lines (&lines);
hbc_close (hbc);
return 0;
}
static int test_empty_debug_info(const char *root) {
char path[512];
snprintf (path, sizeof (path), "%s/test/bins/hbc/index.android.bundle", root);
HBC *hbc = NULL;
CHECK (hbc_open (path, &hbc).code == RESULT_SUCCESS);
HBCDebugInfo di = { 0 };
CHECK (hbc_get_debug_info (hbc, &di).code == RESULT_SUCCESS);
CHECK (di.has_debug_info);
CHECK (di.filename_count == 0);
CHECK (di.file_region_count == 0);
CHECK (di.functions_with_debug_info == 0);
CHECK (di.source_locations_size == 0);
CHECK (!hbc_has_source_lines (hbc));
HBCSourceLineArray lines = { 0 };
CHECK (hbc_get_source_lines (hbc, &lines).code == RESULT_SUCCESS);
CHECK (lines.count == 0);
hbc_free_source_lines (&lines);
hbc_close (hbc);
return 0;
}
int main(int argc, char **argv) {
const char *root = argc > 1? argv[1]: ".";
if (test_small_debug_info (root) || test_empty_debug_info (root)) {
return 1;
}
return 0;
}