mirror of
https://github.com/radareorg/r2hermes.git
synced 2026-11-08 17:42:33 -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 |
No files matched your search
+42
-13
@@ -1,8 +1,5 @@
|
||||
name: CI
|
||||
|
||||
env:
|
||||
R2V: 6.1.4
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
@@ -26,12 +23,9 @@ jobs:
|
||||
run: sudo apt-get update -y && sudo apt-get install -y build-essential autoconf automake libtool pkg-config
|
||||
- name: Build library
|
||||
run: make
|
||||
- name: Downloading Radare2 Debian packages
|
||||
run: |
|
||||
wget -O r2.deb https://github.com/radareorg/radare2/releases/download/${{env.R2V}}/radare2_${{env.R2V}}_amd64.deb
|
||||
wget -O r2-dev.deb https://github.com/radareorg/radare2/releases/download/${{env.R2V}}/radare2-dev_${{env.R2V}}_amd64.deb
|
||||
- name: Installing Radare2
|
||||
run: sudo dpkg -i r2.deb r2-dev.deb
|
||||
- name: Install radare2
|
||||
id: r2
|
||||
uses: radareorg/github-actions@v1
|
||||
- name: Build r2 plugin
|
||||
run: make r2
|
||||
- name: Run testsuite
|
||||
@@ -52,10 +46,37 @@ jobs:
|
||||
echo "tag=" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
release:
|
||||
name: Create release and upload tarball
|
||||
debian:
|
||||
name: Build Debian package
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build]
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install build deps
|
||||
run: sudo apt-get update -y && sudo apt-get install -y build-essential autoconf automake libtool pkg-config
|
||||
|
||||
- name: Install radare2
|
||||
id: r2
|
||||
uses: radareorg/github-actions@v1
|
||||
|
||||
- name: Build Debian package
|
||||
run: make -C dist/debian SUDO=
|
||||
|
||||
- name: Upload Debian package artifact
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: debian-package
|
||||
path: dist/debian/*.deb
|
||||
if-no-files-found: error
|
||||
|
||||
release:
|
||||
name: Create release and upload artifacts
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build, debian]
|
||||
if: needs.build.outputs.tag != ''
|
||||
|
||||
steps:
|
||||
@@ -71,11 +92,19 @@ jobs:
|
||||
git archive --format=tar.gz --prefix=libhbc-$TAG/ $TAG -o libhbc-$TAG.tgz
|
||||
ls -l libhbc*
|
||||
|
||||
- name: Download Debian package artifact
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: debian-package
|
||||
path: release-artifacts
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/[email protected]
|
||||
with:
|
||||
tag_name: ${{ needs.build.outputs.tag }}
|
||||
files: ./libhbc-${{ needs.build.outputs.tag }}.tgz
|
||||
files: |
|
||||
./libhbc-${{ needs.build.outputs.tag }}.tgz
|
||||
./release-artifacts/*.deb
|
||||
make_latest: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -1,8 +1,5 @@
|
||||
name: Meson Build
|
||||
|
||||
env:
|
||||
R2V: 6.1.4
|
||||
|
||||
on:
|
||||
push:
|
||||
workflow_dispatch: {}
|
||||
@@ -21,16 +18,9 @@ jobs:
|
||||
- name: Install build deps
|
||||
run: sudo apt-get update -y && sudo apt-get install -y build-essential meson ninja-build pkg-config
|
||||
|
||||
- name: Build library
|
||||
- name: Install radare2
|
||||
id: r2
|
||||
uses: radareorg/github-actions@v1
|
||||
|
||||
- name: Build library and r2 plugin with meson
|
||||
run: meson setup builddir && meson compile -C builddir
|
||||
|
||||
- name: Downloading Radare2 Debian packages
|
||||
run: |
|
||||
wget -O r2.deb https://github.com/radareorg/radare2/releases/download/${{env.R2V}}/radare2_${{env.R2V}}_amd64.deb
|
||||
wget -O r2-dev.deb https://github.com/radareorg/radare2/releases/download/${{env.R2V}}/radare2-dev_${{env.R2V}}_amd64.deb
|
||||
|
||||
- name: Installing Radare2
|
||||
run: sudo dpkg -i r2.deb r2-dev.deb
|
||||
|
||||
- name: Build r2 plugin with meson
|
||||
run: meson compile -C builddir
|
||||
@@ -1,8 +1,5 @@
|
||||
name: CI
|
||||
|
||||
env:
|
||||
R2V: 6.1.4
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
name: Windows Build
|
||||
|
||||
env:
|
||||
R2V: 6.1.4
|
||||
|
||||
on:
|
||||
push:
|
||||
workflow_dispatch: {}
|
||||
@@ -29,16 +26,11 @@ jobs:
|
||||
- name: Install dependencies
|
||||
shell: bash
|
||||
run: |
|
||||
pip install meson ninja wget
|
||||
choco install wget
|
||||
pip install meson ninja
|
||||
|
||||
- name: Download Radare2 release
|
||||
shell: bash
|
||||
run: |
|
||||
python -m wget https://github.com/radareorg/radare2/releases/download/${{env.R2V}}/radare2-${{env.R2V}}-w64.zip -o r2.zip
|
||||
unzip r2.zip
|
||||
mv radare2-${{env.R2V}}-w64 radare2 || mv radare2-w64-${{env.R2V}} radare2
|
||||
cp -f radare2/include/libr/r_util/r_event.h radare2/include/libr/r_event.h || true
|
||||
- name: Install radare2
|
||||
id: r2
|
||||
uses: radareorg/github-actions@v1
|
||||
|
||||
- name: Build with Meson
|
||||
shell: cmd
|
||||
|
||||
+22
-5
@@ -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.
|
||||
@@ -81,6 +81,7 @@ test: $(BIN_FILE) $(TEST_BIN)
|
||||
$(BIN_FILE) sl test/bins/hbc/bespoke_eval.hbc | grep -q '0x000000b4 f=0 +0x4 yes.js:1:5 stmt=0'
|
||||
$(BIN_FILE) h test/bins/hbc/bespoke_eval.hbc | grep -q 'Source Locations: 13 bytes'
|
||||
$(BIN_FILE) h test/bins/hbc/index.android.bundle | grep -q 'Source Locations: 0 bytes'
|
||||
$(BIN_FILE) libs test/bins/hbc/index.android.bundle | grep -q 'react-native'
|
||||
|
||||
r2:
|
||||
$(MAKE) clean
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#ifndef LIBHBC_DECOMPILER_H
|
||||
#define LIBHBC_DECOMPILER_H
|
||||
|
||||
@@ -54,6 +56,10 @@ struct Environment {
|
||||
char **slot_index_to_varname;
|
||||
int var_count;
|
||||
int slot_capacity;
|
||||
/* >=0: synthetic stand-in for a get_environment captured scope at this
|
||||
* nesting level (used when the real parent chain is unknown, i.e. the
|
||||
* function is decompiled standalone). -1: a real created environment. */
|
||||
int captured_level;
|
||||
};
|
||||
|
||||
/* Address to strings mapping */
|
||||
@@ -122,6 +128,38 @@ struct DecompiledFunctionBody {
|
||||
u32 nested_frames_count;
|
||||
u32 nested_frames_capacity;
|
||||
|
||||
/* for-in / loop top addresses: a jump here is a `continue`, not a `goto`,
|
||||
* and the address gets no label (the loop body brace stands in). */
|
||||
u32 *forin_continue_targets;
|
||||
u32 forin_continue_targets_count;
|
||||
u32 forin_continue_targets_capacity;
|
||||
|
||||
/* do-while loops: `do {` opens at `top`, `} while (cond);` replaces the
|
||||
* back-edge conditional jump at `back_edge`. When an entry guard at
|
||||
* `guard_pos` (jumping to `exit_addr`) tests the same condition, the loop is
|
||||
* `promoted` to `while (cond) {` (guard + do/while folded into one header). */
|
||||
struct {
|
||||
u32 top;
|
||||
u32 back_edge;
|
||||
u32 guard_pos;
|
||||
u32 exit_addr;
|
||||
bool promoted;
|
||||
/* For a materialized guard (`r=cmp; if(r){do}while(cmp)`), the while
|
||||
* header renders this condition instead of the guard register. NULL when
|
||||
* the guard already spells the condition (fused compare-jump). */
|
||||
void *while_cond;
|
||||
bool while_cond_invert;
|
||||
} *dowhile_loops;
|
||||
u32 dowhile_loops_count;
|
||||
u32 dowhile_loops_capacity;
|
||||
|
||||
/* Infinite loops: a backward unconditional `goto top` is the back-edge of a
|
||||
* `for (;;) {` opened at `top`; the enclosing if's false path exits at
|
||||
* `exit`, where a `break;` and the loop close are emitted. */
|
||||
struct { u32 top; u32 exit; } *forever_loops;
|
||||
u32 forever_loops_count;
|
||||
u32 forever_loops_capacity;
|
||||
|
||||
/* Generated code */
|
||||
TokenString *statements;
|
||||
u32 statements_count;
|
||||
@@ -144,6 +182,7 @@ typedef struct HermesDecompiler {
|
||||
bool *function_in_progress; /* Track functions currently on the emit stack */
|
||||
int indent_level;
|
||||
bool inlining_function; /* True when outputting a nested function inline */
|
||||
int inline_depth; /* Current nesting depth of inlined closures */
|
||||
bool output_truncated; /* Sticky flag: budget exceeded; stop emitting new content */
|
||||
bool truncation_marker_emitted; /* Sticky flag: the truncation comment was already written */
|
||||
HBCDecompOptions options;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+29
-1
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#ifndef LIBHBC_API_H
|
||||
#define LIBHBC_API_H
|
||||
|
||||
@@ -131,7 +133,10 @@ typedef struct {
|
||||
/* Control flow rendering options */
|
||||
bool force_dispatch; /* Force switch/case dispatch loop for linear functions */
|
||||
bool inline_closures; /* Inline closure definitions (default: true) */
|
||||
int inline_threshold; /* Max instruction count to inline (0 = no limit, -1 = no inline) */
|
||||
int inline_threshold; /* Max closure bytecode size to inline (0 = no limit, -1 = no inline) */
|
||||
int inline_max_depth; /* Max nested inline depth (0 = no limit). Bounds the
|
||||
* recursive closure expansion that otherwise inlines a
|
||||
* whole module graph into one function. */
|
||||
/* Early-stop budgets (0 = unlimited). max_ast_statements caps pass2 AST
|
||||
* growth per function; max_output_bytes caps total emitted output. */
|
||||
int max_ast_statements;
|
||||
@@ -344,6 +349,22 @@ typedef struct {
|
||||
u32 count;
|
||||
} HBCBindings;
|
||||
|
||||
typedef struct {
|
||||
char *name;
|
||||
char *path;
|
||||
char *version;
|
||||
const char *kind; /* cjs, package, js, native, global */
|
||||
u32 function_id;
|
||||
u32 offset;
|
||||
u32 string_id;
|
||||
bool inferred;
|
||||
} HBCModule;
|
||||
|
||||
typedef struct {
|
||||
HBCModule *modules;
|
||||
u32 count;
|
||||
} HBCModules;
|
||||
|
||||
/**
|
||||
* Free instruction array.
|
||||
*/
|
||||
@@ -357,6 +378,13 @@ void hbc_free_insns(HBCInsn *insns, u32 count);
|
||||
Result hbc_scan_bindings(HBC *hbc, HBCBindings *out);
|
||||
void hbc_free_bindings(HBCBindings *bindings);
|
||||
|
||||
/**
|
||||
* List probable bundled modules/libraries. Hermes bytecode has no SBOM table,
|
||||
* so versions are only populated when they are explicitly preserved in strings.
|
||||
*/
|
||||
Result hbc_list_modules(HBC *hbc, HBCModules *out);
|
||||
void hbc_free_modules(HBCModules *modules);
|
||||
|
||||
/**
|
||||
* Encoding buffer and functions
|
||||
*/
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#ifndef LIBHBC_ENCODER_H
|
||||
#define LIBHBC_ENCODER_H
|
||||
|
||||
|
||||
+14
-18
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#ifndef LIBHBC_LITERALS_PUBLIC_H
|
||||
#define LIBHBC_LITERALS_PUBLIC_H
|
||||
|
||||
@@ -14,7 +16,7 @@
|
||||
* Responsibilities:
|
||||
* - Format a literal from opcode immediates into a JS-like string.
|
||||
* - Maintain a lazy cache of distinct literals so the disassembler and
|
||||
* decompiler can look up previously-formatted literals in O(1) and avoid
|
||||
* decompiler can look up previously-formatted literals in O (1) and avoid
|
||||
* re-walking the SLP buffer on every call.
|
||||
* - Track the set of instruction addresses that reference each literal.
|
||||
* - Enumerate literals from the pool side (scan SLP stream) and from the
|
||||
@@ -32,7 +34,7 @@ typedef enum {
|
||||
} HBCLiteralKind;
|
||||
|
||||
/* One entry in the literal cache. The strings / xref array are owned by the
|
||||
* HBC and freed on hbc_close() or hbc_literals_reset(). */
|
||||
* HBC and freed on hbc_close () or hbc_literals_reset (). */
|
||||
typedef struct {
|
||||
HBCLiteralKind kind;
|
||||
u32 num_items;
|
||||
@@ -45,25 +47,21 @@ typedef struct {
|
||||
} HBCLiteralEntry;
|
||||
|
||||
/* Format a literal directly from raw parameters. Does not touch the cache.
|
||||
* Returned string is heap-allocated; caller frees with free(). */
|
||||
Result hbc_literals_format_raw(HBC *hbc, HBCLiteralKind kind, u32 num_items,
|
||||
u32 primary_id, u32 secondary_id, char **out);
|
||||
* Returned string is heap-allocated; caller frees with free (). */
|
||||
Result hbc_literals_format_raw(HBC *hbc, HBCLiteralKind kind, u32 num_items, u32 primary_id, u32 secondary_id, char **out);
|
||||
|
||||
/* Convenience wrapper taking opcode + raw arg3/arg4/arg5 as decoded by
|
||||
* hbc_dec(). Knows the per-opcode mapping; useful as the "give me the string
|
||||
* hbc_dec (). Knows the per-opcode mapping; useful as the "give me the string
|
||||
* for these parameters" helper. */
|
||||
Result hbc_literals_format_for_opcode(HBC *hbc, u8 opcode, u32 arg3, u32 arg4,
|
||||
u32 arg5, char **out);
|
||||
Result hbc_literals_format_for_opcode(HBC *hbc, u8 opcode, u32 arg3, u32 arg4, u32 arg5, char **out);
|
||||
|
||||
/* Look up (or create) a cache entry for a literal and return the formatted
|
||||
* text. The returned pointer is owned by the cache — do not free it. */
|
||||
Result hbc_literals_get(HBC *hbc, HBCLiteralKind kind, u32 num_items,
|
||||
u32 primary_id, u32 secondary_id, const char **out_formatted);
|
||||
Result hbc_literals_get(HBC *hbc, HBCLiteralKind kind, u32 num_items, u32 primary_id, u32 secondary_id, const char **out_formatted);
|
||||
|
||||
/* Register that `from_addr` references the literal described by the key.
|
||||
* Creates the cache entry (and formats) on first reference. */
|
||||
Result hbc_literals_register(HBC *hbc, HBCLiteralKind kind, u32 num_items,
|
||||
u32 primary_id, u32 secondary_id, u32 from_addr);
|
||||
Result hbc_literals_register(HBC *hbc, HBCLiteralKind kind, u32 num_items, u32 primary_id, u32 secondary_id, u32 from_addr);
|
||||
|
||||
/* Full-binary scan: walk every function body; record every
|
||||
* New{Array,Object}WithBuffer[Long] call site into the cache (with xrefs).
|
||||
@@ -74,7 +72,7 @@ Result hbc_literals_scan_code(HBC *hbc, u32 *out_count);
|
||||
* kind. For arrays and object-values it walks the byte stream linearly.
|
||||
* For object-keys on v97+ it enumerates the shape table instead.
|
||||
* Does not populate the cache — this is an inspection helper. Caller frees
|
||||
* *out via free(). */
|
||||
* *out via free (). */
|
||||
typedef struct {
|
||||
u32 paddr; /* absolute file offset */
|
||||
u32 pool_offset; /* byte offset within the pool */
|
||||
@@ -82,13 +80,11 @@ typedef struct {
|
||||
u8 tag; /* SLP tag 0..7 (for arrays/values); 0xff if not applicable */
|
||||
} HBCPoolGroup;
|
||||
|
||||
Result hbc_literals_scan_pool(HBC *hbc, HBCLiteralKind kind,
|
||||
HBCPoolGroup **out, u32 *out_count);
|
||||
Result hbc_literals_scan_pool(HBC *hbc, HBCLiteralKind kind, HBCPoolGroup **out, u32 *out_count);
|
||||
|
||||
/* Enumerate the current cache contents. The returned array is owned by the
|
||||
* HBC and valid until the next cache mutation. */
|
||||
Result hbc_literals_list(HBC *hbc, const HBCLiteralEntry **out,
|
||||
u32 *out_count);
|
||||
Result hbc_literals_list(HBC *hbc, const HBCLiteralEntry **out, u32 *out_count);
|
||||
|
||||
/* Drop every cached entry and every xref. Fast. Does not affect the SLP
|
||||
* pools themselves. */
|
||||
@@ -106,7 +102,7 @@ u32 hbc_get_pool_size(HBC *hbc, HBCLiteralKind kind);
|
||||
u32 hbc_get_object_values_paddr(HBC *hbc);
|
||||
u32 hbc_get_object_values_size(HBC *hbc);
|
||||
|
||||
/* Global toggle: whether hbc_dec() should inline the formatted literal as a
|
||||
/* Global toggle: whether hbc_dec () should inline the formatted literal as a
|
||||
* trailing " ; <text>" comment on the disasm line. Default: off. */
|
||||
void hbc_set_inline_literals(bool on);
|
||||
bool hbc_get_inline_literals(void);
|
||||
|
||||
+17
-11
@@ -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 */
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#ifndef LIBHBC_FILE_PARSER_H
|
||||
#define LIBHBC_FILE_PARSER_H
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
project(
|
||||
'r2hermes',
|
||||
'c',
|
||||
version: '1.2.6',
|
||||
version: '1.4.0',
|
||||
license: 'MIT',
|
||||
default_options: [
|
||||
'c_std=c11',
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#ifdef R2_CORE_H
|
||||
extern RCorePlugin r_core_plugin_r2hermes;
|
||||
#endif
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#ifdef R2_CORE_H
|
||||
#include "core/deps.h"
|
||||
#endif
|
||||
|
||||
+1949
-748
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>
|
||||
@@ -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>
|
||||
@@ -443,10 +445,6 @@ Result _hbc_token_to_string(Token *token, StringBuffer *buffer) {
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_punct(TokenType t) {
|
||||
return t == TOKEN_TYPE_LEFT_PARENTHESIS || t == TOKEN_TYPE_RIGHT_PARENTHESIS || t == TOKEN_TYPE_DOT_ACCESSOR;
|
||||
}
|
||||
|
||||
Result _hbc_token_string_to_string(TokenString *ts, StringBuffer *out) {
|
||||
if (!ts || !out) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "_hbc_token_string_to_string: NULL");
|
||||
@@ -454,24 +452,15 @@ Result _hbc_token_string_to_string(TokenString *ts, StringBuffer *out) {
|
||||
Token *cur = ts->head;
|
||||
TokenType prev = (TokenType) (-1);
|
||||
while (cur) {
|
||||
/* spacing rules: add space before '(', no space after '(' or before ')', add spaces around '=' */
|
||||
/* spacing rules: space before '(', none after '(' or '.' nor before ')' or '.' */
|
||||
if (cur != ts->head) {
|
||||
bool need_space = true;
|
||||
/* Space before '(' for readability */
|
||||
bool need_space;
|
||||
if (cur->type == TOKEN_TYPE_LEFT_PARENTHESIS) {
|
||||
need_space = true;
|
||||
}
|
||||
/* No space before other punctuation */
|
||||
else if (is_punct (cur->type) || cur->type == TOKEN_TYPE_RIGHT_PARENTHESIS || cur->type == TOKEN_TYPE_DOT_ACCESSOR) {
|
||||
} else if (cur->type == TOKEN_TYPE_RIGHT_PARENTHESIS || cur->type == TOKEN_TYPE_DOT_ACCESSOR) {
|
||||
need_space = false;
|
||||
}
|
||||
/* No space after '(' or after '.' */
|
||||
else if (prev == TOKEN_TYPE_LEFT_PARENTHESIS || prev == TOKEN_TYPE_DOT_ACCESSOR) {
|
||||
need_space = false;
|
||||
}
|
||||
/* Always space around '=' */
|
||||
else if (prev == TOKEN_TYPE_ASSIGNMENT || cur->type == TOKEN_TYPE_ASSIGNMENT) {
|
||||
need_space = true;
|
||||
} else {
|
||||
need_space = prev != TOKEN_TYPE_LEFT_PARENTHESIS && prev != TOKEN_TYPE_DOT_ACCESSOR;
|
||||
}
|
||||
if (need_space) {
|
||||
RETURN_IF_ERROR (_hbc_string_buffer_append_char (out, ' '));
|
||||
|
||||
+154
-277
@@ -1,8 +1,9 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/decompilation/translator.h>
|
||||
#include <hbc/opcodes.h>
|
||||
#include <hbc/parser.h>
|
||||
#include <stdbool.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include <hbc/decompilation/literals.h>
|
||||
|
||||
@@ -155,7 +156,6 @@ static const char *lookup_op_table(const OpTableEntry *table, u8 opcode) {
|
||||
static Token *reg_l_safe(const ParsedInstruction *insn, int idx);
|
||||
static Token *reg_r_safe(const ParsedInstruction *insn, int idx);
|
||||
static Token *num_token_u32(u32 v);
|
||||
static u32 compute_target_address(const ParsedInstruction *insn, int op_index);
|
||||
|
||||
static Result emit_literal_assign(TokenString *out, const ParsedInstruction *insn, const char *literal) {
|
||||
RETURN_IF_ERROR (add (out, reg_l_safe (insn, 0)));
|
||||
@@ -232,19 +232,47 @@ static Result emit_call_fixed(TokenString *out, const ParsedInstruction *insn, i
|
||||
return add (out, create_right_parenthesis_token ());
|
||||
}
|
||||
|
||||
static Result emit_call_with_argc(TokenString *out, const ParsedInstruction *insn) {
|
||||
/* Hermes reserves this many frame-header registers at the very top of a
|
||||
* function's register frame; an outgoing call's `this`+args sit just below. */
|
||||
#define HBC_CALL_FRAME_RESERVED 6
|
||||
|
||||
/* Variable-arity Call/Construct. Hermes places the call's argument registers at
|
||||
* the top of the caller's frame, below the reserved header: `this` at
|
||||
* frame_size-1-RESERVED, the explicit arguments descending below it.
|
||||
* Reconstruct them as fn.call (this, a1, ...) (or new fn (a1, ...) for construct).
|
||||
* Falls back to an argc note when the frame layout is unknown or too small. */
|
||||
static Result emit_call_with_argc(TokenString *out, const ParsedInstruction *insn, bool is_construct) {
|
||||
const u32 argc = insn->arg3;
|
||||
RETURN_IF_ERROR (add (out, reg_l_safe (insn, 0)));
|
||||
RETURN_IF_ERROR (add (out, create_assignment_token ()));
|
||||
if (is_construct) {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("new")));
|
||||
}
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn, 1)));
|
||||
if (!is_construct) {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token (".call")));
|
||||
}
|
||||
RETURN_IF_ERROR (add (out, create_left_parenthesis_token ()));
|
||||
char buf2[32];
|
||||
snprintf (buf2, sizeof (buf2), "/*argc:%u*/", (unsigned)insn->arg3);
|
||||
RETURN_IF_ERROR (add (out, create_raw_token (buf2)));
|
||||
const int base = (int)insn->frame_size - 1 - HBC_CALL_FRAME_RESERVED;
|
||||
if (argc == 0 || base < 0 || (int)argc > base + 1) {
|
||||
char buf2[32];
|
||||
snprintf (buf2, sizeof (buf2), "/*argc:%u*/", (unsigned)argc);
|
||||
RETURN_IF_ERROR (add (out, create_raw_token (buf2)));
|
||||
return add (out, create_right_parenthesis_token ());
|
||||
}
|
||||
/* Construct omits the implicit `this` (the freshly created object). */
|
||||
const u32 first = is_construct? 1: 0;
|
||||
for (u32 i = first; i < argc; i++) {
|
||||
if (i > first) {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token (",")));
|
||||
}
|
||||
RETURN_IF_ERROR (add (out, create_right_hand_reg_token (base - (int)i)));
|
||||
}
|
||||
return add (out, create_right_parenthesis_token ());
|
||||
}
|
||||
|
||||
static Result emit_simple_jump(TokenString *out, const ParsedInstruction *insn, bool positive) {
|
||||
u32 target = compute_target_address (insn, 0);
|
||||
u32 target = _hbc_compute_target_address (insn, 0);
|
||||
if (positive) {
|
||||
RETURN_IF_ERROR (add (out, create_jump_condition_token (target)));
|
||||
return add (out, create_raw_token ("true"));
|
||||
@@ -278,34 +306,6 @@ static bool is_operand_register(const Instruction *inst, int idx) {
|
||||
return t == OPERAND_TYPE_REG8 || t == OPERAND_TYPE_REG32;
|
||||
}
|
||||
|
||||
static bool is_operand_addr(const Instruction *inst, int idx) {
|
||||
if (!inst || idx < 0 || idx >= 6) {
|
||||
return false;
|
||||
}
|
||||
OperandType t = inst->operands[idx].operand_type;
|
||||
return t == OPERAND_TYPE_ADDR8 || t == OPERAND_TYPE_ADDR32;
|
||||
}
|
||||
|
||||
static u32 get_operand_value(const ParsedInstruction *insn, int idx) {
|
||||
switch (idx) {
|
||||
case 0: return insn->arg1;
|
||||
case 1: return insn->arg2;
|
||||
case 2: return insn->arg3;
|
||||
case 3: return insn->arg4;
|
||||
case 4: return insn->arg5;
|
||||
default: return insn->arg6;
|
||||
}
|
||||
}
|
||||
|
||||
static u32 compute_target_address(const ParsedInstruction *insn, int op_index) {
|
||||
u32 v = get_operand_value (insn, op_index);
|
||||
u32 base = insn->original_pos;
|
||||
if (_hbc_is_jump_instruction (insn->opcode)) {
|
||||
base += insn->inst->binary_size;
|
||||
}
|
||||
return base + v;
|
||||
}
|
||||
|
||||
static const char *jump_cmp_operator(u8 op) {
|
||||
switch (op) {
|
||||
case OP_JEqual:
|
||||
@@ -360,7 +360,7 @@ static Token *reg_l_safe(const ParsedInstruction *insn, int idx) {
|
||||
if (!is_operand_register (insn->inst, idx)) {
|
||||
return create_raw_token ("/*not_reg*/");
|
||||
}
|
||||
u32 r = get_operand_value (insn, idx);
|
||||
u32 r = hbc_operand_value (insn, idx);
|
||||
if (!is_valid_register (r, insn)) {
|
||||
/* Out-of-bounds register: show normally but warn in debug mode.
|
||||
* This is a rare error condition indicating bytecode corruption
|
||||
@@ -377,7 +377,7 @@ static Token *reg_r_safe(const ParsedInstruction *insn, int idx) {
|
||||
if (!is_operand_register (insn->inst, idx)) {
|
||||
return create_raw_token ("/*not_reg*/");
|
||||
}
|
||||
u32 r = get_operand_value (insn, idx);
|
||||
u32 r = hbc_operand_value (insn, idx);
|
||||
if (!is_valid_register (r, insn)) {
|
||||
/* Out-of-bounds register: show normally but warn in debug mode.
|
||||
* This is a rare error condition indicating bytecode corruption
|
||||
@@ -452,6 +452,54 @@ static Token *unquoted_string(HBCReader *r, u32 sid) {
|
||||
return create_raw_token ("unknown");
|
||||
}
|
||||
|
||||
/* Emit `.name` when the property is a valid identifier, `["name"]` otherwise */
|
||||
static Result emit_property_access(TokenString *out, const ParsedInstruction *insn, u32 sid) {
|
||||
HBCReader *r = insn->hbc_reader;
|
||||
const char *s = (r && r->strings && sid < r->header.stringCount)? r->strings[sid]: NULL;
|
||||
if (s && _hbc_is_js_identifier (s)) {
|
||||
RETURN_IF_ERROR (add (out, create_dot_accessor_token ()));
|
||||
return add (out, create_raw_token (s));
|
||||
}
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("[")));
|
||||
RETURN_IF_ERROR (add (out, quoted_string (r, sid)));
|
||||
return add (out, create_raw_token ("]"));
|
||||
}
|
||||
|
||||
/* Emit a compare-and-jump as `if (rA cmp rB)` or its negated goto form */
|
||||
static Result emit_cmp_jump(TokenString *out, const ParsedInstruction *insn_c, const char *cmp) {
|
||||
i32 rel = (i32)hbc_operand_value (insn_c, 0);
|
||||
u32 target = _hbc_compute_target_address (insn_c, 0);
|
||||
if (rel > 0) {
|
||||
RETURN_IF_ERROR (add (out, create_jump_not_condition_token (target)));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("!")));
|
||||
RETURN_IF_ERROR (add (out, create_left_parenthesis_token ()));
|
||||
} else {
|
||||
RETURN_IF_ERROR (add (out, create_jump_condition_token (target)));
|
||||
}
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 1)));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token (cmp)));
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 2)));
|
||||
if (rel > 0) {
|
||||
RETURN_IF_ERROR (add (out, create_right_parenthesis_token ()));
|
||||
}
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
/* Emit `rD = <function table entry>` with environment and closure kind flags */
|
||||
static Result emit_closure_token(TokenString *out, const ParsedInstruction *insn_c, bool is_closure, bool is_generator, bool is_async) {
|
||||
RETURN_IF_ERROR (add (out, reg_l_safe (insn_c, 0)));
|
||||
RETURN_IF_ERROR (add (out, create_assignment_token ()));
|
||||
FunctionTableIndexToken *t = (FunctionTableIndexToken *)create_function_table_index_token (hbc_operand_value (insn_c, 2), NULL);
|
||||
if (!t) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "oom fti");
|
||||
}
|
||||
t->environment_id = (int)hbc_operand_value (insn_c, 1);
|
||||
t->is_closure = is_closure;
|
||||
t->is_generator = is_generator;
|
||||
t->is_async = is_async;
|
||||
return add (out, (Token *)t);
|
||||
}
|
||||
|
||||
Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, TokenString *out) {
|
||||
if (!insn_c || !out || !insn_c->inst) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "translate: bad args");
|
||||
@@ -535,10 +583,12 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
case OP_LoadParam:
|
||||
case OP_LoadParamLong:
|
||||
{
|
||||
/* LoadParam index maps directly to args[i]: index 0 is `this`,
|
||||
* matching the a0..aN-1 names emitted in the function signature. */
|
||||
RETURN_IF_ERROR (add (out, reg_l_safe (insn_c, 0)));
|
||||
RETURN_IF_ERROR (add (out, create_assignment_token ()));
|
||||
char buf[32];
|
||||
snprintf (buf, sizeof (buf), "args[%u]", insn->arg2 > 0? insn->arg2 - 1: 0);
|
||||
snprintf (buf, sizeof (buf), "args[%u]", insn->arg2);
|
||||
RETURN_IF_ERROR (add (out, create_raw_token (buf)));
|
||||
break;
|
||||
}
|
||||
@@ -558,7 +608,7 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
{
|
||||
/* catch (rN) marker */
|
||||
if (is_operand_register (insn->inst, 0)) {
|
||||
RETURN_IF_ERROR (add (out, create_catch_block_start_token ((int)get_operand_value (insn, 0))));
|
||||
RETURN_IF_ERROR (add (out, create_catch_block_start_token ((int)hbc_operand_value (insn, 0))));
|
||||
} else {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("/*catch non-reg*/")));
|
||||
}
|
||||
@@ -568,7 +618,7 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
{
|
||||
/* Create a new environment object in rD */
|
||||
if (is_operand_register (insn->inst, 0)) {
|
||||
RETURN_IF_ERROR (add (out, create_new_environment_token ((int)get_operand_value (insn_c, 0))));
|
||||
RETURN_IF_ERROR (add (out, create_new_environment_token ((int)hbc_operand_value (insn_c, 0))));
|
||||
} else {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("/*new_env_invalid*/")));
|
||||
}
|
||||
@@ -577,7 +627,7 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
case OP_CreateInnerEnvironment:
|
||||
{
|
||||
if (is_operand_register (insn->inst, 0) && is_operand_register (insn->inst, 1)) {
|
||||
RETURN_IF_ERROR (add (out, create_new_inner_environment_token ((int)get_operand_value (insn_c, 0), (int)get_operand_value (insn_c, 1), (int)get_operand_value (insn_c, 2))));
|
||||
RETURN_IF_ERROR (add (out, create_new_inner_environment_token ((int)hbc_operand_value (insn_c, 0), (int)hbc_operand_value (insn_c, 1), (int)hbc_operand_value (insn_c, 2))));
|
||||
} else {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("/*new_inner_env_invalid*/")));
|
||||
}
|
||||
@@ -585,47 +635,16 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
}
|
||||
case OP_CreateClosure:
|
||||
case OP_CreateClosureLongIndex:
|
||||
{
|
||||
RETURN_IF_ERROR (add (out, reg_l_safe (insn_c, 0)));
|
||||
RETURN_IF_ERROR (add (out, create_assignment_token ()));
|
||||
FunctionTableIndexToken *t = (FunctionTableIndexToken *)create_function_table_index_token (get_operand_value (insn_c, 2), NULL);
|
||||
if (!t) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "oom fti");
|
||||
}
|
||||
t->environment_id = (int)get_operand_value (insn_c, 1);
|
||||
t->is_closure = true;
|
||||
RETURN_IF_ERROR (add (out, (Token *)t));
|
||||
break;
|
||||
}
|
||||
RETURN_IF_ERROR (emit_closure_token (out, insn_c, true, false, false));
|
||||
break;
|
||||
case OP_CreateGeneratorClosure:
|
||||
case OP_CreateGeneratorClosureLongIndex:
|
||||
{
|
||||
RETURN_IF_ERROR (add (out, reg_l_safe (insn_c, 0)));
|
||||
RETURN_IF_ERROR (add (out, create_assignment_token ()));
|
||||
FunctionTableIndexToken *t = (FunctionTableIndexToken *)create_function_table_index_token (get_operand_value (insn_c, 2), NULL);
|
||||
if (!t) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "oom fti");
|
||||
}
|
||||
t->environment_id = (int)get_operand_value (insn_c, 1);
|
||||
t->is_closure = true;
|
||||
t->is_generator = true;
|
||||
RETURN_IF_ERROR (add (out, (Token *)t));
|
||||
break;
|
||||
}
|
||||
RETURN_IF_ERROR (emit_closure_token (out, insn_c, true, true, false));
|
||||
break;
|
||||
case OP_CreateGenerator:
|
||||
case OP_CreateGeneratorLongIndex:
|
||||
{
|
||||
RETURN_IF_ERROR (add (out, reg_l_safe (insn_c, 0)));
|
||||
RETURN_IF_ERROR (add (out, create_assignment_token ()));
|
||||
FunctionTableIndexToken *t = (FunctionTableIndexToken *)create_function_table_index_token (get_operand_value (insn_c, 2), NULL);
|
||||
if (!t) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "oom fti");
|
||||
}
|
||||
t->environment_id = (int)get_operand_value (insn_c, 1);
|
||||
t->is_generator = true;
|
||||
RETURN_IF_ERROR (add (out, (Token *)t));
|
||||
break;
|
||||
}
|
||||
RETURN_IF_ERROR (emit_closure_token (out, insn_c, false, true, false));
|
||||
break;
|
||||
case OP_NewArray:
|
||||
{
|
||||
/* rD = new Array (count) */
|
||||
@@ -643,30 +662,7 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
RETURN_IF_ERROR (add (out, reg_l_safe (insn_c, 0)));
|
||||
RETURN_IF_ERROR (add (out, create_assignment_token ()));
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 1)));
|
||||
u32 sid = insn->arg4;
|
||||
const char *s = NULL;
|
||||
if (insn->hbc_reader && insn->hbc_reader->strings && sid < insn->hbc_reader->header.stringCount) {
|
||||
s = insn->hbc_reader->strings[sid];
|
||||
}
|
||||
bool ident = true;
|
||||
if (!s || !*s) {
|
||||
ident = false;
|
||||
} else {
|
||||
if (! (isalpha ((unsigned char)*s) || *s == '_' || *s == '$')) {
|
||||
ident = false;
|
||||
}
|
||||
for (const char *p = s + 1; ident && *p; p++) {
|
||||
ident = (isalnum ((unsigned char)*p) || *p == '_' || *p == '$');
|
||||
}
|
||||
}
|
||||
if (ident) {
|
||||
RETURN_IF_ERROR (add (out, create_dot_accessor_token ()));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token (s)));
|
||||
} else {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("[")));
|
||||
RETURN_IF_ERROR (add (out, quoted_string (insn->hbc_reader, sid)));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("]")));
|
||||
}
|
||||
RETURN_IF_ERROR (emit_property_access (out, insn_c, insn->arg4));
|
||||
break;
|
||||
}
|
||||
case OP_GetByVal:
|
||||
@@ -704,20 +700,15 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
case OP_PutNewOwnByIdShort:
|
||||
case OP_PutNewOwnById:
|
||||
case OP_PutNewOwnByIdLong:
|
||||
case OP_TryPutById:
|
||||
case OP_TryPutByIdLong:
|
||||
case OP_PutNewOwnNEById:
|
||||
case OP_PutNewOwnNEByIdLong:
|
||||
{
|
||||
/* obj.name = value (prefer dot accessor) */
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 0)));
|
||||
u32 sid = (op == OP_PutNewOwnByIdShort)? insn->arg3: (op == OP_PutNewOwnById)? insn->arg3
|
||||
: insn->arg4;
|
||||
const char *s = (insn->hbc_reader && insn->hbc_reader->strings && sid < insn->hbc_reader->header.stringCount)? insn->hbc_reader->strings[sid]: NULL;
|
||||
if (s && _hbc_is_js_identifier (s)) {
|
||||
RETURN_IF_ERROR (add (out, create_dot_accessor_token ()));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token (s)));
|
||||
} else {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("[")));
|
||||
RETURN_IF_ERROR (add (out, quoted_string (insn->hbc_reader, sid)));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("]")));
|
||||
}
|
||||
u32 sid = (op == OP_PutNewOwnByIdLong || op == OP_PutNewOwnNEByIdLong)? insn->arg4: insn->arg3;
|
||||
RETURN_IF_ERROR (emit_property_access (out, insn_c, sid));
|
||||
RETURN_IF_ERROR (add (out, create_assignment_token ()));
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 1)));
|
||||
break;
|
||||
@@ -748,12 +739,9 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
case OP_PutById:
|
||||
case OP_PutByIdLong:
|
||||
{
|
||||
/* rObj["name"] = rVal */
|
||||
/* rObj.name = rVal (prefer dot accessor, matching GetById) */
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 0)));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("[")));
|
||||
u32 sid = insn->arg4;
|
||||
RETURN_IF_ERROR (add (out, quoted_string (insn->hbc_reader, sid)));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("]")));
|
||||
RETURN_IF_ERROR (emit_property_access (out, insn_c, insn->arg4));
|
||||
RETURN_IF_ERROR (add (out, create_assignment_token ()));
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 1)));
|
||||
break;
|
||||
@@ -821,14 +809,14 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
case OP_SaveGenerator:
|
||||
case OP_SaveGeneratorLong:
|
||||
{
|
||||
u32 target = compute_target_address (insn, 0);
|
||||
u32 target = _hbc_compute_target_address (insn, 0);
|
||||
RETURN_IF_ERROR (add (out, create_save_generator_token (target)));
|
||||
break;
|
||||
}
|
||||
case OP_GetEnvironment:
|
||||
{
|
||||
if (is_operand_register (insn->inst, 0)) {
|
||||
RETURN_IF_ERROR (add (out, create_get_environment_token ((int)get_operand_value (insn_c, 0), (int)get_operand_value (insn_c, 1))));
|
||||
RETURN_IF_ERROR (add (out, create_get_environment_token ((int)hbc_operand_value (insn_c, 0), (int)hbc_operand_value (insn_c, 1))));
|
||||
} else {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("/*get_env_invalid*/")));
|
||||
}
|
||||
@@ -836,24 +824,17 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
}
|
||||
case OP_StoreToEnvironment:
|
||||
case OP_StoreToEnvironmentL:
|
||||
{
|
||||
if (is_operand_register (insn->inst, 0) && is_operand_register (insn->inst, 2)) {
|
||||
RETURN_IF_ERROR (add (out, create_store_to_environment_token ((int)get_operand_value (insn_c, 0), (int)get_operand_value (insn_c, 1), (int)get_operand_value (insn_c, 2))));
|
||||
} else {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("/*env_store_invalid*/")));
|
||||
}
|
||||
break;
|
||||
}
|
||||
/* The NP ("non-pointer") variant has identical operands and decompiled
|
||||
* semantics; route it through the same closure-var path so it is named
|
||||
* consistently and not misread as a plain register write by DCE. */
|
||||
case OP_StoreNPToEnvironment:
|
||||
case OP_StoreNPToEnvironmentL:
|
||||
{
|
||||
/* env[slot] = value (NP variant) */
|
||||
RETURN_IF_ERROR (add (out, reg_l_safe (insn_c, 0)));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("[")));
|
||||
RETURN_IF_ERROR (add (out, num_token_u32 (insn->arg2)));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("]")));
|
||||
RETURN_IF_ERROR (add (out, create_assignment_token ()));
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 2)));
|
||||
if (is_operand_register (insn->inst, 0) && is_operand_register (insn->inst, 2)) {
|
||||
RETURN_IF_ERROR (add (out, create_store_to_environment_token ((int)hbc_operand_value (insn_c, 0), (int)hbc_operand_value (insn_c, 1), (int)hbc_operand_value (insn_c, 2))));
|
||||
} else {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("/*env_store_invalid*/")));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OP_LoadFromEnvironment:
|
||||
@@ -863,7 +844,7 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
RETURN_IF_ERROR (add (out, reg_l_safe (insn_c, 0)));
|
||||
RETURN_IF_ERROR (add (out, create_assignment_token ()));
|
||||
if (is_operand_register (insn->inst, 1)) {
|
||||
RETURN_IF_ERROR (add (out, create_load_from_environment_token ((int)get_operand_value (insn_c, 1), (int)get_operand_value (insn_c, 2))));
|
||||
RETURN_IF_ERROR (add (out, create_load_from_environment_token ((int)hbc_operand_value (insn_c, 1), (int)hbc_operand_value (insn_c, 2))));
|
||||
} else {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("/*env_load_invalid*/")));
|
||||
}
|
||||
@@ -917,9 +898,10 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
return emit_call_fixed (out, insn_c, 6); /* dest, callee, arg1, arg2, arg3, arg4 */
|
||||
case OP_Call:
|
||||
case OP_CallLong:
|
||||
return emit_call_with_argc (out, insn_c, false);
|
||||
case OP_Construct:
|
||||
case OP_ConstructLong:
|
||||
return emit_call_with_argc (out, insn_c);
|
||||
return emit_call_with_argc (out, insn_c, true);
|
||||
/* Delete operations */
|
||||
case OP_DelById:
|
||||
case OP_DelByIdLong:
|
||||
@@ -974,12 +956,12 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
/* Jump instructions */
|
||||
case OP_Jmp:
|
||||
case OP_JmpLong:
|
||||
return emit_simple_jump (out, insn_c, (i32)get_operand_value (insn_c, 0) <= 0);
|
||||
return emit_simple_jump (out, insn_c, (i32)hbc_operand_value (insn_c, 0) <= 0);
|
||||
case OP_JmpTrue:
|
||||
case OP_JmpTrueLong:
|
||||
{
|
||||
i32 rel = (i32)get_operand_value (insn_c, 0);
|
||||
u32 target = compute_target_address (insn, 0);
|
||||
i32 rel = (i32)hbc_operand_value (insn_c, 0);
|
||||
u32 target = _hbc_compute_target_address (insn, 0);
|
||||
if (rel > 0) {
|
||||
RETURN_IF_ERROR (add (out, create_jump_not_condition_token (target)));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("!")));
|
||||
@@ -993,8 +975,8 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
case OP_JmpFalse:
|
||||
case OP_JmpFalseLong:
|
||||
{
|
||||
i32 rel = (i32)get_operand_value (insn_c, 0);
|
||||
u32 target = compute_target_address (insn, 0);
|
||||
i32 rel = (i32)hbc_operand_value (insn_c, 0);
|
||||
u32 target = _hbc_compute_target_address (insn, 0);
|
||||
if (rel > 0) {
|
||||
RETURN_IF_ERROR (add (out, create_jump_not_condition_token (target)));
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 1)));
|
||||
@@ -1008,8 +990,8 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
case OP_JmpUndefined:
|
||||
case OP_JmpUndefinedLong:
|
||||
{
|
||||
i32 rel = (i32)get_operand_value (insn_c, 0);
|
||||
u32 target = compute_target_address (insn, 0);
|
||||
i32 rel = (i32)hbc_operand_value (insn_c, 0);
|
||||
u32 target = _hbc_compute_target_address (insn, 0);
|
||||
if (rel > 0) {
|
||||
RETURN_IF_ERROR (add (out, create_jump_not_condition_token (target)));
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 1)));
|
||||
@@ -1040,27 +1022,30 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
case OP_JStrictEqualLong:
|
||||
case OP_JStrictNotEqual:
|
||||
case OP_JStrictNotEqualLong:
|
||||
/* Conditional jump instructions (N variants) */
|
||||
case OP_JLessN:
|
||||
case OP_JLessNLong:
|
||||
case OP_JNotLessN:
|
||||
case OP_JNotLessNLong:
|
||||
case OP_JGreaterN:
|
||||
case OP_JGreaterNLong:
|
||||
case OP_JNotGreaterN:
|
||||
case OP_JNotGreaterNLong:
|
||||
case OP_JLessEqualN:
|
||||
case OP_JLessEqualNLong:
|
||||
case OP_JNotLessEqualN:
|
||||
case OP_JNotLessEqualNLong:
|
||||
case OP_JGreaterEqualN:
|
||||
case OP_JGreaterEqualNLong:
|
||||
case OP_JNotGreaterEqualN:
|
||||
case OP_JNotGreaterEqualNLong:
|
||||
{
|
||||
const char *cmp = jump_cmp_operator (op);
|
||||
if (!cmp) {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("/*jump_cmp*/")));
|
||||
break;
|
||||
}
|
||||
i32 rel = (i32)get_operand_value (insn_c, 0);
|
||||
u32 target = compute_target_address (insn, 0);
|
||||
if (rel > 0) {
|
||||
RETURN_IF_ERROR (add (out, create_jump_not_condition_token (target)));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("!")));
|
||||
RETURN_IF_ERROR (add (out, create_left_parenthesis_token ()));
|
||||
} else {
|
||||
RETURN_IF_ERROR (add (out, create_jump_condition_token (target)));
|
||||
}
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 1)));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token (cmp)));
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 2)));
|
||||
if (rel > 0) {
|
||||
RETURN_IF_ERROR (add (out, create_right_parenthesis_token ()));
|
||||
}
|
||||
RETURN_IF_ERROR (emit_cmp_jump (out, insn_c, cmp));
|
||||
break;
|
||||
}
|
||||
/* Built-in calls */
|
||||
@@ -1084,7 +1069,7 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
{
|
||||
RETURN_IF_ERROR (add (out, reg_l_safe (insn_c, 0)));
|
||||
RETURN_IF_ERROR (add (out, create_assignment_token ()));
|
||||
FunctionTableIndexToken *t = (FunctionTableIndexToken *)create_function_table_index_token (get_operand_value (insn_c, 1), NULL);
|
||||
FunctionTableIndexToken *t = (FunctionTableIndexToken *)create_function_table_index_token (hbc_operand_value (insn_c, 1), NULL);
|
||||
if (!t) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "oom fti");
|
||||
}
|
||||
@@ -1098,14 +1083,14 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
{
|
||||
RETURN_IF_ERROR (add (out, reg_l_safe (insn_c, 0)));
|
||||
RETURN_IF_ERROR (add (out, create_assignment_token ()));
|
||||
Token *t = create_function_table_index_token (get_operand_value (insn_c, 2), NULL);
|
||||
Token *t = create_function_table_index_token (hbc_operand_value (insn_c, 2), NULL);
|
||||
if (!t) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "oom fti");
|
||||
}
|
||||
RETURN_IF_ERROR (add (out, t));
|
||||
RETURN_IF_ERROR (add (out, create_left_parenthesis_token ()));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("/*argc:")));
|
||||
RETURN_IF_ERROR (add (out, num_token_u32 (get_operand_value (insn_c, 1))));
|
||||
RETURN_IF_ERROR (add (out, num_token_u32 (hbc_operand_value (insn_c, 1))));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("*/")));
|
||||
RETURN_IF_ERROR (add (out, create_right_parenthesis_token ()));
|
||||
break;
|
||||
@@ -1155,53 +1140,10 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("switch(")));
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 0)));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token (") /*tableSize:")));
|
||||
RETURN_IF_ERROR (add (out, num_token_u32 (insn->arg2)));
|
||||
RETURN_IF_ERROR (add (out, num_token_u32 (insn->switch_jump_table_size)));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("*/")));
|
||||
break;
|
||||
}
|
||||
/* Property operations (variants) */
|
||||
case OP_TryPutById:
|
||||
case OP_TryPutByIdLong:
|
||||
{
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 0)));
|
||||
u32 sid = insn->arg3;
|
||||
const char *s = NULL;
|
||||
if (insn->hbc_reader && insn->hbc_reader->strings && sid < insn->hbc_reader->header.stringCount) {
|
||||
s = insn->hbc_reader->strings[sid];
|
||||
}
|
||||
if (s && _hbc_is_js_identifier (s)) {
|
||||
RETURN_IF_ERROR (add (out, create_dot_accessor_token ()));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token (s)));
|
||||
} else {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("[")));
|
||||
RETURN_IF_ERROR (add (out, quoted_string (insn->hbc_reader, sid)));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("]")));
|
||||
}
|
||||
RETURN_IF_ERROR (add (out, create_assignment_token ()));
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 1)));
|
||||
break;
|
||||
}
|
||||
case OP_PutNewOwnNEById:
|
||||
case OP_PutNewOwnNEByIdLong:
|
||||
{
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 0)));
|
||||
u32 sid = (op == OP_PutNewOwnNEById)? insn->arg3: insn->arg4;
|
||||
const char *s = NULL;
|
||||
if (insn->hbc_reader && insn->hbc_reader->strings && sid < insn->hbc_reader->header.stringCount) {
|
||||
s = insn->hbc_reader->strings[sid];
|
||||
}
|
||||
if (s && _hbc_is_js_identifier (s)) {
|
||||
RETURN_IF_ERROR (add (out, create_dot_accessor_token ()));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token (s)));
|
||||
} else {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("[")));
|
||||
RETURN_IF_ERROR (add (out, quoted_string (insn->hbc_reader, sid)));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("]")));
|
||||
}
|
||||
RETURN_IF_ERROR (add (out, create_assignment_token ()));
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 1)));
|
||||
break;
|
||||
}
|
||||
case OP_PutOwnByVal:
|
||||
{
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 0)));
|
||||
@@ -1230,7 +1172,7 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
{
|
||||
if (is_operand_register (insn->inst, 0) && is_operand_register (insn->inst, 1) &&
|
||||
is_operand_register (insn->inst, 2) && is_operand_register (insn->inst, 3)) {
|
||||
RETURN_IF_ERROR (add (out, create_for_in_loop_init_token ((int)get_operand_value (insn_c, 0), (int)get_operand_value (insn_c, 1), (int)get_operand_value (insn_c, 2), (int)get_operand_value (insn_c, 3))));
|
||||
RETURN_IF_ERROR (add (out, create_for_in_loop_init_token ((int)hbc_operand_value (insn_c, 0), (int)hbc_operand_value (insn_c, 1), (int)hbc_operand_value (insn_c, 2), (int)hbc_operand_value (insn_c, 3))));
|
||||
} else {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("/*forin_init_invalid*/")));
|
||||
}
|
||||
@@ -1241,7 +1183,7 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
if (is_operand_register (insn->inst, 0) && is_operand_register (insn->inst, 1) &&
|
||||
is_operand_register (insn->inst, 2) && is_operand_register (insn->inst, 3) &&
|
||||
is_operand_register (insn->inst, 4)) {
|
||||
RETURN_IF_ERROR (add (out, create_for_in_loop_next_iter_token ((int)get_operand_value (insn_c, 0), (int)get_operand_value (insn_c, 1), (int)get_operand_value (insn_c, 2), (int)get_operand_value (insn_c, 3), (int)get_operand_value (insn_c, 4))));
|
||||
RETURN_IF_ERROR (add (out, create_for_in_loop_next_iter_token ((int)hbc_operand_value (insn_c, 0), (int)hbc_operand_value (insn_c, 1), (int)hbc_operand_value (insn_c, 2), (int)hbc_operand_value (insn_c, 3), (int)hbc_operand_value (insn_c, 4))));
|
||||
} else {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("/*forin_next_invalid*/")));
|
||||
}
|
||||
@@ -1250,59 +1192,8 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
/* Async closures */
|
||||
case OP_CreateAsyncClosure:
|
||||
case OP_CreateAsyncClosureLongIndex:
|
||||
{
|
||||
RETURN_IF_ERROR (add (out, reg_l_safe (insn_c, 0)));
|
||||
RETURN_IF_ERROR (add (out, create_assignment_token ()));
|
||||
FunctionTableIndexToken *t = (FunctionTableIndexToken *)create_function_table_index_token (get_operand_value (insn_c, 2), NULL);
|
||||
if (!t) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "oom fti");
|
||||
}
|
||||
t->environment_id = (int)get_operand_value (insn_c, 1);
|
||||
t->is_async = true;
|
||||
t->is_closure = true;
|
||||
RETURN_IF_ERROR (add (out, (Token *)t));
|
||||
break;
|
||||
}
|
||||
/* Conditional jump instructions (N variants) */
|
||||
case OP_JLessN:
|
||||
case OP_JLessNLong:
|
||||
case OP_JNotLessN:
|
||||
case OP_JNotLessNLong:
|
||||
case OP_JGreaterN:
|
||||
case OP_JGreaterNLong:
|
||||
case OP_JNotGreaterN:
|
||||
case OP_JNotGreaterNLong:
|
||||
case OP_JLessEqualN:
|
||||
case OP_JLessEqualNLong:
|
||||
case OP_JNotLessEqualN:
|
||||
case OP_JNotLessEqualNLong:
|
||||
case OP_JGreaterEqualN:
|
||||
case OP_JGreaterEqualNLong:
|
||||
case OP_JNotGreaterEqualN:
|
||||
case OP_JNotGreaterEqualNLong:
|
||||
{
|
||||
const char *cmp = jump_cmp_operator (op);
|
||||
if (!cmp) {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("/*jump_cmp*/")));
|
||||
break;
|
||||
}
|
||||
i32 rel = (i32)get_operand_value (insn_c, 0);
|
||||
u32 target = compute_target_address (insn, 0);
|
||||
if (rel > 0) {
|
||||
RETURN_IF_ERROR (add (out, create_jump_not_condition_token (target)));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("!")));
|
||||
RETURN_IF_ERROR (add (out, create_left_parenthesis_token ()));
|
||||
} else {
|
||||
RETURN_IF_ERROR (add (out, create_jump_condition_token (target)));
|
||||
}
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 1)));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token (cmp)));
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 2)));
|
||||
if (rel > 0) {
|
||||
RETURN_IF_ERROR (add (out, create_right_parenthesis_token ()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
RETURN_IF_ERROR (emit_closure_token (out, insn_c, true, false, true));
|
||||
break;
|
||||
default:
|
||||
{
|
||||
if (!insn->inst->name || strcmp (insn->inst->name, "Unknown") == 0) {
|
||||
@@ -1323,22 +1214,8 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
|
||||
/* Generic compare-jump fallback (covers variants not explicitly handled above). */
|
||||
const char *cmp = jump_cmp_operator (op);
|
||||
if (cmp && is_operand_addr (insn->inst, 0) && is_operand_register (insn->inst, 1) && is_operand_register (insn->inst, 2)) {
|
||||
i32 rel = (i32)get_operand_value (insn_c, 0);
|
||||
u32 target = compute_target_address (insn, 0);
|
||||
if (rel > 0) {
|
||||
RETURN_IF_ERROR (add (out, create_jump_not_condition_token (target)));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("!")));
|
||||
RETURN_IF_ERROR (add (out, create_left_parenthesis_token ()));
|
||||
} else {
|
||||
RETURN_IF_ERROR (add (out, create_jump_condition_token (target)));
|
||||
}
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 1)));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token (cmp)));
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 2)));
|
||||
if (rel > 0) {
|
||||
RETURN_IF_ERROR (add (out, create_right_parenthesis_token ()));
|
||||
}
|
||||
if (cmp && _hbc_operand_is_addr (insn->inst, 0) && is_operand_register (insn->inst, 1) && is_operand_register (insn->inst, 2)) {
|
||||
RETURN_IF_ERROR (emit_cmp_jump (out, insn_c, cmp));
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
+308
-166
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/hbc.h>
|
||||
#include <hbc/common.h>
|
||||
#include <hbc/disasm.h>
|
||||
@@ -119,7 +121,7 @@ Result hbc_open_from_memory(const u8 *data, size_t size, HBC **out) {
|
||||
return res;
|
||||
}
|
||||
/* Read only the DebugInfoHeader eagerly — a handful of u32s — so
|
||||
* hbc_has_source_lines() can answer accurately without committing to the
|
||||
* hbc_has_source_lines () can answer accurately without committing to the
|
||||
* full body parse. The filename, file region and sources/scope/textified
|
||||
* storage blobs are read lazily on first hbc_get_source_lines /
|
||||
* hbc_get_debug_info call. */
|
||||
@@ -276,7 +278,7 @@ static bool read_sleb128(const u8 *data, size_t size, size_t *pos, int64_t *out)
|
||||
if (shift == 63 && (byte & 0x7e)) {
|
||||
return false;
|
||||
}
|
||||
result |= ((uint64_t)(byte & 0x7f)) << shift;
|
||||
result |= ((uint64_t) (byte & 0x7f)) << shift;
|
||||
shift += 7;
|
||||
} while (byte & 0x80);
|
||||
|
||||
@@ -340,7 +342,11 @@ static Result append_source_line(HBCSourceLineArray *arr, u32 *cap, const HBCSou
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
#define PARSE_FAIL(msg) do { res = ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, msg); goto fail; } while (0)
|
||||
#define PARSE_FAIL(msg) \
|
||||
do { \
|
||||
res = ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, msg); \
|
||||
goto fail; \
|
||||
} while (0)
|
||||
|
||||
Result hbc_get_source_lines(HBC *hbc, HBCSourceLineArray *out) {
|
||||
if (!hbc || !out) {
|
||||
@@ -361,14 +367,10 @@ Result hbc_get_source_lines(HBC *hbc, HBCSourceLineArray *out) {
|
||||
while (pos < size) {
|
||||
const size_t source_offset = pos;
|
||||
int64_t function_id = 0, line = 0, column = 0;
|
||||
if (source_offset > UINT32_MAX
|
||||
|| !read_sleb128 (data, size, &pos, &function_id)
|
||||
|| !read_sleb128 (data, size, &pos, &line)
|
||||
|| !read_sleb128 (data, size, &pos, &column)) {
|
||||
if (source_offset > UINT32_MAX || !read_sleb128 (data, size, &pos, &function_id) || !read_sleb128 (data, size, &pos, &line) || !read_sleb128 (data, size, &pos, &column)) {
|
||||
PARSE_FAIL ("Invalid source location header");
|
||||
}
|
||||
if (function_id < 0 || function_id >= (int64_t)reader->header.functionCount
|
||||
|| line < 0 || line > UINT32_MAX || column < 0 || column > UINT32_MAX) {
|
||||
if (function_id < 0 || function_id >= (int64_t)reader->header.functionCount || line < 0 || line > UINT32_MAX || column < 0 || column > UINT32_MAX) {
|
||||
PARSE_FAIL ("Source location header out of range");
|
||||
}
|
||||
|
||||
@@ -396,11 +398,7 @@ Result hbc_get_source_lines(HBC *hbc, HBCSourceLineArray *out) {
|
||||
break;
|
||||
}
|
||||
int64_t ldelta = 0, cdelta = 0, scope = 0, env = 0, sdelta = 0;
|
||||
if (adelta < 0
|
||||
|| !read_sleb128 (data, size, &pos, &ldelta)
|
||||
|| !read_sleb128 (data, size, &pos, &cdelta)
|
||||
|| !read_sleb128 (data, size, &pos, &scope)
|
||||
|| !read_sleb128 (data, size, &pos, &env)) {
|
||||
if (adelta < 0 || !read_sleb128 (data, size, &pos, &ldelta) || !read_sleb128 (data, size, &pos, &cdelta) || !read_sleb128 (data, size, &pos, &scope) || !read_sleb128 (data, size, &pos, &env)) {
|
||||
PARSE_FAIL ("Invalid source location entry");
|
||||
}
|
||||
if ((ldelta & 1) && !read_sleb128 (data, size, &pos, &sdelta)) {
|
||||
@@ -411,9 +409,7 @@ Result hbc_get_source_lines(HBC *hbc, HBCSourceLineArray *out) {
|
||||
line += ldelta;
|
||||
column += cdelta;
|
||||
statement += sdelta;
|
||||
if (address < 0 || address > UINT32_MAX || (uint64_t)fi.offset + (uint64_t)address > UINT32_MAX
|
||||
|| line < 0 || line > UINT32_MAX || column < 0 || column > UINT32_MAX
|
||||
|| statement < 0 || statement > UINT32_MAX) {
|
||||
if (address < 0 || address > UINT32_MAX || (uint64_t)fi.offset + (uint64_t)address > UINT32_MAX || line < 0 || line > UINT32_MAX || column < 0 || column > UINT32_MAX || statement < 0 || statement > UINT32_MAX) {
|
||||
PARSE_FAIL ("Source location entry out of range");
|
||||
}
|
||||
sl.address = fi.offset + (u32)address;
|
||||
@@ -626,6 +622,10 @@ static char *build_literal_comment(HBCReader *reader, const char *mnemonic, cons
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Read each operand by type into values[6], little-endian with sign-extension
|
||||
* for relative addresses; returns total instruction size, or 0 if truncated. */
|
||||
static size_t decode_operands(const Instruction *inst, const u8 *bytes, size_t len, u32 values[6]);
|
||||
|
||||
Result hbc_dec(const HBCDecodeCtx *ctx, HBCInsnInfo *out) {
|
||||
if (!ctx || !out) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Invalid context");
|
||||
@@ -665,62 +665,8 @@ Result hbc_dec(const HBCDecodeCtx *ctx, HBCInsnInfo *out) {
|
||||
}
|
||||
|
||||
/* Parse operands */
|
||||
u32 operand_values[6] = { 0 };
|
||||
size_t pos = 1; /* Start after opcode */
|
||||
|
||||
for (int i = 0; i < 6 && inst->operands[i].operand_type != OPERAND_TYPE_NONE; i++) {
|
||||
OperandType operand_type = inst->operands[i].operand_type;
|
||||
|
||||
/* Check if we have enough bytes */
|
||||
size_t need = 0;
|
||||
switch (operand_type) {
|
||||
case OPERAND_TYPE_REG8:
|
||||
case OPERAND_TYPE_UINT8:
|
||||
case OPERAND_TYPE_ADDR8: need = 1; break;
|
||||
case OPERAND_TYPE_UINT16: need = 2; break;
|
||||
case OPERAND_TYPE_REG32:
|
||||
case OPERAND_TYPE_UINT32:
|
||||
case OPERAND_TYPE_IMM32:
|
||||
case OPERAND_TYPE_ADDR32: need = 4; break;
|
||||
case OPERAND_TYPE_DOUBLE: need = 8; break;
|
||||
default: need = 0; break;
|
||||
}
|
||||
|
||||
if (pos + need > ctx->len) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Read operand value */
|
||||
switch (operand_type) {
|
||||
case OPERAND_TYPE_REG8:
|
||||
case OPERAND_TYPE_UINT8:
|
||||
operand_values[i] = ctx->bytes[pos];
|
||||
pos += 1;
|
||||
break;
|
||||
case OPERAND_TYPE_ADDR8:
|
||||
operand_values[i] = (u32) (i32) (i8)ctx->bytes[pos];
|
||||
pos += 1;
|
||||
break;
|
||||
case OPERAND_TYPE_UINT16:
|
||||
operand_values[i] = (u32)ctx->bytes[pos] | ((u32)ctx->bytes[pos + 1] << 8);
|
||||
pos += 2;
|
||||
break;
|
||||
case OPERAND_TYPE_REG32:
|
||||
case OPERAND_TYPE_UINT32:
|
||||
case OPERAND_TYPE_IMM32:
|
||||
case OPERAND_TYPE_ADDR32:
|
||||
operand_values[i] = (u32)ctx->bytes[pos] | ((u32)ctx->bytes[pos + 1] << 8) |
|
||||
((u32)ctx->bytes[pos + 2] << 16) | ((u32)ctx->bytes[pos + 3] << 24);
|
||||
pos += 4;
|
||||
break;
|
||||
case OPERAND_TYPE_DOUBLE:
|
||||
/* Skip for now */
|
||||
pos += 8;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
u32 operand_values[6];
|
||||
size_t pos = decode_operands (inst, ctx->bytes, ctx->len, operand_values);
|
||||
|
||||
/* Format output string */
|
||||
char buf[512];
|
||||
@@ -832,43 +778,41 @@ Result hbc_dec(const HBCDecodeCtx *ctx, HBCInsnInfo *out) {
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
const char *name; /* exact match, else match prefix */
|
||||
const char *name; /* exact match, else match prefix */
|
||||
const char *prefix;
|
||||
const char *kind;
|
||||
const char *module; /* NULL for exports */
|
||||
const char *module; /* NULL for exports */
|
||||
HBCBindingType type;
|
||||
} BindingHint;
|
||||
|
||||
static const BindingHint g_hints[] = {
|
||||
{ "NativeModules", NULL, "native", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "TurboModuleRegistry", NULL, "native", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "__turboModuleProxy", NULL, "native", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "requireNativeComponent", NULL, "native", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "codegenNativeComponent", NULL, "native", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "NativeEventEmitter", NULL, "native", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "RCTDeviceEventEmitter", NULL, "native", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "HermesInternal", NULL, "native", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "require", NULL, "js", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "metroRequire", NULL, "js", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "__r", NULL, "js", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "importScripts", NULL, "js", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "global", NULL, "global", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "globalThis", NULL, "global", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "exports", NULL, "module", NULL, HBC_BINDING_EXPORT },
|
||||
{ "module", NULL, "module", NULL, HBC_BINDING_EXPORT },
|
||||
{ "__esModule", NULL, "module", NULL, HBC_BINDING_EXPORT },
|
||||
{ "default", NULL, "module", NULL, HBC_BINDING_EXPORT },
|
||||
{ NULL, "RCT", "native", "react-native", HBC_BINDING_IMPORT },
|
||||
{ NULL, "Native", "native", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "NativeModules", NULL, "native", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "TurboModuleRegistry", NULL, "native", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "__turboModuleProxy", NULL, "native", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "requireNativeComponent", NULL, "native", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "codegenNativeComponent", NULL, "native", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "NativeEventEmitter", NULL, "native", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "RCTDeviceEventEmitter", NULL, "native", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "HermesInternal", NULL, "native", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "require", NULL, "js", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "metroRequire", NULL, "js", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "__r", NULL, "js", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "importScripts", NULL, "js", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "global", NULL, "global", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "globalThis", NULL, "global", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "exports", NULL, "module", NULL, HBC_BINDING_EXPORT },
|
||||
{ "module", NULL, "module", NULL, HBC_BINDING_EXPORT },
|
||||
{ "__esModule", NULL, "module", NULL, HBC_BINDING_EXPORT },
|
||||
{ "default", NULL, "module", NULL, HBC_BINDING_EXPORT },
|
||||
{ NULL, "RCT", "native", "react-native", HBC_BINDING_IMPORT },
|
||||
{ NULL, "Native", "native", "react-native", HBC_BINDING_IMPORT },
|
||||
};
|
||||
#define HINTS_N (sizeof (g_hints) / sizeof (g_hints[0]))
|
||||
|
||||
static bool str_eq(const char *a, const char *b) {
|
||||
if (a == b) {
|
||||
return true;
|
||||
}
|
||||
return a && b && !strcmp (a, b);
|
||||
}
|
||||
/* Per-sid dedup flags for hbc_scan_bindings; avoids O (N^2) strcmp dedup. */
|
||||
#define SEEN_CJS 0x1
|
||||
#define SEEN_IMP 0x2
|
||||
#define SEEN_EXP 0x4
|
||||
|
||||
static bool is_export_marker(const char *s) {
|
||||
return !strcmp (s, "exports") || !strcmp (s, "module");
|
||||
@@ -878,15 +822,7 @@ static bool is_export_store(const char *name) {
|
||||
return strstr (name, "put") && strstr (name, "by_id");
|
||||
}
|
||||
|
||||
static Result binding_add(HBCBindings *out, HBCBindingType type, const char *kind,
|
||||
const char *name, const char *module, u32 function_id, u32 offset, u32 string_id) {
|
||||
for (u32 i = 0; i < out->count; i++) {
|
||||
HBCBinding *b = &out->bindings[i];
|
||||
if (b->type == type && !strcmp (b->kind, kind)
|
||||
&& !strcmp (b->name, name) && str_eq (b->module, module)) {
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
}
|
||||
static Result binding_add(HBCBindings *out, HBCBindingType type, const char *kind, const char *name, const char *module, u32 function_id, u32 offset, u32 string_id) {
|
||||
HBCBinding *nb = realloc (out->bindings, (out->count + 1) * sizeof (HBCBinding));
|
||||
if (!nb) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "OOM");
|
||||
@@ -894,18 +830,21 @@ static Result binding_add(HBCBindings *out, HBCBindingType type, const char *kin
|
||||
out->bindings = nb;
|
||||
HBCBinding *b = &nb[out->count++];
|
||||
*b = (HBCBinding){
|
||||
.type = type, .kind = kind, .name = strdup (name),
|
||||
.type = type,
|
||||
.kind = kind,
|
||||
.name = strdup (name),
|
||||
.module = module? strdup (module): NULL,
|
||||
.function_id = function_id, .offset = offset, .string_id = string_id,
|
||||
};
|
||||
.function_id = function_id,
|
||||
.offset = offset,
|
||||
.string_id = string_id,
|
||||
};
|
||||
if (!b->name || (module && !b->module)) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "OOM");
|
||||
}
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
static size_t decode_operands(const Instruction *inst, const u8 *bytes, size_t len,
|
||||
u32 values[6]) {
|
||||
static size_t decode_operands(const Instruction *inst, const u8 *bytes, size_t len, u32 values[6]) {
|
||||
size_t pos = 1;
|
||||
memset (values, 0, 6 * sizeof (u32));
|
||||
for (int i = 0; i < 6 && inst->operands[i].operand_type != OPERAND_TYPE_NONE; i++) {
|
||||
@@ -915,17 +854,23 @@ static size_t decode_operands(const Instruction *inst, const u8 *bytes, size_t l
|
||||
case OPERAND_TYPE_REG8:
|
||||
case OPERAND_TYPE_UINT8:
|
||||
need = 1;
|
||||
if (pos + need > len) { return 0; }
|
||||
if (pos + need > len) {
|
||||
return 0;
|
||||
}
|
||||
values[i] = bytes[pos];
|
||||
break;
|
||||
case OPERAND_TYPE_ADDR8:
|
||||
need = 1;
|
||||
if (pos + need > len) { return 0; }
|
||||
values[i] = (u32)(i32)(i8)bytes[pos];
|
||||
if (pos + need > len) {
|
||||
return 0;
|
||||
}
|
||||
values[i] = (u32) (i32) (i8)bytes[pos];
|
||||
break;
|
||||
case OPERAND_TYPE_UINT16:
|
||||
need = 2;
|
||||
if (pos + need > len) { return 0; }
|
||||
if (pos + need > len) {
|
||||
return 0;
|
||||
}
|
||||
values[i] = (u32)bytes[pos] | ((u32)bytes[pos + 1] << 8);
|
||||
break;
|
||||
case OPERAND_TYPE_REG32:
|
||||
@@ -933,13 +878,16 @@ static size_t decode_operands(const Instruction *inst, const u8 *bytes, size_t l
|
||||
case OPERAND_TYPE_IMM32:
|
||||
case OPERAND_TYPE_ADDR32:
|
||||
need = 4;
|
||||
if (pos + need > len) { return 0; }
|
||||
values[i] = (u32)bytes[pos] | ((u32)bytes[pos + 1] << 8)
|
||||
| ((u32)bytes[pos + 2] << 16) | ((u32)bytes[pos + 3] << 24);
|
||||
if (pos + need > len) {
|
||||
return 0;
|
||||
}
|
||||
values[i] = (u32)bytes[pos] | ((u32)bytes[pos + 1] << 8) | ((u32)bytes[pos + 2] << 16) | ((u32)bytes[pos + 3] << 24);
|
||||
break;
|
||||
case OPERAND_TYPE_DOUBLE:
|
||||
need = 8;
|
||||
if (pos + need > len) { return 0; }
|
||||
if (pos + need > len) {
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
@@ -949,43 +897,35 @@ static size_t decode_operands(const Instruction *inst, const u8 *bytes, size_t l
|
||||
return pos;
|
||||
}
|
||||
|
||||
static Result scan_hint_matches(HBCBindings *out, const char *s, u32 fid, u32 off, u32 sid) {
|
||||
static Result scan_hint_matches(HBCBindings *out, const char *s, u32 fid, u32 off, u32 sid, u8 *flag) {
|
||||
/* Dedup identical (kind,module) across hints (pointers are stable literals).
|
||||
* Set SEEN_EXP when adding an EXPORT so caller's put_by_id skips this sid. */
|
||||
const void *added[HINTS_N * 2];
|
||||
u32 added_n = 0;
|
||||
for (size_t h = 0; h < HINTS_N; h++) {
|
||||
const BindingHint *hint = &g_hints[h];
|
||||
bool match = hint->name? !strcmp (s, hint->name):
|
||||
!strncmp (s, hint->prefix, strlen (hint->prefix));
|
||||
bool match = hint->name? !strcmp (s, hint->name): !strncmp (s, hint->prefix, strlen (hint->prefix));
|
||||
if (!match) {
|
||||
continue;
|
||||
}
|
||||
RETURN_IF_ERROR (binding_add (out, hint->type, hint->kind, s,
|
||||
hint->module, fid, off, sid));
|
||||
}
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
static bool function_has_export_marker(HBCReader *r, const u8 *code, u32 size, HBCISA isa) {
|
||||
for (u32 pc = 0; pc < size; ) {
|
||||
u8 op = code[pc];
|
||||
const Instruction *inst = (op < isa.count)? &isa.instructions[op]: NULL;
|
||||
if (!inst || !inst->name || !inst->binary_size) {
|
||||
break;
|
||||
}
|
||||
u32 values[6];
|
||||
if (!decode_operands (inst, code + pc, size - pc, values)) {
|
||||
break;
|
||||
}
|
||||
for (int i = 0; i < 6 && inst->operands[i].operand_type != OPERAND_TYPE_NONE; i++) {
|
||||
if (inst->operands[i].operand_meaning == OPERAND_MEANING_STRING_ID) {
|
||||
u32 sid = values[i];
|
||||
const char *s = (sid < r->header.stringCount && r->strings)? r->strings[sid]: NULL;
|
||||
if (s && is_export_marker (s)) {
|
||||
return true;
|
||||
}
|
||||
bool dup = false;
|
||||
for (u32 a = 0; a < added_n; a += 2) {
|
||||
if (added[a] == hint->kind && added[a + 1] == hint->module) {
|
||||
dup = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
pc += inst->binary_size;
|
||||
if (dup) {
|
||||
continue;
|
||||
}
|
||||
added[added_n++] = hint->kind;
|
||||
added[added_n++] = hint->module;
|
||||
if (hint->type == HBC_BINDING_EXPORT) {
|
||||
*flag |= SEEN_EXP;
|
||||
}
|
||||
RETURN_IF_ERROR (binding_add (out, hint->type, hint->kind, s, hint->module, fid, off, sid));
|
||||
}
|
||||
return false;
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
Result hbc_scan_bindings(HBC *hbc, HBCBindings *out) {
|
||||
@@ -998,27 +938,41 @@ Result hbc_scan_bindings(HBC *hbc, HBCBindings *out) {
|
||||
if (!isa.instructions) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "no isa for version");
|
||||
}
|
||||
u32 sc = r->header.stringCount;
|
||||
u8 *seen = sc? calloc (sc, 1): NULL;
|
||||
if (sc && !seen) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "OOM");
|
||||
}
|
||||
Result res = SUCCESS_RESULT ();
|
||||
if (r->header.cjsModuleCount > 0 && r->header.version >= 77 && r->cjs_modules) {
|
||||
for (u32 i = 0; i < r->header.cjsModuleCount; i++) {
|
||||
u32 sid = r->cjs_modules[i].symbol_id;
|
||||
const char *name = (sid < r->header.stringCount && r->strings)? r->strings[sid]: NULL;
|
||||
if (name && *name) {
|
||||
RETURN_IF_ERROR (binding_add (out, HBC_BINDING_IMPORT,
|
||||
"cjs", name, "commonjs", 0, r->cjs_modules[i].offset, sid));
|
||||
const char *name = (sid < sc && r->strings)? r->strings[sid]: NULL;
|
||||
if (!name || !*name || (seen[sid] & SEEN_CJS)) {
|
||||
continue;
|
||||
}
|
||||
seen[sid] |= SEEN_CJS;
|
||||
res = binding_add (out, HBC_BINDING_IMPORT, "cjs", name, "commonjs", 0, r->cjs_modules[i].offset, sid);
|
||||
if (res.code != RESULT_SUCCESS) {
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
}
|
||||
struct {
|
||||
u32 sid, off;
|
||||
} *cand = NULL;
|
||||
u32 cand_cap = 0;
|
||||
for (u32 fid = 0; fid < r->header.functionCount; fid++) {
|
||||
const u8 *code = NULL;
|
||||
u32 size = 0;
|
||||
if (hbc_get_function_bytecode (hbc, fid, &code, &size).code != RESULT_SUCCESS
|
||||
|| !code || !size) {
|
||||
if (hbc_get_function_bytecode (hbc, fid, &code, &size).code != RESULT_SUCCESS || !code || !size) {
|
||||
continue;
|
||||
}
|
||||
HBCFunc fi = { 0 };
|
||||
(void)hbc_get_function_info (hbc, fid, &fi);
|
||||
bool has_export_marker = function_has_export_marker (r, code, size, isa);
|
||||
for (u32 pc = 0; pc < size; ) {
|
||||
u32 cand_n = 0;
|
||||
bool has_marker = false;
|
||||
for (u32 pc = 0; pc < size;) {
|
||||
u8 op = code[pc];
|
||||
const Instruction *inst = (op < isa.count)? &isa.instructions[op]: NULL;
|
||||
if (!inst || !inst->name || !inst->binary_size) {
|
||||
@@ -1028,25 +982,71 @@ Result hbc_scan_bindings(HBC *hbc, HBCBindings *out) {
|
||||
if (!decode_operands (inst, code + pc, size - pc, values)) {
|
||||
break;
|
||||
}
|
||||
bool is_store = is_export_store (inst->name);
|
||||
for (int i = 0; i < 6 && inst->operands[i].operand_type != OPERAND_TYPE_NONE; i++) {
|
||||
if (inst->operands[i].operand_meaning != OPERAND_MEANING_STRING_ID) {
|
||||
continue;
|
||||
}
|
||||
u32 sid = values[i];
|
||||
const char *s = (sid < r->header.stringCount && r->strings)? r->strings[sid]: NULL;
|
||||
if (sid >= sc || !r->strings) {
|
||||
continue;
|
||||
}
|
||||
const char *s = r->strings[sid];
|
||||
if (!s || !*s) {
|
||||
continue;
|
||||
}
|
||||
RETURN_IF_ERROR (scan_hint_matches (out, s, fid, fi.offset + pc, sid));
|
||||
if (has_export_marker && is_export_store (inst->name) && !is_export_marker (s)) {
|
||||
RETURN_IF_ERROR (binding_add (out, HBC_BINDING_EXPORT,
|
||||
"module", s, NULL, fid, fi.offset + pc, sid));
|
||||
bool marker = is_export_marker (s);
|
||||
if (marker) {
|
||||
has_marker = true;
|
||||
}
|
||||
if (! (seen[sid] & SEEN_IMP)) {
|
||||
seen[sid] |= SEEN_IMP;
|
||||
res = scan_hint_matches (out, s, fid, fi.offset + pc, sid, &seen[sid]);
|
||||
if (res.code != RESULT_SUCCESS) {
|
||||
goto done_scan;
|
||||
}
|
||||
}
|
||||
if (is_store && !marker && ! (seen[sid] & SEEN_EXP)) {
|
||||
if (cand_n == cand_cap) {
|
||||
u32 nc = cand_cap? cand_cap * 2: 16;
|
||||
void *nb = realloc (cand, nc * sizeof (*cand));
|
||||
if (!nb) {
|
||||
res = ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "OOM");
|
||||
goto done_scan;
|
||||
}
|
||||
cand = nb;
|
||||
cand_cap = nc;
|
||||
}
|
||||
cand[cand_n].sid = sid;
|
||||
cand[cand_n].off = fi.offset + pc;
|
||||
cand_n++;
|
||||
}
|
||||
}
|
||||
pc += inst->binary_size;
|
||||
}
|
||||
if (!has_marker) {
|
||||
continue;
|
||||
}
|
||||
for (u32 k = 0; k < cand_n; k++) {
|
||||
u32 sid = cand[k].sid;
|
||||
if (seen[sid] & SEEN_EXP) {
|
||||
continue;
|
||||
}
|
||||
seen[sid] |= SEEN_EXP;
|
||||
res = binding_add (out, HBC_BINDING_EXPORT, "module", r->strings[sid], NULL, fid, cand[k].off, sid);
|
||||
if (res.code != RESULT_SUCCESS) {
|
||||
goto done_scan;
|
||||
}
|
||||
}
|
||||
}
|
||||
return SUCCESS_RESULT ();
|
||||
done_scan:
|
||||
free (cand);
|
||||
done:
|
||||
free (seen);
|
||||
if (res.code != RESULT_SUCCESS) {
|
||||
hbc_free_bindings (out);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void hbc_free_bindings(HBCBindings *bindings) {
|
||||
@@ -1062,6 +1062,148 @@ void hbc_free_bindings(HBCBindings *bindings) {
|
||||
bindings->count = 0;
|
||||
}
|
||||
|
||||
static char *dup_range(const char *s, size_t n) {
|
||||
char *ret = (char *)malloc (n + 1);
|
||||
if (!ret) {
|
||||
return NULL;
|
||||
}
|
||||
memcpy (ret, s, n);
|
||||
ret[n] = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static char *package_from_path(const char *s) {
|
||||
const char *nm = strstr (s, "node_modules/");
|
||||
if (nm) {
|
||||
const char *p = nm + strlen ("node_modules/");
|
||||
const char *end;
|
||||
if (*p == '@') {
|
||||
const char *scope_end = strchr (p, '/');
|
||||
if (!scope_end) {
|
||||
return strdup (p);
|
||||
}
|
||||
end = strchr (scope_end + 1, '/');
|
||||
} else {
|
||||
end = strchr (p, '/');
|
||||
}
|
||||
return end? dup_range (p, (size_t) (end - p)): strdup (p);
|
||||
}
|
||||
if (s[0] == '@') {
|
||||
const char *scope_end = strchr (s, '/');
|
||||
if (scope_end) {
|
||||
const char *end = strchr (scope_end + 1, '/');
|
||||
return end? dup_range (s, (size_t) (end - s)): strdup (s);
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static bool module_eq(const HBCModule *m, const char *kind, const char *name) {
|
||||
return m->kind && kind && !strcmp (m->kind, kind) && m->name && name && !strcmp (m->name, name);
|
||||
}
|
||||
|
||||
static Result module_add(HBCModules *out, const char *kind, const char *name, const char *path, const char *version, u32 function_id, u32 offset, u32 string_id, bool inferred) {
|
||||
if (!name || !*name) {
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
for (u32 i = 0; i < out->count; i++) {
|
||||
if (module_eq (&out->modules[i], kind, name)) {
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
}
|
||||
HBCModule *nm = realloc (out->modules, (out->count + 1) * sizeof (HBCModule));
|
||||
if (!nm) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "OOM");
|
||||
}
|
||||
out->modules = nm;
|
||||
HBCModule *m = &nm[out->count++];
|
||||
*m = (HBCModule){
|
||||
.name = strdup (name),
|
||||
.path = path? strdup (path): NULL,
|
||||
.version = version? strdup (version): NULL,
|
||||
.kind = kind,
|
||||
.function_id = function_id,
|
||||
.offset = offset,
|
||||
.string_id = string_id,
|
||||
.inferred = inferred,
|
||||
};
|
||||
if (!m->name || (path && !m->path) || (version && !m->version)) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "OOM");
|
||||
}
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
Result hbc_list_modules(HBC *hbc, HBCModules *out) {
|
||||
if (!hbc || !out) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "invalid args");
|
||||
}
|
||||
memset (out, 0, sizeof (*out));
|
||||
HBCReader *r = &hbc->reader;
|
||||
if (r->header.cjsModuleCount > 0 && r->header.version >= 77 && r->cjs_modules) {
|
||||
for (u32 i = 0; i < r->header.cjsModuleCount; i++) {
|
||||
u32 sid = r->cjs_modules[i].symbol_id;
|
||||
const char *s = (sid < r->header.stringCount && r->strings)? r->strings[sid]: NULL;
|
||||
if (!s || !*s) {
|
||||
continue;
|
||||
}
|
||||
char *pkg = package_from_path (s);
|
||||
if (pkg) {
|
||||
Result res = module_add (out, "package", pkg, s, NULL, 0, r->cjs_modules[i].offset, sid, true);
|
||||
free (pkg);
|
||||
if (res.code != RESULT_SUCCESS) {
|
||||
hbc_free_modules (out);
|
||||
return res;
|
||||
}
|
||||
} else {
|
||||
Result res = module_add (out, "cjs", s, s, NULL, 0, r->cjs_modules[i].offset, sid, false);
|
||||
if (res.code != RESULT_SUCCESS) {
|
||||
hbc_free_modules (out);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HBCBindings bindings = { 0 };
|
||||
Result res = hbc_scan_bindings (hbc, &bindings);
|
||||
if (res.code != RESULT_SUCCESS) {
|
||||
hbc_free_modules (out);
|
||||
return res;
|
||||
}
|
||||
for (u32 i = 0; i < bindings.count; i++) {
|
||||
HBCBinding *b = &bindings.bindings[i];
|
||||
if (b->type != HBC_BINDING_IMPORT) {
|
||||
continue;
|
||||
}
|
||||
const char *name = b->module? b->module: b->name;
|
||||
if (!name || !*name || !strcmp (name, "commonjs")) {
|
||||
name = b->name;
|
||||
}
|
||||
res = module_add (out, b->kind? b->kind: "js", name, b->name, NULL, b->function_id, b->offset, b->string_id, true);
|
||||
if (res.code != RESULT_SUCCESS) {
|
||||
hbc_free_bindings (&bindings);
|
||||
hbc_free_modules (out);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
hbc_free_bindings (&bindings);
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
void hbc_free_modules(HBCModules *modules) {
|
||||
if (!modules || !modules->modules) {
|
||||
return;
|
||||
}
|
||||
for (u32 i = 0; i < modules->count; i++) {
|
||||
free (modules->modules[i].name);
|
||||
free (modules->modules[i].path);
|
||||
free (modules->modules[i].version);
|
||||
}
|
||||
free (modules->modules);
|
||||
modules->modules = NULL;
|
||||
modules->count = 0;
|
||||
}
|
||||
|
||||
/* Encoding functions */
|
||||
|
||||
Result hbc_enc(
|
||||
|
||||
@@ -1,9 +1,32 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#ifndef HBC_INTERNAL_H
|
||||
#define HBC_INTERNAL_H
|
||||
|
||||
#include <hbc/hbc.h>
|
||||
#include <hbc/parser.h>
|
||||
#include <hbc/literals.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Grow a dynamic array (doubling) so it has room for one more element.
|
||||
* arrp points at the array pointer (e.g. &vec->data); *cap is the capacity in
|
||||
* elements; initial is the capacity to allocate when growing from empty. The
|
||||
* stored pointer is read/written via memcpy so no aliasing cast is needed. */
|
||||
static inline Result grow_array(void *arrp, u32 *cap, u32 count, size_t elem_size, u32 initial) {
|
||||
if (count < *cap) {
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
u32 new_cap = *cap? *cap * 2: initial;
|
||||
void *cur;
|
||||
memcpy (&cur, arrp, sizeof (cur));
|
||||
void *grown = realloc (cur, (size_t)new_cap * elem_size);
|
||||
if (!grown) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "out of memory");
|
||||
}
|
||||
memcpy (arrp, &grown, sizeof (grown));
|
||||
*cap = new_cap;
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
HBCLiteralEntry *entries;
|
||||
|
||||
+58
-88
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
/* Public literals API: lazy cache, formatting, scanning. See
|
||||
* include/hbc/literals.h for the contract. */
|
||||
|
||||
@@ -10,6 +12,14 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Little-endian readers for raw bytecode operands. */
|
||||
static inline u32 rd_u16le(const u8 *p) {
|
||||
return (u32)p[0] | ((u32)p[1] << 8);
|
||||
}
|
||||
static inline u32 rd_u32le(const u8 *p) {
|
||||
return (u32)p[0] | ((u32)p[1] << 8) | ((u32)p[2] << 16) | ((u32)p[3] << 24);
|
||||
}
|
||||
|
||||
/* ---------- Global toggles ---------- */
|
||||
|
||||
static bool g_inline_literals = false;
|
||||
@@ -60,8 +70,7 @@ u32 hbc_get_object_values_size(HBC *hbc) {
|
||||
|
||||
/* ---------- Formatting ---------- */
|
||||
|
||||
static Result format_raw_impl(HBCReader *r, HBCLiteralKind kind, u32 num_items,
|
||||
u32 primary_id, u32 secondary_id, char **out) {
|
||||
static Result format_raw_impl(HBCReader *r, HBCLiteralKind kind, u32 num_items, u32 primary_id, u32 secondary_id, char **out) {
|
||||
*out = NULL;
|
||||
StringBuffer sb;
|
||||
Result ir = _hbc_string_buffer_init (&sb, 128);
|
||||
@@ -70,14 +79,12 @@ static Result format_raw_impl(HBCReader *r, HBCLiteralKind kind, u32 num_items,
|
||||
}
|
||||
Result fr;
|
||||
if (kind == HBC_LIT_ARRAY) {
|
||||
fr = _hbc_format_array_literal (r, num_items, primary_id, &sb,
|
||||
LITERALS_PRETTY_NEVER, true);
|
||||
fr = _hbc_format_array_literal (r, num_items, primary_id, &sb, LITERALS_PRETTY_NEVER, true);
|
||||
} else {
|
||||
/* For v97+ primary_id is the shape index; the internal formatter
|
||||
* expects that convention already (first arg is "key_count" which is
|
||||
* reinterpreted as shape id when version >= 97). */
|
||||
fr = _hbc_format_object_literal (r, num_items, num_items, primary_id,
|
||||
secondary_id, &sb, LITERALS_PRETTY_NEVER, true);
|
||||
fr = _hbc_format_object_literal (r, num_items, num_items, primary_id, secondary_id, &sb, LITERALS_PRETTY_NEVER, true);
|
||||
}
|
||||
if (fr.code == RESULT_SUCCESS && sb.data && sb.length > 0) {
|
||||
*out = strdup (sb.data);
|
||||
@@ -89,17 +96,14 @@ static Result format_raw_impl(HBCReader *r, HBCLiteralKind kind, u32 num_items,
|
||||
return fr;
|
||||
}
|
||||
|
||||
Result hbc_literals_format_raw(HBC *hbc, HBCLiteralKind kind, u32 num_items,
|
||||
u32 primary_id, u32 secondary_id, char **out) {
|
||||
Result hbc_literals_format_raw(HBC *hbc, HBCLiteralKind kind, u32 num_items, u32 primary_id, u32 secondary_id, char **out) {
|
||||
if (!hbc || !out) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "invalid args");
|
||||
}
|
||||
return format_raw_impl (&hbc->reader, kind, num_items, primary_id,
|
||||
secondary_id, out);
|
||||
return format_raw_impl (&hbc->reader, kind, num_items, primary_id, secondary_id, out);
|
||||
}
|
||||
|
||||
Result hbc_literals_format_for_opcode(HBC *hbc, u8 opcode, u32 arg3, u32 arg4,
|
||||
u32 arg5, char **out) {
|
||||
Result hbc_literals_format_for_opcode(HBC *hbc, u8 opcode, u32 arg3, u32 arg4, u32 arg5, char **out) {
|
||||
if (!hbc || !out) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "invalid args");
|
||||
}
|
||||
@@ -117,13 +121,10 @@ Result hbc_literals_format_for_opcode(HBC *hbc, u8 opcode, u32 arg3, u32 arg4,
|
||||
|
||||
/* ---------- Cache ---------- */
|
||||
|
||||
static HBCLiteralEntry *cache_find(HBCLiteralCache *c, HBCLiteralKind kind,
|
||||
u32 num_items, u32 primary_id, u32 secondary_id) {
|
||||
static HBCLiteralEntry *cache_find(HBCLiteralCache *c, HBCLiteralKind kind, u32 num_items, u32 primary_id, u32 secondary_id) {
|
||||
for (u32 i = 0; i < c->count; i++) {
|
||||
HBCLiteralEntry *e = &c->entries[i];
|
||||
if (e->kind == kind && e->num_items == num_items
|
||||
&& e->primary_id == primary_id
|
||||
&& e->secondary_id == secondary_id) {
|
||||
if (e->kind == kind && e->num_items == num_items && e->primary_id == primary_id && e->secondary_id == secondary_id) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
@@ -131,14 +132,8 @@ static HBCLiteralEntry *cache_find(HBCLiteralCache *c, HBCLiteralKind kind,
|
||||
}
|
||||
|
||||
static HBCLiteralEntry *cache_alloc(HBCLiteralCache *c) {
|
||||
if (c->count == c->cap) {
|
||||
u32 nc = c->cap? c->cap * 2: 64;
|
||||
HBCLiteralEntry *ne = realloc (c->entries, nc * sizeof (HBCLiteralEntry));
|
||||
if (!ne) {
|
||||
return NULL;
|
||||
}
|
||||
c->entries = ne;
|
||||
c->cap = nc;
|
||||
if (grow_array (&c->entries, &c->cap, c->count, sizeof (HBCLiteralEntry), 64).code != RESULT_SUCCESS) {
|
||||
return NULL;
|
||||
}
|
||||
HBCLiteralEntry *e = &c->entries[c->count++];
|
||||
memset (e, 0, sizeof (*e));
|
||||
@@ -159,8 +154,7 @@ static u32 entry_paddr(HBC *hbc, HBCLiteralKind kind, u32 primary_id) {
|
||||
return r->arrays_paddr + primary_id;
|
||||
}
|
||||
/* object */
|
||||
if (r->header.version >= 97 && r->object_shapes
|
||||
&& primary_id < r->object_shape_count) {
|
||||
if (r->header.version >= 97 && r->object_shapes && primary_id < r->object_shape_count) {
|
||||
u32 key_off = r->object_shapes[primary_id].key_buffer_offset;
|
||||
if (r->object_keys && key_off < r->header.objKeyBufferSize) {
|
||||
return r->object_keys_paddr + key_off;
|
||||
@@ -173,10 +167,8 @@ static u32 entry_paddr(HBC *hbc, HBCLiteralKind kind, u32 primary_id) {
|
||||
return r->object_keys_paddr + primary_id;
|
||||
}
|
||||
|
||||
static Result cache_get_or_create(HBC *hbc, HBCLiteralKind kind, u32 num_items,
|
||||
u32 primary_id, u32 secondary_id, HBCLiteralEntry **out_entry) {
|
||||
HBCLiteralEntry *e = cache_find (&hbc->lit_cache, kind, num_items,
|
||||
primary_id, secondary_id);
|
||||
static Result cache_get_or_create(HBC *hbc, HBCLiteralKind kind, u32 num_items, u32 primary_id, u32 secondary_id, HBCLiteralEntry **out_entry) {
|
||||
HBCLiteralEntry *e = cache_find (&hbc->lit_cache, kind, num_items, primary_id, secondary_id);
|
||||
if (e) {
|
||||
*out_entry = e;
|
||||
return SUCCESS_RESULT ();
|
||||
@@ -193,8 +185,7 @@ static Result cache_get_or_create(HBC *hbc, HBCLiteralKind kind, u32 num_items,
|
||||
/* Format now (lazy across calls, but eager per key once we've decided to
|
||||
* cache it). The formatted string is small and reused on every access. */
|
||||
char *txt = NULL;
|
||||
Result fr = format_raw_impl (&hbc->reader, kind, num_items, primary_id,
|
||||
secondary_id, &txt);
|
||||
Result fr = format_raw_impl (&hbc->reader, kind, num_items, primary_id, secondary_id, &txt);
|
||||
if (fr.code == RESULT_SUCCESS) {
|
||||
e->formatted = txt;
|
||||
}
|
||||
@@ -202,14 +193,12 @@ static Result cache_get_or_create(HBC *hbc, HBCLiteralKind kind, u32 num_items,
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
Result hbc_literals_get(HBC *hbc, HBCLiteralKind kind, u32 num_items,
|
||||
u32 primary_id, u32 secondary_id, const char **out_formatted) {
|
||||
Result hbc_literals_get(HBC *hbc, HBCLiteralKind kind, u32 num_items, u32 primary_id, u32 secondary_id, const char **out_formatted) {
|
||||
if (!hbc || !out_formatted) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "invalid args");
|
||||
}
|
||||
HBCLiteralEntry *e;
|
||||
Result r = cache_get_or_create (hbc, kind, num_items, primary_id,
|
||||
secondary_id, &e);
|
||||
Result r = cache_get_or_create (hbc, kind, num_items, primary_id, secondary_id, &e);
|
||||
if (r.code != RESULT_SUCCESS) {
|
||||
return r;
|
||||
}
|
||||
@@ -232,14 +221,12 @@ static void xref_add(HBCLiteralEntry *e, u32 from_addr) {
|
||||
e->xref_addrs[e->xref_count++] = from_addr;
|
||||
}
|
||||
|
||||
Result hbc_literals_register(HBC *hbc, HBCLiteralKind kind, u32 num_items,
|
||||
u32 primary_id, u32 secondary_id, u32 from_addr) {
|
||||
Result hbc_literals_register(HBC *hbc, HBCLiteralKind kind, u32 num_items, u32 primary_id, u32 secondary_id, u32 from_addr) {
|
||||
if (!hbc) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "invalid args");
|
||||
}
|
||||
HBCLiteralEntry *e;
|
||||
Result r = cache_get_or_create (hbc, kind, num_items, primary_id,
|
||||
secondary_id, &e);
|
||||
Result r = cache_get_or_create (hbc, kind, num_items, primary_id, secondary_id, &e);
|
||||
if (r.code != RESULT_SUCCESS) {
|
||||
return r;
|
||||
}
|
||||
@@ -296,9 +283,7 @@ static bool is_buffer_op(u8 opcode, HBCLiteralKind *out_kind) {
|
||||
/* Decode opcode immediates into (num_items, primary, secondary), version-aware.
|
||||
* Returns false if the opcode is not a buffer-literal constructor or if the
|
||||
* buffer would be read out of bounds. */
|
||||
static bool decode_buffer_op(u8 op, u32 version, const u8 *code, u32 size,
|
||||
u32 pc, u32 inst_size, HBCLiteralKind *kind, u32 *num_items, u32 *primary,
|
||||
u32 *secondary) {
|
||||
static bool decode_buffer_op(u8 op, u32 version, const u8 *code, u32 size, u32 pc, u32 inst_size, HBCLiteralKind *kind, u32 *num_items, u32 *primary, u32 *secondary) {
|
||||
if (pc + inst_size > size) {
|
||||
return false;
|
||||
}
|
||||
@@ -306,30 +291,29 @@ static bool decode_buffer_op(u8 op, u32 version, const u8 *code, u32 size,
|
||||
if (op == OP_NewArrayWithBuffer) {
|
||||
/* reg8 + u16 prealloc + u16 num + u16 id (v<97 AND v97+) */
|
||||
*kind = HBC_LIT_ARRAY;
|
||||
*num_items = (u32)code[pc + 4] | ((u32)code[pc + 5] << 8);
|
||||
*primary = (u32)code[pc + 6] | ((u32)code[pc + 7] << 8);
|
||||
*num_items = rd_u16le (code + pc + 4);
|
||||
*primary = rd_u16le (code + pc + 6);
|
||||
return true;
|
||||
}
|
||||
if (op == OP_NewArrayWithBufferLong) {
|
||||
/* reg8 + u16 prealloc + u16 num + u32 id */
|
||||
*kind = HBC_LIT_ARRAY;
|
||||
*num_items = (u32)code[pc + 4] | ((u32)code[pc + 5] << 8);
|
||||
*primary = (u32)code[pc + 6] | ((u32)code[pc + 7] << 8)
|
||||
| ((u32)code[pc + 8] << 16) | ((u32)code[pc + 9] << 24);
|
||||
*num_items = rd_u16le (code + pc + 4);
|
||||
*primary = rd_u32le (code + pc + 6);
|
||||
return true;
|
||||
}
|
||||
if (op == OP_NewObjectWithBuffer) {
|
||||
*kind = HBC_LIT_OBJECT;
|
||||
if (version >= 97) {
|
||||
/* reg8 + u16 shape_id + u16 values_id */
|
||||
*primary = (u32)code[pc + 2] | ((u32)code[pc + 3] << 8);
|
||||
*secondary = (u32)code[pc + 4] | ((u32)code[pc + 5] << 8);
|
||||
*num_items = 0; /* resolved via shape table by the formatter */
|
||||
*primary = rd_u16le (code + pc + 2);
|
||||
*secondary = rd_u16le (code + pc + 4);
|
||||
*num_items = 0; /* resolved via shape table by the formatter */
|
||||
} else {
|
||||
/* reg8 + u16 prealloc + u16 num + u16 keys + u16 vals */
|
||||
*num_items = (u32)code[pc + 4] | ((u32)code[pc + 5] << 8);
|
||||
*primary = (u32)code[pc + 6] | ((u32)code[pc + 7] << 8);
|
||||
*secondary = (u32)code[pc + 8] | ((u32)code[pc + 9] << 8);
|
||||
*num_items = rd_u16le (code + pc + 4);
|
||||
*primary = rd_u16le (code + pc + 6);
|
||||
*secondary = rd_u16le (code + pc + 8);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -337,18 +321,14 @@ static bool decode_buffer_op(u8 op, u32 version, const u8 *code, u32 size,
|
||||
*kind = HBC_LIT_OBJECT;
|
||||
if (version >= 97) {
|
||||
/* reg8 + u32 shape_id + u32 values_id */
|
||||
*primary = (u32)code[pc + 2] | ((u32)code[pc + 3] << 8)
|
||||
| ((u32)code[pc + 4] << 16) | ((u32)code[pc + 5] << 24);
|
||||
*secondary = (u32)code[pc + 6] | ((u32)code[pc + 7] << 8)
|
||||
| ((u32)code[pc + 8] << 16) | ((u32)code[pc + 9] << 24);
|
||||
*primary = rd_u32le (code + pc + 2);
|
||||
*secondary = rd_u32le (code + pc + 6);
|
||||
*num_items = 0;
|
||||
} else {
|
||||
/* reg8 + u16 prealloc + u16 num + u32 keys + u32 vals */
|
||||
*num_items = (u32)code[pc + 4] | ((u32)code[pc + 5] << 8);
|
||||
*primary = (u32)code[pc + 6] | ((u32)code[pc + 7] << 8)
|
||||
| ((u32)code[pc + 8] << 16) | ((u32)code[pc + 9] << 24);
|
||||
*secondary = (u32)code[pc + 10] | ((u32)code[pc + 11] << 8)
|
||||
| ((u32)code[pc + 12] << 16) | ((u32)code[pc + 13] << 24);
|
||||
*num_items = rd_u16le (code + pc + 4);
|
||||
*primary = rd_u32le (code + pc + 6);
|
||||
*secondary = rd_u32le (code + pc + 10);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -368,8 +348,7 @@ Result hbc_literals_scan_code(HBC *hbc, u32 *out_count) {
|
||||
for (u32 fid = 0; fid < nfuncs; fid++) {
|
||||
const u8 *code = NULL;
|
||||
u32 size = 0;
|
||||
if (hbc_get_function_bytecode (hbc, fid, &code, &size).code
|
||||
!= RESULT_SUCCESS || !code || size == 0) {
|
||||
if (hbc_get_function_bytecode (hbc, fid, &code, &size).code != RESULT_SUCCESS || !code || size == 0) {
|
||||
continue;
|
||||
}
|
||||
HBCFunc fi;
|
||||
@@ -386,11 +365,9 @@ Result hbc_literals_scan_code(HBC *hbc, u32 *out_count) {
|
||||
}
|
||||
HBCLiteralKind kind;
|
||||
u32 num_items = 0, primary = 0, secondary = 0;
|
||||
if (decode_buffer_op (op, version, code, size, pc, ins->binary_size,
|
||||
&kind, &num_items, &primary, &secondary)) {
|
||||
if (decode_buffer_op (op, version, code, size, pc, ins->binary_size, &kind, &num_items, &primary, &secondary)) {
|
||||
u32 call_addr = fn_paddr + pc;
|
||||
hbc_literals_register (hbc, kind, num_items, primary, secondary,
|
||||
call_addr);
|
||||
hbc_literals_register (hbc, kind, num_items, primary, secondary, call_addr);
|
||||
}
|
||||
(void)is_buffer_op; /* retained for tools that need the predicate */
|
||||
pc += ins->binary_size;
|
||||
@@ -404,8 +381,7 @@ Result hbc_literals_scan_code(HBC *hbc, u32 *out_count) {
|
||||
|
||||
/* ---------- Pool-side scan ---------- */
|
||||
|
||||
static bool slp_skip_group(const u8 *base, size_t size, size_t *pos,
|
||||
u8 *out_tag, u32 *out_length) {
|
||||
static bool slp_skip_group(const u8 *base, size_t size, size_t *pos, u8 *out_tag, u32 *out_length) {
|
||||
if (*pos >= size) {
|
||||
return false;
|
||||
}
|
||||
@@ -416,13 +392,16 @@ static bool slp_skip_group(const u8 *base, size_t size, size_t *pos,
|
||||
if (*pos >= size) {
|
||||
return false;
|
||||
}
|
||||
length = ((u32)(taglen & 0x0F) << 8) | base[(*pos)++];
|
||||
length = ((u32) (taglen & 0x0F) << 8) | base[(*pos)++];
|
||||
}
|
||||
size_t per_value = 0;
|
||||
switch (tag) {
|
||||
case 0: case 1: case 2: per_value = 0; break; /* null/true/false */
|
||||
case 0:
|
||||
case 1:
|
||||
case 2: per_value = 0; break; /* null/true/false */
|
||||
case 3: per_value = 8; break; /* number */
|
||||
case 4: case 7: per_value = 4; break; /* longstr/int */
|
||||
case 4:
|
||||
case 7: per_value = 4; break; /* longstr/int */
|
||||
case 5: per_value = 2; break; /* shortstr */
|
||||
case 6: per_value = 1; break; /* bytestr */
|
||||
default: return false;
|
||||
@@ -440,8 +419,7 @@ static bool slp_skip_group(const u8 *base, size_t size, size_t *pos,
|
||||
return true;
|
||||
}
|
||||
|
||||
Result hbc_literals_scan_pool(HBC *hbc, HBCLiteralKind kind,
|
||||
HBCPoolGroup **out, u32 *out_count) {
|
||||
Result hbc_literals_scan_pool(HBC *hbc, HBCLiteralKind kind, HBCPoolGroup **out, u32 *out_count) {
|
||||
if (!hbc || !out || !out_count) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "invalid args");
|
||||
}
|
||||
@@ -449,9 +427,7 @@ Result hbc_literals_scan_pool(HBC *hbc, HBCLiteralKind kind,
|
||||
*out_count = 0;
|
||||
|
||||
/* Object-keys on v97+: enumerate shape table instead of the raw pool. */
|
||||
if (kind == HBC_LIT_OBJECT && hbc->reader.header.version >= 97
|
||||
&& hbc->reader.object_shapes
|
||||
&& hbc->reader.object_shape_count > 0) {
|
||||
if (kind == HBC_LIT_OBJECT && hbc->reader.header.version >= 97 && hbc->reader.object_shapes && hbc->reader.object_shape_count > 0) {
|
||||
size_t n = hbc->reader.object_shape_count;
|
||||
HBCPoolGroup *arr = calloc (n, sizeof (HBCPoolGroup));
|
||||
if (!arr) {
|
||||
@@ -498,14 +474,8 @@ Result hbc_literals_scan_pool(HBC *hbc, HBCLiteralKind kind,
|
||||
if (!slp_skip_group (base, size, &pos, &tag, &length)) {
|
||||
break;
|
||||
}
|
||||
if (n == cap) {
|
||||
u32 nc = cap * 2;
|
||||
HBCPoolGroup *na = realloc (arr, nc * sizeof (HBCPoolGroup));
|
||||
if (!na) {
|
||||
break;
|
||||
}
|
||||
arr = na;
|
||||
cap = nc;
|
||||
if (grow_array (&arr, &cap, n, sizeof (HBCPoolGroup), 64).code != RESULT_SUCCESS) {
|
||||
break;
|
||||
}
|
||||
arr[n].pool_offset = (u32)group_start;
|
||||
arr[n].paddr = paddr + (u32)group_start;
|
||||
|
||||
+7
-100
@@ -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: "));
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/hermes_encoder.h>
|
||||
#include <hbc/opcodes.h>
|
||||
#include <hbc/bytecode.h>
|
||||
|
||||
@@ -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: ["));
|
||||
|
||||
@@ -1,8 +1,20 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/parser.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Unpack the function-header flags byte into the header struct. */
|
||||
static void unpack_function_flags(FunctionHeader *h, u8 flags) {
|
||||
h->prohibitInvoke = flags & 0x3u;
|
||||
h->strictMode = (flags >> 2) & 0x1u;
|
||||
h->hasExceptionHandler = (flags >> 3) & 0x1u;
|
||||
h->hasDebugInfo = (flags >> 4) & 0x1u;
|
||||
h->overflowed = (flags >> 5) & 0x1u;
|
||||
h->unused = (flags >> 6) & 0x3u;
|
||||
}
|
||||
|
||||
/* Initialize HBC reader */
|
||||
Result _hbc_reader_init(HBCReader *reader) {
|
||||
R_RETURN_VAL_IF_FAIL (reader,
|
||||
@@ -142,8 +154,10 @@ Result _hbc_reader_read_file(HBCReader *reader, const char *filename) {
|
||||
}
|
||||
}
|
||||
|
||||
#if 0
|
||||
fprintf (stderr, "Warning: File does not start with Hermes bytecode magic number (found 0x%016llx, expected 0x%016llx).\n", (unsigned long long)magic, (unsigned long long)HEADER_MAGIC);
|
||||
fprintf (stderr, "This file may not be a Hermes bytecode file or might be corrupted.\n");
|
||||
#endif
|
||||
}
|
||||
|
||||
return SUCCESS_RESULT ();
|
||||
@@ -173,6 +187,45 @@ static Result align_over_padding(BufferReader *reader, size_t padding_amount) {
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
static Result read_exception_handlers(HBCReader *reader, u32 function_id, u32 info_offset) {
|
||||
RETURN_IF_ERROR (_hbc_buffer_reader_seek (&reader->file_buffer, info_offset));
|
||||
RETURN_IF_ERROR (align_over_padding (&reader->file_buffer, 4));
|
||||
|
||||
u32 count;
|
||||
RETURN_IF_ERROR (_hbc_buffer_reader_read_u32 (&reader->file_buffer, &count));
|
||||
size_t remaining = reader->file_buffer.size - reader->file_buffer.position;
|
||||
if (count > remaining / (3 * sizeof (u32))) {
|
||||
return ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, "Exception handler table exceeds file size");
|
||||
}
|
||||
|
||||
ExceptionHandlerInfo *handlers = NULL;
|
||||
if (count) {
|
||||
handlers = (ExceptionHandlerInfo *)malloc (count * sizeof (ExceptionHandlerInfo));
|
||||
if (!handlers) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "Failed to allocate exception handlers");
|
||||
}
|
||||
}
|
||||
for (u32 j = 0; j < count; j++) {
|
||||
Result res = _hbc_buffer_reader_read_u32 (&reader->file_buffer, &handlers[j].start);
|
||||
if (res.code == RESULT_SUCCESS) {
|
||||
res = _hbc_buffer_reader_read_u32 (&reader->file_buffer, &handlers[j].end);
|
||||
}
|
||||
if (res.code == RESULT_SUCCESS) {
|
||||
res = _hbc_buffer_reader_read_u32 (&reader->file_buffer, &handlers[j].target);
|
||||
}
|
||||
if (res.code != RESULT_SUCCESS) {
|
||||
free (handlers);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
ExceptionHandlerList *list = &reader->function_id_to_exc_handlers[function_id];
|
||||
free (list->handlers);
|
||||
list->handlers = handlers;
|
||||
list->count = count;
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
/* Read and validate the header */
|
||||
Result _hbc_reader_read_header(HBCReader *reader) {
|
||||
if (!reader) {
|
||||
@@ -352,15 +405,7 @@ static Result hbc_reader_read_functions_modern(HBCReader *reader) {
|
||||
header->highestReadCacheIndex = (raw3 >> 8) & 0xFFu;
|
||||
header->highestWriteCacheIndex = (reader->header.version >= 99)? ((raw3 >> 16) & 0x7Fu): ((raw3 >> 16) & 0xFFu);
|
||||
header->environmentSize = (reader->header.version >= 99)? ((raw3 >> 23) & 0x1u): 0;
|
||||
{
|
||||
u8 flags = (raw3 >> 24) & 0xFFu;
|
||||
header->prohibitInvoke = flags & 0x3u;
|
||||
header->strictMode = (flags >> 2) & 0x1u;
|
||||
header->hasExceptionHandler = (flags >> 3) & 0x1u;
|
||||
header->hasDebugInfo = (flags >> 4) & 0x1u;
|
||||
header->overflowed = (flags >> 5) & 0x1u;
|
||||
header->unused = (flags >> 6) & 0x3u;
|
||||
}
|
||||
unpack_function_flags (header, (raw3 >> 24) & 0xFFu);
|
||||
header->infoOffset = 0;
|
||||
|
||||
if (header->overflowed) {
|
||||
@@ -382,12 +427,7 @@ static Result hbc_reader_read_functions_modern(HBCReader *reader) {
|
||||
{
|
||||
u8 flags = 0;
|
||||
RETURN_IF_ERROR (_hbc_buffer_reader_read_u8 (&reader->file_buffer, &flags));
|
||||
header->prohibitInvoke = flags & 0x3u;
|
||||
header->strictMode = (flags >> 2) & 0x1u;
|
||||
header->hasExceptionHandler = (flags >> 3) & 0x1u;
|
||||
header->hasDebugInfo = (flags >> 4) & 0x1u;
|
||||
header->overflowed = (flags >> 5) & 0x1u;
|
||||
header->unused = (flags >> 6) & 0x3u;
|
||||
unpack_function_flags (header, flags);
|
||||
}
|
||||
} else {
|
||||
RETURN_IF_ERROR (_hbc_buffer_reader_read_u32 (&reader->file_buffer, &header->bytecodeSizeInBytes));
|
||||
@@ -398,12 +438,7 @@ static Result hbc_reader_read_functions_modern(HBCReader *reader) {
|
||||
{
|
||||
u8 flags = 0;
|
||||
RETURN_IF_ERROR (_hbc_buffer_reader_read_u8 (&reader->file_buffer, &flags));
|
||||
header->prohibitInvoke = flags & 0x3u;
|
||||
header->strictMode = (flags >> 2) & 0x1u;
|
||||
header->hasExceptionHandler = (flags >> 3) & 0x1u;
|
||||
header->hasDebugInfo = (flags >> 4) & 0x1u;
|
||||
header->overflowed = (flags >> 5) & 0x1u;
|
||||
header->unused = (flags >> 6) & 0x3u;
|
||||
unpack_function_flags (header, flags);
|
||||
}
|
||||
}
|
||||
RETURN_IF_ERROR (_hbc_buffer_reader_seek (&reader->file_buffer, header_pos + 12));
|
||||
@@ -575,13 +610,6 @@ Result _hbc_reader_read_functions(HBCReader *reader) {
|
||||
small_header.highestReadCacheIndex = (u8) ((raw_data[3] >> 8) & 0xFF);
|
||||
small_header.highestWriteCacheIndex = (u8) ((raw_data[3] >> 16) & 0xFF);
|
||||
|
||||
small_header.prohibitInvoke = (raw_data[3] >> 24) & 0x3;
|
||||
small_header.strictMode = (raw_data[3] >> 26) & 0x1;
|
||||
small_header.hasExceptionHandler = (raw_data[3] >> 27) & 0x1;
|
||||
small_header.hasDebugInfo = (raw_data[3] >> 28) & 0x1;
|
||||
small_header.overflowed = (raw_data[3] >> 29) & 0x1;
|
||||
small_header.unused = (raw_data[3] >> 30) & 0x3;
|
||||
|
||||
/* Store the function header */
|
||||
FunctionHeader *header = &reader->function_headers[i];
|
||||
header->offset = small_header.offset;
|
||||
@@ -593,18 +621,13 @@ Result _hbc_reader_read_functions(HBCReader *reader) {
|
||||
header->environmentSize = small_header.environmentSize;
|
||||
header->highestReadCacheIndex = small_header.highestReadCacheIndex;
|
||||
header->highestWriteCacheIndex = small_header.highestWriteCacheIndex;
|
||||
header->prohibitInvoke = small_header.prohibitInvoke;
|
||||
header->strictMode = small_header.strictMode;
|
||||
header->hasExceptionHandler = small_header.hasExceptionHandler;
|
||||
header->hasDebugInfo = small_header.hasDebugInfo;
|
||||
header->overflowed = small_header.overflowed;
|
||||
header->unused = small_header.unused;
|
||||
unpack_function_flags (header, (u8) (raw_data[3] >> 24));
|
||||
|
||||
/* Save current position */
|
||||
size_t current_pos = reader->file_buffer.position;
|
||||
|
||||
/* Handle overflowed header */
|
||||
if (small_header.overflowed) {
|
||||
if (header->overflowed) {
|
||||
/* Calculate the absolute offset of the large header */
|
||||
u32 large_header_offset = (small_header.infoOffset << 16) | small_header.offset;
|
||||
|
||||
@@ -631,13 +654,6 @@ Result _hbc_reader_read_functions(HBCReader *reader) {
|
||||
u8 flags;
|
||||
RETURN_IF_ERROR (_hbc_buffer_reader_read_u8 (&reader->file_buffer, &flags));
|
||||
|
||||
large_header.prohibitInvoke = flags & 0x3;
|
||||
large_header.strictMode = (flags >> 2) & 0x1;
|
||||
large_header.hasExceptionHandler = (flags >> 3) & 0x1;
|
||||
large_header.hasDebugInfo = (flags >> 4) & 0x1;
|
||||
large_header.overflowed = (flags >> 5) & 0x1;
|
||||
large_header.unused = (flags >> 6) & 0x3;
|
||||
|
||||
/* Copy to the combined header */
|
||||
header->offset = large_header.offset;
|
||||
header->paramCount = large_header.paramCount;
|
||||
@@ -648,12 +664,7 @@ Result _hbc_reader_read_functions(HBCReader *reader) {
|
||||
header->environmentSize = (u8)large_header.environmentSize; /* Cast from u32 to u8 */
|
||||
header->highestReadCacheIndex = large_header.highestReadCacheIndex;
|
||||
header->highestWriteCacheIndex = large_header.highestWriteCacheIndex;
|
||||
header->prohibitInvoke = large_header.prohibitInvoke;
|
||||
header->strictMode = large_header.strictMode;
|
||||
header->hasExceptionHandler = large_header.hasExceptionHandler;
|
||||
header->hasDebugInfo = large_header.hasDebugInfo;
|
||||
header->overflowed = large_header.overflowed;
|
||||
header->unused = large_header.unused;
|
||||
unpack_function_flags (header, flags);
|
||||
}
|
||||
|
||||
/* Read the function bytecode */
|
||||
@@ -674,34 +685,8 @@ Result _hbc_reader_read_functions(HBCReader *reader) {
|
||||
/* Store the bytecode pointer */
|
||||
header->bytecode = bytecode;
|
||||
|
||||
/* Read exception handlers if present */
|
||||
if (header->hasExceptionHandler) {
|
||||
/* Seek to the exception handler info */
|
||||
RETURN_IF_ERROR (_hbc_buffer_reader_seek (&reader->file_buffer, header->infoOffset));
|
||||
|
||||
/* Align buffer */
|
||||
RETURN_IF_ERROR (align_over_padding (&reader->file_buffer, 4));
|
||||
|
||||
/* Read the number of exception handlers */
|
||||
u32 exc_handler_count;
|
||||
RETURN_IF_ERROR (_hbc_buffer_reader_read_u32 (&reader->file_buffer, &exc_handler_count));
|
||||
|
||||
/* Allocate exception handlers */
|
||||
ExceptionHandlerInfo *handlers = (ExceptionHandlerInfo *)malloc (exc_handler_count * sizeof (ExceptionHandlerInfo));
|
||||
if (!handlers) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "Failed to allocate exception handlers");
|
||||
}
|
||||
|
||||
/* Read each exception handler */
|
||||
for (u32 j = 0; j < exc_handler_count; j++) {
|
||||
RETURN_IF_ERROR (_hbc_buffer_reader_read_u32 (&reader->file_buffer, &handlers[j].start));
|
||||
RETURN_IF_ERROR (_hbc_buffer_reader_read_u32 (&reader->file_buffer, &handlers[j].end));
|
||||
RETURN_IF_ERROR (_hbc_buffer_reader_read_u32 (&reader->file_buffer, &handlers[j].target));
|
||||
}
|
||||
|
||||
/* Store the exception handlers */
|
||||
reader->function_id_to_exc_handlers[i].handlers = handlers;
|
||||
reader->function_id_to_exc_handlers[i].count = exc_handler_count;
|
||||
RETURN_IF_ERROR (read_exception_handlers (reader, i, header->infoOffset));
|
||||
}
|
||||
|
||||
/* Read debug information if present */
|
||||
@@ -1860,12 +1845,6 @@ Result _hbc_reader_read_functions_robust(HBCReader *reader) {
|
||||
const u8 small_environmentSize = (u8) (raw_data[3] & 0xFF);
|
||||
const u8 small_highestReadCacheIndex = (u8) ((raw_data[3] >> 8) & 0xFF);
|
||||
const u8 small_highestWriteCacheIndex = (u8) ((raw_data[3] >> 16) & 0xFF);
|
||||
const u8 small_prohibitInvoke = (raw_data[3] >> 24) & 0x3;
|
||||
const u8 small_strictMode = (raw_data[3] >> 26) & 0x1;
|
||||
const u8 small_hasExceptionHandler = (raw_data[3] >> 27) & 0x1;
|
||||
const u8 small_hasDebugInfo = (raw_data[3] >> 28) & 0x1;
|
||||
const u8 small_overflowed = (raw_data[3] >> 29) & 0x1;
|
||||
const u8 small_unused = (raw_data[3] >> 30) & 0x3;
|
||||
|
||||
/* Initialize header with small header values */
|
||||
header->offset = small_offset;
|
||||
@@ -1877,15 +1856,10 @@ Result _hbc_reader_read_functions_robust(HBCReader *reader) {
|
||||
header->environmentSize = small_environmentSize;
|
||||
header->highestReadCacheIndex = small_highestReadCacheIndex;
|
||||
header->highestWriteCacheIndex = small_highestWriteCacheIndex;
|
||||
header->prohibitInvoke = small_prohibitInvoke;
|
||||
header->strictMode = small_strictMode;
|
||||
header->hasExceptionHandler = small_hasExceptionHandler;
|
||||
header->hasDebugInfo = small_hasDebugInfo;
|
||||
header->overflowed = small_overflowed;
|
||||
header->unused = small_unused;
|
||||
unpack_function_flags (header, (u8) (raw_data[3] >> 24));
|
||||
|
||||
/* Try to read large header if needed and override values */
|
||||
if (small_overflowed || small_bytecodeSizeInBytes == 0) {
|
||||
if (header->overflowed || small_bytecodeSizeInBytes == 0) {
|
||||
const u32 large_header_offset = (small_infoOffset << 16) | small_offset;
|
||||
const Result sr = _hbc_buffer_reader_seek (&reader->file_buffer, large_header_offset);
|
||||
|
||||
@@ -1914,12 +1888,7 @@ Result _hbc_reader_read_functions_robust(HBCReader *reader) {
|
||||
header->environmentSize = (u8)large_header.environmentSize;
|
||||
header->highestReadCacheIndex = large_header.highestReadCacheIndex;
|
||||
header->highestWriteCacheIndex = large_header.highestWriteCacheIndex;
|
||||
header->prohibitInvoke = flags & 0x3;
|
||||
header->strictMode = (flags >> 2) & 0x1;
|
||||
header->hasExceptionHandler = (flags >> 3) & 0x1;
|
||||
header->hasDebugInfo = (flags >> 4) & 0x1;
|
||||
header->overflowed = (flags >> 5) & 0x1;
|
||||
header->unused = (flags >> 6) & 0x3;
|
||||
unpack_function_flags (header, flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1951,6 +1920,13 @@ Result _hbc_reader_read_functions_robust(HBCReader *reader) {
|
||||
/* For robustness, we don't load bytecode here */
|
||||
header->bytecode = NULL;
|
||||
|
||||
if (header->hasExceptionHandler) {
|
||||
Result eh_res = read_exception_handlers (reader, i, header->infoOffset);
|
||||
if (eh_res.code != RESULT_SUCCESS) {
|
||||
hbc_debug_printf ("Warning: Skipping exception handlers for function %u: %s\n", i, eh_res.error_message);
|
||||
}
|
||||
}
|
||||
|
||||
successful_functions++;
|
||||
|
||||
/* Ensure position is at next small header (16 bytes per entry) */
|
||||
@@ -1989,7 +1965,9 @@ Result _hbc_reader_read_whole_file(HBCReader *reader, const char *filename) {
|
||||
/* Read header */
|
||||
result = _hbc_reader_read_header (reader);
|
||||
if (result.code != RESULT_SUCCESS) {
|
||||
#if 0
|
||||
fprintf (stderr, "Error reading header: %s\n", result.error_message);
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -2070,7 +2048,7 @@ Result _hbc_reader_read_whole_file(HBCReader *reader, const char *filename) {
|
||||
}
|
||||
|
||||
/* Read only the DebugInfoHeader eagerly (cheap). The body is parsed lazily
|
||||
* via _hbc_reader_read_debug_info() on first API use. */
|
||||
* via _hbc_reader_read_debug_info () on first API use. */
|
||||
result = _hbc_reader_read_debug_info_header (reader);
|
||||
if (result.code != RESULT_SUCCESS) {
|
||||
fprintf (stderr, "Error reading debug info header: %s\n", result.error_message);
|
||||
|
||||
+22
-61
@@ -1,7 +1,18 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/hbc.h>
|
||||
#include <hbc/parser.h>
|
||||
#include <ctype.h>
|
||||
|
||||
static void sanitize_identifier(const char *src, char *dst, size_t dst_size) {
|
||||
size_t out = 0;
|
||||
for (size_t j = 0; src[j] && out + 1 < dst_size; j++) {
|
||||
char c = src[j];
|
||||
dst[out++] = (isalnum ((unsigned char)c) || c == '_')? c: '_';
|
||||
}
|
||||
dst[out] = '\0';
|
||||
}
|
||||
|
||||
/* Generate r2 script with function flags - robust version inspired by the Python implementation */
|
||||
Result _hbc_generate_r2_script(const char *input_file, const char *output_file) {
|
||||
if (!input_file) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Input file is NULL");
|
||||
@@ -209,21 +220,7 @@ Result _hbc_generate_r2_script(const char *input_file, const char *output_file)
|
||||
|
||||
/* Generate sanitized name for r2 */
|
||||
char sanitized_name[256] = { 0 };
|
||||
size_t name_len = strlen (function_name);
|
||||
size_t sanitized_idx = 0;
|
||||
|
||||
for (size_t j = 0; j < name_len && sanitized_idx < sizeof (sanitized_name) - 1; j++) {
|
||||
char c = function_name[j];
|
||||
if ((c >= 'a' && c <= 'z') ||
|
||||
(c >= 'A' && c <= 'Z') ||
|
||||
(c >= '0' && c <= '9') ||
|
||||
c == '_') {
|
||||
sanitized_name[sanitized_idx++] = c;
|
||||
} else {
|
||||
sanitized_name[sanitized_idx++] = '_';
|
||||
}
|
||||
}
|
||||
sanitized_name[sanitized_idx] = '\0';
|
||||
sanitize_identifier (function_name, sanitized_name, sizeof (sanitized_name));
|
||||
|
||||
/* If sanitized name is empty, use a default */
|
||||
if (sanitized_name[0] == '\0') {
|
||||
@@ -375,56 +372,20 @@ Result _hbc_generate_r2_script(const char *input_file, const char *output_file)
|
||||
display_length = 32;
|
||||
}
|
||||
|
||||
/* Generate a string value for the flag name */
|
||||
/* Generate a string value for the flag name: decode each char (1 or
|
||||
* 2 bytes, LE) and keep only ASCII-printable bytes, else '_'. */
|
||||
char str_value[64] = { 0 };
|
||||
if (isUTF16) {
|
||||
/* UTF-16 string - simplified handling */
|
||||
u32 chars_added = 0;
|
||||
for (u32 j = 0; j < display_length && chars_added < sizeof (str_value) - 1; j++) {
|
||||
/* Read 16-bit character */
|
||||
u16 c = (u16) (string_storage[offset + (j * 2)] | (string_storage[offset + (j * 2) + 1] << 8));
|
||||
|
||||
/* Only add ASCII-printable characters to name */
|
||||
if (c >= 32 && c < 127) {
|
||||
str_value[chars_added++] = (char)c;
|
||||
} else {
|
||||
str_value[chars_added++] = '_';
|
||||
}
|
||||
}
|
||||
str_value[chars_added] = '\0';
|
||||
} else {
|
||||
/* ASCII string */
|
||||
u32 chars_added = 0;
|
||||
for (u32 j = 0; j < display_length && chars_added < sizeof (str_value) - 1; j++) {
|
||||
char c = (char)string_storage[offset + j];
|
||||
|
||||
/* Only add printable characters to name */
|
||||
if (c >= 32 && c < 127) {
|
||||
str_value[chars_added++] = c;
|
||||
} else {
|
||||
str_value[chars_added++] = '_';
|
||||
}
|
||||
}
|
||||
str_value[chars_added] = '\0';
|
||||
u32 chars_added = 0;
|
||||
for (u32 j = 0; j < display_length && chars_added < sizeof (str_value) - 1; j++) {
|
||||
const u8 *p = string_storage + offset + (j * bpc);
|
||||
u32 c = isUTF16? ((u32)p[0] | ((u32)p[1] << 8)): p[0];
|
||||
str_value[chars_added++] = (c >= 32 && c < 127)? (char)c: '_';
|
||||
}
|
||||
str_value[chars_added] = '\0';
|
||||
|
||||
/* Generate sanitized name for r2 - only include alphanumeric and underscores */
|
||||
char sanitized_name[64] = { 0 };
|
||||
size_t name_len = strlen (str_value);
|
||||
size_t sanitized_idx = 0;
|
||||
|
||||
for (size_t j = 0; j < name_len && sanitized_idx < sizeof (sanitized_name) - 1; j++) {
|
||||
char c = str_value[j];
|
||||
if ((c >= 'a' && c <= 'z') ||
|
||||
(c >= 'A' && c <= 'Z') ||
|
||||
(c >= '0' && c <= '9') ||
|
||||
c == '_') {
|
||||
sanitized_name[sanitized_idx++] = c;
|
||||
} else {
|
||||
sanitized_name[sanitized_idx++] = '_';
|
||||
}
|
||||
}
|
||||
sanitized_name[sanitized_idx] = '\0';
|
||||
sanitize_identifier (str_value, sanitized_name, sizeof (sanitized_name));
|
||||
|
||||
/* Add index to name to ensure uniqueness */
|
||||
char unique_name[96];
|
||||
|
||||
+37
-117
@@ -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;
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ BIN_SRC = bin_hbc.c
|
||||
ASM_SRC = asm_hbc.c
|
||||
CORE_SRC = core_hbc.c
|
||||
CORE_ONE_SRC = core_hbc_one.c
|
||||
HBC_HEADERS = $(shell find ../../include -name '*.h') utils.inc.c
|
||||
HBC_HEADERS = $(shell find ../../include -name '*.h') utils.inc.c sbom.inc.c
|
||||
|
||||
# Object files
|
||||
ARCH_OBJ = arch_hbc.o
|
||||
|
||||
+47
-27
@@ -1,4 +1,4 @@
|
||||
/* radare2 - LGPL - Copyright 2025-2026 - pancake */
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <r_anal.h>
|
||||
#include <hbc/hbc.h>
|
||||
@@ -7,14 +7,14 @@
|
||||
#include <hbc/bytecode.h>
|
||||
#include <hbc/parser.h>
|
||||
|
||||
#define MAX_OP_SIZE 16
|
||||
#define MAX_OP_SIZE 32
|
||||
#define HBC_VADDR_BASE 0x10000000
|
||||
#ifndef R_ARCH_SYNTAX_CAMEL
|
||||
#define R_ARCH_SYNTAX_CAMEL 6
|
||||
#endif
|
||||
|
||||
typedef struct {
|
||||
ut32 bytecode_version; /* cached from RBinInfo->cpu if available */
|
||||
ut32 bytecode_version; /* cached effective HBC bytecode version */
|
||||
HBC *hbc; /* Hermes state for direct access */
|
||||
u32 string_count;
|
||||
const void *small_string_table;
|
||||
@@ -22,25 +22,41 @@ typedef struct {
|
||||
u64 string_storage_offset;
|
||||
} HermesArchSession;
|
||||
|
||||
static ut32 parse_cpu_version(const char *cpu) {
|
||||
if (!cpu || !*cpu) {
|
||||
return 0;
|
||||
}
|
||||
const char *p = cpu;
|
||||
if (p[0] != 'v') {
|
||||
return 0;
|
||||
}
|
||||
unsigned long v = strtoul (p + 1, NULL, 10);
|
||||
return v > 0 && v <= UT32_MAX? (ut32)v: 0;
|
||||
}
|
||||
|
||||
static ut32 detect_version_from_bin(RArchSession *s) {
|
||||
if (!s || !s->arch || !s->arch->binb.bin) {
|
||||
return 96; /* sane default */
|
||||
return 0;
|
||||
}
|
||||
RBin *bin = s->arch->binb.bin;
|
||||
RBinInfo *bi = r_bin_get_info (bin);
|
||||
if (bi && bi->cpu && *bi->cpu) {
|
||||
const char *p = bi->cpu;
|
||||
/* Accept optional leading 'v' or 'V' (e.g., "v76") */
|
||||
if (p[0] == 'v' || p[0] == 'V') {
|
||||
p++;
|
||||
}
|
||||
/* cpu holds the version string set by bin plugin */
|
||||
ut32 v = (ut32)strtoul (p, NULL, 10);
|
||||
ut32 v = parse_cpu_version (bi->cpu);
|
||||
if (v > 0) {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
return 96;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ut32 detect_version(RArchSession *s) {
|
||||
ut32 v = parse_cpu_version (s && s->config? s->config->cpu: NULL);
|
||||
if (v > 0) {
|
||||
return v;
|
||||
}
|
||||
v = detect_version_from_bin (s);
|
||||
return v > 0? v: 96; /* sane default */
|
||||
}
|
||||
|
||||
#include "esil.inc.c"
|
||||
@@ -171,7 +187,12 @@ static void parse_operands_and_set_ptr(RAnalOp *op, const ut8 *bytes, ut32 size,
|
||||
HBCStringMeta meta;
|
||||
Result meta_result = hbc_get_string_meta (hs->hbc, string_id, &meta);
|
||||
if (meta_result.code == RESULT_SUCCESS) {
|
||||
op->ptr = (st64)(HBC_VADDR_BASE + meta.offset);
|
||||
op->ptr = (st64) (HBC_VADDR_BASE + meta.offset);
|
||||
/* Expose the string byte length (size of the pointed
|
||||
* contents) so the disassembler caps r2's string read and
|
||||
* picks the right flag among overlapping, non-null-terminated
|
||||
* Hermes strings that share this storage offset. */
|
||||
op->ptrsize = meta.isUTF16? (int) (meta.length * 2): (int)meta.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -187,7 +208,7 @@ static void parse_operands_and_set_ptr(RAnalOp *op, const ut8 *bytes, ut32 size,
|
||||
offset = fi.offset;
|
||||
(void)fi.size;
|
||||
(void)fi.param_count;
|
||||
op->ptr = (st64)(HBC_VADDR_BASE + offset);
|
||||
op->ptr = (st64) (HBC_VADDR_BASE + offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -207,13 +228,12 @@ static bool decode(RArchSession *s, RAnalOp *op, RArchDecodeMask mask) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!hs->bytecode_version) {
|
||||
hs->bytecode_version = detect_version_from_bin (s);
|
||||
}
|
||||
hs->bytecode_version = detect_version (s);
|
||||
|
||||
/* Load string tables if not already loaded */
|
||||
if (!hs->hbc) {
|
||||
load_string_tables (hs, s);
|
||||
hs->bytecode_version = detect_version (s);
|
||||
}
|
||||
|
||||
/* Build decode context */
|
||||
@@ -235,9 +255,9 @@ static bool decode(RArchSession *s, RAnalOp *op, RArchDecodeMask mask) {
|
||||
.hbc = hs->hbc,
|
||||
};
|
||||
/* Inlining of buffer-literal contents into the disasm line is opt-in —
|
||||
* it makes arch.decode O(n) in literal size and breaks r2's O(1) per-op
|
||||
* disasm assumption. Toggle via `e hbc.inline_buffer_literals=true`
|
||||
* (core plugin updates a process-wide flag). */
|
||||
* it makes arch.decode O (n) in literal size and breaks r2's O(1) per-op
|
||||
* disasm assumption. Toggle via `e r2hermes.inline_buffer_literals=true`
|
||||
*(core plugin updates a process-wide flag). */
|
||||
if ((mask & R_ARCH_OP_MASK_DISASM) && hbc_get_inline_literals ()) {
|
||||
ctx.build_objects = true;
|
||||
}
|
||||
@@ -533,7 +553,10 @@ static int archinfo(RArchSession *s, ut32 q) {
|
||||
|
||||
static char *mnemonics(RArchSession *s, int id, bool json) {
|
||||
HermesArchSession *hs = s? (HermesArchSession *)s->data: NULL;
|
||||
ut32 version = (hs && hs->bytecode_version)? hs->bytecode_version: 96;
|
||||
ut32 version = detect_version (s);
|
||||
if (hs) {
|
||||
hs->bytecode_version = version;
|
||||
}
|
||||
HBCISA isa = hbc_isa_getv (version);
|
||||
if (!isa.instructions || isa.count == 0) {
|
||||
return NULL;
|
||||
@@ -571,12 +594,7 @@ static bool encode(RArchSession *s, RAnalOp *op, RArchEncodeMask mask) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!hs->bytecode_version) {
|
||||
hs->bytecode_version = detect_version_from_bin (s);
|
||||
if (!hs->bytecode_version) {
|
||||
hs->bytecode_version = 96;
|
||||
}
|
||||
}
|
||||
hs->bytecode_version = detect_version (s);
|
||||
|
||||
const char *asm_line = op->mnemonic;
|
||||
|
||||
@@ -667,7 +685,9 @@ const RArchPlugin r_arch_plugin_r2hermes = {
|
||||
},
|
||||
.arch = "hbc",
|
||||
.bits = R_SYS_BITS_PACK1 (64),
|
||||
.cpus = "v76,v90,v91,v92,v93,v94,v95,v96,v97,v98,v99",
|
||||
.cpus = "v51,v58,v59,v61,v62,v68,v69,v70,v72,v73,v76,"
|
||||
"v80,v81,v82,v83,v84,v85,v86,v87,v88,v89,"
|
||||
"v90,v91,v92,v93,v94,v95,v96,v97,v98,v99",
|
||||
.decode = &decode,
|
||||
.encode = &encode,
|
||||
.regs = regs,
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
/* radare2 - LGPL - Copyright 2025-2026 - pancake */
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <r_asm.h>
|
||||
|
||||
@@ -219,7 +219,7 @@ const RAsmPlugin r_asm_plugin_r2hermes = {
|
||||
.name = "hbc",
|
||||
.desc = "Hermes bytecode pseudo syntax",
|
||||
.author = "pancake",
|
||||
.license = "LGPL-3.0-only",
|
||||
.license = "BSD-3-Clause",
|
||||
},
|
||||
.parse = parse,
|
||||
};
|
||||
|
||||
+401
-18
@@ -1,4 +1,4 @@
|
||||
/* radare2 - LGPL - Copyright 2025-2026 - pancake */
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <r_bin.h>
|
||||
#include <r_bin_dwarf.h>
|
||||
@@ -142,7 +142,7 @@ static void fill_info(RBinInfo *ret, const char *file_path, bool has_version, ut
|
||||
ret->bits = 32;
|
||||
ret->type = strdup ("Hermes bytecode");
|
||||
ret->machine = strdup ("Hermes VM");
|
||||
ret->cpu = has_version? r_str_newf ("%u", version): strdup ("unknown");
|
||||
ret->cpu = has_version? r_str_newf ("v%u", version): strdup ("unknown");
|
||||
ret->has_va = true;
|
||||
ret->dbg_info = has_source_lines? R_BIN_DBG_LINENUMS: R_BIN_DBG_STRIPPED;
|
||||
}
|
||||
@@ -220,15 +220,9 @@ static RList *sections(RBinFile *bf) {
|
||||
* literals live, and lets users `s` / `pd @` straight into them. */
|
||||
HBC *hbc = get_hbc (bf);
|
||||
if (hbc) {
|
||||
add_pool_section (sections, "slp.arrays",
|
||||
hbc_get_pool_paddr (hbc, HBC_LIT_ARRAY),
|
||||
hbc_get_pool_size (hbc, HBC_LIT_ARRAY));
|
||||
add_pool_section (sections, "slp.object_keys",
|
||||
hbc_get_pool_paddr (hbc, HBC_LIT_OBJECT),
|
||||
hbc_get_pool_size (hbc, HBC_LIT_OBJECT));
|
||||
add_pool_section (sections, "slp.object_values",
|
||||
hbc_get_object_values_paddr (hbc),
|
||||
hbc_get_object_values_size (hbc));
|
||||
add_pool_section (sections, "slp.arrays", hbc_get_pool_paddr (hbc, HBC_LIT_ARRAY), hbc_get_pool_size (hbc, HBC_LIT_ARRAY));
|
||||
add_pool_section (sections, "slp.object_keys", hbc_get_pool_paddr (hbc, HBC_LIT_OBJECT), hbc_get_pool_size (hbc, HBC_LIT_OBJECT));
|
||||
add_pool_section (sections, "slp.object_values", hbc_get_object_values_paddr (hbc), hbc_get_object_values_size (hbc));
|
||||
}
|
||||
|
||||
return sections;
|
||||
@@ -337,15 +331,14 @@ static RList *symbols(RBinFile *bf) {
|
||||
|
||||
/* SLP pool groups as symbols — one per group discovered by the
|
||||
* pool-side linear scan. Lets users see `lit.arr.* / lit.obj.*` in `is`
|
||||
* without needing to run pd:hLs. The scan is linear in pool size and
|
||||
* without needing to run r2hermes-Lp. The scan is linear in pool size and
|
||||
* runs at RBin load time. */
|
||||
for (int k = 0; k < 2; k++) {
|
||||
HBCLiteralKind kind = k? HBC_LIT_OBJECT: HBC_LIT_ARRAY;
|
||||
const char *prefix = k? "lit.obj.": "lit.arr.";
|
||||
HBCPoolGroup *groups = NULL;
|
||||
u32 n = 0;
|
||||
if (hbc_literals_scan_pool (hbc, kind, &groups, &n).code
|
||||
!= RESULT_SUCCESS || !groups) {
|
||||
if (hbc_literals_scan_pool (hbc, kind, &groups, &n).code != RESULT_SUCCESS || !groups) {
|
||||
continue;
|
||||
}
|
||||
for (u32 i = 0; i < n; i++) {
|
||||
@@ -399,13 +392,44 @@ static RList *imports(RBinFile *bf) {
|
||||
return imports;
|
||||
}
|
||||
|
||||
static bool list_has_string(RList *list, const char *name) {
|
||||
RListIter *iter;
|
||||
const char *it;
|
||||
r_list_foreach (list, iter, it) {
|
||||
if (it && name && !strcmp (it, name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static RList *libs(RBinFile *bf) {
|
||||
RList *libs = r_list_newf (free);
|
||||
HBC *hbc = get_hbc (bf);
|
||||
if (!libs || !hbc) {
|
||||
return libs;
|
||||
}
|
||||
HBCModules modules = { 0 };
|
||||
if (hbc_list_modules (hbc, &modules).code != RESULT_SUCCESS) {
|
||||
return libs;
|
||||
}
|
||||
for (u32 i = 0; i < modules.count; i++) {
|
||||
HBCModule *m = &modules.modules[i];
|
||||
if (m->name && *m->name && !list_has_string (libs, m->name)) {
|
||||
r_list_append (libs, strdup (m->name));
|
||||
}
|
||||
}
|
||||
hbc_free_modules (&modules);
|
||||
return libs;
|
||||
}
|
||||
|
||||
static R_UNOWNED RList *lines(RBinFile *bf) {
|
||||
HBC *hbc = get_hbc (bf);
|
||||
if (!hbc) {
|
||||
return NULL;
|
||||
}
|
||||
/* Respect bin.dbginfo=false — return early so the lazy debug-info parse
|
||||
* (triggered by hbc_get_source_lines) never runs for users who opted out. */
|
||||
*(triggered by hbc_get_source_lines) never runs for users who opted out. */
|
||||
if (bf->rbin && !bf->rbin->want_dbginfo) {
|
||||
return NULL;
|
||||
}
|
||||
@@ -447,7 +471,7 @@ done:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static RList *strings(RBinFile *bf) {
|
||||
static RList *strings_list(RBinFile *bf) {
|
||||
RList *ret = r_list_newf ((RListFree)r_bin_string_free);
|
||||
HBC *hbc = get_hbc (bf);
|
||||
if (!hbc) {
|
||||
@@ -495,6 +519,355 @@ static RList *strings(RBinFile *bf) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
#if R2_ABIVERSION >= 104
|
||||
static bool sections_vec(RBinFile *bf) {
|
||||
if (!bf || !bf->bo) {
|
||||
return false;
|
||||
}
|
||||
if (!RVecRBinSection_empty (&bf->bo->sections_vec)) {
|
||||
return true;
|
||||
}
|
||||
RList *list = sections (bf);
|
||||
if (!list) {
|
||||
return false;
|
||||
}
|
||||
bool ok = true;
|
||||
RListIter *iter;
|
||||
RBinSection *section;
|
||||
r_list_foreach (list, iter, section) {
|
||||
RBinSection *dst = RVecRBinSection_emplace_back (&bf->bo->sections_vec);
|
||||
if (!dst) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
*dst = *section;
|
||||
memset (section, 0, sizeof (*section));
|
||||
}
|
||||
if (!ok) {
|
||||
RVecRBinSection_clear (&bf->bo->sections_vec);
|
||||
}
|
||||
list->free = (RListFree)r_bin_section_free;
|
||||
r_list_free (list);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static bool symbols_vec(RBinFile *bf) {
|
||||
if (!bf || !bf->bo) {
|
||||
return false;
|
||||
}
|
||||
if (!RVecRBinSymbol_empty (&bf->bo->symbols_vec)) {
|
||||
return true;
|
||||
}
|
||||
RList *list = symbols (bf);
|
||||
if (!list) {
|
||||
return false;
|
||||
}
|
||||
bool ok = true;
|
||||
RListIter *iter;
|
||||
RBinSymbol *symbol;
|
||||
r_list_foreach (list, iter, symbol) {
|
||||
RBinSymbol *dst = RVecRBinSymbol_emplace_back (&bf->bo->symbols_vec);
|
||||
if (!dst) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
*dst = *symbol;
|
||||
memset (symbol, 0, sizeof (*symbol));
|
||||
}
|
||||
if (!ok) {
|
||||
RVecRBinSymbol_clear (&bf->bo->symbols_vec);
|
||||
}
|
||||
r_list_free (list);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static bool imports_vec(RBinFile *bf) {
|
||||
if (!bf || !bf->bo) {
|
||||
return false;
|
||||
}
|
||||
if (!RVecRBinImport_empty (&bf->bo->imports_vec)) {
|
||||
return true;
|
||||
}
|
||||
RList *list = imports (bf);
|
||||
if (!list) {
|
||||
return false;
|
||||
}
|
||||
bool ok = true;
|
||||
RListIter *iter;
|
||||
RBinImport *imp;
|
||||
r_list_foreach (list, iter, imp) {
|
||||
RBinImport *dst = RVecRBinImport_emplace_back (&bf->bo->imports_vec);
|
||||
if (!dst) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
*dst = *imp;
|
||||
memset (imp, 0, sizeof (*imp));
|
||||
}
|
||||
if (!ok) {
|
||||
RVecRBinImport_clear (&bf->bo->imports_vec);
|
||||
}
|
||||
r_list_free (list);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static RVecRBinString *strings_vec(RBinFile *bf) {
|
||||
RList *list = strings_list (bf);
|
||||
if (!list) {
|
||||
return NULL;
|
||||
}
|
||||
RVecRBinString *ret = RVecRBinString_new ();
|
||||
if (!ret) {
|
||||
r_list_free (list);
|
||||
return NULL;
|
||||
}
|
||||
bool ok = true;
|
||||
RListIter *iter;
|
||||
RBinString *str;
|
||||
r_list_foreach (list, iter, str) {
|
||||
RBinString *dst = RVecRBinString_emplace_back (ret);
|
||||
if (!dst) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
*dst = *str;
|
||||
memset (str, 0, sizeof (*str));
|
||||
}
|
||||
r_list_free (list);
|
||||
if (!ok) {
|
||||
RVecRBinString_free (ret);
|
||||
return NULL;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void hbc_source_hash_hex(const HBCHeader *hh, char *dst, size_t dstsz) {
|
||||
if (!dst || dstsz < 41) {
|
||||
return;
|
||||
}
|
||||
r_hex_bin2str (hh->sourceHash, 20, dst);
|
||||
}
|
||||
|
||||
static void hbc_header_print_u32(RStrBuf *sb, ut64 off, const char *name, ut32 value) {
|
||||
r_strbuf_appendf (sb, "0x%08" PFMT64x " %-29s 0x%08x\n", off, name, value);
|
||||
}
|
||||
|
||||
static void hbc_header_print_bool(RStrBuf *sb, ut64 off, const char *name, bool value) {
|
||||
r_strbuf_appendf (sb, "0x%08" PFMT64x " %-29s %s\n", off, name, value? "true": "false");
|
||||
}
|
||||
|
||||
static char *header(RBinFile *bf, int mode) {
|
||||
HBC *hbc = get_hbc (bf);
|
||||
if (!hbc) {
|
||||
return NULL;
|
||||
}
|
||||
HBCHeader hh;
|
||||
if (hbc_get_header (hbc, &hh).code != RESULT_SUCCESS) {
|
||||
return NULL;
|
||||
}
|
||||
char hash[41] = { 0 };
|
||||
hbc_source_hash_hex (&hh, hash, sizeof (hash));
|
||||
RStrBuf *sb = r_strbuf_new ("");
|
||||
if (!sb) {
|
||||
return NULL;
|
||||
}
|
||||
if (mode == R_MODE_EQUAL) {
|
||||
r_strbuf_appendf (sb, "magic=0x%016" PFMT64x "\n", hh.magic);
|
||||
r_strbuf_appendf (sb, "version=%u\n", hh.version);
|
||||
r_strbuf_appendf (sb, "sourceHash=%s\n", hash);
|
||||
r_strbuf_appendf (sb, "fileLength=%u\n", hh.fileLength);
|
||||
r_strbuf_appendf (sb, "globalCodeIndex=%u\n", hh.globalCodeIndex);
|
||||
r_strbuf_appendf (sb, "functionCount=%u\n", hh.functionCount);
|
||||
r_strbuf_appendf (sb, "stringKindCount=%u\n", hh.stringKindCount);
|
||||
r_strbuf_appendf (sb, "identifierCount=%u\n", hh.identifierCount);
|
||||
r_strbuf_appendf (sb, "stringCount=%u\n", hh.stringCount);
|
||||
r_strbuf_appendf (sb, "overflowStringCount=%u\n", hh.overflowStringCount);
|
||||
r_strbuf_appendf (sb, "stringStorageSize=%u\n", hh.stringStorageSize);
|
||||
r_strbuf_appendf (sb, "bigIntCount=%u\n", hh.bigIntCount);
|
||||
r_strbuf_appendf (sb, "bigIntStorageSize=%u\n", hh.bigIntStorageSize);
|
||||
r_strbuf_appendf (sb, "regExpCount=%u\n", hh.regExpCount);
|
||||
r_strbuf_appendf (sb, "regExpStorageSize=%u\n", hh.regExpStorageSize);
|
||||
r_strbuf_appendf (sb, "literalValueBufferSize=%u\n", hh.literalValueBufferSize);
|
||||
r_strbuf_appendf (sb, "arrayBufferSize=%u\n", hh.arrayBufferSize);
|
||||
r_strbuf_appendf (sb, "objKeyBufferSize=%u\n", hh.objKeyBufferSize);
|
||||
r_strbuf_appendf (sb, "objValueBufferSize=%u\n", hh.objValueBufferSize);
|
||||
r_strbuf_appendf (sb, "objShapeTableCount=%u\n", hh.objShapeTableCount);
|
||||
r_strbuf_appendf (sb, "numStringSwitchImms=%u\n", hh.numStringSwitchImms);
|
||||
r_strbuf_appendf (sb, "segmentID=%u\n", hh.segmentID);
|
||||
r_strbuf_appendf (sb, "cjsModuleCount=%u\n", hh.cjsModuleCount);
|
||||
r_strbuf_appendf (sb, "functionSourceCount=%u\n", hh.functionSourceCount);
|
||||
r_strbuf_appendf (sb, "debugInfoOffset=%u\n", hh.debugInfoOffset);
|
||||
r_strbuf_appendf (sb, "staticBuiltins=%s\n", hh.staticBuiltins? "true": "false");
|
||||
r_strbuf_appendf (sb, "cjsModulesStaticallyResolved=%s\n", hh.cjsModulesStaticallyResolved? "true": "false");
|
||||
r_strbuf_appendf (sb, "hasAsync=%s\n", hh.hasAsync? "true": "false");
|
||||
return r_strbuf_drain (sb);
|
||||
}
|
||||
if (mode == R_MODE_JSON) {
|
||||
PJ *pj = pj_new ();
|
||||
if (!pj) {
|
||||
r_strbuf_free (sb);
|
||||
return NULL;
|
||||
}
|
||||
pj_o (pj);
|
||||
pj_kn (pj, "magic", hh.magic);
|
||||
pj_kn (pj, "version", hh.version);
|
||||
pj_ks (pj, "sourceHash", hash);
|
||||
pj_kn (pj, "fileLength", hh.fileLength);
|
||||
pj_kn (pj, "globalCodeIndex", hh.globalCodeIndex);
|
||||
pj_kn (pj, "functionCount", hh.functionCount);
|
||||
pj_kn (pj, "stringKindCount", hh.stringKindCount);
|
||||
pj_kn (pj, "identifierCount", hh.identifierCount);
|
||||
pj_kn (pj, "stringCount", hh.stringCount);
|
||||
pj_kn (pj, "overflowStringCount", hh.overflowStringCount);
|
||||
pj_kn (pj, "stringStorageSize", hh.stringStorageSize);
|
||||
pj_kn (pj, "bigIntCount", hh.bigIntCount);
|
||||
pj_kn (pj, "bigIntStorageSize", hh.bigIntStorageSize);
|
||||
pj_kn (pj, "regExpCount", hh.regExpCount);
|
||||
pj_kn (pj, "regExpStorageSize", hh.regExpStorageSize);
|
||||
pj_kn (pj, "literalValueBufferSize", hh.literalValueBufferSize);
|
||||
pj_kn (pj, "arrayBufferSize", hh.arrayBufferSize);
|
||||
pj_kn (pj, "objKeyBufferSize", hh.objKeyBufferSize);
|
||||
pj_kn (pj, "objValueBufferSize", hh.objValueBufferSize);
|
||||
pj_kn (pj, "objShapeTableCount", hh.objShapeTableCount);
|
||||
pj_kn (pj, "numStringSwitchImms", hh.numStringSwitchImms);
|
||||
pj_kn (pj, "segmentID", hh.segmentID);
|
||||
pj_kn (pj, "cjsModuleCount", hh.cjsModuleCount);
|
||||
pj_kn (pj, "functionSourceCount", hh.functionSourceCount);
|
||||
pj_kn (pj, "debugInfoOffset", hh.debugInfoOffset);
|
||||
pj_kb (pj, "staticBuiltins", hh.staticBuiltins);
|
||||
pj_kb (pj, "cjsModulesStaticallyResolved", hh.cjsModulesStaticallyResolved);
|
||||
pj_kb (pj, "hasAsync", hh.hasAsync);
|
||||
pj_end (pj);
|
||||
r_strbuf_free (sb);
|
||||
return pj_drain (pj);
|
||||
}
|
||||
ut64 off = 0;
|
||||
r_strbuf_append (sb, "Hermes bytecode header:\n");
|
||||
r_strbuf_appendf (sb, "0x%08" PFMT64x " %-29s 0x%016" PFMT64x "\n", off, "magic", hh.magic);
|
||||
off += 8;
|
||||
hbc_header_print_u32 (sb, off, "version", hh.version);
|
||||
off += 4;
|
||||
r_strbuf_appendf (sb, "0x%08" PFMT64x " %-29s %s\n", off, "sourceHash", hash);
|
||||
off += 20;
|
||||
hbc_header_print_u32 (sb, off, "fileLength", hh.fileLength);
|
||||
off += 4;
|
||||
hbc_header_print_u32 (sb, off, "globalCodeIndex", hh.globalCodeIndex);
|
||||
off += 4;
|
||||
hbc_header_print_u32 (sb, off, "functionCount", hh.functionCount);
|
||||
off += 4;
|
||||
hbc_header_print_u32 (sb, off, "stringKindCount", hh.stringKindCount);
|
||||
off += 4;
|
||||
hbc_header_print_u32 (sb, off, "identifierCount", hh.identifierCount);
|
||||
off += 4;
|
||||
hbc_header_print_u32 (sb, off, "stringCount", hh.stringCount);
|
||||
off += 4;
|
||||
hbc_header_print_u32 (sb, off, "overflowStringCount", hh.overflowStringCount);
|
||||
off += 4;
|
||||
hbc_header_print_u32 (sb, off, "stringStorageSize", hh.stringStorageSize);
|
||||
off += 4;
|
||||
if (hh.version >= 87) {
|
||||
hbc_header_print_u32 (sb, off, "bigIntCount", hh.bigIntCount);
|
||||
off += 4;
|
||||
hbc_header_print_u32 (sb, off, "bigIntStorageSize", hh.bigIntStorageSize);
|
||||
off += 4;
|
||||
}
|
||||
hbc_header_print_u32 (sb, off, "regExpCount", hh.regExpCount);
|
||||
off += 4;
|
||||
hbc_header_print_u32 (sb, off, "regExpStorageSize", hh.regExpStorageSize);
|
||||
off += 4;
|
||||
hbc_header_print_u32 (sb, off, "literalValueBufferSize", hh.literalValueBufferSize);
|
||||
off += 4;
|
||||
hbc_header_print_u32 (sb, off, "objKeyBufferSize", hh.objKeyBufferSize);
|
||||
off += 4;
|
||||
if (hh.version >= 97) {
|
||||
hbc_header_print_u32 (sb, off, "objShapeTableCount", hh.objShapeTableCount);
|
||||
off += 4;
|
||||
} else {
|
||||
hbc_header_print_u32 (sb, off, "objValueBufferSize", hh.objValueBufferSize);
|
||||
off += 4;
|
||||
}
|
||||
if (hh.version >= 99) {
|
||||
hbc_header_print_u32 (sb, off, "numStringSwitchImms", hh.numStringSwitchImms);
|
||||
off += 4;
|
||||
}
|
||||
hbc_header_print_u32 (sb, off, "segmentID", hh.segmentID);
|
||||
off += 4;
|
||||
hbc_header_print_u32 (sb, off, "cjsModuleCount", hh.cjsModuleCount);
|
||||
off += 4;
|
||||
if (hh.version >= 84) {
|
||||
hbc_header_print_u32 (sb, off, "functionSourceCount", hh.functionSourceCount);
|
||||
off += 4;
|
||||
}
|
||||
hbc_header_print_u32 (sb, off, "debugInfoOffset", hh.debugInfoOffset);
|
||||
off += 4;
|
||||
hbc_header_print_bool (sb, off, "staticBuiltins", hh.staticBuiltins);
|
||||
hbc_header_print_bool (sb, off, "cjsModulesStaticallyResolved", hh.cjsModulesStaticallyResolved);
|
||||
hbc_header_print_bool (sb, off, "hasAsync", hh.hasAsync);
|
||||
return r_strbuf_drain (sb);
|
||||
}
|
||||
|
||||
static void hbc_fields_append_u32(RList *ret, ut64 *off, const char *name, ut32 value) {
|
||||
r_list_append (ret, r_bin_field_new (*off, *off, value, 4, name, NULL, "x", false));
|
||||
*off += 4;
|
||||
}
|
||||
|
||||
static RList *fields(RBinFile *bf) {
|
||||
RList *ret = r_list_new ();
|
||||
HBC *hbc = get_hbc (bf);
|
||||
if (!ret || !hbc) {
|
||||
return ret;
|
||||
}
|
||||
HBCHeader hh;
|
||||
if (hbc_get_header (hbc, &hh).code != RESULT_SUCCESS) {
|
||||
return ret;
|
||||
}
|
||||
char hash[41] = { 0 };
|
||||
hbc_source_hash_hex (&hh, hash, sizeof (hash));
|
||||
ut64 off = 0;
|
||||
r_list_append (ret, r_bin_field_new (off, off, hh.magic, 8, "magic", NULL, "q", false));
|
||||
off += 8;
|
||||
hbc_fields_append_u32 (ret, &off, "version", hh.version);
|
||||
r_list_append (ret, r_bin_field_new (off, off, 0, 20, "sourceHash", hash, "s", false));
|
||||
off += 20;
|
||||
hbc_fields_append_u32 (ret, &off, "fileLength", hh.fileLength);
|
||||
hbc_fields_append_u32 (ret, &off, "globalCodeIndex", hh.globalCodeIndex);
|
||||
hbc_fields_append_u32 (ret, &off, "functionCount", hh.functionCount);
|
||||
hbc_fields_append_u32 (ret, &off, "stringKindCount", hh.stringKindCount);
|
||||
hbc_fields_append_u32 (ret, &off, "identifierCount", hh.identifierCount);
|
||||
hbc_fields_append_u32 (ret, &off, "stringCount", hh.stringCount);
|
||||
hbc_fields_append_u32 (ret, &off, "overflowStringCount", hh.overflowStringCount);
|
||||
hbc_fields_append_u32 (ret, &off, "stringStorageSize", hh.stringStorageSize);
|
||||
if (hh.version >= 87) {
|
||||
hbc_fields_append_u32 (ret, &off, "bigIntCount", hh.bigIntCount);
|
||||
hbc_fields_append_u32 (ret, &off, "bigIntStorageSize", hh.bigIntStorageSize);
|
||||
}
|
||||
hbc_fields_append_u32 (ret, &off, "regExpCount", hh.regExpCount);
|
||||
hbc_fields_append_u32 (ret, &off, "regExpStorageSize", hh.regExpStorageSize);
|
||||
hbc_fields_append_u32 (ret, &off, "literalValueBufferSize", hh.literalValueBufferSize);
|
||||
hbc_fields_append_u32 (ret, &off, "objKeyBufferSize", hh.objKeyBufferSize);
|
||||
if (hh.version >= 97) {
|
||||
hbc_fields_append_u32 (ret, &off, "objShapeTableCount", hh.objShapeTableCount);
|
||||
} else {
|
||||
hbc_fields_append_u32 (ret, &off, "objValueBufferSize", hh.objValueBufferSize);
|
||||
}
|
||||
if (hh.version >= 99) {
|
||||
hbc_fields_append_u32 (ret, &off, "numStringSwitchImms", hh.numStringSwitchImms);
|
||||
}
|
||||
hbc_fields_append_u32 (ret, &off, "segmentID", hh.segmentID);
|
||||
hbc_fields_append_u32 (ret, &off, "cjsModuleCount", hh.cjsModuleCount);
|
||||
if (hh.version >= 84) {
|
||||
hbc_fields_append_u32 (ret, &off, "functionSourceCount", hh.functionSourceCount);
|
||||
}
|
||||
hbc_fields_append_u32 (ret, &off, "debugInfoOffset", hh.debugInfoOffset);
|
||||
ut32 flags = (hh.staticBuiltins? 1: 0) | (hh.cjsModulesStaticallyResolved? 2: 0) | (hh.hasAsync? 4: 0);
|
||||
r_list_append (ret, r_bin_field_new (off, off, flags, 1, "flags", NULL, "b", false));
|
||||
r_list_append (ret, r_bin_field_new (off, off, hh.staticBuiltins? 1: 0, 1, "staticBuiltins", NULL, "b", false));
|
||||
r_list_append (ret, r_bin_field_new (off, off, hh.cjsModulesStaticallyResolved? 1: 0, 1, "cjsModulesStaticallyResolved", NULL, "b", false));
|
||||
r_list_append (ret, r_bin_field_new (off, off, hh.hasAsync? 1: 0, 1, "hasAsync", NULL, "b", false));
|
||||
return ret;
|
||||
}
|
||||
|
||||
const RBinPlugin r_bin_plugin_r2hermes = {
|
||||
.meta = {
|
||||
.name = "hbc.bin",
|
||||
@@ -507,12 +880,22 @@ const RBinPlugin r_bin_plugin_r2hermes = {
|
||||
.destroy = &destroy,
|
||||
.check = &check,
|
||||
.entries = &entries,
|
||||
#if R2_ABIVERSION >= 104
|
||||
.sections_vec = §ions_vec,
|
||||
.symbols_vec = &symbols_vec,
|
||||
.imports_vec = &imports_vec,
|
||||
.strings = &strings_vec,
|
||||
#else
|
||||
.sections = §ions,
|
||||
.baddr = &baddr,
|
||||
.symbols = &symbols,
|
||||
.imports = &imports,
|
||||
.strings = &strings_list,
|
||||
#endif
|
||||
.baddr = &baddr,
|
||||
.libs = &libs,
|
||||
.lines = &lines,
|
||||
.strings = &strings,
|
||||
.header = &header,
|
||||
.fields = &fields,
|
||||
};
|
||||
|
||||
#ifndef R2_PLUGIN_INCORE
|
||||
|
||||
+394
-133
@@ -1,10 +1,11 @@
|
||||
/* radare2 - LGPL - Copyright 2025-2026 - pancake */
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
/* r2 core plugin for Hermes bytecode decompilation */
|
||||
|
||||
#include <r_core.h>
|
||||
#include <hbc/hbc.h>
|
||||
#include <hbc/literals.h>
|
||||
#include <hbc/opcodes.h>
|
||||
#include "utils.inc.c"
|
||||
|
||||
#define HBC_VADDR_BASE 0x10000000ULL
|
||||
@@ -40,11 +41,8 @@ static ut32 parse_function_id(const char *addr_str) {
|
||||
/* Comment callback for retrieving r2 comments (CC command) at an address */
|
||||
static char *r2_comment_callback(void *context, u64 address) {
|
||||
RCore *core = (RCore *)context;
|
||||
if (core) {
|
||||
const char *comment = r_meta_get_string (core->anal, R_META_TYPE_COMMENT, address);
|
||||
return comment? strdup (comment): NULL;
|
||||
}
|
||||
return NULL;
|
||||
const char *comment = r_meta_get_string (core->anal, R_META_TYPE_COMMENT, address);
|
||||
return comment? strdup (comment): NULL;
|
||||
}
|
||||
|
||||
/* Flag callback for retrieving r2 flag/symbol names at an address */
|
||||
@@ -60,13 +58,8 @@ static char *r2_flag_callback(void *context, u64 address) {
|
||||
}
|
||||
|
||||
static const char *current_file_path(RCore *core) {
|
||||
if (core && core->bin) {
|
||||
RBinInfo *bi = r_bin_get_info (core->bin);
|
||||
if (bi) {
|
||||
return bi->file;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
RBinInfo *bi = r_bin_get_info (core->bin);
|
||||
return bi? bi->file: NULL;
|
||||
}
|
||||
|
||||
/* Helper to load the current binary as HBC using provider API */
|
||||
@@ -106,17 +99,24 @@ static Result hbc_load_current_binary(HbcContext *ctx, RCore *core) {
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
/* Find function ID at a given offset */
|
||||
static int find_function_at_offset(HbcContext *ctx, u32 offset, u32 *out_id) {
|
||||
/* Find function ID containing a given r2 vaddr.
|
||||
*
|
||||
* r2 reports vaddrs (HBC_VADDR_BASE + paddr), but HBCFunc::offset is the raw
|
||||
* bytecode paddr. Translate before comparing — otherwise the lookup never
|
||||
* matches and `pd:h` silently falls back to decompiling all functions
|
||||
*(starting at the global one), which trips the AST budget and hides the
|
||||
* function the user actually navigated to. */
|
||||
static int find_function_at_offset(HbcContext *ctx, u64 vaddr, u32 *out_id) {
|
||||
if (!out_id || !ctx->hbc) {
|
||||
return -1;
|
||||
}
|
||||
u32 paddr = (vaddr >= HBC_VADDR_BASE)? (u32) (vaddr - HBC_VADDR_BASE): (u32)vaddr;
|
||||
u32 count = hbc_function_count (ctx->hbc);
|
||||
for (u32 i = 0; i < count; i++) {
|
||||
HBCFunc fi;
|
||||
Result res = hbc_get_function_info (ctx->hbc, i, &fi);
|
||||
if (res.code == RESULT_SUCCESS) {
|
||||
if (offset >= fi.offset && offset < (fi.offset + fi.size)) {
|
||||
if (paddr >= fi.offset && paddr < (fi.offset + fi.size)) {
|
||||
*out_id = i;
|
||||
return 0;
|
||||
}
|
||||
@@ -128,23 +128,24 @@ static int find_function_at_offset(HbcContext *ctx, u32 offset, u32 *out_id) {
|
||||
/* Create decompile options from radare2 settings */
|
||||
static HBCDecompOptions make_decompile_options(RCore *core, bool show_offsets, u64 function_base) {
|
||||
HBCDecompOptions opts = {
|
||||
.pretty_literals = r_config_get_b (core->config, "hbc.pretty_literals"),
|
||||
.suppress_comments = r_config_get_b (core->config, "hbc.suppress_comments"),
|
||||
.show_offsets = show_offsets || r_config_get_b (core->config, "hbc.show_offsets"),
|
||||
.pretty_literals = r_config_get_b (core->config, "r2hermes.pretty_literals"),
|
||||
.suppress_comments = r_config_get_b (core->config, "r2hermes.suppress_comments"),
|
||||
.show_offsets = show_offsets || r_config_get_b (core->config, "r2hermes.show_offsets"),
|
||||
.function_base = function_base,
|
||||
.comment_callback = r2_comment_callback,
|
||||
.comment_context = core,
|
||||
.flag_callback = r2_flag_callback,
|
||||
.flag_context = core,
|
||||
.skip_pass1_metadata = r_config_get_b (core->config, "hbc.skip_pass1"),
|
||||
.skip_pass2_transform = r_config_get_b (core->config, "hbc.skip_pass2"),
|
||||
.skip_pass3_forin = r_config_get_b (core->config, "hbc.skip_pass3"),
|
||||
.skip_pass4_closure = r_config_get_b (core->config, "hbc.skip_pass4"),
|
||||
.force_dispatch = r_config_get_b (core->config, "hbc.force_dispatch"),
|
||||
.inline_closures = r_config_get_b (core->config, "hbc.inline_closures"),
|
||||
.inline_threshold = r_config_get_i (core->config, "hbc.inline_threshold"),
|
||||
.max_ast_statements = r_config_get_i (core->config, "hbc.max_ast"),
|
||||
.max_output_bytes = r_config_get_i (core->config, "hbc.max_bytes")
|
||||
.skip_pass1_metadata = r_config_get_b (core->config, "r2hermes.skip_pass1"),
|
||||
.skip_pass2_transform = r_config_get_b (core->config, "r2hermes.skip_pass2"),
|
||||
.skip_pass3_forin = r_config_get_b (core->config, "r2hermes.skip_pass3"),
|
||||
.skip_pass4_closure = r_config_get_b (core->config, "r2hermes.skip_pass4"),
|
||||
.force_dispatch = r_config_get_b (core->config, "r2hermes.force_dispatch"),
|
||||
.inline_closures = r_config_get_b (core->config, "r2hermes.inline_closures"),
|
||||
.inline_threshold = r_config_get_i (core->config, "r2hermes.inline_threshold"),
|
||||
.inline_max_depth = r_config_get_i (core->config, "r2hermes.inline_depth"),
|
||||
.max_ast_statements = r_config_get_i (core->config, "r2hermes.max_ast"),
|
||||
.max_output_bytes = r_config_get_i (core->config, "r2hermes.max_bytes")
|
||||
};
|
||||
return opts;
|
||||
}
|
||||
@@ -158,8 +159,7 @@ static void cmd_decompile_current_ex(HbcContext *ctx, RCore *core, bool show_off
|
||||
}
|
||||
|
||||
u32 function_id = 0;
|
||||
/* Get current offset from r2 core */
|
||||
int found = find_function_at_offset (ctx, (u32)core->addr, &function_id);
|
||||
int found = find_function_at_offset (ctx, (u64)core->addr, &function_id);
|
||||
|
||||
if (found == 0) {
|
||||
/* Found function at current offset - get its base address */
|
||||
@@ -260,6 +260,57 @@ static void cmd_list_functions(HbcContext *ctx, RCore *core) {
|
||||
}
|
||||
}
|
||||
|
||||
/* List functions containing direct eval instructions */
|
||||
static void cmd_list_eval_functions(HbcContext *ctx, RCore *core) {
|
||||
Result res = hbc_load_current_binary (ctx, core);
|
||||
if (res.code != RESULT_SUCCESS) {
|
||||
R_LOG_ERROR ("%s", safe_errmsg (res.error_message));
|
||||
return;
|
||||
}
|
||||
|
||||
HBCHeader header;
|
||||
res = hbc_get_header (ctx->hbc, &header);
|
||||
if (res.code != RESULT_SUCCESS) {
|
||||
R_LOG_ERROR ("reading header: %s", safe_errmsg (res.error_message));
|
||||
return;
|
||||
}
|
||||
HBCISA isa = hbc_isa_getv (header.version);
|
||||
if (!isa.instructions) {
|
||||
R_LOG_ERROR ("No ISA for Hermes bytecode version %u", header.version);
|
||||
return;
|
||||
}
|
||||
|
||||
const u32 count = hbc_function_count (ctx->hbc);
|
||||
for (u32 fid = 0; fid < count; fid++) {
|
||||
HBCFunc info;
|
||||
if (hbc_get_function_info (ctx->hbc, fid, &info).code != RESULT_SUCCESS) {
|
||||
continue;
|
||||
}
|
||||
const u8 *code = NULL;
|
||||
u32 size = 0;
|
||||
if (hbc_get_function_bytecode (ctx->hbc, fid, &code, &size).code != RESULT_SUCCESS || !code) {
|
||||
continue;
|
||||
}
|
||||
for (u32 pc = 0; pc < size;) {
|
||||
const u8 op = code[pc];
|
||||
const Instruction *inst = (op < isa.count)? &isa.instructions[op]: NULL;
|
||||
if (!inst || !inst->name || !inst->binary_size) {
|
||||
break;
|
||||
}
|
||||
if (hbc_canonical_opcode_from_name (inst->name) == OP_DirectEval) {
|
||||
r_cons_printf (core->cons,
|
||||
"0x%08" PFMT64x " f=%u +0x%x func=0x%08" PFMT64x " name=%s\n",
|
||||
(ut64)HBC_VADDR_BASE + info.offset + pc,
|
||||
fid,
|
||||
pc,
|
||||
(ut64)HBC_VADDR_BASE + info.offset,
|
||||
r_str_get_fail (info.name, "unknown"));
|
||||
}
|
||||
pc += inst->binary_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Show file information */
|
||||
static void cmd_file_info(HbcContext *ctx, RCore *core) {
|
||||
Result res = hbc_load_current_binary (ctx, core);
|
||||
@@ -438,10 +489,7 @@ static void cmd_source_lines(HbcContext *ctx, RCore *core, const char *arg) {
|
||||
if (filter && sl->function_id != filter_id) {
|
||||
continue;
|
||||
}
|
||||
r_cons_printf (core->cons, "0x%08x f=%u +0x%x %s:%u:%u stmt=%u\n",
|
||||
sl->address, sl->function_id, sl->function_address,
|
||||
sl->filename? sl->filename: "",
|
||||
sl->line, sl->column, sl->statement);
|
||||
r_cons_printf (core->cons, "0x%08x f=%u +0x%x %s:%u:%u stmt=%u\n", sl->address, sl->function_id, sl->function_address, sl->filename? sl->filename: "", sl->line, sl->column, sl->statement);
|
||||
}
|
||||
hbc_free_source_lines (&lines);
|
||||
}
|
||||
@@ -483,7 +531,7 @@ static void cmd_json(HbcContext *ctx, RCore *core, const char *addr_str) {
|
||||
const char *as = r_str_trim_head_ro (addr_str);
|
||||
if (*as) {
|
||||
function_id = parse_function_id (addr_str);
|
||||
} else if (find_function_at_offset (ctx, (u32)core->addr, &function_id) != 0) {
|
||||
} else if (find_function_at_offset (ctx, (u64)core->addr, &function_id) != 0) {
|
||||
emit_json_error (core, "No function at current offset");
|
||||
return;
|
||||
}
|
||||
@@ -592,7 +640,7 @@ static void cmd_json(HbcContext *ctx, RCore *core, const char *addr_str) {
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
* Literal-buffer commands (pd:hL*)
|
||||
* Literal-buffer commands (r2hermes-L*)
|
||||
*
|
||||
* Work against the lazy HBC literal cache. Registration in r2 is additive:
|
||||
* flags (lit.arr.* / lit.obj.*) and CC comments are created at the literal
|
||||
@@ -646,21 +694,111 @@ static Result ensure_hbc_loaded(HbcContext *ctx, RCore *core, HBC **out) {
|
||||
return res;
|
||||
}
|
||||
|
||||
static void cmd_lit_list(HbcContext *ctx, RCore *core, bool as_json) {
|
||||
/* Populate the literal cache on demand by scanning function code. */
|
||||
static bool ensure_lit_cache_populated(RCore *core, HBC *hbc) {
|
||||
if (hbc_literals_count (hbc) > 0) {
|
||||
return true;
|
||||
}
|
||||
Result r = hbc_literals_scan_code (hbc, NULL);
|
||||
if (r.code != RESULT_SUCCESS) {
|
||||
R_LOG_ERROR ("scan failed: %s", safe_errmsg (r.error_message));
|
||||
return false;
|
||||
}
|
||||
register_all_artifacts (core, hbc);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Shared setup for lit list commands. Returns the entries and count on
|
||||
* success; writes a non-NULL *out_arr only when *out_count > 0. */
|
||||
static Result lit_list_collect(HbcContext *ctx, RCore *core, const HBCLiteralEntry **out_arr, u32 *out_n) {
|
||||
*out_arr = NULL;
|
||||
*out_n = 0;
|
||||
HBC *hbc = NULL;
|
||||
Result r = ensure_hbc_loaded (ctx, core, &hbc);
|
||||
if (r.code != RESULT_SUCCESS) {
|
||||
return r;
|
||||
}
|
||||
if (!ensure_lit_cache_populated (core, hbc)) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_DATA, "literal cache not populated");
|
||||
}
|
||||
r = hbc_literals_list (hbc, out_arr, out_n);
|
||||
if (r.code != RESULT_SUCCESS) {
|
||||
return r;
|
||||
}
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
static void cmd_lit_list_quiet(HbcContext *ctx, RCore *core) {
|
||||
const HBCLiteralEntry *arr = NULL;
|
||||
u32 n = 0;
|
||||
Result r = lit_list_collect (ctx, core, &arr, &n);
|
||||
if (r.code != RESULT_SUCCESS) {
|
||||
R_LOG_ERROR ("%s", safe_errmsg (r.error_message));
|
||||
return;
|
||||
}
|
||||
for (u32 i = 0; i < n; i++) {
|
||||
const HBCLiteralEntry *e = &arr[i];
|
||||
r_cons_printf (core->cons, "0x%" PFMT64x " %s\n", (ut64)HBC_VADDR_BASE + e->paddr, e->formatted? e->formatted: "");
|
||||
}
|
||||
}
|
||||
|
||||
static void print_xref_line(RCore *core, const HBCLiteralEntry *e, bool va) {
|
||||
ut64 base = va? (ut64)HBC_VADDR_BASE: 0;
|
||||
r_cons_printf (core->cons, "0x%" PFMT64x, base + e->paddr);
|
||||
for (u32 j = 0; j < e->xref_count; j++) {
|
||||
r_cons_printf (core->cons, " 0x%" PFMT64x, base + (ut64)e->xref_addrs[j]);
|
||||
}
|
||||
r_cons_println (core->cons, "");
|
||||
}
|
||||
|
||||
static void cmd_lit_list_xrefs(HbcContext *ctx, RCore *core, const char *args) {
|
||||
const HBCLiteralEntry *arr = NULL;
|
||||
u32 n = 0;
|
||||
if (hbc_literals_list (hbc, &arr, &n).code != RESULT_SUCCESS) {
|
||||
R_LOG_ERROR ("list failed");
|
||||
Result r = lit_list_collect (ctx, core, &arr, &n);
|
||||
if (r.code != RESULT_SUCCESS) {
|
||||
R_LOG_ERROR ("%s", safe_errmsg (r.error_message));
|
||||
return;
|
||||
}
|
||||
bool va = r_config_get_b (core->config, "io.va");
|
||||
/* Parse optional address argument. Accepts 0x... or bare hex. */
|
||||
if (args) {
|
||||
while (*args && isspace ((unsigned char)*args)) {
|
||||
args++;
|
||||
}
|
||||
}
|
||||
if (args && *args) {
|
||||
ut64 want = r_num_get (core->num, args);
|
||||
ut64 base = va? (ut64)HBC_VADDR_BASE: 0;
|
||||
ut64 target_paddr = (want >= base)? (want - base): want;
|
||||
const HBCLiteralEntry *match = NULL;
|
||||
for (u32 i = 0; i < n; i++) {
|
||||
if (arr[i].paddr == target_paddr) {
|
||||
match = &arr[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!match) {
|
||||
R_LOG_ERROR ("no literal at that address");
|
||||
return;
|
||||
}
|
||||
print_xref_line (core, match, va);
|
||||
return;
|
||||
}
|
||||
for (u32 i = 0; i < n; i++) {
|
||||
print_xref_line (core, &arr[i], va);
|
||||
}
|
||||
}
|
||||
|
||||
static void cmd_lit_list(HbcContext *ctx, RCore *core, bool as_json) {
|
||||
const HBCLiteralEntry *arr = NULL;
|
||||
u32 n = 0;
|
||||
Result r = lit_list_collect (ctx, core, &arr, &n);
|
||||
if (r.code != RESULT_SUCCESS) {
|
||||
R_LOG_ERROR ("%s", safe_errmsg (r.error_message));
|
||||
return;
|
||||
}
|
||||
if (n == 0) {
|
||||
r_cons_println (core->cons, "(cache empty — run pd:hLs to scan)");
|
||||
R_LOG_INFO ("No SLP literals found");
|
||||
return;
|
||||
}
|
||||
if (as_json) {
|
||||
@@ -690,33 +828,33 @@ static void cmd_lit_list(HbcContext *ctx, RCore *core, bool as_json) {
|
||||
return;
|
||||
}
|
||||
r_cons_printf (core->cons, "literals: %u\n", n);
|
||||
bool va = r_config_get_b (core->config, "io.va");
|
||||
for (u32 i = 0; i < n; i++) {
|
||||
const HBCLiteralEntry *e = &arr[i];
|
||||
r_cons_printf (core->cons,
|
||||
"%-6s n=%-4u id=(%u,%u) paddr=0x%08x xrefs=%u %s\n",
|
||||
kind_label (e->kind), e->num_items, e->primary_id,
|
||||
e->secondary_id, e->paddr, e->xref_count,
|
||||
e->formatted? e->formatted: "");
|
||||
if (va) {
|
||||
r_cons_printf (core->cons,
|
||||
"%-6s n=%-4u id=(%u,%u) vaddr=0x%08" PFMT64x " xrefs=%u %s\n",
|
||||
kind_label (e->kind),
|
||||
e->num_items,
|
||||
e->primary_id,
|
||||
e->secondary_id,
|
||||
(ut64)HBC_VADDR_BASE + e->paddr,
|
||||
e->xref_count,
|
||||
e->formatted? e->formatted: "");
|
||||
} else {
|
||||
r_cons_printf (core->cons,
|
||||
"%-6s n=%-4u id=(%u,%u) paddr=0x%08x xrefs=%u %s\n",
|
||||
kind_label (e->kind),
|
||||
e->num_items,
|
||||
e->primary_id,
|
||||
e->secondary_id,
|
||||
e->paddr,
|
||||
e->xref_count,
|
||||
e->formatted? e->formatted: "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void cmd_lit_scan_code(HbcContext *ctx, RCore *core) {
|
||||
HBC *hbc = NULL;
|
||||
Result r = ensure_hbc_loaded (ctx, core, &hbc);
|
||||
if (r.code != RESULT_SUCCESS) {
|
||||
R_LOG_ERROR ("%s", safe_errmsg (r.error_message));
|
||||
return;
|
||||
}
|
||||
u32 n = 0;
|
||||
r = hbc_literals_scan_code (hbc, &n);
|
||||
if (r.code != RESULT_SUCCESS) {
|
||||
R_LOG_ERROR ("scan failed: %s", safe_errmsg (r.error_message));
|
||||
return;
|
||||
}
|
||||
register_all_artifacts (core, hbc);
|
||||
r_cons_printf (core->cons, "scanned code, %u distinct literals (flags + xrefs registered)\n", n);
|
||||
}
|
||||
|
||||
static void cmd_lit_scan_pool(HbcContext *ctx, RCore *core, HBCLiteralKind kind) {
|
||||
HBC *hbc = NULL;
|
||||
Result r = ensure_hbc_loaded (ctx, core, &hbc);
|
||||
@@ -735,12 +873,83 @@ static void cmd_lit_scan_pool(HbcContext *ctx, RCore *core, HBCLiteralKind kind)
|
||||
for (u32 i = 0; i < n; i++) {
|
||||
r_cons_printf (core->cons,
|
||||
" paddr=0x%08x pool_off=0x%08x n=%-4u tag=%u\n",
|
||||
groups[i].paddr, groups[i].pool_offset, groups[i].num_items,
|
||||
groups[i].paddr,
|
||||
groups[i].pool_offset,
|
||||
groups[i].num_items,
|
||||
groups[i].tag);
|
||||
}
|
||||
free (groups);
|
||||
}
|
||||
|
||||
/* Print r2 oneliners that register flag + comment + xrefs for one entry.
|
||||
* When va is true addresses are shown as vaddrs (HBC_VADDR_BASE + paddr),
|
||||
* otherwise as paddr. The formatted literal text is embedded in a CC comment;
|
||||
* embedded newlines are squashed into spaces and a single quote is escaped. */
|
||||
static void print_r2_for_entry(RCore *core, const HBCLiteralEntry *e, bool va) {
|
||||
ut64 base = va? (ut64)HBC_VADDR_BASE: 0;
|
||||
ut64 vaddr = base + e->paddr;
|
||||
const char *prefix = lit_kind_prefix (e->kind);
|
||||
/* flag at literal vaddr */
|
||||
r_cons_printf (core->cons, "f %s0x%x 1 @ 0x%" PFMT64x "\n", prefix, e->paddr, vaddr);
|
||||
/* embed the formatted text as a comment at the literal vaddr */
|
||||
if (e->formatted && *e->formatted) {
|
||||
char *clean = strdup (e->formatted);
|
||||
if (clean) {
|
||||
for (char *p = clean; *p; p++) {
|
||||
if (*p == '\n' || *p == '\r') {
|
||||
*p = ' ';
|
||||
} else if (*p == '\'') {
|
||||
*p = '"';
|
||||
}
|
||||
}
|
||||
r_cons_printf (core->cons, "\" /* %s */ CC %s @ 0x%" PFMT64x "\n", prefix, clean, vaddr);
|
||||
free (clean);
|
||||
}
|
||||
}
|
||||
/* xref from each call site to the literal */
|
||||
for (u32 j = 0; j < e->xref_count; j++) {
|
||||
ut64 from = base + (ut64)e->xref_addrs[j];
|
||||
r_cons_printf (core->cons, "axd 0x%" PFMT64x " 0x%" PFMT64x "\n", from, vaddr);
|
||||
}
|
||||
}
|
||||
|
||||
static void cmd_lit_print_r2(HbcContext *ctx, RCore *core, const char *args) {
|
||||
const HBCLiteralEntry *arr = NULL;
|
||||
u32 n = 0;
|
||||
Result r = lit_list_collect (ctx, core, &arr, &n);
|
||||
if (r.code != RESULT_SUCCESS) {
|
||||
R_LOG_ERROR ("%s", safe_errmsg (r.error_message));
|
||||
return;
|
||||
}
|
||||
bool va = r_config_get_b (core->config, "io.va");
|
||||
if (args) {
|
||||
while (*args && isspace ((unsigned char)*args)) {
|
||||
args++;
|
||||
}
|
||||
}
|
||||
if (args && *args) {
|
||||
ut64 want = r_num_get (core->num, args);
|
||||
ut64 base = va? (ut64)HBC_VADDR_BASE: 0;
|
||||
ut64 target_paddr = (want >= base)? (want - base): want;
|
||||
const HBCLiteralEntry *match = NULL;
|
||||
for (u32 i = 0; i < n; i++) {
|
||||
if (arr[i].paddr == target_paddr) {
|
||||
match = &arr[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!match) {
|
||||
R_LOG_ERROR ("no literal at that address");
|
||||
return;
|
||||
}
|
||||
print_r2_for_entry (core, match, va);
|
||||
return;
|
||||
}
|
||||
for (u32 i = 0; i < n; i++) {
|
||||
print_r2_for_entry (core, &arr[i], va);
|
||||
}
|
||||
}
|
||||
|
||||
static void cmd_lit_reset(HbcContext *ctx, RCore *core) {
|
||||
HBC *hbc = NULL;
|
||||
Result r = ensure_hbc_loaded (ctx, core, &hbc);
|
||||
@@ -759,19 +968,17 @@ static void cmd_lit_get(HbcContext *ctx, RCore *core, const char *args) {
|
||||
R_LOG_ERROR ("%s", safe_errmsg (r.error_message));
|
||||
return;
|
||||
}
|
||||
/* Syntax: pd:hLg a <num> <array_id> (array)
|
||||
* pd:hLg o <num> <primary> [<sec>] (object) */
|
||||
/* Syntax: r2hermes-Lg a <num> <array_id> (array)
|
||||
* r2hermes-Lg o <num> <primary> [<sec>] (object) */
|
||||
char kindc = 0;
|
||||
u64 num = 0, primary = 0, secondary = 0;
|
||||
if (!args || sscanf (args, " %c %" SCNu64 " %" SCNu64 " %" SCNu64,
|
||||
&kindc, &num, &primary, &secondary) < 3) {
|
||||
R_LOG_ERROR ("usage: pd:hLg {a|o} <num_items> <primary_id> [<secondary_id>]");
|
||||
if (!args || sscanf (args, " %c %" SCNu64 " %" SCNu64 " %" SCNu64, &kindc, &num, &primary, &secondary) < 3) {
|
||||
R_LOG_ERROR ("usage: r2hermes-Lg {a|o} <num_items> <primary_id> [<secondary_id>]");
|
||||
return;
|
||||
}
|
||||
HBCLiteralKind kind = (kindc == 'a')? HBC_LIT_ARRAY: HBC_LIT_OBJECT;
|
||||
char *text = NULL;
|
||||
r = hbc_literals_format_raw (hbc, kind, (u32)num, (u32)primary,
|
||||
(u32)secondary, &text);
|
||||
r = hbc_literals_format_raw (hbc, kind, (u32)num, (u32)primary, (u32)secondary, &text);
|
||||
if (r.code != RESULT_SUCCESS || !text) {
|
||||
R_LOG_ERROR ("format failed: %s", safe_errmsg (r.error_message));
|
||||
free (text);
|
||||
@@ -785,21 +992,26 @@ static void cmd_lit_toggle_inline(RCore *core) {
|
||||
bool cur = hbc_get_inline_literals ();
|
||||
bool next = !cur;
|
||||
hbc_set_inline_literals (next);
|
||||
r_config_set_b (core->config, "hbc.inline_buffer_literals", next);
|
||||
r_config_set_b (core->config, "r2hermes.inline_buffer_literals", next);
|
||||
r_cons_printf (core->cons, "inline buffer literals: %s\n", next? "on": "off");
|
||||
}
|
||||
|
||||
static const char LIT_HELP[] =
|
||||
"Usage: pd:hL[subcmd]\n"
|
||||
" pd:hL List cached literals\n"
|
||||
" pd:hLj List as JSON\n"
|
||||
" pd:hLs Scan all code; register literals as flags/xrefs/comments\n"
|
||||
" pd:hLp[ao] Scan SLP pool (default: arrays; a=arrays, o=objects)\n"
|
||||
" pd:hLr Reset literal cache (does not remove r2 flags/comments)\n"
|
||||
" pd:hLg <k> <n> <primary> [<sec>]\n"
|
||||
"Usage: r2hermes-L[subcmd]\n"
|
||||
" r2hermes-L List cached literals (auto-scans code if cache is empty)\n"
|
||||
" r2hermes-Lj List as JSON (auto-scans if needed)\n"
|
||||
" r2hermes-Lq List as `<vaddr> <formatted>` per line (one per literal)\n"
|
||||
" r2hermes-Lx [addr] List as `<vaddr> <xref1> <xref2> ...` (one per literal)\n"
|
||||
" With addr: print xrefs for that single literal only\n"
|
||||
" r2hermes-Lp[ao] Scan SLP pool (default: arrays; a=arrays, o=objects)\n"
|
||||
" r2hermes-LR Reset literal cache (does not remove r2 flags/comments)\n"
|
||||
" r2hermes-Lr [addr] Print r2 oneliners (f/CC/axd) to register flag+comment+xrefs\n"
|
||||
" With addr: print oneliners for that single literal only\n"
|
||||
" r2hermes-Lg <k> <n> <primary> [<sec>]\n"
|
||||
" Format a literal from raw params (k=a|o)\n"
|
||||
" pd:hLi Toggle inline literal comments in disasm\n"
|
||||
" pd:hL? This help\n";
|
||||
" r2hermes-Li Toggle inline literal comments in disasm\n"
|
||||
" r2hermes-L? This help\n"
|
||||
"\nDeprecated alias: pd:hL[subcmd]\n";
|
||||
|
||||
static void cmd_literals(HbcContext *ctx, RCore *core, const char *arg) {
|
||||
while (*arg && isspace ((unsigned char)*arg)) {
|
||||
@@ -812,18 +1024,25 @@ static void cmd_literals(HbcContext *ctx, RCore *core, const char *arg) {
|
||||
case 'j':
|
||||
cmd_lit_list (ctx, core, true);
|
||||
break;
|
||||
case 's':
|
||||
cmd_lit_scan_code (ctx, core);
|
||||
case 'q':
|
||||
cmd_lit_list_quiet (ctx, core);
|
||||
break;
|
||||
case 'p': {
|
||||
char k = arg[1];
|
||||
HBCLiteralKind kind = (k == 'o')? HBC_LIT_OBJECT: HBC_LIT_ARRAY;
|
||||
cmd_lit_scan_pool (ctx, core, kind);
|
||||
case 'x':
|
||||
cmd_lit_list_xrefs (ctx, core, arg + 1);
|
||||
break;
|
||||
}
|
||||
case 'r':
|
||||
case 'p':
|
||||
{
|
||||
char k = arg[1];
|
||||
HBCLiteralKind kind = (k == 'o')? HBC_LIT_OBJECT: HBC_LIT_ARRAY;
|
||||
cmd_lit_scan_pool (ctx, core, kind);
|
||||
break;
|
||||
}
|
||||
case 'R':
|
||||
cmd_lit_reset (ctx, core);
|
||||
break;
|
||||
case 'r':
|
||||
cmd_lit_print_r2 (ctx, core, arg + 1);
|
||||
break;
|
||||
case 'g':
|
||||
cmd_lit_get (ctx, core, arg + 1);
|
||||
break;
|
||||
@@ -834,48 +1053,76 @@ static void cmd_literals(HbcContext *ctx, RCore *core, const char *arg) {
|
||||
r_cons_print (core->cons, LIT_HELP);
|
||||
break;
|
||||
default:
|
||||
R_LOG_ERROR ("Unknown subcommand. Use pd:hL? for help");
|
||||
R_LOG_ERROR ("Unknown subcommand. Use r2hermes-L? for help");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Show help */
|
||||
static void r2hermes_help(RCore *core) {
|
||||
const char msg[] =
|
||||
"Usage: r2hermes[-arg] # see also pd:h for decompilation\n"
|
||||
"r2hermes-h - help message (same as r2hermes-?, see pd:h? too)\n"
|
||||
"r2hermes-E - List functions containing eval instructions\n"
|
||||
"r2hermes-H - Show file information and hash status\n"
|
||||
"r2hermes-L[?] - SLP literal cache: list/scan/reset/format/toggle\n"
|
||||
"r2hermes-S[jr?] - emit SBOM from SLP literals (j=CycloneDX JSON, r=raw input)\n";
|
||||
r_cons_print (core->cons, msg);
|
||||
}
|
||||
|
||||
static void cmd_help(RCore *core) {
|
||||
r_cons_print (core->cons,
|
||||
"Usage: pd:h[subcommand]\n"
|
||||
"Usage: pd:h[subcommand] (see r2hermes-? for more commands)\n"
|
||||
"Hermes bytecode decompiler via libhbc\n\n"
|
||||
"Subcommands:\n"
|
||||
" pd:h - Decompile function at current offset (or all if not in function)\n"
|
||||
" pd:hc [id] - Decompile function by id\n"
|
||||
" pd:ha - Decompile all functions\n"
|
||||
" pd:hf - List all functions\n"
|
||||
" pd:hi - Show file information\n"
|
||||
" pd:hj [id] - JSON output for function\n"
|
||||
" pd:ho [id] - Decompile with offsets (addresses) per statement\n"
|
||||
" pd:hoa - Decompile all with offsets\n"
|
||||
" pd:hs [id] - List source-line information\n"
|
||||
" pd:hL[?] - SLP literal cache: list/scan/reset (see pd:hL?)\n"
|
||||
" pd:hL[?] - Deprecated alias for r2hermes-L[?]\n"
|
||||
" pd:h? - Show this help\n"
|
||||
"\nNote: r2 comments (CC command) are automatically inlined in decompiler output.\n");
|
||||
}
|
||||
|
||||
/* Main handler for pd:h commands */
|
||||
static bool cmd_handler(RCorePluginSession *s, const char *input) {
|
||||
RCore *core = s? s->core: NULL;
|
||||
#include "sbom.inc.c"
|
||||
|
||||
if (!core || !input) {
|
||||
return false;
|
||||
}
|
||||
if (!r_str_startswith (input, "pd:h")) {
|
||||
return false;
|
||||
}
|
||||
HbcContext *ctx = s->data;
|
||||
if (!ctx) {
|
||||
return false;
|
||||
static void cmd_r2hermes(RCore *core, HbcContext *ctx, const char *arg) {
|
||||
arg = r_str_trim_head_ro (arg);
|
||||
if (*arg == '-') {
|
||||
switch (arg[1]) {
|
||||
case 'E':
|
||||
cmd_list_eval_functions (ctx, core);
|
||||
break;
|
||||
case 'H':
|
||||
cmd_file_info (ctx, core);
|
||||
break;
|
||||
case 'L':
|
||||
cmd_literals (ctx, core, arg + 2);
|
||||
break;
|
||||
case 'S':
|
||||
cmd_sbom (ctx, core, arg + 2);
|
||||
break;
|
||||
case '?':
|
||||
case 'h':
|
||||
r2hermes_help (core);
|
||||
break;
|
||||
default:
|
||||
R_LOG_ERROR ("Invalid argument. See r2hermes-?");
|
||||
break;
|
||||
}
|
||||
} else if (!*arg) {
|
||||
r2hermes_help (core);
|
||||
} else {
|
||||
R_LOG_ERROR ("Invalid argument for r2hermes");
|
||||
}
|
||||
}
|
||||
|
||||
const char *arg = input + 4;
|
||||
|
||||
static void cmd_pdh(RCore *core, HbcContext *ctx, const char *arg) {
|
||||
// PANCAKE all those cmd_* functions taking ctx as first argument.. they should take core first and then ctx.. so swap them all
|
||||
switch (*arg) {
|
||||
case '\0': /* pd:h */
|
||||
case ' ': /* pd:h (with spaces) */
|
||||
@@ -894,17 +1141,9 @@ static bool cmd_handler(RCorePluginSession *s, const char *input) {
|
||||
case 'f': /* pd:hf */
|
||||
cmd_list_functions (ctx, core);
|
||||
break;
|
||||
case 'i': /* pd:hi */
|
||||
cmd_file_info (ctx, core);
|
||||
case 'j': /* pd:hj [id] */
|
||||
cmd_json (ctx, core, r_str_trim_head_ro (arg + 1));
|
||||
break;
|
||||
case 'j': { /* pd:hj [id] */
|
||||
const char *addr_str = arg + 1;
|
||||
while (*addr_str && isspace ((unsigned char)*addr_str)) {
|
||||
addr_str++;
|
||||
}
|
||||
cmd_json (ctx, core, addr_str);
|
||||
break;
|
||||
}
|
||||
case 'o': { /* pd:ho [id] or pd:hoa */
|
||||
const char *sub = arg + 1;
|
||||
if (*sub == 'a') { // "pd:hoa"
|
||||
@@ -921,7 +1160,8 @@ static bool cmd_handler(RCorePluginSession *s, const char *input) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'L': /* pd:hL* — literal cache / buffer-literal commands */
|
||||
case 'L': /* pd:hL* — deprecated alias for r2hermes-L* */
|
||||
R_LOG_WARN ("pd:hL is deprecated; use r2hermes-L instead");
|
||||
cmd_literals (ctx, core, arg + 1);
|
||||
break;
|
||||
case 's': /* pd:hs [id] */
|
||||
@@ -934,8 +1174,24 @@ static bool cmd_handler(RCorePluginSession *s, const char *input) {
|
||||
R_LOG_ERROR ("Unknown subcommand. Use pd:h? for help");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
/* Main handler for pd:h commands */
|
||||
static bool cmd_handler(RCorePluginSession *s, const char *input) {
|
||||
if (s && s->core && input) {
|
||||
HbcContext *ctx = s->data;
|
||||
if (!ctx) {
|
||||
R_LOG_ERROR ("Missing HBC context, not an hermes bin here?")
|
||||
} else if (r_str_startswith (input, "pd:h")) {
|
||||
cmd_pdh (s->core, ctx, input + 4);
|
||||
} else if (r_str_startswith (input, "r2hermes")) {
|
||||
cmd_r2hermes (s->core, ctx, input + 8);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool cb_inline_buffer_literals(void *user, void *data) {
|
||||
@@ -973,73 +1229,78 @@ static bool plugin_init(RCorePluginSession *s) {
|
||||
|
||||
r_config_lock (cfg, false);
|
||||
|
||||
RConfigNode *node = r_config_set (cfg, "hbc.pretty_literals", "true");
|
||||
RConfigNode *node = r_config_set (cfg, "r2hermes.pretty_literals", "true");
|
||||
if (node) {
|
||||
r_config_node_desc (node, "Format literals nicely (objects, arrays, etc)");
|
||||
}
|
||||
|
||||
node = r_config_set (cfg, "hbc.suppress_comments", "false");
|
||||
node = r_config_set (cfg, "r2hermes.suppress_comments", "false");
|
||||
if (node) {
|
||||
r_config_node_desc (node, "Omit comments in decompiled output");
|
||||
}
|
||||
|
||||
node = r_config_set (cfg, "hbc.show_offsets", "false");
|
||||
node = r_config_set (cfg, "r2hermes.show_offsets", "false");
|
||||
if (node) {
|
||||
r_config_node_desc (node, "Show bytecode offsets for each statement");
|
||||
}
|
||||
|
||||
/* Optimization/transformation pass control */
|
||||
node = r_config_set (cfg, "hbc.skip_pass1", "false");
|
||||
node = r_config_set (cfg, "r2hermes.skip_pass1", "false");
|
||||
if (node) {
|
||||
r_config_node_desc (node, "Skip pass 1: metadata collection (closure/generator/async flags)");
|
||||
}
|
||||
|
||||
node = r_config_set (cfg, "hbc.skip_pass2", "false");
|
||||
node = r_config_set (cfg, "r2hermes.skip_pass2", "false");
|
||||
if (node) {
|
||||
r_config_node_desc (node, "Skip pass 2: code transformation to tokens and statements");
|
||||
}
|
||||
|
||||
node = r_config_set (cfg, "hbc.skip_pass3", "false");
|
||||
node = r_config_set (cfg, "r2hermes.skip_pass3", "false");
|
||||
if (node) {
|
||||
r_config_node_desc (node, "Skip pass 3: for-in loop parsing (structural recovery via CFG)");
|
||||
}
|
||||
|
||||
node = r_config_set (cfg, "hbc.skip_pass4", "false");
|
||||
node = r_config_set (cfg, "r2hermes.skip_pass4", "false");
|
||||
if (node) {
|
||||
r_config_node_desc (node, "Skip pass 4: closure variable naming and environment resolution");
|
||||
}
|
||||
|
||||
/* Control flow rendering options */
|
||||
node = r_config_set (cfg, "hbc.force_dispatch", "false");
|
||||
node = r_config_set (cfg, "r2hermes.force_dispatch", "false");
|
||||
if (node) {
|
||||
r_config_node_desc (node, "Force switch/case dispatch loop even for linear multi-block functions");
|
||||
}
|
||||
|
||||
node = r_config_set (cfg, "hbc.inline_closures", "true");
|
||||
node = r_config_set (cfg, "r2hermes.inline_closures", "true");
|
||||
if (node) {
|
||||
r_config_node_desc (node, "Inline closure definitions into parent function (true/false)");
|
||||
}
|
||||
|
||||
node = r_config_set (cfg, "hbc.inline_threshold", "0");
|
||||
node = r_config_set (cfg, "r2hermes.inline_threshold", "256");
|
||||
if (node) {
|
||||
r_config_node_desc (node, "Max bytecode size (bytes) for closure inlining (0=no limit, -1=never inline)");
|
||||
}
|
||||
|
||||
node = r_config_set_i (cfg, "hbc.max_ast", 5000);
|
||||
node = r_config_set_i (cfg, "r2hermes.inline_depth", 3);
|
||||
if (node) {
|
||||
r_config_node_desc (node, "Max nested inline depth for closures (0=no limit); bounds module-graph explosion");
|
||||
}
|
||||
|
||||
node = r_config_set_i (cfg, "r2hermes.max_ast", 5000);
|
||||
if (node) {
|
||||
r_config_node_desc (node, "Abort AST build once N statements per function (0=unlimited)");
|
||||
}
|
||||
|
||||
node = r_config_set_i (cfg, "hbc.max_bytes", 262144);
|
||||
node = r_config_set_i (cfg, "r2hermes.max_bytes", 262144);
|
||||
if (node) {
|
||||
r_config_node_desc (node, "Stop decompilation once total output reaches N bytes (0=unlimited)");
|
||||
}
|
||||
|
||||
/* Default off: inlining the SLP literal as a disasm-line comment makes
|
||||
* arch.decode O(n) in literal size. Use pd:hL* to inspect literals
|
||||
* arch.decode O (n) in literal size. Use r2hermes-L* to inspect literals
|
||||
* without impacting disasm speed. */
|
||||
hbc_set_inline_literals (false);
|
||||
node = r_config_set_b_cb (cfg, "hbc.inline_buffer_literals", false, cb_inline_buffer_literals);
|
||||
node = r_config_set_b_cb (cfg, "r2hermes.inline_buffer_literals", false, cb_inline_buffer_literals);
|
||||
if (node) {
|
||||
r_config_node_desc (node, "Inline SLP buffer literals as comments in disasm (slow; default off)");
|
||||
}
|
||||
@@ -1065,7 +1326,7 @@ RCorePlugin r_core_plugin_r2hermes = {
|
||||
.name = "core_hbc",
|
||||
.desc = "Hermes bytecode decompiler plugin",
|
||||
.author = "pancake",
|
||||
.license = "LGPL-3.0-only",
|
||||
.license = "BSD-3-Clause",
|
||||
},
|
||||
.call = cmd_handler,
|
||||
.init = plugin_init,
|
||||
|
||||
@@ -1,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
|
||||
|
||||
+243
-82
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/common.h>
|
||||
#include <hbc/hbc.h>
|
||||
#include <hbc/literals.h>
|
||||
@@ -23,20 +25,28 @@ typedef struct {
|
||||
CliFlags flags;
|
||||
} CliContext;
|
||||
|
||||
typedef Result (*CommandHandler)(const CliContext *ctx, int argc, char **argv);
|
||||
typedef struct { const char *name; const char *help; CommandHandler handler; } Command;
|
||||
typedef Result(*CommandHandler)(const CliContext *ctx, int argc, char **argv);
|
||||
typedef struct {
|
||||
const char *name;
|
||||
const char *help;
|
||||
CommandHandler handler;
|
||||
} Command;
|
||||
|
||||
static void json_print_string(const char *s);
|
||||
|
||||
static const struct { const char *lname; char sname; size_t offset; } flag_table[] = {
|
||||
{ "json", 'j', offsetof (CliFlags, json) },
|
||||
{ "verbose", 'v', offsetof (CliFlags, verbose) },
|
||||
{ "bytecode", 'b', offsetof (CliFlags, bytecode) },
|
||||
{ "debug", 'd', offsetof (CliFlags, debug) },
|
||||
{ "asmsyntax", 0, offsetof (CliFlags, asm_syntax) },
|
||||
{ "pretty-literals", 'P', offsetof (CliFlags, pretty_literals) },
|
||||
static const struct {
|
||||
const char *lname;
|
||||
char sname;
|
||||
size_t offset;
|
||||
} flag_table[] = {
|
||||
{ "json", 'j', offsetof (CliFlags, json) },
|
||||
{ "verbose", 'v', offsetof (CliFlags, verbose) },
|
||||
{ "bytecode", 'b', offsetof (CliFlags, bytecode) },
|
||||
{ "debug", 'd', offsetof (CliFlags, debug) },
|
||||
{ "asmsyntax", 0, offsetof (CliFlags, asm_syntax) },
|
||||
{ "pretty-literals", 'P', offsetof (CliFlags, pretty_literals) },
|
||||
{ "no-pretty-literals", 'N', offsetof (CliFlags, no_pretty_literals) },
|
||||
{ "no-comments", 'C', offsetof (CliFlags, no_comments) },
|
||||
{ "no-comments", 'C', offsetof (CliFlags, no_comments) },
|
||||
};
|
||||
#define FLAG_TABLE_N (sizeof (flag_table) / sizeof (flag_table[0]))
|
||||
|
||||
@@ -68,7 +78,7 @@ static bool flag_set(CliFlags *f, const char *lname, char sname) {
|
||||
bool m = lname? !strcmp (lname, flag_table[i].lname)
|
||||
: (flag_table[i].sname && flag_table[i].sname == sname);
|
||||
if (m) {
|
||||
*(bool *)((char *)f + flag_table[i].offset) = true;
|
||||
*(bool *) ((char *)f + flag_table[i].offset) = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -166,9 +176,15 @@ static Result read_entire_file(const char *path, char **out_buf, size_t *out_len
|
||||
}
|
||||
|
||||
static int hex_nibble(char c) {
|
||||
if (c >= '0' && c <= '9') { return c - '0'; }
|
||||
if (c >= 'a' && c <= 'f') { return 10 + c - 'a'; }
|
||||
if (c >= 'A' && c <= 'F') { return 10 + c - 'A'; }
|
||||
if (c >= '0' && c <= '9') {
|
||||
return c - '0';
|
||||
}
|
||||
if (c >= 'a' && c <= 'f') {
|
||||
return 10 + c - 'a';
|
||||
}
|
||||
if (c >= 'A' && c <= 'F') {
|
||||
return 10 + c - 'A';
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -181,15 +197,24 @@ static Result parse_hex_bytes(const char *s, u8 *out, size_t out_cap, size_t *ou
|
||||
if (!s || !*s) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Empty hex string");
|
||||
}
|
||||
for (const char *p = s; *p; ) {
|
||||
while (*p && is_hex_sep (*p)) { p++; }
|
||||
if (!*p) { break; }
|
||||
if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) { p += 2; continue; }
|
||||
for (const char *p = s; *p;) {
|
||||
while (*p && is_hex_sep (*p)) {
|
||||
p++;
|
||||
}
|
||||
if (!*p) {
|
||||
break;
|
||||
}
|
||||
if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) {
|
||||
p += 2;
|
||||
continue;
|
||||
}
|
||||
int hi = hex_nibble (*p++);
|
||||
if (hi < 0) {
|
||||
return errorf (RESULT_ERROR_INVALID_ARGUMENT, "Invalid hex character: '%c'", p[-1]);
|
||||
}
|
||||
while (*p && is_hex_sep (*p)) { p++; }
|
||||
while (*p && is_hex_sep (*p)) {
|
||||
p++;
|
||||
}
|
||||
int lo = hex_nibble (*p++);
|
||||
if (lo < 0) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Odd number of hex nibbles");
|
||||
@@ -197,7 +222,7 @@ static Result parse_hex_bytes(const char *s, u8 *out, size_t out_cap, size_t *ou
|
||||
if (*out_len >= out_cap) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Too many bytes (max 64)");
|
||||
}
|
||||
out[(*out_len)++] = (u8)((hi << 4) | lo);
|
||||
out[(*out_len)++] = (u8) ((hi << 4) | lo);
|
||||
}
|
||||
if (!*out_len) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "No bytes parsed");
|
||||
@@ -220,7 +245,11 @@ static Result cmd_d(const CliContext *ctx, int argc, char **argv) {
|
||||
continue;
|
||||
}
|
||||
printf ("Function %u: %s\n Offset: 0x%08x\n Size: %u bytes\n Params: %u\n\n",
|
||||
i, fi.name? fi.name: "unnamed", fi.offset, fi.size, fi.param_count);
|
||||
i,
|
||||
fi.name? fi.name: "unnamed",
|
||||
fi.offset,
|
||||
fi.size,
|
||||
fi.param_count);
|
||||
}
|
||||
hbc_close (hbc);
|
||||
return SUCCESS_RESULT ();
|
||||
@@ -243,9 +272,13 @@ static Result cmd_dis(const CliContext *ctx, int argc, char **argv) {
|
||||
RETURN_IF_ERROR (parse_hex_bytes (argv[0], bytes, sizeof (bytes), &bcount));
|
||||
HBCInsnInfo sinfo = { 0 };
|
||||
HBCDecodeCtx dec_ctx = {
|
||||
.bytes = bytes, .len = bcount, .bytecode_version = 96, .pc = 0,
|
||||
.asm_syntax = ctx->flags.asm_syntax, .build_objects = true,
|
||||
};
|
||||
.bytes = bytes,
|
||||
.len = bcount,
|
||||
.bytecode_version = 96,
|
||||
.pc = 0,
|
||||
.asm_syntax = ctx->flags.asm_syntax,
|
||||
.build_objects = true,
|
||||
};
|
||||
Result r = hbc_dec (&dec_ctx, &sinfo);
|
||||
if (r.code == RESULT_SUCCESS) {
|
||||
printf ("%s\n", sinfo.text? sinfo.text: "");
|
||||
@@ -330,8 +363,7 @@ static Result cmd_v(const CliContext *ctx, int argc, char **argv) {
|
||||
return err;
|
||||
}
|
||||
fprintf (out, "HBC File Validation Report\n===========================\n\n");
|
||||
fprintf (out, "%llu (magic)\n%u (version)\n%u functions\n%u strings\n\nFile appears valid.\n",
|
||||
(unsigned long long)h.magic, h.version, h.functionCount, h.stringCount);
|
||||
fprintf (out, "%llu (magic)\n%u (version)\n%u functions\n%u strings\n\nFile appears valid.\n", (unsigned long long)h.magic, h.version, h.functionCount, h.stringCount);
|
||||
if (out != stdout) {
|
||||
fclose (out);
|
||||
}
|
||||
@@ -394,8 +426,7 @@ static Result cmd_h(const CliContext *ctx, int argc, char **argv) {
|
||||
if (hh.version >= 99) {
|
||||
fprintf (out, " String Switch Instruction Count: %u\n", hh.numStringSwitchImms);
|
||||
}
|
||||
fprintf (out, " %s: %u\n CJS Module Count: %u\n",
|
||||
hh.version < 78? "CJS Module Offset": "Segment ID", hh.segmentID, hh.cjsModuleCount);
|
||||
fprintf (out, " %s: %u\n CJS Module Count: %u\n", hh.version < 78? "CJS Module Offset": "Segment ID", hh.segmentID, hh.cjsModuleCount);
|
||||
if (hh.version >= 84) {
|
||||
fprintf (out, " Function Source Count: %u\n", hh.functionSourceCount);
|
||||
}
|
||||
@@ -432,14 +463,18 @@ static Result cmd_f(const CliContext *ctx, int argc, char **argv) {
|
||||
return errorf (RESULT_ERROR_INVALID_ARGUMENT, "Usage: f <input> [n]");
|
||||
}
|
||||
u32 n = (argc == 2)? (u32)strtoul (argv[1], NULL, 0): 50;
|
||||
if (!n) { n = 50; }
|
||||
if (!n) {
|
||||
n = 50;
|
||||
}
|
||||
HBC *hbc;
|
||||
RETURN_IF_ERROR (open_hbc (argv[0], &hbc));
|
||||
u32 fc = hbc_function_count (hbc);
|
||||
u32 count = fc < n? fc: n;
|
||||
for (u32 i = 0; i < count; i++) {
|
||||
HBCFunc fi;
|
||||
if (hbc_get_function_info (hbc, i, &fi).code != RESULT_SUCCESS) { continue; }
|
||||
if (hbc_get_function_info (hbc, i, &fi).code != RESULT_SUCCESS) {
|
||||
continue;
|
||||
}
|
||||
printf ("id=%u offset=0x%08x size=%u name=%s\n", i, fi.offset, fi.size, fi.name? fi.name: "");
|
||||
}
|
||||
hbc_close (hbc);
|
||||
@@ -474,16 +509,24 @@ static Result cmd_sl(const CliContext *ctx, int argc, char **argv) {
|
||||
if (json) {
|
||||
printf ("%s{\"function\":%u,\"address\":%u,\"offset\":%u,\"line\":%u,\"column\":%u,\"statement\":%u,\"file\":",
|
||||
first? "": ",",
|
||||
sl->function_id, sl->address, sl->function_address,
|
||||
sl->line, sl->column, sl->statement);
|
||||
sl->function_id,
|
||||
sl->address,
|
||||
sl->function_address,
|
||||
sl->line,
|
||||
sl->column,
|
||||
sl->statement);
|
||||
json_print_string (sl->filename);
|
||||
putchar ('}');
|
||||
first = false;
|
||||
} else {
|
||||
printf ("0x%08x f=%u +0x%x %s:%u:%u stmt=%u\n",
|
||||
sl->address, sl->function_id, sl->function_address,
|
||||
sl->address,
|
||||
sl->function_id,
|
||||
sl->function_address,
|
||||
sl->filename? sl->filename: "",
|
||||
sl->line, sl->column, sl->statement);
|
||||
sl->line,
|
||||
sl->column,
|
||||
sl->statement);
|
||||
}
|
||||
}
|
||||
if (json) {
|
||||
@@ -500,7 +543,9 @@ static Result cmd_cmp(const CliContext *ctx, int argc, char **argv) {
|
||||
return errorf (RESULT_ERROR_INVALID_ARGUMENT, "Usage: cmp <input> [n]");
|
||||
}
|
||||
u32 n = (argc == 2)? (u32)strtoul (argv[1], NULL, 0): 100;
|
||||
if (!n) { n = 100; }
|
||||
if (!n) {
|
||||
n = 100;
|
||||
}
|
||||
HBC *hbc;
|
||||
RETURN_IF_ERROR (open_hbc (argv[0], &hbc));
|
||||
u32 fc = hbc_function_count (hbc);
|
||||
@@ -523,29 +568,48 @@ static Result cmd_cmp(const CliContext *ctx, int argc, char **argv) {
|
||||
char line[4096];
|
||||
while (fgets (line, sizeof (line), py)) {
|
||||
char *p = strstr (line, "=> [Function #");
|
||||
if (!p) { continue; }
|
||||
if (!p) {
|
||||
continue;
|
||||
}
|
||||
p += strlen ("=> [Function #");
|
||||
char *end = NULL;
|
||||
long id = strtol (p, &end, 10);
|
||||
if (end == p || id < 0 || (u32)id >= count) { continue; }
|
||||
if (end == p || id < 0 || (u32)id >= count) {
|
||||
continue;
|
||||
}
|
||||
char *ofp = strstr (end, " of ");
|
||||
if (!ofp) { continue; }
|
||||
if (!ofp) {
|
||||
continue;
|
||||
}
|
||||
long sz = strtol (ofp + 4, &end, 10);
|
||||
if (end == ofp + 4 || sz < 0) { continue; }
|
||||
if (end == ofp + 4 || sz < 0) {
|
||||
continue;
|
||||
}
|
||||
char *offp = strstr (end, " at 0x");
|
||||
if (!offp) { continue; }
|
||||
if (!offp) {
|
||||
continue;
|
||||
}
|
||||
unsigned int off = 0;
|
||||
if (sscanf (offp + 6, "%x", &off) != 1) { continue; }
|
||||
if (sscanf (offp + 6, "%x", &off) != 1) {
|
||||
continue;
|
||||
}
|
||||
py_sizes[id] = (u32)sz;
|
||||
py_offs[id] = (u32)off;
|
||||
}
|
||||
fclose (py);
|
||||
for (u32 i = 0; i < count; i++) {
|
||||
HBCFunc fi;
|
||||
if (hbc_get_function_info (hbc, i, &fi).code != RESULT_SUCCESS) { continue; }
|
||||
if (hbc_get_function_info (hbc, i, &fi).code != RESULT_SUCCESS) {
|
||||
continue;
|
||||
}
|
||||
const char *res = (fi.offset == py_offs[i] && fi.size == py_sizes[i])? "OK": "MISMATCH";
|
||||
printf ("id=%u C(off=0x%08x,sz=%u) PY(off=0x%08x,sz=%u) => %s\n",
|
||||
i, fi.offset, fi.size, py_offs[i], py_sizes[i], res);
|
||||
i,
|
||||
fi.offset,
|
||||
fi.size,
|
||||
py_offs[i],
|
||||
py_sizes[i],
|
||||
res);
|
||||
}
|
||||
free (py_sizes);
|
||||
free (py_offs);
|
||||
@@ -568,13 +632,15 @@ static Result disasm_function(HBC *hbc, u32 function_id, char **out) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "OOM");
|
||||
}
|
||||
buf[0] = '\0';
|
||||
for (u32 offset = 0; offset < bytecode_size; ) {
|
||||
for (u32 offset = 0; offset < bytecode_size;) {
|
||||
HBCInsnInfo ii = { 0 };
|
||||
HBCDecodeCtx dc = {
|
||||
.bytes = bytecode + offset, .len = bytecode_size - offset,
|
||||
.pc = fi.offset + offset, .bytecode_version = hdr.version,
|
||||
.bytes = bytecode + offset,
|
||||
.len = bytecode_size - offset,
|
||||
.pc = fi.offset + offset,
|
||||
.bytecode_version = hdr.version,
|
||||
.hbc = hbc,
|
||||
};
|
||||
};
|
||||
Result r = hbc_dec (&dc, &ii);
|
||||
if (r.code != RESULT_SUCCESS || !ii.size) {
|
||||
free (ii.text);
|
||||
@@ -630,12 +696,20 @@ static Result cmd_cf(const CliContext *ctx, int argc, char **argv) {
|
||||
char line_py[2048], line_c[2048];
|
||||
size_t cpos = 0;
|
||||
while (fgets (line_py, sizeof (line_py), py)) {
|
||||
if (strncmp (line_py, ">> ", 3)) { continue; }
|
||||
if (strncmp (line_py, ">> ", 3)) {
|
||||
continue;
|
||||
}
|
||||
while (disasm_str[cpos] && strncmp (disasm_str + cpos, "==> ", 4)) {
|
||||
while (disasm_str[cpos] && disasm_str[cpos] != '\n') { cpos++; }
|
||||
if (disasm_str[cpos] == '\n') { cpos++; }
|
||||
while (disasm_str[cpos] && disasm_str[cpos] != '\n') {
|
||||
cpos++;
|
||||
}
|
||||
if (disasm_str[cpos] == '\n') {
|
||||
cpos++;
|
||||
}
|
||||
}
|
||||
if (!disasm_str[cpos]) {
|
||||
break;
|
||||
}
|
||||
if (!disasm_str[cpos]) { break; }
|
||||
size_t l = 0;
|
||||
while (disasm_str[cpos + l] && disasm_str[cpos + l] != '\n' && l < sizeof (line_c) - 1) {
|
||||
line_c[l] = disasm_str[cpos + l];
|
||||
@@ -752,10 +826,17 @@ static Result lit_list_impl(const char *input, bool as_json) {
|
||||
printf ("%s{\"kind\":\"%s\",\"num_items\":%u,"
|
||||
"\"primary_id\":%u,\"secondary_id\":%u,\"paddr\":%u,"
|
||||
"\"xrefs\":%u,\"formatted\":\"",
|
||||
i? ",": "", lit_kind_name (e->kind), e->num_items,
|
||||
e->primary_id, e->secondary_id, e->paddr, e->xref_count);
|
||||
i? ",": "",
|
||||
lit_kind_name (e->kind),
|
||||
e->num_items,
|
||||
e->primary_id,
|
||||
e->secondary_id,
|
||||
e->paddr,
|
||||
e->xref_count);
|
||||
for (const char *p = e->formatted; p && *p; p++) {
|
||||
if (*p == '"' || *p == '\\') { putchar ('\\'); }
|
||||
if (*p == '"' || *p == '\\') {
|
||||
putchar ('\\');
|
||||
}
|
||||
putchar (*p);
|
||||
}
|
||||
printf ("\"}");
|
||||
@@ -766,8 +847,12 @@ static Result lit_list_impl(const char *input, bool as_json) {
|
||||
for (u32 i = 0; i < n; i++) {
|
||||
const HBCLiteralEntry *e = &arr[i];
|
||||
printf ("%-6s n=%-4u id=(%u,%u) paddr=0x%08x xrefs=%u %s\n",
|
||||
lit_kind_name (e->kind), e->num_items, e->primary_id,
|
||||
e->secondary_id, e->paddr, e->xref_count,
|
||||
lit_kind_name (e->kind),
|
||||
e->num_items,
|
||||
e->primary_id,
|
||||
e->secondary_id,
|
||||
e->paddr,
|
||||
e->xref_count,
|
||||
e->formatted? e->formatted: "");
|
||||
}
|
||||
}
|
||||
@@ -802,7 +887,10 @@ static Result lit_pool_impl(const char *input, HBCLiteralKind kind) {
|
||||
printf ("%s pool: %u groups\n", lit_kind_name (kind), n);
|
||||
for (u32 i = 0; i < n; i++) {
|
||||
printf (" paddr=0x%08x pool_off=0x%08x n=%-4u tag=%u\n",
|
||||
groups[i].paddr, groups[i].pool_offset, groups[i].num_items, groups[i].tag);
|
||||
groups[i].paddr,
|
||||
groups[i].pool_offset,
|
||||
groups[i].num_items,
|
||||
groups[i].tag);
|
||||
}
|
||||
free (groups);
|
||||
hbc_close (hbc);
|
||||
@@ -818,7 +906,10 @@ static Result lit_xrefs_impl(const char *input) {
|
||||
const HBCLiteralEntry *e = &arr[i];
|
||||
for (u32 j = 0; j < e->xref_count; j++) {
|
||||
printf ("0x%08x -> %s 0x%08x (n=%u)\n",
|
||||
e->xref_addrs[j], lit_kind_name (e->kind), e->paddr, e->num_items);
|
||||
e->xref_addrs[j],
|
||||
lit_kind_name (e->kind),
|
||||
e->paddr,
|
||||
e->num_items);
|
||||
}
|
||||
}
|
||||
hbc_close (hbc);
|
||||
@@ -852,10 +943,7 @@ static Result cmd_lit(const CliContext *ctx, int argc, char **argv) {
|
||||
return errorf (RESULT_ERROR_INVALID_ARGUMENT,
|
||||
"Usage: lit get <file> {a|o} <num> <primary> [<secondary>]");
|
||||
}
|
||||
return lit_get_impl (input, argv[2][0],
|
||||
(u32)strtoul (argv[3], NULL, 0),
|
||||
(u32)strtoul (argv[4], NULL, 0),
|
||||
argc > 5? (u32)strtoul (argv[5], NULL, 0): 0);
|
||||
return lit_get_impl (input, argv[2][0], (u32)strtoul (argv[3], NULL, 0), (u32)strtoul (argv[4], NULL, 0), argc > 5? (u32)strtoul (argv[5], NULL, 0): 0);
|
||||
}
|
||||
return errorf (RESULT_ERROR_INVALID_ARGUMENT, "unknown lit subcommand: %s", sub);
|
||||
}
|
||||
@@ -879,6 +967,14 @@ static void json_print_string(const char *s) {
|
||||
putchar ('"');
|
||||
}
|
||||
|
||||
static void json_print_nullable_string(const char *s) {
|
||||
if (s) {
|
||||
json_print_string (s);
|
||||
} else {
|
||||
fputs ("null", stdout);
|
||||
}
|
||||
}
|
||||
|
||||
static const char *binding_type_name(HBCBindingType type) {
|
||||
return type == HBC_BINDING_EXPORT? "export": "import";
|
||||
}
|
||||
@@ -908,7 +1004,9 @@ static Result cmd_bind(const CliContext *ctx, int argc, char **argv) {
|
||||
printf (",\"module\":");
|
||||
json_print_string (b->module);
|
||||
printf (",\"function_id\":%u,\"offset\":%u,\"string_id\":%u}",
|
||||
b->function_id, b->offset, b->string_id);
|
||||
b->function_id,
|
||||
b->offset,
|
||||
b->string_id);
|
||||
}
|
||||
puts ("]");
|
||||
} else {
|
||||
@@ -916,9 +1014,13 @@ static Result cmd_bind(const CliContext *ctx, int argc, char **argv) {
|
||||
for (u32 i = 0; i < bindings.count; i++) {
|
||||
HBCBinding *b = &bindings.bindings[i];
|
||||
printf ("%-6s %-7s sid=%-5u fn=%-5u off=0x%08x %s%s%s\n",
|
||||
binding_type_name (b->type), b->kind? b->kind: "",
|
||||
b->string_id, b->function_id, b->offset,
|
||||
b->module? b->module: "", b->module? ":": "",
|
||||
binding_type_name (b->type),
|
||||
b->kind? b->kind: "",
|
||||
b->string_id,
|
||||
b->function_id,
|
||||
b->offset,
|
||||
b->module? b->module: "",
|
||||
b->module? ":": "",
|
||||
b->name? b->name: "");
|
||||
}
|
||||
}
|
||||
@@ -927,24 +1029,83 @@ static Result cmd_bind(const CliContext *ctx, int argc, char **argv) {
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
static Result cmd_libs(const CliContext *ctx, int argc, char **argv) {
|
||||
if (argc != 1) {
|
||||
return errorf (RESULT_ERROR_INVALID_ARGUMENT, "Usage: libs <input>");
|
||||
}
|
||||
HBC *hbc;
|
||||
RETURN_IF_ERROR (open_hbc (argv[0], &hbc));
|
||||
HBCModules modules = { 0 };
|
||||
Result r = hbc_list_modules (hbc, &modules);
|
||||
if (r.code != RESULT_SUCCESS) {
|
||||
hbc_close (hbc);
|
||||
return r;
|
||||
}
|
||||
if (ctx->flags.json) {
|
||||
putchar ('[');
|
||||
for (u32 i = 0; i < modules.count; i++) {
|
||||
HBCModule *m = &modules.modules[i];
|
||||
printf ("%s{\"kind\":", i? ",": "");
|
||||
json_print_string (m->kind);
|
||||
printf (",\"name\":");
|
||||
json_print_string (m->name);
|
||||
printf (",\"path\":");
|
||||
json_print_nullable_string (m->path);
|
||||
printf (",\"version\":");
|
||||
json_print_nullable_string (m->version);
|
||||
printf (",\"function_id\":%u,\"offset\":%u,\"string_id\":%u,\"inferred\":%s}",
|
||||
m->function_id,
|
||||
m->offset,
|
||||
m->string_id,
|
||||
m->inferred? "true": "false");
|
||||
}
|
||||
puts ("]");
|
||||
} else {
|
||||
printf ("libs: %u\n", modules.count);
|
||||
for (u32 i = 0; i < modules.count; i++) {
|
||||
HBCModule *m = &modules.modules[i];
|
||||
printf ("%-8s sid=%-5u fn=%-5u off=0x%08x %-30s",
|
||||
m->kind? m->kind: "",
|
||||
m->string_id,
|
||||
m->function_id,
|
||||
m->offset,
|
||||
m->name? m->name: "");
|
||||
if (m->version && *m->version) {
|
||||
printf (" version=%s", m->version);
|
||||
}
|
||||
if (m->path && *m->path && (!m->name || strcmp (m->path, m->name))) {
|
||||
printf (" path=%s", m->path);
|
||||
}
|
||||
if (m->inferred) {
|
||||
printf (" inferred");
|
||||
}
|
||||
putchar ('\n');
|
||||
}
|
||||
}
|
||||
hbc_free_modules (&modules);
|
||||
hbc_close (hbc);
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
static const Command commands[] = {
|
||||
{ "d", "Disassemble a Hermes bytecode file", cmd_d },
|
||||
{ "c", "Decompile a Hermes bytecode file", cmd_c },
|
||||
{ "dis", "Disassemble raw hex bytes (rasm2-like)", cmd_dis },
|
||||
{ "a", "Assemble a single instruction", cmd_a },
|
||||
{ "asm", "Assemble instructions from file", cmd_asm },
|
||||
{ "h", "Display header information", cmd_h },
|
||||
{ "v", "Validate file and show details", cmd_v },
|
||||
{ "r", "Generate an r2 script with function flags",cmd_r },
|
||||
{ "f", "Dump first N function headers", cmd_f },
|
||||
{ "sl", "List source-line information", cmd_sl },
|
||||
{ "cmp", "Compare first N funcs with parser.txt", cmd_cmp },
|
||||
{ "cf", "Compare one function vs Python disasm", cmd_cf },
|
||||
{ "s", "Print a string by index", cmd_s },
|
||||
{ "fs", "Find strings by substring", cmd_fs },
|
||||
{ "sm", "Show string entry metadata", cmd_sm },
|
||||
{ "d", "Disassemble a Hermes bytecode file", cmd_d },
|
||||
{ "c", "Decompile a Hermes bytecode file", cmd_c },
|
||||
{ "dis", "Disassemble raw hex bytes (rasm2-like)", cmd_dis },
|
||||
{ "a", "Assemble a single instruction", cmd_a },
|
||||
{ "asm", "Assemble instructions from file", cmd_asm },
|
||||
{ "h", "Display header information", cmd_h },
|
||||
{ "v", "Validate file and show details", cmd_v },
|
||||
{ "r", "Generate an r2 script with function flags", cmd_r },
|
||||
{ "f", "Dump first N function headers", cmd_f },
|
||||
{ "sl", "List source-line information", cmd_sl },
|
||||
{ "cmp", "Compare first N funcs with parser.txt", cmd_cmp },
|
||||
{ "cf", "Compare one function vs Python disasm", cmd_cf },
|
||||
{ "s", "Print a string by index", cmd_s },
|
||||
{ "fs", "Find strings by substring", cmd_fs },
|
||||
{ "sm", "Show string entry metadata", cmd_sm },
|
||||
{ "lit", "SLP buffer literals: list, get, pool, xrefs", cmd_lit },
|
||||
{ "bind", "List probable imports/exports/native bindings", cmd_bind },
|
||||
{ "libs", "List probable bundled modules/libraries", cmd_libs },
|
||||
};
|
||||
#define COMMANDS_N (sizeof (commands) / sizeof (commands[0]))
|
||||
|
||||
@@ -954,7 +1115,7 @@ static void print_usage(const char *program_name) {
|
||||
printf (" %-5s %s\n", commands[i].name, commands[i].help);
|
||||
}
|
||||
printf ("\nOptions (may appear anywhere after the command):\n");
|
||||
printf (" --json, -j JSON output (lit list)\n");
|
||||
printf (" --json, -j JSON output (lit list, bind, libs)\n");
|
||||
printf (" --asmsyntax CPU-like asm syntax (dis)\n");
|
||||
printf (" --verbose, -v Verbose output\n");
|
||||
printf (" --bytecode, -b Include bytecode bytes\n");
|
||||
|
||||
+235
-14
@@ -18,30 +18,40 @@ EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hi file info small
|
||||
NAME=r2hermes-? help eval command
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
pd:hi~Version
|
||||
r2hermes-?~r2hermes-E
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
r2hermes-E - List functions containing eval instructions
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=r2hermes-H file info small
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
r2hermes-H~Version
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
Version: 96
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hi file length small
|
||||
NAME=r2hermes-H file length small
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
pd:hi~File Length
|
||||
r2hermes-H~File Length
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
File Length: 292 bytes
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hi function count small
|
||||
NAME=r2hermes-H function count small
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
pd:hi~Functions
|
||||
r2hermes-H~Functions
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
Functions: 1
|
||||
@@ -49,10 +59,10 @@ EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hi string count small
|
||||
NAME=r2hermes-H string count small
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
pd:hi~Strings
|
||||
r2hermes-H~Strings
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
Strings: 2
|
||||
@@ -60,10 +70,10 @@ EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hi file info large
|
||||
NAME=r2hermes-H file info large
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hi~Functions
|
||||
r2hermes-H~Functions
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
Functions: 5388
|
||||
@@ -71,10 +81,10 @@ EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hi strings large
|
||||
NAME=r2hermes-H strings large
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hi~Strings
|
||||
r2hermes-H~Strings
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
Strings: 6170
|
||||
@@ -82,10 +92,10 @@ EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hi identifiers large
|
||||
NAME=r2hermes-H identifiers large
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hi~Identifiers
|
||||
r2hermes-H~Identifiers
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
Identifiers: 3449
|
||||
@@ -126,6 +136,26 @@ EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=r2hermes-E list eval sites
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
r2hermes-E
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x100000b4 f=0 +0x4 func=0x100000b0 name=global
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=r2hermes-E list eval sites large
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
r2hermes-E
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x100d166b f=4953 +0x28 func=0x100d1643 name=onPress
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hc decompile function return
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
@@ -214,3 +244,194 @@ EXPECT=<<EOF
|
||||
r0 = eval (r0);
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hc for-in loop structuring
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hc 6~for (,continue
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
for (r6 in r5)
|
||||
if (!r11) continue;
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hc for-in no back-edge goto or loop-top label
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hc 6~for (r6 in,goto loc_0003e68d,loc_0003e68d:
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
for (r6 in r5)
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hc switch fallthrough case grouping
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hc 562~case 26,case 27
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
case 26:
|
||||
case 27: goto loc_0004d144;
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hc comparison condition goto-form no double parens
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hc 5~if (r3!==r1)
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
if (r3!==r1) goto loc_0003e637;
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hc comparison condition structured-if negated operator
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hc 46~if (r5!==r0)
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
if (r5!==r0) {
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hc if-blocks structured in catch-containing function
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hc 12~if (r7) {
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
if (r7) {
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hc structured try/catch for return-terminated try
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hc 189~try {,} catch (r0) {
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
try {
|
||||
} catch (r0) {
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hc multiple independent try/catch blocks structured
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hc 344~} catch
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
} catch (r0) {
|
||||
} catch (r1) {
|
||||
} catch (r1) {
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hc do-while loop structuring with no back-edge goto or loop-top label
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hc 513~do {,while (r1,goto
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
do {
|
||||
} while (r1>r2);
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hc materialized guard folds into a while loop
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hc 199~while (r2,do {,goto
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
while (r2<r1) {
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hc new Array call has no stray space after the parenthesis
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hc 513~new Array(0
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
r0 = new Array(0);
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hc select_object args have single spacing
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hc 310~select_object(r
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
r2 = select_object(r3, r2);
|
||||
r1 = select_object(r2, r1);
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hc forward substitution folds single-use temp into call
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hc 199~.call (undefined
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
r0 = r2 .call (undefined, r1);
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hc if-else reconstruction with no trailing goto or join label
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hc 311~if (!r0) {,} else {,goto,loc_
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
if (!r0) {
|
||||
} else {
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hc short-circuit OR merges consecutive same-target conditional gotos
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hc 730~ || r2===r20 ||
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
if (r1===r20 || r2===r20 || r3===r20 || r4===r20) goto loc_00055f28;
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hc while loop folds entry guard and do-while into a while header
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hc 2303~while (r2,do {,goto
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
while (r2!=r1) {
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hc infinite loop structures as for(;;) with continue and break
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hc 3896~for (,continue,break
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
for (;;) {
|
||||
continue;
|
||||
break;
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hc break reconstruction renders loop-exit goto as break
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hc 3406~while (r0,break,goto loc
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
while (r0<r2) {
|
||||
if (r6>=r5) break;
|
||||
EOF
|
||||
RUN
|
||||
@@ -58,7 +58,7 @@ CMDS=<<EOF
|
||||
i~cpu
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
cpu 96
|
||||
cpu v96
|
||||
EOF
|
||||
RUN
|
||||
|
||||
@@ -719,6 +719,30 @@ mov r2, r1
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc pad respects asm.cpu v99
|
||||
FILE=--
|
||||
CMDS=<<EOF
|
||||
-a hbc
|
||||
e asm.cpu=v99
|
||||
pad 010203040506
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
new_object_with_buffer r2, 1027, 1541
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc pa respects asm.cpu v99
|
||||
FILE=--
|
||||
CMDS=<<EOF
|
||||
-a hbc
|
||||
e asm.cpu=v99
|
||||
pa new_object_with_buffer r2, 1027, 1541
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
010203040506
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc pad construct
|
||||
FILE=--
|
||||
CMDS=<<EOF
|
||||
|
||||
@@ -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
|
||||
@@ -1,3 +1,5 @@
|
||||
/* radare2 - BSD - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <hbc/hbc.h>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
Reference in new issue
Block a user