mirror of
https://github.com/radareorg/r2hermes.git
synced 2026-11-09 04:19:34 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f5a033b45e | ||
|
|
78eaad9e55 | ||
|
|
6eb777ae11 | ||
|
|
d23ec88b12 | ||
|
|
0e49c04565 | ||
|
|
84abea0501 | ||
|
|
b84cc1a37c | ||
|
|
1489606813 | ||
|
|
41df2c071f | ||
|
|
5d0fab69c1 | ||
|
|
47f864ae72 | ||
|
|
93a276b894 | ||
|
|
4818337181 | ||
|
|
b557b3a00a | ||
|
|
6d99e666d9 | ||
|
|
9ee9be2b53 | ||
|
|
01f049a7c4 | ||
|
|
f5f2efd37c | ||
|
|
49282811c0 | ||
|
|
2781a9e7f9 | ||
|
|
af303a0a52 | ||
|
|
1c4714eb71 | ||
|
|
8a53361ff1 | ||
|
|
4c25c3d680 | ||
|
|
fac40c417c | ||
|
|
5d5a5e0a67 | ||
|
|
1623480b8b | ||
|
|
eab7978191 | ||
|
|
d06b250947 | ||
|
|
67bfe95cfa | ||
|
|
33ce3f5f5b | ||
|
|
8049145c71 | ||
|
|
d9567ea001 | ||
|
|
0b17938421 | ||
|
|
5334a8f048 | ||
|
|
9984f7800e | ||
|
|
3676c6af71 | ||
|
|
67f1680668 | ||
|
|
3e020c5892 | ||
|
|
f30fc67a56 | ||
|
|
6dda1f2a05 | ||
|
|
6904cc5a46 | ||
|
|
06104892e4 | ||
|
|
4e98c0e3bf | ||
|
|
4c0de18d7c | ||
|
|
2b50f46206 | ||
|
|
753e01fdda | ||
|
|
007cdc403e | ||
|
|
ea6d64532c | ||
|
|
b55860249c | ||
|
|
bad277e5de | ||
|
|
65b2677053 | ||
|
|
90d1d692e0 | ||
|
|
4cca427d1b | ||
|
|
c35f58f6d9 | ||
|
|
c1bdf1bb1f | ||
|
|
535611437d | ||
|
|
3696b78774 | ||
|
|
953d6b9d7b | ||
|
|
fd948123a2 | ||
|
|
822beaf2f7 | ||
|
|
9d7efe34f9 | ||
|
|
b131b263b6 | ||
|
|
35f2231e0a | ||
|
|
b18aeb94c7 | ||
|
|
9c57cba8b5 | ||
|
|
9fb8ec1393 | ||
|
|
02ee8c6dea | ||
|
|
c9384a7f3c | ||
|
|
d73ec29dc8 | ||
|
|
bfae644555 | ||
|
|
56c943a4ca | ||
|
|
21231841f5 | ||
|
|
e7985ec5e4 | ||
|
|
5f830f3dc8 | ||
|
|
74acfedc8c | ||
|
|
9fd443304a | ||
|
|
55460d4253 | ||
|
|
55eb2dd38d | ||
|
|
57a6817d9c | ||
|
|
2a0ca11c6b | ||
|
|
55f1658b23 | ||
|
|
40cec248c0 | ||
|
|
e21259b873 | ||
|
|
04750b84cb | ||
|
|
11284e7081 | ||
|
|
5ff2a61c2c | ||
|
|
29c1cadb98 | ||
|
|
a1d520c845 | ||
|
|
40be994134 |
No files matched your search
+43
-14
@@ -1,8 +1,5 @@
|
||||
name: CI
|
||||
|
||||
env:
|
||||
R2V: 6.0.8
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
@@ -26,12 +23,9 @@ jobs:
|
||||
run: sudo apt-get update -y && sudo apt-get install -y build-essential autoconf automake libtool pkg-config
|
||||
- name: Build library
|
||||
run: make
|
||||
- name: Downloading Radare2 Debian packages
|
||||
run: |
|
||||
wget -O r2.deb https://github.com/radareorg/radare2/releases/download/${{env.R2V}}/radare2_${{env.R2V}}_amd64.deb
|
||||
wget -O r2-dev.deb https://github.com/radareorg/radare2/releases/download/${{env.R2V}}/radare2-dev_${{env.R2V}}_amd64.deb
|
||||
- name: Installing Radare2
|
||||
run: sudo dpkg -i r2.deb r2-dev.deb
|
||||
- name: Install radare2
|
||||
id: r2
|
||||
uses: radareorg/github-actions@v1
|
||||
- name: Build r2 plugin
|
||||
run: make r2
|
||||
- name: Run testsuite
|
||||
@@ -52,10 +46,37 @@ jobs:
|
||||
echo "tag=" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
release:
|
||||
name: Create release and upload tarball
|
||||
debian:
|
||||
name: Build Debian package
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build]
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install build deps
|
||||
run: sudo apt-get update -y && sudo apt-get install -y build-essential autoconf automake libtool pkg-config
|
||||
|
||||
- name: Install radare2
|
||||
id: r2
|
||||
uses: radareorg/github-actions@v1
|
||||
|
||||
- name: Build Debian package
|
||||
run: make -C dist/debian SUDO=
|
||||
|
||||
- name: Upload Debian package artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: debian-package
|
||||
path: dist/debian/*.deb
|
||||
if-no-files-found: error
|
||||
|
||||
release:
|
||||
name: Create release and upload artifacts
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build, debian]
|
||||
if: needs.build.outputs.tag != ''
|
||||
|
||||
steps:
|
||||
@@ -71,11 +92,19 @@ jobs:
|
||||
git archive --format=tar.gz --prefix=libhbc-$TAG/ $TAG -o libhbc-$TAG.tgz
|
||||
ls -l libhbc*
|
||||
|
||||
- name: Download Debian package artifact
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: debian-package
|
||||
path: release-artifacts
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v2.6.1
|
||||
uses: softprops/action-gh-release@v3.0.0
|
||||
with:
|
||||
tag_name: ${{ needs.build.outputs.tag }}
|
||||
files: ./libhbc-${{ needs.build.outputs.tag }}.tgz
|
||||
files: |
|
||||
./libhbc-${{ needs.build.outputs.tag }}.tgz
|
||||
./release-artifacts/*.deb
|
||||
make_latest: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -1,8 +1,5 @@
|
||||
name: Meson Build
|
||||
|
||||
env:
|
||||
R2V: 6.0.8
|
||||
|
||||
on:
|
||||
push:
|
||||
workflow_dispatch: {}
|
||||
@@ -21,16 +18,9 @@ jobs:
|
||||
- name: Install build deps
|
||||
run: sudo apt-get update -y && sudo apt-get install -y build-essential meson ninja-build pkg-config
|
||||
|
||||
- name: Build library
|
||||
- name: Install radare2
|
||||
id: r2
|
||||
uses: radareorg/github-actions@v1
|
||||
|
||||
- name: Build library and r2 plugin with meson
|
||||
run: meson setup builddir && meson compile -C builddir
|
||||
|
||||
- name: Downloading Radare2 Debian packages
|
||||
run: |
|
||||
wget -O r2.deb https://github.com/radareorg/radare2/releases/download/${{env.R2V}}/radare2_${{env.R2V}}_amd64.deb
|
||||
wget -O r2-dev.deb https://github.com/radareorg/radare2/releases/download/${{env.R2V}}/radare2-dev_${{env.R2V}}_amd64.deb
|
||||
|
||||
- name: Installing Radare2
|
||||
run: sudo dpkg -i r2.deb r2-dev.deb
|
||||
|
||||
- name: Build r2 plugin with meson
|
||||
run: meson compile -C builddir
|
||||
@@ -1,8 +1,5 @@
|
||||
name: CI
|
||||
|
||||
env:
|
||||
R2V: 6.0.8
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
name: Windows Build
|
||||
|
||||
env:
|
||||
R2V: 6.0.8
|
||||
|
||||
on:
|
||||
push:
|
||||
workflow_dispatch: {}
|
||||
@@ -29,16 +26,11 @@ jobs:
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
pip install meson ninja wget
|
||||
choco install wget
|
||||
pip install meson ninja
|
||||
|
||||
- name: Download Radare2 release
|
||||
shell: bash
|
||||
run: |
|
||||
python -m wget https://github.com/radareorg/radare2/releases/download/${{env.R2V}}/radare2-${{env.R2V}}-w64.zip -o r2.zip
|
||||
unzip r2.zip
|
||||
mv radare2-${{env.R2V}}-w64 radare2 || mv radare2-w64-${{env.R2V}} radare2
|
||||
cp -f radare2/include/libr/r_util/r_event.h radare2/include/libr/r_event.h || true
|
||||
- name: Install radare2
|
||||
id: r2
|
||||
uses: radareorg/github-actions@v1
|
||||
|
||||
- name: Build with Meson
|
||||
shell: cmd
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
build/
|
||||
bin/libhbctool
|
||||
bin/
|
||||
third_party/
|
||||
*.d
|
||||
*.o
|
||||
|
||||
+22
-5
@@ -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.
|
||||
@@ -18,8 +18,9 @@ DISASM_SRC = $(wildcard $(SRC_DIR)/disassembly/*.c)
|
||||
DECOMPILE_SRC = $(wildcard $(SRC_DIR)/decompilation/*.c)
|
||||
OPCODES_SRC = $(filter-out $(SRC_DIR)/opcodes/%.inc.c,$(wildcard $(SRC_DIR)/opcodes/*.c))
|
||||
LIB_SRC = $(UTILS_SRC) $(PARSERS_SRC) $(DISASM_SRC) $(DECOMPILE_SRC) $(OPCODES_SRC) \
|
||||
$(SRC_DIR)/hbc.c $(SRC_DIR)/opcodes/encoder.c $(SRC_DIR)/opcodes/decoder.c $(SRC_DIR)/r2.c
|
||||
$(SRC_DIR)/hbc.c $(SRC_DIR)/literals_api.c $(SRC_DIR)/opcodes/encoder.c $(SRC_DIR)/opcodes/decoder.c $(SRC_DIR)/r2.c
|
||||
MAIN_SRC = src/tool/libhbctool.c
|
||||
HBC_HEADERS = $(shell find include -name '*.h')
|
||||
|
||||
## Object files
|
||||
LIB_OBJ = $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/%.o,$(LIB_SRC))
|
||||
@@ -55,11 +56,11 @@ $(STATIC_LIB): $(LIB_OBJ)
|
||||
$(BIN_FILE): $(STATIC_LIB) $(MAIN_OBJ) | $(shell mkdir -p $(BIN_DIR))
|
||||
$(CC) $(CFLAGS) -o $@ $(MAIN_OBJ) -L$(BUILD_DIR) -lhbc
|
||||
|
||||
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c | $(VH)
|
||||
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c $(HBC_HEADERS) | $(VH)
|
||||
@mkdir -p $(dir $@)
|
||||
$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@
|
||||
|
||||
$(MAIN_OBJ): $(MAIN_SRC) | $(VH)
|
||||
$(MAIN_OBJ): $(MAIN_SRC) $(HBC_HEADERS) | $(VH)
|
||||
$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@
|
||||
|
||||
clean:
|
||||
@@ -72,6 +73,15 @@ TEST_DIR = tests
|
||||
TEST_SRC = $(wildcard $(TEST_DIR)/*.c)
|
||||
TEST_BIN = $(BIN_DIR)/run_tests
|
||||
|
||||
$(TEST_BIN): $(STATIC_LIB) $(TEST_SRC) | $(shell mkdir -p $(BIN_DIR))
|
||||
$(CC) $(CFLAGS) $(INCLUDES) -o $@ $(TEST_SRC) -L$(BUILD_DIR) -lhbc
|
||||
|
||||
test: $(BIN_FILE) $(TEST_BIN)
|
||||
$(TEST_BIN)
|
||||
$(BIN_FILE) sl test/bins/hbc/bespoke_eval.hbc | grep -q '0x000000b4 f=0 +0x4 yes.js:1:5 stmt=0'
|
||||
$(BIN_FILE) h test/bins/hbc/bespoke_eval.hbc | grep -q 'Source Locations: 13 bytes'
|
||||
$(BIN_FILE) h test/bins/hbc/index.android.bundle | grep -q 'Source Locations: 0 bytes'
|
||||
$(BIN_FILE) libs test/bins/hbc/index.android.bundle | grep -q 'react-native'
|
||||
|
||||
r2:
|
||||
$(MAKE) clean
|
||||
|
||||
@@ -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
|
||||
|
||||
Vendored
+20
@@ -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
|
||||
Vendored
+2
@@ -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.
|
||||
Vendored
+42
@@ -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
|
||||
Vendored
+85
@@ -0,0 +1,85 @@
|
||||
# Create .deb without using dpkg tools.
|
||||
#
|
||||
# Author: Tim Wegener <[email protected]>
|
||||
#
|
||||
# Use 'include deb.mk' after defining the user variables in a local
|
||||
# makefile.
|
||||
|
||||
ifeq ($(shell uname),Darwin)
|
||||
MD5SUM=md5
|
||||
else
|
||||
MD5SUM=md5sum
|
||||
endif
|
||||
|
||||
GAWK=awk
|
||||
PACKAGE_DIR=$(shell pwd)
|
||||
CONTROL_EXTRAS ?= ${wildcard preinst postinst prerm postrm}
|
||||
|
||||
${PACKAGE_DIR}/control: ${PACKAGE_DIR}/data ${CONTROL_EXTRAS} DESCR \
|
||||
${ICON_SOURCE}
|
||||
mkdir -p $@
|
||||
ifneq (${CONTROL_EXTRAS},)
|
||||
cp ${CONTROL_EXTRAS} $@
|
||||
endif
|
||||
echo "Package: ${PACKAGE}" > $@/control
|
||||
echo "Version: ${VERSION}" >> $@/control
|
||||
echo "Section: ${SECTION}" >> $@/control
|
||||
echo "Priority: ${PRIORITY}" >> $@/control
|
||||
echo "Architecture: ${ARCH}" >> $@/control
|
||||
ifneq (${DEPENDS},)
|
||||
echo "Depends: ${DEPENDS}" >> $@/control
|
||||
endif
|
||||
echo "Installed-Size: ${shell du -s ${PACKAGE_DIR}/data|cut -f1}" \
|
||||
>> $@/control
|
||||
echo "Maintainer: ${MAINTAINER}" >> $@/control
|
||||
printf "Description:" >> $@/control
|
||||
cat DESCR | ${GAWK} '{print " "$$0;}' >> $@/control
|
||||
cd ${PACKAGE_DIR}/data && find . -type f -exec ${MD5SUM} {} \; \
|
||||
| sed -e 's| \./||' \
|
||||
> $@/md5sums
|
||||
|
||||
${PACKAGE_DIR}/debian-binary:
|
||||
echo "2.0" > $@
|
||||
|
||||
${PACKAGE_DIR}/clean:
|
||||
rm -rf ${PACKAGE_DIR}/data ${PACKAGE_DIR}/control ${PACKAGE_DIR}/build *.deb
|
||||
|
||||
${PACKAGE_DIR}/build: ${PACKAGE_DIR}/debian-binary ${PACKAGE_DIR}/control \
|
||||
${PACKAGE_DIR}/data
|
||||
rm -rf $@
|
||||
mkdir $@
|
||||
cp ${PACKAGE_DIR}/debian-binary $@/
|
||||
cd ${PACKAGE_DIR}/control && tar czvf $@/control.tar.gz *
|
||||
cd ${PACKAGE_DIR}/data && \
|
||||
COPY_EXTENDED_ATTRIBUTES_DISABLE=true \
|
||||
COPYFILE_DISABLE=true \
|
||||
tar cpzvf $@/data.tar.gz *
|
||||
|
||||
${PACKAGE_DIR}/${PACKAGE}_${VERSION}_${ARCH}.deb: ${PACKAGE_DIR}/build
|
||||
ar -rc $@ $</debian-binary $</control.tar.gz $</data.tar.gz
|
||||
|
||||
.PHONY: data
|
||||
data: ${PACKAGE_DIR}/data
|
||||
|
||||
.PHONY: control
|
||||
control: ${PACKAGE_DIR}/control
|
||||
|
||||
.PHONY: build
|
||||
build: ${PACKAGE_DIR}/build
|
||||
|
||||
.PHONY: clean
|
||||
clean: ${PACKAGE_DIR}/clean $(EXTRA_CLEAN)
|
||||
rm -f debian-binary
|
||||
|
||||
.PHONY: deb
|
||||
deb: ${PACKAGE_DIR}/${PACKAGE}_${VERSION}_${ARCH}.deb
|
||||
|
||||
clobber::
|
||||
rm -rf ${PACKAGE_DIR}/debian_binary ${PACKAGE_DIR}/control \
|
||||
${PACKAGE_DIR}/data ${PACKAGE_DIR}/build
|
||||
|
||||
push:
|
||||
scp *.deb radare.org:/srv/http/radareorg/cydia/debs
|
||||
|
||||
mrproper: clean
|
||||
rm -rf root
|
||||
-339
@@ -1,339 +0,0 @@
|
||||
add=binary add of two registers (numeric or string concatenation)
|
||||
add_empty_string=convert a value to string by concatenating with the empty string
|
||||
add_n=numeric add (operands known to be numeric)
|
||||
add_own_private_by_sym=add a private own property using a symbol key
|
||||
add_s=string add (operands known to be strings)
|
||||
add32=32-bit integer add
|
||||
async_break_check=check if execution should break asynchronously
|
||||
bit_and=bitwise AND of two registers
|
||||
bit_not=bitwise NOT of a register
|
||||
bit_or=bitwise OR of two registers
|
||||
bit_xor=bitwise XOR of two registers
|
||||
call=call a function with a given number of arguments
|
||||
call_builtin=call a builtin function by id
|
||||
call_builtin_long=call a builtin function by id (long form)
|
||||
call_direct=call a function whose code address is statically known
|
||||
call_direct_long_index=call a function with a long function index
|
||||
call_long=call a function with a long argument count
|
||||
call_require=call the require builtin to import a module
|
||||
call_with_new_target=call a function passing an explicit new.target
|
||||
call_with_new_target_long=call with new.target (long form)
|
||||
call1=call a function with 1 argument
|
||||
call2=call a function with 2 arguments
|
||||
call3=call a function with 3 arguments
|
||||
call4=call a function with 4 arguments
|
||||
catch=mark catch handler entry, receives the thrown exception
|
||||
coerce_this_n_s=coerce 'this' to an object in non-strict mode
|
||||
coerce_this_ns=coerce 'this' to an object in non-strict mode
|
||||
complete_generator=mark generator as completed
|
||||
construct=invoke a constructor with a given number of arguments
|
||||
construct_long=invoke a constructor (long form)
|
||||
create_async_closure=create an async closure
|
||||
create_async_closure_long_index=create an async closure (long function index)
|
||||
create_base_class=create a class with no superclass
|
||||
create_base_class_long_index=create a base class (long function index)
|
||||
create_closure=create a closure from a function
|
||||
create_closure_long_index=create a closure (long function index)
|
||||
create_derived_class=create a class that extends a superclass
|
||||
create_derived_class_long_index=create a derived class (long function index)
|
||||
create_environment=create a new lexical environment for the current function
|
||||
create_function_environment=create the function's lexical environment
|
||||
create_generator=create a generator function instance
|
||||
create_generator_closure=create a generator closure
|
||||
create_generator_closure_long_index=create a generator closure (long function index)
|
||||
create_generator_long_index=create a generator (long function index)
|
||||
create_inner_environment=create an inner (nested) lexical environment
|
||||
create_private_name=create a unique private name symbol
|
||||
create_reg_exp=create a RegExp object from a pattern and flags
|
||||
create_this=create the 'this' object for a constructor call
|
||||
create_this_for_new=allocate 'this' for a 'new' expression
|
||||
create_this_for_super=allocate 'this' for a super constructor call
|
||||
create_top_level_environment=create the top-level lexical environment
|
||||
debugger=trigger the JavaScript debugger statement
|
||||
debugger_check=check if a debugger break is requested
|
||||
debugger_check_break=check and yield to debugger if breakpoint is set
|
||||
dec=decrement a register by 1
|
||||
declare_global_var=declare a variable on the global object
|
||||
define_own_by_id=define an own property by string id
|
||||
define_own_by_id_long=define an own property by string id (long form)
|
||||
define_own_by_index=define an own indexed property
|
||||
define_own_by_index_l=define an own indexed property (long form)
|
||||
define_own_by_val=define an own property by computed value
|
||||
define_own_getter_setter_by_val=define accessor (getter/setter) property
|
||||
define_own_in_dense_array=define an element in a dense array literal
|
||||
define_own_in_dense_array_l=define an element in a dense array literal (long)
|
||||
del_by_id=delete a property by string id
|
||||
del_by_id_long=delete a property by string id (long form)
|
||||
del_by_id_loose=delete a property by id (loose mode)
|
||||
del_by_id_loose_long=delete a property by id (loose, long form)
|
||||
del_by_id_strict=delete a property by id (strict mode)
|
||||
del_by_id_strict_long=delete a property by id (strict, long form)
|
||||
del_by_val=delete a property by computed value
|
||||
del_by_val_loose=delete a property by computed value (loose mode)
|
||||
del_by_val_strict=delete a property by computed value (strict mode)
|
||||
direct_eval=invoke direct eval()
|
||||
div=divide two registers
|
||||
div_n=numeric divide (operands known to be numeric)
|
||||
divi32=signed 32-bit integer divide
|
||||
divu32=unsigned 32-bit integer divide
|
||||
eq=loose equality comparison (==)
|
||||
fast_array_append=append all elements of a fast array to another
|
||||
fast_array_length=read the length of a fast array
|
||||
fast_array_load=load element from a fast array by index
|
||||
fast_array_push=push a value into a fast array
|
||||
fast_array_store=store value into a fast array at index
|
||||
get_arguments_length=read 'arguments.length' lazily
|
||||
get_arguments_prop_by_val=read an element from 'arguments' lazily
|
||||
get_arguments_prop_by_val_loose=read 'arguments' element (loose mode)
|
||||
get_arguments_prop_by_val_strict=read 'arguments' element (strict mode)
|
||||
get_builtin_closure=get a closure for a builtin function
|
||||
get_by_id=load a property by string id
|
||||
get_by_id_long=load a property by string id (long form)
|
||||
get_by_id_short=load a property by string id (short cache)
|
||||
get_by_id_with_receiver_long=load a property with explicit receiver (long form)
|
||||
get_by_index=load an element by integer index
|
||||
get_by_val=load a property by computed value
|
||||
get_by_val_with_receiver=load a property by computed value with receiver
|
||||
get_closure_environment=get the environment of an enclosing closure
|
||||
get_environment=get the lexical environment at a given depth
|
||||
get_global_object=get the global object
|
||||
get_new_target=read the new.target meta-property
|
||||
get_next_p_name=advance a for-in property iterator
|
||||
get_next_pname=advance a for-in property iterator
|
||||
get_own_by_slot_idx=load own property by inline cache slot index
|
||||
get_own_by_slot_idx_long=load own property by slot index (long form)
|
||||
get_own_private_by_sym=load an own private property by symbol
|
||||
get_p_name_list=initialize a for-in property name list
|
||||
get_parent_environment=get the parent of a lexical environment
|
||||
get_pname_list=initialize a for-in property name list
|
||||
greater=greater than comparison (>)
|
||||
greater_eq=greater than or equal comparison (>=)
|
||||
inc=increment a register by 1
|
||||
instance_of=instanceof operator
|
||||
is_in='in' operator (property exists in object)
|
||||
iterator_begin=start iteration over an iterable
|
||||
iterator_close=close (cleanup) an iterator
|
||||
iterator_next=advance an iterator and read the next value
|
||||
j_equal=branch if loose equal
|
||||
j_equal_long=branch if loose equal (long offset)
|
||||
j_greater=branch if greater than
|
||||
j_greater_equal=branch if greater than or equal
|
||||
j_greater_equal_long=branch if greater than or equal (long offset)
|
||||
j_greater_equal_n=branch if greater or equal (numeric)
|
||||
j_greater_equal_n_long=branch if greater or equal (numeric, long offset)
|
||||
j_greater_long=branch if greater than (long offset)
|
||||
j_greater_n=branch if greater than (numeric)
|
||||
j_greater_n_long=branch if greater than (numeric, long offset)
|
||||
j_less=branch if less than
|
||||
j_less_equal=branch if less than or equal
|
||||
j_less_equal_long=branch if less than or equal (long offset)
|
||||
j_less_equal_n=branch if less than or equal (numeric)
|
||||
j_less_equal_n_long=branch if less than or equal (numeric, long offset)
|
||||
j_less_long=branch if less than (long offset)
|
||||
j_less_n=branch if less than (numeric)
|
||||
j_less_n_long=branch if less than (numeric, long offset)
|
||||
j_not_equal=branch if loose not equal
|
||||
j_not_equal_long=branch if loose not equal (long offset)
|
||||
j_not_greater=branch if not greater than
|
||||
j_not_greater_equal=branch if not greater than or equal
|
||||
j_not_greater_equal_long=branch if not greater than or equal (long offset)
|
||||
j_not_greater_equal_n=branch if not greater than or equal (numeric)
|
||||
j_not_greater_equal_n_long=branch if not greater than or equal (numeric, long offset)
|
||||
j_not_greater_long=branch if not greater than (long offset)
|
||||
j_not_greater_n=branch if not greater than (numeric)
|
||||
j_not_greater_n_long=branch if not greater than (numeric, long offset)
|
||||
j_not_less=branch if not less than
|
||||
j_not_less_equal=branch if not less than or equal
|
||||
j_not_less_equal_long=branch if not less than or equal (long offset)
|
||||
j_not_less_equal_n=branch if not less than or equal (numeric)
|
||||
j_not_less_equal_n_long=branch if not less than or equal (numeric, long offset)
|
||||
j_not_less_long=branch if not less than (long offset)
|
||||
j_not_less_n=branch if not less than (numeric)
|
||||
j_not_less_n_long=branch if not less than (numeric, long offset)
|
||||
j_strict_equal=branch if strict equal (===)
|
||||
j_strict_equal_long=branch if strict equal (long offset)
|
||||
j_strict_not_equal=branch if strict not equal (!==)
|
||||
j_strict_not_equal_long=branch if strict not equal (long offset)
|
||||
jequal=branch if loose equal
|
||||
jequal_long=branch if loose equal (long offset)
|
||||
jgreater=branch if greater than
|
||||
jgreater_equal=branch if greater than or equal
|
||||
jgreater_equal_long=branch if greater than or equal (long offset)
|
||||
jgreater_equal_n=branch if greater or equal (numeric)
|
||||
jgreater_equal_nlong=branch if greater or equal (numeric, long offset)
|
||||
jgreater_long=branch if greater than (long offset)
|
||||
jgreater_n=branch if greater than (numeric)
|
||||
jgreater_nlong=branch if greater than (numeric, long offset)
|
||||
jless=branch if less than
|
||||
jless_equal=branch if less than or equal
|
||||
jless_equal_long=branch if less than or equal (long offset)
|
||||
jless_equal_n=branch if less than or equal (numeric)
|
||||
jless_equal_nlong=branch if less than or equal (numeric, long offset)
|
||||
jless_long=branch if less than (long offset)
|
||||
jless_n=branch if less than (numeric)
|
||||
jless_nlong=branch if less than (numeric, long offset)
|
||||
jmp=unconditional branch
|
||||
jmp_builtin_is=branch if value is a specific builtin
|
||||
jmp_builtin_is_long=branch if value is a specific builtin (long offset)
|
||||
jmp_builtin_is_not=branch if value is not a specific builtin
|
||||
jmp_builtin_is_not_long=branch if value is not a specific builtin (long offset)
|
||||
jmp_false=branch if value is falsy
|
||||
jmp_false_long=branch if value is falsy (long offset)
|
||||
jmp_long=unconditional branch (long offset)
|
||||
jmp_true=branch if value is truthy
|
||||
jmp_true_long=branch if value is truthy (long offset)
|
||||
jmp_type_of_is=branch if typeof value matches a tag
|
||||
jmp_undefined=branch if value is undefined
|
||||
jmp_undefined_long=branch if value is undefined (long offset)
|
||||
jnot_equal=branch if loose not equal
|
||||
jnot_equal_long=branch if loose not equal (long offset)
|
||||
jnot_greater=branch if not greater than
|
||||
jnot_greater_equal=branch if not greater than or equal
|
||||
jnot_greater_equal_long=branch if not greater than or equal (long offset)
|
||||
jnot_greater_equal_n=branch if not greater than or equal (numeric)
|
||||
jnot_greater_equal_nlong=branch if not greater than or equal (numeric, long offset)
|
||||
jnot_greater_long=branch if not greater than (long offset)
|
||||
jnot_greater_n=branch if not greater than (numeric)
|
||||
jnot_greater_nlong=branch if not greater than (numeric, long offset)
|
||||
jnot_less=branch if not less than
|
||||
jnot_less_equal=branch if not less than or equal
|
||||
jnot_less_equal_long=branch if not less than or equal (long offset)
|
||||
jnot_less_equal_n=branch if not less than or equal (numeric)
|
||||
jnot_less_equal_nlong=branch if not less than or equal (numeric, long offset)
|
||||
jnot_less_long=branch if not less than (long offset)
|
||||
jnot_less_n=branch if not less than (numeric)
|
||||
jnot_less_nlong=branch if not less than (numeric, long offset)
|
||||
jstrict_equal=branch if strict equal (===)
|
||||
jstrict_equal_long=branch if strict equal (long offset)
|
||||
jstrict_not_equal=branch if strict not equal (!==)
|
||||
jstrict_not_equal_long=branch if strict not equal (long offset)
|
||||
l_shift=left shift (<<)
|
||||
less=less than comparison (<)
|
||||
less_eq=less than or equal comparison (<=)
|
||||
load_const_big_int=load a BigInt constant
|
||||
load_const_big_int_long_index=load a BigInt constant (long index)
|
||||
load_const_double=load a double-precision float constant
|
||||
load_const_empty=load the 'empty' sentinel value
|
||||
load_const_false=load the boolean false constant
|
||||
load_const_int=load an integer constant
|
||||
load_const_null=load the null constant
|
||||
load_const_string=load a string constant by id
|
||||
load_const_string_long_index=load a string constant by id (long index)
|
||||
load_const_true=load the boolean true constant
|
||||
load_const_u_int8=load an 8-bit unsigned integer constant
|
||||
load_const_uint8=load an 8-bit unsigned integer constant
|
||||
load_const_undefined=load the undefined constant
|
||||
load_const_zero=load the integer zero constant
|
||||
load_from_environment=read a variable from a lexical environment slot
|
||||
load_from_environment_l=read from environment slot (long index)
|
||||
load_param=read a function parameter into a register
|
||||
load_param_long=read a function parameter (long index)
|
||||
load_parent_no_traps=load object's prototype skipping proxy traps
|
||||
load_this_n_s=load 'this' in non-strict mode
|
||||
load_this_ns=load 'this' in non-strict mode
|
||||
loadi16=load a signed 16-bit integer
|
||||
loadi32=load a signed 32-bit integer
|
||||
loadi8=load a signed 8-bit integer
|
||||
loadu16=load an unsigned 16-bit integer
|
||||
loadu32=load an unsigned 32-bit integer
|
||||
loadu8=load an unsigned 8-bit integer
|
||||
lshift=left shift (<<)
|
||||
mod=modulo (%) operation
|
||||
mov=copy a register into another register
|
||||
mov_long=copy a register (long form)
|
||||
mul=multiply two registers
|
||||
mul_n=numeric multiply (operands known to be numeric)
|
||||
mul32=32-bit integer multiply
|
||||
name=get the name (string id) of an object
|
||||
negate=arithmetic negation (unary minus)
|
||||
neq=loose inequality comparison (!=)
|
||||
new_array=create an empty array of given size
|
||||
new_array_with_buffer=create an array initialized from a literal buffer
|
||||
new_array_with_buffer_long=create an array from a literal buffer (long form)
|
||||
new_fast_array=create a new fast (dense, integer-indexed) array
|
||||
new_object=create an empty object
|
||||
new_object_with_buffer=create an object initialized from a literal buffer
|
||||
new_object_with_buffer_and_parent=create an object from a buffer with a given prototype
|
||||
new_object_with_buffer_long=create an object from a literal buffer (long form)
|
||||
new_object_with_parent=create an empty object with a given prototype
|
||||
new_typed_object_with_buffer=create a typed object from a literal buffer
|
||||
not=logical NOT (!)
|
||||
private_is_in=test if a private name is present in an object
|
||||
profile_point=marker used by the profiler to record execution
|
||||
put_by_id=store a property by string id
|
||||
put_by_id_long=store a property by string id (long form)
|
||||
put_by_id_loose=store a property by id (loose mode)
|
||||
put_by_id_loose_long=store a property by id (loose, long form)
|
||||
put_by_id_strict=store a property by id (strict mode)
|
||||
put_by_id_strict_long=store a property by id (strict, long form)
|
||||
put_by_val=store a property by computed value
|
||||
put_by_val_loose=store a property by computed value (loose mode)
|
||||
put_by_val_strict=store a property by computed value (strict mode)
|
||||
put_by_val_with_receiver=store a property by computed value with receiver
|
||||
put_new_own_by_id=define a new own property by id
|
||||
put_new_own_by_id_long=define a new own property by id (long form)
|
||||
put_new_own_by_id_short=define a new own property by id (short form)
|
||||
put_new_own_n_eby_id=define a new own non-enumerable property by id
|
||||
put_new_own_n_eby_id_long=define a new own non-enumerable property by id (long form)
|
||||
put_new_own_ne_by_id=define a new own non-enumerable property by id
|
||||
put_new_own_ne_by_id_long=define a new own non-enumerable property by id (long form)
|
||||
put_own_by_index=define an own property by integer index
|
||||
put_own_by_index_l=define an own property by integer index (long form)
|
||||
put_own_by_slot_idx=store an own property by inline cache slot
|
||||
put_own_by_slot_idx_long=store an own property by slot index (long form)
|
||||
put_own_by_val=define an own property by computed value
|
||||
put_own_getter_setter_by_val=define an own accessor (getter/setter) property
|
||||
put_own_private_by_sym=store an own private property by symbol
|
||||
r_shift=signed right shift (>>)
|
||||
reify_arguments=create the actual 'arguments' object lazily
|
||||
reify_arguments_loose=reify 'arguments' object in loose mode
|
||||
reify_arguments_strict=reify 'arguments' object in strict mode
|
||||
resume_generator=resume a suspended generator
|
||||
ret=return from the current function
|
||||
rshift=signed right shift (>>)
|
||||
save_generator=suspend the current generator and yield
|
||||
save_generator_long=suspend the current generator (long offset)
|
||||
select_object=pick the object operand among two values
|
||||
start_generator=initialize execution of a generator body
|
||||
store_n_pto_environment=store non-pointer value to environment slot
|
||||
store_n_pto_environment_l=store non-pointer value to environment slot (long index)
|
||||
store_np_to_environment=store non-pointer value to environment slot
|
||||
store_np_to_environment_l=store non-pointer value to environment slot (long index)
|
||||
store_to_environment=store a variable into a lexical environment slot
|
||||
store_to_environment_l=store into environment slot (long index)
|
||||
store16=store a 16-bit value to memory
|
||||
store32=store a 32-bit value to memory
|
||||
store8=store an 8-bit value to memory
|
||||
strict_eq=strict equality comparison (===)
|
||||
strict_neq=strict inequality comparison (!==)
|
||||
string_switch_imm=switch on a string with an inline jump table
|
||||
sub=subtract two registers
|
||||
sub_n=numeric subtract (operands known to be numeric)
|
||||
sub32=32-bit integer subtract
|
||||
switch_imm=switch on an integer with an inline jump table
|
||||
throw=throw an exception
|
||||
throw_if_empty=throw a ReferenceError if value is the empty sentinel (TDZ)
|
||||
throw_if_has_restricted_global_property=throw if a restricted global property already exists
|
||||
throw_if_this_initialized=throw if 'this' has already been initialized in a derived ctor
|
||||
throw_if_undefined=throw a TypeError if the value is undefined
|
||||
throw_if_undefined_inst=throw TypeError if the value is undefined (instance form)
|
||||
to_int32=convert a value to a signed 32-bit integer (ToInt32)
|
||||
to_number=convert a value to a number (ToNumber)
|
||||
to_numeric=convert a value to numeric (ToNumeric, supports BigInt)
|
||||
to_property_key=convert a value to a property key (ToPropertyKey)
|
||||
to_uint32=convert a value to an unsigned 32-bit integer (ToUint32)
|
||||
try_get_by_id=load a property by id, throwing if missing
|
||||
try_get_by_id_long=load a property by id, throwing if missing (long form)
|
||||
try_put_by_id=store a property by id, throwing if missing
|
||||
try_put_by_id_long=store a property by id, throwing if missing (long form)
|
||||
try_put_by_id_loose=store a property by id, throwing if missing (loose)
|
||||
try_put_by_id_loose_long=store a property by id, throwing if missing (loose, long form)
|
||||
try_put_by_id_strict=store a property by id, throwing if missing (strict)
|
||||
try_put_by_id_strict_long=store a property by id, throwing if missing (strict, long form)
|
||||
type_of=typeof operator
|
||||
type_of_is=test if typeof value equals a specific tag
|
||||
typed_load_parent=load the prototype of a typed object
|
||||
u_int_switch_imm=switch on an unsigned integer with an inline jump table
|
||||
u_rshift=unsigned right shift (>>>)
|
||||
unreachable=trap; this point should never be reached
|
||||
urshift=unsigned right shift (>>>)
|
||||
@@ -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);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#ifndef LIBHBC_COMMON_H
|
||||
#define LIBHBC_COMMON_H
|
||||
|
||||
@@ -9,7 +11,6 @@
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
#include "version.h"
|
||||
|
||||
#ifndef R2_VERSION
|
||||
#define R_RETURN_VAL_IF_FAIL(x, y) \
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#ifndef LIBHBC_DECOMPILER_H
|
||||
#define LIBHBC_DECOMPILER_H
|
||||
|
||||
@@ -54,6 +56,10 @@ struct Environment {
|
||||
char **slot_index_to_varname;
|
||||
int var_count;
|
||||
int slot_capacity;
|
||||
/* >=0: synthetic stand-in for a get_environment captured scope at this
|
||||
* nesting level (used when the real parent chain is unknown, i.e. the
|
||||
* function is decompiled standalone). -1: a real created environment. */
|
||||
int captured_level;
|
||||
};
|
||||
|
||||
/* Address to strings mapping */
|
||||
@@ -122,6 +128,38 @@ struct DecompiledFunctionBody {
|
||||
u32 nested_frames_count;
|
||||
u32 nested_frames_capacity;
|
||||
|
||||
/* for-in / loop top addresses: a jump here is a `continue`, not a `goto`,
|
||||
* and the address gets no label (the loop body brace stands in). */
|
||||
u32 *forin_continue_targets;
|
||||
u32 forin_continue_targets_count;
|
||||
u32 forin_continue_targets_capacity;
|
||||
|
||||
/* do-while loops: `do {` opens at `top`, `} while (cond);` replaces the
|
||||
* back-edge conditional jump at `back_edge`. When an entry guard at
|
||||
* `guard_pos` (jumping to `exit_addr`) tests the same condition, the loop is
|
||||
* `promoted` to `while (cond) {` (guard + do/while folded into one header). */
|
||||
struct {
|
||||
u32 top;
|
||||
u32 back_edge;
|
||||
u32 guard_pos;
|
||||
u32 exit_addr;
|
||||
bool promoted;
|
||||
/* For a materialized guard (`r=cmp; if(r){do}while(cmp)`), the while
|
||||
* header renders this condition instead of the guard register. NULL when
|
||||
* the guard already spells the condition (fused compare-jump). */
|
||||
void *while_cond;
|
||||
bool while_cond_invert;
|
||||
} *dowhile_loops;
|
||||
u32 dowhile_loops_count;
|
||||
u32 dowhile_loops_capacity;
|
||||
|
||||
/* Infinite loops: a backward unconditional `goto top` is the back-edge of a
|
||||
* `for (;;) {` opened at `top`; the enclosing if's false path exits at
|
||||
* `exit`, where a `break;` and the loop close are emitted. */
|
||||
struct { u32 top; u32 exit; } *forever_loops;
|
||||
u32 forever_loops_count;
|
||||
u32 forever_loops_capacity;
|
||||
|
||||
/* Generated code */
|
||||
TokenString *statements;
|
||||
u32 statements_count;
|
||||
@@ -144,6 +182,9 @@ typedef struct HermesDecompiler {
|
||||
bool *function_in_progress; /* Track functions currently on the emit stack */
|
||||
int indent_level;
|
||||
bool inlining_function; /* True when outputting a nested function inline */
|
||||
int inline_depth; /* Current nesting depth of inlined closures */
|
||||
bool output_truncated; /* Sticky flag: budget exceeded; stop emitting new content */
|
||||
bool truncation_marker_emitted; /* Sticky flag: the truncation comment was already written */
|
||||
HBCDecompOptions options;
|
||||
StringBuffer output;
|
||||
} HermesDecompiler;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#ifndef LIBHBC_LITERALS_H
|
||||
#define LIBHBC_LITERALS_H
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#ifndef LIBHBC_TOKEN_H
|
||||
#define LIBHBC_TOKEN_H
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#ifndef LIBHBC_TRANSLATOR_H
|
||||
#define LIBHBC_TRANSLATOR_H
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#ifndef LIBHBC_DISASSEMBLER_H
|
||||
#define LIBHBC_DISASSEMBLER_H
|
||||
|
||||
|
||||
+97
-1
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#ifndef LIBHBC_API_H
|
||||
#define LIBHBC_API_H
|
||||
|
||||
@@ -63,6 +65,34 @@ typedef struct {
|
||||
u32 param_count; // Number of parameters
|
||||
} HBCFunc;
|
||||
|
||||
typedef struct {
|
||||
u32 function_id;
|
||||
u32 address;
|
||||
u32 function_address;
|
||||
u32 line;
|
||||
u32 column;
|
||||
u32 statement;
|
||||
const char *filename;
|
||||
} HBCSourceLine;
|
||||
|
||||
typedef struct {
|
||||
HBCSourceLine *lines;
|
||||
u32 count;
|
||||
} HBCSourceLineArray;
|
||||
|
||||
typedef struct {
|
||||
bool has_debug_info;
|
||||
u32 filename_count;
|
||||
u32 filename_storage_size;
|
||||
u32 file_region_count;
|
||||
u32 source_locations_size;
|
||||
u32 scope_desc_data_size;
|
||||
u32 textified_data_size;
|
||||
u32 string_table_size;
|
||||
u32 debug_data_size;
|
||||
u32 functions_with_debug_info;
|
||||
} HBCDebugInfo;
|
||||
|
||||
/* String table data */
|
||||
typedef struct {
|
||||
u32 string_count;
|
||||
@@ -103,7 +133,14 @@ typedef struct {
|
||||
/* Control flow rendering options */
|
||||
bool force_dispatch; /* Force switch/case dispatch loop for linear functions */
|
||||
bool inline_closures; /* Inline closure definitions (default: true) */
|
||||
int inline_threshold; /* Max instruction count to inline (0 = no limit, -1 = no inline) */
|
||||
int inline_threshold; /* Max closure bytecode size to inline (0 = no limit, -1 = no inline) */
|
||||
int inline_max_depth; /* Max nested inline depth (0 = no limit). Bounds the
|
||||
* recursive closure expansion that otherwise inlines a
|
||||
* whole module graph into one function. */
|
||||
/* Early-stop budgets (0 = unlimited). max_ast_statements caps pass2 AST
|
||||
* growth per function; max_output_bytes caps total emitted output. */
|
||||
int max_ast_statements;
|
||||
int max_output_bytes;
|
||||
} HBCDecompOptions;
|
||||
|
||||
/* Per-instruction details */
|
||||
@@ -216,6 +253,14 @@ Result hbc_get_string_tables(HBC *hbc, HBCStrs *out);
|
||||
*/
|
||||
Result hbc_get_function_source(HBC *hbc, u32 function_id, const char **out_src);
|
||||
|
||||
/**
|
||||
* Get decoded source-line records from debug information.
|
||||
*/
|
||||
Result hbc_get_source_lines(HBC *hbc, HBCSourceLineArray *out);
|
||||
void hbc_free_source_lines(HBCSourceLineArray *arr);
|
||||
bool hbc_has_source_lines(HBC *hbc);
|
||||
Result hbc_get_debug_info(HBC *hbc, HBCDebugInfo *out);
|
||||
|
||||
/**
|
||||
* Get the raw bytecode bytes for a function.
|
||||
*/
|
||||
@@ -284,11 +329,62 @@ typedef struct {
|
||||
u32 count;
|
||||
} HBCInsns;
|
||||
|
||||
typedef enum {
|
||||
HBC_BINDING_IMPORT = 0,
|
||||
HBC_BINDING_EXPORT = 1
|
||||
} HBCBindingType;
|
||||
|
||||
typedef struct {
|
||||
HBCBindingType type;
|
||||
const char *kind; /* js, native, cjs, global, module */
|
||||
char *name;
|
||||
char *module;
|
||||
u32 function_id;
|
||||
u32 offset;
|
||||
u32 string_id;
|
||||
} HBCBinding;
|
||||
|
||||
typedef struct {
|
||||
HBCBinding *bindings;
|
||||
u32 count;
|
||||
} HBCBindings;
|
||||
|
||||
typedef struct {
|
||||
char *name;
|
||||
char *path;
|
||||
char *version;
|
||||
const char *kind; /* cjs, package, js, native, global */
|
||||
u32 function_id;
|
||||
u32 offset;
|
||||
u32 string_id;
|
||||
bool inferred;
|
||||
} HBCModule;
|
||||
|
||||
typedef struct {
|
||||
HBCModule *modules;
|
||||
u32 count;
|
||||
} HBCModules;
|
||||
|
||||
/**
|
||||
* Free instruction array.
|
||||
*/
|
||||
void hbc_free_insns(HBCInsn *insns, u32 count);
|
||||
|
||||
/**
|
||||
* Scan bytecode/string references for probable JS/native import/export
|
||||
* boundaries. Hermes has no native import table, so these entries are derived
|
||||
* from bytecode operands and CJS metadata.
|
||||
*/
|
||||
Result hbc_scan_bindings(HBC *hbc, HBCBindings *out);
|
||||
void hbc_free_bindings(HBCBindings *bindings);
|
||||
|
||||
/**
|
||||
* List probable bundled modules/libraries. Hermes bytecode has no SBOM table,
|
||||
* so versions are only populated when they are explicitly preserved in strings.
|
||||
*/
|
||||
Result hbc_list_modules(HBC *hbc, HBCModules *out);
|
||||
void hbc_free_modules(HBCModules *modules);
|
||||
|
||||
/**
|
||||
* Encoding buffer and functions
|
||||
*/
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#ifndef LIBHBC_ENCODER_H
|
||||
#define LIBHBC_ENCODER_H
|
||||
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#ifndef LIBHBC_LITERALS_PUBLIC_H
|
||||
#define LIBHBC_LITERALS_PUBLIC_H
|
||||
|
||||
#include <hbc/common.h>
|
||||
#include <hbc/hbc.h>
|
||||
|
||||
/* ============================================================================
|
||||
* Literals API (public)
|
||||
*
|
||||
* Covers the buffer-backed literals produced by:
|
||||
* OP_NewArrayWithBuffer / OP_NewArrayWithBufferLong
|
||||
* OP_NewObjectWithBuffer / OP_NewObjectWithBufferLong
|
||||
*
|
||||
* Responsibilities:
|
||||
* - Format a literal from opcode immediates into a JS-like string.
|
||||
* - Maintain a lazy cache of distinct literals so the disassembler and
|
||||
* decompiler can look up previously-formatted literals in O (1) and avoid
|
||||
* re-walking the SLP buffer on every call.
|
||||
* - Track the set of instruction addresses that reference each literal.
|
||||
* - Enumerate literals from the pool side (scan SLP stream) and from the
|
||||
* code side (walk function bodies).
|
||||
* - Allow resetting the cache to recover from a bad scan.
|
||||
*
|
||||
* Caching policy: entries are keyed by (kind, num_items, primary_id,
|
||||
* secondary_id); formatting happens at registration time and is memoized.
|
||||
* The cache can be cleared at any time.
|
||||
* ============================================================================ */
|
||||
|
||||
typedef enum {
|
||||
HBC_LIT_ARRAY = 0,
|
||||
HBC_LIT_OBJECT = 1
|
||||
} HBCLiteralKind;
|
||||
|
||||
/* One entry in the literal cache. The strings / xref array are owned by the
|
||||
* HBC and freed on hbc_close () or hbc_literals_reset (). */
|
||||
typedef struct {
|
||||
HBCLiteralKind kind;
|
||||
u32 num_items;
|
||||
u32 primary_id; /* array_id (array) or keys/shape id (object) */
|
||||
u32 secondary_id; /* values_id for objects, 0 for arrays */
|
||||
u32 paddr; /* absolute file offset of the literal data in the SLP pool (0 if unknown) */
|
||||
char *formatted; /* cached JS literal text; NULL if not yet formatted */
|
||||
u32 *xref_addrs; /* instruction addresses that construct this literal */
|
||||
u32 xref_count;
|
||||
} HBCLiteralEntry;
|
||||
|
||||
/* Format a literal directly from raw parameters. Does not touch the cache.
|
||||
* Returned string is heap-allocated; caller frees with free (). */
|
||||
Result hbc_literals_format_raw(HBC *hbc, HBCLiteralKind kind, u32 num_items, u32 primary_id, u32 secondary_id, char **out);
|
||||
|
||||
/* Convenience wrapper taking opcode + raw arg3/arg4/arg5 as decoded by
|
||||
* hbc_dec (). Knows the per-opcode mapping; useful as the "give me the string
|
||||
* for these parameters" helper. */
|
||||
Result hbc_literals_format_for_opcode(HBC *hbc, u8 opcode, u32 arg3, u32 arg4, u32 arg5, char **out);
|
||||
|
||||
/* Look up (or create) a cache entry for a literal and return the formatted
|
||||
* text. The returned pointer is owned by the cache — do not free it. */
|
||||
Result hbc_literals_get(HBC *hbc, HBCLiteralKind kind, u32 num_items, u32 primary_id, u32 secondary_id, const char **out_formatted);
|
||||
|
||||
/* Register that `from_addr` references the literal described by the key.
|
||||
* Creates the cache entry (and formats) on first reference. */
|
||||
Result hbc_literals_register(HBC *hbc, HBCLiteralKind kind, u32 num_items, u32 primary_id, u32 secondary_id, u32 from_addr);
|
||||
|
||||
/* Full-binary scan: walk every function body; record every
|
||||
* New{Array,Object}WithBuffer[Long] call site into the cache (with xrefs).
|
||||
* Returns the number of distinct literals found in *out_count. */
|
||||
Result hbc_literals_scan_code(HBC *hbc, u32 *out_count);
|
||||
|
||||
/* Pool-side scan: enumerate group boundaries in the SLP pool for the given
|
||||
* kind. For arrays and object-values it walks the byte stream linearly.
|
||||
* For object-keys on v97+ it enumerates the shape table instead.
|
||||
* Does not populate the cache — this is an inspection helper. Caller frees
|
||||
* *out via free (). */
|
||||
typedef struct {
|
||||
u32 paddr; /* absolute file offset */
|
||||
u32 pool_offset; /* byte offset within the pool */
|
||||
u32 num_items; /* number of values in this group (or shape prop_count) */
|
||||
u8 tag; /* SLP tag 0..7 (for arrays/values); 0xff if not applicable */
|
||||
} HBCPoolGroup;
|
||||
|
||||
Result hbc_literals_scan_pool(HBC *hbc, HBCLiteralKind kind, HBCPoolGroup **out, u32 *out_count);
|
||||
|
||||
/* Enumerate the current cache contents. The returned array is owned by the
|
||||
* HBC and valid until the next cache mutation. */
|
||||
Result hbc_literals_list(HBC *hbc, const HBCLiteralEntry **out, u32 *out_count);
|
||||
|
||||
/* Drop every cached entry and every xref. Fast. Does not affect the SLP
|
||||
* pools themselves. */
|
||||
void hbc_literals_reset(HBC *hbc);
|
||||
|
||||
/* Number of cached entries. 0 before any register / scan call. */
|
||||
u32 hbc_literals_count(HBC *hbc);
|
||||
|
||||
/* Absolute file offset of the named pool, or 0 if the pool is empty/absent. */
|
||||
u32 hbc_get_pool_paddr(HBC *hbc, HBCLiteralKind kind);
|
||||
u32 hbc_get_pool_size(HBC *hbc, HBCLiteralKind kind);
|
||||
|
||||
/* Value pool paddr/size for objects (arrays & object-keys reuse the kind-based
|
||||
* accessors; object values is a distinct pool). */
|
||||
u32 hbc_get_object_values_paddr(HBC *hbc);
|
||||
u32 hbc_get_object_values_size(HBC *hbc);
|
||||
|
||||
/* Global toggle: whether hbc_dec () should inline the formatted literal as a
|
||||
* trailing " ; <text>" comment on the disasm line. Default: off. */
|
||||
void hbc_set_inline_literals(bool on);
|
||||
bool hbc_get_inline_literals(void);
|
||||
|
||||
#endif /* LIBHBC_LITERALS_PUBLIC_H */
|
||||
+17
-11
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#ifndef LIBHBC_HERMES_OPCODES_H
|
||||
#define LIBHBC_HERMES_OPCODES_H
|
||||
|
||||
@@ -5,16 +7,16 @@
|
||||
|
||||
/* Operand type enum */
|
||||
typedef enum {
|
||||
OPERAND_TYPE_NONE,
|
||||
OPERAND_TYPE_REG8,
|
||||
OPERAND_TYPE_REG32,
|
||||
OPERAND_TYPE_UINT8,
|
||||
OPERAND_TYPE_UINT16,
|
||||
OPERAND_TYPE_UINT32,
|
||||
OPERAND_TYPE_ADDR8,
|
||||
OPERAND_TYPE_ADDR32,
|
||||
OPERAND_TYPE_IMM32,
|
||||
OPERAND_TYPE_DOUBLE
|
||||
OPERAND_TYPE_NONE, // 0
|
||||
OPERAND_TYPE_REG8, // 'r'
|
||||
OPERAND_TYPE_REG32, // 'R'
|
||||
OPERAND_TYPE_UINT8, // 'b'
|
||||
OPERAND_TYPE_UINT16, // 'u'
|
||||
OPERAND_TYPE_UINT32, // 'U'
|
||||
OPERAND_TYPE_ADDR8, // 'a'
|
||||
OPERAND_TYPE_ADDR32, // 'A'
|
||||
OPERAND_TYPE_IMM32, // 'i'
|
||||
OPERAND_TYPE_DOUBLE // 'd'
|
||||
} OperandType;
|
||||
|
||||
/* Operand meaning enum */
|
||||
@@ -42,7 +44,7 @@ typedef struct {
|
||||
u32 binary_size; /* Total size in bytes */
|
||||
} Instruction;
|
||||
|
||||
/* Hermes opcodes based on hbc95.py (version 95) - Complete list */
|
||||
/* Hermes opcodes based on v95 */
|
||||
enum HermesOpcodes {
|
||||
OP_Unreachable = 0,
|
||||
OP_NewObjectWithBuffer = 1,
|
||||
@@ -265,4 +267,8 @@ typedef struct {
|
||||
HBCISA hbc_isa_getv(int version);
|
||||
u8 hbc_canonical_opcode_from_name(const char *name);
|
||||
|
||||
/* Human-readable label for an operand, derived from its meaning or type.
|
||||
* Never returns NULL; falls back to "Unknown" for unexpected values. */
|
||||
const char *hbc_operand_name(const InstructionOperand *operand);
|
||||
|
||||
#endif /* LIBHBC_HERMES_OPCODES_H */
|
||||
+19
-1
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#ifndef LIBHBC_FILE_PARSER_H
|
||||
#define LIBHBC_FILE_PARSER_H
|
||||
|
||||
@@ -239,6 +241,11 @@ struct HBCReader {
|
||||
ShapeTableEntry *object_shapes;
|
||||
size_t object_shape_count;
|
||||
|
||||
/* Absolute file offsets of the SLP pools (0 if the pool is absent). */
|
||||
u32 arrays_paddr;
|
||||
u32 object_keys_paddr;
|
||||
u32 object_values_paddr;
|
||||
|
||||
/* BigInt data */
|
||||
i64 *bigint_values;
|
||||
size_t bigint_count;
|
||||
@@ -258,11 +265,21 @@ struct HBCReader {
|
||||
FunctionSourceEntry *function_sources;
|
||||
size_t function_source_count;
|
||||
|
||||
/* Debug info */
|
||||
/* Debug info — split into two phases:
|
||||
* debug_info_header_loaded: cheap parse of the DebugInfoHeader fields
|
||||
* only (a handful of u32s), populated eagerly at load time so callers
|
||||
* can report accurate "has source lines" / "stripped" without paying
|
||||
* for the full parse.
|
||||
* debug_info_loaded: full parse of filenames, file regions, and the
|
||||
* sources / scope / textified / string-table storage blobs. Populated
|
||||
* lazily on first hbc_get_source_lines / hbc_get_debug_info call. */
|
||||
bool debug_info_header_loaded;
|
||||
bool debug_info_loaded;
|
||||
DebugInfoHeader debug_info_header;
|
||||
OffsetLengthPair *debug_string_table;
|
||||
u8 *debug_string_storage;
|
||||
size_t debug_string_storage_size;
|
||||
char **debug_filenames;
|
||||
DebugFileRegion *debug_file_regions;
|
||||
size_t debug_file_region_count;
|
||||
u8 *sources_data_storage;
|
||||
@@ -293,6 +310,7 @@ Result _hbc_reader_read_bigints(HBCReader *reader);
|
||||
Result _hbc_reader_read_regexp(HBCReader *reader);
|
||||
Result _hbc_reader_read_cjs_modules(HBCReader *reader);
|
||||
Result _hbc_reader_read_function_sources(HBCReader *reader);
|
||||
Result _hbc_reader_read_debug_info_header(HBCReader *reader);
|
||||
Result _hbc_reader_read_debug_info(HBCReader *reader);
|
||||
Result _hbc_reader_read_whole_file(HBCReader *reader, const char *filename);
|
||||
|
||||
|
||||
+2
-1
@@ -1,7 +1,7 @@
|
||||
project(
|
||||
'r2hermes',
|
||||
'c',
|
||||
version: '1.2.2',
|
||||
version: '1.4.0',
|
||||
license: 'MIT',
|
||||
default_options: [
|
||||
'c_std=c11',
|
||||
@@ -67,6 +67,7 @@ lib_opcodes_src = files(
|
||||
|
||||
lib_core_src = files(
|
||||
'src/lib/hbc.c',
|
||||
'src/lib/literals_api.c',
|
||||
'src/lib/r2.c'
|
||||
)
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#ifdef R2_CORE_H
|
||||
extern RCorePlugin r_core_plugin_r2hermes;
|
||||
#endif
|
||||
+7
-47
@@ -1,66 +1,26 @@
|
||||
# Version
|
||||
# XXX reuse from parent meson.build or wtf
|
||||
version = '1.0.0'
|
||||
# r2hermes XPS integration for radare2.
|
||||
# Included from libr/xps/meson.build when r2hermes sources live under
|
||||
# libr/xps/p/r2hermes/. Appends sources and include paths to the r_core
|
||||
# aggregate so the static plugin is linked into libr_core.
|
||||
|
||||
# Version extraction
|
||||
version_parts = version.split('.')
|
||||
version_major = version_parts[0]
|
||||
version_minor = version_parts.length() > 1 ? version_parts[1] : '0'
|
||||
version_patch = version_parts.length() > 2 ? version_parts[2] : '0'
|
||||
|
||||
# Generate version.h
|
||||
configure_file(
|
||||
input: '../include/hbc/version.h.in',
|
||||
output: 'version.h',
|
||||
configuration: {
|
||||
'VERSION': version,
|
||||
'VERSION_MAJOR': version_major,
|
||||
'VERSION_MINOR': version_minor,
|
||||
'VERSION_PATCH': version_patch,
|
||||
},
|
||||
install_dir: 'libr/hbc',
|
||||
install: true,
|
||||
)
|
||||
|
||||
# Register plugins in the static plugin lists
|
||||
core_plugins += ['r2hermes']
|
||||
|
||||
# Include directories (relative to this meson.build file location)
|
||||
include_dirs = include_directories(join_paths('..', 'include'))
|
||||
r_core_additional_inc += include_dirs
|
||||
r_core_additional_inc += include_directories(join_paths('..', 'include'))
|
||||
|
||||
# Library source files
|
||||
lib_utils_src = files(
|
||||
r_core_additional_sources += files(
|
||||
'../src/lib/utils/string_buffer.c',
|
||||
'../src/lib/utils/buffer_reader.c',
|
||||
)
|
||||
|
||||
lib_parsers_src = files(
|
||||
'../src/lib/parsers/hbc_file_parser.c',
|
||||
'../src/lib/parsers/hbc_bytecode_parser.c',
|
||||
)
|
||||
|
||||
lib_disasm_src = [] # Disassembly sources (if any)
|
||||
|
||||
lib_decompile_src = files(
|
||||
'../src/lib/decompilation/translator.c',
|
||||
'../src/lib/decompilation/token.c',
|
||||
'../src/lib/decompilation/literals.c',
|
||||
'../src/lib/decompilation/decompiler.c',
|
||||
)
|
||||
|
||||
lib_opcodes_src = files(
|
||||
'../src/lib/opcodes/isa.c',
|
||||
'../src/lib/opcodes/encoder.c',
|
||||
'../src/lib/opcodes/decoder.c',
|
||||
)
|
||||
|
||||
lib_core_src = files(
|
||||
'../src/lib/hbc.c',
|
||||
'../src/lib/literals_api.c',
|
||||
'../src/lib/r2.c',
|
||||
'../src/r2/core_hbc_one.c',
|
||||
)
|
||||
|
||||
lib_src = lib_utils_src + lib_parsers_src + lib_disasm_src + lib_decompile_src + lib_opcodes_src + lib_core_src
|
||||
|
||||
r_core_additional_sources += lib_src
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#ifdef R2_CORE_H
|
||||
#include "core/deps.h"
|
||||
#endif
|
||||
|
||||
+1977
-746
File diff suppressed because it is too large.
Load diff
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/decompilation/literals.h>
|
||||
#include <ctype.h>
|
||||
#include <math.h>
|
||||
@@ -91,7 +93,7 @@ static bool slp_read_u16(const u8 *base, size_t size, size_t *pos, u16 *out) {
|
||||
if (*pos + 2 > size) {
|
||||
return false;
|
||||
}
|
||||
*out = (u16) (base[*pos] | (base[*pos + 1] << 8));
|
||||
*out = (u16) ((u16)base[*pos] | ((u16)base[*pos + 1] << 8));
|
||||
(*pos) += 2;
|
||||
return true;
|
||||
}
|
||||
@@ -99,7 +101,7 @@ static bool slp_read_u32(const u8 *base, size_t size, size_t *pos, u32 *out) {
|
||||
if (*pos + 4 > size) {
|
||||
return false;
|
||||
}
|
||||
*out = (u32) (base[*pos] | (base[*pos + 1] << 8) | (base[*pos + 2] << 16) | (base[*pos + 3] << 24));
|
||||
*out = (u32)base[*pos] | ((u32)base[*pos + 1] << 8) | ((u32)base[*pos + 2] << 16) | ((u32)base[*pos + 3] << 24);
|
||||
(*pos) += 4;
|
||||
return true;
|
||||
}
|
||||
@@ -216,8 +218,8 @@ static Result slp_append_value_js(HBCReader *r, const SLPValue *v, bool for_key,
|
||||
case SLP_ShortStringTag:
|
||||
case SLP_ByteStringTag:
|
||||
{
|
||||
const char *s = (r && r->strings && v->v.str_id < r->header.stringCount)? r->strings[v->v.str_id]: NULL;
|
||||
if (for_key && s && _hbc_is_js_identifier (s)) {
|
||||
const char *s = (r && r->strings && v->v.str_id < r->header.stringCount)? r->strings[v->v.str_id]: NULL;
|
||||
if (for_key && s && _hbc_is_js_identifier (s)) {
|
||||
return _hbc_string_buffer_append (out, s);
|
||||
}
|
||||
if (s) {
|
||||
@@ -311,9 +313,11 @@ Result _hbc_format_object_literal(HBCReader *r, u32 key_count, u32 value_count,
|
||||
}
|
||||
|
||||
Result _hbc_format_array_literal(HBCReader *r, u32 value_count, u32 array_id, StringBuffer *out, LiteralsPrettyPolicy policy, bool suppress_comments) {
|
||||
/* Apply policy */
|
||||
bool pretty = (policy == LITERALS_PRETTY_ALWAYS) || (policy == LITERALS_PRETTY_AUTO && (value_count > 0));
|
||||
bool multiline = pretty && (value_count > 0);
|
||||
/* Apply policy. AUTO renders arrays inline (`[1, 2, 3]`): these are data
|
||||
* (e.g. module dependency lists) often hundreds long, and one-element-per-
|
||||
* line dominates the output while its embedded newlines ignore the statement
|
||||
* indent. Only ALWAYS expands them vertically. */
|
||||
bool multiline = (policy == LITERALS_PRETTY_ALWAYS) && (value_count > 0);
|
||||
RETURN_IF_ERROR (_hbc_string_buffer_append (out, multiline? "[\n": "["));
|
||||
if (!r || !r->arrays || value_count == 0) {
|
||||
if (suppress_comments) {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/decompilation/token.h>
|
||||
#include <hbc/decompilation/decompiler.h>
|
||||
#include <hbc/common.h>
|
||||
@@ -326,10 +328,10 @@ Result _hbc_token_to_string(Token *token, StringBuffer *buffer) {
|
||||
{
|
||||
FunctionTableIndexToken *t = (FunctionTableIndexToken *)token;
|
||||
/* Try to resolve name if possible */
|
||||
if (t->state && t->state->hbc_reader && t->function_id < t->state->hbc_reader->header.functionCount) {
|
||||
if (t->state && t->state->hbc_reader && t->function_id < t->state->hbc_reader->header.functionCount) {
|
||||
u32 name_id = t->state->hbc_reader->function_headers[t->function_id].functionName;
|
||||
const char *name = (name_id < t->state->hbc_reader->header.stringCount && t->state->hbc_reader->strings)? t->state->hbc_reader->strings[name_id]: NULL;
|
||||
if (name && *name) {
|
||||
const char *name = (name_id < t->state->hbc_reader->header.stringCount && t->state->hbc_reader->strings)? t->state->hbc_reader->strings[name_id]: NULL;
|
||||
if (name && *name) {
|
||||
RETURN_IF_ERROR (_hbc_string_buffer_append (buffer, name));
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
@@ -443,10 +445,6 @@ Result _hbc_token_to_string(Token *token, StringBuffer *buffer) {
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_punct(TokenType t) {
|
||||
return t == TOKEN_TYPE_LEFT_PARENTHESIS || t == TOKEN_TYPE_RIGHT_PARENTHESIS || t == TOKEN_TYPE_DOT_ACCESSOR;
|
||||
}
|
||||
|
||||
Result _hbc_token_string_to_string(TokenString *ts, StringBuffer *out) {
|
||||
if (!ts || !out) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "_hbc_token_string_to_string: NULL");
|
||||
@@ -454,24 +452,15 @@ Result _hbc_token_string_to_string(TokenString *ts, StringBuffer *out) {
|
||||
Token *cur = ts->head;
|
||||
TokenType prev = (TokenType) (-1);
|
||||
while (cur) {
|
||||
/* spacing rules: add space before '(', no space after '(' or before ')', add spaces around '=' */
|
||||
/* spacing rules: space before '(', none after '(' or '.' nor before ')' or '.' */
|
||||
if (cur != ts->head) {
|
||||
bool need_space = true;
|
||||
/* Space before '(' for readability */
|
||||
bool need_space;
|
||||
if (cur->type == TOKEN_TYPE_LEFT_PARENTHESIS) {
|
||||
need_space = true;
|
||||
}
|
||||
/* No space before other punctuation */
|
||||
else if (is_punct (cur->type) || cur->type == TOKEN_TYPE_RIGHT_PARENTHESIS || cur->type == TOKEN_TYPE_DOT_ACCESSOR) {
|
||||
} else if (cur->type == TOKEN_TYPE_RIGHT_PARENTHESIS || cur->type == TOKEN_TYPE_DOT_ACCESSOR) {
|
||||
need_space = false;
|
||||
}
|
||||
/* No space after '(' or after '.' */
|
||||
else if (prev == TOKEN_TYPE_LEFT_PARENTHESIS || prev == TOKEN_TYPE_DOT_ACCESSOR) {
|
||||
need_space = false;
|
||||
}
|
||||
/* Always space around '=' */
|
||||
else if (prev == TOKEN_TYPE_ASSIGNMENT || cur->type == TOKEN_TYPE_ASSIGNMENT) {
|
||||
need_space = true;
|
||||
} else {
|
||||
need_space = prev != TOKEN_TYPE_LEFT_PARENTHESIS && prev != TOKEN_TYPE_DOT_ACCESSOR;
|
||||
}
|
||||
if (need_space) {
|
||||
RETURN_IF_ERROR (_hbc_string_buffer_append_char (out, ' '));
|
||||
|
||||
+179
-298
@@ -1,8 +1,9 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/decompilation/translator.h>
|
||||
#include <hbc/opcodes.h>
|
||||
#include <hbc/parser.h>
|
||||
#include <stdbool.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include <hbc/decompilation/literals.h>
|
||||
|
||||
@@ -155,7 +156,6 @@ static const char *lookup_op_table(const OpTableEntry *table, u8 opcode) {
|
||||
static Token *reg_l_safe(const ParsedInstruction *insn, int idx);
|
||||
static Token *reg_r_safe(const ParsedInstruction *insn, int idx);
|
||||
static Token *num_token_u32(u32 v);
|
||||
static u32 compute_target_address(const ParsedInstruction *insn, int op_index);
|
||||
|
||||
static Result emit_literal_assign(TokenString *out, const ParsedInstruction *insn, const char *literal) {
|
||||
RETURN_IF_ERROR (add (out, reg_l_safe (insn, 0)));
|
||||
@@ -220,8 +220,10 @@ static Result emit_call_fixed(TokenString *out, const ParsedInstruction *insn, i
|
||||
RETURN_IF_ERROR (add (out, reg_l_safe (insn, 0))); /* dest */
|
||||
RETURN_IF_ERROR (add (out, create_assignment_token ()));
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn, 1))); /* callee */
|
||||
/* Call1..Call4 encode an explicit `this` register before the user arguments. */
|
||||
RETURN_IF_ERROR (add (out, create_raw_token (".call")));
|
||||
RETURN_IF_ERROR (add (out, create_left_parenthesis_token ()));
|
||||
for (int i = 2; i < operand_count; i++) { /* args start at operand 2 */
|
||||
for (int i = 2; i < operand_count; i++) {
|
||||
if (i > 2) {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token (",")));
|
||||
}
|
||||
@@ -230,20 +232,47 @@ static Result emit_call_fixed(TokenString *out, const ParsedInstruction *insn, i
|
||||
return add (out, create_right_parenthesis_token ());
|
||||
}
|
||||
|
||||
static Result emit_call_with_argc(TokenString *out, const ParsedInstruction *insn) {
|
||||
/* Hermes reserves this many frame-header registers at the very top of a
|
||||
* function's register frame; an outgoing call's `this`+args sit just below. */
|
||||
#define HBC_CALL_FRAME_RESERVED 6
|
||||
|
||||
/* Variable-arity Call/Construct. Hermes places the call's argument registers at
|
||||
* the top of the caller's frame, below the reserved header: `this` at
|
||||
* frame_size-1-RESERVED, the explicit arguments descending below it.
|
||||
* Reconstruct them as fn.call (this, a1, ...) (or new fn (a1, ...) for construct).
|
||||
* Falls back to an argc note when the frame layout is unknown or too small. */
|
||||
static Result emit_call_with_argc(TokenString *out, const ParsedInstruction *insn, bool is_construct) {
|
||||
const u32 argc = insn->arg3;
|
||||
RETURN_IF_ERROR (add (out, reg_l_safe (insn, 0)));
|
||||
RETURN_IF_ERROR (add (out, create_assignment_token ()));
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn, 0)));
|
||||
RETURN_IF_ERROR (add (out, create_left_parenthesis_token ()));
|
||||
if (is_construct) {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("new")));
|
||||
}
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn, 1)));
|
||||
char buf2[32];
|
||||
snprintf (buf2, sizeof (buf2), ", /*argc:%u*/", (unsigned)insn->arg3);
|
||||
RETURN_IF_ERROR (add (out, create_raw_token (buf2)));
|
||||
if (!is_construct) {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token (".call")));
|
||||
}
|
||||
RETURN_IF_ERROR (add (out, create_left_parenthesis_token ()));
|
||||
const int base = (int)insn->frame_size - 1 - HBC_CALL_FRAME_RESERVED;
|
||||
if (argc == 0 || base < 0 || (int)argc > base + 1) {
|
||||
char buf2[32];
|
||||
snprintf (buf2, sizeof (buf2), "/*argc:%u*/", (unsigned)argc);
|
||||
RETURN_IF_ERROR (add (out, create_raw_token (buf2)));
|
||||
return add (out, create_right_parenthesis_token ());
|
||||
}
|
||||
/* Construct omits the implicit `this` (the freshly created object). */
|
||||
const u32 first = is_construct? 1: 0;
|
||||
for (u32 i = first; i < argc; i++) {
|
||||
if (i > first) {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token (",")));
|
||||
}
|
||||
RETURN_IF_ERROR (add (out, create_right_hand_reg_token (base - (int)i)));
|
||||
}
|
||||
return add (out, create_right_parenthesis_token ());
|
||||
}
|
||||
|
||||
static Result emit_simple_jump(TokenString *out, const ParsedInstruction *insn, bool positive) {
|
||||
u32 target = compute_target_address (insn, 0);
|
||||
u32 target = _hbc_compute_target_address (insn, 0);
|
||||
if (positive) {
|
||||
RETURN_IF_ERROR (add (out, create_jump_condition_token (target)));
|
||||
return add (out, create_raw_token ("true"));
|
||||
@@ -277,34 +306,6 @@ static bool is_operand_register(const Instruction *inst, int idx) {
|
||||
return t == OPERAND_TYPE_REG8 || t == OPERAND_TYPE_REG32;
|
||||
}
|
||||
|
||||
static bool is_operand_addr(const Instruction *inst, int idx) {
|
||||
if (!inst || idx < 0 || idx >= 6) {
|
||||
return false;
|
||||
}
|
||||
OperandType t = inst->operands[idx].operand_type;
|
||||
return t == OPERAND_TYPE_ADDR8 || t == OPERAND_TYPE_ADDR32;
|
||||
}
|
||||
|
||||
static u32 get_operand_value(const ParsedInstruction *insn, int idx) {
|
||||
switch (idx) {
|
||||
case 0: return insn->arg1;
|
||||
case 1: return insn->arg2;
|
||||
case 2: return insn->arg3;
|
||||
case 3: return insn->arg4;
|
||||
case 4: return insn->arg5;
|
||||
default: return insn->arg6;
|
||||
}
|
||||
}
|
||||
|
||||
static u32 compute_target_address(const ParsedInstruction *insn, int op_index) {
|
||||
u32 v = get_operand_value (insn, op_index);
|
||||
u32 base = insn->original_pos;
|
||||
if (_hbc_is_jump_instruction (insn->opcode)) {
|
||||
base += insn->inst->binary_size;
|
||||
}
|
||||
return base + v;
|
||||
}
|
||||
|
||||
static const char *jump_cmp_operator(u8 op) {
|
||||
switch (op) {
|
||||
case OP_JEqual:
|
||||
@@ -359,7 +360,7 @@ static Token *reg_l_safe(const ParsedInstruction *insn, int idx) {
|
||||
if (!is_operand_register (insn->inst, idx)) {
|
||||
return create_raw_token ("/*not_reg*/");
|
||||
}
|
||||
u32 r = get_operand_value (insn, idx);
|
||||
u32 r = hbc_operand_value (insn, idx);
|
||||
if (!is_valid_register (r, insn)) {
|
||||
/* Out-of-bounds register: show normally but warn in debug mode.
|
||||
* This is a rare error condition indicating bytecode corruption
|
||||
@@ -376,7 +377,7 @@ static Token *reg_r_safe(const ParsedInstruction *insn, int idx) {
|
||||
if (!is_operand_register (insn->inst, idx)) {
|
||||
return create_raw_token ("/*not_reg*/");
|
||||
}
|
||||
u32 r = get_operand_value (insn, idx);
|
||||
u32 r = hbc_operand_value (insn, idx);
|
||||
if (!is_valid_register (r, insn)) {
|
||||
/* Out-of-bounds register: show normally but warn in debug mode.
|
||||
* This is a rare error condition indicating bytecode corruption
|
||||
@@ -451,6 +452,54 @@ static Token *unquoted_string(HBCReader *r, u32 sid) {
|
||||
return create_raw_token ("unknown");
|
||||
}
|
||||
|
||||
/* Emit `.name` when the property is a valid identifier, `["name"]` otherwise */
|
||||
static Result emit_property_access(TokenString *out, const ParsedInstruction *insn, u32 sid) {
|
||||
HBCReader *r = insn->hbc_reader;
|
||||
const char *s = (r && r->strings && sid < r->header.stringCount)? r->strings[sid]: NULL;
|
||||
if (s && _hbc_is_js_identifier (s)) {
|
||||
RETURN_IF_ERROR (add (out, create_dot_accessor_token ()));
|
||||
return add (out, create_raw_token (s));
|
||||
}
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("[")));
|
||||
RETURN_IF_ERROR (add (out, quoted_string (r, sid)));
|
||||
return add (out, create_raw_token ("]"));
|
||||
}
|
||||
|
||||
/* Emit a compare-and-jump as `if (rA cmp rB)` or its negated goto form */
|
||||
static Result emit_cmp_jump(TokenString *out, const ParsedInstruction *insn_c, const char *cmp) {
|
||||
i32 rel = (i32)hbc_operand_value (insn_c, 0);
|
||||
u32 target = _hbc_compute_target_address (insn_c, 0);
|
||||
if (rel > 0) {
|
||||
RETURN_IF_ERROR (add (out, create_jump_not_condition_token (target)));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("!")));
|
||||
RETURN_IF_ERROR (add (out, create_left_parenthesis_token ()));
|
||||
} else {
|
||||
RETURN_IF_ERROR (add (out, create_jump_condition_token (target)));
|
||||
}
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 1)));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token (cmp)));
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 2)));
|
||||
if (rel > 0) {
|
||||
RETURN_IF_ERROR (add (out, create_right_parenthesis_token ()));
|
||||
}
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
/* Emit `rD = <function table entry>` with environment and closure kind flags */
|
||||
static Result emit_closure_token(TokenString *out, const ParsedInstruction *insn_c, bool is_closure, bool is_generator, bool is_async) {
|
||||
RETURN_IF_ERROR (add (out, reg_l_safe (insn_c, 0)));
|
||||
RETURN_IF_ERROR (add (out, create_assignment_token ()));
|
||||
FunctionTableIndexToken *t = (FunctionTableIndexToken *)create_function_table_index_token (hbc_operand_value (insn_c, 2), NULL);
|
||||
if (!t) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "oom fti");
|
||||
}
|
||||
t->environment_id = (int)hbc_operand_value (insn_c, 1);
|
||||
t->is_closure = is_closure;
|
||||
t->is_generator = is_generator;
|
||||
t->is_async = is_async;
|
||||
return add (out, (Token *)t);
|
||||
}
|
||||
|
||||
Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, TokenString *out) {
|
||||
if (!insn_c || !out || !insn_c->inst) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "translate: bad args");
|
||||
@@ -534,10 +583,12 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
case OP_LoadParam:
|
||||
case OP_LoadParamLong:
|
||||
{
|
||||
/* LoadParam index maps directly to args[i]: index 0 is `this`,
|
||||
* matching the a0..aN-1 names emitted in the function signature. */
|
||||
RETURN_IF_ERROR (add (out, reg_l_safe (insn_c, 0)));
|
||||
RETURN_IF_ERROR (add (out, create_assignment_token ()));
|
||||
char buf[32];
|
||||
snprintf (buf, sizeof (buf), "args[%u]", insn->arg2 > 0? insn->arg2 - 1: 0);
|
||||
snprintf (buf, sizeof (buf), "args[%u]", insn->arg2);
|
||||
RETURN_IF_ERROR (add (out, create_raw_token (buf)));
|
||||
break;
|
||||
}
|
||||
@@ -557,7 +608,7 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
{
|
||||
/* catch (rN) marker */
|
||||
if (is_operand_register (insn->inst, 0)) {
|
||||
RETURN_IF_ERROR (add (out, create_catch_block_start_token ((int)get_operand_value (insn, 0))));
|
||||
RETURN_IF_ERROR (add (out, create_catch_block_start_token ((int)hbc_operand_value (insn, 0))));
|
||||
} else {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("/*catch non-reg*/")));
|
||||
}
|
||||
@@ -567,7 +618,7 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
{
|
||||
/* Create a new environment object in rD */
|
||||
if (is_operand_register (insn->inst, 0)) {
|
||||
RETURN_IF_ERROR (add (out, create_new_environment_token ((int)get_operand_value (insn_c, 0))));
|
||||
RETURN_IF_ERROR (add (out, create_new_environment_token ((int)hbc_operand_value (insn_c, 0))));
|
||||
} else {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("/*new_env_invalid*/")));
|
||||
}
|
||||
@@ -575,8 +626,8 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
}
|
||||
case OP_CreateInnerEnvironment:
|
||||
{
|
||||
if (is_operand_register (insn->inst, 0) && is_operand_register (insn->inst, 1)) {
|
||||
RETURN_IF_ERROR (add (out, create_new_inner_environment_token ((int)get_operand_value (insn_c, 0), (int)get_operand_value (insn_c, 1), (int)get_operand_value (insn_c, 2))));
|
||||
if (is_operand_register (insn->inst, 0) && is_operand_register (insn->inst, 1)) {
|
||||
RETURN_IF_ERROR (add (out, create_new_inner_environment_token ((int)hbc_operand_value (insn_c, 0), (int)hbc_operand_value (insn_c, 1), (int)hbc_operand_value (insn_c, 2))));
|
||||
} else {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("/*new_inner_env_invalid*/")));
|
||||
}
|
||||
@@ -584,47 +635,16 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
}
|
||||
case OP_CreateClosure:
|
||||
case OP_CreateClosureLongIndex:
|
||||
{
|
||||
RETURN_IF_ERROR (add (out, reg_l_safe (insn_c, 0)));
|
||||
RETURN_IF_ERROR (add (out, create_assignment_token ()));
|
||||
FunctionTableIndexToken *t = (FunctionTableIndexToken *)create_function_table_index_token (get_operand_value (insn_c, 2), NULL);
|
||||
if (!t) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "oom fti");
|
||||
}
|
||||
t->environment_id = (int)get_operand_value (insn_c, 1);
|
||||
t->is_closure = true;
|
||||
RETURN_IF_ERROR (add (out, (Token *)t));
|
||||
break;
|
||||
}
|
||||
RETURN_IF_ERROR (emit_closure_token (out, insn_c, true, false, false));
|
||||
break;
|
||||
case OP_CreateGeneratorClosure:
|
||||
case OP_CreateGeneratorClosureLongIndex:
|
||||
{
|
||||
RETURN_IF_ERROR (add (out, reg_l_safe (insn_c, 0)));
|
||||
RETURN_IF_ERROR (add (out, create_assignment_token ()));
|
||||
FunctionTableIndexToken *t = (FunctionTableIndexToken *)create_function_table_index_token (get_operand_value (insn_c, 2), NULL);
|
||||
if (!t) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "oom fti");
|
||||
}
|
||||
t->environment_id = (int)get_operand_value (insn_c, 1);
|
||||
t->is_closure = true;
|
||||
t->is_generator = true;
|
||||
RETURN_IF_ERROR (add (out, (Token *)t));
|
||||
break;
|
||||
}
|
||||
RETURN_IF_ERROR (emit_closure_token (out, insn_c, true, true, false));
|
||||
break;
|
||||
case OP_CreateGenerator:
|
||||
case OP_CreateGeneratorLongIndex:
|
||||
{
|
||||
RETURN_IF_ERROR (add (out, reg_l_safe (insn_c, 0)));
|
||||
RETURN_IF_ERROR (add (out, create_assignment_token ()));
|
||||
FunctionTableIndexToken *t = (FunctionTableIndexToken *)create_function_table_index_token (get_operand_value (insn_c, 2), NULL);
|
||||
if (!t) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "oom fti");
|
||||
}
|
||||
t->environment_id = (int)get_operand_value (insn_c, 1);
|
||||
t->is_generator = true;
|
||||
RETURN_IF_ERROR (add (out, (Token *)t));
|
||||
break;
|
||||
}
|
||||
RETURN_IF_ERROR (emit_closure_token (out, insn_c, false, true, false));
|
||||
break;
|
||||
case OP_NewArray:
|
||||
{
|
||||
/* rD = new Array (count) */
|
||||
@@ -642,30 +662,7 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
RETURN_IF_ERROR (add (out, reg_l_safe (insn_c, 0)));
|
||||
RETURN_IF_ERROR (add (out, create_assignment_token ()));
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 1)));
|
||||
u32 sid = insn->arg4;
|
||||
const char *s = NULL;
|
||||
if (insn->hbc_reader && insn->hbc_reader->strings && sid < insn->hbc_reader->header.stringCount) {
|
||||
s = insn->hbc_reader->strings[sid];
|
||||
}
|
||||
bool ident = true;
|
||||
if (!s || !*s) {
|
||||
ident = false;
|
||||
} else {
|
||||
if (! (isalpha ((unsigned char)*s) || *s == '_' || *s == '$')) {
|
||||
ident = false;
|
||||
}
|
||||
for (const char *p = s + 1; ident && *p; p++) {
|
||||
ident = (isalnum ((unsigned char)*p) || *p == '_' || *p == '$');
|
||||
}
|
||||
}
|
||||
if (ident) {
|
||||
RETURN_IF_ERROR (add (out, create_dot_accessor_token ()));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token (s)));
|
||||
} else {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("[")));
|
||||
RETURN_IF_ERROR (add (out, quoted_string (insn->hbc_reader, sid)));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("]")));
|
||||
}
|
||||
RETURN_IF_ERROR (emit_property_access (out, insn_c, insn->arg4));
|
||||
break;
|
||||
}
|
||||
case OP_GetByVal:
|
||||
@@ -685,12 +682,12 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
RETURN_IF_ERROR (add (out, reg_l_safe (insn_c, 0)));
|
||||
RETURN_IF_ERROR (add (out, create_assignment_token ()));
|
||||
StringBuffer sb;
|
||||
_hbc_string_buffer_init (&sb, 48);
|
||||
RETURN_IF_ERROR (_hbc_string_buffer_init (&sb, 48));
|
||||
_hbc_string_buffer_append (&sb, "try_get(");
|
||||
char nb[16];
|
||||
snprintf (nb, sizeof (nb), "r%u", (unsigned)insn->arg2);
|
||||
_hbc_string_buffer_append (&sb, nb);
|
||||
_hbc_format_property_from_string_id (insn->hbc_reader, (op == OP_TryGetById)? insn->arg4: insn->arg4, &sb);
|
||||
_hbc_format_property_from_string_id (insn->hbc_reader, insn->arg4, &sb);
|
||||
_hbc_string_buffer_append (&sb, ")");
|
||||
Token *t = create_raw_token (sb.data? sb.data: "try_get(r0)");
|
||||
_hbc_string_buffer_free (&sb);
|
||||
@@ -703,20 +700,15 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
case OP_PutNewOwnByIdShort:
|
||||
case OP_PutNewOwnById:
|
||||
case OP_PutNewOwnByIdLong:
|
||||
case OP_TryPutById:
|
||||
case OP_TryPutByIdLong:
|
||||
case OP_PutNewOwnNEById:
|
||||
case OP_PutNewOwnNEByIdLong:
|
||||
{
|
||||
/* obj.name = value (prefer dot accessor) */
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 0)));
|
||||
u32 sid = (op == OP_PutNewOwnByIdShort)? insn->arg3: (op == OP_PutNewOwnById)? insn->arg3
|
||||
: insn->arg4;
|
||||
const char *s = (insn->hbc_reader && insn->hbc_reader->strings && sid < insn->hbc_reader->header.stringCount)? insn->hbc_reader->strings[sid]: NULL;
|
||||
if (s && _hbc_is_js_identifier (s)) {
|
||||
RETURN_IF_ERROR (add (out, create_dot_accessor_token ()));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token (s)));
|
||||
} else {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("[")));
|
||||
RETURN_IF_ERROR (add (out, quoted_string (insn->hbc_reader, sid)));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("]")));
|
||||
}
|
||||
u32 sid = (op == OP_PutNewOwnByIdLong || op == OP_PutNewOwnNEByIdLong)? insn->arg4: insn->arg3;
|
||||
RETURN_IF_ERROR (emit_property_access (out, insn_c, sid));
|
||||
RETURN_IF_ERROR (add (out, create_assignment_token ()));
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 1)));
|
||||
break;
|
||||
@@ -724,13 +716,13 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
case OP_PutOwnByIndex:
|
||||
case OP_PutOwnByIndexL:
|
||||
{
|
||||
/* obj[idx] = value */
|
||||
/* obj[idx] = value; operands: obj reg, value reg, literal idx */
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 0)));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("[")));
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 1)));
|
||||
RETURN_IF_ERROR (add (out, num_token_u32 (insn->arg3)));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("]")));
|
||||
RETURN_IF_ERROR (add (out, create_assignment_token ()));
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 2)));
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 1)));
|
||||
break;
|
||||
}
|
||||
case OP_PutByVal:
|
||||
@@ -747,12 +739,9 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
case OP_PutById:
|
||||
case OP_PutByIdLong:
|
||||
{
|
||||
/* rObj["name"] = rVal */
|
||||
/* rObj.name = rVal (prefer dot accessor, matching GetById) */
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 0)));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("[")));
|
||||
u32 sid = (op == OP_PutById)? insn->arg4: insn->arg4;
|
||||
RETURN_IF_ERROR (add (out, quoted_string (insn->hbc_reader, sid)));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("]")));
|
||||
RETURN_IF_ERROR (emit_property_access (out, insn_c, insn->arg4));
|
||||
RETURN_IF_ERROR (add (out, create_assignment_token ()));
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 1)));
|
||||
break;
|
||||
@@ -810,7 +799,7 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
case OP_ResumeGenerator:
|
||||
{
|
||||
/* args: result_out, return_bool_out */
|
||||
if (is_operand_register (insn->inst, 0) && is_operand_register (insn->inst, 1)) {
|
||||
if (is_operand_register (insn->inst, 0) && is_operand_register (insn->inst, 1)) {
|
||||
RETURN_IF_ERROR (add (out, create_resume_generator_token ((int)insn->arg1, (int)insn->arg2)));
|
||||
} else {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("/*resume_gen_invalid*/")));
|
||||
@@ -820,14 +809,14 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
case OP_SaveGenerator:
|
||||
case OP_SaveGeneratorLong:
|
||||
{
|
||||
u32 target = compute_target_address (insn, 0);
|
||||
u32 target = _hbc_compute_target_address (insn, 0);
|
||||
RETURN_IF_ERROR (add (out, create_save_generator_token (target)));
|
||||
break;
|
||||
}
|
||||
case OP_GetEnvironment:
|
||||
{
|
||||
if (is_operand_register (insn->inst, 0)) {
|
||||
RETURN_IF_ERROR (add (out, create_get_environment_token ((int)get_operand_value (insn_c, 0), (int)get_operand_value (insn_c, 1))));
|
||||
RETURN_IF_ERROR (add (out, create_get_environment_token ((int)hbc_operand_value (insn_c, 0), (int)hbc_operand_value (insn_c, 1))));
|
||||
} else {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("/*get_env_invalid*/")));
|
||||
}
|
||||
@@ -835,24 +824,17 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
}
|
||||
case OP_StoreToEnvironment:
|
||||
case OP_StoreToEnvironmentL:
|
||||
{
|
||||
if (is_operand_register (insn->inst, 0) && is_operand_register (insn->inst, 2)) {
|
||||
RETURN_IF_ERROR (add (out, create_store_to_environment_token ((int)get_operand_value (insn_c, 0), (int)get_operand_value (insn_c, 1), (int)get_operand_value (insn_c, 2))));
|
||||
} else {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("/*env_store_invalid*/")));
|
||||
}
|
||||
break;
|
||||
}
|
||||
/* The NP ("non-pointer") variant has identical operands and decompiled
|
||||
* semantics; route it through the same closure-var path so it is named
|
||||
* consistently and not misread as a plain register write by DCE. */
|
||||
case OP_StoreNPToEnvironment:
|
||||
case OP_StoreNPToEnvironmentL:
|
||||
{
|
||||
/* env[slot] = value (NP variant) */
|
||||
RETURN_IF_ERROR (add (out, reg_l_safe (insn_c, 0)));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("[")));
|
||||
RETURN_IF_ERROR (add (out, num_token_u32 (insn->arg2)));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("]")));
|
||||
RETURN_IF_ERROR (add (out, create_assignment_token ()));
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 2)));
|
||||
if (is_operand_register (insn->inst, 0) && is_operand_register (insn->inst, 2)) {
|
||||
RETURN_IF_ERROR (add (out, create_store_to_environment_token ((int)hbc_operand_value (insn_c, 0), (int)hbc_operand_value (insn_c, 1), (int)hbc_operand_value (insn_c, 2))));
|
||||
} else {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("/*env_store_invalid*/")));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OP_LoadFromEnvironment:
|
||||
@@ -862,7 +844,7 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
RETURN_IF_ERROR (add (out, reg_l_safe (insn_c, 0)));
|
||||
RETURN_IF_ERROR (add (out, create_assignment_token ()));
|
||||
if (is_operand_register (insn->inst, 1)) {
|
||||
RETURN_IF_ERROR (add (out, create_load_from_environment_token ((int)get_operand_value (insn_c, 1), (int)get_operand_value (insn_c, 2))));
|
||||
RETURN_IF_ERROR (add (out, create_load_from_environment_token ((int)hbc_operand_value (insn_c, 1), (int)hbc_operand_value (insn_c, 2))));
|
||||
} else {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("/*env_load_invalid*/")));
|
||||
}
|
||||
@@ -879,7 +861,7 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
return r;
|
||||
}
|
||||
r = _hbc_format_object_literal (insn->hbc_reader, insn->arg2, insn->arg3, insn->arg4, insn->arg5, &sb, LITERALS_PRETTY_AUTO, false);
|
||||
Token *t = create_raw_token ((r.code == RESULT_SUCCESS && sb.data)? sb.data: "{ /*object*/ }");
|
||||
Token *t = create_raw_token ((r.code == RESULT_SUCCESS && sb.data)? sb.data: "{ /*object*/ }");
|
||||
_hbc_string_buffer_free (&sb);
|
||||
if (!t) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "oom");
|
||||
@@ -898,7 +880,7 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
return r;
|
||||
}
|
||||
r = _hbc_format_array_literal (insn->hbc_reader, insn->arg3, insn->arg4, &sb, LITERALS_PRETTY_AUTO, false);
|
||||
Token *t = create_raw_token ((r.code == RESULT_SUCCESS && sb.data)? sb.data: "[ /*array*/ ]");
|
||||
Token *t = create_raw_token ((r.code == RESULT_SUCCESS && sb.data)? sb.data: "[ /*array*/ ]");
|
||||
_hbc_string_buffer_free (&sb);
|
||||
if (!t) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "oom");
|
||||
@@ -916,9 +898,10 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
return emit_call_fixed (out, insn_c, 6); /* dest, callee, arg1, arg2, arg3, arg4 */
|
||||
case OP_Call:
|
||||
case OP_CallLong:
|
||||
return emit_call_with_argc (out, insn_c, false);
|
||||
case OP_Construct:
|
||||
case OP_ConstructLong:
|
||||
return emit_call_with_argc (out, insn_c);
|
||||
return emit_call_with_argc (out, insn_c, true);
|
||||
/* Delete operations */
|
||||
case OP_DelById:
|
||||
case OP_DelByIdLong:
|
||||
@@ -927,10 +910,10 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 0)));
|
||||
u32 sid = insn->arg2;
|
||||
const char *s = NULL;
|
||||
if (insn->hbc_reader && insn->hbc_reader->strings && sid < insn->hbc_reader->header.stringCount) {
|
||||
if (insn->hbc_reader && insn->hbc_reader->strings && sid < insn->hbc_reader->header.stringCount) {
|
||||
s = insn->hbc_reader->strings[sid];
|
||||
}
|
||||
if (s && _hbc_is_js_identifier (s)) {
|
||||
if (s && _hbc_is_js_identifier (s)) {
|
||||
RETURN_IF_ERROR (add (out, create_dot_accessor_token ()));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token (s)));
|
||||
} else {
|
||||
@@ -973,12 +956,12 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
/* Jump instructions */
|
||||
case OP_Jmp:
|
||||
case OP_JmpLong:
|
||||
return emit_simple_jump (out, insn_c, (i32)get_operand_value (insn_c, 0) <= 0);
|
||||
return emit_simple_jump (out, insn_c, (i32)hbc_operand_value (insn_c, 0) <= 0);
|
||||
case OP_JmpTrue:
|
||||
case OP_JmpTrueLong:
|
||||
{
|
||||
i32 rel = (i32)get_operand_value (insn_c, 0);
|
||||
u32 target = compute_target_address (insn, 0);
|
||||
i32 rel = (i32)hbc_operand_value (insn_c, 0);
|
||||
u32 target = _hbc_compute_target_address (insn, 0);
|
||||
if (rel > 0) {
|
||||
RETURN_IF_ERROR (add (out, create_jump_not_condition_token (target)));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("!")));
|
||||
@@ -992,8 +975,8 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
case OP_JmpFalse:
|
||||
case OP_JmpFalseLong:
|
||||
{
|
||||
i32 rel = (i32)get_operand_value (insn_c, 0);
|
||||
u32 target = compute_target_address (insn, 0);
|
||||
i32 rel = (i32)hbc_operand_value (insn_c, 0);
|
||||
u32 target = _hbc_compute_target_address (insn, 0);
|
||||
if (rel > 0) {
|
||||
RETURN_IF_ERROR (add (out, create_jump_not_condition_token (target)));
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 1)));
|
||||
@@ -1007,8 +990,8 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
case OP_JmpUndefined:
|
||||
case OP_JmpUndefinedLong:
|
||||
{
|
||||
i32 rel = (i32)get_operand_value (insn_c, 0);
|
||||
u32 target = compute_target_address (insn, 0);
|
||||
i32 rel = (i32)hbc_operand_value (insn_c, 0);
|
||||
u32 target = _hbc_compute_target_address (insn, 0);
|
||||
if (rel > 0) {
|
||||
RETURN_IF_ERROR (add (out, create_jump_not_condition_token (target)));
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 1)));
|
||||
@@ -1039,27 +1022,30 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
case OP_JStrictEqualLong:
|
||||
case OP_JStrictNotEqual:
|
||||
case OP_JStrictNotEqualLong:
|
||||
/* Conditional jump instructions (N variants) */
|
||||
case OP_JLessN:
|
||||
case OP_JLessNLong:
|
||||
case OP_JNotLessN:
|
||||
case OP_JNotLessNLong:
|
||||
case OP_JGreaterN:
|
||||
case OP_JGreaterNLong:
|
||||
case OP_JNotGreaterN:
|
||||
case OP_JNotGreaterNLong:
|
||||
case OP_JLessEqualN:
|
||||
case OP_JLessEqualNLong:
|
||||
case OP_JNotLessEqualN:
|
||||
case OP_JNotLessEqualNLong:
|
||||
case OP_JGreaterEqualN:
|
||||
case OP_JGreaterEqualNLong:
|
||||
case OP_JNotGreaterEqualN:
|
||||
case OP_JNotGreaterEqualNLong:
|
||||
{
|
||||
const char *cmp = jump_cmp_operator (op);
|
||||
if (!cmp) {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("/*jump_cmp*/")));
|
||||
break;
|
||||
}
|
||||
i32 rel = (i32)get_operand_value (insn_c, 0);
|
||||
u32 target = compute_target_address (insn, 0);
|
||||
if (rel > 0) {
|
||||
RETURN_IF_ERROR (add (out, create_jump_not_condition_token (target)));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("!")));
|
||||
RETURN_IF_ERROR (add (out, create_left_parenthesis_token ()));
|
||||
} else {
|
||||
RETURN_IF_ERROR (add (out, create_jump_condition_token (target)));
|
||||
}
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 1)));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token (cmp)));
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 2)));
|
||||
if (rel > 0) {
|
||||
RETURN_IF_ERROR (add (out, create_right_parenthesis_token ()));
|
||||
}
|
||||
RETURN_IF_ERROR (emit_cmp_jump (out, insn_c, cmp));
|
||||
break;
|
||||
}
|
||||
/* Built-in calls */
|
||||
@@ -1083,7 +1069,7 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
{
|
||||
RETURN_IF_ERROR (add (out, reg_l_safe (insn_c, 0)));
|
||||
RETURN_IF_ERROR (add (out, create_assignment_token ()));
|
||||
FunctionTableIndexToken *t = (FunctionTableIndexToken *)create_function_table_index_token (get_operand_value (insn_c, 1), NULL);
|
||||
FunctionTableIndexToken *t = (FunctionTableIndexToken *)create_function_table_index_token (hbc_operand_value (insn_c, 1), NULL);
|
||||
if (!t) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "oom fti");
|
||||
}
|
||||
@@ -1097,11 +1083,14 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
{
|
||||
RETURN_IF_ERROR (add (out, reg_l_safe (insn_c, 0)));
|
||||
RETURN_IF_ERROR (add (out, create_assignment_token ()));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("fn_")));
|
||||
RETURN_IF_ERROR (add (out, num_token_u32 ((op == OP_CallDirect)? insn->arg4: insn->arg4)));
|
||||
Token *t = create_function_table_index_token (hbc_operand_value (insn_c, 2), NULL);
|
||||
if (!t) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "oom fti");
|
||||
}
|
||||
RETURN_IF_ERROR (add (out, t));
|
||||
RETURN_IF_ERROR (add (out, create_left_parenthesis_token ()));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("/*argc:")));
|
||||
RETURN_IF_ERROR (add (out, num_token_u32 (insn->arg3)));
|
||||
RETURN_IF_ERROR (add (out, num_token_u32 (hbc_operand_value (insn_c, 1))));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("*/")));
|
||||
RETURN_IF_ERROR (add (out, create_right_parenthesis_token ()));
|
||||
break;
|
||||
@@ -1151,53 +1140,10 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("switch(")));
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 0)));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token (") /*tableSize:")));
|
||||
RETURN_IF_ERROR (add (out, num_token_u32 (insn->arg2)));
|
||||
RETURN_IF_ERROR (add (out, num_token_u32 (insn->switch_jump_table_size)));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("*/")));
|
||||
break;
|
||||
}
|
||||
/* Property operations (variants) */
|
||||
case OP_TryPutById:
|
||||
case OP_TryPutByIdLong:
|
||||
{
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 0)));
|
||||
u32 sid = insn->arg3;
|
||||
const char *s = NULL;
|
||||
if (insn->hbc_reader && insn->hbc_reader->strings && sid < insn->hbc_reader->header.stringCount) {
|
||||
s = insn->hbc_reader->strings[sid];
|
||||
}
|
||||
if (s && _hbc_is_js_identifier (s)) {
|
||||
RETURN_IF_ERROR (add (out, create_dot_accessor_token ()));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token (s)));
|
||||
} else {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("[")));
|
||||
RETURN_IF_ERROR (add (out, quoted_string (insn->hbc_reader, sid)));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("]")));
|
||||
}
|
||||
RETURN_IF_ERROR (add (out, create_assignment_token ()));
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 1)));
|
||||
break;
|
||||
}
|
||||
case OP_PutNewOwnNEById:
|
||||
case OP_PutNewOwnNEByIdLong:
|
||||
{
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 0)));
|
||||
u32 sid = (op == OP_PutNewOwnNEById)? insn->arg3: insn->arg4;
|
||||
const char *s = NULL;
|
||||
if (insn->hbc_reader && insn->hbc_reader->strings && sid < insn->hbc_reader->header.stringCount) {
|
||||
s = insn->hbc_reader->strings[sid];
|
||||
}
|
||||
if (s && _hbc_is_js_identifier (s)) {
|
||||
RETURN_IF_ERROR (add (out, create_dot_accessor_token ()));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token (s)));
|
||||
} else {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("[")));
|
||||
RETURN_IF_ERROR (add (out, quoted_string (insn->hbc_reader, sid)));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("]")));
|
||||
}
|
||||
RETURN_IF_ERROR (add (out, create_assignment_token ()));
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 1)));
|
||||
break;
|
||||
}
|
||||
case OP_PutOwnByVal:
|
||||
{
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 0)));
|
||||
@@ -1224,9 +1170,9 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
/* Property list operations */
|
||||
case OP_GetPNameList:
|
||||
{
|
||||
if (is_operand_register (insn->inst, 0) && is_operand_register (insn->inst, 1) &&
|
||||
is_operand_register (insn->inst, 2) && is_operand_register (insn->inst, 3)) {
|
||||
RETURN_IF_ERROR (add (out, create_for_in_loop_init_token ((int)get_operand_value (insn_c, 0), (int)get_operand_value (insn_c, 1), (int)get_operand_value (insn_c, 2), (int)get_operand_value (insn_c, 3))));
|
||||
if (is_operand_register (insn->inst, 0) && is_operand_register (insn->inst, 1) &&
|
||||
is_operand_register (insn->inst, 2) && is_operand_register (insn->inst, 3)) {
|
||||
RETURN_IF_ERROR (add (out, create_for_in_loop_init_token ((int)hbc_operand_value (insn_c, 0), (int)hbc_operand_value (insn_c, 1), (int)hbc_operand_value (insn_c, 2), (int)hbc_operand_value (insn_c, 3))));
|
||||
} else {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("/*forin_init_invalid*/")));
|
||||
}
|
||||
@@ -1234,10 +1180,10 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
}
|
||||
case OP_GetNextPName:
|
||||
{
|
||||
if (is_operand_register (insn->inst, 0) && is_operand_register (insn->inst, 1) &&
|
||||
is_operand_register (insn->inst, 2) && is_operand_register (insn->inst, 3) &&
|
||||
if (is_operand_register (insn->inst, 0) && is_operand_register (insn->inst, 1) &&
|
||||
is_operand_register (insn->inst, 2) && is_operand_register (insn->inst, 3) &&
|
||||
is_operand_register (insn->inst, 4)) {
|
||||
RETURN_IF_ERROR (add (out, create_for_in_loop_next_iter_token ((int)get_operand_value (insn_c, 0), (int)get_operand_value (insn_c, 1), (int)get_operand_value (insn_c, 2), (int)get_operand_value (insn_c, 3), (int)get_operand_value (insn_c, 4))));
|
||||
RETURN_IF_ERROR (add (out, create_for_in_loop_next_iter_token ((int)hbc_operand_value (insn_c, 0), (int)hbc_operand_value (insn_c, 1), (int)hbc_operand_value (insn_c, 2), (int)hbc_operand_value (insn_c, 3), (int)hbc_operand_value (insn_c, 4))));
|
||||
} else {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("/*forin_next_invalid*/")));
|
||||
}
|
||||
@@ -1246,62 +1192,11 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
/* Async closures */
|
||||
case OP_CreateAsyncClosure:
|
||||
case OP_CreateAsyncClosureLongIndex:
|
||||
{
|
||||
RETURN_IF_ERROR (add (out, reg_l_safe (insn_c, 0)));
|
||||
RETURN_IF_ERROR (add (out, create_assignment_token ()));
|
||||
FunctionTableIndexToken *t = (FunctionTableIndexToken *)create_function_table_index_token (get_operand_value (insn_c, 2), NULL);
|
||||
if (!t) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "oom fti");
|
||||
}
|
||||
t->environment_id = (int)get_operand_value (insn_c, 1);
|
||||
t->is_async = true;
|
||||
t->is_closure = true;
|
||||
RETURN_IF_ERROR (add (out, (Token *)t));
|
||||
break;
|
||||
}
|
||||
/* Conditional jump instructions (N variants) */
|
||||
case OP_JLessN:
|
||||
case OP_JLessNLong:
|
||||
case OP_JNotLessN:
|
||||
case OP_JNotLessNLong:
|
||||
case OP_JGreaterN:
|
||||
case OP_JGreaterNLong:
|
||||
case OP_JNotGreaterN:
|
||||
case OP_JNotGreaterNLong:
|
||||
case OP_JLessEqualN:
|
||||
case OP_JLessEqualNLong:
|
||||
case OP_JNotLessEqualN:
|
||||
case OP_JNotLessEqualNLong:
|
||||
case OP_JGreaterEqualN:
|
||||
case OP_JGreaterEqualNLong:
|
||||
case OP_JNotGreaterEqualN:
|
||||
case OP_JNotGreaterEqualNLong:
|
||||
{
|
||||
const char *cmp = jump_cmp_operator (op);
|
||||
if (!cmp) {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("/*jump_cmp*/")));
|
||||
break;
|
||||
}
|
||||
i32 rel = (i32)get_operand_value (insn_c, 0);
|
||||
u32 target = compute_target_address (insn, 0);
|
||||
if (rel > 0) {
|
||||
RETURN_IF_ERROR (add (out, create_jump_not_condition_token (target)));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("!")));
|
||||
RETURN_IF_ERROR (add (out, create_left_parenthesis_token ()));
|
||||
} else {
|
||||
RETURN_IF_ERROR (add (out, create_jump_condition_token (target)));
|
||||
}
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 1)));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token (cmp)));
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 2)));
|
||||
if (rel > 0) {
|
||||
RETURN_IF_ERROR (add (out, create_right_parenthesis_token ()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
RETURN_IF_ERROR (emit_closure_token (out, insn_c, true, false, true));
|
||||
break;
|
||||
default:
|
||||
{
|
||||
if (!insn->inst->name || strcmp (insn->inst->name, "Unknown") == 0) {
|
||||
if (!insn->inst->name || strcmp (insn->inst->name, "Unknown") == 0) {
|
||||
char buf[96];
|
||||
u32 skipped = insn->arg1;
|
||||
if (skipped) {
|
||||
@@ -1319,28 +1214,14 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
|
||||
/* Generic compare-jump fallback (covers variants not explicitly handled above). */
|
||||
const char *cmp = jump_cmp_operator (op);
|
||||
if (cmp && is_operand_addr (insn->inst, 0) && is_operand_register (insn->inst, 1) && is_operand_register (insn->inst, 2)) {
|
||||
i32 rel = (i32)get_operand_value (insn_c, 0);
|
||||
u32 target = compute_target_address (insn, 0);
|
||||
if (rel > 0) {
|
||||
RETURN_IF_ERROR (add (out, create_jump_not_condition_token (target)));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("!")));
|
||||
RETURN_IF_ERROR (add (out, create_left_parenthesis_token ()));
|
||||
} else {
|
||||
RETURN_IF_ERROR (add (out, create_jump_condition_token (target)));
|
||||
}
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 1)));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token (cmp)));
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 2)));
|
||||
if (rel > 0) {
|
||||
RETURN_IF_ERROR (add (out, create_right_parenthesis_token ()));
|
||||
}
|
||||
if (cmp && _hbc_operand_is_addr (insn->inst, 0) && is_operand_register (insn->inst, 1) && is_operand_register (insn->inst, 2)) {
|
||||
RETURN_IF_ERROR (emit_cmp_jump (out, insn_c, cmp));
|
||||
break;
|
||||
}
|
||||
|
||||
/* Fallback: emit a comment-like raw token with mnemonic */
|
||||
StringBuffer sb;
|
||||
_hbc_string_buffer_init (&sb, 64);
|
||||
RETURN_IF_ERROR (_hbc_string_buffer_init (&sb, 64));
|
||||
_hbc_string_buffer_append (&sb, "/* ");
|
||||
_hbc_string_buffer_append (&sb, insn->inst->name);
|
||||
_hbc_string_buffer_append (&sb, " */");
|
||||
|
||||
+675
-60
@@ -1,8 +1,11 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/hbc.h>
|
||||
#include <hbc/common.h>
|
||||
#include <hbc/disasm.h>
|
||||
#include <hbc/decompilation/decompiler.h>
|
||||
#include <hbc/decompilation/literals.h>
|
||||
#include <hbc/literals.h>
|
||||
#include <hbc/hermes_encoder.h>
|
||||
#include <hbc/parser.h>
|
||||
#include <hbc/bytecode.h>
|
||||
@@ -117,7 +120,16 @@ Result hbc_open_from_memory(const u8 *data, size_t size, HBC **out) {
|
||||
hbc_close (hbc);
|
||||
return res;
|
||||
}
|
||||
(void)_hbc_reader_read_debug_info;
|
||||
/* Read only the DebugInfoHeader eagerly — a handful of u32s — so
|
||||
* hbc_has_source_lines () can answer accurately without committing to the
|
||||
* full body parse. The filename, file region and sources/scope/textified
|
||||
* storage blobs are read lazily on first hbc_get_source_lines /
|
||||
* hbc_get_debug_info call. */
|
||||
res = _hbc_reader_read_debug_info_header (&hbc->reader);
|
||||
if (res.code != RESULT_SUCCESS) {
|
||||
hbc_close (hbc);
|
||||
return res;
|
||||
}
|
||||
|
||||
*out = hbc;
|
||||
return SUCCESS_RESULT ();
|
||||
@@ -127,6 +139,7 @@ void hbc_close(HBC *hbc) {
|
||||
if (!hbc) {
|
||||
return;
|
||||
}
|
||||
hbc_literals_reset (hbc);
|
||||
_hbc_reader_cleanup (&hbc->reader);
|
||||
free (hbc);
|
||||
}
|
||||
@@ -237,10 +250,232 @@ Result hbc_get_function_source(HBC *hbc, u32 function_id, const char **out_src)
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Function ID out of range");
|
||||
}
|
||||
*out_src = NULL;
|
||||
if (hbc->reader.function_sources && function_id < hbc->reader.header.functionSourceCount) {
|
||||
u32 string_id = hbc->reader.function_sources[function_id].string_id;
|
||||
if (string_id < hbc->reader.header.stringCount) {
|
||||
*out_src = hbc->reader.strings[string_id];
|
||||
if (hbc->reader.function_sources) {
|
||||
for (u32 i = 0; i < hbc->reader.header.functionSourceCount; i++) {
|
||||
if (hbc->reader.function_sources[i].function_id != function_id) {
|
||||
continue;
|
||||
}
|
||||
u32 string_id = hbc->reader.function_sources[i].string_id;
|
||||
if (string_id < hbc->reader.header.stringCount) {
|
||||
*out_src = hbc->reader.strings[string_id];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
static bool read_sleb128(const u8 *data, size_t size, size_t *pos, int64_t *out) {
|
||||
uint64_t result = 0;
|
||||
unsigned shift = 0;
|
||||
u8 byte = 0;
|
||||
|
||||
do {
|
||||
if (*pos >= size || shift > 63) {
|
||||
return false;
|
||||
}
|
||||
byte = data[(*pos)++];
|
||||
if (shift == 63 && (byte & 0x7e)) {
|
||||
return false;
|
||||
}
|
||||
result |= ((uint64_t) (byte & 0x7f)) << shift;
|
||||
shift += 7;
|
||||
} while (byte & 0x80);
|
||||
|
||||
if ((shift < 64) && (byte & 0x40)) {
|
||||
result |= (~0ULL) << shift;
|
||||
}
|
||||
*out = (int64_t)result;
|
||||
return true;
|
||||
}
|
||||
|
||||
static const char *debug_filename_at(HBCReader *reader, u32 index) {
|
||||
const u32 count = reader->debug_info_header.filename_count;
|
||||
if (index >= count || !reader->debug_string_table || !reader->debug_string_storage) {
|
||||
return NULL;
|
||||
}
|
||||
if (!reader->debug_filenames) {
|
||||
reader->debug_filenames = (char **)calloc (count, sizeof (char *));
|
||||
if (!reader->debug_filenames) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
if (!reader->debug_filenames[index]) {
|
||||
const OffsetLengthPair *entry = &reader->debug_string_table[index];
|
||||
const size_t storage_size = reader->debug_string_storage_size;
|
||||
if ((size_t)entry->offset > storage_size || (size_t)entry->length > storage_size - entry->offset) {
|
||||
return NULL;
|
||||
}
|
||||
char *filename = (char *)malloc ((size_t)entry->length + 1);
|
||||
if (!filename) {
|
||||
return NULL;
|
||||
}
|
||||
memcpy (filename, reader->debug_string_storage + entry->offset, entry->length);
|
||||
filename[entry->length] = '\0';
|
||||
reader->debug_filenames[index] = filename;
|
||||
}
|
||||
return reader->debug_filenames[index];
|
||||
}
|
||||
|
||||
static const char *debug_filename_for_source_offset(HBCReader *reader, u32 source_offset) {
|
||||
const DebugFileRegion *best = NULL;
|
||||
for (u32 i = 0; i < reader->debug_file_region_count; i++) {
|
||||
const DebugFileRegion *region = &reader->debug_file_regions[i];
|
||||
if (region->from_address <= source_offset && (!best || region->from_address >= best->from_address)) {
|
||||
best = region;
|
||||
}
|
||||
}
|
||||
return best? debug_filename_at (reader, best->filename_id): NULL;
|
||||
}
|
||||
|
||||
static Result append_source_line(HBCSourceLineArray *arr, u32 *cap, const HBCSourceLine *line) {
|
||||
if (arr->count >= *cap) {
|
||||
u32 next = *cap? (*cap * 2): 64;
|
||||
HBCSourceLine *lines = (HBCSourceLine *)realloc (arr->lines, (size_t)next * sizeof (HBCSourceLine));
|
||||
if (!lines) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "Failed to allocate source lines");
|
||||
}
|
||||
arr->lines = lines;
|
||||
*cap = next;
|
||||
}
|
||||
arr->lines[arr->count++] = *line;
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
#define PARSE_FAIL(msg) \
|
||||
do { \
|
||||
res = ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, msg); \
|
||||
goto fail; \
|
||||
} while (0)
|
||||
|
||||
Result hbc_get_source_lines(HBC *hbc, HBCSourceLineArray *out) {
|
||||
if (!hbc || !out) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Invalid arguments");
|
||||
}
|
||||
memset (out, 0, sizeof (*out));
|
||||
HBCReader *reader = &hbc->reader;
|
||||
RETURN_IF_ERROR (_hbc_reader_read_debug_info (reader));
|
||||
const u8 *data = reader->sources_data_storage;
|
||||
const size_t size = reader->sources_data_storage_size;
|
||||
if (!data || !size) {
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
Result res = SUCCESS_RESULT ();
|
||||
u32 cap = 0;
|
||||
size_t pos = 0;
|
||||
while (pos < size) {
|
||||
const size_t source_offset = pos;
|
||||
int64_t function_id = 0, line = 0, column = 0;
|
||||
if (source_offset > UINT32_MAX || !read_sleb128 (data, size, &pos, &function_id) || !read_sleb128 (data, size, &pos, &line) || !read_sleb128 (data, size, &pos, &column)) {
|
||||
PARSE_FAIL ("Invalid source location header");
|
||||
}
|
||||
if (function_id < 0 || function_id >= (int64_t)reader->header.functionCount || line < 0 || line > UINT32_MAX || column < 0 || column > UINT32_MAX) {
|
||||
PARSE_FAIL ("Source location header out of range");
|
||||
}
|
||||
|
||||
HBCFunc fi = { 0 };
|
||||
(void)hbc_get_function_info (hbc, (u32)function_id, &fi);
|
||||
HBCSourceLine sl = {
|
||||
.function_id = (u32)function_id,
|
||||
.address = fi.offset,
|
||||
.line = (u32)line,
|
||||
.column = (u32)column,
|
||||
.filename = debug_filename_for_source_offset (reader, (u32)source_offset)
|
||||
};
|
||||
res = append_source_line (out, &cap, &sl);
|
||||
if (res.code != RESULT_SUCCESS) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
int64_t address = 0, statement = 0;
|
||||
for (;;) {
|
||||
int64_t adelta = 0;
|
||||
if (!read_sleb128 (data, size, &pos, &adelta)) {
|
||||
PARSE_FAIL ("Invalid source location address delta");
|
||||
}
|
||||
if (adelta == -1) {
|
||||
break;
|
||||
}
|
||||
int64_t ldelta = 0, cdelta = 0, scope = 0, env = 0, sdelta = 0;
|
||||
if (adelta < 0 || !read_sleb128 (data, size, &pos, &ldelta) || !read_sleb128 (data, size, &pos, &cdelta) || !read_sleb128 (data, size, &pos, &scope) || !read_sleb128 (data, size, &pos, &env)) {
|
||||
PARSE_FAIL ("Invalid source location entry");
|
||||
}
|
||||
if ((ldelta & 1) && !read_sleb128 (data, size, &pos, &sdelta)) {
|
||||
PARSE_FAIL ("Invalid source location statement delta");
|
||||
}
|
||||
ldelta = (ldelta - (ldelta & 1)) / 2;
|
||||
address += adelta;
|
||||
line += ldelta;
|
||||
column += cdelta;
|
||||
statement += sdelta;
|
||||
if (address < 0 || address > UINT32_MAX || (uint64_t)fi.offset + (uint64_t)address > UINT32_MAX || line < 0 || line > UINT32_MAX || column < 0 || column > UINT32_MAX || statement < 0 || statement > UINT32_MAX) {
|
||||
PARSE_FAIL ("Source location entry out of range");
|
||||
}
|
||||
sl.address = fi.offset + (u32)address;
|
||||
sl.function_address = (u32)address;
|
||||
sl.line = (u32)line;
|
||||
sl.column = (u32)column;
|
||||
sl.statement = (u32)statement;
|
||||
res = append_source_line (out, &cap, &sl);
|
||||
if (res.code != RESULT_SUCCESS) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
}
|
||||
return SUCCESS_RESULT ();
|
||||
fail:
|
||||
hbc_free_source_lines (out);
|
||||
return res;
|
||||
}
|
||||
|
||||
#undef PARSE_FAIL
|
||||
|
||||
void hbc_free_source_lines(HBCSourceLineArray *arr) {
|
||||
if (!arr) {
|
||||
return;
|
||||
}
|
||||
free (arr->lines);
|
||||
memset (arr, 0, sizeof (*arr));
|
||||
}
|
||||
|
||||
/* Describes the file, not the runtime preference: true iff the file carries
|
||||
* a non-empty source-locations blob. Relies on the DebugInfoHeader parsed at
|
||||
* load time, so callers pay no body-parse cost for this query. */
|
||||
bool hbc_has_source_lines(HBC *hbc) {
|
||||
if (!hbc || hbc->reader.header.debugInfoOffset == 0) {
|
||||
return false;
|
||||
}
|
||||
if (_hbc_reader_read_debug_info_header (&hbc->reader).code != RESULT_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
return hbc->reader.debug_info_header.scope_desc_data_offset > 0;
|
||||
}
|
||||
|
||||
Result hbc_get_debug_info(HBC *hbc, HBCDebugInfo *out) {
|
||||
if (!hbc || !out) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Invalid arguments");
|
||||
}
|
||||
memset (out, 0, sizeof (*out));
|
||||
HBCReader *reader = &hbc->reader;
|
||||
out->has_debug_info = reader->header.debugInfoOffset != 0;
|
||||
if (!out->has_debug_info) {
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
RETURN_IF_ERROR (_hbc_reader_read_debug_info (reader));
|
||||
out->filename_count = reader->debug_info_header.filename_count;
|
||||
out->filename_storage_size = reader->debug_info_header.filename_storage_size;
|
||||
out->file_region_count = reader->debug_info_header.file_region_count;
|
||||
out->source_locations_size = (u32)reader->sources_data_storage_size;
|
||||
out->scope_desc_data_size = (u32)reader->scope_desc_data_storage_size;
|
||||
out->textified_data_size = (u32)reader->textified_data_storage_size;
|
||||
out->string_table_size = (u32)reader->string_table_storage_size;
|
||||
out->debug_data_size = reader->debug_info_header.debug_data_size;
|
||||
if (reader->function_headers) {
|
||||
for (u32 i = 0; i < reader->header.functionCount; i++) {
|
||||
if (reader->function_headers[i].hasDebugInfo) {
|
||||
out->functions_with_debug_info++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return SUCCESS_RESULT ();
|
||||
@@ -387,6 +622,10 @@ static char *build_literal_comment(HBCReader *reader, const char *mnemonic, cons
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Read each operand by type into values[6], little-endian with sign-extension
|
||||
* for relative addresses; returns total instruction size, or 0 if truncated. */
|
||||
static size_t decode_operands(const Instruction *inst, const u8 *bytes, size_t len, u32 values[6]);
|
||||
|
||||
Result hbc_dec(const HBCDecodeCtx *ctx, HBCInsnInfo *out) {
|
||||
if (!ctx || !out) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Invalid context");
|
||||
@@ -412,7 +651,7 @@ Result hbc_dec(const HBCDecodeCtx *ctx, HBCInsnInfo *out) {
|
||||
u8 opcode = ctx->bytes[0];
|
||||
|
||||
/* Get instruction definition */
|
||||
const Instruction *inst = (opcode < isa.count) ? &isa.instructions[opcode] : NULL;
|
||||
const Instruction *inst = (opcode < isa.count)? &isa.instructions[opcode]: NULL;
|
||||
if (!inst || !inst->name) {
|
||||
out->text = strdup ("unk");
|
||||
out->size = 1;
|
||||
@@ -426,59 +665,8 @@ Result hbc_dec(const HBCDecodeCtx *ctx, HBCInsnInfo *out) {
|
||||
}
|
||||
|
||||
/* Parse operands */
|
||||
u32 operand_values[6] = { 0 };
|
||||
size_t pos = 1; /* Start after opcode */
|
||||
|
||||
for (int i = 0; i < 6 && inst->operands[i].operand_type != OPERAND_TYPE_NONE; i++) {
|
||||
OperandType operand_type = inst->operands[i].operand_type;
|
||||
|
||||
/* Check if we have enough bytes */
|
||||
size_t need = 0;
|
||||
switch (operand_type) {
|
||||
case OPERAND_TYPE_REG8:
|
||||
case OPERAND_TYPE_UINT8:
|
||||
case OPERAND_TYPE_ADDR8: need = 1; break;
|
||||
case OPERAND_TYPE_UINT16: need = 2; break;
|
||||
case OPERAND_TYPE_REG32:
|
||||
case OPERAND_TYPE_UINT32:
|
||||
case OPERAND_TYPE_IMM32:
|
||||
case OPERAND_TYPE_ADDR32: need = 4; break;
|
||||
case OPERAND_TYPE_DOUBLE: need = 8; break;
|
||||
default: need = 0; break;
|
||||
}
|
||||
|
||||
if (pos + need > ctx->len) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Read operand value */
|
||||
switch (operand_type) {
|
||||
case OPERAND_TYPE_REG8:
|
||||
case OPERAND_TYPE_UINT8:
|
||||
case OPERAND_TYPE_ADDR8:
|
||||
operand_values[i] = ctx->bytes[pos];
|
||||
pos += 1;
|
||||
break;
|
||||
case OPERAND_TYPE_UINT16:
|
||||
operand_values[i] = ctx->bytes[pos] | (ctx->bytes[pos + 1] << 8);
|
||||
pos += 2;
|
||||
break;
|
||||
case OPERAND_TYPE_REG32:
|
||||
case OPERAND_TYPE_UINT32:
|
||||
case OPERAND_TYPE_IMM32:
|
||||
case OPERAND_TYPE_ADDR32:
|
||||
operand_values[i] = ctx->bytes[pos] | (ctx->bytes[pos + 1] << 8) |
|
||||
(ctx->bytes[pos + 2] << 16) | (ctx->bytes[pos + 3] << 24);
|
||||
pos += 4;
|
||||
break;
|
||||
case OPERAND_TYPE_DOUBLE:
|
||||
/* Skip for now */
|
||||
pos += 8;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
u32 operand_values[6];
|
||||
size_t pos = decode_operands (inst, ctx->bytes, ctx->len, operand_values);
|
||||
|
||||
/* Format output string */
|
||||
char buf[512];
|
||||
@@ -561,7 +749,7 @@ Result hbc_dec(const HBCDecodeCtx *ctx, HBCInsnInfo *out) {
|
||||
!strncmp (mnemonic, "new_array_with_buffer", strlen ("new_array_with_buffer"));
|
||||
if (is_literal_op) {
|
||||
char *with_comment = build_literal_comment (&ctx->hbc->reader, mnemonic, operand_values, buf);
|
||||
out->text = with_comment ? with_comment : strdup (buf);
|
||||
out->text = with_comment? with_comment: strdup (buf);
|
||||
} else {
|
||||
out->text = strdup (buf);
|
||||
}
|
||||
@@ -589,6 +777,433 @@ Result hbc_dec(const HBCDecodeCtx *ctx, HBCInsnInfo *out) {
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
const char *name; /* exact match, else match prefix */
|
||||
const char *prefix;
|
||||
const char *kind;
|
||||
const char *module; /* NULL for exports */
|
||||
HBCBindingType type;
|
||||
} BindingHint;
|
||||
|
||||
static const BindingHint g_hints[] = {
|
||||
{ "NativeModules", NULL, "native", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "TurboModuleRegistry", NULL, "native", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "__turboModuleProxy", NULL, "native", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "requireNativeComponent", NULL, "native", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "codegenNativeComponent", NULL, "native", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "NativeEventEmitter", NULL, "native", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "RCTDeviceEventEmitter", NULL, "native", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "HermesInternal", NULL, "native", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "require", NULL, "js", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "metroRequire", NULL, "js", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "__r", NULL, "js", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "importScripts", NULL, "js", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "global", NULL, "global", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "globalThis", NULL, "global", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "exports", NULL, "module", NULL, HBC_BINDING_EXPORT },
|
||||
{ "module", NULL, "module", NULL, HBC_BINDING_EXPORT },
|
||||
{ "__esModule", NULL, "module", NULL, HBC_BINDING_EXPORT },
|
||||
{ "default", NULL, "module", NULL, HBC_BINDING_EXPORT },
|
||||
{ NULL, "RCT", "native", "react-native", HBC_BINDING_IMPORT },
|
||||
{ NULL, "Native", "native", "react-native", HBC_BINDING_IMPORT },
|
||||
};
|
||||
#define HINTS_N (sizeof (g_hints) / sizeof (g_hints[0]))
|
||||
|
||||
/* Per-sid dedup flags for hbc_scan_bindings; avoids O (N^2) strcmp dedup. */
|
||||
#define SEEN_CJS 0x1
|
||||
#define SEEN_IMP 0x2
|
||||
#define SEEN_EXP 0x4
|
||||
|
||||
static bool is_export_marker(const char *s) {
|
||||
return !strcmp (s, "exports") || !strcmp (s, "module");
|
||||
}
|
||||
|
||||
static bool is_export_store(const char *name) {
|
||||
return strstr (name, "put") && strstr (name, "by_id");
|
||||
}
|
||||
|
||||
static Result binding_add(HBCBindings *out, HBCBindingType type, const char *kind, const char *name, const char *module, u32 function_id, u32 offset, u32 string_id) {
|
||||
HBCBinding *nb = realloc (out->bindings, (out->count + 1) * sizeof (HBCBinding));
|
||||
if (!nb) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "OOM");
|
||||
}
|
||||
out->bindings = nb;
|
||||
HBCBinding *b = &nb[out->count++];
|
||||
*b = (HBCBinding){
|
||||
.type = type,
|
||||
.kind = kind,
|
||||
.name = strdup (name),
|
||||
.module = module? strdup (module): NULL,
|
||||
.function_id = function_id,
|
||||
.offset = offset,
|
||||
.string_id = string_id,
|
||||
};
|
||||
if (!b->name || (module && !b->module)) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "OOM");
|
||||
}
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
static size_t decode_operands(const Instruction *inst, const u8 *bytes, size_t len, u32 values[6]) {
|
||||
size_t pos = 1;
|
||||
memset (values, 0, 6 * sizeof (u32));
|
||||
for (int i = 0; i < 6 && inst->operands[i].operand_type != OPERAND_TYPE_NONE; i++) {
|
||||
OperandType t = inst->operands[i].operand_type;
|
||||
size_t need;
|
||||
switch (t) {
|
||||
case OPERAND_TYPE_REG8:
|
||||
case OPERAND_TYPE_UINT8:
|
||||
need = 1;
|
||||
if (pos + need > len) {
|
||||
return 0;
|
||||
}
|
||||
values[i] = bytes[pos];
|
||||
break;
|
||||
case OPERAND_TYPE_ADDR8:
|
||||
need = 1;
|
||||
if (pos + need > len) {
|
||||
return 0;
|
||||
}
|
||||
values[i] = (u32) (i32) (i8)bytes[pos];
|
||||
break;
|
||||
case OPERAND_TYPE_UINT16:
|
||||
need = 2;
|
||||
if (pos + need > len) {
|
||||
return 0;
|
||||
}
|
||||
values[i] = (u32)bytes[pos] | ((u32)bytes[pos + 1] << 8);
|
||||
break;
|
||||
case OPERAND_TYPE_REG32:
|
||||
case OPERAND_TYPE_UINT32:
|
||||
case OPERAND_TYPE_IMM32:
|
||||
case OPERAND_TYPE_ADDR32:
|
||||
need = 4;
|
||||
if (pos + need > len) {
|
||||
return 0;
|
||||
}
|
||||
values[i] = (u32)bytes[pos] | ((u32)bytes[pos + 1] << 8) | ((u32)bytes[pos + 2] << 16) | ((u32)bytes[pos + 3] << 24);
|
||||
break;
|
||||
case OPERAND_TYPE_DOUBLE:
|
||||
need = 8;
|
||||
if (pos + need > len) {
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
pos += need;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
static Result scan_hint_matches(HBCBindings *out, const char *s, u32 fid, u32 off, u32 sid, u8 *flag) {
|
||||
/* Dedup identical (kind,module) across hints (pointers are stable literals).
|
||||
* Set SEEN_EXP when adding an EXPORT so caller's put_by_id skips this sid. */
|
||||
const void *added[HINTS_N * 2];
|
||||
u32 added_n = 0;
|
||||
for (size_t h = 0; h < HINTS_N; h++) {
|
||||
const BindingHint *hint = &g_hints[h];
|
||||
bool match = hint->name? !strcmp (s, hint->name): !strncmp (s, hint->prefix, strlen (hint->prefix));
|
||||
if (!match) {
|
||||
continue;
|
||||
}
|
||||
bool dup = false;
|
||||
for (u32 a = 0; a < added_n; a += 2) {
|
||||
if (added[a] == hint->kind && added[a + 1] == hint->module) {
|
||||
dup = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (dup) {
|
||||
continue;
|
||||
}
|
||||
added[added_n++] = hint->kind;
|
||||
added[added_n++] = hint->module;
|
||||
if (hint->type == HBC_BINDING_EXPORT) {
|
||||
*flag |= SEEN_EXP;
|
||||
}
|
||||
RETURN_IF_ERROR (binding_add (out, hint->type, hint->kind, s, hint->module, fid, off, sid));
|
||||
}
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
Result hbc_scan_bindings(HBC *hbc, HBCBindings *out) {
|
||||
if (!hbc || !out) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "invalid args");
|
||||
}
|
||||
memset (out, 0, sizeof (*out));
|
||||
HBCReader *r = &hbc->reader;
|
||||
HBCISA isa = hbc_isa_getv (r->header.version);
|
||||
if (!isa.instructions) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "no isa for version");
|
||||
}
|
||||
u32 sc = r->header.stringCount;
|
||||
u8 *seen = sc? calloc (sc, 1): NULL;
|
||||
if (sc && !seen) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "OOM");
|
||||
}
|
||||
Result res = SUCCESS_RESULT ();
|
||||
if (r->header.cjsModuleCount > 0 && r->header.version >= 77 && r->cjs_modules) {
|
||||
for (u32 i = 0; i < r->header.cjsModuleCount; i++) {
|
||||
u32 sid = r->cjs_modules[i].symbol_id;
|
||||
const char *name = (sid < sc && r->strings)? r->strings[sid]: NULL;
|
||||
if (!name || !*name || (seen[sid] & SEEN_CJS)) {
|
||||
continue;
|
||||
}
|
||||
seen[sid] |= SEEN_CJS;
|
||||
res = binding_add (out, HBC_BINDING_IMPORT, "cjs", name, "commonjs", 0, r->cjs_modules[i].offset, sid);
|
||||
if (res.code != RESULT_SUCCESS) {
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
}
|
||||
struct {
|
||||
u32 sid, off;
|
||||
} *cand = NULL;
|
||||
u32 cand_cap = 0;
|
||||
for (u32 fid = 0; fid < r->header.functionCount; fid++) {
|
||||
const u8 *code = NULL;
|
||||
u32 size = 0;
|
||||
if (hbc_get_function_bytecode (hbc, fid, &code, &size).code != RESULT_SUCCESS || !code || !size) {
|
||||
continue;
|
||||
}
|
||||
HBCFunc fi = { 0 };
|
||||
(void)hbc_get_function_info (hbc, fid, &fi);
|
||||
u32 cand_n = 0;
|
||||
bool has_marker = false;
|
||||
for (u32 pc = 0; pc < size;) {
|
||||
u8 op = code[pc];
|
||||
const Instruction *inst = (op < isa.count)? &isa.instructions[op]: NULL;
|
||||
if (!inst || !inst->name || !inst->binary_size) {
|
||||
break;
|
||||
}
|
||||
u32 values[6];
|
||||
if (!decode_operands (inst, code + pc, size - pc, values)) {
|
||||
break;
|
||||
}
|
||||
bool is_store = is_export_store (inst->name);
|
||||
for (int i = 0; i < 6 && inst->operands[i].operand_type != OPERAND_TYPE_NONE; i++) {
|
||||
if (inst->operands[i].operand_meaning != OPERAND_MEANING_STRING_ID) {
|
||||
continue;
|
||||
}
|
||||
u32 sid = values[i];
|
||||
if (sid >= sc || !r->strings) {
|
||||
continue;
|
||||
}
|
||||
const char *s = r->strings[sid];
|
||||
if (!s || !*s) {
|
||||
continue;
|
||||
}
|
||||
bool marker = is_export_marker (s);
|
||||
if (marker) {
|
||||
has_marker = true;
|
||||
}
|
||||
if (! (seen[sid] & SEEN_IMP)) {
|
||||
seen[sid] |= SEEN_IMP;
|
||||
res = scan_hint_matches (out, s, fid, fi.offset + pc, sid, &seen[sid]);
|
||||
if (res.code != RESULT_SUCCESS) {
|
||||
goto done_scan;
|
||||
}
|
||||
}
|
||||
if (is_store && !marker && ! (seen[sid] & SEEN_EXP)) {
|
||||
if (cand_n == cand_cap) {
|
||||
u32 nc = cand_cap? cand_cap * 2: 16;
|
||||
void *nb = realloc (cand, nc * sizeof (*cand));
|
||||
if (!nb) {
|
||||
res = ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "OOM");
|
||||
goto done_scan;
|
||||
}
|
||||
cand = nb;
|
||||
cand_cap = nc;
|
||||
}
|
||||
cand[cand_n].sid = sid;
|
||||
cand[cand_n].off = fi.offset + pc;
|
||||
cand_n++;
|
||||
}
|
||||
}
|
||||
pc += inst->binary_size;
|
||||
}
|
||||
if (!has_marker) {
|
||||
continue;
|
||||
}
|
||||
for (u32 k = 0; k < cand_n; k++) {
|
||||
u32 sid = cand[k].sid;
|
||||
if (seen[sid] & SEEN_EXP) {
|
||||
continue;
|
||||
}
|
||||
seen[sid] |= SEEN_EXP;
|
||||
res = binding_add (out, HBC_BINDING_EXPORT, "module", r->strings[sid], NULL, fid, cand[k].off, sid);
|
||||
if (res.code != RESULT_SUCCESS) {
|
||||
goto done_scan;
|
||||
}
|
||||
}
|
||||
}
|
||||
done_scan:
|
||||
free (cand);
|
||||
done:
|
||||
free (seen);
|
||||
if (res.code != RESULT_SUCCESS) {
|
||||
hbc_free_bindings (out);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void hbc_free_bindings(HBCBindings *bindings) {
|
||||
if (!bindings || !bindings->bindings) {
|
||||
return;
|
||||
}
|
||||
for (u32 i = 0; i < bindings->count; i++) {
|
||||
free (bindings->bindings[i].name);
|
||||
free (bindings->bindings[i].module);
|
||||
}
|
||||
free (bindings->bindings);
|
||||
bindings->bindings = NULL;
|
||||
bindings->count = 0;
|
||||
}
|
||||
|
||||
static char *dup_range(const char *s, size_t n) {
|
||||
char *ret = (char *)malloc (n + 1);
|
||||
if (!ret) {
|
||||
return NULL;
|
||||
}
|
||||
memcpy (ret, s, n);
|
||||
ret[n] = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static char *package_from_path(const char *s) {
|
||||
const char *nm = strstr (s, "node_modules/");
|
||||
if (nm) {
|
||||
const char *p = nm + strlen ("node_modules/");
|
||||
const char *end;
|
||||
if (*p == '@') {
|
||||
const char *scope_end = strchr (p, '/');
|
||||
if (!scope_end) {
|
||||
return strdup (p);
|
||||
}
|
||||
end = strchr (scope_end + 1, '/');
|
||||
} else {
|
||||
end = strchr (p, '/');
|
||||
}
|
||||
return end? dup_range (p, (size_t) (end - p)): strdup (p);
|
||||
}
|
||||
if (s[0] == '@') {
|
||||
const char *scope_end = strchr (s, '/');
|
||||
if (scope_end) {
|
||||
const char *end = strchr (scope_end + 1, '/');
|
||||
return end? dup_range (s, (size_t) (end - s)): strdup (s);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool module_eq(const HBCModule *m, const char *kind, const char *name) {
|
||||
return m->kind && kind && !strcmp (m->kind, kind) && m->name && name && !strcmp (m->name, name);
|
||||
}
|
||||
|
||||
static Result module_add(HBCModules *out, const char *kind, const char *name, const char *path, const char *version, u32 function_id, u32 offset, u32 string_id, bool inferred) {
|
||||
if (!name || !*name) {
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
for (u32 i = 0; i < out->count; i++) {
|
||||
if (module_eq (&out->modules[i], kind, name)) {
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
}
|
||||
HBCModule *nm = realloc (out->modules, (out->count + 1) * sizeof (HBCModule));
|
||||
if (!nm) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "OOM");
|
||||
}
|
||||
out->modules = nm;
|
||||
HBCModule *m = &nm[out->count++];
|
||||
*m = (HBCModule){
|
||||
.name = strdup (name),
|
||||
.path = path? strdup (path): NULL,
|
||||
.version = version? strdup (version): NULL,
|
||||
.kind = kind,
|
||||
.function_id = function_id,
|
||||
.offset = offset,
|
||||
.string_id = string_id,
|
||||
.inferred = inferred,
|
||||
};
|
||||
if (!m->name || (path && !m->path) || (version && !m->version)) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "OOM");
|
||||
}
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
Result hbc_list_modules(HBC *hbc, HBCModules *out) {
|
||||
if (!hbc || !out) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "invalid args");
|
||||
}
|
||||
memset (out, 0, sizeof (*out));
|
||||
HBCReader *r = &hbc->reader;
|
||||
if (r->header.cjsModuleCount > 0 && r->header.version >= 77 && r->cjs_modules) {
|
||||
for (u32 i = 0; i < r->header.cjsModuleCount; i++) {
|
||||
u32 sid = r->cjs_modules[i].symbol_id;
|
||||
const char *s = (sid < r->header.stringCount && r->strings)? r->strings[sid]: NULL;
|
||||
if (!s || !*s) {
|
||||
continue;
|
||||
}
|
||||
char *pkg = package_from_path (s);
|
||||
if (pkg) {
|
||||
Result res = module_add (out, "package", pkg, s, NULL, 0, r->cjs_modules[i].offset, sid, true);
|
||||
free (pkg);
|
||||
if (res.code != RESULT_SUCCESS) {
|
||||
hbc_free_modules (out);
|
||||
return res;
|
||||
}
|
||||
} else {
|
||||
Result res = module_add (out, "cjs", s, s, NULL, 0, r->cjs_modules[i].offset, sid, false);
|
||||
if (res.code != RESULT_SUCCESS) {
|
||||
hbc_free_modules (out);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HBCBindings bindings = { 0 };
|
||||
Result res = hbc_scan_bindings (hbc, &bindings);
|
||||
if (res.code != RESULT_SUCCESS) {
|
||||
hbc_free_modules (out);
|
||||
return res;
|
||||
}
|
||||
for (u32 i = 0; i < bindings.count; i++) {
|
||||
HBCBinding *b = &bindings.bindings[i];
|
||||
if (b->type != HBC_BINDING_IMPORT) {
|
||||
continue;
|
||||
}
|
||||
const char *name = b->module? b->module: b->name;
|
||||
if (!name || !*name || !strcmp (name, "commonjs")) {
|
||||
name = b->name;
|
||||
}
|
||||
res = module_add (out, b->kind? b->kind: "js", name, b->name, NULL, b->function_id, b->offset, b->string_id, true);
|
||||
if (res.code != RESULT_SUCCESS) {
|
||||
hbc_free_bindings (&bindings);
|
||||
hbc_free_modules (out);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
hbc_free_bindings (&bindings);
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
void hbc_free_modules(HBCModules *modules) {
|
||||
if (!modules || !modules->modules) {
|
||||
return;
|
||||
}
|
||||
for (u32 i = 0; i < modules->count; i++) {
|
||||
free (modules->modules[i].name);
|
||||
free (modules->modules[i].path);
|
||||
free (modules->modules[i].version);
|
||||
}
|
||||
free (modules->modules);
|
||||
modules->modules = NULL;
|
||||
modules->count = 0;
|
||||
}
|
||||
|
||||
/* Encoding functions */
|
||||
|
||||
Result hbc_enc(
|
||||
|
||||
@@ -1,8 +1,38 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#ifndef HBC_INTERNAL_H
|
||||
#define HBC_INTERNAL_H
|
||||
|
||||
#include <hbc/hbc.h>
|
||||
#include <hbc/parser.h>
|
||||
#include <hbc/literals.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Grow a dynamic array (doubling) so it has room for one more element.
|
||||
* arrp points at the array pointer (e.g. &vec->data); *cap is the capacity in
|
||||
* elements; initial is the capacity to allocate when growing from empty. The
|
||||
* stored pointer is read/written via memcpy so no aliasing cast is needed. */
|
||||
static inline Result grow_array(void *arrp, u32 *cap, u32 count, size_t elem_size, u32 initial) {
|
||||
if (count < *cap) {
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
u32 new_cap = *cap? *cap * 2: initial;
|
||||
void *cur;
|
||||
memcpy (&cur, arrp, sizeof (cur));
|
||||
void *grown = realloc (cur, (size_t)new_cap * elem_size);
|
||||
if (!grown) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "out of memory");
|
||||
}
|
||||
memcpy (arrp, &grown, sizeof (grown));
|
||||
*cap = new_cap;
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
HBCLiteralEntry *entries;
|
||||
u32 count;
|
||||
u32 cap;
|
||||
} HBCLiteralCache;
|
||||
|
||||
struct HBC {
|
||||
HBCReader reader;
|
||||
@@ -11,6 +41,7 @@ struct HBC {
|
||||
u32 string_count;
|
||||
const char **strings;
|
||||
u32 function_count;
|
||||
HBCLiteralCache lit_cache;
|
||||
};
|
||||
|
||||
#endif /* HBC_INTERNAL_H */
|
||||
@@ -0,0 +1,489 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
/* Public literals API: lazy cache, formatting, scanning. See
|
||||
* include/hbc/literals.h for the contract. */
|
||||
|
||||
#include <hbc/literals.h>
|
||||
#include <hbc/opcodes.h>
|
||||
#include <hbc/bytecode.h>
|
||||
#include <hbc/decompilation/literals.h>
|
||||
#include "hbc_internal.h"
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Little-endian readers for raw bytecode operands. */
|
||||
static inline u32 rd_u16le(const u8 *p) {
|
||||
return (u32)p[0] | ((u32)p[1] << 8);
|
||||
}
|
||||
static inline u32 rd_u32le(const u8 *p) {
|
||||
return (u32)p[0] | ((u32)p[1] << 8) | ((u32)p[2] << 16) | ((u32)p[3] << 24);
|
||||
}
|
||||
|
||||
/* ---------- Global toggles ---------- */
|
||||
|
||||
static bool g_inline_literals = false;
|
||||
|
||||
void hbc_set_inline_literals(bool on) {
|
||||
g_inline_literals = on;
|
||||
}
|
||||
|
||||
bool hbc_get_inline_literals(void) {
|
||||
return g_inline_literals;
|
||||
}
|
||||
|
||||
/* ---------- Pool accessors ---------- */
|
||||
|
||||
u32 hbc_get_pool_paddr(HBC *hbc, HBCLiteralKind kind) {
|
||||
if (!hbc) {
|
||||
return 0;
|
||||
}
|
||||
const HBCReader *r = &hbc->reader;
|
||||
switch (kind) {
|
||||
case HBC_LIT_ARRAY:
|
||||
return r->arrays? r->arrays_paddr: 0;
|
||||
case HBC_LIT_OBJECT:
|
||||
return r->object_keys? r->object_keys_paddr: 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 hbc_get_pool_size(HBC *hbc, HBCLiteralKind kind) {
|
||||
if (!hbc) {
|
||||
return 0;
|
||||
}
|
||||
const HBCReader *r = &hbc->reader;
|
||||
switch (kind) {
|
||||
case HBC_LIT_ARRAY: return r->header.arrayBufferSize;
|
||||
case HBC_LIT_OBJECT: return r->header.objKeyBufferSize;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 hbc_get_object_values_paddr(HBC *hbc) {
|
||||
return (hbc && hbc->reader.object_values)? hbc->reader.object_values_paddr: 0;
|
||||
}
|
||||
|
||||
u32 hbc_get_object_values_size(HBC *hbc) {
|
||||
return hbc? hbc->reader.header.objValueBufferSize: 0;
|
||||
}
|
||||
|
||||
/* ---------- Formatting ---------- */
|
||||
|
||||
static Result format_raw_impl(HBCReader *r, HBCLiteralKind kind, u32 num_items, u32 primary_id, u32 secondary_id, char **out) {
|
||||
*out = NULL;
|
||||
StringBuffer sb;
|
||||
Result ir = _hbc_string_buffer_init (&sb, 128);
|
||||
if (ir.code != RESULT_SUCCESS) {
|
||||
return ir;
|
||||
}
|
||||
Result fr;
|
||||
if (kind == HBC_LIT_ARRAY) {
|
||||
fr = _hbc_format_array_literal (r, num_items, primary_id, &sb, LITERALS_PRETTY_NEVER, true);
|
||||
} else {
|
||||
/* For v97+ primary_id is the shape index; the internal formatter
|
||||
* expects that convention already (first arg is "key_count" which is
|
||||
* reinterpreted as shape id when version >= 97). */
|
||||
fr = _hbc_format_object_literal (r, num_items, num_items, primary_id, secondary_id, &sb, LITERALS_PRETTY_NEVER, true);
|
||||
}
|
||||
if (fr.code == RESULT_SUCCESS && sb.data && sb.length > 0) {
|
||||
*out = strdup (sb.data);
|
||||
if (!*out) {
|
||||
fr = ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "oom");
|
||||
}
|
||||
}
|
||||
_hbc_string_buffer_free (&sb);
|
||||
return fr;
|
||||
}
|
||||
|
||||
Result hbc_literals_format_raw(HBC *hbc, HBCLiteralKind kind, u32 num_items, u32 primary_id, u32 secondary_id, char **out) {
|
||||
if (!hbc || !out) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "invalid args");
|
||||
}
|
||||
return format_raw_impl (&hbc->reader, kind, num_items, primary_id, secondary_id, out);
|
||||
}
|
||||
|
||||
Result hbc_literals_format_for_opcode(HBC *hbc, u8 opcode, u32 arg3, u32 arg4, u32 arg5, char **out) {
|
||||
if (!hbc || !out) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "invalid args");
|
||||
}
|
||||
switch (opcode) {
|
||||
case OP_NewArrayWithBuffer:
|
||||
case OP_NewArrayWithBufferLong:
|
||||
return hbc_literals_format_raw (hbc, HBC_LIT_ARRAY, arg3, arg4, 0, out);
|
||||
case OP_NewObjectWithBuffer:
|
||||
case OP_NewObjectWithBufferLong:
|
||||
return hbc_literals_format_raw (hbc, HBC_LIT_OBJECT, arg3, arg4, arg5, out);
|
||||
}
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT,
|
||||
"opcode is not a buffer-literal constructor");
|
||||
}
|
||||
|
||||
/* ---------- Cache ---------- */
|
||||
|
||||
static HBCLiteralEntry *cache_find(HBCLiteralCache *c, HBCLiteralKind kind, u32 num_items, u32 primary_id, u32 secondary_id) {
|
||||
for (u32 i = 0; i < c->count; i++) {
|
||||
HBCLiteralEntry *e = &c->entries[i];
|
||||
if (e->kind == kind && e->num_items == num_items && e->primary_id == primary_id && e->secondary_id == secondary_id) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static HBCLiteralEntry *cache_alloc(HBCLiteralCache *c) {
|
||||
if (grow_array (&c->entries, &c->cap, c->count, sizeof (HBCLiteralEntry), 64).code != RESULT_SUCCESS) {
|
||||
return NULL;
|
||||
}
|
||||
HBCLiteralEntry *e = &c->entries[c->count++];
|
||||
memset (e, 0, sizeof (*e));
|
||||
return e;
|
||||
}
|
||||
|
||||
/* Compute paddr for an entry based on its kind/primary_id. For v97+ objects,
|
||||
* primary_id is a shape index — resolve via the shape table. */
|
||||
static u32 entry_paddr(HBC *hbc, HBCLiteralKind kind, u32 primary_id) {
|
||||
const HBCReader *r = &hbc->reader;
|
||||
if (kind == HBC_LIT_ARRAY) {
|
||||
if (!r->arrays) {
|
||||
return 0;
|
||||
}
|
||||
if (primary_id >= r->header.arrayBufferSize) {
|
||||
return 0;
|
||||
}
|
||||
return r->arrays_paddr + primary_id;
|
||||
}
|
||||
/* object */
|
||||
if (r->header.version >= 97 && r->object_shapes && primary_id < r->object_shape_count) {
|
||||
u32 key_off = r->object_shapes[primary_id].key_buffer_offset;
|
||||
if (r->object_keys && key_off < r->header.objKeyBufferSize) {
|
||||
return r->object_keys_paddr + key_off;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (!r->object_keys || primary_id >= r->header.objKeyBufferSize) {
|
||||
return 0;
|
||||
}
|
||||
return r->object_keys_paddr + primary_id;
|
||||
}
|
||||
|
||||
static Result cache_get_or_create(HBC *hbc, HBCLiteralKind kind, u32 num_items, u32 primary_id, u32 secondary_id, HBCLiteralEntry **out_entry) {
|
||||
HBCLiteralEntry *e = cache_find (&hbc->lit_cache, kind, num_items, primary_id, secondary_id);
|
||||
if (e) {
|
||||
*out_entry = e;
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
e = cache_alloc (&hbc->lit_cache);
|
||||
if (!e) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "oom");
|
||||
}
|
||||
e->kind = kind;
|
||||
e->num_items = num_items;
|
||||
e->primary_id = primary_id;
|
||||
e->secondary_id = secondary_id;
|
||||
e->paddr = entry_paddr (hbc, kind, primary_id);
|
||||
/* Format now (lazy across calls, but eager per key once we've decided to
|
||||
* cache it). The formatted string is small and reused on every access. */
|
||||
char *txt = NULL;
|
||||
Result fr = format_raw_impl (&hbc->reader, kind, num_items, primary_id, secondary_id, &txt);
|
||||
if (fr.code == RESULT_SUCCESS) {
|
||||
e->formatted = txt;
|
||||
}
|
||||
*out_entry = e;
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
Result hbc_literals_get(HBC *hbc, HBCLiteralKind kind, u32 num_items, u32 primary_id, u32 secondary_id, const char **out_formatted) {
|
||||
if (!hbc || !out_formatted) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "invalid args");
|
||||
}
|
||||
HBCLiteralEntry *e;
|
||||
Result r = cache_get_or_create (hbc, kind, num_items, primary_id, secondary_id, &e);
|
||||
if (r.code != RESULT_SUCCESS) {
|
||||
return r;
|
||||
}
|
||||
*out_formatted = e->formatted;
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
static void xref_add(HBCLiteralEntry *e, u32 from_addr) {
|
||||
/* Keep xrefs as a small sorted-by-insertion array. Linear dedup. */
|
||||
for (u32 i = 0; i < e->xref_count; i++) {
|
||||
if (e->xref_addrs[i] == from_addr) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
u32 *na = realloc (e->xref_addrs, (e->xref_count + 1) * sizeof (u32));
|
||||
if (!na) {
|
||||
return;
|
||||
}
|
||||
e->xref_addrs = na;
|
||||
e->xref_addrs[e->xref_count++] = from_addr;
|
||||
}
|
||||
|
||||
Result hbc_literals_register(HBC *hbc, HBCLiteralKind kind, u32 num_items, u32 primary_id, u32 secondary_id, u32 from_addr) {
|
||||
if (!hbc) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "invalid args");
|
||||
}
|
||||
HBCLiteralEntry *e;
|
||||
Result r = cache_get_or_create (hbc, kind, num_items, primary_id, secondary_id, &e);
|
||||
if (r.code != RESULT_SUCCESS) {
|
||||
return r;
|
||||
}
|
||||
if (from_addr) {
|
||||
xref_add (e, from_addr);
|
||||
}
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
Result hbc_literals_list(HBC *hbc, const HBCLiteralEntry **out, u32 *out_count) {
|
||||
if (!hbc || !out || !out_count) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "invalid args");
|
||||
}
|
||||
*out = hbc->lit_cache.entries;
|
||||
*out_count = hbc->lit_cache.count;
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
u32 hbc_literals_count(HBC *hbc) {
|
||||
return hbc? hbc->lit_cache.count: 0;
|
||||
}
|
||||
|
||||
void hbc_literals_reset(HBC *hbc) {
|
||||
if (!hbc) {
|
||||
return;
|
||||
}
|
||||
HBCLiteralCache *c = &hbc->lit_cache;
|
||||
for (u32 i = 0; i < c->count; i++) {
|
||||
free (c->entries[i].formatted);
|
||||
free (c->entries[i].xref_addrs);
|
||||
}
|
||||
free (c->entries);
|
||||
c->entries = NULL;
|
||||
c->count = 0;
|
||||
c->cap = 0;
|
||||
}
|
||||
|
||||
/* ---------- Code-side scan ---------- */
|
||||
|
||||
static bool is_buffer_op(u8 opcode, HBCLiteralKind *out_kind) {
|
||||
switch (opcode) {
|
||||
case OP_NewArrayWithBuffer:
|
||||
case OP_NewArrayWithBufferLong:
|
||||
*out_kind = HBC_LIT_ARRAY;
|
||||
return true;
|
||||
case OP_NewObjectWithBuffer:
|
||||
case OP_NewObjectWithBufferLong:
|
||||
*out_kind = HBC_LIT_OBJECT;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Decode opcode immediates into (num_items, primary, secondary), version-aware.
|
||||
* Returns false if the opcode is not a buffer-literal constructor or if the
|
||||
* buffer would be read out of bounds. */
|
||||
static bool decode_buffer_op(u8 op, u32 version, const u8 *code, u32 size, u32 pc, u32 inst_size, HBCLiteralKind *kind, u32 *num_items, u32 *primary, u32 *secondary) {
|
||||
if (pc + inst_size > size) {
|
||||
return false;
|
||||
}
|
||||
*secondary = 0;
|
||||
if (op == OP_NewArrayWithBuffer) {
|
||||
/* reg8 + u16 prealloc + u16 num + u16 id (v<97 AND v97+) */
|
||||
*kind = HBC_LIT_ARRAY;
|
||||
*num_items = rd_u16le (code + pc + 4);
|
||||
*primary = rd_u16le (code + pc + 6);
|
||||
return true;
|
||||
}
|
||||
if (op == OP_NewArrayWithBufferLong) {
|
||||
/* reg8 + u16 prealloc + u16 num + u32 id */
|
||||
*kind = HBC_LIT_ARRAY;
|
||||
*num_items = rd_u16le (code + pc + 4);
|
||||
*primary = rd_u32le (code + pc + 6);
|
||||
return true;
|
||||
}
|
||||
if (op == OP_NewObjectWithBuffer) {
|
||||
*kind = HBC_LIT_OBJECT;
|
||||
if (version >= 97) {
|
||||
/* reg8 + u16 shape_id + u16 values_id */
|
||||
*primary = rd_u16le (code + pc + 2);
|
||||
*secondary = rd_u16le (code + pc + 4);
|
||||
*num_items = 0; /* resolved via shape table by the formatter */
|
||||
} else {
|
||||
/* reg8 + u16 prealloc + u16 num + u16 keys + u16 vals */
|
||||
*num_items = rd_u16le (code + pc + 4);
|
||||
*primary = rd_u16le (code + pc + 6);
|
||||
*secondary = rd_u16le (code + pc + 8);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (op == OP_NewObjectWithBufferLong) {
|
||||
*kind = HBC_LIT_OBJECT;
|
||||
if (version >= 97) {
|
||||
/* reg8 + u32 shape_id + u32 values_id */
|
||||
*primary = rd_u32le (code + pc + 2);
|
||||
*secondary = rd_u32le (code + pc + 6);
|
||||
*num_items = 0;
|
||||
} else {
|
||||
/* reg8 + u16 prealloc + u16 num + u32 keys + u32 vals */
|
||||
*num_items = rd_u16le (code + pc + 4);
|
||||
*primary = rd_u32le (code + pc + 6);
|
||||
*secondary = rd_u32le (code + pc + 10);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Result hbc_literals_scan_code(HBC *hbc, u32 *out_count) {
|
||||
if (!hbc) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "invalid args");
|
||||
}
|
||||
u32 nfuncs = hbc->reader.header.functionCount;
|
||||
u32 version = hbc->reader.header.version;
|
||||
HBCISA isa = hbc_isa_getv (version);
|
||||
if (!isa.instructions) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "no isa for version");
|
||||
}
|
||||
for (u32 fid = 0; fid < nfuncs; fid++) {
|
||||
const u8 *code = NULL;
|
||||
u32 size = 0;
|
||||
if (hbc_get_function_bytecode (hbc, fid, &code, &size).code != RESULT_SUCCESS || !code || size == 0) {
|
||||
continue;
|
||||
}
|
||||
HBCFunc fi;
|
||||
u32 fn_paddr = 0;
|
||||
if (hbc_get_function_info (hbc, fid, &fi).code == RESULT_SUCCESS) {
|
||||
fn_paddr = fi.offset;
|
||||
}
|
||||
u32 pc = 0;
|
||||
while (pc < size) {
|
||||
u8 op = code[pc];
|
||||
const Instruction *ins = (op < isa.count)? &isa.instructions[op]: NULL;
|
||||
if (!ins || !ins->name || ins->binary_size == 0) {
|
||||
break;
|
||||
}
|
||||
HBCLiteralKind kind;
|
||||
u32 num_items = 0, primary = 0, secondary = 0;
|
||||
if (decode_buffer_op (op, version, code, size, pc, ins->binary_size, &kind, &num_items, &primary, &secondary)) {
|
||||
u32 call_addr = fn_paddr + pc;
|
||||
hbc_literals_register (hbc, kind, num_items, primary, secondary, call_addr);
|
||||
}
|
||||
(void)is_buffer_op; /* retained for tools that need the predicate */
|
||||
pc += ins->binary_size;
|
||||
}
|
||||
}
|
||||
if (out_count) {
|
||||
*out_count = hbc->lit_cache.count;
|
||||
}
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
/* ---------- Pool-side scan ---------- */
|
||||
|
||||
static bool slp_skip_group(const u8 *base, size_t size, size_t *pos, u8 *out_tag, u32 *out_length) {
|
||||
if (*pos >= size) {
|
||||
return false;
|
||||
}
|
||||
u8 taglen = base[(*pos)++];
|
||||
u8 tag = (taglen >> 4) & 0x7;
|
||||
u32 length = taglen & 0x0F;
|
||||
if (taglen & 0x80) {
|
||||
if (*pos >= size) {
|
||||
return false;
|
||||
}
|
||||
length = ((u32) (taglen & 0x0F) << 8) | base[(*pos)++];
|
||||
}
|
||||
size_t per_value = 0;
|
||||
switch (tag) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 2: per_value = 0; break; /* null/true/false */
|
||||
case 3: per_value = 8; break; /* number */
|
||||
case 4:
|
||||
case 7: per_value = 4; break; /* longstr/int */
|
||||
case 5: per_value = 2; break; /* shortstr */
|
||||
case 6: per_value = 1; break; /* bytestr */
|
||||
default: return false;
|
||||
}
|
||||
if (*pos + per_value * length > size) {
|
||||
return false;
|
||||
}
|
||||
*pos += per_value * length;
|
||||
if (out_tag) {
|
||||
*out_tag = tag;
|
||||
}
|
||||
if (out_length) {
|
||||
*out_length = length;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Result hbc_literals_scan_pool(HBC *hbc, HBCLiteralKind kind, HBCPoolGroup **out, u32 *out_count) {
|
||||
if (!hbc || !out || !out_count) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "invalid args");
|
||||
}
|
||||
*out = NULL;
|
||||
*out_count = 0;
|
||||
|
||||
/* Object-keys on v97+: enumerate shape table instead of the raw pool. */
|
||||
if (kind == HBC_LIT_OBJECT && hbc->reader.header.version >= 97 && hbc->reader.object_shapes && hbc->reader.object_shape_count > 0) {
|
||||
size_t n = hbc->reader.object_shape_count;
|
||||
HBCPoolGroup *arr = calloc (n, sizeof (HBCPoolGroup));
|
||||
if (!arr) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "oom");
|
||||
}
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
arr[i].pool_offset = hbc->reader.object_shapes[i].key_buffer_offset;
|
||||
arr[i].num_items = hbc->reader.object_shapes[i].prop_count;
|
||||
arr[i].paddr = hbc->reader.object_keys_paddr + arr[i].pool_offset;
|
||||
arr[i].tag = 0xff; /* N/A for a shape row */
|
||||
}
|
||||
*out = arr;
|
||||
*out_count = (u32)n;
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
const u8 *base;
|
||||
u32 size;
|
||||
u32 paddr;
|
||||
if (kind == HBC_LIT_ARRAY) {
|
||||
base = hbc->reader.arrays;
|
||||
size = hbc->reader.header.arrayBufferSize;
|
||||
paddr = hbc->reader.arrays_paddr;
|
||||
} else {
|
||||
base = hbc->reader.object_keys;
|
||||
size = hbc->reader.header.objKeyBufferSize;
|
||||
paddr = hbc->reader.object_keys_paddr;
|
||||
}
|
||||
if (!base || !size) {
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
u32 cap = 64;
|
||||
HBCPoolGroup *arr = calloc (cap, sizeof (HBCPoolGroup));
|
||||
if (!arr) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "oom");
|
||||
}
|
||||
u32 n = 0;
|
||||
size_t pos = 0;
|
||||
while (pos < size) {
|
||||
size_t group_start = pos;
|
||||
u8 tag = 0;
|
||||
u32 length = 0;
|
||||
if (!slp_skip_group (base, size, &pos, &tag, &length)) {
|
||||
break;
|
||||
}
|
||||
if (grow_array (&arr, &cap, n, sizeof (HBCPoolGroup), 64).code != RESULT_SUCCESS) {
|
||||
break;
|
||||
}
|
||||
arr[n].pool_offset = (u32)group_start;
|
||||
arr[n].paddr = paddr + (u32)group_start;
|
||||
arr[n].num_items = length;
|
||||
arr[n].tag = tag;
|
||||
n++;
|
||||
}
|
||||
*out = arr;
|
||||
*out_count = n;
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
+7
-100
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/disasm.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -135,15 +137,7 @@ Result _hbc_print_function_header(Disassembler *disassembler, FunctionHeader *fu
|
||||
static Result format_operand_asm(Disassembler *d, ParsedInstruction *ins, int idx, StringBuffer *out) {
|
||||
OperandType t = ins->inst->operands[idx].operand_type;
|
||||
OperandMeaning m = ins->inst->operands[idx].operand_meaning;
|
||||
u32 v = 0;
|
||||
switch (idx) {
|
||||
case 0: v = ins->arg1; break;
|
||||
case 1: v = ins->arg2; break;
|
||||
case 2: v = ins->arg3; break;
|
||||
case 3: v = ins->arg4; break;
|
||||
case 4: v = ins->arg5; break;
|
||||
case 5: v = ins->arg6; break;
|
||||
}
|
||||
u32 v = hbc_operand_value (ins, idx);
|
||||
|
||||
HBCReader *r = d->reader;
|
||||
FunctionHeader *fh = &r->function_headers[d->current_function_id];
|
||||
@@ -263,82 +257,13 @@ Result _hbc_print_instruction(Disassembler *disassembler, ParsedInstruction *ins
|
||||
}
|
||||
first = false;
|
||||
|
||||
/* Get operand name */
|
||||
const char *operand_name = "Unknown";
|
||||
switch (instruction->inst->operands[i].operand_meaning) {
|
||||
case OPERAND_MEANING_NONE:
|
||||
switch (operand_type) {
|
||||
case OPERAND_TYPE_REG8:
|
||||
operand_name = "Reg8";
|
||||
break;
|
||||
case OPERAND_TYPE_REG32:
|
||||
operand_name = "Reg32";
|
||||
break;
|
||||
case OPERAND_TYPE_UINT8:
|
||||
operand_name = "UInt8";
|
||||
break;
|
||||
case OPERAND_TYPE_UINT16:
|
||||
operand_name = "UInt16";
|
||||
break;
|
||||
case OPERAND_TYPE_UINT32:
|
||||
operand_name = "UInt32";
|
||||
break;
|
||||
case OPERAND_TYPE_ADDR8:
|
||||
operand_name = "Addr8";
|
||||
break;
|
||||
case OPERAND_TYPE_ADDR32:
|
||||
operand_name = "Addr32";
|
||||
break;
|
||||
case OPERAND_TYPE_IMM32:
|
||||
operand_name = "Imm32";
|
||||
break;
|
||||
case OPERAND_TYPE_DOUBLE:
|
||||
operand_name = "Double";
|
||||
break;
|
||||
break;
|
||||
default:
|
||||
operand_name = "Unknown";
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case OPERAND_MEANING_STRING_ID:
|
||||
operand_name = "string_id";
|
||||
break;
|
||||
case OPERAND_MEANING_BIGINT_ID:
|
||||
operand_name = "bigint_id";
|
||||
break;
|
||||
case OPERAND_MEANING_FUNCTION_ID:
|
||||
operand_name = "function_id";
|
||||
break;
|
||||
case OPERAND_MEANING_BUILTIN_ID:
|
||||
operand_name = "builtin_id";
|
||||
break;
|
||||
case OPERAND_MEANING_ARRAY_ID:
|
||||
operand_name = "array_id";
|
||||
break;
|
||||
case OPERAND_MEANING_OBJ_KEY_ID:
|
||||
operand_name = "obj_key_id";
|
||||
break;
|
||||
case OPERAND_MEANING_OBJ_VAL_ID:
|
||||
operand_name = "obj_val_id";
|
||||
break;
|
||||
}
|
||||
|
||||
/* Print operand name and value */
|
||||
const char *operand_name = hbc_operand_name (&instruction->inst->operands[i]);
|
||||
RETURN_IF_ERROR (_hbc_string_buffer_append (output, operand_name));
|
||||
RETURN_IF_ERROR (_hbc_string_buffer_append (output, ": "));
|
||||
|
||||
/* Get operand value */
|
||||
u32 value;
|
||||
switch (i) {
|
||||
case 0: value = instruction->arg1; break;
|
||||
case 1: value = instruction->arg2; break;
|
||||
case 2: value = instruction->arg3; break;
|
||||
case 3: value = instruction->arg4; break;
|
||||
case 4: value = instruction->arg5; break;
|
||||
case 5: value = instruction->arg6; break;
|
||||
default: value = 0;
|
||||
}
|
||||
u32 value = hbc_operand_value (instruction, i);
|
||||
|
||||
/* Print operand value */
|
||||
if (instruction->inst->operands[i].operand_type != OPERAND_TYPE_DOUBLE) {
|
||||
@@ -362,16 +287,7 @@ Result _hbc_print_instruction(Disassembler *disassembler, ParsedInstruction *ins
|
||||
continue;
|
||||
}
|
||||
|
||||
u32 value;
|
||||
switch (i) {
|
||||
case 0: value = instruction->arg1; break;
|
||||
case 1: value = instruction->arg2; break;
|
||||
case 2: value = instruction->arg3; break;
|
||||
case 3: value = instruction->arg4; break;
|
||||
case 4: value = instruction->arg5; break;
|
||||
case 5: value = instruction->arg6; break;
|
||||
default: value = 0;
|
||||
}
|
||||
u32 value = hbc_operand_value (instruction, i);
|
||||
|
||||
switch (operand_meaning) {
|
||||
case OPERAND_MEANING_STRING_ID:
|
||||
@@ -437,16 +353,7 @@ Result _hbc_print_instruction(Disassembler *disassembler, ParsedInstruction *ins
|
||||
continue;
|
||||
}
|
||||
|
||||
u32 value;
|
||||
switch (i) {
|
||||
case 0: value = instruction->arg1; break;
|
||||
case 1: value = instruction->arg2; break;
|
||||
case 2: value = instruction->arg3; break;
|
||||
case 3: value = instruction->arg4; break;
|
||||
case 4: value = instruction->arg5; break;
|
||||
case 5: value = instruction->arg6; break;
|
||||
default: value = 0;
|
||||
}
|
||||
u32 value = hbc_operand_value (instruction, i);
|
||||
|
||||
RETURN_IF_ERROR (_hbc_string_buffer_append (output, " # Address: "));
|
||||
|
||||
|
||||
+102
-98
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/hermes_encoder.h>
|
||||
#include <hbc/opcodes.h>
|
||||
#include <hbc/bytecode.h>
|
||||
@@ -131,7 +133,7 @@ Result hbc_encoder_parse_instruction(HBCEncoder *encoder, const char *asm_line,
|
||||
}
|
||||
|
||||
/* Parse operands separated by commas */
|
||||
while (*line && operand_count < 6) {
|
||||
while (*line) {
|
||||
/* Skip whitespace */
|
||||
while (*line && isspace (*line)) {
|
||||
line++;
|
||||
@@ -177,104 +179,106 @@ Result hbc_encoder_parse_instruction(HBCEncoder *encoder, const char *asm_line,
|
||||
}
|
||||
*trim_end = '\0';
|
||||
|
||||
/* Parse operand based on expected type */
|
||||
if (operand_count < 6 && inst->operands[operand_count].operand_type != OPERAND_TYPE_NONE) {
|
||||
OperandType expected_type = inst->operands[operand_count].operand_type;
|
||||
char *endptr;
|
||||
bool parse_success = false;
|
||||
|
||||
switch (expected_type) {
|
||||
case OPERAND_TYPE_REG8:
|
||||
case OPERAND_TYPE_REG32:
|
||||
/* Register: rN or RN */
|
||||
if (trim_start[0] == 'r' || trim_start[0] == 'R') {
|
||||
operand_values[operand_count] = (u32)strtoul (trim_start + 1, &endptr, 10);
|
||||
if (*endptr == '\0') {
|
||||
parse_success = true;
|
||||
/* Validate register range */
|
||||
if (expected_type == OPERAND_TYPE_REG8 && operand_values[operand_count] > 255) {
|
||||
return ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, "Register number out of range for REG8");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Allow bare numbers for registers too */
|
||||
operand_values[operand_count] = (u32)strtoul (trim_start, &endptr, 10);
|
||||
if (*endptr == '\0') {
|
||||
parse_success = true;
|
||||
/* Validate register range */
|
||||
if (expected_type == OPERAND_TYPE_REG8 && operand_values[operand_count] > 255) {
|
||||
return ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, "Register number out of range for REG8");
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case OPERAND_TYPE_UINT8:
|
||||
case OPERAND_TYPE_UINT16:
|
||||
case OPERAND_TYPE_UINT32:
|
||||
case OPERAND_TYPE_IMM32:
|
||||
case OPERAND_TYPE_ADDR8:
|
||||
case OPERAND_TYPE_ADDR32:
|
||||
/* Immediate or address */
|
||||
if (trim_start[0] == '0' && (trim_start[1] == 'x' || trim_start[1] == 'X')) {
|
||||
/* Hex */
|
||||
operand_values[operand_count] = (u32)strtoul (trim_start, &endptr, 16);
|
||||
if (*endptr == '\0') {
|
||||
parse_success = true;
|
||||
}
|
||||
} else {
|
||||
/* Decimal */
|
||||
operand_values[operand_count] = (u32)strtoul (trim_start, &endptr, 10);
|
||||
if (*endptr == '\0') {
|
||||
parse_success = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case OPERAND_TYPE_DOUBLE:
|
||||
/* Parse as double literal */
|
||||
{
|
||||
char *endptr_double;
|
||||
double dval = strtod (trim_start, &endptr_double);
|
||||
if (*endptr_double == '\0') {
|
||||
/* Convert double to IEEE 754 bits */
|
||||
u64 bits;
|
||||
memcpy (&bits, &dval, sizeof (double));
|
||||
operand_values[operand_count] = bits;
|
||||
parse_success = true;
|
||||
} else if (trim_start[0] == '0' && (trim_start[1] == 'x' || trim_start[1] == 'X')) {
|
||||
/* Allow hex for bit patterns */
|
||||
operand_values[operand_count] = (u64)strtoull (trim_start, &endptr_double, 16);
|
||||
if (*endptr_double == '\0') {
|
||||
parse_success = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
/* Unknown type, try to parse as number */
|
||||
if (trim_start[0] == '0' && (trim_start[1] == 'x' || trim_start[1] == 'X')) {
|
||||
operand_values[operand_count] = (u32)strtoul (trim_start, &endptr, 16);
|
||||
if (*endptr == '\0') {
|
||||
parse_success = true;
|
||||
}
|
||||
} else {
|
||||
operand_values[operand_count] = (u32)strtoul (trim_start, &endptr, 10);
|
||||
if (*endptr == '\0') {
|
||||
parse_success = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!parse_success) {
|
||||
return ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, "Invalid operand format");
|
||||
}
|
||||
|
||||
operand_count++;
|
||||
if (operand_count >= 6 || inst->operands[operand_count].operand_type == OPERAND_TYPE_NONE) {
|
||||
return ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, "Incorrect number of operands");
|
||||
}
|
||||
|
||||
/* Parse operand based on expected type */
|
||||
OperandType expected_type = inst->operands[operand_count].operand_type;
|
||||
char *endptr;
|
||||
bool parse_success = false;
|
||||
|
||||
switch (expected_type) {
|
||||
case OPERAND_TYPE_REG8:
|
||||
case OPERAND_TYPE_REG32:
|
||||
/* Register: rN or RN */
|
||||
if (trim_start[0] == 'r' || trim_start[0] == 'R') {
|
||||
operand_values[operand_count] = (u32)strtoul (trim_start + 1, &endptr, 10);
|
||||
if (*endptr == '\0') {
|
||||
parse_success = true;
|
||||
/* Validate register range */
|
||||
if (expected_type == OPERAND_TYPE_REG8 && operand_values[operand_count] > 255) {
|
||||
return ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, "Register number out of range for REG8");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Allow bare numbers for registers too */
|
||||
operand_values[operand_count] = (u32)strtoul (trim_start, &endptr, 10);
|
||||
if (*endptr == '\0') {
|
||||
parse_success = true;
|
||||
/* Validate register range */
|
||||
if (expected_type == OPERAND_TYPE_REG8 && operand_values[operand_count] > 255) {
|
||||
return ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, "Register number out of range for REG8");
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case OPERAND_TYPE_UINT8:
|
||||
case OPERAND_TYPE_UINT16:
|
||||
case OPERAND_TYPE_UINT32:
|
||||
case OPERAND_TYPE_IMM32:
|
||||
case OPERAND_TYPE_ADDR8:
|
||||
case OPERAND_TYPE_ADDR32:
|
||||
/* Immediate or address */
|
||||
if (trim_start[0] == '0' && (trim_start[1] == 'x' || trim_start[1] == 'X')) {
|
||||
/* Hex */
|
||||
operand_values[operand_count] = (u32)strtoul (trim_start, &endptr, 16);
|
||||
if (*endptr == '\0') {
|
||||
parse_success = true;
|
||||
}
|
||||
} else {
|
||||
/* Decimal */
|
||||
operand_values[operand_count] = (u32)strtoul (trim_start, &endptr, 10);
|
||||
if (*endptr == '\0') {
|
||||
parse_success = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case OPERAND_TYPE_DOUBLE:
|
||||
/* Parse as double literal */
|
||||
{
|
||||
char *endptr_double;
|
||||
double dval = strtod (trim_start, &endptr_double);
|
||||
if (*endptr_double == '\0') {
|
||||
/* Convert double to IEEE 754 bits */
|
||||
u64 bits;
|
||||
memcpy (&bits, &dval, sizeof (double));
|
||||
operand_values[operand_count] = bits;
|
||||
parse_success = true;
|
||||
} else if (trim_start[0] == '0' && (trim_start[1] == 'x' || trim_start[1] == 'X')) {
|
||||
/* Allow hex for bit patterns */
|
||||
operand_values[operand_count] = (u64)strtoull (trim_start, &endptr_double, 16);
|
||||
if (*endptr_double == '\0') {
|
||||
parse_success = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
/* Unknown type, try to parse as number */
|
||||
if (trim_start[0] == '0' && (trim_start[1] == 'x' || trim_start[1] == 'X')) {
|
||||
operand_values[operand_count] = (u32)strtoul (trim_start, &endptr, 16);
|
||||
if (*endptr == '\0') {
|
||||
parse_success = true;
|
||||
}
|
||||
} else {
|
||||
operand_values[operand_count] = (u32)strtoul (trim_start, &endptr, 10);
|
||||
if (*endptr == '\0') {
|
||||
parse_success = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!parse_success) {
|
||||
return ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, "Invalid operand format");
|
||||
}
|
||||
|
||||
operand_count++;
|
||||
|
||||
/* Move to next operand */
|
||||
line = operand_end;
|
||||
if (*line == ',') {
|
||||
@@ -338,7 +342,7 @@ Result hbc_encoder_encode_instruction(HBCEncoder *encoder, const HBCEncodedInstr
|
||||
/* Write operands based on their types */
|
||||
for (int i = 0; i < 6 && inst->operands[i].operand_type != OPERAND_TYPE_NONE; i++) {
|
||||
OperandType type = inst->operands[i].operand_type;
|
||||
u32 value = operand_values[i];
|
||||
u64 value = operand_values[i];
|
||||
|
||||
switch (type) {
|
||||
case OPERAND_TYPE_REG8:
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/opcodes.h>
|
||||
|
||||
static const Instruction k_instructions_v51[167] = {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/opcodes.h>
|
||||
|
||||
static const Instruction k_instructions_v58[176] = {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/opcodes.h>
|
||||
|
||||
static const Instruction k_instructions_v59[177] = {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/opcodes.h>
|
||||
|
||||
static const Instruction k_instructions_v61[177] = {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/opcodes.h>
|
||||
|
||||
static const Instruction k_instructions_v62[177] = {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/opcodes.h>
|
||||
|
||||
static const Instruction k_instructions_v68[177] = {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/opcodes.h>
|
||||
|
||||
static const Instruction k_instructions_v69[177] = {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/opcodes.h>
|
||||
|
||||
static const Instruction k_instructions_v70[177] = {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/opcodes.h>
|
||||
|
||||
static const Instruction k_instructions_v72[177] = {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/opcodes.h>
|
||||
|
||||
static const Instruction k_instructions_v73[180] = {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/opcodes.h>
|
||||
|
||||
static const Instruction k_instructions_v76[256] = {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/opcodes.h>
|
||||
|
||||
static const Instruction k_instructions_v80[181] = {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/opcodes.h>
|
||||
|
||||
static const Instruction k_instructions_v81[183] = {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/opcodes.h>
|
||||
|
||||
static const Instruction k_instructions_v82[185] = {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/opcodes.h>
|
||||
|
||||
static const Instruction k_instructions_v83[185] = {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/opcodes.h>
|
||||
|
||||
static const Instruction k_instructions_v84[256] = {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/opcodes.h>
|
||||
|
||||
static const Instruction k_instructions_v85[201] = {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/opcodes.h>
|
||||
|
||||
static const Instruction k_instructions_v86[201] = {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/opcodes.h>
|
||||
|
||||
static const Instruction k_instructions_v87[204] = {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/opcodes.h>
|
||||
|
||||
static const Instruction k_instructions_v89[256] = {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/opcodes.h>
|
||||
|
||||
static const Instruction k_instructions_v90[256] = {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/opcodes.h>
|
||||
|
||||
static const Instruction k_instructions_v92[206] = {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/opcodes.h>
|
||||
|
||||
static const Instruction k_instructions_v93[256] = {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/opcodes.h>
|
||||
|
||||
static const Instruction k_instructions_v94[256] = {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/opcodes.h>
|
||||
|
||||
static const Instruction k_instructions_v95[256] = {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/opcodes.h>
|
||||
|
||||
static const Instruction k_instructions_v96[256] = {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/opcodes.h>
|
||||
|
||||
static const Instruction k_instructions_v97[200] = {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/opcodes.h>
|
||||
|
||||
static const Instruction k_instructions_v99[223] = {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/bytecode.h>
|
||||
#include <hbc/opcodes.h>
|
||||
|
||||
@@ -116,16 +118,10 @@ Result _hbc_parse_function_bytecode(HBCReader *reader, u32 function_id, ParsedIn
|
||||
/* Initialize instruction list */
|
||||
RETURN_IF_ERROR (_hbc_parsed_instruction_list_init (out_instructions, 32)); /* Start with space for 32 instructions */
|
||||
|
||||
/* Create a fresh bytecode buffer similar to the Python BytesIO approach */
|
||||
BufferReader bytecode_buffer;
|
||||
Result result = _hbc_buffer_reader_init_from_memory (&bytecode_buffer,
|
||||
function_header->bytecode,
|
||||
function_header->bytecodeSizeInBytes);
|
||||
|
||||
if (result.code != RESULT_SUCCESS) {
|
||||
_hbc_parsed_instruction_list_free (out_instructions);
|
||||
return result;
|
||||
}
|
||||
/* Read directly from the function bytecode: the parser never writes and
|
||||
* the header-owned buffer outlives the parse, so no owning copy is needed */
|
||||
BufferReader bytecode_buffer = { function_header->bytecode, function_header->bytecodeSizeInBytes, 0 };
|
||||
Result result;
|
||||
|
||||
/* Parse instructions in a loop similar to Python's implementation */
|
||||
while (bytecode_buffer.position < bytecode_buffer.size) {
|
||||
@@ -174,6 +170,7 @@ Result _hbc_parse_function_bytecode(HBCReader *reader, u32 function_id, ParsedIn
|
||||
instruction.original_pos = original_pos;
|
||||
instruction.next_pos = original_pos + (u32) (remaining > UINT32_MAX? UINT32_MAX: remaining);
|
||||
instruction.function_offset = function_header->offset;
|
||||
instruction.frame_size = function_header->frameSize;
|
||||
instruction.hbc_reader = reader;
|
||||
instruction.switch_jump_table = NULL;
|
||||
instruction.switch_jump_table_size = 0;
|
||||
@@ -192,6 +189,7 @@ Result _hbc_parse_function_bytecode(HBCReader *reader, u32 function_id, ParsedIn
|
||||
instruction.opcode = hbc_canonical_opcode_from_name (inst->name);
|
||||
instruction.original_pos = original_pos;
|
||||
instruction.function_offset = function_header->offset;
|
||||
instruction.frame_size = function_header->frameSize;
|
||||
instruction.hbc_reader = reader;
|
||||
|
||||
/* Set the expected next position based on instruction size */
|
||||
@@ -368,7 +366,13 @@ Result _hbc_parse_function_bytecode(HBCReader *reader, u32 function_id, ParsedIn
|
||||
candidates[1] = function_header->offset + base; /* function-relative */
|
||||
candidates[2] = base; /* absolute */
|
||||
size_t func_start = function_header->offset;
|
||||
size_t func_end = func_start + function_header->bytecodeSizeInBytes;
|
||||
size_t table_limit = fsz;
|
||||
if (function_id + 1 < reader->header.functionCount) {
|
||||
size_t next_func = reader->function_headers[function_id + 1].offset;
|
||||
if (next_func > func_start && next_func < table_limit) {
|
||||
table_limit = next_func;
|
||||
}
|
||||
}
|
||||
|
||||
/* Save file position */
|
||||
size_t saved_pos = reader->file_buffer.position;
|
||||
@@ -398,8 +402,8 @@ Result _hbc_parse_function_bytecode(HBCReader *reader, u32 function_id, ParsedIn
|
||||
if (table_end > fsz) {
|
||||
continue;
|
||||
}
|
||||
/* Also require the table to lie within the function's bytecode region */
|
||||
if (! (reader->file_buffer.position >= func_start && table_end <= func_end)) {
|
||||
/* Jump tables live after the bytecode body, before the next function. */
|
||||
if (! (reader->file_buffer.position >= func_start && table_end <= table_limit)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -446,6 +450,38 @@ Result _hbc_parse_function_bytecode(HBCReader *reader, u32 function_id, ParsedIn
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
u32 hbc_operand_value(const ParsedInstruction *insn, int i) {
|
||||
if (!insn) {
|
||||
return 0;
|
||||
}
|
||||
switch (i) {
|
||||
case 0: return insn->arg1;
|
||||
case 1: return insn->arg2;
|
||||
case 2: return insn->arg3;
|
||||
case 3: return insn->arg4;
|
||||
case 4: return insn->arg5;
|
||||
case 5: return insn->arg6;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool _hbc_operand_is_addr(const Instruction *inst, int idx) {
|
||||
if (!inst || idx < 0 || idx >= 6) {
|
||||
return false;
|
||||
}
|
||||
OperandType t = inst->operands[idx].operand_type;
|
||||
return t == OPERAND_TYPE_ADDR8 || t == OPERAND_TYPE_ADDR32;
|
||||
}
|
||||
|
||||
u32 _hbc_compute_target_address(const ParsedInstruction *insn, int op_index) {
|
||||
if (!insn) {
|
||||
return 0;
|
||||
}
|
||||
/* Hermes jump and address offsets are relative to the start of the
|
||||
* instruction (original_pos), matching the disassembler and the VM. */
|
||||
return insn->original_pos + hbc_operand_value (insn, op_index);
|
||||
}
|
||||
|
||||
/* Convert instruction to string */
|
||||
Result _hbc_instruction_to_string(ParsedInstruction *instruction, StringBuffer *out_string) {
|
||||
if (!instruction || !out_string || !instruction->inst) {
|
||||
@@ -476,80 +512,10 @@ Result _hbc_instruction_to_string(ParsedInstruction *instruction, StringBuffer *
|
||||
first = false;
|
||||
|
||||
/* Get operand value */
|
||||
u32 value;
|
||||
switch (i) {
|
||||
case 0: value = instruction->arg1; break;
|
||||
case 1: value = instruction->arg2; break;
|
||||
case 2: value = instruction->arg3; break;
|
||||
case 3: value = instruction->arg4; break;
|
||||
case 4: value = instruction->arg5; break;
|
||||
case 5: value = instruction->arg6; break;
|
||||
default: value = 0;
|
||||
}
|
||||
u32 value = hbc_operand_value (instruction, i);
|
||||
|
||||
/* Get operand name */
|
||||
const char *operand_name;
|
||||
if (instruction->inst->operands[i].operand_meaning != OPERAND_MEANING_NONE) {
|
||||
switch (instruction->inst->operands[i].operand_meaning) {
|
||||
case OPERAND_MEANING_STRING_ID:
|
||||
operand_name = "string_id";
|
||||
break;
|
||||
case OPERAND_MEANING_BIGINT_ID:
|
||||
operand_name = "bigint_id";
|
||||
break;
|
||||
case OPERAND_MEANING_FUNCTION_ID:
|
||||
operand_name = "function_id";
|
||||
break;
|
||||
case OPERAND_MEANING_BUILTIN_ID:
|
||||
operand_name = "builtin_id";
|
||||
break;
|
||||
case OPERAND_MEANING_ARRAY_ID:
|
||||
operand_name = "array_id";
|
||||
break;
|
||||
case OPERAND_MEANING_OBJ_KEY_ID:
|
||||
operand_name = "obj_key_id";
|
||||
break;
|
||||
case OPERAND_MEANING_OBJ_VAL_ID:
|
||||
operand_name = "obj_val_id";
|
||||
break;
|
||||
default:
|
||||
operand_name = "unknown";
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (instruction->inst->operands[i].operand_type) {
|
||||
case OPERAND_TYPE_REG8:
|
||||
operand_name = "Reg8";
|
||||
break;
|
||||
case OPERAND_TYPE_REG32:
|
||||
operand_name = "Reg32";
|
||||
break;
|
||||
case OPERAND_TYPE_UINT8:
|
||||
operand_name = "UInt8";
|
||||
break;
|
||||
case OPERAND_TYPE_UINT16:
|
||||
operand_name = "UInt16";
|
||||
break;
|
||||
case OPERAND_TYPE_UINT32:
|
||||
operand_name = "UInt32";
|
||||
break;
|
||||
case OPERAND_TYPE_ADDR8:
|
||||
operand_name = "Addr8";
|
||||
break;
|
||||
case OPERAND_TYPE_ADDR32:
|
||||
operand_name = "Addr32";
|
||||
break;
|
||||
case OPERAND_TYPE_IMM32:
|
||||
operand_name = "Imm32";
|
||||
break;
|
||||
case OPERAND_TYPE_DOUBLE:
|
||||
operand_name = "Double";
|
||||
break;
|
||||
default:
|
||||
operand_name = "Unknown";
|
||||
break;
|
||||
}
|
||||
}
|
||||
const char *operand_name = hbc_operand_name (&instruction->inst->operands[i]);
|
||||
|
||||
/* Print operand name and value */
|
||||
RETURN_IF_ERROR (_hbc_string_buffer_append (out_string, operand_name));
|
||||
@@ -573,16 +539,7 @@ Result _hbc_instruction_to_string(ParsedInstruction *instruction, StringBuffer *
|
||||
continue;
|
||||
}
|
||||
|
||||
u32 value;
|
||||
switch (i) {
|
||||
case 0: value = instruction->arg1; break;
|
||||
case 1: value = instruction->arg2; break;
|
||||
case 2: value = instruction->arg3; break;
|
||||
case 3: value = instruction->arg4; break;
|
||||
case 4: value = instruction->arg5; break;
|
||||
case 5: value = instruction->arg6; break;
|
||||
default: value = 0;
|
||||
}
|
||||
u32 value = hbc_operand_value (instruction, i);
|
||||
|
||||
switch (operand_meaning) {
|
||||
case OPERAND_MEANING_STRING_ID:
|
||||
@@ -638,16 +595,7 @@ Result _hbc_instruction_to_string(ParsedInstruction *instruction, StringBuffer *
|
||||
continue;
|
||||
}
|
||||
|
||||
u32 value;
|
||||
switch (i) {
|
||||
case 0: value = instruction->arg1; break;
|
||||
case 1: value = instruction->arg2; break;
|
||||
case 2: value = instruction->arg3; break;
|
||||
case 3: value = instruction->arg4; break;
|
||||
case 4: value = instruction->arg5; break;
|
||||
case 5: value = instruction->arg6; break;
|
||||
default: value = 0;
|
||||
}
|
||||
u32 value = hbc_operand_value (instruction, i);
|
||||
|
||||
/* For addresses, calculate absolute target address */
|
||||
u32 absolute_address = instruction->function_offset + instruction->original_pos + value;
|
||||
@@ -658,7 +606,7 @@ Result _hbc_instruction_to_string(ParsedInstruction *instruction, StringBuffer *
|
||||
}
|
||||
|
||||
/* Add jump table for switch instructions */
|
||||
if (strcmp (instruction->inst->name, "SwitchImm") == 0 &&
|
||||
if (instruction->opcode == OP_SwitchImm &&
|
||||
instruction->switch_jump_table && instruction->switch_jump_table_size > 0) {
|
||||
|
||||
RETURN_IF_ERROR (_hbc_string_buffer_append (out_string, " # Jump table: ["));
|
||||
|
||||
+132
-116
@@ -1,8 +1,20 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/parser.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Unpack the function-header flags byte into the header struct. */
|
||||
static void unpack_function_flags(FunctionHeader *h, u8 flags) {
|
||||
h->prohibitInvoke = flags & 0x3u;
|
||||
h->strictMode = (flags >> 2) & 0x1u;
|
||||
h->hasExceptionHandler = (flags >> 3) & 0x1u;
|
||||
h->hasDebugInfo = (flags >> 4) & 0x1u;
|
||||
h->overflowed = (flags >> 5) & 0x1u;
|
||||
h->unused = (flags >> 6) & 0x3u;
|
||||
}
|
||||
|
||||
/* Initialize HBC reader */
|
||||
Result _hbc_reader_init(HBCReader *reader) {
|
||||
R_RETURN_VAL_IF_FAIL (reader,
|
||||
@@ -81,6 +93,12 @@ Result _hbc_reader_cleanup(HBCReader *reader) {
|
||||
/* Clean up debug info */
|
||||
free (reader->debug_string_table);
|
||||
free (reader->debug_string_storage);
|
||||
if (reader->debug_filenames) {
|
||||
for (u32 i = 0; i < reader->debug_info_header.filename_count; i++) {
|
||||
free (reader->debug_filenames[i]);
|
||||
}
|
||||
free (reader->debug_filenames);
|
||||
}
|
||||
free (reader->debug_file_regions);
|
||||
free (reader->sources_data_storage);
|
||||
free (reader->scope_desc_data_storage);
|
||||
@@ -136,8 +154,10 @@ Result _hbc_reader_read_file(HBCReader *reader, const char *filename) {
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
fprintf (stderr, "Warning: File does not start with Hermes bytecode magic number (found 0x%016llx, expected 0x%016llx).\n", (unsigned long long)magic, (unsigned long long)HEADER_MAGIC);
|
||||
fprintf (stderr, "This file may not be a Hermes bytecode file or might be corrupted.\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
return SUCCESS_RESULT ();
|
||||
@@ -167,6 +187,45 @@ static Result align_over_padding(BufferReader *reader, size_t padding_amount) {
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
static Result read_exception_handlers(HBCReader *reader, u32 function_id, u32 info_offset) {
|
||||
RETURN_IF_ERROR (_hbc_buffer_reader_seek (&reader->file_buffer, info_offset));
|
||||
RETURN_IF_ERROR (align_over_padding (&reader->file_buffer, 4));
|
||||
|
||||
u32 count;
|
||||
RETURN_IF_ERROR (_hbc_buffer_reader_read_u32 (&reader->file_buffer, &count));
|
||||
size_t remaining = reader->file_buffer.size - reader->file_buffer.position;
|
||||
if (count > remaining / (3 * sizeof (u32))) {
|
||||
return ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, "Exception handler table exceeds file size");
|
||||
}
|
||||
|
||||
ExceptionHandlerInfo *handlers = NULL;
|
||||
if (count) {
|
||||
handlers = (ExceptionHandlerInfo *)malloc (count * sizeof (ExceptionHandlerInfo));
|
||||
if (!handlers) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "Failed to allocate exception handlers");
|
||||
}
|
||||
}
|
||||
for (u32 j = 0; j < count; j++) {
|
||||
Result res = _hbc_buffer_reader_read_u32 (&reader->file_buffer, &handlers[j].start);
|
||||
if (res.code == RESULT_SUCCESS) {
|
||||
res = _hbc_buffer_reader_read_u32 (&reader->file_buffer, &handlers[j].end);
|
||||
}
|
||||
if (res.code == RESULT_SUCCESS) {
|
||||
res = _hbc_buffer_reader_read_u32 (&reader->file_buffer, &handlers[j].target);
|
||||
}
|
||||
if (res.code != RESULT_SUCCESS) {
|
||||
free (handlers);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
ExceptionHandlerList *list = &reader->function_id_to_exc_handlers[function_id];
|
||||
free (list->handlers);
|
||||
list->handlers = handlers;
|
||||
list->count = count;
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
/* Read and validate the header */
|
||||
Result _hbc_reader_read_header(HBCReader *reader) {
|
||||
if (!reader) {
|
||||
@@ -346,15 +405,7 @@ static Result hbc_reader_read_functions_modern(HBCReader *reader) {
|
||||
header->highestReadCacheIndex = (raw3 >> 8) & 0xFFu;
|
||||
header->highestWriteCacheIndex = (reader->header.version >= 99)? ((raw3 >> 16) & 0x7Fu): ((raw3 >> 16) & 0xFFu);
|
||||
header->environmentSize = (reader->header.version >= 99)? ((raw3 >> 23) & 0x1u): 0;
|
||||
{
|
||||
u8 flags = (raw3 >> 24) & 0xFFu;
|
||||
header->prohibitInvoke = flags & 0x3u;
|
||||
header->strictMode = (flags >> 2) & 0x1u;
|
||||
header->hasExceptionHandler = (flags >> 3) & 0x1u;
|
||||
header->hasDebugInfo = (flags >> 4) & 0x1u;
|
||||
header->overflowed = (flags >> 5) & 0x1u;
|
||||
header->unused = (flags >> 6) & 0x3u;
|
||||
}
|
||||
unpack_function_flags (header, (raw3 >> 24) & 0xFFu);
|
||||
header->infoOffset = 0;
|
||||
|
||||
if (header->overflowed) {
|
||||
@@ -376,12 +427,7 @@ static Result hbc_reader_read_functions_modern(HBCReader *reader) {
|
||||
{
|
||||
u8 flags = 0;
|
||||
RETURN_IF_ERROR (_hbc_buffer_reader_read_u8 (&reader->file_buffer, &flags));
|
||||
header->prohibitInvoke = flags & 0x3u;
|
||||
header->strictMode = (flags >> 2) & 0x1u;
|
||||
header->hasExceptionHandler = (flags >> 3) & 0x1u;
|
||||
header->hasDebugInfo = (flags >> 4) & 0x1u;
|
||||
header->overflowed = (flags >> 5) & 0x1u;
|
||||
header->unused = (flags >> 6) & 0x3u;
|
||||
unpack_function_flags (header, flags);
|
||||
}
|
||||
} else {
|
||||
RETURN_IF_ERROR (_hbc_buffer_reader_read_u32 (&reader->file_buffer, &header->bytecodeSizeInBytes));
|
||||
@@ -392,12 +438,7 @@ static Result hbc_reader_read_functions_modern(HBCReader *reader) {
|
||||
{
|
||||
u8 flags = 0;
|
||||
RETURN_IF_ERROR (_hbc_buffer_reader_read_u8 (&reader->file_buffer, &flags));
|
||||
header->prohibitInvoke = flags & 0x3u;
|
||||
header->strictMode = (flags >> 2) & 0x1u;
|
||||
header->hasExceptionHandler = (flags >> 3) & 0x1u;
|
||||
header->hasDebugInfo = (flags >> 4) & 0x1u;
|
||||
header->overflowed = (flags >> 5) & 0x1u;
|
||||
header->unused = (flags >> 6) & 0x3u;
|
||||
unpack_function_flags (header, flags);
|
||||
}
|
||||
}
|
||||
RETURN_IF_ERROR (_hbc_buffer_reader_seek (&reader->file_buffer, header_pos + 12));
|
||||
@@ -569,13 +610,6 @@ Result _hbc_reader_read_functions(HBCReader *reader) {
|
||||
small_header.highestReadCacheIndex = (u8) ((raw_data[3] >> 8) & 0xFF);
|
||||
small_header.highestWriteCacheIndex = (u8) ((raw_data[3] >> 16) & 0xFF);
|
||||
|
||||
small_header.prohibitInvoke = (raw_data[3] >> 24) & 0x3;
|
||||
small_header.strictMode = (raw_data[3] >> 26) & 0x1;
|
||||
small_header.hasExceptionHandler = (raw_data[3] >> 27) & 0x1;
|
||||
small_header.hasDebugInfo = (raw_data[3] >> 28) & 0x1;
|
||||
small_header.overflowed = (raw_data[3] >> 29) & 0x1;
|
||||
small_header.unused = (raw_data[3] >> 30) & 0x3;
|
||||
|
||||
/* Store the function header */
|
||||
FunctionHeader *header = &reader->function_headers[i];
|
||||
header->offset = small_header.offset;
|
||||
@@ -587,18 +621,13 @@ Result _hbc_reader_read_functions(HBCReader *reader) {
|
||||
header->environmentSize = small_header.environmentSize;
|
||||
header->highestReadCacheIndex = small_header.highestReadCacheIndex;
|
||||
header->highestWriteCacheIndex = small_header.highestWriteCacheIndex;
|
||||
header->prohibitInvoke = small_header.prohibitInvoke;
|
||||
header->strictMode = small_header.strictMode;
|
||||
header->hasExceptionHandler = small_header.hasExceptionHandler;
|
||||
header->hasDebugInfo = small_header.hasDebugInfo;
|
||||
header->overflowed = small_header.overflowed;
|
||||
header->unused = small_header.unused;
|
||||
unpack_function_flags (header, (u8) (raw_data[3] >> 24));
|
||||
|
||||
/* Save current position */
|
||||
size_t current_pos = reader->file_buffer.position;
|
||||
|
||||
/* Handle overflowed header */
|
||||
if (small_header.overflowed) {
|
||||
if (header->overflowed) {
|
||||
/* Calculate the absolute offset of the large header */
|
||||
u32 large_header_offset = (small_header.infoOffset << 16) | small_header.offset;
|
||||
|
||||
@@ -625,13 +654,6 @@ Result _hbc_reader_read_functions(HBCReader *reader) {
|
||||
u8 flags;
|
||||
RETURN_IF_ERROR (_hbc_buffer_reader_read_u8 (&reader->file_buffer, &flags));
|
||||
|
||||
large_header.prohibitInvoke = flags & 0x3;
|
||||
large_header.strictMode = (flags >> 2) & 0x1;
|
||||
large_header.hasExceptionHandler = (flags >> 3) & 0x1;
|
||||
large_header.hasDebugInfo = (flags >> 4) & 0x1;
|
||||
large_header.overflowed = (flags >> 5) & 0x1;
|
||||
large_header.unused = (flags >> 6) & 0x3;
|
||||
|
||||
/* Copy to the combined header */
|
||||
header->offset = large_header.offset;
|
||||
header->paramCount = large_header.paramCount;
|
||||
@@ -642,12 +664,7 @@ Result _hbc_reader_read_functions(HBCReader *reader) {
|
||||
header->environmentSize = (u8)large_header.environmentSize; /* Cast from u32 to u8 */
|
||||
header->highestReadCacheIndex = large_header.highestReadCacheIndex;
|
||||
header->highestWriteCacheIndex = large_header.highestWriteCacheIndex;
|
||||
header->prohibitInvoke = large_header.prohibitInvoke;
|
||||
header->strictMode = large_header.strictMode;
|
||||
header->hasExceptionHandler = large_header.hasExceptionHandler;
|
||||
header->hasDebugInfo = large_header.hasDebugInfo;
|
||||
header->overflowed = large_header.overflowed;
|
||||
header->unused = large_header.unused;
|
||||
unpack_function_flags (header, flags);
|
||||
}
|
||||
|
||||
/* Read the function bytecode */
|
||||
@@ -668,34 +685,8 @@ Result _hbc_reader_read_functions(HBCReader *reader) {
|
||||
/* Store the bytecode pointer */
|
||||
header->bytecode = bytecode;
|
||||
|
||||
/* Read exception handlers if present */
|
||||
if (header->hasExceptionHandler) {
|
||||
/* Seek to the exception handler info */
|
||||
RETURN_IF_ERROR (_hbc_buffer_reader_seek (&reader->file_buffer, header->infoOffset));
|
||||
|
||||
/* Align buffer */
|
||||
RETURN_IF_ERROR (align_over_padding (&reader->file_buffer, 4));
|
||||
|
||||
/* Read the number of exception handlers */
|
||||
u32 exc_handler_count;
|
||||
RETURN_IF_ERROR (_hbc_buffer_reader_read_u32 (&reader->file_buffer, &exc_handler_count));
|
||||
|
||||
/* Allocate exception handlers */
|
||||
ExceptionHandlerInfo *handlers = (ExceptionHandlerInfo *)malloc (exc_handler_count * sizeof (ExceptionHandlerInfo));
|
||||
if (!handlers) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "Failed to allocate exception handlers");
|
||||
}
|
||||
|
||||
/* Read each exception handler */
|
||||
for (u32 j = 0; j < exc_handler_count; j++) {
|
||||
RETURN_IF_ERROR (_hbc_buffer_reader_read_u32 (&reader->file_buffer, &handlers[j].start));
|
||||
RETURN_IF_ERROR (_hbc_buffer_reader_read_u32 (&reader->file_buffer, &handlers[j].end));
|
||||
RETURN_IF_ERROR (_hbc_buffer_reader_read_u32 (&reader->file_buffer, &handlers[j].target));
|
||||
}
|
||||
|
||||
/* Store the exception handlers */
|
||||
reader->function_id_to_exc_handlers[i].handlers = handlers;
|
||||
reader->function_id_to_exc_handlers[i].count = exc_handler_count;
|
||||
RETURN_IF_ERROR (read_exception_handlers (reader, i, header->infoOffset));
|
||||
}
|
||||
|
||||
/* Read debug information if present */
|
||||
@@ -984,12 +975,9 @@ Result _hbc_reader_read_string_tables(HBCReader *reader) {
|
||||
is_utf16 = reader->small_string_table[i].isUTF16;
|
||||
}
|
||||
|
||||
size_t decoded_length = (size_t)length;
|
||||
size_t available = (size_t)offset > string_storage_size? 0: string_storage_size - (size_t)offset;
|
||||
if ((size_t)offset > string_storage_size ||
|
||||
decoded_length == SIZE_MAX ||
|
||||
(is_utf16 && decoded_length > available / 2) ||
|
||||
(!is_utf16 && decoded_length > available)) {
|
||||
/* Overflow-safe bounds check via subtraction */
|
||||
const u32 bpc = is_utf16? 2: 1;
|
||||
if (offset > string_storage_size || length > (string_storage_size - offset) / bpc) {
|
||||
fprintf (stderr, "Warning: String %u offset/length out of bounds, treating as empty string\n", i);
|
||||
reader->strings[i] = strdup ("");
|
||||
if (!reader->strings[i]) {
|
||||
@@ -1000,9 +988,7 @@ Result _hbc_reader_read_string_tables(HBCReader *reader) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Allocate string buffer (+1 for null terminator) */
|
||||
size_t buffer_size = decoded_length + 1;
|
||||
char *str = (char *)malloc (buffer_size);
|
||||
char *str = (char *)malloc (length * bpc + 1);
|
||||
if (!str) {
|
||||
free (string_storage);
|
||||
reader->string_table_storage = NULL;
|
||||
@@ -1082,6 +1068,7 @@ Result _hbc_reader_read_arrays(HBCReader *reader) {
|
||||
|
||||
/* Read array buffer */
|
||||
if (reader->header.arrayBufferSize > 0) {
|
||||
reader->arrays_paddr = (u32)reader->file_buffer.position;
|
||||
reader->arrays = (u8 *)malloc (reader->header.arrayBufferSize);
|
||||
if (!reader->arrays) {
|
||||
hbc_debug_printf ("Failed to allocate %u bytes for array buffer\n", reader->header.arrayBufferSize);
|
||||
@@ -1113,6 +1100,7 @@ Result _hbc_reader_read_arrays(HBCReader *reader) {
|
||||
reader->header.objKeyBufferSize = 0;
|
||||
} else {
|
||||
/* Safe to read object keys */
|
||||
reader->object_keys_paddr = (u32)reader->file_buffer.position;
|
||||
reader->object_keys = (u8 *)malloc (reader->header.objKeyBufferSize);
|
||||
if (!reader->object_keys) {
|
||||
fprintf (stderr, "Failed to allocate %u bytes for object key buffer\n", reader->header.objKeyBufferSize);
|
||||
@@ -1154,6 +1142,7 @@ Result _hbc_reader_read_arrays(HBCReader *reader) {
|
||||
}
|
||||
reader->literal_values = reader->arrays;
|
||||
reader->object_values = reader->arrays;
|
||||
reader->object_values_paddr = reader->arrays_paddr;
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
@@ -1169,6 +1158,7 @@ Result _hbc_reader_read_arrays(HBCReader *reader) {
|
||||
reader->header.objValueBufferSize = 0;
|
||||
} else {
|
||||
/* Safe to read object values */
|
||||
reader->object_values_paddr = (u32)reader->file_buffer.position;
|
||||
reader->object_values = (u8 *)malloc (reader->header.objValueBufferSize);
|
||||
if (!reader->object_values) {
|
||||
fprintf (stderr, "Failed to allocate %u bytes for object value buffer\n", reader->header.objValueBufferSize);
|
||||
@@ -1339,10 +1329,8 @@ Result _hbc_reader_read_bigints(HBCReader *reader) {
|
||||
u32 offset = bigint_table[i].offset;
|
||||
u32 length = bigint_table[i].length;
|
||||
|
||||
/* Check bounds */
|
||||
size_t bigint_available = (size_t)offset > reader->header.bigIntStorageSize? 0:
|
||||
(size_t)reader->header.bigIntStorageSize - (size_t)offset;
|
||||
if ((size_t)offset > reader->header.bigIntStorageSize || (size_t)length > bigint_available) {
|
||||
/* Overflow-safe bounds check via subtraction */
|
||||
if (offset > reader->header.bigIntStorageSize || length > reader->header.bigIntStorageSize - offset) {
|
||||
fprintf (stderr, "Warning: BigInt %u has invalid offset/length (%u+%u exceeds %u)\n", i, offset, length, reader->header.bigIntStorageSize);
|
||||
/* Use zero for this value */
|
||||
reader->bigint_values[i] = 0;
|
||||
@@ -1581,22 +1569,31 @@ Result _hbc_reader_read_function_sources(HBCReader *reader) {
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
/* Read debug information */
|
||||
Result _hbc_reader_read_debug_info(HBCReader *reader) {
|
||||
/* Size of the on-disk DebugInfoHeader block, in bytes. */
|
||||
static u32 debug_info_header_bytes(u32 version) {
|
||||
/* v<91: 5 u32s (filename_count, filename_storage_size, file_region_count,
|
||||
* scope_desc_data_offset, debug_data_size).
|
||||
* v>=91: 7 u32s (adds textified_data_offset, string_table_offset). */
|
||||
return (version >= 91? 7u: 5u) * sizeof (u32);
|
||||
}
|
||||
|
||||
/* Read just the DebugInfoHeader fields (no storage blobs, no allocations).
|
||||
* Cheap enough to run eagerly at load time so "has source lines" is knowable
|
||||
* without committing to a full debug-info parse. Idempotent. */
|
||||
Result _hbc_reader_read_debug_info_header(HBCReader *reader) {
|
||||
if (!reader) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Reader is NULL");
|
||||
}
|
||||
if (reader->header.debugInfoOffset == 0) {
|
||||
if (reader->debug_info_header_loaded) {
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
if (reader->header.version >= 97) {
|
||||
if (reader->header.debugInfoOffset == 0) {
|
||||
reader->debug_info_header_loaded = true;
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
/* Seek to debug info offset */
|
||||
RETURN_IF_ERROR (_hbc_buffer_reader_seek (&reader->file_buffer, reader->header.debugInfoOffset));
|
||||
|
||||
/* Read debug info header */
|
||||
RETURN_IF_ERROR (_hbc_buffer_reader_read_u32 (&reader->file_buffer, &reader->debug_info_header.filename_count));
|
||||
RETURN_IF_ERROR (_hbc_buffer_reader_read_u32 (&reader->file_buffer, &reader->debug_info_header.filename_storage_size));
|
||||
RETURN_IF_ERROR (_hbc_buffer_reader_read_u32 (&reader->file_buffer, &reader->debug_info_header.file_region_count));
|
||||
@@ -1609,6 +1606,30 @@ Result _hbc_reader_read_debug_info(HBCReader *reader) {
|
||||
|
||||
RETURN_IF_ERROR (_hbc_buffer_reader_read_u32 (&reader->file_buffer, &reader->debug_info_header.debug_data_size));
|
||||
|
||||
reader->debug_info_header_loaded = true;
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
/* Read the full debug-info body (filenames, file regions, sources/scope/
|
||||
* textified/string-table storage blobs). Builds on the cheap header parse.
|
||||
* Idempotent: subsequent calls after a successful load are no-ops. */
|
||||
Result _hbc_reader_read_debug_info(HBCReader *reader) {
|
||||
if (!reader) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Reader is NULL");
|
||||
}
|
||||
if (reader->debug_info_loaded) {
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
RETURN_IF_ERROR (_hbc_reader_read_debug_info_header (reader));
|
||||
if (reader->header.debugInfoOffset == 0) {
|
||||
reader->debug_info_loaded = true;
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
/* Seek past the already-parsed header to the start of the body. */
|
||||
RETURN_IF_ERROR (_hbc_buffer_reader_seek (&reader->file_buffer,
|
||||
reader->header.debugInfoOffset + debug_info_header_bytes (reader->header.version)));
|
||||
|
||||
/* Allocate debug string table */
|
||||
if (reader->debug_info_header.filename_count > 0) {
|
||||
reader->debug_string_table = (OffsetLengthPair *)malloc (
|
||||
@@ -1710,6 +1731,7 @@ Result _hbc_reader_read_debug_info(HBCReader *reader) {
|
||||
reader->string_table_storage_size = string_table_size;
|
||||
}
|
||||
|
||||
reader->debug_info_loaded = true;
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
@@ -1823,12 +1845,6 @@ Result _hbc_reader_read_functions_robust(HBCReader *reader) {
|
||||
const u8 small_environmentSize = (u8) (raw_data[3] & 0xFF);
|
||||
const u8 small_highestReadCacheIndex = (u8) ((raw_data[3] >> 8) & 0xFF);
|
||||
const u8 small_highestWriteCacheIndex = (u8) ((raw_data[3] >> 16) & 0xFF);
|
||||
const u8 small_prohibitInvoke = (raw_data[3] >> 24) & 0x3;
|
||||
const u8 small_strictMode = (raw_data[3] >> 26) & 0x1;
|
||||
const u8 small_hasExceptionHandler = (raw_data[3] >> 27) & 0x1;
|
||||
const u8 small_hasDebugInfo = (raw_data[3] >> 28) & 0x1;
|
||||
const u8 small_overflowed = (raw_data[3] >> 29) & 0x1;
|
||||
const u8 small_unused = (raw_data[3] >> 30) & 0x3;
|
||||
|
||||
/* Initialize header with small header values */
|
||||
header->offset = small_offset;
|
||||
@@ -1840,15 +1856,10 @@ Result _hbc_reader_read_functions_robust(HBCReader *reader) {
|
||||
header->environmentSize = small_environmentSize;
|
||||
header->highestReadCacheIndex = small_highestReadCacheIndex;
|
||||
header->highestWriteCacheIndex = small_highestWriteCacheIndex;
|
||||
header->prohibitInvoke = small_prohibitInvoke;
|
||||
header->strictMode = small_strictMode;
|
||||
header->hasExceptionHandler = small_hasExceptionHandler;
|
||||
header->hasDebugInfo = small_hasDebugInfo;
|
||||
header->overflowed = small_overflowed;
|
||||
header->unused = small_unused;
|
||||
unpack_function_flags (header, (u8) (raw_data[3] >> 24));
|
||||
|
||||
/* Try to read large header if needed and override values */
|
||||
if (small_overflowed || small_bytecodeSizeInBytes == 0) {
|
||||
if (header->overflowed || small_bytecodeSizeInBytes == 0) {
|
||||
const u32 large_header_offset = (small_infoOffset << 16) | small_offset;
|
||||
const Result sr = _hbc_buffer_reader_seek (&reader->file_buffer, large_header_offset);
|
||||
|
||||
@@ -1877,12 +1888,7 @@ Result _hbc_reader_read_functions_robust(HBCReader *reader) {
|
||||
header->environmentSize = (u8)large_header.environmentSize;
|
||||
header->highestReadCacheIndex = large_header.highestReadCacheIndex;
|
||||
header->highestWriteCacheIndex = large_header.highestWriteCacheIndex;
|
||||
header->prohibitInvoke = flags & 0x3;
|
||||
header->strictMode = (flags >> 2) & 0x1;
|
||||
header->hasExceptionHandler = (flags >> 3) & 0x1;
|
||||
header->hasDebugInfo = (flags >> 4) & 0x1;
|
||||
header->overflowed = (flags >> 5) & 0x1;
|
||||
header->unused = (flags >> 6) & 0x3;
|
||||
unpack_function_flags (header, flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1914,6 +1920,13 @@ Result _hbc_reader_read_functions_robust(HBCReader *reader) {
|
||||
/* For robustness, we don't load bytecode here */
|
||||
header->bytecode = NULL;
|
||||
|
||||
if (header->hasExceptionHandler) {
|
||||
Result eh_res = read_exception_handlers (reader, i, header->infoOffset);
|
||||
if (eh_res.code != RESULT_SUCCESS) {
|
||||
hbc_debug_printf ("Warning: Skipping exception handlers for function %u: %s\n", i, eh_res.error_message);
|
||||
}
|
||||
}
|
||||
|
||||
successful_functions++;
|
||||
|
||||
/* Ensure position is at next small header (16 bytes per entry) */
|
||||
@@ -1952,7 +1965,9 @@ Result _hbc_reader_read_whole_file(HBCReader *reader, const char *filename) {
|
||||
/* Read header */
|
||||
result = _hbc_reader_read_header (reader);
|
||||
if (result.code != RESULT_SUCCESS) {
|
||||
#if 0
|
||||
fprintf (stderr, "Error reading header: %s\n", result.error_message);
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -2032,10 +2047,11 @@ Result _hbc_reader_read_whole_file(HBCReader *reader, const char *filename) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Read debug info */
|
||||
result = _hbc_reader_read_debug_info (reader);
|
||||
/* Read only the DebugInfoHeader eagerly (cheap). The body is parsed lazily
|
||||
* via _hbc_reader_read_debug_info () on first API use. */
|
||||
result = _hbc_reader_read_debug_info_header (reader);
|
||||
if (result.code != RESULT_SUCCESS) {
|
||||
fprintf (stderr, "Error reading debug info: %s\n", result.error_message);
|
||||
fprintf (stderr, "Error reading debug info header: %s\n", result.error_message);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
+26
-68
@@ -1,7 +1,18 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/hbc.h>
|
||||
#include <hbc/parser.h>
|
||||
#include <ctype.h>
|
||||
|
||||
static void sanitize_identifier(const char *src, char *dst, size_t dst_size) {
|
||||
size_t out = 0;
|
||||
for (size_t j = 0; src[j] && out + 1 < dst_size; j++) {
|
||||
char c = src[j];
|
||||
dst[out++] = (isalnum ((unsigned char)c) || c == '_')? c: '_';
|
||||
}
|
||||
dst[out] = '\0';
|
||||
}
|
||||
|
||||
/* Generate r2 script with function flags - robust version inspired by the Python implementation */
|
||||
Result _hbc_generate_r2_script(const char *input_file, const char *output_file) {
|
||||
if (!input_file) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Input file is NULL");
|
||||
@@ -209,21 +220,7 @@ Result _hbc_generate_r2_script(const char *input_file, const char *output_file)
|
||||
|
||||
/* Generate sanitized name for r2 */
|
||||
char sanitized_name[256] = { 0 };
|
||||
size_t name_len = strlen (function_name);
|
||||
size_t sanitized_idx = 0;
|
||||
|
||||
for (size_t j = 0; j < name_len && sanitized_idx < sizeof (sanitized_name) - 1; j++) {
|
||||
char c = function_name[j];
|
||||
if ((c >= 'a' && c <= 'z') ||
|
||||
(c >= 'A' && c <= 'Z') ||
|
||||
(c >= '0' && c <= '9') ||
|
||||
c == '_') {
|
||||
sanitized_name[sanitized_idx++] = c;
|
||||
} else {
|
||||
sanitized_name[sanitized_idx++] = '_';
|
||||
}
|
||||
}
|
||||
sanitized_name[sanitized_idx] = '\0';
|
||||
sanitize_identifier (function_name, sanitized_name, sizeof (sanitized_name));
|
||||
|
||||
/* If sanitized name is empty, use a default */
|
||||
if (sanitized_name[0] == '\0') {
|
||||
@@ -363,11 +360,9 @@ Result _hbc_generate_r2_script(const char *input_file, const char *output_file)
|
||||
u32 length = string_infos[i].length;
|
||||
bool isUTF16 = string_infos[i].isUTF16;
|
||||
|
||||
/* Skip strings with obviously invalid offsets/lengths */
|
||||
size_t available = (size_t)offset > string_storage_size? 0: string_storage_size - (size_t)offset;
|
||||
if ((size_t)offset > string_storage_size ||
|
||||
(isUTF16 && (size_t)length > available / 2) ||
|
||||
(!isUTF16 && (size_t)length > available)) {
|
||||
/* Overflow-safe bounds check via subtraction */
|
||||
const u32 bpc = isUTF16? 2: 1;
|
||||
if (offset > string_storage_size || length > (string_storage_size - offset) / bpc) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -377,65 +372,28 @@ Result _hbc_generate_r2_script(const char *input_file, const char *output_file)
|
||||
display_length = 32;
|
||||
}
|
||||
|
||||
/* Generate a string value for the flag name */
|
||||
/* Generate a string value for the flag name: decode each char (1 or
|
||||
* 2 bytes, LE) and keep only ASCII-printable bytes, else '_'. */
|
||||
char str_value[64] = { 0 };
|
||||
if (isUTF16) {
|
||||
/* UTF-16 string - simplified handling */
|
||||
u32 chars_added = 0;
|
||||
for (u32 j = 0; j < display_length && chars_added < sizeof (str_value) - 1; j++) {
|
||||
/* Read 16-bit character */
|
||||
u16 c = (u16) (string_storage[offset + (j * 2)] | (string_storage[offset + (j * 2) + 1] << 8));
|
||||
|
||||
/* Only add ASCII-printable characters to name */
|
||||
if (c >= 32 && c < 127) {
|
||||
str_value[chars_added++] = (char)c;
|
||||
} else {
|
||||
str_value[chars_added++] = '_';
|
||||
}
|
||||
}
|
||||
str_value[chars_added] = '\0';
|
||||
} else {
|
||||
/* ASCII string */
|
||||
u32 chars_added = 0;
|
||||
for (u32 j = 0; j < display_length && chars_added < sizeof (str_value) - 1; j++) {
|
||||
char c = (char)string_storage[offset + j];
|
||||
|
||||
/* Only add printable characters to name */
|
||||
if (c >= 32 && c < 127) {
|
||||
str_value[chars_added++] = c;
|
||||
} else {
|
||||
str_value[chars_added++] = '_';
|
||||
}
|
||||
}
|
||||
str_value[chars_added] = '\0';
|
||||
u32 chars_added = 0;
|
||||
for (u32 j = 0; j < display_length && chars_added < sizeof (str_value) - 1; j++) {
|
||||
const u8 *p = string_storage + offset + (j * bpc);
|
||||
u32 c = isUTF16? ((u32)p[0] | ((u32)p[1] << 8)): p[0];
|
||||
str_value[chars_added++] = (c >= 32 && c < 127)? (char)c: '_';
|
||||
}
|
||||
str_value[chars_added] = '\0';
|
||||
|
||||
/* Generate sanitized name for r2 - only include alphanumeric and underscores */
|
||||
char sanitized_name[64] = { 0 };
|
||||
size_t name_len = strlen (str_value);
|
||||
size_t sanitized_idx = 0;
|
||||
|
||||
for (size_t j = 0; j < name_len && sanitized_idx < sizeof (sanitized_name) - 1; j++) {
|
||||
char c = str_value[j];
|
||||
if ((c >= 'a' && c <= 'z') ||
|
||||
(c >= 'A' && c <= 'Z') ||
|
||||
(c >= '0' && c <= '9') ||
|
||||
c == '_') {
|
||||
sanitized_name[sanitized_idx++] = c;
|
||||
} else {
|
||||
sanitized_name[sanitized_idx++] = '_';
|
||||
}
|
||||
}
|
||||
sanitized_name[sanitized_idx] = '\0';
|
||||
sanitize_identifier (str_value, sanitized_name, sizeof (sanitized_name));
|
||||
|
||||
/* Add index to name to ensure uniqueness */
|
||||
char unique_name[96];
|
||||
snprintf (unique_name, sizeof (unique_name), "%s_%u", sanitized_name[0]? sanitized_name: "str", i);
|
||||
|
||||
/* Write the string flag - offset is relative to string storage base */
|
||||
size_t size = isUTF16? (size_t)length * 2: (size_t)length;
|
||||
uint32_t addr = (unsigned long) (reader.file_buffer.position - string_storage_size + offset);
|
||||
fprintf (out, "'f str.%s %zu 0x%08x\n", unique_name, size, addr);
|
||||
fprintf (out, "'f str.%s %u 0x%08x\n", unique_name, length * bpc, addr);
|
||||
#if 0
|
||||
/* Add flag for string length */
|
||||
if (isUTF16) {
|
||||
|
||||
+37
-117
@@ -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 ();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,30 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/common.h>
|
||||
|
||||
/* Grow the buffer (doubling) so it can hold at least required_capacity bytes. */
|
||||
static Result string_buffer_ensure(StringBuffer *buffer, size_t required_capacity) {
|
||||
if (required_capacity <= buffer->capacity) {
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
size_t new_capacity = buffer->capacity? buffer->capacity: 64;
|
||||
while (new_capacity < required_capacity) {
|
||||
size_t doubled = new_capacity * 2;
|
||||
if (doubled <= new_capacity) { /* overflow: just take what's needed */
|
||||
new_capacity = required_capacity;
|
||||
break;
|
||||
}
|
||||
new_capacity = doubled;
|
||||
}
|
||||
char *new_data = (char *)realloc (buffer->data, new_capacity);
|
||||
if (!new_data) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "Failed to resize string buffer");
|
||||
}
|
||||
buffer->data = new_data;
|
||||
buffer->capacity = new_capacity;
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
Result _hbc_string_buffer_init(StringBuffer *buffer, size_t initial_capacity) {
|
||||
if (!buffer) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Buffer is NULL");
|
||||
@@ -32,21 +57,7 @@ Result _hbc_string_buffer_append(StringBuffer *buffer, const char *str) {
|
||||
}
|
||||
|
||||
/* Ensure enough capacity */
|
||||
size_t required_capacity = buffer->length + str_len + 1;
|
||||
if (required_capacity > buffer->capacity) {
|
||||
size_t new_capacity = buffer->capacity * 2;
|
||||
while (new_capacity < required_capacity) {
|
||||
new_capacity *= 2;
|
||||
}
|
||||
|
||||
char *new_data = (char *)realloc (buffer->data, new_capacity);
|
||||
if (!new_data) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "Failed to resize string buffer");
|
||||
}
|
||||
|
||||
buffer->data = new_data;
|
||||
buffer->capacity = new_capacity;
|
||||
}
|
||||
RETURN_IF_ERROR (string_buffer_ensure (buffer, buffer->length + str_len + 1));
|
||||
|
||||
/* Append the string */
|
||||
memcpy (buffer->data + buffer->length, str, str_len);
|
||||
@@ -61,19 +72,8 @@ Result _hbc_string_buffer_append_char(StringBuffer *buffer, char c) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Buffer is NULL");
|
||||
}
|
||||
|
||||
/* Ensure enough capacity */
|
||||
size_t required_capacity = buffer->length + 2; /* +1 for the char, +1 for null terminator */
|
||||
if (required_capacity > buffer->capacity) {
|
||||
size_t new_capacity = buffer->capacity * 2;
|
||||
|
||||
char *new_data = (char *)realloc (buffer->data, new_capacity);
|
||||
if (!new_data) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "Failed to resize string buffer");
|
||||
}
|
||||
|
||||
buffer->data = new_data;
|
||||
buffer->capacity = new_capacity;
|
||||
}
|
||||
/* Ensure enough capacity (+1 for the char, +1 for null terminator) */
|
||||
RETURN_IF_ERROR (string_buffer_ensure (buffer, buffer->length + 2));
|
||||
|
||||
/* Append the character */
|
||||
buffer->data[buffer->length] = c;
|
||||
|
||||
+6
-5
@@ -33,6 +33,7 @@ BIN_SRC = bin_hbc.c
|
||||
ASM_SRC = asm_hbc.c
|
||||
CORE_SRC = core_hbc.c
|
||||
CORE_ONE_SRC = core_hbc_one.c
|
||||
HBC_HEADERS = $(shell find ../../include -name '*.h') utils.inc.c sbom.inc.c
|
||||
|
||||
# Object files
|
||||
ARCH_OBJ = arch_hbc.o
|
||||
@@ -107,19 +108,19 @@ $(CORE_ONE_PLUGIN): $(CORE_ONE_OBJ) $(HERMES_LIB)
|
||||
$(CC) $(LDFLAGS) -o $@ $(CORE_ONE_OBJ) $(HERMES_LIB)
|
||||
|
||||
# Compile object files
|
||||
$(ARCH_OBJ): $(ARCH_SRC)
|
||||
$(ARCH_OBJ): $(ARCH_SRC) $(HBC_HEADERS)
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BIN_OBJ): $(BIN_SRC)
|
||||
$(BIN_OBJ): $(BIN_SRC) $(HBC_HEADERS)
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(ASM_OBJ): $(ASM_SRC)
|
||||
$(ASM_OBJ): $(ASM_SRC) $(HBC_HEADERS)
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(CORE_OBJ): $(CORE_SRC)
|
||||
$(CORE_OBJ): $(CORE_SRC) $(HBC_HEADERS)
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(CORE_ONE_OBJ): $(CORE_ONE_SRC)
|
||||
$(CORE_ONE_OBJ): $(CORE_ONE_SRC) $(HBC_HEADERS)
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
|
||||
|
||||
+52
-26
@@ -1,18 +1,20 @@
|
||||
/* radare2 - LGPL - Copyright 2025-2026 - pancake */
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <r_anal.h>
|
||||
#include <hbc/hbc.h>
|
||||
#include <hbc/literals.h>
|
||||
#include <hbc/opcodes.h>
|
||||
#include <hbc/bytecode.h>
|
||||
#include <hbc/parser.h>
|
||||
|
||||
#define MAX_OP_SIZE 16
|
||||
#define MAX_OP_SIZE 32
|
||||
#define HBC_VADDR_BASE 0x10000000
|
||||
#ifndef R_ARCH_SYNTAX_CAMEL
|
||||
#define R_ARCH_SYNTAX_CAMEL 6
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
ut32 bytecode_version; /* cached from RBinInfo->cpu if available */
|
||||
ut32 bytecode_version; /* cached effective HBC bytecode version */
|
||||
HBC *hbc; /* Hermes state for direct access */
|
||||
u32 string_count;
|
||||
const void *small_string_table;
|
||||
@@ -20,25 +22,41 @@ typedef struct {
|
||||
u64 string_storage_offset;
|
||||
} HermesArchSession;
|
||||
|
||||
static ut32 parse_cpu_version(const char *cpu) {
|
||||
if (!cpu || !*cpu) {
|
||||
return 0;
|
||||
}
|
||||
const char *p = cpu;
|
||||
if (p[0] != 'v') {
|
||||
return 0;
|
||||
}
|
||||
unsigned long v = strtoul (p + 1, NULL, 10);
|
||||
return v > 0 && v <= UT32_MAX? (ut32)v: 0;
|
||||
}
|
||||
|
||||
static ut32 detect_version_from_bin(RArchSession *s) {
|
||||
if (!s || !s->arch || !s->arch->binb.bin) {
|
||||
return 96; /* sane default */
|
||||
return 0;
|
||||
}
|
||||
RBin *bin = s->arch->binb.bin;
|
||||
RBinInfo *bi = r_bin_get_info (bin);
|
||||
if (bi && bi->cpu && *bi->cpu) {
|
||||
const char *p = bi->cpu;
|
||||
/* Accept optional leading 'v' or 'V' (e.g., "v76") */
|
||||
if (p[0] == 'v' || p[0] == 'V') {
|
||||
p++;
|
||||
}
|
||||
/* cpu holds the version string set by bin plugin */
|
||||
ut32 v = (ut32)strtoul (p, NULL, 10);
|
||||
ut32 v = parse_cpu_version (bi->cpu);
|
||||
if (v > 0) {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
return 96;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ut32 detect_version(RArchSession *s) {
|
||||
ut32 v = parse_cpu_version (s && s->config? s->config->cpu: NULL);
|
||||
if (v > 0) {
|
||||
return v;
|
||||
}
|
||||
v = detect_version_from_bin (s);
|
||||
return v > 0? v: 96; /* sane default */
|
||||
}
|
||||
|
||||
#include "esil.inc.c"
|
||||
@@ -169,7 +187,12 @@ static void parse_operands_and_set_ptr(RAnalOp *op, const ut8 *bytes, ut32 size,
|
||||
HBCStringMeta meta;
|
||||
Result meta_result = hbc_get_string_meta (hs->hbc, string_id, &meta);
|
||||
if (meta_result.code == RESULT_SUCCESS) {
|
||||
op->ptr = (st64)meta.offset;
|
||||
op->ptr = (st64) (HBC_VADDR_BASE + meta.offset);
|
||||
/* Expose the string byte length (size of the pointed
|
||||
* contents) so the disassembler caps r2's string read and
|
||||
* picks the right flag among overlapping, non-null-terminated
|
||||
* Hermes strings that share this storage offset. */
|
||||
op->ptrsize = meta.isUTF16? (int) (meta.length * 2): (int)meta.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -185,7 +208,7 @@ static void parse_operands_and_set_ptr(RAnalOp *op, const ut8 *bytes, ut32 size,
|
||||
offset = fi.offset;
|
||||
(void)fi.size;
|
||||
(void)fi.param_count;
|
||||
op->ptr = (st64)offset;
|
||||
op->ptr = (st64) (HBC_VADDR_BASE + offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -205,13 +228,12 @@ static bool decode(RArchSession *s, RAnalOp *op, RArchDecodeMask mask) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!hs->bytecode_version) {
|
||||
hs->bytecode_version = detect_version_from_bin (s);
|
||||
}
|
||||
hs->bytecode_version = detect_version (s);
|
||||
|
||||
/* Load string tables if not already loaded */
|
||||
if (!hs->hbc) {
|
||||
load_string_tables (hs, s);
|
||||
hs->bytecode_version = detect_version (s);
|
||||
}
|
||||
|
||||
/* Build decode context */
|
||||
@@ -231,8 +253,12 @@ static bool decode(RArchSession *s, RAnalOp *op, RArchDecodeMask mask) {
|
||||
.resolve_string_ids = true,
|
||||
.string_tables = &string_tables,
|
||||
.hbc = hs->hbc,
|
||||
};
|
||||
if (mask & R_ARCH_OP_MASK_ESIL && mask & R_ARCH_OP_MASK_DISASM) {
|
||||
};
|
||||
/* Inlining of buffer-literal contents into the disasm line is opt-in —
|
||||
* it makes arch.decode O (n) in literal size and breaks r2's O(1) per-op
|
||||
* disasm assumption. Toggle via `e r2hermes.inline_buffer_literals=true`
|
||||
*(core plugin updates a process-wide flag). */
|
||||
if ((mask & R_ARCH_OP_MASK_DISASM) && hbc_get_inline_literals ()) {
|
||||
ctx.build_objects = true;
|
||||
}
|
||||
|
||||
@@ -527,7 +553,10 @@ static int archinfo(RArchSession *s, ut32 q) {
|
||||
|
||||
static char *mnemonics(RArchSession *s, int id, bool json) {
|
||||
HermesArchSession *hs = s? (HermesArchSession *)s->data: NULL;
|
||||
ut32 version = (hs && hs->bytecode_version)? hs->bytecode_version: 96;
|
||||
ut32 version = detect_version (s);
|
||||
if (hs) {
|
||||
hs->bytecode_version = version;
|
||||
}
|
||||
HBCISA isa = hbc_isa_getv (version);
|
||||
if (!isa.instructions || isa.count == 0) {
|
||||
return NULL;
|
||||
@@ -565,12 +594,7 @@ static bool encode(RArchSession *s, RAnalOp *op, RArchEncodeMask mask) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!hs->bytecode_version) {
|
||||
hs->bytecode_version = detect_version_from_bin (s);
|
||||
if (!hs->bytecode_version) {
|
||||
hs->bytecode_version = 96;
|
||||
}
|
||||
}
|
||||
hs->bytecode_version = detect_version (s);
|
||||
|
||||
const char *asm_line = op->mnemonic;
|
||||
|
||||
@@ -661,7 +685,9 @@ const RArchPlugin r_arch_plugin_r2hermes = {
|
||||
},
|
||||
.arch = "hbc",
|
||||
.bits = R_SYS_BITS_PACK1 (64),
|
||||
.cpus = "v76,v90,v91,v92,v93,v94,v95,v96,v97,v98,v99",
|
||||
.cpus = "v51,v58,v59,v61,v62,v68,v69,v70,v72,v73,v76,"
|
||||
"v80,v81,v82,v83,v84,v85,v86,v87,v88,v89,"
|
||||
"v90,v91,v92,v93,v94,v95,v96,v97,v98,v99",
|
||||
.decode = &decode,
|
||||
.encode = &encode,
|
||||
.regs = regs,
|
||||
|
||||
+171
-171
@@ -1,210 +1,210 @@
|
||||
/* radare2 - LGPL - Copyright 2025-2026 - pancake */
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <r_asm.h>
|
||||
|
||||
/* Pseudo transformation rules for Hermes bytecode instructions */
|
||||
static const char *pseudo_rules[] = {
|
||||
/* Mov/Load operations */
|
||||
"mov/2/$1 = $2",
|
||||
"mov_long/2/$1 = $2",
|
||||
"loadi8/2/$1 = (int8)$2",
|
||||
"loadu8/2/$1 = (uint8)$2",
|
||||
"loadi16/2/$1 = (int16)$2",
|
||||
"loadu16/2/$1 = (uint16)$2",
|
||||
"loadi32/2/$1 = (int32)$2",
|
||||
"loadu32/2/$1 = (uint32)$2",
|
||||
"load_const_uint8/2/$1 = $2",
|
||||
"load_const_u_int8/2/$1 = $2",
|
||||
"load_const_int/2/$1 = $2",
|
||||
"load_const_double/2/$1 = $2",
|
||||
"load_const_bigint/2/$1 = $2",
|
||||
"load_const_string/2/$1 = $2",
|
||||
"load_const_string_long_index/2/$1 = $2",
|
||||
"load_const_empty/1/$1 = empty",
|
||||
"load_const_undefined/1/$1 = undefined",
|
||||
"load_const_null/1/$1 = null",
|
||||
"load_const_true/1/$1 = true",
|
||||
"load_const_false/1/$1 = false",
|
||||
"load_const_zero/1/$1 = 0",
|
||||
"mov/2/$1 = $2",
|
||||
"mov_long/2/$1 = $2",
|
||||
"loadi8/2/$1 = (int8)$2",
|
||||
"loadu8/2/$1 = (uint8)$2",
|
||||
"loadi16/2/$1 = (int16)$2",
|
||||
"loadu16/2/$1 = (uint16)$2",
|
||||
"loadi32/2/$1 = (int32)$2",
|
||||
"loadu32/2/$1 = (uint32)$2",
|
||||
"load_const_uint8/2/$1 = $2",
|
||||
"load_const_u_int8/2/$1 = $2",
|
||||
"load_const_int/2/$1 = $2",
|
||||
"load_const_double/2/$1 = $2",
|
||||
"load_const_bigint/2/$1 = $2",
|
||||
"load_const_string/2/$1 = $2",
|
||||
"load_const_string_long_index/2/$1 = $2",
|
||||
"load_const_empty/1/$1 = empty",
|
||||
"load_const_undefined/1/$1 = undefined",
|
||||
"load_const_null/1/$1 = null",
|
||||
"load_const_true/1/$1 = true",
|
||||
"load_const_false/1/$1 = false",
|
||||
"load_const_zero/1/$1 = 0",
|
||||
|
||||
/* Property access - get_by_id has 4 args: dest, obj, cache_idx, prop_name */
|
||||
"get_by_id/4/$1 = $2.$4",
|
||||
"get_by_id_long/4/$1 = $2.$4",
|
||||
"get_by_id_short/4/$1 = $2.$4",
|
||||
"get_by_val/3/$1 = $2[$3]",
|
||||
"put_by_id/4/$1.$4 = $3",
|
||||
"put_by_id_long/4/$1.$4 = $3",
|
||||
"put_by_val/3/$1[$2] = $3",
|
||||
"put_new_own_by_id/3/$1.$3 = $2",
|
||||
"put_new_own_by_id_short/3/$1.$3 = $2",
|
||||
"put_new_own_by_id_long/3/$1.$3 = $2",
|
||||
"put_new_own_ne_by_id/3/$1.$3 = $2",
|
||||
"put_new_own_ne_by_id_long/3/$1.$3 = $2",
|
||||
"try_get_by_id/4/$1 = try_get($2, $4)",
|
||||
"try_get_by_id_long/4/$1 = try_get($2, $4)",
|
||||
"try_put_by_id/4/try_put($1, $4, $3)",
|
||||
"try_put_by_id_long/4/try_put($1, $4, $3)",
|
||||
"del_by_id/3/delete $1.$3",
|
||||
"del_by_id_long/3/delete $1.$3",
|
||||
"del_by_val/3/delete $1[$2]",
|
||||
"get_by_id/4/$1 = $2.$4",
|
||||
"get_by_id_long/4/$1 = $2.$4",
|
||||
"get_by_id_short/4/$1 = $2.$4",
|
||||
"get_by_val/3/$1 = $2[$3]",
|
||||
"put_by_id/4/$1.$4 = $3",
|
||||
"put_by_id_long/4/$1.$4 = $3",
|
||||
"put_by_val/3/$1[$2] = $3",
|
||||
"put_new_own_by_id/3/$1.$3 = $2",
|
||||
"put_new_own_by_id_short/3/$1.$3 = $2",
|
||||
"put_new_own_by_id_long/3/$1.$3 = $2",
|
||||
"put_new_own_ne_by_id/3/$1.$3 = $2",
|
||||
"put_new_own_ne_by_id_long/3/$1.$3 = $2",
|
||||
"try_get_by_id/4/$1 = try_get($2, $4)",
|
||||
"try_get_by_id_long/4/$1 = try_get($2, $4)",
|
||||
"try_put_by_id/4/try_put($1, $4, $3)",
|
||||
"try_put_by_id_long/4/try_put($1, $4, $3)",
|
||||
"del_by_id/3/delete $1.$3",
|
||||
"del_by_id_long/3/delete $1.$3",
|
||||
"del_by_val/3/delete $1[$2]",
|
||||
|
||||
/* Arithmetic operations */
|
||||
"add/3/$1 = $2 + $3",
|
||||
"add_n/3/$1 = $2 + $3",
|
||||
"add_32/3/$1 = (int32)($2 + $3)",
|
||||
"add_empty_string/2/$1 = \"\" + $2",
|
||||
"sub/3/$1 = $2 - $3",
|
||||
"sub_n/3/$1 = $2 - $3",
|
||||
"sub_32/3/$1 = (int32)($2 - $3)",
|
||||
"mul/3/$1 = $2 * $3",
|
||||
"mul_n/3/$1 = $2 * $3",
|
||||
"mul_32/3/$1 = (int32)($2 * $3)",
|
||||
"div/3/$1 = $2 / $3",
|
||||
"div_n/3/$1 = $2 / $3",
|
||||
"divi32/3/$1 = (int32)($2 / $3)",
|
||||
"divu32/3/$1 = (uint32)($2 / $3)",
|
||||
"mod/3/$1 = $2 % $3",
|
||||
"inc/2/$1 = $2 + 1",
|
||||
"dec/2/$1 = $2 - 1",
|
||||
"negate/2/$1 = -$2",
|
||||
"add/3/$1 = $2 + $3",
|
||||
"add_n/3/$1 = $2 + $3",
|
||||
"add_32/3/$1 = (int32)($2 + $3)",
|
||||
"add_empty_string/2/$1 = \"\" + $2",
|
||||
"sub/3/$1 = $2 - $3",
|
||||
"sub_n/3/$1 = $2 - $3",
|
||||
"sub_32/3/$1 = (int32)($2 - $3)",
|
||||
"mul/3/$1 = $2 * $3",
|
||||
"mul_n/3/$1 = $2 * $3",
|
||||
"mul_32/3/$1 = (int32)($2 * $3)",
|
||||
"div/3/$1 = $2 / $3",
|
||||
"div_n/3/$1 = $2 / $3",
|
||||
"divi32/3/$1 = (int32)($2 / $3)",
|
||||
"divu32/3/$1 = (uint32)($2 / $3)",
|
||||
"mod/3/$1 = $2 % $3",
|
||||
"inc/2/$1 = $2 + 1",
|
||||
"dec/2/$1 = $2 - 1",
|
||||
"negate/2/$1 = -$2",
|
||||
|
||||
/* Bitwise operations */
|
||||
"bit_and/3/$1 = $2 & $3",
|
||||
"bit_or/3/$1 = $2 | $3",
|
||||
"bit_xor/3/$1 = $2 ^ $3",
|
||||
"bit_not/2/$1 = ~$2",
|
||||
"lshift/3/$1 = $2 << $3",
|
||||
"rshift/3/$1 = $2 >> $3",
|
||||
"urshift/3/$1 = (unsigned)$2 >> $3",
|
||||
"bit_and/3/$1 = $2 & $3",
|
||||
"bit_or/3/$1 = $2 | $3",
|
||||
"bit_xor/3/$1 = $2 ^ $3",
|
||||
"bit_not/2/$1 = ~$2",
|
||||
"lshift/3/$1 = $2 << $3",
|
||||
"rshift/3/$1 = $2 >> $3",
|
||||
"urshift/3/$1 = (unsigned)$2 >> $3",
|
||||
|
||||
/* Logical operations */
|
||||
"not/2/$1 = !$2",
|
||||
"eq/3/$1 = $2 == $3",
|
||||
"strict_eq/3/$1 = $2 === $3",
|
||||
"neq/3/$1 = $2 != $3",
|
||||
"strict_neq/3/$1 = $2 !== $3",
|
||||
"less/3/$1 = $2 < $3",
|
||||
"greater/3/$1 = $2 > $3",
|
||||
"less_eq/3/$1 = $2 <= $3",
|
||||
"greater_eq/3/$1 = $2 >= $3",
|
||||
"not/2/$1 = !$2",
|
||||
"eq/3/$1 = $2 == $3",
|
||||
"strict_eq/3/$1 = $2 === $3",
|
||||
"neq/3/$1 = $2 != $3",
|
||||
"strict_neq/3/$1 = $2 !== $3",
|
||||
"less/3/$1 = $2 < $3",
|
||||
"greater/3/$1 = $2 > $3",
|
||||
"less_eq/3/$1 = $2 <= $3",
|
||||
"greater_eq/3/$1 = $2 >= $3",
|
||||
|
||||
/* Type operations */
|
||||
"typeof/2/$1 = typeof($2)",
|
||||
"is_in/3/$1 = ($2 in $3)",
|
||||
"instanceof/3/$1 = ($2 instanceof $3)",
|
||||
"to_number/2/$1 = Number($2)",
|
||||
"to_numeric/2/$1 = Numeric($2)",
|
||||
"to_int32/2/$1 = (int32)$2",
|
||||
"typeof/2/$1 = typeof($2)",
|
||||
"is_in/3/$1 = ($2 in $3)",
|
||||
"instanceof/3/$1 = ($2 instanceof $3)",
|
||||
"to_number/2/$1 = Number($2)",
|
||||
"to_numeric/2/$1 = Numeric($2)",
|
||||
"to_int32/2/$1 = (int32)$2",
|
||||
|
||||
/* Object/Array creation */
|
||||
"new_object/1/$1 = {}",
|
||||
"new_object_with_parent/2/$1 = Object.create($2)",
|
||||
"new_object_with_buffer/5/$1 = {/*buffer*/}",
|
||||
"new_object_with_buffer_long/5/$1 = {/*buffer*/}",
|
||||
"new_array/2/$1 = []",
|
||||
"new_array_with_buffer/4/$1 = [/*buffer*/]",
|
||||
"new_array_with_buffer_long/4/$1 = [/*buffer*/]",
|
||||
"create_closure/3/$1 = closure($3)",
|
||||
"create_closure_long_index/3/$1 = closure($3)",
|
||||
"create_generator_closure/3/$1 = gen_closure($3)",
|
||||
"create_generator/3/$1 = generator($3)",
|
||||
"create_regexp/3/$1 = regexp($3)",
|
||||
"new_object/1/$1 = {}",
|
||||
"new_object_with_parent/2/$1 = Object.create($2)",
|
||||
"new_object_with_buffer/5/$1 = {/*buffer*/}",
|
||||
"new_object_with_buffer_long/5/$1 = {/*buffer*/}",
|
||||
"new_array/2/$1 = []",
|
||||
"new_array_with_buffer/4/$1 = [/*buffer*/]",
|
||||
"new_array_with_buffer_long/4/$1 = [/*buffer*/]",
|
||||
"create_closure/3/$1 = closure($3)",
|
||||
"create_closure_long_index/3/$1 = closure($3)",
|
||||
"create_generator_closure/3/$1 = gen_closure($3)",
|
||||
"create_generator/3/$1 = generator($3)",
|
||||
"create_regexp/3/$1 = regexp($3)",
|
||||
|
||||
/* Control flow */
|
||||
"unreachable/0/unreachable",
|
||||
"jmp/1/goto $1",
|
||||
"jmp_long/1/goto $1",
|
||||
"name/3/if ($2) goto $1",
|
||||
"name##_long/2/if ($2) goto $1",
|
||||
"jmp_true/2/if ($2) goto $1",
|
||||
"jmp_true_long/2/if ($2) goto $1",
|
||||
"jmp_false/2/if (!$2) goto $1",
|
||||
"jmp_false_long/2/if (!$2) goto $1",
|
||||
"jmp_undefined/2/if ($2 === undefined) goto $1",
|
||||
"jmp_undefined_long/2/if ($2 === undefined) goto $1",
|
||||
"jless/3/if ($1 < $2) goto $3",
|
||||
"jless_long/3/if ($1 < $2) goto $3",
|
||||
"jless_eq/3/if ($1 <= $2) goto $3",
|
||||
"jless_eq_long/3/if ($1 <= $2) goto $3",
|
||||
"jgreater/3/if ($1 > $2) goto $3",
|
||||
"jgreater_long/3/if ($1 > $2) goto $3",
|
||||
"jgreater_eq/3/if ($1 >= $2) goto $3",
|
||||
"jgreater_eq_long/3/if ($1 >= $2) goto $3",
|
||||
"jequal/3/if ($1 == $2) goto $3",
|
||||
"jequal_long/3/if ($1 == $2) goto $3",
|
||||
"jstrict_equal/3/if ($1 === $2) goto $3",
|
||||
"jstrict_equal_long/3/if ($1 === $2) goto $3",
|
||||
"jnot_equal/3/if ($1 != $2) goto $3",
|
||||
"jnot_equal_long/3/if ($1 != $2) goto $3",
|
||||
"jstrict_not_equal/3/if ($1 !== $2) goto $3",
|
||||
"jstrict_not_equal_long/3/if ($1 !== $2) goto $3",
|
||||
"unreachable/0/unreachable",
|
||||
"jmp/1/goto $1",
|
||||
"jmp_long/1/goto $1",
|
||||
"name/3/if ($2) goto $1",
|
||||
"name##_long/2/if ($2) goto $1",
|
||||
"jmp_true/2/if ($2) goto $1",
|
||||
"jmp_true_long/2/if ($2) goto $1",
|
||||
"jmp_false/2/if (!$2) goto $1",
|
||||
"jmp_false_long/2/if (!$2) goto $1",
|
||||
"jmp_undefined/2/if ($2 === undefined) goto $1",
|
||||
"jmp_undefined_long/2/if ($2 === undefined) goto $1",
|
||||
"jless/3/if ($1 < $2) goto $3",
|
||||
"jless_long/3/if ($1 < $2) goto $3",
|
||||
"jless_eq/3/if ($1 <= $2) goto $3",
|
||||
"jless_eq_long/3/if ($1 <= $2) goto $3",
|
||||
"jgreater/3/if ($1 > $2) goto $3",
|
||||
"jgreater_long/3/if ($1 > $2) goto $3",
|
||||
"jgreater_eq/3/if ($1 >= $2) goto $3",
|
||||
"jgreater_eq_long/3/if ($1 >= $2) goto $3",
|
||||
"jequal/3/if ($1 == $2) goto $3",
|
||||
"jequal_long/3/if ($1 == $2) goto $3",
|
||||
"jstrict_equal/3/if ($1 === $2) goto $3",
|
||||
"jstrict_equal_long/3/if ($1 === $2) goto $3",
|
||||
"jnot_equal/3/if ($1 != $2) goto $3",
|
||||
"jnot_equal_long/3/if ($1 != $2) goto $3",
|
||||
"jstrict_not_equal/3/if ($1 !== $2) goto $3",
|
||||
"jstrict_not_equal_long/3/if ($1 !== $2) goto $3",
|
||||
|
||||
/* Function calls and returns */
|
||||
/* call: result = callee (this, args...) */
|
||||
"call/2/$1($2)",
|
||||
"call1/3/$1 = $2($3)",
|
||||
"call2/4/$1 = $2($4)",
|
||||
"call3/5/$1 = $2($4, $5)",
|
||||
"call4/6/$1 = $2($4, $5, $6)",
|
||||
"call_long/2/$1($2)",
|
||||
"call_builtin/3/$1 = builtin.$2($3)",
|
||||
"call_builtin_long/3/$1 = builtin.$2($3)",
|
||||
"call_direct/3/$1 = $2($3)",
|
||||
"call_direct_long_index/3/$1 = $2($3)",
|
||||
"construct/3/$1 = new $2($3)",
|
||||
"construct_long/3/$1 = new $2($3)",
|
||||
"ret/1/return $1",
|
||||
"ret_undefined/0/return",
|
||||
"call/2/$1($2)",
|
||||
"call1/3/$1 = $2($3)",
|
||||
"call2/4/$1 = $2($4)",
|
||||
"call3/5/$1 = $2($4, $5)",
|
||||
"call4/6/$1 = $2($4, $5, $6)",
|
||||
"call_long/2/$1($2)",
|
||||
"call_builtin/3/$1 = builtin.$2($3)",
|
||||
"call_builtin_long/3/$1 = builtin.$2($3)",
|
||||
"call_direct/3/$1 = $2($3)",
|
||||
"call_direct_long_index/3/$1 = $2($3)",
|
||||
"construct/3/$1 = new $2($3)",
|
||||
"construct_long/3/$1 = new $2($3)",
|
||||
"ret/1/return $1",
|
||||
"ret_undefined/0/return",
|
||||
|
||||
/* Exception handling */
|
||||
"throw/1/throw $1",
|
||||
"throw_if_empty/2/if (empty($1)) throw $2",
|
||||
"catch/2/$1 = catch $2",
|
||||
"throw/1/throw $1",
|
||||
"throw_if_empty/2/if (empty($1)) throw $2",
|
||||
"catch/2/$1 = catch $2",
|
||||
|
||||
/* Parameter handling */
|
||||
"load_param/2/$1 = param[$2]",
|
||||
"load_param_long/2/$1 = param[$2]",
|
||||
"reify_arguments/1/arguments = reify($1)",
|
||||
"get_arguments_length/2/$1 = arguments.length",
|
||||
"get_arguments_prop_by_val/3/$1 = arguments[$2]",
|
||||
"load_param/2/$1 = param[$2]",
|
||||
"load_param_long/2/$1 = param[$2]",
|
||||
"reify_arguments/1/arguments = reify($1)",
|
||||
"get_arguments_length/2/$1 = arguments.length",
|
||||
"get_arguments_prop_by_val/3/$1 = arguments[$2]",
|
||||
|
||||
/* Environment operations */
|
||||
"get_environment/2/$1 = env[$2]",
|
||||
"load_from_environment/3/$1 = $2[$3]",
|
||||
"load_from_environment_l/3/$1 = $2[$3]",
|
||||
"store_to_environment/3/$2[$3] = $1",
|
||||
"store_to_environment_l/3/$2[$3] = $1",
|
||||
"store_np_to_environment/3/$2[$3] = $1",
|
||||
"store_np_to_environment_l/3/$2[$3] = $1",
|
||||
"create_environment/1/$1 = new_env()",
|
||||
"create_inner_environment/1/$1 = new_inner_env()",
|
||||
"get_environment/2/$1 = env[$2]",
|
||||
"load_from_environment/3/$1 = $2[$3]",
|
||||
"load_from_environment_l/3/$1 = $2[$3]",
|
||||
"store_to_environment/3/$2[$3] = $1",
|
||||
"store_to_environment_l/3/$2[$3] = $1",
|
||||
"store_np_to_environment/3/$2[$3] = $1",
|
||||
"store_np_to_environment_l/3/$2[$3] = $1",
|
||||
"create_environment/1/$1 = new_env()",
|
||||
"create_inner_environment/1/$1 = new_inner_env()",
|
||||
|
||||
/* Generator operations */
|
||||
"start_generator/0/start_generator()",
|
||||
"resume_generator/2/$1 = resume_generator($2)",
|
||||
"save_generator/1/save_generator($1)",
|
||||
"complete_generator/0/complete_generator()",
|
||||
"start_generator/0/start_generator()",
|
||||
"resume_generator/2/$1 = resume_generator($2)",
|
||||
"save_generator/1/save_generator($1)",
|
||||
"complete_generator/0/complete_generator()",
|
||||
|
||||
/* Miscellaneous */
|
||||
"debugger/0/debugger",
|
||||
"profile_point/1/profile($1)",
|
||||
"select_object/3/$1 = select($2, $3)",
|
||||
"catch/2/$1 = catch_value($2)",
|
||||
"get_builtin_closure/2/$1 = builtin_closure($2)",
|
||||
"get_global_object/1/$1 = global",
|
||||
"get_new_target/1/$1 = new.target",
|
||||
"get_this_ns/1/$1 = this",
|
||||
"coerce_this_ns/1/$1 = coerce_this()",
|
||||
"debugger/0/debugger",
|
||||
"profile_point/1/profile($1)",
|
||||
"select_object/3/$1 = select($2, $3)",
|
||||
"catch/2/$1 = catch_value($2)",
|
||||
"get_builtin_closure/2/$1 = builtin_closure($2)",
|
||||
"get_global_object/1/$1 = global",
|
||||
"get_new_target/1/$1 = new.target",
|
||||
"get_this_ns/1/$1 = this",
|
||||
"coerce_this_ns/1/$1 = coerce_this()",
|
||||
|
||||
/* Iterator operations */
|
||||
"iterator_begin/2/$1 = iterator_begin($2)",
|
||||
"iterator_next/3/$1 = iterator_next($2, $3)",
|
||||
"iterator_close/2/iterator_close($1, $2)",
|
||||
"get_pname_list/2/$1 = property_names($2)",
|
||||
"get_next_pname/4/$1 = next_property_name($2, $3, $4)",
|
||||
"iterator_begin/2/$1 = iterator_begin($2)",
|
||||
"iterator_next/3/$1 = iterator_next($2, $3)",
|
||||
"iterator_close/2/iterator_close($1, $2)",
|
||||
"get_pname_list/2/$1 = property_names($2)",
|
||||
"get_next_pname/4/$1 = next_property_name($2, $3, $4)",
|
||||
|
||||
/* Switch */
|
||||
"switch_imm/3/switch($1) { cases in $2 }",
|
||||
"switch_imm/3/switch($1) { cases in $2 }",
|
||||
|
||||
NULL
|
||||
};
|
||||
@@ -219,7 +219,7 @@ const RAsmPlugin r_asm_plugin_r2hermes = {
|
||||
.name = "hbc",
|
||||
.desc = "Hermes bytecode pseudo syntax",
|
||||
.author = "pancake",
|
||||
.license = "LGPL-3.0-only",
|
||||
.license = "BSD-3-Clause",
|
||||
},
|
||||
.parse = parse,
|
||||
};
|
||||
|
||||
+582
-19
@@ -1,7 +1,9 @@
|
||||
/* radare2 - LGPL - Copyright 2025-2026 - pancake */
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <r_bin.h>
|
||||
#include <r_bin_dwarf.h>
|
||||
#include <hbc/hbc.h>
|
||||
#include <hbc/literals.h>
|
||||
#include "utils.inc.c"
|
||||
|
||||
#define HEADER_MAGIC 0x1f1903c103bc1fc6ULL
|
||||
@@ -131,7 +133,7 @@ static ut64 resolve_entrypoint(RBinFile *bf, HBC *hbc) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void fill_info(RBinInfo *ret, const char *file_path, bool has_version, ut32 version) {
|
||||
static void fill_info(RBinInfo *ret, const char *file_path, bool has_version, ut32 version, bool has_source_lines) {
|
||||
ret->file = file_path? strdup (file_path): NULL;
|
||||
ret->bclass = strdup ("Hermes bytecode");
|
||||
ret->rclass = strdup ("hermes");
|
||||
@@ -140,13 +142,16 @@ static void fill_info(RBinInfo *ret, const char *file_path, bool has_version, ut
|
||||
ret->bits = 32;
|
||||
ret->type = strdup ("Hermes bytecode");
|
||||
ret->machine = strdup ("Hermes VM");
|
||||
ret->cpu = has_version? r_str_newf ("%u", version): strdup ("unknown");
|
||||
ret->cpu = has_version? r_str_newf ("v%u", version): strdup ("unknown");
|
||||
ret->has_va = true;
|
||||
ret->dbg_info = has_source_lines? R_BIN_DBG_LINENUMS: R_BIN_DBG_STRIPPED;
|
||||
}
|
||||
|
||||
static RBinInfo *bininfo(RBinFile *bf) {
|
||||
RBinInfo *ret = R_NEW0 (RBinInfo);
|
||||
bool has_version = false;
|
||||
ut32 version = 0;
|
||||
bool has_source_lines = false;
|
||||
|
||||
HBC *hbc = get_hbc (bf);
|
||||
if (hbc) {
|
||||
@@ -155,6 +160,10 @@ static RBinInfo *bininfo(RBinFile *bf) {
|
||||
has_version = true;
|
||||
version = hh.version;
|
||||
}
|
||||
/* Cheap: answered from the DebugInfoHeader parsed at load time.
|
||||
* Reflects file truth (does this file carry source lines?) regardless
|
||||
* of bin.dbginfo — that setting only gates whether we decode them. */
|
||||
has_source_lines = hbc_has_source_lines (hbc);
|
||||
}
|
||||
|
||||
/* Fallback: parse version directly from buffer */
|
||||
@@ -167,10 +176,30 @@ static RBinInfo *bininfo(RBinFile *bf) {
|
||||
}
|
||||
}
|
||||
|
||||
fill_info (ret, bf->file, has_version, version);
|
||||
fill_info (ret, bf->file, has_version, version, has_source_lines);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Helper: append a section if the pool is present. Owns no strings — `name`
|
||||
* is duplicated into a heap-allocated section name via strdup. */
|
||||
static void add_pool_section(RList *list, const char *name, ut64 paddr, ut64 size) {
|
||||
if (!size || !paddr) {
|
||||
return;
|
||||
}
|
||||
RBinSection *s = R_NEW0 (RBinSection);
|
||||
if (!s) {
|
||||
return;
|
||||
}
|
||||
s->name = strdup (name);
|
||||
s->size = size;
|
||||
s->vsize = size;
|
||||
s->paddr = paddr;
|
||||
s->vaddr = HBC_VADDR_BASE + paddr;
|
||||
s->perm = R_PERM_R;
|
||||
s->add = true;
|
||||
r_list_append (list, s);
|
||||
}
|
||||
|
||||
static RList *sections(RBinFile *bf) {
|
||||
RList *sections = r_list_newf ((RListFree)free);
|
||||
if (!sections) {
|
||||
@@ -183,9 +212,19 @@ static RList *sections(RBinFile *bf) {
|
||||
section->vsize = section->size;
|
||||
section->paddr = 0;
|
||||
section->vaddr = HBC_VADDR_BASE;
|
||||
section->perm = R_PERM_R;
|
||||
section->perm = R_PERM_RX;
|
||||
section->add = true;
|
||||
r_list_append (sections, section);
|
||||
|
||||
/* SLP pool subsections — gives `iS` a quick map of where the buffer
|
||||
* literals live, and lets users `s` / `pd @` straight into them. */
|
||||
HBC *hbc = get_hbc (bf);
|
||||
if (hbc) {
|
||||
add_pool_section (sections, "slp.arrays", hbc_get_pool_paddr (hbc, HBC_LIT_ARRAY), hbc_get_pool_size (hbc, HBC_LIT_ARRAY));
|
||||
add_pool_section (sections, "slp.object_keys", hbc_get_pool_paddr (hbc, HBC_LIT_OBJECT), hbc_get_pool_size (hbc, HBC_LIT_OBJECT));
|
||||
add_pool_section (sections, "slp.object_values", hbc_get_object_values_paddr (hbc), hbc_get_object_values_size (hbc));
|
||||
}
|
||||
|
||||
return sections;
|
||||
}
|
||||
|
||||
@@ -210,6 +249,33 @@ static ut64 baddr(RBinFile *bf R_UNUSED) {
|
||||
return HBC_VADDR_BASE;
|
||||
}
|
||||
|
||||
static void append_binding_symbols(RList *symbols, HBC *hbc) {
|
||||
HBCBindings bindings = { 0 };
|
||||
if (hbc_scan_bindings (hbc, &bindings).code != RESULT_SUCCESS) {
|
||||
return;
|
||||
}
|
||||
for (u32 i = 0; i < bindings.count; i++) {
|
||||
HBCBinding *b = &bindings.bindings[i];
|
||||
if (b->type != HBC_BINDING_EXPORT) {
|
||||
continue;
|
||||
}
|
||||
RBinSymbol *sym = R_NEW0 (RBinSymbol);
|
||||
char *nm = r_str_newf ("hbc.export.%s.%s", b->kind? b->kind: "js", b->name);
|
||||
sym->name = r_bin_name_new (nm);
|
||||
r_bin_name_filtered (sym->name, nm);
|
||||
sym->paddr = b->offset;
|
||||
sym->vaddr = HBC_VADDR_BASE + b->offset;
|
||||
sym->size = 1;
|
||||
sym->ordinal = b->string_id;
|
||||
sym->type = R_BIN_TYPE_OBJECT_STR;
|
||||
sym->bind = R_BIN_BIND_GLOBAL_STR;
|
||||
sym->bits = 32;
|
||||
r_list_append (symbols, sym);
|
||||
free (nm);
|
||||
}
|
||||
hbc_free_bindings (&bindings);
|
||||
}
|
||||
|
||||
static RList *symbols(RBinFile *bf) {
|
||||
RList *symbols = r_list_newf ((RListFree)r_bin_symbol_free);
|
||||
HBC *hbc = get_hbc (bf);
|
||||
@@ -263,10 +329,149 @@ static RList *symbols(RBinFile *bf) {
|
||||
free (san);
|
||||
}
|
||||
|
||||
/* SLP pool groups as symbols — one per group discovered by the
|
||||
* pool-side linear scan. Lets users see `lit.arr.* / lit.obj.*` in `is`
|
||||
* without needing to run r2hermes-Lp. The scan is linear in pool size and
|
||||
* runs at RBin load time. */
|
||||
for (int k = 0; k < 2; k++) {
|
||||
HBCLiteralKind kind = k? HBC_LIT_OBJECT: HBC_LIT_ARRAY;
|
||||
const char *prefix = k? "lit.obj.": "lit.arr.";
|
||||
HBCPoolGroup *groups = NULL;
|
||||
u32 n = 0;
|
||||
if (hbc_literals_scan_pool (hbc, kind, &groups, &n).code != RESULT_SUCCESS || !groups) {
|
||||
continue;
|
||||
}
|
||||
for (u32 i = 0; i < n; i++) {
|
||||
RBinSymbol *sym = R_NEW0 (RBinSymbol);
|
||||
char *nm = r_str_newf ("%s0x%x", prefix, groups[i].paddr);
|
||||
sym->name = r_bin_name_new (nm);
|
||||
r_bin_name_filtered (sym->name, nm);
|
||||
sym->paddr = groups[i].paddr;
|
||||
sym->vaddr = HBC_VADDR_BASE + groups[i].paddr;
|
||||
sym->size = 1;
|
||||
sym->type = R_BIN_TYPE_OBJECT_STR;
|
||||
sym->bits = 32;
|
||||
r_list_append (symbols, sym);
|
||||
free (nm);
|
||||
}
|
||||
free (groups);
|
||||
}
|
||||
|
||||
append_binding_symbols (symbols, hbc);
|
||||
return symbols;
|
||||
}
|
||||
|
||||
static RList *strings(RBinFile *bf) {
|
||||
static RList *imports(RBinFile *bf) {
|
||||
RList *imports = r_list_newf ((RListFree)r_bin_import_free);
|
||||
HBC *hbc = get_hbc (bf);
|
||||
if (!imports || !hbc) {
|
||||
return imports;
|
||||
}
|
||||
HBCBindings bindings = { 0 };
|
||||
if (hbc_scan_bindings (hbc, &bindings).code != RESULT_SUCCESS) {
|
||||
return imports;
|
||||
}
|
||||
for (u32 i = 0; i < bindings.count; i++) {
|
||||
HBCBinding *b = &bindings.bindings[i];
|
||||
if (b->type != HBC_BINDING_IMPORT) {
|
||||
continue;
|
||||
}
|
||||
RBinImport *imp = R_NEW0 (RBinImport);
|
||||
char *nm = r_str_newf ("%s.%s", b->kind? b->kind: "js", b->name);
|
||||
imp->name = r_bin_name_new (nm);
|
||||
r_bin_name_filtered (imp->name, nm);
|
||||
imp->libname = strdup (b->module? b->module: "hermes");
|
||||
imp->bind = "NONE";
|
||||
imp->type = "FUNC";
|
||||
imp->ordinal = b->string_id;
|
||||
imp->is_imported = true;
|
||||
r_list_append (imports, imp);
|
||||
free (nm);
|
||||
}
|
||||
hbc_free_bindings (&bindings);
|
||||
return imports;
|
||||
}
|
||||
|
||||
static bool list_has_string(RList *list, const char *name) {
|
||||
RListIter *iter;
|
||||
const char *it;
|
||||
r_list_foreach (list, iter, it) {
|
||||
if (it && name && !strcmp (it, name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static RList *libs(RBinFile *bf) {
|
||||
RList *libs = r_list_newf (free);
|
||||
HBC *hbc = get_hbc (bf);
|
||||
if (!libs || !hbc) {
|
||||
return libs;
|
||||
}
|
||||
HBCModules modules = { 0 };
|
||||
if (hbc_list_modules (hbc, &modules).code != RESULT_SUCCESS) {
|
||||
return libs;
|
||||
}
|
||||
for (u32 i = 0; i < modules.count; i++) {
|
||||
HBCModule *m = &modules.modules[i];
|
||||
if (m->name && *m->name && !list_has_string (libs, m->name)) {
|
||||
r_list_append (libs, strdup (m->name));
|
||||
}
|
||||
}
|
||||
hbc_free_modules (&modules);
|
||||
return libs;
|
||||
}
|
||||
|
||||
static R_UNOWNED RList *lines(RBinFile *bf) {
|
||||
HBC *hbc = get_hbc (bf);
|
||||
if (!hbc) {
|
||||
return NULL;
|
||||
}
|
||||
/* Respect bin.dbginfo=false — return early so the lazy debug-info parse
|
||||
*(triggered by hbc_get_source_lines) never runs for users who opted out. */
|
||||
if (bf->rbin && !bf->rbin->want_dbginfo) {
|
||||
return NULL;
|
||||
}
|
||||
HBCSourceLineArray lines = { 0 };
|
||||
RList *ret = NULL;
|
||||
if (hbc_get_source_lines (hbc, &lines).code != RESULT_SUCCESS || !lines.count) {
|
||||
goto done;
|
||||
}
|
||||
ret = r_list_newf (free);
|
||||
if (!ret) {
|
||||
goto done;
|
||||
}
|
||||
const bool has_al = bf->addrline.al_add && bf->addrline.al_get;
|
||||
for (u32 i = 0; i < lines.count; i++) {
|
||||
HBCSourceLine *sl = &lines.lines[i];
|
||||
ut64 addr = HBC_VADDR_BASE + sl->address;
|
||||
const char *file = sl->filename? sl->filename: "";
|
||||
if (has_al) {
|
||||
bf->addrline.al_add (&bf->addrline, addr, file, NULL, sl->line, sl->column);
|
||||
}
|
||||
RBinAddrline *row = R_NEW0 (RBinAddrline);
|
||||
if (!row) {
|
||||
continue;
|
||||
}
|
||||
const RBinAddrline *stored = has_al? bf->addrline.al_get (&bf->addrline, addr): NULL;
|
||||
if (stored) {
|
||||
*row = *stored;
|
||||
} else {
|
||||
row->addr = addr;
|
||||
row->file = UT32_MAX;
|
||||
row->path = UT32_MAX;
|
||||
row->line = sl->line;
|
||||
row->column = sl->column;
|
||||
}
|
||||
r_list_append (ret, row);
|
||||
}
|
||||
done:
|
||||
hbc_free_source_lines (&lines);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static RList *strings_list(RBinFile *bf) {
|
||||
RList *ret = r_list_newf ((RListFree)r_bin_string_free);
|
||||
HBC *hbc = get_hbc (bf);
|
||||
if (!hbc) {
|
||||
@@ -285,21 +490,18 @@ static RList *strings(RBinFile *bf) {
|
||||
if (hbc_get_string_meta (hbc, i, &meta).code != RESULT_SUCCESS) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (meta.length == 0) {
|
||||
const size_t str_len = strlen (str);
|
||||
if (str_len == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
RBinString *ptr = R_NEW0 (RBinString);
|
||||
if (meta.length >= R_BIN_SIZEOF_STRINGS) {
|
||||
ptr->string = r_str_ndup (str, R_BIN_SIZEOF_STRINGS - 4);
|
||||
if (ptr->string) {
|
||||
char *tmp = r_str_newf ("%s...", ptr->string);
|
||||
free (ptr->string);
|
||||
ptr->string = tmp;
|
||||
}
|
||||
if (str_len >= R_BIN_SIZEOF_STRINGS) {
|
||||
char *trunc = r_str_ndup (str, R_BIN_SIZEOF_STRINGS - 4);
|
||||
ptr->string = r_str_newf ("%s...", trunc? trunc: "");
|
||||
free (trunc);
|
||||
} else {
|
||||
ptr->string = r_str_ndup (str, meta.length);
|
||||
ptr->string = r_str_ndup (str, str_len);
|
||||
}
|
||||
if (!ptr->string) {
|
||||
free (ptr);
|
||||
@@ -307,7 +509,7 @@ static RList *strings(RBinFile *bf) {
|
||||
}
|
||||
|
||||
ptr->paddr = meta.offset;
|
||||
ptr->vaddr = meta.offset;
|
||||
ptr->vaddr = HBC_VADDR_BASE + meta.offset;
|
||||
ptr->size = meta.isUTF16? (meta.length * 2): meta.length;
|
||||
ptr->length = meta.length;
|
||||
ptr->ordinal = i;
|
||||
@@ -317,6 +519,355 @@ static RList *strings(RBinFile *bf) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if R2_ABIVERSION >= 104
|
||||
static bool sections_vec(RBinFile *bf) {
|
||||
if (!bf || !bf->bo) {
|
||||
return false;
|
||||
}
|
||||
if (!RVecRBinSection_empty (&bf->bo->sections_vec)) {
|
||||
return true;
|
||||
}
|
||||
RList *list = sections (bf);
|
||||
if (!list) {
|
||||
return false;
|
||||
}
|
||||
bool ok = true;
|
||||
RListIter *iter;
|
||||
RBinSection *section;
|
||||
r_list_foreach (list, iter, section) {
|
||||
RBinSection *dst = RVecRBinSection_emplace_back (&bf->bo->sections_vec);
|
||||
if (!dst) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
*dst = *section;
|
||||
memset (section, 0, sizeof (*section));
|
||||
}
|
||||
if (!ok) {
|
||||
RVecRBinSection_clear (&bf->bo->sections_vec);
|
||||
}
|
||||
list->free = (RListFree)r_bin_section_free;
|
||||
r_list_free (list);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static bool symbols_vec(RBinFile *bf) {
|
||||
if (!bf || !bf->bo) {
|
||||
return false;
|
||||
}
|
||||
if (!RVecRBinSymbol_empty (&bf->bo->symbols_vec)) {
|
||||
return true;
|
||||
}
|
||||
RList *list = symbols (bf);
|
||||
if (!list) {
|
||||
return false;
|
||||
}
|
||||
bool ok = true;
|
||||
RListIter *iter;
|
||||
RBinSymbol *symbol;
|
||||
r_list_foreach (list, iter, symbol) {
|
||||
RBinSymbol *dst = RVecRBinSymbol_emplace_back (&bf->bo->symbols_vec);
|
||||
if (!dst) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
*dst = *symbol;
|
||||
memset (symbol, 0, sizeof (*symbol));
|
||||
}
|
||||
if (!ok) {
|
||||
RVecRBinSymbol_clear (&bf->bo->symbols_vec);
|
||||
}
|
||||
r_list_free (list);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static bool imports_vec(RBinFile *bf) {
|
||||
if (!bf || !bf->bo) {
|
||||
return false;
|
||||
}
|
||||
if (!RVecRBinImport_empty (&bf->bo->imports_vec)) {
|
||||
return true;
|
||||
}
|
||||
RList *list = imports (bf);
|
||||
if (!list) {
|
||||
return false;
|
||||
}
|
||||
bool ok = true;
|
||||
RListIter *iter;
|
||||
RBinImport *imp;
|
||||
r_list_foreach (list, iter, imp) {
|
||||
RBinImport *dst = RVecRBinImport_emplace_back (&bf->bo->imports_vec);
|
||||
if (!dst) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
*dst = *imp;
|
||||
memset (imp, 0, sizeof (*imp));
|
||||
}
|
||||
if (!ok) {
|
||||
RVecRBinImport_clear (&bf->bo->imports_vec);
|
||||
}
|
||||
r_list_free (list);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static RVecRBinString *strings_vec(RBinFile *bf) {
|
||||
RList *list = strings_list (bf);
|
||||
if (!list) {
|
||||
return NULL;
|
||||
}
|
||||
RVecRBinString *ret = RVecRBinString_new ();
|
||||
if (!ret) {
|
||||
r_list_free (list);
|
||||
return NULL;
|
||||
}
|
||||
bool ok = true;
|
||||
RListIter *iter;
|
||||
RBinString *str;
|
||||
r_list_foreach (list, iter, str) {
|
||||
RBinString *dst = RVecRBinString_emplace_back (ret);
|
||||
if (!dst) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
*dst = *str;
|
||||
memset (str, 0, sizeof (*str));
|
||||
}
|
||||
r_list_free (list);
|
||||
if (!ok) {
|
||||
RVecRBinString_free (ret);
|
||||
return NULL;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void hbc_source_hash_hex(const HBCHeader *hh, char *dst, size_t dstsz) {
|
||||
if (!dst || dstsz < 41) {
|
||||
return;
|
||||
}
|
||||
r_hex_bin2str (hh->sourceHash, 20, dst);
|
||||
}
|
||||
|
||||
static void hbc_header_print_u32(RStrBuf *sb, ut64 off, const char *name, ut32 value) {
|
||||
r_strbuf_appendf (sb, "0x%08" PFMT64x " %-29s 0x%08x\n", off, name, value);
|
||||
}
|
||||
|
||||
static void hbc_header_print_bool(RStrBuf *sb, ut64 off, const char *name, bool value) {
|
||||
r_strbuf_appendf (sb, "0x%08" PFMT64x " %-29s %s\n", off, name, value? "true": "false");
|
||||
}
|
||||
|
||||
static char *header(RBinFile *bf, int mode) {
|
||||
HBC *hbc = get_hbc (bf);
|
||||
if (!hbc) {
|
||||
return NULL;
|
||||
}
|
||||
HBCHeader hh;
|
||||
if (hbc_get_header (hbc, &hh).code != RESULT_SUCCESS) {
|
||||
return NULL;
|
||||
}
|
||||
char hash[41] = { 0 };
|
||||
hbc_source_hash_hex (&hh, hash, sizeof (hash));
|
||||
RStrBuf *sb = r_strbuf_new ("");
|
||||
if (!sb) {
|
||||
return NULL;
|
||||
}
|
||||
if (mode == R_MODE_EQUAL) {
|
||||
r_strbuf_appendf (sb, "magic=0x%016" PFMT64x "\n", hh.magic);
|
||||
r_strbuf_appendf (sb, "version=%u\n", hh.version);
|
||||
r_strbuf_appendf (sb, "sourceHash=%s\n", hash);
|
||||
r_strbuf_appendf (sb, "fileLength=%u\n", hh.fileLength);
|
||||
r_strbuf_appendf (sb, "globalCodeIndex=%u\n", hh.globalCodeIndex);
|
||||
r_strbuf_appendf (sb, "functionCount=%u\n", hh.functionCount);
|
||||
r_strbuf_appendf (sb, "stringKindCount=%u\n", hh.stringKindCount);
|
||||
r_strbuf_appendf (sb, "identifierCount=%u\n", hh.identifierCount);
|
||||
r_strbuf_appendf (sb, "stringCount=%u\n", hh.stringCount);
|
||||
r_strbuf_appendf (sb, "overflowStringCount=%u\n", hh.overflowStringCount);
|
||||
r_strbuf_appendf (sb, "stringStorageSize=%u\n", hh.stringStorageSize);
|
||||
r_strbuf_appendf (sb, "bigIntCount=%u\n", hh.bigIntCount);
|
||||
r_strbuf_appendf (sb, "bigIntStorageSize=%u\n", hh.bigIntStorageSize);
|
||||
r_strbuf_appendf (sb, "regExpCount=%u\n", hh.regExpCount);
|
||||
r_strbuf_appendf (sb, "regExpStorageSize=%u\n", hh.regExpStorageSize);
|
||||
r_strbuf_appendf (sb, "literalValueBufferSize=%u\n", hh.literalValueBufferSize);
|
||||
r_strbuf_appendf (sb, "arrayBufferSize=%u\n", hh.arrayBufferSize);
|
||||
r_strbuf_appendf (sb, "objKeyBufferSize=%u\n", hh.objKeyBufferSize);
|
||||
r_strbuf_appendf (sb, "objValueBufferSize=%u\n", hh.objValueBufferSize);
|
||||
r_strbuf_appendf (sb, "objShapeTableCount=%u\n", hh.objShapeTableCount);
|
||||
r_strbuf_appendf (sb, "numStringSwitchImms=%u\n", hh.numStringSwitchImms);
|
||||
r_strbuf_appendf (sb, "segmentID=%u\n", hh.segmentID);
|
||||
r_strbuf_appendf (sb, "cjsModuleCount=%u\n", hh.cjsModuleCount);
|
||||
r_strbuf_appendf (sb, "functionSourceCount=%u\n", hh.functionSourceCount);
|
||||
r_strbuf_appendf (sb, "debugInfoOffset=%u\n", hh.debugInfoOffset);
|
||||
r_strbuf_appendf (sb, "staticBuiltins=%s\n", hh.staticBuiltins? "true": "false");
|
||||
r_strbuf_appendf (sb, "cjsModulesStaticallyResolved=%s\n", hh.cjsModulesStaticallyResolved? "true": "false");
|
||||
r_strbuf_appendf (sb, "hasAsync=%s\n", hh.hasAsync? "true": "false");
|
||||
return r_strbuf_drain (sb);
|
||||
}
|
||||
if (mode == R_MODE_JSON) {
|
||||
PJ *pj = pj_new ();
|
||||
if (!pj) {
|
||||
r_strbuf_free (sb);
|
||||
return NULL;
|
||||
}
|
||||
pj_o (pj);
|
||||
pj_kn (pj, "magic", hh.magic);
|
||||
pj_kn (pj, "version", hh.version);
|
||||
pj_ks (pj, "sourceHash", hash);
|
||||
pj_kn (pj, "fileLength", hh.fileLength);
|
||||
pj_kn (pj, "globalCodeIndex", hh.globalCodeIndex);
|
||||
pj_kn (pj, "functionCount", hh.functionCount);
|
||||
pj_kn (pj, "stringKindCount", hh.stringKindCount);
|
||||
pj_kn (pj, "identifierCount", hh.identifierCount);
|
||||
pj_kn (pj, "stringCount", hh.stringCount);
|
||||
pj_kn (pj, "overflowStringCount", hh.overflowStringCount);
|
||||
pj_kn (pj, "stringStorageSize", hh.stringStorageSize);
|
||||
pj_kn (pj, "bigIntCount", hh.bigIntCount);
|
||||
pj_kn (pj, "bigIntStorageSize", hh.bigIntStorageSize);
|
||||
pj_kn (pj, "regExpCount", hh.regExpCount);
|
||||
pj_kn (pj, "regExpStorageSize", hh.regExpStorageSize);
|
||||
pj_kn (pj, "literalValueBufferSize", hh.literalValueBufferSize);
|
||||
pj_kn (pj, "arrayBufferSize", hh.arrayBufferSize);
|
||||
pj_kn (pj, "objKeyBufferSize", hh.objKeyBufferSize);
|
||||
pj_kn (pj, "objValueBufferSize", hh.objValueBufferSize);
|
||||
pj_kn (pj, "objShapeTableCount", hh.objShapeTableCount);
|
||||
pj_kn (pj, "numStringSwitchImms", hh.numStringSwitchImms);
|
||||
pj_kn (pj, "segmentID", hh.segmentID);
|
||||
pj_kn (pj, "cjsModuleCount", hh.cjsModuleCount);
|
||||
pj_kn (pj, "functionSourceCount", hh.functionSourceCount);
|
||||
pj_kn (pj, "debugInfoOffset", hh.debugInfoOffset);
|
||||
pj_kb (pj, "staticBuiltins", hh.staticBuiltins);
|
||||
pj_kb (pj, "cjsModulesStaticallyResolved", hh.cjsModulesStaticallyResolved);
|
||||
pj_kb (pj, "hasAsync", hh.hasAsync);
|
||||
pj_end (pj);
|
||||
r_strbuf_free (sb);
|
||||
return pj_drain (pj);
|
||||
}
|
||||
ut64 off = 0;
|
||||
r_strbuf_append (sb, "Hermes bytecode header:\n");
|
||||
r_strbuf_appendf (sb, "0x%08" PFMT64x " %-29s 0x%016" PFMT64x "\n", off, "magic", hh.magic);
|
||||
off += 8;
|
||||
hbc_header_print_u32 (sb, off, "version", hh.version);
|
||||
off += 4;
|
||||
r_strbuf_appendf (sb, "0x%08" PFMT64x " %-29s %s\n", off, "sourceHash", hash);
|
||||
off += 20;
|
||||
hbc_header_print_u32 (sb, off, "fileLength", hh.fileLength);
|
||||
off += 4;
|
||||
hbc_header_print_u32 (sb, off, "globalCodeIndex", hh.globalCodeIndex);
|
||||
off += 4;
|
||||
hbc_header_print_u32 (sb, off, "functionCount", hh.functionCount);
|
||||
off += 4;
|
||||
hbc_header_print_u32 (sb, off, "stringKindCount", hh.stringKindCount);
|
||||
off += 4;
|
||||
hbc_header_print_u32 (sb, off, "identifierCount", hh.identifierCount);
|
||||
off += 4;
|
||||
hbc_header_print_u32 (sb, off, "stringCount", hh.stringCount);
|
||||
off += 4;
|
||||
hbc_header_print_u32 (sb, off, "overflowStringCount", hh.overflowStringCount);
|
||||
off += 4;
|
||||
hbc_header_print_u32 (sb, off, "stringStorageSize", hh.stringStorageSize);
|
||||
off += 4;
|
||||
if (hh.version >= 87) {
|
||||
hbc_header_print_u32 (sb, off, "bigIntCount", hh.bigIntCount);
|
||||
off += 4;
|
||||
hbc_header_print_u32 (sb, off, "bigIntStorageSize", hh.bigIntStorageSize);
|
||||
off += 4;
|
||||
}
|
||||
hbc_header_print_u32 (sb, off, "regExpCount", hh.regExpCount);
|
||||
off += 4;
|
||||
hbc_header_print_u32 (sb, off, "regExpStorageSize", hh.regExpStorageSize);
|
||||
off += 4;
|
||||
hbc_header_print_u32 (sb, off, "literalValueBufferSize", hh.literalValueBufferSize);
|
||||
off += 4;
|
||||
hbc_header_print_u32 (sb, off, "objKeyBufferSize", hh.objKeyBufferSize);
|
||||
off += 4;
|
||||
if (hh.version >= 97) {
|
||||
hbc_header_print_u32 (sb, off, "objShapeTableCount", hh.objShapeTableCount);
|
||||
off += 4;
|
||||
} else {
|
||||
hbc_header_print_u32 (sb, off, "objValueBufferSize", hh.objValueBufferSize);
|
||||
off += 4;
|
||||
}
|
||||
if (hh.version >= 99) {
|
||||
hbc_header_print_u32 (sb, off, "numStringSwitchImms", hh.numStringSwitchImms);
|
||||
off += 4;
|
||||
}
|
||||
hbc_header_print_u32 (sb, off, "segmentID", hh.segmentID);
|
||||
off += 4;
|
||||
hbc_header_print_u32 (sb, off, "cjsModuleCount", hh.cjsModuleCount);
|
||||
off += 4;
|
||||
if (hh.version >= 84) {
|
||||
hbc_header_print_u32 (sb, off, "functionSourceCount", hh.functionSourceCount);
|
||||
off += 4;
|
||||
}
|
||||
hbc_header_print_u32 (sb, off, "debugInfoOffset", hh.debugInfoOffset);
|
||||
off += 4;
|
||||
hbc_header_print_bool (sb, off, "staticBuiltins", hh.staticBuiltins);
|
||||
hbc_header_print_bool (sb, off, "cjsModulesStaticallyResolved", hh.cjsModulesStaticallyResolved);
|
||||
hbc_header_print_bool (sb, off, "hasAsync", hh.hasAsync);
|
||||
return r_strbuf_drain (sb);
|
||||
}
|
||||
|
||||
static void hbc_fields_append_u32(RList *ret, ut64 *off, const char *name, ut32 value) {
|
||||
r_list_append (ret, r_bin_field_new (*off, *off, value, 4, name, NULL, "x", false));
|
||||
*off += 4;
|
||||
}
|
||||
|
||||
static RList *fields(RBinFile *bf) {
|
||||
RList *ret = r_list_new ();
|
||||
HBC *hbc = get_hbc (bf);
|
||||
if (!ret || !hbc) {
|
||||
return ret;
|
||||
}
|
||||
HBCHeader hh;
|
||||
if (hbc_get_header (hbc, &hh).code != RESULT_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
char hash[41] = { 0 };
|
||||
hbc_source_hash_hex (&hh, hash, sizeof (hash));
|
||||
ut64 off = 0;
|
||||
r_list_append (ret, r_bin_field_new (off, off, hh.magic, 8, "magic", NULL, "q", false));
|
||||
off += 8;
|
||||
hbc_fields_append_u32 (ret, &off, "version", hh.version);
|
||||
r_list_append (ret, r_bin_field_new (off, off, 0, 20, "sourceHash", hash, "s", false));
|
||||
off += 20;
|
||||
hbc_fields_append_u32 (ret, &off, "fileLength", hh.fileLength);
|
||||
hbc_fields_append_u32 (ret, &off, "globalCodeIndex", hh.globalCodeIndex);
|
||||
hbc_fields_append_u32 (ret, &off, "functionCount", hh.functionCount);
|
||||
hbc_fields_append_u32 (ret, &off, "stringKindCount", hh.stringKindCount);
|
||||
hbc_fields_append_u32 (ret, &off, "identifierCount", hh.identifierCount);
|
||||
hbc_fields_append_u32 (ret, &off, "stringCount", hh.stringCount);
|
||||
hbc_fields_append_u32 (ret, &off, "overflowStringCount", hh.overflowStringCount);
|
||||
hbc_fields_append_u32 (ret, &off, "stringStorageSize", hh.stringStorageSize);
|
||||
if (hh.version >= 87) {
|
||||
hbc_fields_append_u32 (ret, &off, "bigIntCount", hh.bigIntCount);
|
||||
hbc_fields_append_u32 (ret, &off, "bigIntStorageSize", hh.bigIntStorageSize);
|
||||
}
|
||||
hbc_fields_append_u32 (ret, &off, "regExpCount", hh.regExpCount);
|
||||
hbc_fields_append_u32 (ret, &off, "regExpStorageSize", hh.regExpStorageSize);
|
||||
hbc_fields_append_u32 (ret, &off, "literalValueBufferSize", hh.literalValueBufferSize);
|
||||
hbc_fields_append_u32 (ret, &off, "objKeyBufferSize", hh.objKeyBufferSize);
|
||||
if (hh.version >= 97) {
|
||||
hbc_fields_append_u32 (ret, &off, "objShapeTableCount", hh.objShapeTableCount);
|
||||
} else {
|
||||
hbc_fields_append_u32 (ret, &off, "objValueBufferSize", hh.objValueBufferSize);
|
||||
}
|
||||
if (hh.version >= 99) {
|
||||
hbc_fields_append_u32 (ret, &off, "numStringSwitchImms", hh.numStringSwitchImms);
|
||||
}
|
||||
hbc_fields_append_u32 (ret, &off, "segmentID", hh.segmentID);
|
||||
hbc_fields_append_u32 (ret, &off, "cjsModuleCount", hh.cjsModuleCount);
|
||||
if (hh.version >= 84) {
|
||||
hbc_fields_append_u32 (ret, &off, "functionSourceCount", hh.functionSourceCount);
|
||||
}
|
||||
hbc_fields_append_u32 (ret, &off, "debugInfoOffset", hh.debugInfoOffset);
|
||||
ut32 flags = (hh.staticBuiltins? 1: 0) | (hh.cjsModulesStaticallyResolved? 2: 0) | (hh.hasAsync? 4: 0);
|
||||
r_list_append (ret, r_bin_field_new (off, off, flags, 1, "flags", NULL, "b", false));
|
||||
r_list_append (ret, r_bin_field_new (off, off, hh.staticBuiltins? 1: 0, 1, "staticBuiltins", NULL, "b", false));
|
||||
r_list_append (ret, r_bin_field_new (off, off, hh.cjsModulesStaticallyResolved? 1: 0, 1, "cjsModulesStaticallyResolved", NULL, "b", false));
|
||||
r_list_append (ret, r_bin_field_new (off, off, hh.hasAsync? 1: 0, 1, "hasAsync", NULL, "b", false));
|
||||
return ret;
|
||||
}
|
||||
|
||||
const RBinPlugin r_bin_plugin_r2hermes = {
|
||||
.meta = {
|
||||
.name = "hbc.bin",
|
||||
@@ -329,10 +880,22 @@ const RBinPlugin r_bin_plugin_r2hermes = {
|
||||
.destroy = &destroy,
|
||||
.check = &check,
|
||||
.entries = &entries,
|
||||
#if R2_ABIVERSION >= 104
|
||||
.sections_vec = §ions_vec,
|
||||
.symbols_vec = &symbols_vec,
|
||||
.imports_vec = &imports_vec,
|
||||
.strings = &strings_vec,
|
||||
#else
|
||||
.sections = §ions,
|
||||
.baddr = &baddr,
|
||||
.symbols = &symbols,
|
||||
.strings = &strings,
|
||||
.imports = &imports,
|
||||
.strings = &strings_list,
|
||||
#endif
|
||||
.baddr = &baddr,
|
||||
.libs = &libs,
|
||||
.lines = &lines,
|
||||
.header = &header,
|
||||
.fields = &fields,
|
||||
};
|
||||
|
||||
#ifndef R2_PLUGIN_INCORE
|
||||
|
||||
+828
-105
File diff suppressed because it is too large.
Load diff
@@ -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
File diff suppressed because it is too large.
Load diff
@@ -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
@@ -1,4 +1,4 @@
|
||||
/* radare2 - LGPL - Copyright 2025-2026 - pancake */
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#ifndef R2HERMES_UTILS_INC_C
|
||||
#define R2HERMES_UTILS_INC_C
|
||||
|
||||
+664
-400
File diff suppressed because it is too large.
Load diff
+18
-2
@@ -1,4 +1,4 @@
|
||||
NAME=r2hermes test
|
||||
NAME=r2hermes test (va)
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e asm.sub.varmin=16
|
||||
CMDS=<<EOF
|
||||
@@ -9,7 +9,23 @@ EXPECT=<<EOF
|
||||
hbc
|
||||
;-- entry0:
|
||||
;-- global_0x000000b0:
|
||||
0x000000b0 73000100 load_const_string r0, str.print_123__ ; 0xa2 ; "print(123);"
|
||||
0x100000b0 73000100 load_const_string r0, 0xa2 ; yes.js:1:1 ; str.print_123__
|
||||
; 0x100000a2 ; "print(123);"
|
||||
0x100000b4 5e000000 direct_eval r0, r0, 0 ; yes.js:1:5
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=r2hermes test (no va)
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e asm.sub.varmin=16 -e io.va=false
|
||||
CMDS=<<EOF
|
||||
-a
|
||||
pd 2 @ 0xb0
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
hbc
|
||||
0x000000b0 73000100 load_const_string r0, 0xa2 ; str.print_123__
|
||||
; 0x100000a2
|
||||
0x000000b4 5e000000 direct_eval r0, r0, 0
|
||||
EOF
|
||||
RUN
|
||||
+254
-17
@@ -18,70 +18,84 @@ EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hi file info small
|
||||
NAME=r2hermes-? help eval command
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
pd:hi~Version
|
||||
r2hermes-?~r2hermes-E
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
r2hermes-E - List functions containing eval instructions
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=r2hermes-H file info small
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
r2hermes-H~Version
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
Version: 96
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hi file length small
|
||||
NAME=r2hermes-H file length small
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
pd:hi~File Length
|
||||
r2hermes-H~File Length
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
File Length: 292 bytes
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hi function count small
|
||||
NAME=r2hermes-H function count small
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
pd:hi~Functions
|
||||
r2hermes-H~Functions
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
Functions: 1
|
||||
Functions With Debug Info: 1
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hi string count small
|
||||
NAME=r2hermes-H string count small
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
pd:hi~Strings
|
||||
r2hermes-H~Strings
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
Strings: 2
|
||||
Overflow Strings: 0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hi file info large
|
||||
NAME=r2hermes-H file info large
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hi~Functions
|
||||
r2hermes-H~Functions
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
Functions: 5388
|
||||
Functions With Debug Info: 0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hi strings large
|
||||
NAME=r2hermes-H strings large
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hi~Strings
|
||||
r2hermes-H~Strings
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
Strings: 6170
|
||||
Overflow Strings: 7
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hi identifiers large
|
||||
NAME=r2hermes-H identifiers large
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hi~Identifiers
|
||||
r2hermes-H~Identifiers
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
Identifiers: 3449
|
||||
@@ -122,6 +136,26 @@ EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=r2hermes-E list eval sites
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
r2hermes-E
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x100000b4 f=0 +0x4 func=0x100000b0 name=global
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=r2hermes-E list eval sites large
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
r2hermes-E
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x100d166b f=4953 +0x28 func=0x100d1643 name=onPress
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hc decompile function return
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
@@ -178,13 +212,25 @@ EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hj json output
|
||||
NAME=pd:hj json code field
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
pd:hj 0~function_id
|
||||
pd:hj 0~{code}
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
{"function_id":0,"decompilation":"function global() {\n r0 = \"print(123);\";\n r0 = eval (r0);\n return r0;\n}\n\n\n"}
|
||||
function global() {\n r0 = \"print(123);\";\n r0 = eval (r0);\n return r0;\n}\n\n\n
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hj json annotation offsets
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
pd:hj 0~{annotations[0].type}
|
||||
pd:hj 0~{annotations[0].offset}
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
offset
|
||||
176
|
||||
EOF
|
||||
RUN
|
||||
|
||||
@@ -198,3 +244,194 @@ EXPECT=<<EOF
|
||||
r0 = eval (r0);
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hc for-in loop structuring
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hc 6~for (,continue
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
for (r6 in r5)
|
||||
if (!r11) continue;
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hc for-in no back-edge goto or loop-top label
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hc 6~for (r6 in,goto loc_0003e68d,loc_0003e68d:
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
for (r6 in r5)
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hc switch fallthrough case grouping
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hc 562~case 26,case 27
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
case 26:
|
||||
case 27: goto loc_0004d144;
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hc comparison condition goto-form no double parens
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hc 5~if (r3!==r1)
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
if (r3!==r1) goto loc_0003e637;
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hc comparison condition structured-if negated operator
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hc 46~if (r5!==r0)
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
if (r5!==r0) {
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hc if-blocks structured in catch-containing function
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hc 12~if (r7) {
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
if (r7) {
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hc structured try/catch for return-terminated try
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hc 189~try {,} catch (r0) {
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
try {
|
||||
} catch (r0) {
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hc multiple independent try/catch blocks structured
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hc 344~} catch
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
} catch (r0) {
|
||||
} catch (r1) {
|
||||
} catch (r1) {
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hc do-while loop structuring with no back-edge goto or loop-top label
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hc 513~do {,while (r1,goto
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
do {
|
||||
} while (r1>r2);
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hc materialized guard folds into a while loop
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hc 199~while (r2,do {,goto
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
while (r2<r1) {
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hc new Array call has no stray space after the parenthesis
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hc 513~new Array(0
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
r0 = new Array(0);
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hc select_object args have single spacing
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hc 310~select_object(r
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
r2 = select_object(r3, r2);
|
||||
r1 = select_object(r2, r1);
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hc forward substitution folds single-use temp into call
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hc 199~.call (undefined
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
r0 = r2 .call (undefined, r1);
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hc if-else reconstruction with no trailing goto or join label
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hc 311~if (!r0) {,} else {,goto,loc_
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
if (!r0) {
|
||||
} else {
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hc short-circuit OR merges consecutive same-target conditional gotos
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hc 730~ || r2===r20 ||
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
if (r1===r20 || r2===r20 || r3===r20 || r4===r20) goto loc_00055f28;
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hc while loop folds entry guard and do-while into a while header
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hc 2303~while (r2,do {,goto
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
while (r2!=r1) {
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hc infinite loop structures as for(;;) with continue and break
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hc 3896~for (,continue,break
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
for (;;) {
|
||||
continue;
|
||||
break;
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hc break reconstruction renders loop-exit goto as break
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hc 3406~while (r0,break,goto loc
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
while (r0<r2) {
|
||||
if (r6>=r5) break;
|
||||
EOF
|
||||
RUN
|
||||
+616
-30
@@ -1,4 +1,4 @@
|
||||
NAME=hbc disasm basic instructions
|
||||
NAME=hbc disasm basic instructions (va)
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e asm.sub.varmin=16
|
||||
CMDS=<<EOF
|
||||
@@ -7,7 +7,22 @@ EOF
|
||||
EXPECT=<<EOF
|
||||
;-- entry0:
|
||||
;-- global_0x000000b0:
|
||||
0x000000b0 73000100 load_const_string r0, str.print_123__ ; 0xa2 ; "print(123);"
|
||||
0x100000b0 73000100 load_const_string r0, 0xa2 ; yes.js:1:1 ; str.print_123__
|
||||
; 0x100000a2 ; "print(123);"
|
||||
0x100000b4 5e000000 direct_eval r0, r0, 0 ; yes.js:1:5
|
||||
0x100000b8 5c00 ret r0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc disasm basic instructions (no va)
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e asm.sub.varmin=16 -e io.va=false
|
||||
CMDS=<<EOF
|
||||
pd 3 @ 0xb0
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x000000b0 73000100 load_const_string r0, 0xa2 ; str.print_123__
|
||||
; 0x100000a2
|
||||
0x000000b4 5e000000 direct_eval r0, r0, 0
|
||||
0x000000b8 5c00 ret r0
|
||||
EOF
|
||||
@@ -43,36 +58,70 @@ CMDS=<<EOF
|
||||
i~cpu
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
cpu 96
|
||||
cpu v96
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc symbols list
|
||||
NAME=hbc symbols list (va)
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e asm.sub.varmin=16
|
||||
CMDS=<<EOF
|
||||
is~FUNC
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0 0x000000b0 0x100000b0 NONE FUNC 10 global_0x000000b0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc symbols list (no va)
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e asm.sub.varmin=16 -e io.va=false
|
||||
CMDS=<<EOF
|
||||
is~FUNC
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0 0x000000b0 0x000000b0 NONE FUNC 10 global_0x000000b0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc entry point
|
||||
NAME=hbc entry point (va)
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e asm.sub.varmin=16
|
||||
CMDS=<<EOF
|
||||
ie~program
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x000000b0 0x100000b0 ---------- ---------- program
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc entry point (no va)
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e asm.sub.varmin=16 -e io.va=false
|
||||
CMDS=<<EOF
|
||||
ie~program
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x000000b0 0x000000b0 ---------- ---------- program
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc disasm ret instruction
|
||||
NAME=hbc disasm ret instruction (va)
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e asm.sub.varmin=16
|
||||
CMDS=<<EOF
|
||||
s 0x100000b8
|
||||
pd 1
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x100000b8 5c00 ret r0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc disasm ret instruction (no va)
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e asm.sub.varmin=16 -e io.va=false
|
||||
CMDS=<<EOF
|
||||
s 0xb8
|
||||
pd 1
|
||||
EOF
|
||||
@@ -81,24 +130,50 @@ EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc disasm load_const_string
|
||||
NAME=hbc disasm load_const_string (va)
|
||||
ARGS=-e asm.sub.varmin=16
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
s 0x100000b0
|
||||
pd 1
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
;-- entry0:
|
||||
;-- global_0x000000b0:
|
||||
0x100000b0 73000100 load_const_string r0, 0xa2 ; yes.js:1:1 ; str.print_123__
|
||||
; 0x100000a2 ; "print(123);"
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc disasm load_const_string (no va)
|
||||
ARGS=-e asm.sub.varmin=16 -e io.va=false
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
s 0xb0
|
||||
pd 1
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
;-- entry0:
|
||||
;-- global_0x000000b0:
|
||||
0x000000b0 73000100 load_const_string r0, str.print_123__ ; 0xa2 ; "print(123);"
|
||||
0x000000b0 73000100 load_const_string r0, 0xa2 ; str.print_123__
|
||||
; 0x100000a2
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc disasm direct_eval
|
||||
NAME=hbc disasm direct_eval (va)
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e asm.sub.varmin=16
|
||||
CMDS=<<EOF
|
||||
s 0x100000b4
|
||||
pd 1
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x100000b4 5e000000 direct_eval r0, r0, 0 ; yes.js:1:5
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc disasm direct_eval (no va)
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e asm.sub.varmin=16 -e io.va=false
|
||||
CMDS=<<EOF
|
||||
s 0xb4
|
||||
pd 1
|
||||
EOF
|
||||
@@ -107,18 +182,29 @@ EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc sections
|
||||
NAME=hbc sections (va)
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e asm.sub.varmin=16
|
||||
CMDS=<<EOF
|
||||
iS~hermes
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0 0x00000000 0x124 0x00000000 0x124 -r-- 0x0 ---- hermes_bytecode
|
||||
0 0x00000000 0x124 0x10000000 0x124 -r-x 0x0 ---- hermes_bytecode
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc seek and disasm
|
||||
NAME=hbc sections (no va)
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e asm.sub.varmin=16 -e io.va=false
|
||||
CMDS=<<EOF
|
||||
iS~hermes
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0 0x00000000 0x124 0x00000000 0x124 -r-x 0x0 ---- hermes_bytecode
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc seek and disasm (va)
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e asm.sub.varmin=16
|
||||
CMDS=<<EOF
|
||||
@@ -128,16 +214,32 @@ EOF
|
||||
EXPECT=<<EOF
|
||||
;-- entry0:
|
||||
;-- global_0x000000b0:
|
||||
0x000000b0 73000100 load_const_string r0, str.print_123__ ; 0xa2 ; "print(123);"
|
||||
0x000000b4 5e000000 direct_eval r0, r0, 0
|
||||
0x100000b0 73000100 load_const_string r0, 0xa2 ; yes.js:1:1 ; str.print_123__
|
||||
; 0x100000a2 ; "print(123);"
|
||||
0x100000b4 5e000000 direct_eval r0, r0, 0 ; yes.js:1:5
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc print hex bytes
|
||||
NAME=hbc print hex bytes (va)
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e asm.sub.varmin=16
|
||||
CMDS=<<EOF
|
||||
s 0x100000b0
|
||||
-e hex.header=true
|
||||
px 10
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
- offset - B0B1 B2B3 B4B5 B6B7 B8B9 BABB BCBD BEBF 0123456789ABCDEF
|
||||
0x100000b0 7300 0100 5e00 0000 5c00 s...^...\.
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc print hex bytes (no va)
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e asm.sub.varmin=16 -e io.va=false
|
||||
CMDS=<<EOF
|
||||
s 0xb0
|
||||
-e hex.header=true
|
||||
px 10
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
@@ -146,10 +248,23 @@ EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc unreachable padding
|
||||
NAME=hbc unreachable padding (va)
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e asm.sub.varmin=16
|
||||
CMDS=<<EOF
|
||||
s 0x100000ba
|
||||
pd 2
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x100000ba 00 unreachable
|
||||
0x100000bb 00 unreachable
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc unreachable padding (no va)
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e asm.sub.varmin=16 -e io.va=false
|
||||
CMDS=<<EOF
|
||||
s 0xba
|
||||
pd 2
|
||||
EOF
|
||||
@@ -169,21 +284,82 @@ EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc get_global_object instruction
|
||||
NAME=hbc imports js require
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
ARGS=-e asm.sub.varmin=16
|
||||
CMDS=<<EOF
|
||||
ii~js.__r
|
||||
ii~?
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
3048 0x00000000 NONE FUNC react-native js.__r
|
||||
58
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc imports native module
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
ARGS=-e asm.sub.varmin=16
|
||||
CMDS=<<EOF
|
||||
ii~native.NativeModules
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
3894 0x00000000 NONE FUNC react-native native.NativeModules
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc exports module symbol
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
ARGS=-e asm.sub.varmin=16
|
||||
CMDS=<<EOF
|
||||
iE~hbc.export.module.RN$AppRegistry
|
||||
iE~?
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
3630 0x00071961 0x10071961 GLOBAL OBJ 1 hbc.export.module.RN$AppRegistry
|
||||
451
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc get_global_object instruction (va)
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
s 0x1003e510
|
||||
pd 1
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
;-- clear_0x0003e510:
|
||||
0x1003e510 3000 get_global_object r0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc get_global_object instruction (no va)
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
ARGS=-e io.va=false
|
||||
CMDS=<<EOF
|
||||
s 0x3e510
|
||||
pd 1
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
;-- clear_0x0003e510:
|
||||
0x0003e510 3000 get_global_object r0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc try_get_by_id instruction
|
||||
NAME=hbc try_get_by_id instruction (va)
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
s 0x1003e512
|
||||
ao~mnemonic
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
mnemonic: try_get_by_id
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc try_get_by_id instruction (no va)
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
ARGS=-e io.va=false
|
||||
CMDS=<<EOF
|
||||
s 0x3e512
|
||||
ao~mnemonic
|
||||
EOF
|
||||
@@ -192,9 +368,21 @@ mnemonic: try_get_by_id
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc get_by_id_short instruction
|
||||
NAME=hbc get_by_id_short instruction (va)
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
s 0x1003e518
|
||||
ao~mnemonic
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
mnemonic: get_by_id_short
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc get_by_id_short instruction (no va)
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
ARGS=-e io.va=false
|
||||
CMDS=<<EOF
|
||||
s 0x3e518
|
||||
ao~mnemonic
|
||||
EOF
|
||||
@@ -203,9 +391,21 @@ mnemonic: get_by_id_short
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc create_this instruction
|
||||
NAME=hbc create_this instruction (va)
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
s 0x1003e51d
|
||||
pd 1
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x1003e51d 6a010100 create_this r1, r1, r0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc create_this instruction (no va)
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
ARGS=-e io.va=false
|
||||
CMDS=<<EOF
|
||||
s 0x3e51d
|
||||
pd 1
|
||||
EOF
|
||||
@@ -214,9 +414,21 @@ EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc mov instruction
|
||||
NAME=hbc mov instruction (va)
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
s 0x1003e521
|
||||
pd 1
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x1003e521 080201 mov r2, r1
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc mov instruction (no va)
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
ARGS=-e io.va=false
|
||||
CMDS=<<EOF
|
||||
s 0x3e521
|
||||
pd 1
|
||||
EOF
|
||||
@@ -225,9 +437,21 @@ EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc construct instruction
|
||||
NAME=hbc construct instruction (va)
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
s 0x1003e524
|
||||
pd 1
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x1003e524 50000001 construct r0, r0, 1
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc construct instruction (no va)
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
ARGS=-e io.va=false
|
||||
CMDS=<<EOF
|
||||
s 0x3e524
|
||||
pd 1
|
||||
EOF
|
||||
@@ -236,9 +460,21 @@ EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc select_object instruction
|
||||
NAME=hbc select_object instruction (va)
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
s 0x1003e528
|
||||
pd 1
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x1003e528 6b000100 select_object r0, r1, r0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc select_object instruction (no va)
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
ARGS=-e io.va=false
|
||||
CMDS=<<EOF
|
||||
s 0x3e528
|
||||
pd 1
|
||||
EOF
|
||||
@@ -247,9 +483,21 @@ EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc get_environment instruction
|
||||
NAME=hbc get_environment instruction (va)
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
s 0x1003e52c
|
||||
pd 1
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x1003e52c 290100 get_environment r1, 0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc get_environment instruction (no va)
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
ARGS=-e io.va=false
|
||||
CMDS=<<EOF
|
||||
s 0x3e52c
|
||||
pd 1
|
||||
EOF
|
||||
@@ -258,9 +506,21 @@ EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc store_to_environment instruction
|
||||
NAME=hbc store_to_environment instruction (va)
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
s 0x1003e52f
|
||||
pd 1
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x1003e52f 2a010100 store_to_environment r1, 1, r0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc store_to_environment instruction (no va)
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
ARGS=-e io.va=false
|
||||
CMDS=<<EOF
|
||||
s 0x3e52f
|
||||
pd 1
|
||||
EOF
|
||||
@@ -269,9 +529,25 @@ EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc complete function disasm
|
||||
NAME=hbc complete function disasm (va)
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
s 0x1003e510
|
||||
pd 3~get_global
|
||||
pd 1 @ 0x1003e51d~create_this
|
||||
pd 1 @ 0x1003e533~ret
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x1003e510 3000 get_global_object r0
|
||||
0x1003e51d 6a010100 create_this r1, r1, r0
|
||||
0x1003e533 5c00 ret r0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc complete function disasm (no va)
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
ARGS=-e io.va=false
|
||||
CMDS=<<EOF
|
||||
s 0x3e510
|
||||
pd 3~get_global
|
||||
pd 1 @ 0x3e51d~create_this
|
||||
@@ -284,7 +560,7 @@ EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc assembler
|
||||
NAME=hbc pa ret
|
||||
FILE=--
|
||||
CMDS=<<EOF
|
||||
-a hbc
|
||||
@@ -294,3 +570,313 @@ EXPECT=<<EOF
|
||||
5c00
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc pa get_global_object
|
||||
FILE=--
|
||||
CMDS=<<EOF
|
||||
-a hbc
|
||||
pa get_global_object r0
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
3000
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc pa mov two regs
|
||||
FILE=--
|
||||
CMDS=<<EOF
|
||||
-a hbc
|
||||
pa mov r2, r1
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
080201
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc pa construct three operands
|
||||
FILE=--
|
||||
CMDS=<<EOF
|
||||
-a hbc
|
||||
pa construct r0, r0, 1
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
50000001
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc pa select_object three regs
|
||||
FILE=--
|
||||
CMDS=<<EOF
|
||||
-a hbc
|
||||
pa select_object r0, r1, r0
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
6b000100
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc pa store_to_environment
|
||||
FILE=--
|
||||
CMDS=<<EOF
|
||||
-a hbc
|
||||
pa store_to_environment r1, 1, r0
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
2a010100
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc pa rejects extra operand
|
||||
FILE=--
|
||||
CMDS=<<EOF
|
||||
-a hbc
|
||||
"pa ret r0, r1"
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc pa rejects multiple extra operands
|
||||
FILE=--
|
||||
CMDS=<<EOF
|
||||
-a hbc
|
||||
"pa ret r0, r1, r2"
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc pa rejects extra operand on zero-arg instr
|
||||
FILE=--
|
||||
CMDS=<<EOF
|
||||
-a hbc
|
||||
"pa get_global_object r0, r1"
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc pa rejects missing operand
|
||||
FILE=--
|
||||
CMDS=<<EOF
|
||||
-a hbc
|
||||
pa ret
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc pa rejects truncated mov
|
||||
FILE=--
|
||||
CMDS=<<EOF
|
||||
-a hbc
|
||||
pa mov r2
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc pa rejects unknown mnemonic
|
||||
FILE=--
|
||||
CMDS=<<EOF
|
||||
-a hbc
|
||||
pa bogus_op r0
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc pad ret
|
||||
FILE=--
|
||||
CMDS=<<EOF
|
||||
-a hbc
|
||||
pad 5c00
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
ret r0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc pad get_global_object
|
||||
FILE=--
|
||||
CMDS=<<EOF
|
||||
-a hbc
|
||||
pad 3000
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
get_global_object r0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc pad mov
|
||||
FILE=--
|
||||
CMDS=<<EOF
|
||||
-a hbc
|
||||
pad 080201
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
mov r2, r1
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc pad respects asm.cpu v99
|
||||
FILE=--
|
||||
CMDS=<<EOF
|
||||
-a hbc
|
||||
e asm.cpu=v99
|
||||
pad 010203040506
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
new_object_with_buffer r2, 1027, 1541
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc pa respects asm.cpu v99
|
||||
FILE=--
|
||||
CMDS=<<EOF
|
||||
-a hbc
|
||||
e asm.cpu=v99
|
||||
pa new_object_with_buffer r2, 1027, 1541
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
010203040506
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc pad construct
|
||||
FILE=--
|
||||
CMDS=<<EOF
|
||||
-a hbc
|
||||
pad 50000001
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
construct r0, r0, 1
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc pad select_object
|
||||
FILE=--
|
||||
CMDS=<<EOF
|
||||
-a hbc
|
||||
pad 6b000100
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
select_object r0, r1, r0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc pad store_to_environment
|
||||
FILE=--
|
||||
CMDS=<<EOF
|
||||
-a hbc
|
||||
pad 2a010100
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
store_to_environment r1, 1, r0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc pa+pad roundtrip ret
|
||||
FILE=--
|
||||
CMDS=<<EOF
|
||||
-a hbc
|
||||
pad `pa ret r0`
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
ret r0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc pa+pad roundtrip mov
|
||||
FILE=--
|
||||
CMDS=<<EOF
|
||||
-a hbc
|
||||
pad `pa mov r2, r1`
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
mov r2, r1
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc pa+pad roundtrip construct
|
||||
FILE=--
|
||||
CMDS=<<EOF
|
||||
-a hbc
|
||||
pad `pa construct r0, r0, 1`
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
construct r0, r0, 1
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc pi ret after wx
|
||||
FILE=malloc://16
|
||||
CMDS=<<EOF
|
||||
-a hbc
|
||||
wx 5c00
|
||||
pi 1
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
ret r0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc pi mov after wx
|
||||
FILE=malloc://16
|
||||
CMDS=<<EOF
|
||||
-a hbc
|
||||
wx 080201
|
||||
pi 1
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
mov r2, r1
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc pi construct after wx
|
||||
FILE=malloc://16
|
||||
CMDS=<<EOF
|
||||
-a hbc
|
||||
wx 50000001
|
||||
pi 1
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
construct r0, r0, 1
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc pa+wx+pi roundtrip ret
|
||||
FILE=malloc://16
|
||||
CMDS=<<EOF
|
||||
-a hbc
|
||||
wx `pa ret r0`
|
||||
pi 1
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
ret r0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc pa+wx+pi roundtrip mov
|
||||
FILE=malloc://16
|
||||
CMDS=<<EOF
|
||||
-a hbc
|
||||
wx `pa mov r2, r1`
|
||||
pi 1
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
mov r2, r1
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc pa+wx+pi roundtrip store_to_environment
|
||||
FILE=malloc://16
|
||||
CMDS=<<EOF
|
||||
-a hbc
|
||||
wx `pa store_to_environment r1, 1, r0`
|
||||
pi 1
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
store_to_environment r1, 1, r0
|
||||
EOF
|
||||
RUN
|
||||
@@ -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
|
||||
@@ -0,0 +1,172 @@
|
||||
NAME=pd:hs help entry
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
pd:h?~pd:hs
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
pd:hs [id] - List source-line information
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hs source lines small
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
pd:hs
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x000000b0 f=0 +0x0 yes.js:1:1 stmt=0
|
||||
0x000000b4 f=0 +0x4 yes.js:1:5 stmt=0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hs source lines filter
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
pd:hs 0
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x000000b0 f=0 +0x0 yes.js:1:1 stmt=0
|
||||
0x000000b4 f=0 +0x4 yes.js:1:5 stmt=0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hs source lines filter miss
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
pd:hs 99
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hs empty source lines
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hs
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=r2hermes-H debug info present
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
r2hermes-H~Debug Info Present
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
Debug Info Present: yes
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=r2hermes-H source locations size
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
r2hermes-H~Source Locations
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
Source Locations: 13 bytes
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=r2hermes-H debug files count
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
r2hermes-H~Debug Files
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
Debug Files: 1 (6 bytes)
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=iI linenum exposed (debug info)
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
iI~linenum
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
linenum true
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=iI linenum stripped
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
iI~linenum
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
linenum false
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=iI linenum honours file truth with bin.dbginfo=false
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e bin.dbginfo=false
|
||||
CMDS=<<EOF
|
||||
iI~linenum,stripped
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
linenum true
|
||||
stripped false
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=iI stripped file still stripped with bin.dbginfo=false
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
ARGS=-e bin.dbginfo=false
|
||||
CMDS=<<EOF
|
||||
iI~linenum,stripped
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
linenum false
|
||||
stripped true
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=iI stripped file stripped with bin.dbginfo=true
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
ARGS=-e bin.dbginfo=true
|
||||
CMDS=<<EOF
|
||||
iI~linenum,stripped
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
linenum false
|
||||
stripped true
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=CL populated with bin.dbginfo=true
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e bin.dbginfo=true
|
||||
CMDS=<<EOF
|
||||
CL
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
file: yes.js
|
||||
line: 1
|
||||
colu: 1
|
||||
addr: 0x100000b0
|
||||
file: yes.js
|
||||
line: 1
|
||||
colu: 5
|
||||
addr: 0x100000b4
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=CL empty with bin.dbginfo=false
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e bin.dbginfo=false
|
||||
CMDS=<<EOF
|
||||
CL
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=CL empty on stripped file
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
CL
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
+3
-2
@@ -1,13 +1,14 @@
|
||||
test_src = files(
|
||||
# Add test files here as needed
|
||||
'test_debug_info.c',
|
||||
)
|
||||
|
||||
if test_src.length() > 0
|
||||
executable(
|
||||
run_tests = executable(
|
||||
'run_tests',
|
||||
test_src,
|
||||
link_with: libhbc,
|
||||
include_directories: include_dirs,
|
||||
install: false,
|
||||
)
|
||||
test('debug-info', run_tests, args: [meson.project_source_root()])
|
||||
endif
|
||||
@@ -0,0 +1,83 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/hbc.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define CHECK(x) do { \
|
||||
if (!(x)) { \
|
||||
fprintf (stderr, "check failed: %s:%d: %s\n", __FILE__, __LINE__, #x); \
|
||||
return 1; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static int test_small_debug_info(const char *root) {
|
||||
char path[512];
|
||||
snprintf (path, sizeof (path), "%s/test/bins/hbc/bespoke_eval.hbc", root);
|
||||
|
||||
HBC *hbc = NULL;
|
||||
CHECK (hbc_open (path, &hbc).code == RESULT_SUCCESS);
|
||||
|
||||
HBCDebugInfo di = { 0 };
|
||||
CHECK (hbc_get_debug_info (hbc, &di).code == RESULT_SUCCESS);
|
||||
CHECK (di.has_debug_info);
|
||||
CHECK (di.filename_count == 1);
|
||||
CHECK (di.filename_storage_size == 6);
|
||||
CHECK (di.file_region_count == 1);
|
||||
CHECK (di.functions_with_debug_info == 1);
|
||||
CHECK (di.source_locations_size == 13);
|
||||
CHECK (di.scope_desc_data_size == 3);
|
||||
CHECK (di.textified_data_size == 1);
|
||||
|
||||
HBCSourceLineArray lines = { 0 };
|
||||
CHECK (hbc_get_source_lines (hbc, &lines).code == RESULT_SUCCESS);
|
||||
CHECK (hbc_has_source_lines (hbc));
|
||||
CHECK (lines.count == 2);
|
||||
CHECK (lines.lines[0].address == 0xb0);
|
||||
CHECK (lines.lines[0].function_address == 0);
|
||||
CHECK (lines.lines[0].line == 1);
|
||||
CHECK (lines.lines[0].column == 1);
|
||||
CHECK (!strcmp (lines.lines[0].filename, "yes.js"));
|
||||
CHECK (lines.lines[1].address == 0xb4);
|
||||
CHECK (lines.lines[1].function_address == 4);
|
||||
CHECK (lines.lines[1].line == 1);
|
||||
CHECK (lines.lines[1].column == 5);
|
||||
|
||||
hbc_free_source_lines (&lines);
|
||||
hbc_close (hbc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_empty_debug_info(const char *root) {
|
||||
char path[512];
|
||||
snprintf (path, sizeof (path), "%s/test/bins/hbc/index.android.bundle", root);
|
||||
|
||||
HBC *hbc = NULL;
|
||||
CHECK (hbc_open (path, &hbc).code == RESULT_SUCCESS);
|
||||
|
||||
HBCDebugInfo di = { 0 };
|
||||
CHECK (hbc_get_debug_info (hbc, &di).code == RESULT_SUCCESS);
|
||||
CHECK (di.has_debug_info);
|
||||
CHECK (di.filename_count == 0);
|
||||
CHECK (di.file_region_count == 0);
|
||||
CHECK (di.functions_with_debug_info == 0);
|
||||
CHECK (di.source_locations_size == 0);
|
||||
CHECK (!hbc_has_source_lines (hbc));
|
||||
|
||||
HBCSourceLineArray lines = { 0 };
|
||||
CHECK (hbc_get_source_lines (hbc, &lines).code == RESULT_SUCCESS);
|
||||
CHECK (lines.count == 0);
|
||||
|
||||
hbc_free_source_lines (&lines);
|
||||
hbc_close (hbc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const char *root = argc > 1? argv[1]: ".";
|
||||
if (test_small_debug_info (root) || test_empty_debug_info (root)) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user