Compare commits

..
71 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
84 changed files with 5369 additions and 3102 deletions

No files matched your search

+42 -13
View File
@@ -1,8 +1,5 @@
name: CI
env:
R2V: 6.1.4
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/[email protected]
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.1.4
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.1.4
permissions:
contents: write
+4 -12
View File
@@ -1,8 +1,5 @@
name: Windows Build
env:
R2V: 6.1.4
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
+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.
+1
View File
@@ -81,6 +81,7 @@ test: $(BIN_FILE) $(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
+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
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#ifndef LIBHBC_COMMON_H
#define LIBHBC_COMMON_H
+39
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,7 @@ 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;
+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
+29 -1
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#ifndef LIBHBC_API_H
#define LIBHBC_API_H
@@ -131,7 +133,10 @@ 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;
@@ -344,6 +349,22 @@ typedef struct {
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.
*/
@@ -357,6 +378,13 @@ void hbc_free_insns(HBCInsn *insns, u32 count);
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
+14 -18
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#ifndef LIBHBC_LITERALS_PUBLIC_H
#define LIBHBC_LITERALS_PUBLIC_H
@@ -14,7 +16,7 @@
* 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
* 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
@@ -32,7 +34,7 @@ typedef enum {
} 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(). */
* HBC and freed on hbc_close () or hbc_literals_reset (). */
typedef struct {
HBCLiteralKind kind;
u32 num_items;
@@ -45,25 +47,21 @@ typedef struct {
} 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);
* 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
* 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);
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);
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);
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).
@@ -74,7 +72,7 @@ Result hbc_literals_scan_code(HBC *hbc, u32 *out_count);
* 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(). */
* *out via free (). */
typedef struct {
u32 paddr; /* absolute file offset */
u32 pool_offset; /* byte offset within the pool */
@@ -82,13 +80,11 @@ typedef struct {
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);
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);
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. */
@@ -106,7 +102,7 @@ u32 hbc_get_pool_size(HBC *hbc, HBCLiteralKind kind);
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
/* 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);
+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 */
+2
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#ifndef LIBHBC_FILE_PARSER_H
#define LIBHBC_FILE_PARSER_H
+1 -1
View File
@@ -1,7 +1,7 @@
project(
'r2hermes',
'c',
version: '1.2.6',
version: '1.4.0',
license: 'MIT',
default_options: [
'c_std=c11',
+2
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#ifdef R2_CORE_H
extern RCorePlugin r_core_plugin_r2hermes;
#endif
+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
+7 -3
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/decompilation/literals.h>
#include <ctype.h>
#include <math.h>
@@ -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) {
+7 -18
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>
@@ -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, ' '));
+154 -277
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)));
@@ -232,19 +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 ()));
if (is_construct) {
RETURN_IF_ERROR (add (out, create_raw_token ("new")));
}
RETURN_IF_ERROR (add (out, reg_r_safe (insn, 1)));
if (!is_construct) {
RETURN_IF_ERROR (add (out, create_raw_token (".call")));
}
RETURN_IF_ERROR (add (out, create_left_parenthesis_token ()));
char buf2[32];
snprintf (buf2, sizeof (buf2), "/*argc:%u*/", (unsigned)insn->arg3);
RETURN_IF_ERROR (add (out, create_raw_token (buf2)));
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"));
@@ -278,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:
@@ -360,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
@@ -377,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
@@ -452,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");
@@ -535,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;
}
@@ -558,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*/")));
}
@@ -568,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*/")));
}
@@ -577,7 +627,7 @@ 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))));
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*/")));
}
@@ -585,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) */
@@ -643,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:
@@ -704,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;
@@ -748,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 = 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;
@@ -821,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*/")));
}
@@ -836,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:
@@ -863,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*/")));
}
@@ -917,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:
@@ -974,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 ("!")));
@@ -993,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)));
@@ -1008,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)));
@@ -1040,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 */
@@ -1084,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");
}
@@ -1098,14 +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 ()));
Token *t = create_function_table_index_token (get_operand_value (insn_c, 2), NULL);
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 (get_operand_value (insn_c, 1))));
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;
@@ -1155,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)));
@@ -1230,7 +1172,7 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
{
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))));
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*/")));
}
@@ -1241,7 +1183,7 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
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*/")));
}
@@ -1250,59 +1192,8 @@ 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) {
@@ -1323,22 +1214,8 @@ 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;
}
+308 -166
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/hbc.h>
#include <hbc/common.h>
#include <hbc/disasm.h>
@@ -119,7 +121,7 @@ Result hbc_open_from_memory(const u8 *data, size_t size, HBC **out) {
return res;
}
/* Read only the DebugInfoHeader eagerly — a handful of u32s — so
* hbc_has_source_lines() can answer accurately without committing to the
* 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. */
@@ -276,7 +278,7 @@ static bool read_sleb128(const u8 *data, size_t size, size_t *pos, int64_t *out)
if (shift == 63 && (byte & 0x7e)) {
return false;
}
result |= ((uint64_t)(byte & 0x7f)) << shift;
result |= ((uint64_t) (byte & 0x7f)) << shift;
shift += 7;
} while (byte & 0x80);
@@ -340,7 +342,11 @@ static Result append_source_line(HBCSourceLineArray *arr, u32 *cap, const HBCSou
return SUCCESS_RESULT ();
}
#define PARSE_FAIL(msg) do { res = ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, msg); goto fail; } while (0)
#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) {
@@ -361,14 +367,10 @@ Result hbc_get_source_lines(HBC *hbc, HBCSourceLineArray *out) {
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)) {
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) {
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");
}
@@ -396,11 +398,7 @@ Result hbc_get_source_lines(HBC *hbc, HBCSourceLineArray *out) {
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)) {
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)) {
@@ -411,9 +409,7 @@ Result hbc_get_source_lines(HBC *hbc, HBCSourceLineArray *out) {
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) {
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;
@@ -626,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");
@@ -665,62 +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:
operand_values[i] = ctx->bytes[pos];
pos += 1;
break;
case OPERAND_TYPE_ADDR8:
operand_values[i] = (u32) (i32) (i8)ctx->bytes[pos];
pos += 1;
break;
case OPERAND_TYPE_UINT16:
operand_values[i] = (u32)ctx->bytes[pos] | ((u32)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] = (u32)ctx->bytes[pos] | ((u32)ctx->bytes[pos + 1] << 8) |
((u32)ctx->bytes[pos + 2] << 16) | ((u32)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];
@@ -832,43 +778,41 @@ Result hbc_dec(const HBCDecodeCtx *ctx, HBCInsnInfo *out) {
}
typedef struct {
const char *name; /* exact match, else match prefix */
const char *name; /* exact match, else match prefix */
const char *prefix;
const char *kind;
const char *module; /* NULL for exports */
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 },
{ "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]))
static bool str_eq(const char *a, const char *b) {
if (a == b) {
return true;
}
return a && b && !strcmp (a, b);
}
/* 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");
@@ -878,15 +822,7 @@ 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) {
for (u32 i = 0; i < out->count; i++) {
HBCBinding *b = &out->bindings[i];
if (b->type == type && !strcmp (b->kind, kind)
&& !strcmp (b->name, name) && str_eq (b->module, module)) {
return SUCCESS_RESULT ();
}
}
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");
@@ -894,18 +830,21 @@ static Result binding_add(HBCBindings *out, HBCBindingType type, const char *kin
out->bindings = nb;
HBCBinding *b = &nb[out->count++];
*b = (HBCBinding){
.type = type, .kind = kind, .name = strdup (name),
.type = type,
.kind = kind,
.name = strdup (name),
.module = module? strdup (module): NULL,
.function_id = function_id, .offset = offset, .string_id = string_id,
};
.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]) {
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++) {
@@ -915,17 +854,23 @@ static size_t decode_operands(const Instruction *inst, const u8 *bytes, size_t l
case OPERAND_TYPE_REG8:
case OPERAND_TYPE_UINT8:
need = 1;
if (pos + need > len) { return 0; }
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];
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; }
if (pos + need > len) {
return 0;
}
values[i] = (u32)bytes[pos] | ((u32)bytes[pos + 1] << 8);
break;
case OPERAND_TYPE_REG32:
@@ -933,13 +878,16 @@ static size_t decode_operands(const Instruction *inst, const u8 *bytes, size_t l
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);
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; }
if (pos + need > len) {
return 0;
}
break;
default:
return 0;
@@ -949,43 +897,35 @@ static size_t decode_operands(const Instruction *inst, const u8 *bytes, size_t l
return pos;
}
static Result scan_hint_matches(HBCBindings *out, const char *s, u32 fid, u32 off, u32 sid) {
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));
bool match = hint->name? !strcmp (s, hint->name): !strncmp (s, hint->prefix, strlen (hint->prefix));
if (!match) {
continue;
}
RETURN_IF_ERROR (binding_add (out, hint->type, hint->kind, s,
hint->module, fid, off, sid));
}
return SUCCESS_RESULT ();
}
static bool function_has_export_marker(HBCReader *r, const u8 *code, u32 size, HBCISA isa) {
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;
}
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) {
u32 sid = values[i];
const char *s = (sid < r->header.stringCount && r->strings)? r->strings[sid]: NULL;
if (s && is_export_marker (s)) {
return true;
}
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;
}
}
pc += inst->binary_size;
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 false;
return SUCCESS_RESULT ();
}
Result hbc_scan_bindings(HBC *hbc, HBCBindings *out) {
@@ -998,27 +938,41 @@ Result hbc_scan_bindings(HBC *hbc, HBCBindings *out) {
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 < r->header.stringCount && r->strings)? r->strings[sid]: NULL;
if (name && *name) {
RETURN_IF_ERROR (binding_add (out, HBC_BINDING_IMPORT,
"cjs", name, "commonjs", 0, r->cjs_modules[i].offset, sid));
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) {
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);
bool has_export_marker = function_has_export_marker (r, code, size, isa);
for (u32 pc = 0; pc < size; ) {
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) {
@@ -1028,25 +982,71 @@ Result hbc_scan_bindings(HBC *hbc, HBCBindings *out) {
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];
const char *s = (sid < r->header.stringCount && r->strings)? r->strings[sid]: NULL;
if (sid >= sc || !r->strings) {
continue;
}
const char *s = r->strings[sid];
if (!s || !*s) {
continue;
}
RETURN_IF_ERROR (scan_hint_matches (out, s, fid, fi.offset + pc, sid));
if (has_export_marker && is_export_store (inst->name) && !is_export_marker (s)) {
RETURN_IF_ERROR (binding_add (out, HBC_BINDING_EXPORT,
"module", s, NULL, fid, fi.offset + pc, sid));
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;
}
}
}
return SUCCESS_RESULT ();
done_scan:
free (cand);
done:
free (seen);
if (res.code != RESULT_SUCCESS) {
hbc_free_bindings (out);
}
return res;
}
void hbc_free_bindings(HBCBindings *bindings) {
@@ -1062,6 +1062,148 @@ void hbc_free_bindings(HBCBindings *bindings) {
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(
+23
View File
@@ -1,9 +1,32 @@
/* 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;
+58 -88
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
/* Public literals API: lazy cache, formatting, scanning. See
* include/hbc/literals.h for the contract. */
@@ -10,6 +12,14 @@
#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;
@@ -60,8 +70,7 @@ u32 hbc_get_object_values_size(HBC *hbc) {
/* ---------- Formatting ---------- */
static Result format_raw_impl(HBCReader *r, HBCLiteralKind kind, u32 num_items,
u32 primary_id, u32 secondary_id, char **out) {
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);
@@ -70,14 +79,12 @@ static Result format_raw_impl(HBCReader *r, HBCLiteralKind kind, u32 num_items,
}
Result fr;
if (kind == HBC_LIT_ARRAY) {
fr = _hbc_format_array_literal (r, num_items, primary_id, &sb,
LITERALS_PRETTY_NEVER, true);
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);
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);
@@ -89,17 +96,14 @@ static Result format_raw_impl(HBCReader *r, HBCLiteralKind kind, u32 num_items,
return fr;
}
Result hbc_literals_format_raw(HBC *hbc, HBCLiteralKind kind, u32 num_items,
u32 primary_id, u32 secondary_id, char **out) {
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);
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) {
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");
}
@@ -117,13 +121,10 @@ Result hbc_literals_format_for_opcode(HBC *hbc, u8 opcode, u32 arg3, u32 arg4,
/* ---------- Cache ---------- */
static HBCLiteralEntry *cache_find(HBCLiteralCache *c, HBCLiteralKind kind,
u32 num_items, u32 primary_id, u32 secondary_id) {
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) {
if (e->kind == kind && e->num_items == num_items && e->primary_id == primary_id && e->secondary_id == secondary_id) {
return e;
}
}
@@ -131,14 +132,8 @@ static HBCLiteralEntry *cache_find(HBCLiteralCache *c, HBCLiteralKind kind,
}
static HBCLiteralEntry *cache_alloc(HBCLiteralCache *c) {
if (c->count == c->cap) {
u32 nc = c->cap? c->cap * 2: 64;
HBCLiteralEntry *ne = realloc (c->entries, nc * sizeof (HBCLiteralEntry));
if (!ne) {
return NULL;
}
c->entries = ne;
c->cap = nc;
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));
@@ -159,8 +154,7 @@ static u32 entry_paddr(HBC *hbc, HBCLiteralKind kind, u32 primary_id) {
return r->arrays_paddr + primary_id;
}
/* object */
if (r->header.version >= 97 && r->object_shapes
&& primary_id < r->object_shape_count) {
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;
@@ -173,10 +167,8 @@ static u32 entry_paddr(HBC *hbc, HBCLiteralKind kind, u32 primary_id) {
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);
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 ();
@@ -193,8 +185,7 @@ static Result cache_get_or_create(HBC *hbc, HBCLiteralKind kind, u32 num_items,
/* 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);
Result fr = format_raw_impl (&hbc->reader, kind, num_items, primary_id, secondary_id, &txt);
if (fr.code == RESULT_SUCCESS) {
e->formatted = txt;
}
@@ -202,14 +193,12 @@ static Result cache_get_or_create(HBC *hbc, HBCLiteralKind kind, u32 num_items,
return SUCCESS_RESULT ();
}
Result hbc_literals_get(HBC *hbc, HBCLiteralKind kind, u32 num_items,
u32 primary_id, u32 secondary_id, const char **out_formatted) {
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);
Result r = cache_get_or_create (hbc, kind, num_items, primary_id, secondary_id, &e);
if (r.code != RESULT_SUCCESS) {
return r;
}
@@ -232,14 +221,12 @@ static void xref_add(HBCLiteralEntry *e, u32 from_addr) {
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) {
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);
Result r = cache_get_or_create (hbc, kind, num_items, primary_id, secondary_id, &e);
if (r.code != RESULT_SUCCESS) {
return r;
}
@@ -296,9 +283,7 @@ static bool is_buffer_op(u8 opcode, HBCLiteralKind *out_kind) {
/* 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) {
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;
}
@@ -306,30 +291,29 @@ static bool decode_buffer_op(u8 op, u32 version, const u8 *code, u32 size,
if (op == OP_NewArrayWithBuffer) {
/* reg8 + u16 prealloc + u16 num + u16 id (v<97 AND v97+) */
*kind = HBC_LIT_ARRAY;
*num_items = (u32)code[pc + 4] | ((u32)code[pc + 5] << 8);
*primary = (u32)code[pc + 6] | ((u32)code[pc + 7] << 8);
*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 = (u32)code[pc + 4] | ((u32)code[pc + 5] << 8);
*primary = (u32)code[pc + 6] | ((u32)code[pc + 7] << 8)
| ((u32)code[pc + 8] << 16) | ((u32)code[pc + 9] << 24);
*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 = (u32)code[pc + 2] | ((u32)code[pc + 3] << 8);
*secondary = (u32)code[pc + 4] | ((u32)code[pc + 5] << 8);
*num_items = 0; /* resolved via shape table by the formatter */
*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 = (u32)code[pc + 4] | ((u32)code[pc + 5] << 8);
*primary = (u32)code[pc + 6] | ((u32)code[pc + 7] << 8);
*secondary = (u32)code[pc + 8] | ((u32)code[pc + 9] << 8);
*num_items = rd_u16le (code + pc + 4);
*primary = rd_u16le (code + pc + 6);
*secondary = rd_u16le (code + pc + 8);
}
return true;
}
@@ -337,18 +321,14 @@ static bool decode_buffer_op(u8 op, u32 version, const u8 *code, u32 size,
*kind = HBC_LIT_OBJECT;
if (version >= 97) {
/* reg8 + u32 shape_id + u32 values_id */
*primary = (u32)code[pc + 2] | ((u32)code[pc + 3] << 8)
| ((u32)code[pc + 4] << 16) | ((u32)code[pc + 5] << 24);
*secondary = (u32)code[pc + 6] | ((u32)code[pc + 7] << 8)
| ((u32)code[pc + 8] << 16) | ((u32)code[pc + 9] << 24);
*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 = (u32)code[pc + 4] | ((u32)code[pc + 5] << 8);
*primary = (u32)code[pc + 6] | ((u32)code[pc + 7] << 8)
| ((u32)code[pc + 8] << 16) | ((u32)code[pc + 9] << 24);
*secondary = (u32)code[pc + 10] | ((u32)code[pc + 11] << 8)
| ((u32)code[pc + 12] << 16) | ((u32)code[pc + 13] << 24);
*num_items = rd_u16le (code + pc + 4);
*primary = rd_u32le (code + pc + 6);
*secondary = rd_u32le (code + pc + 10);
}
return true;
}
@@ -368,8 +348,7 @@ Result hbc_literals_scan_code(HBC *hbc, u32 *out_count) {
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) {
if (hbc_get_function_bytecode (hbc, fid, &code, &size).code != RESULT_SUCCESS || !code || size == 0) {
continue;
}
HBCFunc fi;
@@ -386,11 +365,9 @@ Result hbc_literals_scan_code(HBC *hbc, u32 *out_count) {
}
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)) {
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);
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;
@@ -404,8 +381,7 @@ Result hbc_literals_scan_code(HBC *hbc, u32 *out_count) {
/* ---------- Pool-side scan ---------- */
static bool slp_skip_group(const u8 *base, size_t size, size_t *pos,
u8 *out_tag, u32 *out_length) {
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;
}
@@ -416,13 +392,16 @@ static bool slp_skip_group(const u8 *base, size_t size, size_t *pos,
if (*pos >= size) {
return false;
}
length = ((u32)(taglen & 0x0F) << 8) | base[(*pos)++];
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 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 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;
@@ -440,8 +419,7 @@ static bool slp_skip_group(const u8 *base, size_t size, size_t *pos,
return true;
}
Result hbc_literals_scan_pool(HBC *hbc, HBCLiteralKind kind,
HBCPoolGroup **out, u32 *out_count) {
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");
}
@@ -449,9 +427,7 @@ Result hbc_literals_scan_pool(HBC *hbc, HBCLiteralKind kind,
*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) {
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) {
@@ -498,14 +474,8 @@ Result hbc_literals_scan_pool(HBC *hbc, HBCLiteralKind kind,
if (!slp_skip_group (base, size, &pos, &tag, &length)) {
break;
}
if (n == cap) {
u32 nc = cap * 2;
HBCPoolGroup *na = realloc (arr, nc * sizeof (HBCPoolGroup));
if (!na) {
break;
}
arr = na;
cap = nc;
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;
+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: "));
+2
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>
+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: ["));
+73 -95
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,
@@ -142,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 ();
@@ -173,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) {
@@ -352,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) {
@@ -382,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));
@@ -398,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));
@@ -575,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;
@@ -593,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;
@@ -631,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;
@@ -648,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 */
@@ -674,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 */
@@ -1860,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;
@@ -1877,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);
@@ -1914,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);
}
}
}
@@ -1951,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) */
@@ -1989,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;
}
@@ -2070,7 +2048,7 @@ Result _hbc_reader_read_whole_file(HBCReader *reader, const char *filename) {
}
/* Read only the DebugInfoHeader eagerly (cheap). The body is parsed lazily
* via _hbc_reader_read_debug_info() on first API use. */
* 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 header: %s\n", result.error_message);
+22 -61
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') {
@@ -375,56 +372,20 @@ 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];
+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;
+1 -1
View File
@@ -33,7 +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
HBC_HEADERS = $(shell find ../../include -name '*.h') utils.inc.c sbom.inc.c
# Object files
ARCH_OBJ = arch_hbc.o
+47 -27
View File
@@ -1,4 +1,4 @@
/* radare2 - LGPL - Copyright 2025-2026 - pancake */
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <r_anal.h>
#include <hbc/hbc.h>
@@ -7,14 +7,14 @@
#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;
@@ -22,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"
@@ -171,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)(HBC_VADDR_BASE + 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;
}
}
}
@@ -187,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)(HBC_VADDR_BASE + offset);
op->ptr = (st64) (HBC_VADDR_BASE + offset);
}
}
}
@@ -207,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 */
@@ -235,9 +255,9 @@ static bool decode(RArchSession *s, RAnalOp *op, RArchDecodeMask mask) {
.hbc = hs->hbc,
};
/* 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 hbc.inline_buffer_literals=true`
* (core plugin updates a process-wide flag). */
* 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;
}
@@ -533,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;
@@ -571,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;
@@ -667,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,
+2 -2
View File
@@ -1,4 +1,4 @@
/* radare2 - LGPL - Copyright 2025-2026 - pancake */
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <r_asm.h>
@@ -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,
};
+401 -18
View File
@@ -1,4 +1,4 @@
/* radare2 - LGPL - Copyright 2025-2026 - pancake */
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <r_bin.h>
#include <r_bin_dwarf.h>
@@ -142,7 +142,7 @@ 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;
}
@@ -220,15 +220,9 @@ static RList *sections(RBinFile *bf) {
* 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));
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;
@@ -337,15 +331,14 @@ static RList *symbols(RBinFile *bf) {
/* 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 pd:hLs. The scan is linear in pool size and
* 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) {
if (hbc_literals_scan_pool (hbc, kind, &groups, &n).code != RESULT_SUCCESS || !groups) {
continue;
}
for (u32 i = 0; i < n; i++) {
@@ -399,13 +392,44 @@ static RList *imports(RBinFile *bf) {
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. */
*(triggered by hbc_get_source_lines) never runs for users who opted out. */
if (bf->rbin && !bf->rbin->want_dbginfo) {
return NULL;
}
@@ -447,7 +471,7 @@ done:
return ret;
}
static RList *strings(RBinFile *bf) {
static RList *strings_list(RBinFile *bf) {
RList *ret = r_list_newf ((RListFree)r_bin_string_free);
HBC *hbc = get_hbc (bf);
if (!hbc) {
@@ -495,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",
@@ -507,12 +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,
.imports = &imports,
.strings = &strings_list,
#endif
.baddr = &baddr,
.libs = &libs,
.lines = &lines,
.strings = &strings,
.header = &header,
.fields = &fields,
};
#ifndef R2_PLUGIN_INCORE
+394 -133
View File
@@ -1,10 +1,11 @@
/* radare2 - LGPL - Copyright 2025-2026 - pancake */
/* radare2 - BSD - Copyright 2025-2026 - pancake */
/* r2 core plugin for Hermes bytecode decompilation */
#include <r_core.h>
#include <hbc/hbc.h>
#include <hbc/literals.h>
#include <hbc/opcodes.h>
#include "utils.inc.c"
#define HBC_VADDR_BASE 0x10000000ULL
@@ -40,11 +41,8 @@ static ut32 parse_function_id(const char *addr_str) {
/* Comment callback for retrieving r2 comments (CC command) at an address */
static char *r2_comment_callback(void *context, u64 address) {
RCore *core = (RCore *)context;
if (core) {
const char *comment = r_meta_get_string (core->anal, R_META_TYPE_COMMENT, address);
return comment? strdup (comment): NULL;
}
return NULL;
const char *comment = r_meta_get_string (core->anal, R_META_TYPE_COMMENT, address);
return comment? strdup (comment): NULL;
}
/* Flag callback for retrieving r2 flag/symbol names at an address */
@@ -60,13 +58,8 @@ static char *r2_flag_callback(void *context, u64 address) {
}
static const char *current_file_path(RCore *core) {
if (core && core->bin) {
RBinInfo *bi = r_bin_get_info (core->bin);
if (bi) {
return bi->file;
}
}
return NULL;
RBinInfo *bi = r_bin_get_info (core->bin);
return bi? bi->file: NULL;
}
/* Helper to load the current binary as HBC using provider API */
@@ -106,17 +99,24 @@ static Result hbc_load_current_binary(HbcContext *ctx, RCore *core) {
return SUCCESS_RESULT ();
}
/* Find function ID at a given offset */
static int find_function_at_offset(HbcContext *ctx, u32 offset, u32 *out_id) {
/* Find function ID containing a given r2 vaddr.
*
* r2 reports vaddrs (HBC_VADDR_BASE + paddr), but HBCFunc::offset is the raw
* bytecode paddr. Translate before comparing — otherwise the lookup never
* matches and `pd:h` silently falls back to decompiling all functions
*(starting at the global one), which trips the AST budget and hides the
* function the user actually navigated to. */
static int find_function_at_offset(HbcContext *ctx, u64 vaddr, u32 *out_id) {
if (!out_id || !ctx->hbc) {
return -1;
}
u32 paddr = (vaddr >= HBC_VADDR_BASE)? (u32) (vaddr - HBC_VADDR_BASE): (u32)vaddr;
u32 count = hbc_function_count (ctx->hbc);
for (u32 i = 0; i < count; i++) {
HBCFunc fi;
Result res = hbc_get_function_info (ctx->hbc, i, &fi);
if (res.code == RESULT_SUCCESS) {
if (offset >= fi.offset && offset < (fi.offset + fi.size)) {
if (paddr >= fi.offset && paddr < (fi.offset + fi.size)) {
*out_id = i;
return 0;
}
@@ -128,23 +128,24 @@ static int find_function_at_offset(HbcContext *ctx, u32 offset, u32 *out_id) {
/* Create decompile options from radare2 settings */
static HBCDecompOptions make_decompile_options(RCore *core, bool show_offsets, u64 function_base) {
HBCDecompOptions opts = {
.pretty_literals = r_config_get_b (core->config, "hbc.pretty_literals"),
.suppress_comments = r_config_get_b (core->config, "hbc.suppress_comments"),
.show_offsets = show_offsets || r_config_get_b (core->config, "hbc.show_offsets"),
.pretty_literals = r_config_get_b (core->config, "r2hermes.pretty_literals"),
.suppress_comments = r_config_get_b (core->config, "r2hermes.suppress_comments"),
.show_offsets = show_offsets || r_config_get_b (core->config, "r2hermes.show_offsets"),
.function_base = function_base,
.comment_callback = r2_comment_callback,
.comment_context = core,
.flag_callback = r2_flag_callback,
.flag_context = core,
.skip_pass1_metadata = r_config_get_b (core->config, "hbc.skip_pass1"),
.skip_pass2_transform = r_config_get_b (core->config, "hbc.skip_pass2"),
.skip_pass3_forin = r_config_get_b (core->config, "hbc.skip_pass3"),
.skip_pass4_closure = r_config_get_b (core->config, "hbc.skip_pass4"),
.force_dispatch = r_config_get_b (core->config, "hbc.force_dispatch"),
.inline_closures = r_config_get_b (core->config, "hbc.inline_closures"),
.inline_threshold = r_config_get_i (core->config, "hbc.inline_threshold"),
.max_ast_statements = r_config_get_i (core->config, "hbc.max_ast"),
.max_output_bytes = r_config_get_i (core->config, "hbc.max_bytes")
.skip_pass1_metadata = r_config_get_b (core->config, "r2hermes.skip_pass1"),
.skip_pass2_transform = r_config_get_b (core->config, "r2hermes.skip_pass2"),
.skip_pass3_forin = r_config_get_b (core->config, "r2hermes.skip_pass3"),
.skip_pass4_closure = r_config_get_b (core->config, "r2hermes.skip_pass4"),
.force_dispatch = r_config_get_b (core->config, "r2hermes.force_dispatch"),
.inline_closures = r_config_get_b (core->config, "r2hermes.inline_closures"),
.inline_threshold = r_config_get_i (core->config, "r2hermes.inline_threshold"),
.inline_max_depth = r_config_get_i (core->config, "r2hermes.inline_depth"),
.max_ast_statements = r_config_get_i (core->config, "r2hermes.max_ast"),
.max_output_bytes = r_config_get_i (core->config, "r2hermes.max_bytes")
};
return opts;
}
@@ -158,8 +159,7 @@ static void cmd_decompile_current_ex(HbcContext *ctx, RCore *core, bool show_off
}
u32 function_id = 0;
/* Get current offset from r2 core */
int found = find_function_at_offset (ctx, (u32)core->addr, &function_id);
int found = find_function_at_offset (ctx, (u64)core->addr, &function_id);
if (found == 0) {
/* Found function at current offset - get its base address */
@@ -260,6 +260,57 @@ static void cmd_list_functions(HbcContext *ctx, RCore *core) {
}
}
/* List functions containing direct eval instructions */
static void cmd_list_eval_functions(HbcContext *ctx, RCore *core) {
Result res = hbc_load_current_binary (ctx, core);
if (res.code != RESULT_SUCCESS) {
R_LOG_ERROR ("%s", safe_errmsg (res.error_message));
return;
}
HBCHeader header;
res = hbc_get_header (ctx->hbc, &header);
if (res.code != RESULT_SUCCESS) {
R_LOG_ERROR ("reading header: %s", safe_errmsg (res.error_message));
return;
}
HBCISA isa = hbc_isa_getv (header.version);
if (!isa.instructions) {
R_LOG_ERROR ("No ISA for Hermes bytecode version %u", header.version);
return;
}
const u32 count = hbc_function_count (ctx->hbc);
for (u32 fid = 0; fid < count; fid++) {
HBCFunc info;
if (hbc_get_function_info (ctx->hbc, fid, &info).code != RESULT_SUCCESS) {
continue;
}
const u8 *code = NULL;
u32 size = 0;
if (hbc_get_function_bytecode (ctx->hbc, fid, &code, &size).code != RESULT_SUCCESS || !code) {
continue;
}
for (u32 pc = 0; pc < size;) {
const u8 op = code[pc];
const Instruction *inst = (op < isa.count)? &isa.instructions[op]: NULL;
if (!inst || !inst->name || !inst->binary_size) {
break;
}
if (hbc_canonical_opcode_from_name (inst->name) == OP_DirectEval) {
r_cons_printf (core->cons,
"0x%08" PFMT64x " f=%u +0x%x func=0x%08" PFMT64x " name=%s\n",
(ut64)HBC_VADDR_BASE + info.offset + pc,
fid,
pc,
(ut64)HBC_VADDR_BASE + info.offset,
r_str_get_fail (info.name, "unknown"));
}
pc += inst->binary_size;
}
}
}
/* Show file information */
static void cmd_file_info(HbcContext *ctx, RCore *core) {
Result res = hbc_load_current_binary (ctx, core);
@@ -438,10 +489,7 @@ static void cmd_source_lines(HbcContext *ctx, RCore *core, const char *arg) {
if (filter && sl->function_id != filter_id) {
continue;
}
r_cons_printf (core->cons, "0x%08x f=%u +0x%x %s:%u:%u stmt=%u\n",
sl->address, sl->function_id, sl->function_address,
sl->filename? sl->filename: "",
sl->line, sl->column, sl->statement);
r_cons_printf (core->cons, "0x%08x f=%u +0x%x %s:%u:%u stmt=%u\n", sl->address, sl->function_id, sl->function_address, sl->filename? sl->filename: "", sl->line, sl->column, sl->statement);
}
hbc_free_source_lines (&lines);
}
@@ -483,7 +531,7 @@ static void cmd_json(HbcContext *ctx, RCore *core, const char *addr_str) {
const char *as = r_str_trim_head_ro (addr_str);
if (*as) {
function_id = parse_function_id (addr_str);
} else if (find_function_at_offset (ctx, (u32)core->addr, &function_id) != 0) {
} else if (find_function_at_offset (ctx, (u64)core->addr, &function_id) != 0) {
emit_json_error (core, "No function at current offset");
return;
}
@@ -592,7 +640,7 @@ static void cmd_json(HbcContext *ctx, RCore *core, const char *addr_str) {
}
/* ==========================================================================
* Literal-buffer commands (pd:hL*)
* Literal-buffer commands (r2hermes-L*)
*
* Work against the lazy HBC literal cache. Registration in r2 is additive:
* flags (lit.arr.* / lit.obj.*) and CC comments are created at the literal
@@ -646,21 +694,111 @@ static Result ensure_hbc_loaded(HbcContext *ctx, RCore *core, HBC **out) {
return res;
}
static void cmd_lit_list(HbcContext *ctx, RCore *core, bool as_json) {
/* Populate the literal cache on demand by scanning function code. */
static bool ensure_lit_cache_populated(RCore *core, HBC *hbc) {
if (hbc_literals_count (hbc) > 0) {
return true;
}
Result r = hbc_literals_scan_code (hbc, NULL);
if (r.code != RESULT_SUCCESS) {
R_LOG_ERROR ("scan failed: %s", safe_errmsg (r.error_message));
return false;
}
register_all_artifacts (core, hbc);
return true;
}
/* Shared setup for lit list commands. Returns the entries and count on
* success; writes a non-NULL *out_arr only when *out_count > 0. */
static Result lit_list_collect(HbcContext *ctx, RCore *core, const HBCLiteralEntry **out_arr, u32 *out_n) {
*out_arr = NULL;
*out_n = 0;
HBC *hbc = NULL;
Result r = ensure_hbc_loaded (ctx, core, &hbc);
if (r.code != RESULT_SUCCESS) {
return r;
}
if (!ensure_lit_cache_populated (core, hbc)) {
return ERROR_RESULT (RESULT_ERROR_INVALID_DATA, "literal cache not populated");
}
r = hbc_literals_list (hbc, out_arr, out_n);
if (r.code != RESULT_SUCCESS) {
return r;
}
return SUCCESS_RESULT ();
}
static void cmd_lit_list_quiet(HbcContext *ctx, RCore *core) {
const HBCLiteralEntry *arr = NULL;
u32 n = 0;
Result r = lit_list_collect (ctx, core, &arr, &n);
if (r.code != RESULT_SUCCESS) {
R_LOG_ERROR ("%s", safe_errmsg (r.error_message));
return;
}
for (u32 i = 0; i < n; i++) {
const HBCLiteralEntry *e = &arr[i];
r_cons_printf (core->cons, "0x%" PFMT64x " %s\n", (ut64)HBC_VADDR_BASE + e->paddr, e->formatted? e->formatted: "");
}
}
static void print_xref_line(RCore *core, const HBCLiteralEntry *e, bool va) {
ut64 base = va? (ut64)HBC_VADDR_BASE: 0;
r_cons_printf (core->cons, "0x%" PFMT64x, base + e->paddr);
for (u32 j = 0; j < e->xref_count; j++) {
r_cons_printf (core->cons, " 0x%" PFMT64x, base + (ut64)e->xref_addrs[j]);
}
r_cons_println (core->cons, "");
}
static void cmd_lit_list_xrefs(HbcContext *ctx, RCore *core, const char *args) {
const HBCLiteralEntry *arr = NULL;
u32 n = 0;
if (hbc_literals_list (hbc, &arr, &n).code != RESULT_SUCCESS) {
R_LOG_ERROR ("list failed");
Result r = lit_list_collect (ctx, core, &arr, &n);
if (r.code != RESULT_SUCCESS) {
R_LOG_ERROR ("%s", safe_errmsg (r.error_message));
return;
}
bool va = r_config_get_b (core->config, "io.va");
/* Parse optional address argument. Accepts 0x... or bare hex. */
if (args) {
while (*args && isspace ((unsigned char)*args)) {
args++;
}
}
if (args && *args) {
ut64 want = r_num_get (core->num, args);
ut64 base = va? (ut64)HBC_VADDR_BASE: 0;
ut64 target_paddr = (want >= base)? (want - base): want;
const HBCLiteralEntry *match = NULL;
for (u32 i = 0; i < n; i++) {
if (arr[i].paddr == target_paddr) {
match = &arr[i];
break;
}
}
if (!match) {
R_LOG_ERROR ("no literal at that address");
return;
}
print_xref_line (core, match, va);
return;
}
for (u32 i = 0; i < n; i++) {
print_xref_line (core, &arr[i], va);
}
}
static void cmd_lit_list(HbcContext *ctx, RCore *core, bool as_json) {
const HBCLiteralEntry *arr = NULL;
u32 n = 0;
Result r = lit_list_collect (ctx, core, &arr, &n);
if (r.code != RESULT_SUCCESS) {
R_LOG_ERROR ("%s", safe_errmsg (r.error_message));
return;
}
if (n == 0) {
r_cons_println (core->cons, "(cache empty — run pd:hLs to scan)");
R_LOG_INFO ("No SLP literals found");
return;
}
if (as_json) {
@@ -690,33 +828,33 @@ static void cmd_lit_list(HbcContext *ctx, RCore *core, bool as_json) {
return;
}
r_cons_printf (core->cons, "literals: %u\n", n);
bool va = r_config_get_b (core->config, "io.va");
for (u32 i = 0; i < n; i++) {
const HBCLiteralEntry *e = &arr[i];
r_cons_printf (core->cons,
"%-6s n=%-4u id=(%u,%u) paddr=0x%08x xrefs=%u %s\n",
kind_label (e->kind), e->num_items, e->primary_id,
e->secondary_id, e->paddr, e->xref_count,
e->formatted? e->formatted: "");
if (va) {
r_cons_printf (core->cons,
"%-6s n=%-4u id=(%u,%u) vaddr=0x%08" PFMT64x " xrefs=%u %s\n",
kind_label (e->kind),
e->num_items,
e->primary_id,
e->secondary_id,
(ut64)HBC_VADDR_BASE + e->paddr,
e->xref_count,
e->formatted? e->formatted: "");
} else {
r_cons_printf (core->cons,
"%-6s n=%-4u id=(%u,%u) paddr=0x%08x xrefs=%u %s\n",
kind_label (e->kind),
e->num_items,
e->primary_id,
e->secondary_id,
e->paddr,
e->xref_count,
e->formatted? e->formatted: "");
}
}
}
static void cmd_lit_scan_code(HbcContext *ctx, RCore *core) {
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 n = 0;
r = hbc_literals_scan_code (hbc, &n);
if (r.code != RESULT_SUCCESS) {
R_LOG_ERROR ("scan failed: %s", safe_errmsg (r.error_message));
return;
}
register_all_artifacts (core, hbc);
r_cons_printf (core->cons, "scanned code, %u distinct literals (flags + xrefs registered)\n", n);
}
static void cmd_lit_scan_pool(HbcContext *ctx, RCore *core, HBCLiteralKind kind) {
HBC *hbc = NULL;
Result r = ensure_hbc_loaded (ctx, core, &hbc);
@@ -735,12 +873,83 @@ static void cmd_lit_scan_pool(HbcContext *ctx, RCore *core, HBCLiteralKind kind)
for (u32 i = 0; i < n; i++) {
r_cons_printf (core->cons,
" paddr=0x%08x pool_off=0x%08x n=%-4u tag=%u\n",
groups[i].paddr, groups[i].pool_offset, groups[i].num_items,
groups[i].paddr,
groups[i].pool_offset,
groups[i].num_items,
groups[i].tag);
}
free (groups);
}
/* Print r2 oneliners that register flag + comment + xrefs for one entry.
* When va is true addresses are shown as vaddrs (HBC_VADDR_BASE + paddr),
* otherwise as paddr. The formatted literal text is embedded in a CC comment;
* embedded newlines are squashed into spaces and a single quote is escaped. */
static void print_r2_for_entry(RCore *core, const HBCLiteralEntry *e, bool va) {
ut64 base = va? (ut64)HBC_VADDR_BASE: 0;
ut64 vaddr = base + e->paddr;
const char *prefix = lit_kind_prefix (e->kind);
/* flag at literal vaddr */
r_cons_printf (core->cons, "f %s0x%x 1 @ 0x%" PFMT64x "\n", prefix, e->paddr, vaddr);
/* embed the formatted text as a comment at the literal vaddr */
if (e->formatted && *e->formatted) {
char *clean = strdup (e->formatted);
if (clean) {
for (char *p = clean; *p; p++) {
if (*p == '\n' || *p == '\r') {
*p = ' ';
} else if (*p == '\'') {
*p = '"';
}
}
r_cons_printf (core->cons, "\" /* %s */ CC %s @ 0x%" PFMT64x "\n", prefix, clean, vaddr);
free (clean);
}
}
/* xref from each call site to the literal */
for (u32 j = 0; j < e->xref_count; j++) {
ut64 from = base + (ut64)e->xref_addrs[j];
r_cons_printf (core->cons, "axd 0x%" PFMT64x " 0x%" PFMT64x "\n", from, vaddr);
}
}
static void cmd_lit_print_r2(HbcContext *ctx, RCore *core, const char *args) {
const HBCLiteralEntry *arr = NULL;
u32 n = 0;
Result r = lit_list_collect (ctx, core, &arr, &n);
if (r.code != RESULT_SUCCESS) {
R_LOG_ERROR ("%s", safe_errmsg (r.error_message));
return;
}
bool va = r_config_get_b (core->config, "io.va");
if (args) {
while (*args && isspace ((unsigned char)*args)) {
args++;
}
}
if (args && *args) {
ut64 want = r_num_get (core->num, args);
ut64 base = va? (ut64)HBC_VADDR_BASE: 0;
ut64 target_paddr = (want >= base)? (want - base): want;
const HBCLiteralEntry *match = NULL;
for (u32 i = 0; i < n; i++) {
if (arr[i].paddr == target_paddr) {
match = &arr[i];
break;
}
}
if (!match) {
R_LOG_ERROR ("no literal at that address");
return;
}
print_r2_for_entry (core, match, va);
return;
}
for (u32 i = 0; i < n; i++) {
print_r2_for_entry (core, &arr[i], va);
}
}
static void cmd_lit_reset(HbcContext *ctx, RCore *core) {
HBC *hbc = NULL;
Result r = ensure_hbc_loaded (ctx, core, &hbc);
@@ -759,19 +968,17 @@ static void cmd_lit_get(HbcContext *ctx, RCore *core, const char *args) {
R_LOG_ERROR ("%s", safe_errmsg (r.error_message));
return;
}
/* Syntax: pd:hLg a <num> <array_id> (array)
* pd:hLg o <num> <primary> [<sec>] (object) */
/* Syntax: r2hermes-Lg a <num> <array_id> (array)
* r2hermes-Lg o <num> <primary> [<sec>] (object) */
char kindc = 0;
u64 num = 0, primary = 0, secondary = 0;
if (!args || sscanf (args, " %c %" SCNu64 " %" SCNu64 " %" SCNu64,
&kindc, &num, &primary, &secondary) < 3) {
R_LOG_ERROR ("usage: pd:hLg {a|o} <num_items> <primary_id> [<secondary_id>]");
if (!args || sscanf (args, " %c %" SCNu64 " %" SCNu64 " %" SCNu64, &kindc, &num, &primary, &secondary) < 3) {
R_LOG_ERROR ("usage: r2hermes-Lg {a|o} <num_items> <primary_id> [<secondary_id>]");
return;
}
HBCLiteralKind kind = (kindc == 'a')? HBC_LIT_ARRAY: HBC_LIT_OBJECT;
char *text = NULL;
r = hbc_literals_format_raw (hbc, kind, (u32)num, (u32)primary,
(u32)secondary, &text);
r = hbc_literals_format_raw (hbc, kind, (u32)num, (u32)primary, (u32)secondary, &text);
if (r.code != RESULT_SUCCESS || !text) {
R_LOG_ERROR ("format failed: %s", safe_errmsg (r.error_message));
free (text);
@@ -785,21 +992,26 @@ static void cmd_lit_toggle_inline(RCore *core) {
bool cur = hbc_get_inline_literals ();
bool next = !cur;
hbc_set_inline_literals (next);
r_config_set_b (core->config, "hbc.inline_buffer_literals", next);
r_config_set_b (core->config, "r2hermes.inline_buffer_literals", next);
r_cons_printf (core->cons, "inline buffer literals: %s\n", next? "on": "off");
}
static const char LIT_HELP[] =
"Usage: pd:hL[subcmd]\n"
" pd:hL List cached literals\n"
" pd:hLj List as JSON\n"
" pd:hLs Scan all code; register literals as flags/xrefs/comments\n"
" pd:hLp[ao] Scan SLP pool (default: arrays; a=arrays, o=objects)\n"
" pd:hLr Reset literal cache (does not remove r2 flags/comments)\n"
" pd:hLg <k> <n> <primary> [<sec>]\n"
"Usage: r2hermes-L[subcmd]\n"
" r2hermes-L List cached literals (auto-scans code if cache is empty)\n"
" r2hermes-Lj List as JSON (auto-scans if needed)\n"
" r2hermes-Lq List as `<vaddr> <formatted>` per line (one per literal)\n"
" r2hermes-Lx [addr] List as `<vaddr> <xref1> <xref2> ...` (one per literal)\n"
" With addr: print xrefs for that single literal only\n"
" r2hermes-Lp[ao] Scan SLP pool (default: arrays; a=arrays, o=objects)\n"
" r2hermes-LR Reset literal cache (does not remove r2 flags/comments)\n"
" r2hermes-Lr [addr] Print r2 oneliners (f/CC/axd) to register flag+comment+xrefs\n"
" With addr: print oneliners for that single literal only\n"
" r2hermes-Lg <k> <n> <primary> [<sec>]\n"
" Format a literal from raw params (k=a|o)\n"
" pd:hLi Toggle inline literal comments in disasm\n"
" pd:hL? This help\n";
" r2hermes-Li Toggle inline literal comments in disasm\n"
" r2hermes-L? This help\n"
"\nDeprecated alias: pd:hL[subcmd]\n";
static void cmd_literals(HbcContext *ctx, RCore *core, const char *arg) {
while (*arg && isspace ((unsigned char)*arg)) {
@@ -812,18 +1024,25 @@ static void cmd_literals(HbcContext *ctx, RCore *core, const char *arg) {
case 'j':
cmd_lit_list (ctx, core, true);
break;
case 's':
cmd_lit_scan_code (ctx, core);
case 'q':
cmd_lit_list_quiet (ctx, core);
break;
case 'p': {
char k = arg[1];
HBCLiteralKind kind = (k == 'o')? HBC_LIT_OBJECT: HBC_LIT_ARRAY;
cmd_lit_scan_pool (ctx, core, kind);
case 'x':
cmd_lit_list_xrefs (ctx, core, arg + 1);
break;
}
case 'r':
case 'p':
{
char k = arg[1];
HBCLiteralKind kind = (k == 'o')? HBC_LIT_OBJECT: HBC_LIT_ARRAY;
cmd_lit_scan_pool (ctx, core, kind);
break;
}
case 'R':
cmd_lit_reset (ctx, core);
break;
case 'r':
cmd_lit_print_r2 (ctx, core, arg + 1);
break;
case 'g':
cmd_lit_get (ctx, core, arg + 1);
break;
@@ -834,48 +1053,76 @@ static void cmd_literals(HbcContext *ctx, RCore *core, const char *arg) {
r_cons_print (core->cons, LIT_HELP);
break;
default:
R_LOG_ERROR ("Unknown subcommand. Use pd:hL? for help");
R_LOG_ERROR ("Unknown subcommand. Use r2hermes-L? for help");
break;
}
}
/* Show help */
static void r2hermes_help(RCore *core) {
const char msg[] =
"Usage: r2hermes[-arg] # see also pd:h for decompilation\n"
"r2hermes-h - help message (same as r2hermes-?, see pd:h? too)\n"
"r2hermes-E - List functions containing eval instructions\n"
"r2hermes-H - Show file information and hash status\n"
"r2hermes-L[?] - SLP literal cache: list/scan/reset/format/toggle\n"
"r2hermes-S[jr?] - emit SBOM from SLP literals (j=CycloneDX JSON, r=raw input)\n";
r_cons_print (core->cons, msg);
}
static void cmd_help(RCore *core) {
r_cons_print (core->cons,
"Usage: pd:h[subcommand]\n"
"Usage: pd:h[subcommand] (see r2hermes-? for more commands)\n"
"Hermes bytecode decompiler via libhbc\n\n"
"Subcommands:\n"
" pd:h - Decompile function at current offset (or all if not in function)\n"
" pd:hc [id] - Decompile function by id\n"
" pd:ha - Decompile all functions\n"
" pd:hf - List all functions\n"
" pd:hi - Show file information\n"
" pd:hj [id] - JSON output for function\n"
" pd:ho [id] - Decompile with offsets (addresses) per statement\n"
" pd:hoa - Decompile all with offsets\n"
" pd:hs [id] - List source-line information\n"
" pd:hL[?] - SLP literal cache: list/scan/reset (see pd:hL?)\n"
" pd:hL[?] - Deprecated alias for r2hermes-L[?]\n"
" pd:h? - Show this help\n"
"\nNote: r2 comments (CC command) are automatically inlined in decompiler output.\n");
}
/* Main handler for pd:h commands */
static bool cmd_handler(RCorePluginSession *s, const char *input) {
RCore *core = s? s->core: NULL;
#include "sbom.inc.c"
if (!core || !input) {
return false;
}
if (!r_str_startswith (input, "pd:h")) {
return false;
}
HbcContext *ctx = s->data;
if (!ctx) {
return false;
static void cmd_r2hermes(RCore *core, HbcContext *ctx, const char *arg) {
arg = r_str_trim_head_ro (arg);
if (*arg == '-') {
switch (arg[1]) {
case 'E':
cmd_list_eval_functions (ctx, core);
break;
case 'H':
cmd_file_info (ctx, core);
break;
case 'L':
cmd_literals (ctx, core, arg + 2);
break;
case 'S':
cmd_sbom (ctx, core, arg + 2);
break;
case '?':
case 'h':
r2hermes_help (core);
break;
default:
R_LOG_ERROR ("Invalid argument. See r2hermes-?");
break;
}
} else if (!*arg) {
r2hermes_help (core);
} else {
R_LOG_ERROR ("Invalid argument for r2hermes");
}
}
const char *arg = input + 4;
static void cmd_pdh(RCore *core, HbcContext *ctx, const char *arg) {
// PANCAKE all those cmd_* functions taking ctx as first argument.. they should take core first and then ctx.. so swap them all
switch (*arg) {
case '\0': /* pd:h */
case ' ': /* pd:h (with spaces) */
@@ -894,17 +1141,9 @@ static bool cmd_handler(RCorePluginSession *s, const char *input) {
case 'f': /* pd:hf */
cmd_list_functions (ctx, core);
break;
case 'i': /* pd:hi */
cmd_file_info (ctx, core);
case 'j': /* pd:hj [id] */
cmd_json (ctx, core, r_str_trim_head_ro (arg + 1));
break;
case 'j': { /* pd:hj [id] */
const char *addr_str = arg + 1;
while (*addr_str && isspace ((unsigned char)*addr_str)) {
addr_str++;
}
cmd_json (ctx, core, addr_str);
break;
}
case 'o': { /* pd:ho [id] or pd:hoa */
const char *sub = arg + 1;
if (*sub == 'a') { // "pd:hoa"
@@ -921,7 +1160,8 @@ static bool cmd_handler(RCorePluginSession *s, const char *input) {
}
break;
}
case 'L': /* pd:hL* — literal cache / buffer-literal commands */
case 'L': /* pd:hL* — deprecated alias for r2hermes-L* */
R_LOG_WARN ("pd:hL is deprecated; use r2hermes-L instead");
cmd_literals (ctx, core, arg + 1);
break;
case 's': /* pd:hs [id] */
@@ -934,8 +1174,24 @@ static bool cmd_handler(RCorePluginSession *s, const char *input) {
R_LOG_ERROR ("Unknown subcommand. Use pd:h? for help");
break;
}
}
return true;
/* Main handler for pd:h commands */
static bool cmd_handler(RCorePluginSession *s, const char *input) {
if (s && s->core && input) {
HbcContext *ctx = s->data;
if (!ctx) {
R_LOG_ERROR ("Missing HBC context, not an hermes bin here?")
} else if (r_str_startswith (input, "pd:h")) {
cmd_pdh (s->core, ctx, input + 4);
} else if (r_str_startswith (input, "r2hermes")) {
cmd_r2hermes (s->core, ctx, input + 8);
} else {
return false;
}
return true;
}
return false;
}
static bool cb_inline_buffer_literals(void *user, void *data) {
@@ -973,73 +1229,78 @@ static bool plugin_init(RCorePluginSession *s) {
r_config_lock (cfg, false);
RConfigNode *node = r_config_set (cfg, "hbc.pretty_literals", "true");
RConfigNode *node = r_config_set (cfg, "r2hermes.pretty_literals", "true");
if (node) {
r_config_node_desc (node, "Format literals nicely (objects, arrays, etc)");
}
node = r_config_set (cfg, "hbc.suppress_comments", "false");
node = r_config_set (cfg, "r2hermes.suppress_comments", "false");
if (node) {
r_config_node_desc (node, "Omit comments in decompiled output");
}
node = r_config_set (cfg, "hbc.show_offsets", "false");
node = r_config_set (cfg, "r2hermes.show_offsets", "false");
if (node) {
r_config_node_desc (node, "Show bytecode offsets for each statement");
}
/* Optimization/transformation pass control */
node = r_config_set (cfg, "hbc.skip_pass1", "false");
node = r_config_set (cfg, "r2hermes.skip_pass1", "false");
if (node) {
r_config_node_desc (node, "Skip pass 1: metadata collection (closure/generator/async flags)");
}
node = r_config_set (cfg, "hbc.skip_pass2", "false");
node = r_config_set (cfg, "r2hermes.skip_pass2", "false");
if (node) {
r_config_node_desc (node, "Skip pass 2: code transformation to tokens and statements");
}
node = r_config_set (cfg, "hbc.skip_pass3", "false");
node = r_config_set (cfg, "r2hermes.skip_pass3", "false");
if (node) {
r_config_node_desc (node, "Skip pass 3: for-in loop parsing (structural recovery via CFG)");
}
node = r_config_set (cfg, "hbc.skip_pass4", "false");
node = r_config_set (cfg, "r2hermes.skip_pass4", "false");
if (node) {
r_config_node_desc (node, "Skip pass 4: closure variable naming and environment resolution");
}
/* Control flow rendering options */
node = r_config_set (cfg, "hbc.force_dispatch", "false");
node = r_config_set (cfg, "r2hermes.force_dispatch", "false");
if (node) {
r_config_node_desc (node, "Force switch/case dispatch loop even for linear multi-block functions");
}
node = r_config_set (cfg, "hbc.inline_closures", "true");
node = r_config_set (cfg, "r2hermes.inline_closures", "true");
if (node) {
r_config_node_desc (node, "Inline closure definitions into parent function (true/false)");
}
node = r_config_set (cfg, "hbc.inline_threshold", "0");
node = r_config_set (cfg, "r2hermes.inline_threshold", "256");
if (node) {
r_config_node_desc (node, "Max bytecode size (bytes) for closure inlining (0=no limit, -1=never inline)");
}
node = r_config_set_i (cfg, "hbc.max_ast", 5000);
node = r_config_set_i (cfg, "r2hermes.inline_depth", 3);
if (node) {
r_config_node_desc (node, "Max nested inline depth for closures (0=no limit); bounds module-graph explosion");
}
node = r_config_set_i (cfg, "r2hermes.max_ast", 5000);
if (node) {
r_config_node_desc (node, "Abort AST build once N statements per function (0=unlimited)");
}
node = r_config_set_i (cfg, "hbc.max_bytes", 262144);
node = r_config_set_i (cfg, "r2hermes.max_bytes", 262144);
if (node) {
r_config_node_desc (node, "Stop decompilation once total output reaches N bytes (0=unlimited)");
}
/* Default off: inlining the SLP literal as a disasm-line comment makes
* arch.decode O(n) in literal size. Use pd:hL* to inspect literals
* arch.decode O (n) in literal size. Use r2hermes-L* to inspect literals
* without impacting disasm speed. */
hbc_set_inline_literals (false);
node = r_config_set_b_cb (cfg, "hbc.inline_buffer_literals", false, cb_inline_buffer_literals);
node = r_config_set_b_cb (cfg, "r2hermes.inline_buffer_literals", false, cb_inline_buffer_literals);
if (node) {
r_config_node_desc (node, "Inline SLP buffer literals as comments in disasm (slow; default off)");
}
@@ -1065,7 +1326,7 @@ RCorePlugin r_core_plugin_r2hermes = {
.name = "core_hbc",
.desc = "Hermes bytecode decompiler plugin",
.author = "pancake",
.license = "LGPL-3.0-only",
.license = "BSD-3-Clause",
},
.call = cmd_handler,
.init = plugin_init,
+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
+243 -82
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/common.h>
#include <hbc/hbc.h>
#include <hbc/literals.h>
@@ -23,20 +25,28 @@ typedef struct {
CliFlags flags;
} CliContext;
typedef Result (*CommandHandler)(const CliContext *ctx, int argc, char **argv);
typedef struct { const char *name; const char *help; CommandHandler handler; } Command;
typedef Result(*CommandHandler)(const CliContext *ctx, int argc, char **argv);
typedef struct {
const char *name;
const char *help;
CommandHandler handler;
} Command;
static void json_print_string(const char *s);
static const struct { const char *lname; char sname; size_t offset; } flag_table[] = {
{ "json", 'j', offsetof (CliFlags, json) },
{ "verbose", 'v', offsetof (CliFlags, verbose) },
{ "bytecode", 'b', offsetof (CliFlags, bytecode) },
{ "debug", 'd', offsetof (CliFlags, debug) },
{ "asmsyntax", 0, offsetof (CliFlags, asm_syntax) },
{ "pretty-literals", 'P', offsetof (CliFlags, pretty_literals) },
static const struct {
const char *lname;
char sname;
size_t offset;
} flag_table[] = {
{ "json", 'j', offsetof (CliFlags, json) },
{ "verbose", 'v', offsetof (CliFlags, verbose) },
{ "bytecode", 'b', offsetof (CliFlags, bytecode) },
{ "debug", 'd', offsetof (CliFlags, debug) },
{ "asmsyntax", 0, offsetof (CliFlags, asm_syntax) },
{ "pretty-literals", 'P', offsetof (CliFlags, pretty_literals) },
{ "no-pretty-literals", 'N', offsetof (CliFlags, no_pretty_literals) },
{ "no-comments", 'C', offsetof (CliFlags, no_comments) },
{ "no-comments", 'C', offsetof (CliFlags, no_comments) },
};
#define FLAG_TABLE_N (sizeof (flag_table) / sizeof (flag_table[0]))
@@ -68,7 +78,7 @@ static bool flag_set(CliFlags *f, const char *lname, char sname) {
bool m = lname? !strcmp (lname, flag_table[i].lname)
: (flag_table[i].sname && flag_table[i].sname == sname);
if (m) {
*(bool *)((char *)f + flag_table[i].offset) = true;
*(bool *) ((char *)f + flag_table[i].offset) = true;
return true;
}
}
@@ -166,9 +176,15 @@ static Result read_entire_file(const char *path, char **out_buf, size_t *out_len
}
static int hex_nibble(char c) {
if (c >= '0' && c <= '9') { return c - '0'; }
if (c >= 'a' && c <= 'f') { return 10 + c - 'a'; }
if (c >= 'A' && c <= 'F') { return 10 + c - 'A'; }
if (c >= '0' && c <= '9') {
return c - '0';
}
if (c >= 'a' && c <= 'f') {
return 10 + c - 'a';
}
if (c >= 'A' && c <= 'F') {
return 10 + c - 'A';
}
return -1;
}
@@ -181,15 +197,24 @@ static Result parse_hex_bytes(const char *s, u8 *out, size_t out_cap, size_t *ou
if (!s || !*s) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Empty hex string");
}
for (const char *p = s; *p; ) {
while (*p && is_hex_sep (*p)) { p++; }
if (!*p) { break; }
if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) { p += 2; continue; }
for (const char *p = s; *p;) {
while (*p && is_hex_sep (*p)) {
p++;
}
if (!*p) {
break;
}
if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) {
p += 2;
continue;
}
int hi = hex_nibble (*p++);
if (hi < 0) {
return errorf (RESULT_ERROR_INVALID_ARGUMENT, "Invalid hex character: '%c'", p[-1]);
}
while (*p && is_hex_sep (*p)) { p++; }
while (*p && is_hex_sep (*p)) {
p++;
}
int lo = hex_nibble (*p++);
if (lo < 0) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Odd number of hex nibbles");
@@ -197,7 +222,7 @@ static Result parse_hex_bytes(const char *s, u8 *out, size_t out_cap, size_t *ou
if (*out_len >= out_cap) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Too many bytes (max 64)");
}
out[(*out_len)++] = (u8)((hi << 4) | lo);
out[(*out_len)++] = (u8) ((hi << 4) | lo);
}
if (!*out_len) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "No bytes parsed");
@@ -220,7 +245,11 @@ static Result cmd_d(const CliContext *ctx, int argc, char **argv) {
continue;
}
printf ("Function %u: %s\n Offset: 0x%08x\n Size: %u bytes\n Params: %u\n\n",
i, fi.name? fi.name: "unnamed", fi.offset, fi.size, fi.param_count);
i,
fi.name? fi.name: "unnamed",
fi.offset,
fi.size,
fi.param_count);
}
hbc_close (hbc);
return SUCCESS_RESULT ();
@@ -243,9 +272,13 @@ static Result cmd_dis(const CliContext *ctx, int argc, char **argv) {
RETURN_IF_ERROR (parse_hex_bytes (argv[0], bytes, sizeof (bytes), &bcount));
HBCInsnInfo sinfo = { 0 };
HBCDecodeCtx dec_ctx = {
.bytes = bytes, .len = bcount, .bytecode_version = 96, .pc = 0,
.asm_syntax = ctx->flags.asm_syntax, .build_objects = true,
};
.bytes = bytes,
.len = bcount,
.bytecode_version = 96,
.pc = 0,
.asm_syntax = ctx->flags.asm_syntax,
.build_objects = true,
};
Result r = hbc_dec (&dec_ctx, &sinfo);
if (r.code == RESULT_SUCCESS) {
printf ("%s\n", sinfo.text? sinfo.text: "");
@@ -330,8 +363,7 @@ static Result cmd_v(const CliContext *ctx, int argc, char **argv) {
return err;
}
fprintf (out, "HBC File Validation Report\n===========================\n\n");
fprintf (out, "%llu (magic)\n%u (version)\n%u functions\n%u strings\n\nFile appears valid.\n",
(unsigned long long)h.magic, h.version, h.functionCount, h.stringCount);
fprintf (out, "%llu (magic)\n%u (version)\n%u functions\n%u strings\n\nFile appears valid.\n", (unsigned long long)h.magic, h.version, h.functionCount, h.stringCount);
if (out != stdout) {
fclose (out);
}
@@ -394,8 +426,7 @@ static Result cmd_h(const CliContext *ctx, int argc, char **argv) {
if (hh.version >= 99) {
fprintf (out, " String Switch Instruction Count: %u\n", hh.numStringSwitchImms);
}
fprintf (out, " %s: %u\n CJS Module Count: %u\n",
hh.version < 78? "CJS Module Offset": "Segment ID", hh.segmentID, hh.cjsModuleCount);
fprintf (out, " %s: %u\n CJS Module Count: %u\n", hh.version < 78? "CJS Module Offset": "Segment ID", hh.segmentID, hh.cjsModuleCount);
if (hh.version >= 84) {
fprintf (out, " Function Source Count: %u\n", hh.functionSourceCount);
}
@@ -432,14 +463,18 @@ static Result cmd_f(const CliContext *ctx, int argc, char **argv) {
return errorf (RESULT_ERROR_INVALID_ARGUMENT, "Usage: f <input> [n]");
}
u32 n = (argc == 2)? (u32)strtoul (argv[1], NULL, 0): 50;
if (!n) { n = 50; }
if (!n) {
n = 50;
}
HBC *hbc;
RETURN_IF_ERROR (open_hbc (argv[0], &hbc));
u32 fc = hbc_function_count (hbc);
u32 count = fc < n? fc: n;
for (u32 i = 0; i < count; i++) {
HBCFunc fi;
if (hbc_get_function_info (hbc, i, &fi).code != RESULT_SUCCESS) { continue; }
if (hbc_get_function_info (hbc, i, &fi).code != RESULT_SUCCESS) {
continue;
}
printf ("id=%u offset=0x%08x size=%u name=%s\n", i, fi.offset, fi.size, fi.name? fi.name: "");
}
hbc_close (hbc);
@@ -474,16 +509,24 @@ static Result cmd_sl(const CliContext *ctx, int argc, char **argv) {
if (json) {
printf ("%s{\"function\":%u,\"address\":%u,\"offset\":%u,\"line\":%u,\"column\":%u,\"statement\":%u,\"file\":",
first? "": ",",
sl->function_id, sl->address, sl->function_address,
sl->line, sl->column, sl->statement);
sl->function_id,
sl->address,
sl->function_address,
sl->line,
sl->column,
sl->statement);
json_print_string (sl->filename);
putchar ('}');
first = false;
} else {
printf ("0x%08x f=%u +0x%x %s:%u:%u stmt=%u\n",
sl->address, sl->function_id, sl->function_address,
sl->address,
sl->function_id,
sl->function_address,
sl->filename? sl->filename: "",
sl->line, sl->column, sl->statement);
sl->line,
sl->column,
sl->statement);
}
}
if (json) {
@@ -500,7 +543,9 @@ static Result cmd_cmp(const CliContext *ctx, int argc, char **argv) {
return errorf (RESULT_ERROR_INVALID_ARGUMENT, "Usage: cmp <input> [n]");
}
u32 n = (argc == 2)? (u32)strtoul (argv[1], NULL, 0): 100;
if (!n) { n = 100; }
if (!n) {
n = 100;
}
HBC *hbc;
RETURN_IF_ERROR (open_hbc (argv[0], &hbc));
u32 fc = hbc_function_count (hbc);
@@ -523,29 +568,48 @@ static Result cmd_cmp(const CliContext *ctx, int argc, char **argv) {
char line[4096];
while (fgets (line, sizeof (line), py)) {
char *p = strstr (line, "=> [Function #");
if (!p) { continue; }
if (!p) {
continue;
}
p += strlen ("=> [Function #");
char *end = NULL;
long id = strtol (p, &end, 10);
if (end == p || id < 0 || (u32)id >= count) { continue; }
if (end == p || id < 0 || (u32)id >= count) {
continue;
}
char *ofp = strstr (end, " of ");
if (!ofp) { continue; }
if (!ofp) {
continue;
}
long sz = strtol (ofp + 4, &end, 10);
if (end == ofp + 4 || sz < 0) { continue; }
if (end == ofp + 4 || sz < 0) {
continue;
}
char *offp = strstr (end, " at 0x");
if (!offp) { continue; }
if (!offp) {
continue;
}
unsigned int off = 0;
if (sscanf (offp + 6, "%x", &off) != 1) { continue; }
if (sscanf (offp + 6, "%x", &off) != 1) {
continue;
}
py_sizes[id] = (u32)sz;
py_offs[id] = (u32)off;
}
fclose (py);
for (u32 i = 0; i < count; i++) {
HBCFunc fi;
if (hbc_get_function_info (hbc, i, &fi).code != RESULT_SUCCESS) { continue; }
if (hbc_get_function_info (hbc, i, &fi).code != RESULT_SUCCESS) {
continue;
}
const char *res = (fi.offset == py_offs[i] && fi.size == py_sizes[i])? "OK": "MISMATCH";
printf ("id=%u C(off=0x%08x,sz=%u) PY(off=0x%08x,sz=%u) => %s\n",
i, fi.offset, fi.size, py_offs[i], py_sizes[i], res);
i,
fi.offset,
fi.size,
py_offs[i],
py_sizes[i],
res);
}
free (py_sizes);
free (py_offs);
@@ -568,13 +632,15 @@ static Result disasm_function(HBC *hbc, u32 function_id, char **out) {
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "OOM");
}
buf[0] = '\0';
for (u32 offset = 0; offset < bytecode_size; ) {
for (u32 offset = 0; offset < bytecode_size;) {
HBCInsnInfo ii = { 0 };
HBCDecodeCtx dc = {
.bytes = bytecode + offset, .len = bytecode_size - offset,
.pc = fi.offset + offset, .bytecode_version = hdr.version,
.bytes = bytecode + offset,
.len = bytecode_size - offset,
.pc = fi.offset + offset,
.bytecode_version = hdr.version,
.hbc = hbc,
};
};
Result r = hbc_dec (&dc, &ii);
if (r.code != RESULT_SUCCESS || !ii.size) {
free (ii.text);
@@ -630,12 +696,20 @@ static Result cmd_cf(const CliContext *ctx, int argc, char **argv) {
char line_py[2048], line_c[2048];
size_t cpos = 0;
while (fgets (line_py, sizeof (line_py), py)) {
if (strncmp (line_py, ">> ", 3)) { continue; }
if (strncmp (line_py, ">> ", 3)) {
continue;
}
while (disasm_str[cpos] && strncmp (disasm_str + cpos, "==> ", 4)) {
while (disasm_str[cpos] && disasm_str[cpos] != '\n') { cpos++; }
if (disasm_str[cpos] == '\n') { cpos++; }
while (disasm_str[cpos] && disasm_str[cpos] != '\n') {
cpos++;
}
if (disasm_str[cpos] == '\n') {
cpos++;
}
}
if (!disasm_str[cpos]) {
break;
}
if (!disasm_str[cpos]) { break; }
size_t l = 0;
while (disasm_str[cpos + l] && disasm_str[cpos + l] != '\n' && l < sizeof (line_c) - 1) {
line_c[l] = disasm_str[cpos + l];
@@ -752,10 +826,17 @@ static Result lit_list_impl(const char *input, bool as_json) {
printf ("%s{\"kind\":\"%s\",\"num_items\":%u,"
"\"primary_id\":%u,\"secondary_id\":%u,\"paddr\":%u,"
"\"xrefs\":%u,\"formatted\":\"",
i? ",": "", lit_kind_name (e->kind), e->num_items,
e->primary_id, e->secondary_id, e->paddr, e->xref_count);
i? ",": "",
lit_kind_name (e->kind),
e->num_items,
e->primary_id,
e->secondary_id,
e->paddr,
e->xref_count);
for (const char *p = e->formatted; p && *p; p++) {
if (*p == '"' || *p == '\\') { putchar ('\\'); }
if (*p == '"' || *p == '\\') {
putchar ('\\');
}
putchar (*p);
}
printf ("\"}");
@@ -766,8 +847,12 @@ static Result lit_list_impl(const char *input, bool as_json) {
for (u32 i = 0; i < n; i++) {
const HBCLiteralEntry *e = &arr[i];
printf ("%-6s n=%-4u id=(%u,%u) paddr=0x%08x xrefs=%u %s\n",
lit_kind_name (e->kind), e->num_items, e->primary_id,
e->secondary_id, e->paddr, e->xref_count,
lit_kind_name (e->kind),
e->num_items,
e->primary_id,
e->secondary_id,
e->paddr,
e->xref_count,
e->formatted? e->formatted: "");
}
}
@@ -802,7 +887,10 @@ static Result lit_pool_impl(const char *input, HBCLiteralKind kind) {
printf ("%s pool: %u groups\n", lit_kind_name (kind), n);
for (u32 i = 0; i < n; i++) {
printf (" paddr=0x%08x pool_off=0x%08x n=%-4u tag=%u\n",
groups[i].paddr, groups[i].pool_offset, groups[i].num_items, groups[i].tag);
groups[i].paddr,
groups[i].pool_offset,
groups[i].num_items,
groups[i].tag);
}
free (groups);
hbc_close (hbc);
@@ -818,7 +906,10 @@ static Result lit_xrefs_impl(const char *input) {
const HBCLiteralEntry *e = &arr[i];
for (u32 j = 0; j < e->xref_count; j++) {
printf ("0x%08x -> %s 0x%08x (n=%u)\n",
e->xref_addrs[j], lit_kind_name (e->kind), e->paddr, e->num_items);
e->xref_addrs[j],
lit_kind_name (e->kind),
e->paddr,
e->num_items);
}
}
hbc_close (hbc);
@@ -852,10 +943,7 @@ static Result cmd_lit(const CliContext *ctx, int argc, char **argv) {
return errorf (RESULT_ERROR_INVALID_ARGUMENT,
"Usage: lit get <file> {a|o} <num> <primary> [<secondary>]");
}
return lit_get_impl (input, argv[2][0],
(u32)strtoul (argv[3], NULL, 0),
(u32)strtoul (argv[4], NULL, 0),
argc > 5? (u32)strtoul (argv[5], NULL, 0): 0);
return lit_get_impl (input, argv[2][0], (u32)strtoul (argv[3], NULL, 0), (u32)strtoul (argv[4], NULL, 0), argc > 5? (u32)strtoul (argv[5], NULL, 0): 0);
}
return errorf (RESULT_ERROR_INVALID_ARGUMENT, "unknown lit subcommand: %s", sub);
}
@@ -879,6 +967,14 @@ static void json_print_string(const char *s) {
putchar ('"');
}
static void json_print_nullable_string(const char *s) {
if (s) {
json_print_string (s);
} else {
fputs ("null", stdout);
}
}
static const char *binding_type_name(HBCBindingType type) {
return type == HBC_BINDING_EXPORT? "export": "import";
}
@@ -908,7 +1004,9 @@ static Result cmd_bind(const CliContext *ctx, int argc, char **argv) {
printf (",\"module\":");
json_print_string (b->module);
printf (",\"function_id\":%u,\"offset\":%u,\"string_id\":%u}",
b->function_id, b->offset, b->string_id);
b->function_id,
b->offset,
b->string_id);
}
puts ("]");
} else {
@@ -916,9 +1014,13 @@ static Result cmd_bind(const CliContext *ctx, int argc, char **argv) {
for (u32 i = 0; i < bindings.count; i++) {
HBCBinding *b = &bindings.bindings[i];
printf ("%-6s %-7s sid=%-5u fn=%-5u off=0x%08x %s%s%s\n",
binding_type_name (b->type), b->kind? b->kind: "",
b->string_id, b->function_id, b->offset,
b->module? b->module: "", b->module? ":": "",
binding_type_name (b->type),
b->kind? b->kind: "",
b->string_id,
b->function_id,
b->offset,
b->module? b->module: "",
b->module? ":": "",
b->name? b->name: "");
}
}
@@ -927,24 +1029,83 @@ static Result cmd_bind(const CliContext *ctx, int argc, char **argv) {
return SUCCESS_RESULT ();
}
static Result cmd_libs(const CliContext *ctx, int argc, char **argv) {
if (argc != 1) {
return errorf (RESULT_ERROR_INVALID_ARGUMENT, "Usage: libs <input>");
}
HBC *hbc;
RETURN_IF_ERROR (open_hbc (argv[0], &hbc));
HBCModules modules = { 0 };
Result r = hbc_list_modules (hbc, &modules);
if (r.code != RESULT_SUCCESS) {
hbc_close (hbc);
return r;
}
if (ctx->flags.json) {
putchar ('[');
for (u32 i = 0; i < modules.count; i++) {
HBCModule *m = &modules.modules[i];
printf ("%s{\"kind\":", i? ",": "");
json_print_string (m->kind);
printf (",\"name\":");
json_print_string (m->name);
printf (",\"path\":");
json_print_nullable_string (m->path);
printf (",\"version\":");
json_print_nullable_string (m->version);
printf (",\"function_id\":%u,\"offset\":%u,\"string_id\":%u,\"inferred\":%s}",
m->function_id,
m->offset,
m->string_id,
m->inferred? "true": "false");
}
puts ("]");
} else {
printf ("libs: %u\n", modules.count);
for (u32 i = 0; i < modules.count; i++) {
HBCModule *m = &modules.modules[i];
printf ("%-8s sid=%-5u fn=%-5u off=0x%08x %-30s",
m->kind? m->kind: "",
m->string_id,
m->function_id,
m->offset,
m->name? m->name: "");
if (m->version && *m->version) {
printf (" version=%s", m->version);
}
if (m->path && *m->path && (!m->name || strcmp (m->path, m->name))) {
printf (" path=%s", m->path);
}
if (m->inferred) {
printf (" inferred");
}
putchar ('\n');
}
}
hbc_free_modules (&modules);
hbc_close (hbc);
return SUCCESS_RESULT ();
}
static const Command commands[] = {
{ "d", "Disassemble a Hermes bytecode file", cmd_d },
{ "c", "Decompile a Hermes bytecode file", cmd_c },
{ "dis", "Disassemble raw hex bytes (rasm2-like)", cmd_dis },
{ "a", "Assemble a single instruction", cmd_a },
{ "asm", "Assemble instructions from file", cmd_asm },
{ "h", "Display header information", cmd_h },
{ "v", "Validate file and show details", cmd_v },
{ "r", "Generate an r2 script with function flags",cmd_r },
{ "f", "Dump first N function headers", cmd_f },
{ "sl", "List source-line information", cmd_sl },
{ "cmp", "Compare first N funcs with parser.txt", cmd_cmp },
{ "cf", "Compare one function vs Python disasm", cmd_cf },
{ "s", "Print a string by index", cmd_s },
{ "fs", "Find strings by substring", cmd_fs },
{ "sm", "Show string entry metadata", cmd_sm },
{ "d", "Disassemble a Hermes bytecode file", cmd_d },
{ "c", "Decompile a Hermes bytecode file", cmd_c },
{ "dis", "Disassemble raw hex bytes (rasm2-like)", cmd_dis },
{ "a", "Assemble a single instruction", cmd_a },
{ "asm", "Assemble instructions from file", cmd_asm },
{ "h", "Display header information", cmd_h },
{ "v", "Validate file and show details", cmd_v },
{ "r", "Generate an r2 script with function flags", cmd_r },
{ "f", "Dump first N function headers", cmd_f },
{ "sl", "List source-line information", cmd_sl },
{ "cmp", "Compare first N funcs with parser.txt", cmd_cmp },
{ "cf", "Compare one function vs Python disasm", cmd_cf },
{ "s", "Print a string by index", cmd_s },
{ "fs", "Find strings by substring", cmd_fs },
{ "sm", "Show string entry metadata", cmd_sm },
{ "lit", "SLP buffer literals: list, get, pool, xrefs", cmd_lit },
{ "bind", "List probable imports/exports/native bindings", cmd_bind },
{ "libs", "List probable bundled modules/libraries", cmd_libs },
};
#define COMMANDS_N (sizeof (commands) / sizeof (commands[0]))
@@ -954,7 +1115,7 @@ static void print_usage(const char *program_name) {
printf (" %-5s %s\n", commands[i].name, commands[i].help);
}
printf ("\nOptions (may appear anywhere after the command):\n");
printf (" --json, -j JSON output (lit list)\n");
printf (" --json, -j JSON output (lit list, bind, libs)\n");
printf (" --asmsyntax CPU-like asm syntax (dis)\n");
printf (" --verbose, -v Verbose output\n");
printf (" --bytecode, -b Include bytecode bytes\n");
+235 -14
View File
@@ -18,30 +18,40 @@ 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
@@ -49,10 +59,10 @@ EXPECT=<<EOF
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
@@ -60,10 +70,10 @@ EXPECT=<<EOF
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
@@ -71,10 +81,10 @@ EXPECT=<<EOF
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
@@ -82,10 +92,10 @@ EXPECT=<<EOF
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
@@ -126,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
@@ -214,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
+25 -1
View File
@@ -58,7 +58,7 @@ CMDS=<<EOF
i~cpu
EOF
EXPECT=<<EOF
cpu 96
cpu v96
EOF
RUN
@@ -719,6 +719,30 @@ 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
+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
+2
View File
@@ -1,3 +1,5 @@
/* radare2 - BSD - Copyright 2025-2026 - pancake */
#include <hbc/hbc.h>
#include <stdio.h>