mirror of
https://github.com/radareorg/r2hermes.git
synced 2026-11-08 17:42:33 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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@v4
|
||||
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
|
||||
|
||||
+1
-2
@@ -1,5 +1,5 @@
|
||||
build/
|
||||
bin/libhbctool
|
||||
bin/
|
||||
third_party/
|
||||
*.d
|
||||
*.o
|
||||
@@ -8,4 +8,3 @@ third_party/
|
||||
test/bins
|
||||
*.swp
|
||||
include/hbc/version.h
|
||||
./bin
|
||||
@@ -20,6 +20,7 @@ OPCODES_SRC = $(filter-out $(SRC_DIR)/opcodes/%.inc.c,$(wildcard $(SRC_DIR)/opco
|
||||
LIB_SRC = $(UTILS_SRC) $(PARSERS_SRC) $(DISASM_SRC) $(DECOMPILE_SRC) $(OPCODES_SRC) \
|
||||
$(SRC_DIR)/hbc.c $(SRC_DIR)/literals_api.c $(SRC_DIR)/opcodes/encoder.c $(SRC_DIR)/opcodes/decoder.c $(SRC_DIR)/r2.c
|
||||
MAIN_SRC = src/tool/libhbctool.c
|
||||
HBC_HEADERS = $(shell find include -name '*.h')
|
||||
|
||||
## Object files
|
||||
LIB_OBJ = $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/%.o,$(LIB_SRC))
|
||||
@@ -55,11 +56,11 @@ $(STATIC_LIB): $(LIB_OBJ)
|
||||
$(BIN_FILE): $(STATIC_LIB) $(MAIN_OBJ) | $(shell mkdir -p $(BIN_DIR))
|
||||
$(CC) $(CFLAGS) -o $@ $(MAIN_OBJ) -L$(BUILD_DIR) -lhbc
|
||||
|
||||
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c | $(VH)
|
||||
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c $(HBC_HEADERS) | $(VH)
|
||||
@mkdir -p $(dir $@)
|
||||
$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@
|
||||
|
||||
$(MAIN_OBJ): $(MAIN_SRC) | $(VH)
|
||||
$(MAIN_OBJ): $(MAIN_SRC) $(HBC_HEADERS) | $(VH)
|
||||
$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@
|
||||
|
||||
clean:
|
||||
@@ -72,6 +73,15 @@ TEST_DIR = tests
|
||||
TEST_SRC = $(wildcard $(TEST_DIR)/*.c)
|
||||
TEST_BIN = $(BIN_DIR)/run_tests
|
||||
|
||||
$(TEST_BIN): $(STATIC_LIB) $(TEST_SRC) | $(shell mkdir -p $(BIN_DIR))
|
||||
$(CC) $(CFLAGS) $(INCLUDES) -o $@ $(TEST_SRC) -L$(BUILD_DIR) -lhbc
|
||||
|
||||
test: $(BIN_FILE) $(TEST_BIN)
|
||||
$(TEST_BIN)
|
||||
$(BIN_FILE) sl test/bins/hbc/bespoke_eval.hbc | grep -q '0x000000b4 f=0 +0x4 yes.js:1:5 stmt=0'
|
||||
$(BIN_FILE) h test/bins/hbc/bespoke_eval.hbc | grep -q 'Source Locations: 13 bytes'
|
||||
$(BIN_FILE) h test/bins/hbc/index.android.bundle | grep -q 'Source Locations: 0 bytes'
|
||||
$(BIN_FILE) libs test/bins/hbc/index.android.bundle | grep -q 'react-native'
|
||||
|
||||
r2:
|
||||
$(MAKE) clean
|
||||
|
||||
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
|
||||
@@ -63,6 +63,34 @@ typedef struct {
|
||||
u32 param_count; // Number of parameters
|
||||
} HBCFunc;
|
||||
|
||||
typedef struct {
|
||||
u32 function_id;
|
||||
u32 address;
|
||||
u32 function_address;
|
||||
u32 line;
|
||||
u32 column;
|
||||
u32 statement;
|
||||
const char *filename;
|
||||
} HBCSourceLine;
|
||||
|
||||
typedef struct {
|
||||
HBCSourceLine *lines;
|
||||
u32 count;
|
||||
} HBCSourceLineArray;
|
||||
|
||||
typedef struct {
|
||||
bool has_debug_info;
|
||||
u32 filename_count;
|
||||
u32 filename_storage_size;
|
||||
u32 file_region_count;
|
||||
u32 source_locations_size;
|
||||
u32 scope_desc_data_size;
|
||||
u32 textified_data_size;
|
||||
u32 string_table_size;
|
||||
u32 debug_data_size;
|
||||
u32 functions_with_debug_info;
|
||||
} HBCDebugInfo;
|
||||
|
||||
/* String table data */
|
||||
typedef struct {
|
||||
u32 string_count;
|
||||
@@ -220,6 +248,14 @@ Result hbc_get_string_tables(HBC *hbc, HBCStrs *out);
|
||||
*/
|
||||
Result hbc_get_function_source(HBC *hbc, u32 function_id, const char **out_src);
|
||||
|
||||
/**
|
||||
* Get decoded source-line records from debug information.
|
||||
*/
|
||||
Result hbc_get_source_lines(HBC *hbc, HBCSourceLineArray *out);
|
||||
void hbc_free_source_lines(HBCSourceLineArray *arr);
|
||||
bool hbc_has_source_lines(HBC *hbc);
|
||||
Result hbc_get_debug_info(HBC *hbc, HBCDebugInfo *out);
|
||||
|
||||
/**
|
||||
* Get the raw bytecode bytes for a function.
|
||||
*/
|
||||
@@ -308,6 +344,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.
|
||||
*/
|
||||
@@ -321,6 +373,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
|
||||
*/
|
||||
|
||||
+12
-1
@@ -263,11 +263,21 @@ struct HBCReader {
|
||||
FunctionSourceEntry *function_sources;
|
||||
size_t function_source_count;
|
||||
|
||||
/* Debug info */
|
||||
/* Debug info — split into two phases:
|
||||
* debug_info_header_loaded: cheap parse of the DebugInfoHeader fields
|
||||
* only (a handful of u32s), populated eagerly at load time so callers
|
||||
* can report accurate "has source lines" / "stripped" without paying
|
||||
* for the full parse.
|
||||
* debug_info_loaded: full parse of filenames, file regions, and the
|
||||
* sources / scope / textified / string-table storage blobs. Populated
|
||||
* lazily on first hbc_get_source_lines / hbc_get_debug_info call. */
|
||||
bool debug_info_header_loaded;
|
||||
bool debug_info_loaded;
|
||||
DebugInfoHeader debug_info_header;
|
||||
OffsetLengthPair *debug_string_table;
|
||||
u8 *debug_string_storage;
|
||||
size_t debug_string_storage_size;
|
||||
char **debug_filenames;
|
||||
DebugFileRegion *debug_file_regions;
|
||||
size_t debug_file_region_count;
|
||||
u8 *sources_data_storage;
|
||||
@@ -298,6 +308,7 @@ Result _hbc_reader_read_bigints(HBCReader *reader);
|
||||
Result _hbc_reader_read_regexp(HBCReader *reader);
|
||||
Result _hbc_reader_read_cjs_modules(HBCReader *reader);
|
||||
Result _hbc_reader_read_function_sources(HBCReader *reader);
|
||||
Result _hbc_reader_read_debug_info_header(HBCReader *reader);
|
||||
Result _hbc_reader_read_debug_info(HBCReader *reader);
|
||||
Result _hbc_reader_read_whole_file(HBCReader *reader, const char *filename);
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
project(
|
||||
'r2hermes',
|
||||
'c',
|
||||
version: '1.2.6',
|
||||
version: '1.3.0',
|
||||
license: 'MIT',
|
||||
default_options: [
|
||||
'c_std=c11',
|
||||
|
||||
+486
-55
@@ -118,7 +118,16 @@ Result hbc_open_from_memory(const u8 *data, size_t size, HBC **out) {
|
||||
hbc_close (hbc);
|
||||
return res;
|
||||
}
|
||||
(void)_hbc_reader_read_debug_info;
|
||||
/* Read only the DebugInfoHeader eagerly — a handful of u32s — so
|
||||
* hbc_has_source_lines() can answer accurately without committing to the
|
||||
* full body parse. The filename, file region and sources/scope/textified
|
||||
* storage blobs are read lazily on first hbc_get_source_lines /
|
||||
* hbc_get_debug_info call. */
|
||||
res = _hbc_reader_read_debug_info_header (&hbc->reader);
|
||||
if (res.code != RESULT_SUCCESS) {
|
||||
hbc_close (hbc);
|
||||
return res;
|
||||
}
|
||||
|
||||
*out = hbc;
|
||||
return SUCCESS_RESULT ();
|
||||
@@ -239,10 +248,238 @@ Result hbc_get_function_source(HBC *hbc, u32 function_id, const char **out_src)
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Function ID out of range");
|
||||
}
|
||||
*out_src = NULL;
|
||||
if (hbc->reader.function_sources && function_id < hbc->reader.header.functionSourceCount) {
|
||||
u32 string_id = hbc->reader.function_sources[function_id].string_id;
|
||||
if (string_id < hbc->reader.header.stringCount) {
|
||||
*out_src = hbc->reader.strings[string_id];
|
||||
if (hbc->reader.function_sources) {
|
||||
for (u32 i = 0; i < hbc->reader.header.functionSourceCount; i++) {
|
||||
if (hbc->reader.function_sources[i].function_id != function_id) {
|
||||
continue;
|
||||
}
|
||||
u32 string_id = hbc->reader.function_sources[i].string_id;
|
||||
if (string_id < hbc->reader.header.stringCount) {
|
||||
*out_src = hbc->reader.strings[string_id];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
static bool read_sleb128(const u8 *data, size_t size, size_t *pos, int64_t *out) {
|
||||
uint64_t result = 0;
|
||||
unsigned shift = 0;
|
||||
u8 byte = 0;
|
||||
|
||||
do {
|
||||
if (*pos >= size || shift > 63) {
|
||||
return false;
|
||||
}
|
||||
byte = data[(*pos)++];
|
||||
if (shift == 63 && (byte & 0x7e)) {
|
||||
return false;
|
||||
}
|
||||
result |= ((uint64_t)(byte & 0x7f)) << shift;
|
||||
shift += 7;
|
||||
} while (byte & 0x80);
|
||||
|
||||
if ((shift < 64) && (byte & 0x40)) {
|
||||
result |= (~0ULL) << shift;
|
||||
}
|
||||
*out = (int64_t)result;
|
||||
return true;
|
||||
}
|
||||
|
||||
static const char *debug_filename_at(HBCReader *reader, u32 index) {
|
||||
const u32 count = reader->debug_info_header.filename_count;
|
||||
if (index >= count || !reader->debug_string_table || !reader->debug_string_storage) {
|
||||
return NULL;
|
||||
}
|
||||
if (!reader->debug_filenames) {
|
||||
reader->debug_filenames = (char **)calloc (count, sizeof (char *));
|
||||
if (!reader->debug_filenames) {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
if (!reader->debug_filenames[index]) {
|
||||
const OffsetLengthPair *entry = &reader->debug_string_table[index];
|
||||
const size_t storage_size = reader->debug_string_storage_size;
|
||||
if ((size_t)entry->offset > storage_size || (size_t)entry->length > storage_size - entry->offset) {
|
||||
return NULL;
|
||||
}
|
||||
char *filename = (char *)malloc ((size_t)entry->length + 1);
|
||||
if (!filename) {
|
||||
return NULL;
|
||||
}
|
||||
memcpy (filename, reader->debug_string_storage + entry->offset, entry->length);
|
||||
filename[entry->length] = '\0';
|
||||
reader->debug_filenames[index] = filename;
|
||||
}
|
||||
return reader->debug_filenames[index];
|
||||
}
|
||||
|
||||
static const char *debug_filename_for_source_offset(HBCReader *reader, u32 source_offset) {
|
||||
const DebugFileRegion *best = NULL;
|
||||
for (u32 i = 0; i < reader->debug_file_region_count; i++) {
|
||||
const DebugFileRegion *region = &reader->debug_file_regions[i];
|
||||
if (region->from_address <= source_offset && (!best || region->from_address >= best->from_address)) {
|
||||
best = region;
|
||||
}
|
||||
}
|
||||
return best? debug_filename_at (reader, best->filename_id): NULL;
|
||||
}
|
||||
|
||||
static Result append_source_line(HBCSourceLineArray *arr, u32 *cap, const HBCSourceLine *line) {
|
||||
if (arr->count >= *cap) {
|
||||
u32 next = *cap? (*cap * 2): 64;
|
||||
HBCSourceLine *lines = (HBCSourceLine *)realloc (arr->lines, (size_t)next * sizeof (HBCSourceLine));
|
||||
if (!lines) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "Failed to allocate source lines");
|
||||
}
|
||||
arr->lines = lines;
|
||||
*cap = next;
|
||||
}
|
||||
arr->lines[arr->count++] = *line;
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
#define PARSE_FAIL(msg) do { res = ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, msg); goto fail; } while (0)
|
||||
|
||||
Result hbc_get_source_lines(HBC *hbc, HBCSourceLineArray *out) {
|
||||
if (!hbc || !out) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Invalid arguments");
|
||||
}
|
||||
memset (out, 0, sizeof (*out));
|
||||
HBCReader *reader = &hbc->reader;
|
||||
RETURN_IF_ERROR (_hbc_reader_read_debug_info (reader));
|
||||
const u8 *data = reader->sources_data_storage;
|
||||
const size_t size = reader->sources_data_storage_size;
|
||||
if (!data || !size) {
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
Result res = SUCCESS_RESULT ();
|
||||
u32 cap = 0;
|
||||
size_t pos = 0;
|
||||
while (pos < size) {
|
||||
const size_t source_offset = pos;
|
||||
int64_t function_id = 0, line = 0, column = 0;
|
||||
if (source_offset > UINT32_MAX
|
||||
|| !read_sleb128 (data, size, &pos, &function_id)
|
||||
|| !read_sleb128 (data, size, &pos, &line)
|
||||
|| !read_sleb128 (data, size, &pos, &column)) {
|
||||
PARSE_FAIL ("Invalid source location header");
|
||||
}
|
||||
if (function_id < 0 || function_id >= (int64_t)reader->header.functionCount
|
||||
|| line < 0 || line > UINT32_MAX || column < 0 || column > UINT32_MAX) {
|
||||
PARSE_FAIL ("Source location header out of range");
|
||||
}
|
||||
|
||||
HBCFunc fi = { 0 };
|
||||
(void)hbc_get_function_info (hbc, (u32)function_id, &fi);
|
||||
HBCSourceLine sl = {
|
||||
.function_id = (u32)function_id,
|
||||
.address = fi.offset,
|
||||
.line = (u32)line,
|
||||
.column = (u32)column,
|
||||
.filename = debug_filename_for_source_offset (reader, (u32)source_offset)
|
||||
};
|
||||
res = append_source_line (out, &cap, &sl);
|
||||
if (res.code != RESULT_SUCCESS) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
int64_t address = 0, statement = 0;
|
||||
for (;;) {
|
||||
int64_t adelta = 0;
|
||||
if (!read_sleb128 (data, size, &pos, &adelta)) {
|
||||
PARSE_FAIL ("Invalid source location address delta");
|
||||
}
|
||||
if (adelta == -1) {
|
||||
break;
|
||||
}
|
||||
int64_t ldelta = 0, cdelta = 0, scope = 0, env = 0, sdelta = 0;
|
||||
if (adelta < 0
|
||||
|| !read_sleb128 (data, size, &pos, &ldelta)
|
||||
|| !read_sleb128 (data, size, &pos, &cdelta)
|
||||
|| !read_sleb128 (data, size, &pos, &scope)
|
||||
|| !read_sleb128 (data, size, &pos, &env)) {
|
||||
PARSE_FAIL ("Invalid source location entry");
|
||||
}
|
||||
if ((ldelta & 1) && !read_sleb128 (data, size, &pos, &sdelta)) {
|
||||
PARSE_FAIL ("Invalid source location statement delta");
|
||||
}
|
||||
ldelta = (ldelta - (ldelta & 1)) / 2;
|
||||
address += adelta;
|
||||
line += ldelta;
|
||||
column += cdelta;
|
||||
statement += sdelta;
|
||||
if (address < 0 || address > UINT32_MAX || (uint64_t)fi.offset + (uint64_t)address > UINT32_MAX
|
||||
|| line < 0 || line > UINT32_MAX || column < 0 || column > UINT32_MAX
|
||||
|| statement < 0 || statement > UINT32_MAX) {
|
||||
PARSE_FAIL ("Source location entry out of range");
|
||||
}
|
||||
sl.address = fi.offset + (u32)address;
|
||||
sl.function_address = (u32)address;
|
||||
sl.line = (u32)line;
|
||||
sl.column = (u32)column;
|
||||
sl.statement = (u32)statement;
|
||||
res = append_source_line (out, &cap, &sl);
|
||||
if (res.code != RESULT_SUCCESS) {
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
}
|
||||
return SUCCESS_RESULT ();
|
||||
fail:
|
||||
hbc_free_source_lines (out);
|
||||
return res;
|
||||
}
|
||||
|
||||
#undef PARSE_FAIL
|
||||
|
||||
void hbc_free_source_lines(HBCSourceLineArray *arr) {
|
||||
if (!arr) {
|
||||
return;
|
||||
}
|
||||
free (arr->lines);
|
||||
memset (arr, 0, sizeof (*arr));
|
||||
}
|
||||
|
||||
/* Describes the file, not the runtime preference: true iff the file carries
|
||||
* a non-empty source-locations blob. Relies on the DebugInfoHeader parsed at
|
||||
* load time, so callers pay no body-parse cost for this query. */
|
||||
bool hbc_has_source_lines(HBC *hbc) {
|
||||
if (!hbc || hbc->reader.header.debugInfoOffset == 0) {
|
||||
return false;
|
||||
}
|
||||
if (_hbc_reader_read_debug_info_header (&hbc->reader).code != RESULT_SUCCESS) {
|
||||
return false;
|
||||
}
|
||||
return hbc->reader.debug_info_header.scope_desc_data_offset > 0;
|
||||
}
|
||||
|
||||
Result hbc_get_debug_info(HBC *hbc, HBCDebugInfo *out) {
|
||||
if (!hbc || !out) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Invalid arguments");
|
||||
}
|
||||
memset (out, 0, sizeof (*out));
|
||||
HBCReader *reader = &hbc->reader;
|
||||
out->has_debug_info = reader->header.debugInfoOffset != 0;
|
||||
if (!out->has_debug_info) {
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
RETURN_IF_ERROR (_hbc_reader_read_debug_info (reader));
|
||||
out->filename_count = reader->debug_info_header.filename_count;
|
||||
out->filename_storage_size = reader->debug_info_header.filename_storage_size;
|
||||
out->file_region_count = reader->debug_info_header.file_region_count;
|
||||
out->source_locations_size = (u32)reader->sources_data_storage_size;
|
||||
out->scope_desc_data_size = (u32)reader->scope_desc_data_storage_size;
|
||||
out->textified_data_size = (u32)reader->textified_data_storage_size;
|
||||
out->string_table_size = (u32)reader->string_table_storage_size;
|
||||
out->debug_data_size = reader->debug_info_header.debug_data_size;
|
||||
if (reader->function_headers) {
|
||||
for (u32 i = 0; i < reader->header.functionCount; i++) {
|
||||
if (reader->function_headers[i].hasDebugInfo) {
|
||||
out->functions_with_debug_info++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return SUCCESS_RESULT ();
|
||||
@@ -626,12 +863,10 @@ static const BindingHint g_hints[] = {
|
||||
};
|
||||
#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");
|
||||
@@ -643,13 +878,6 @@ static bool is_export_store(const char *name) {
|
||||
|
||||
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 ();
|
||||
}
|
||||
}
|
||||
HBCBinding *nb = realloc (out->bindings, (out->count + 1) * sizeof (HBCBinding));
|
||||
if (!nb) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "OOM");
|
||||
@@ -712,7 +940,11 @@ 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):
|
||||
@@ -720,37 +952,27 @@ static Result scan_hint_matches(HBCBindings *out, const char *s, u32 fid, u32 of
|
||||
if (!match) {
|
||||
continue;
|
||||
}
|
||||
bool dup = false;
|
||||
for (u32 a = 0; a < added_n; a += 2) {
|
||||
if (added[a] == hint->kind && added[a + 1] == hint->module) {
|
||||
dup = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (dup) {
|
||||
continue;
|
||||
}
|
||||
added[added_n++] = hint->kind;
|
||||
added[added_n++] = hint->module;
|
||||
if (hint->type == HBC_BINDING_EXPORT) {
|
||||
*flag |= SEEN_EXP;
|
||||
}
|
||||
RETURN_IF_ERROR (binding_add (out, hint->type, hint->kind, s,
|
||||
hint->module, fid, off, sid));
|
||||
}
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
pc += inst->binary_size;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Result hbc_scan_bindings(HBC *hbc, HBCBindings *out) {
|
||||
if (!hbc || !out) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "invalid args");
|
||||
@@ -761,16 +983,29 @@ 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;
|
||||
@@ -780,7 +1015,8 @@ Result hbc_scan_bindings(HBC *hbc, HBCBindings *out) {
|
||||
}
|
||||
HBCFunc fi = { 0 };
|
||||
(void)hbc_get_function_info (hbc, fid, &fi);
|
||||
bool has_export_marker = function_has_export_marker (r, code, size, isa);
|
||||
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;
|
||||
@@ -791,25 +1027,72 @@ 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) {
|
||||
@@ -825,6 +1108,154 @@ 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(
|
||||
|
||||
@@ -81,6 +81,12 @@ Result _hbc_reader_cleanup(HBCReader *reader) {
|
||||
/* Clean up debug info */
|
||||
free (reader->debug_string_table);
|
||||
free (reader->debug_string_storage);
|
||||
if (reader->debug_filenames) {
|
||||
for (u32 i = 0; i < reader->debug_info_header.filename_count; i++) {
|
||||
free (reader->debug_filenames[i]);
|
||||
}
|
||||
free (reader->debug_filenames);
|
||||
}
|
||||
free (reader->debug_file_regions);
|
||||
free (reader->sources_data_storage);
|
||||
free (reader->scope_desc_data_storage);
|
||||
@@ -1578,22 +1584,31 @@ Result _hbc_reader_read_function_sources(HBCReader *reader) {
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
/* Read debug information */
|
||||
Result _hbc_reader_read_debug_info(HBCReader *reader) {
|
||||
/* Size of the on-disk DebugInfoHeader block, in bytes. */
|
||||
static u32 debug_info_header_bytes(u32 version) {
|
||||
/* v<91: 5 u32s (filename_count, filename_storage_size, file_region_count,
|
||||
* scope_desc_data_offset, debug_data_size).
|
||||
* v>=91: 7 u32s (adds textified_data_offset, string_table_offset). */
|
||||
return (version >= 91? 7u: 5u) * sizeof (u32);
|
||||
}
|
||||
|
||||
/* Read just the DebugInfoHeader fields (no storage blobs, no allocations).
|
||||
* Cheap enough to run eagerly at load time so "has source lines" is knowable
|
||||
* without committing to a full debug-info parse. Idempotent. */
|
||||
Result _hbc_reader_read_debug_info_header(HBCReader *reader) {
|
||||
if (!reader) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Reader is NULL");
|
||||
}
|
||||
if (reader->header.debugInfoOffset == 0) {
|
||||
if (reader->debug_info_header_loaded) {
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
if (reader->header.version >= 97) {
|
||||
if (reader->header.debugInfoOffset == 0) {
|
||||
reader->debug_info_header_loaded = true;
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
/* Seek to debug info offset */
|
||||
RETURN_IF_ERROR (_hbc_buffer_reader_seek (&reader->file_buffer, reader->header.debugInfoOffset));
|
||||
|
||||
/* Read debug info header */
|
||||
RETURN_IF_ERROR (_hbc_buffer_reader_read_u32 (&reader->file_buffer, &reader->debug_info_header.filename_count));
|
||||
RETURN_IF_ERROR (_hbc_buffer_reader_read_u32 (&reader->file_buffer, &reader->debug_info_header.filename_storage_size));
|
||||
RETURN_IF_ERROR (_hbc_buffer_reader_read_u32 (&reader->file_buffer, &reader->debug_info_header.file_region_count));
|
||||
@@ -1606,6 +1621,30 @@ Result _hbc_reader_read_debug_info(HBCReader *reader) {
|
||||
|
||||
RETURN_IF_ERROR (_hbc_buffer_reader_read_u32 (&reader->file_buffer, &reader->debug_info_header.debug_data_size));
|
||||
|
||||
reader->debug_info_header_loaded = true;
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
/* Read the full debug-info body (filenames, file regions, sources/scope/
|
||||
* textified/string-table storage blobs). Builds on the cheap header parse.
|
||||
* Idempotent: subsequent calls after a successful load are no-ops. */
|
||||
Result _hbc_reader_read_debug_info(HBCReader *reader) {
|
||||
if (!reader) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Reader is NULL");
|
||||
}
|
||||
if (reader->debug_info_loaded) {
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
RETURN_IF_ERROR (_hbc_reader_read_debug_info_header (reader));
|
||||
if (reader->header.debugInfoOffset == 0) {
|
||||
reader->debug_info_loaded = true;
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
/* Seek past the already-parsed header to the start of the body. */
|
||||
RETURN_IF_ERROR (_hbc_buffer_reader_seek (&reader->file_buffer,
|
||||
reader->header.debugInfoOffset + debug_info_header_bytes (reader->header.version)));
|
||||
|
||||
/* Allocate debug string table */
|
||||
if (reader->debug_info_header.filename_count > 0) {
|
||||
reader->debug_string_table = (OffsetLengthPair *)malloc (
|
||||
@@ -1707,6 +1746,7 @@ Result _hbc_reader_read_debug_info(HBCReader *reader) {
|
||||
reader->string_table_storage_size = string_table_size;
|
||||
}
|
||||
|
||||
reader->debug_info_loaded = true;
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
@@ -2029,10 +2069,11 @@ Result _hbc_reader_read_whole_file(HBCReader *reader, const char *filename) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Read debug info */
|
||||
result = _hbc_reader_read_debug_info (reader);
|
||||
/* Read only the DebugInfoHeader eagerly (cheap). The body is parsed lazily
|
||||
* via _hbc_reader_read_debug_info() on first API use. */
|
||||
result = _hbc_reader_read_debug_info_header (reader);
|
||||
if (result.code != RESULT_SUCCESS) {
|
||||
fprintf (stderr, "Error reading debug info: %s\n", result.error_message);
|
||||
fprintf (stderr, "Error reading debug info header: %s\n", result.error_message);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
+6
-5
@@ -33,6 +33,7 @@ BIN_SRC = bin_hbc.c
|
||||
ASM_SRC = asm_hbc.c
|
||||
CORE_SRC = core_hbc.c
|
||||
CORE_ONE_SRC = core_hbc_one.c
|
||||
HBC_HEADERS = $(shell find ../../include -name '*.h') utils.inc.c
|
||||
|
||||
# Object files
|
||||
ARCH_OBJ = arch_hbc.o
|
||||
@@ -107,19 +108,19 @@ $(CORE_ONE_PLUGIN): $(CORE_ONE_OBJ) $(HERMES_LIB)
|
||||
$(CC) $(LDFLAGS) -o $@ $(CORE_ONE_OBJ) $(HERMES_LIB)
|
||||
|
||||
# Compile object files
|
||||
$(ARCH_OBJ): $(ARCH_SRC)
|
||||
$(ARCH_OBJ): $(ARCH_SRC) $(HBC_HEADERS)
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BIN_OBJ): $(BIN_SRC)
|
||||
$(BIN_OBJ): $(BIN_SRC) $(HBC_HEADERS)
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(ASM_OBJ): $(ASM_SRC)
|
||||
$(ASM_OBJ): $(ASM_SRC) $(HBC_HEADERS)
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(CORE_OBJ): $(CORE_SRC)
|
||||
$(CORE_OBJ): $(CORE_SRC) $(HBC_HEADERS)
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(CORE_ONE_OBJ): $(CORE_ONE_SRC)
|
||||
$(CORE_ONE_OBJ): $(CORE_ONE_SRC) $(HBC_HEADERS)
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
|
||||
|
||||
+455
-16
@@ -1,6 +1,7 @@
|
||||
/* radare2 - LGPL - Copyright 2025-2026 - pancake */
|
||||
|
||||
#include <r_bin.h>
|
||||
#include <r_bin_dwarf.h>
|
||||
#include <hbc/hbc.h>
|
||||
#include <hbc/literals.h>
|
||||
#include "utils.inc.c"
|
||||
@@ -132,7 +133,7 @@ static ut64 resolve_entrypoint(RBinFile *bf, HBC *hbc) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void fill_info(RBinInfo *ret, const char *file_path, bool has_version, ut32 version) {
|
||||
static void fill_info(RBinInfo *ret, const char *file_path, bool has_version, ut32 version, bool has_source_lines) {
|
||||
ret->file = file_path? strdup (file_path): NULL;
|
||||
ret->bclass = strdup ("Hermes bytecode");
|
||||
ret->rclass = strdup ("hermes");
|
||||
@@ -143,12 +144,14 @@ static void fill_info(RBinInfo *ret, const char *file_path, bool has_version, ut
|
||||
ret->machine = strdup ("Hermes VM");
|
||||
ret->cpu = has_version? r_str_newf ("%u", version): strdup ("unknown");
|
||||
ret->has_va = true;
|
||||
ret->dbg_info = has_source_lines? R_BIN_DBG_LINENUMS: R_BIN_DBG_STRIPPED;
|
||||
}
|
||||
|
||||
static RBinInfo *bininfo(RBinFile *bf) {
|
||||
RBinInfo *ret = R_NEW0 (RBinInfo);
|
||||
bool has_version = false;
|
||||
ut32 version = 0;
|
||||
bool has_source_lines = false;
|
||||
|
||||
HBC *hbc = get_hbc (bf);
|
||||
if (hbc) {
|
||||
@@ -157,6 +160,10 @@ static RBinInfo *bininfo(RBinFile *bf) {
|
||||
has_version = true;
|
||||
version = hh.version;
|
||||
}
|
||||
/* Cheap: answered from the DebugInfoHeader parsed at load time.
|
||||
* Reflects file truth (does this file carry source lines?) regardless
|
||||
* of bin.dbginfo — that setting only gates whether we decode them. */
|
||||
has_source_lines = hbc_has_source_lines (hbc);
|
||||
}
|
||||
|
||||
/* Fallback: parse version directly from buffer */
|
||||
@@ -169,7 +176,7 @@ static RBinInfo *bininfo(RBinFile *bf) {
|
||||
}
|
||||
}
|
||||
|
||||
fill_info (ret, bf->file, has_version, version);
|
||||
fill_info (ret, bf->file, has_version, version, has_source_lines);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -213,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,8 +338,7 @@ static RList *symbols(RBinFile *bf) {
|
||||
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++) {
|
||||
@@ -392,7 +392,86 @@ static RList *imports(RBinFile *bf) {
|
||||
return imports;
|
||||
}
|
||||
|
||||
static RList *strings(RBinFile *bf) {
|
||||
static bool list_has_string(RList *list, const char *name) {
|
||||
RListIter *iter;
|
||||
const char *it;
|
||||
r_list_foreach (list, iter, it) {
|
||||
if (it && name && !strcmp (it, name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static RList *libs(RBinFile *bf) {
|
||||
RList *libs = r_list_newf (free);
|
||||
HBC *hbc = get_hbc (bf);
|
||||
if (!libs || !hbc) {
|
||||
return libs;
|
||||
}
|
||||
HBCModules modules = { 0 };
|
||||
if (hbc_list_modules (hbc, &modules).code != RESULT_SUCCESS) {
|
||||
return libs;
|
||||
}
|
||||
for (u32 i = 0; i < modules.count; i++) {
|
||||
HBCModule *m = &modules.modules[i];
|
||||
if (m->name && *m->name && !list_has_string (libs, m->name)) {
|
||||
r_list_append (libs, strdup (m->name));
|
||||
}
|
||||
}
|
||||
hbc_free_modules (&modules);
|
||||
return libs;
|
||||
}
|
||||
|
||||
static R_UNOWNED RList *lines(RBinFile *bf) {
|
||||
HBC *hbc = get_hbc (bf);
|
||||
if (!hbc) {
|
||||
return NULL;
|
||||
}
|
||||
/* Respect bin.dbginfo=false — return early so the lazy debug-info parse
|
||||
*(triggered by hbc_get_source_lines) never runs for users who opted out. */
|
||||
if (bf->rbin && !bf->rbin->want_dbginfo) {
|
||||
return NULL;
|
||||
}
|
||||
HBCSourceLineArray lines = { 0 };
|
||||
RList *ret = NULL;
|
||||
if (hbc_get_source_lines (hbc, &lines).code != RESULT_SUCCESS || !lines.count) {
|
||||
goto done;
|
||||
}
|
||||
ret = r_list_newf (free);
|
||||
if (!ret) {
|
||||
goto done;
|
||||
}
|
||||
const bool has_al = bf->addrline.al_add && bf->addrline.al_get;
|
||||
for (u32 i = 0; i < lines.count; i++) {
|
||||
HBCSourceLine *sl = &lines.lines[i];
|
||||
ut64 addr = HBC_VADDR_BASE + sl->address;
|
||||
const char *file = sl->filename? sl->filename: "";
|
||||
if (has_al) {
|
||||
bf->addrline.al_add (&bf->addrline, addr, file, NULL, sl->line, sl->column);
|
||||
}
|
||||
RBinAddrline *row = R_NEW0 (RBinAddrline);
|
||||
if (!row) {
|
||||
continue;
|
||||
}
|
||||
const RBinAddrline *stored = has_al? bf->addrline.al_get (&bf->addrline, addr): NULL;
|
||||
if (stored) {
|
||||
*row = *stored;
|
||||
} else {
|
||||
row->addr = addr;
|
||||
row->file = UT32_MAX;
|
||||
row->path = UT32_MAX;
|
||||
row->line = sl->line;
|
||||
row->column = sl->column;
|
||||
}
|
||||
r_list_append (ret, row);
|
||||
}
|
||||
done:
|
||||
hbc_free_source_lines (&lines);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static RList *strings_list(RBinFile *bf) {
|
||||
RList *ret = r_list_newf ((RListFree)r_bin_string_free);
|
||||
HBC *hbc = get_hbc (bf);
|
||||
if (!hbc) {
|
||||
@@ -440,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",
|
||||
@@ -452,11 +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,
|
||||
.strings = &strings_list,
|
||||
#endif
|
||||
.baddr = &baddr,
|
||||
.libs = &libs,
|
||||
.lines = &lines,
|
||||
.header = &header,
|
||||
.fields = &fields,
|
||||
};
|
||||
|
||||
#ifndef R2_PLUGIN_INCORE
|
||||
|
||||
+145
-1
@@ -5,6 +5,7 @@
|
||||
#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
|
||||
@@ -260,6 +261,55 @@ 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);
|
||||
@@ -282,19 +332,76 @@ static void cmd_file_info(HbcContext *ctx, RCore *core) {
|
||||
" File Length: %u bytes\n"
|
||||
" Functions: %u\n"
|
||||
" Strings: %u\n"
|
||||
" String Kinds: %u\n"
|
||||
" Overflow Strings: %u\n"
|
||||
" String Storage Size: %u\n"
|
||||
" Identifiers: %u\n"
|
||||
" Global Code Index: %u\n"
|
||||
" Function Sources: %u\n"
|
||||
" Debug Info Offset: %u\n"
|
||||
" BigInts: %u (%u bytes)\n"
|
||||
" RegExps: %u (%u bytes)\n"
|
||||
" Literal Value Buffer: %u bytes\n"
|
||||
" Array Buffer: %u bytes\n"
|
||||
" Object Key Buffer: %u bytes\n"
|
||||
" Object Value Buffer: %u bytes\n"
|
||||
" Object Shapes: %u\n"
|
||||
" CJS Modules: %u\n"
|
||||
" Static Builtins: %s\n"
|
||||
" CJS Modules Statically Resolved: %s\n"
|
||||
" Has Async: %s\n",
|
||||
header.version,
|
||||
header.fileLength,
|
||||
header.functionCount,
|
||||
header.stringCount,
|
||||
header.stringKindCount,
|
||||
header.overflowStringCount,
|
||||
header.stringStorageSize,
|
||||
header.identifierCount,
|
||||
header.globalCodeIndex,
|
||||
header.functionSourceCount,
|
||||
header.debugInfoOffset,
|
||||
header.bigIntCount,
|
||||
header.bigIntStorageSize,
|
||||
header.regExpCount,
|
||||
header.regExpStorageSize,
|
||||
header.literalValueBufferSize,
|
||||
header.arrayBufferSize,
|
||||
header.objKeyBufferSize,
|
||||
header.objValueBufferSize,
|
||||
header.objShapeTableCount,
|
||||
header.cjsModuleCount,
|
||||
header.staticBuiltins? "yes": "no",
|
||||
header.cjsModulesStaticallyResolved? "yes": "no",
|
||||
header.hasAsync? "yes": "no");
|
||||
|
||||
HBCDebugInfo di = { 0 };
|
||||
if (hbc_get_debug_info (ctx->hbc, &di).code == RESULT_SUCCESS) {
|
||||
r_cons_printf (cons,
|
||||
" Debug Info Present: %s\n",
|
||||
di.has_debug_info? "yes": "no");
|
||||
if (di.has_debug_info) {
|
||||
r_cons_printf (cons,
|
||||
" Debug Files: %u (%u bytes)\n"
|
||||
" Debug File Regions: %u\n"
|
||||
" Functions With Debug Info: %u\n"
|
||||
" Source Locations: %u bytes\n"
|
||||
" Scope Descriptors: %u bytes\n"
|
||||
" Textified Callees: %u bytes\n"
|
||||
" Debug String Table: %u bytes\n"
|
||||
" Debug Data Size: %u bytes\n",
|
||||
di.filename_count,
|
||||
di.filename_storage_size,
|
||||
di.file_region_count,
|
||||
di.functions_with_debug_info,
|
||||
di.source_locations_size,
|
||||
di.scope_desc_data_size,
|
||||
di.textified_data_size,
|
||||
di.string_table_size,
|
||||
di.debug_data_size);
|
||||
}
|
||||
}
|
||||
|
||||
char hex[41] = { 0 };
|
||||
r_hex_bin2str (header.sourceHash, 20, hex);
|
||||
r_cons_printf (cons,
|
||||
@@ -360,6 +467,35 @@ static void cmd_file_info(HbcContext *ctx, RCore *core) {
|
||||
}
|
||||
}
|
||||
|
||||
static void cmd_source_lines(HbcContext *ctx, RCore *core, const char *arg) {
|
||||
Result res = hbc_load_current_binary (ctx, core);
|
||||
if (res.code != RESULT_SUCCESS) {
|
||||
R_LOG_ERROR ("%s", safe_errmsg (res.error_message));
|
||||
return;
|
||||
}
|
||||
HBCSourceLineArray lines = { 0 };
|
||||
res = hbc_get_source_lines (ctx->hbc, &lines);
|
||||
if (res.code != RESULT_SUCCESS) {
|
||||
R_LOG_ERROR ("source lines: %s", safe_errmsg (res.error_message));
|
||||
hbc_free_source_lines (&lines);
|
||||
return;
|
||||
}
|
||||
const char *as = r_str_trim_head_ro (arg);
|
||||
const bool filter = *as != '\0';
|
||||
const ut32 filter_id = filter? parse_function_id (as): 0;
|
||||
for (u32 i = 0; i < lines.count; i++) {
|
||||
const HBCSourceLine *sl = &lines.lines[i];
|
||||
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);
|
||||
}
|
||||
hbc_free_source_lines (&lines);
|
||||
}
|
||||
|
||||
/* Emit a JSON error envelope compatible with the {code, annotations, errors} format */
|
||||
static void emit_json_error(RCore *core, const char *msg) {
|
||||
PJ *pj = r_core_pj_new (core);
|
||||
@@ -762,11 +898,13 @@ static void cmd_help(RCore *core) {
|
||||
" 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:he - List functions containing eval instructions\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:h? - Show this help\n"
|
||||
"\nNote: r2 comments (CC command) are automatically inlined in decompiler output.\n");
|
||||
@@ -804,6 +942,9 @@ static bool cmd_handler(RCorePluginSession *s, const char *input) {
|
||||
case 'c': /* pd:hc [id] */
|
||||
cmd_decompile_function_ex (ctx, core, arg + 1, false);
|
||||
break;
|
||||
case 'e': /* pd:he */
|
||||
cmd_list_eval_functions (ctx, core);
|
||||
break;
|
||||
case 'f': /* pd:hf */
|
||||
cmd_list_functions (ctx, core);
|
||||
break;
|
||||
@@ -837,6 +978,9 @@ static bool cmd_handler(RCorePluginSession *s, const char *input) {
|
||||
case 'L': /* pd:hL* — literal cache / buffer-literal commands */
|
||||
cmd_literals (ctx, core, arg + 1);
|
||||
break;
|
||||
case 's': /* pd:hs [id] */
|
||||
cmd_source_lines (ctx, core, arg + 1);
|
||||
break;
|
||||
case '?': /* pd:h? */
|
||||
cmd_help (core);
|
||||
break;
|
||||
@@ -975,7 +1119,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,
|
||||
|
||||
+128
-1
@@ -26,6 +26,8 @@ typedef struct {
|
||||
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) },
|
||||
@@ -402,6 +404,21 @@ static Result cmd_h(const CliContext *ctx, int argc, char **argv) {
|
||||
fprintf (out, " Static Builtins: %s\n", hh.staticBuiltins? "Yes": "No");
|
||||
fprintf (out, " CJS Modules Statically Resolved: %s\n", hh.cjsModulesStaticallyResolved? "Yes": "No");
|
||||
fprintf (out, " Has Async: %s\n", hh.hasAsync? "Yes": "No");
|
||||
HBCDebugInfo di = { 0 };
|
||||
if (hbc_get_debug_info (hbc, &di).code == RESULT_SUCCESS) {
|
||||
fprintf (out, " Debug Info:\n");
|
||||
fprintf (out, " Present: %s\n", di.has_debug_info? "Yes": "No");
|
||||
if (di.has_debug_info) {
|
||||
fprintf (out, " Files: %u (%u bytes)\n", di.filename_count, di.filename_storage_size);
|
||||
fprintf (out, " File Regions: %u\n", di.file_region_count);
|
||||
fprintf (out, " Functions With Debug Info: %u\n", di.functions_with_debug_info);
|
||||
fprintf (out, " Source Locations: %u bytes\n", di.source_locations_size);
|
||||
fprintf (out, " Scope Descriptors: %u bytes\n", di.scope_desc_data_size);
|
||||
fprintf (out, " Textified Callees: %u bytes\n", di.textified_data_size);
|
||||
fprintf (out, " Debug String Table: %u bytes\n", di.string_table_size);
|
||||
fprintf (out, " Debug Data Size: %u bytes\n", di.debug_data_size);
|
||||
}
|
||||
}
|
||||
if (out != stdout) {
|
||||
fclose (out);
|
||||
}
|
||||
@@ -429,6 +446,54 @@ static Result cmd_f(const CliContext *ctx, int argc, char **argv) {
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
static Result cmd_sl(const CliContext *ctx, int argc, char **argv) {
|
||||
if (argc < 1 || argc > 2) {
|
||||
return errorf (RESULT_ERROR_INVALID_ARGUMENT, "Usage: sl <input> [function_id]");
|
||||
}
|
||||
HBC *hbc;
|
||||
RETURN_IF_ERROR (open_hbc (argv[0], &hbc));
|
||||
HBCSourceLineArray lines = { 0 };
|
||||
Result r = hbc_get_source_lines (hbc, &lines);
|
||||
if (r.code != RESULT_SUCCESS) {
|
||||
hbc_close (hbc);
|
||||
return r;
|
||||
}
|
||||
|
||||
const bool filter = argc == 2;
|
||||
const u32 filter_id = filter? (u32)strtoul (argv[1], NULL, 0): 0;
|
||||
const bool json = ctx->flags.json;
|
||||
bool first = true;
|
||||
if (json) {
|
||||
putchar ('[');
|
||||
}
|
||||
for (u32 i = 0; i < lines.count; i++) {
|
||||
const HBCSourceLine *sl = &lines.lines[i];
|
||||
if (filter && sl->function_id != filter_id) {
|
||||
continue;
|
||||
}
|
||||
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);
|
||||
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->filename? sl->filename: "",
|
||||
sl->line, sl->column, sl->statement);
|
||||
}
|
||||
}
|
||||
if (json) {
|
||||
puts ("]");
|
||||
}
|
||||
hbc_free_source_lines (&lines);
|
||||
hbc_close (hbc);
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
static Result cmd_cmp(const CliContext *ctx, int argc, char **argv) {
|
||||
(void)ctx;
|
||||
if (argc < 1 || argc > 2) {
|
||||
@@ -814,6 +879,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";
|
||||
}
|
||||
@@ -862,6 +935,58 @@ 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 },
|
||||
@@ -872,6 +997,7 @@ static const Command commands[] = {
|
||||
{ "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 },
|
||||
@@ -879,6 +1005,7 @@ static const Command commands[] = {
|
||||
{ "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]))
|
||||
|
||||
@@ -888,7 +1015,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");
|
||||
|
||||
@@ -9,9 +9,9 @@ EXPECT=<<EOF
|
||||
hbc
|
||||
;-- entry0:
|
||||
;-- global_0x000000b0:
|
||||
0x100000b0 73000100 load_const_string r0, 0xa2 ; str.print_123__
|
||||
0x100000b0 73000100 load_const_string r0, 0xa2 ; yes.js:1:1 ; str.print_123__
|
||||
; 0x100000a2 ; "print(123);"
|
||||
0x100000b4 5e000000 direct_eval r0, r0, 0
|
||||
0x100000b4 5e000000 direct_eval r0, r0, 0 ; yes.js:1:5
|
||||
EOF
|
||||
RUN
|
||||
|
||||
|
||||
@@ -18,6 +18,16 @@ EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:h help eval command
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
pd:h?~pd:he
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
pd:he - List functions containing eval instructions
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hi file info small
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
@@ -45,6 +55,7 @@ pd:hi~Functions
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
Functions: 1
|
||||
Functions With Debug Info: 1
|
||||
EOF
|
||||
RUN
|
||||
|
||||
@@ -55,6 +66,7 @@ pd:hi~Strings
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
Strings: 2
|
||||
Overflow Strings: 0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
@@ -65,6 +77,7 @@ pd:hi~Functions
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
Functions: 5388
|
||||
Functions With Debug Info: 0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
@@ -75,6 +88,7 @@ pd:hi~Strings
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
Strings: 6170
|
||||
Overflow Strings: 7
|
||||
EOF
|
||||
RUN
|
||||
|
||||
@@ -122,6 +136,26 @@ EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:he list eval sites
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
pd:he
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x100000b4 f=0 +0x4 func=0x100000b0 name=global
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:he list eval sites large
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:he
|
||||
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
|
||||
|
||||
@@ -7,9 +7,9 @@ EOF
|
||||
EXPECT=<<EOF
|
||||
;-- entry0:
|
||||
;-- global_0x000000b0:
|
||||
0x100000b0 73000100 load_const_string r0, 0xa2 ; str.print_123__
|
||||
0x100000b0 73000100 load_const_string r0, 0xa2 ; yes.js:1:1 ; str.print_123__
|
||||
; 0x100000a2 ; "print(123);"
|
||||
0x100000b4 5e000000 direct_eval r0, r0, 0
|
||||
0x100000b4 5e000000 direct_eval r0, r0, 0 ; yes.js:1:5
|
||||
0x100000b8 5c00 ret r0
|
||||
EOF
|
||||
RUN
|
||||
@@ -140,7 +140,7 @@ EOF
|
||||
EXPECT=<<EOF
|
||||
;-- entry0:
|
||||
;-- global_0x000000b0:
|
||||
0x100000b0 73000100 load_const_string r0, 0xa2 ; str.print_123__
|
||||
0x100000b0 73000100 load_const_string r0, 0xa2 ; yes.js:1:1 ; str.print_123__
|
||||
; 0x100000a2 ; "print(123);"
|
||||
EOF
|
||||
RUN
|
||||
@@ -166,7 +166,7 @@ s 0x100000b4
|
||||
pd 1
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x100000b4 5e000000 direct_eval r0, r0, 0
|
||||
0x100000b4 5e000000 direct_eval r0, r0, 0 ; yes.js:1:5
|
||||
EOF
|
||||
RUN
|
||||
|
||||
@@ -214,9 +214,9 @@ EOF
|
||||
EXPECT=<<EOF
|
||||
;-- entry0:
|
||||
;-- global_0x000000b0:
|
||||
0x100000b0 73000100 load_const_string r0, 0xa2 ; str.print_123__
|
||||
0x100000b0 73000100 load_const_string r0, 0xa2 ; yes.js:1:1 ; str.print_123__
|
||||
; 0x100000a2 ; "print(123);"
|
||||
0x100000b4 5e000000 direct_eval r0, r0, 0
|
||||
0x100000b4 5e000000 direct_eval r0, r0, 0 ; yes.js:1:5
|
||||
EOF
|
||||
RUN
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
NAME=hbc ih header fields
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
ih~version
|
||||
ih~functionCount
|
||||
ih~debugInfoOffset
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x00000008 0x00000008 0x00000060 version
|
||||
0x00000028 0x00000028 0x00000001 functionCount
|
||||
0x00000068 0x00000068 0x000000c8 debugInfoOffset
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc ihq header fields
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
ihq~version
|
||||
ihq~functionCount
|
||||
ihq~debugInfoOffset
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x00000008 0x00000060 version
|
||||
0x00000028 0x00000001 functionCount
|
||||
0x00000068 0x000000c8 debugInfoOffset
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc ihj header fields
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
ihj~{}~version
|
||||
ihj~{}~functionCount
|
||||
ihj~{}~debugInfoOffset
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
"name": "version",
|
||||
"name": "functionCount",
|
||||
"name": "debugInfoOffset",
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc iH ascii header
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
iH~version
|
||||
iH~functionCount
|
||||
iH~debugInfoOffset
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x00000008 version 0x00000060
|
||||
0x00000028 functionCount 0x00000001
|
||||
0x00000068 debugInfoOffset 0x000000c8
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc iH key values
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
iH=~version
|
||||
iH=~functionCount
|
||||
iH=~debugInfoOffset
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
version=96
|
||||
functionCount=1
|
||||
debugInfoOffset=200
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc iH json
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
iHj~version
|
||||
iHj~functionCount
|
||||
iHj~debugInfoOffset
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
{"magic":2240826417119764422,"version":96,"sourceHash":"a97c8302dab90bec7184a9183b22b43badd57a65","fileLength":292,"globalCodeIndex":0,"functionCount":1,"stringKindCount":1,"identifierCount":0,"stringCount":2,"overflowStringCount":0,"stringStorageSize":17,"bigIntCount":0,"bigIntStorageSize":0,"regExpCount":0,"regExpStorageSize":0,"literalValueBufferSize":0,"arrayBufferSize":0,"objKeyBufferSize":0,"objValueBufferSize":0,"objShapeTableCount":0,"numStringSwitchImms":0,"segmentID":0,"cjsModuleCount":0,"functionSourceCount":0,"debugInfoOffset":200,"staticBuiltins":false,"cjsModulesStaticallyResolved":false,"hasAsync":false}
|
||||
{"magic":2240826417119764422,"version":96,"sourceHash":"a97c8302dab90bec7184a9183b22b43badd57a65","fileLength":292,"globalCodeIndex":0,"functionCount":1,"stringKindCount":1,"identifierCount":0,"stringCount":2,"overflowStringCount":0,"stringStorageSize":17,"bigIntCount":0,"bigIntStorageSize":0,"regExpCount":0,"regExpStorageSize":0,"literalValueBufferSize":0,"arrayBufferSize":0,"objKeyBufferSize":0,"objValueBufferSize":0,"objShapeTableCount":0,"numStringSwitchImms":0,"segmentID":0,"cjsModuleCount":0,"functionSourceCount":0,"debugInfoOffset":200,"staticBuiltins":false,"cjsModulesStaticallyResolved":false,"hasAsync":false}
|
||||
{"magic":2240826417119764422,"version":96,"sourceHash":"a97c8302dab90bec7184a9183b22b43badd57a65","fileLength":292,"globalCodeIndex":0,"functionCount":1,"stringKindCount":1,"identifierCount":0,"stringCount":2,"overflowStringCount":0,"stringStorageSize":17,"bigIntCount":0,"bigIntStorageSize":0,"regExpCount":0,"regExpStorageSize":0,"literalValueBufferSize":0,"arrayBufferSize":0,"objKeyBufferSize":0,"objValueBufferSize":0,"objShapeTableCount":0,"numStringSwitchImms":0,"segmentID":0,"cjsModuleCount":0,"functionSourceCount":0,"debugInfoOffset":200,"staticBuiltins":false,"cjsModulesStaticallyResolved":false,"hasAsync":false}
|
||||
EOF
|
||||
RUN
|
||||
@@ -0,0 +1,172 @@
|
||||
NAME=pd:hs help entry
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
pd:h?~pd:hs
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
pd:hs [id] - List source-line information
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hs source lines small
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
pd:hs
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x000000b0 f=0 +0x0 yes.js:1:1 stmt=0
|
||||
0x000000b4 f=0 +0x4 yes.js:1:5 stmt=0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hs source lines filter
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
pd:hs 0
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x000000b0 f=0 +0x0 yes.js:1:1 stmt=0
|
||||
0x000000b4 f=0 +0x4 yes.js:1:5 stmt=0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hs source lines filter miss
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
pd:hs 99
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hs empty source lines
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
pd:hs
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hi debug info present
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
pd:hi~Debug Info Present
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
Debug Info Present: yes
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hi source locations size
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
pd:hi~Source Locations
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
Source Locations: 13 bytes
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hi debug files count
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
pd:hi~Debug Files
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
Debug Files: 1 (6 bytes)
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=iI linenum exposed (debug info)
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
iI~linenum
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
linenum true
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=iI linenum stripped
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
iI~linenum
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
linenum false
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=iI linenum honours file truth with bin.dbginfo=false
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e bin.dbginfo=false
|
||||
CMDS=<<EOF
|
||||
iI~linenum,stripped
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
linenum true
|
||||
stripped false
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=iI stripped file still stripped with bin.dbginfo=false
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
ARGS=-e bin.dbginfo=false
|
||||
CMDS=<<EOF
|
||||
iI~linenum,stripped
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
linenum false
|
||||
stripped true
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=iI stripped file stripped with bin.dbginfo=true
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
ARGS=-e bin.dbginfo=true
|
||||
CMDS=<<EOF
|
||||
iI~linenum,stripped
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
linenum false
|
||||
stripped true
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=CL populated with bin.dbginfo=true
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e bin.dbginfo=true
|
||||
CMDS=<<EOF
|
||||
CL
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
file: yes.js
|
||||
line: 1
|
||||
colu: 1
|
||||
addr: 0x100000b0
|
||||
file: yes.js
|
||||
line: 1
|
||||
colu: 5
|
||||
addr: 0x100000b4
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=CL empty with bin.dbginfo=false
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e bin.dbginfo=false
|
||||
CMDS=<<EOF
|
||||
CL
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=CL empty on stripped file
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
CL
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
+3
-2
@@ -1,13 +1,14 @@
|
||||
test_src = files(
|
||||
# Add test files here as needed
|
||||
'test_debug_info.c',
|
||||
)
|
||||
|
||||
if test_src.length() > 0
|
||||
executable(
|
||||
run_tests = executable(
|
||||
'run_tests',
|
||||
test_src,
|
||||
link_with: libhbc,
|
||||
include_directories: include_dirs,
|
||||
install: false,
|
||||
)
|
||||
test('debug-info', run_tests, args: [meson.project_source_root()])
|
||||
endif
|
||||
@@ -0,0 +1,81 @@
|
||||
#include <hbc/hbc.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define CHECK(x) do { \
|
||||
if (!(x)) { \
|
||||
fprintf (stderr, "check failed: %s:%d: %s\n", __FILE__, __LINE__, #x); \
|
||||
return 1; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static int test_small_debug_info(const char *root) {
|
||||
char path[512];
|
||||
snprintf (path, sizeof (path), "%s/test/bins/hbc/bespoke_eval.hbc", root);
|
||||
|
||||
HBC *hbc = NULL;
|
||||
CHECK (hbc_open (path, &hbc).code == RESULT_SUCCESS);
|
||||
|
||||
HBCDebugInfo di = { 0 };
|
||||
CHECK (hbc_get_debug_info (hbc, &di).code == RESULT_SUCCESS);
|
||||
CHECK (di.has_debug_info);
|
||||
CHECK (di.filename_count == 1);
|
||||
CHECK (di.filename_storage_size == 6);
|
||||
CHECK (di.file_region_count == 1);
|
||||
CHECK (di.functions_with_debug_info == 1);
|
||||
CHECK (di.source_locations_size == 13);
|
||||
CHECK (di.scope_desc_data_size == 3);
|
||||
CHECK (di.textified_data_size == 1);
|
||||
|
||||
HBCSourceLineArray lines = { 0 };
|
||||
CHECK (hbc_get_source_lines (hbc, &lines).code == RESULT_SUCCESS);
|
||||
CHECK (hbc_has_source_lines (hbc));
|
||||
CHECK (lines.count == 2);
|
||||
CHECK (lines.lines[0].address == 0xb0);
|
||||
CHECK (lines.lines[0].function_address == 0);
|
||||
CHECK (lines.lines[0].line == 1);
|
||||
CHECK (lines.lines[0].column == 1);
|
||||
CHECK (!strcmp (lines.lines[0].filename, "yes.js"));
|
||||
CHECK (lines.lines[1].address == 0xb4);
|
||||
CHECK (lines.lines[1].function_address == 4);
|
||||
CHECK (lines.lines[1].line == 1);
|
||||
CHECK (lines.lines[1].column == 5);
|
||||
|
||||
hbc_free_source_lines (&lines);
|
||||
hbc_close (hbc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_empty_debug_info(const char *root) {
|
||||
char path[512];
|
||||
snprintf (path, sizeof (path), "%s/test/bins/hbc/index.android.bundle", root);
|
||||
|
||||
HBC *hbc = NULL;
|
||||
CHECK (hbc_open (path, &hbc).code == RESULT_SUCCESS);
|
||||
|
||||
HBCDebugInfo di = { 0 };
|
||||
CHECK (hbc_get_debug_info (hbc, &di).code == RESULT_SUCCESS);
|
||||
CHECK (di.has_debug_info);
|
||||
CHECK (di.filename_count == 0);
|
||||
CHECK (di.file_region_count == 0);
|
||||
CHECK (di.functions_with_debug_info == 0);
|
||||
CHECK (di.source_locations_size == 0);
|
||||
CHECK (!hbc_has_source_lines (hbc));
|
||||
|
||||
HBCSourceLineArray lines = { 0 };
|
||||
CHECK (hbc_get_source_lines (hbc, &lines).code == RESULT_SUCCESS);
|
||||
CHECK (lines.count == 0);
|
||||
|
||||
hbc_free_source_lines (&lines);
|
||||
hbc_close (hbc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const char *root = argc > 1? argv[1]: ".";
|
||||
if (test_small_debug_info (root) || test_empty_debug_info (root)) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user