mirror of
https://github.com/radareorg/r2hermes.git
synced 2026-11-08 17:42:33 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9821c3f425 | ||
|
|
32d1965288 | ||
|
|
56900a5064 | ||
|
|
d6e807bc1c | ||
|
|
76ec5ad0f8 | ||
|
|
c08dcc3268 | ||
|
|
56c943a4ca | ||
|
|
21231841f5 | ||
|
|
e7985ec5e4 | ||
|
|
5f830f3dc8 | ||
|
|
74acfedc8c | ||
|
|
9fd443304a | ||
|
|
55460d4253 | ||
|
|
55eb2dd38d | ||
|
|
57a6817d9c | ||
|
|
2a0ca11c6b | ||
|
|
55f1658b23 | ||
|
|
40cec248c0 | ||
|
|
e21259b873 | ||
|
|
04750b84cb |
No files matched your search
@@ -1,7 +1,7 @@
|
||||
name: CI
|
||||
|
||||
env:
|
||||
R2V: 6.0.8
|
||||
R2V: 6.1.4
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
ls -l libhbc*
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v2.6.1
|
||||
uses: softprops/action-gh-release@v3.0.0
|
||||
with:
|
||||
tag_name: ${{ needs.build.outputs.tag }}
|
||||
files: ./libhbc-${{ needs.build.outputs.tag }}.tgz
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: Meson Build
|
||||
|
||||
env:
|
||||
R2V: 6.0.8
|
||||
R2V: 6.1.4
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: CI
|
||||
|
||||
env:
|
||||
R2V: 6.0.8
|
||||
R2V: 6.1.4
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: Windows Build
|
||||
|
||||
env:
|
||||
R2V: 6.0.8
|
||||
R2V: 6.1.4
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
build/
|
||||
bin/libhbctool
|
||||
bin/
|
||||
third_party/
|
||||
*.d
|
||||
*.o
|
||||
|
||||
@@ -18,8 +18,9 @@ DISASM_SRC = $(wildcard $(SRC_DIR)/disassembly/*.c)
|
||||
DECOMPILE_SRC = $(wildcard $(SRC_DIR)/decompilation/*.c)
|
||||
OPCODES_SRC = $(filter-out $(SRC_DIR)/opcodes/%.inc.c,$(wildcard $(SRC_DIR)/opcodes/*.c))
|
||||
LIB_SRC = $(UTILS_SRC) $(PARSERS_SRC) $(DISASM_SRC) $(DECOMPILE_SRC) $(OPCODES_SRC) \
|
||||
$(SRC_DIR)/hbc.c $(SRC_DIR)/opcodes/encoder.c $(SRC_DIR)/opcodes/decoder.c $(SRC_DIR)/r2.c
|
||||
$(SRC_DIR)/hbc.c $(SRC_DIR)/literals_api.c $(SRC_DIR)/opcodes/encoder.c $(SRC_DIR)/opcodes/decoder.c $(SRC_DIR)/r2.c
|
||||
MAIN_SRC = src/tool/libhbctool.c
|
||||
HBC_HEADERS = $(shell find include -name '*.h')
|
||||
|
||||
## Object files
|
||||
LIB_OBJ = $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/%.o,$(LIB_SRC))
|
||||
@@ -55,11 +56,11 @@ $(STATIC_LIB): $(LIB_OBJ)
|
||||
$(BIN_FILE): $(STATIC_LIB) $(MAIN_OBJ) | $(shell mkdir -p $(BIN_DIR))
|
||||
$(CC) $(CFLAGS) -o $@ $(MAIN_OBJ) -L$(BUILD_DIR) -lhbc
|
||||
|
||||
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c | $(VH)
|
||||
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c $(HBC_HEADERS) | $(VH)
|
||||
@mkdir -p $(dir $@)
|
||||
$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@
|
||||
|
||||
$(MAIN_OBJ): $(MAIN_SRC) | $(VH)
|
||||
$(MAIN_OBJ): $(MAIN_SRC) $(HBC_HEADERS) | $(VH)
|
||||
$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@
|
||||
|
||||
clean:
|
||||
@@ -72,6 +73,14 @@ 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'
|
||||
|
||||
r2:
|
||||
$(MAKE) clean
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
#include "version.h"
|
||||
|
||||
#ifndef R2_VERSION
|
||||
#define R_RETURN_VAL_IF_FAIL(x, y) \
|
||||
|
||||
@@ -144,6 +144,8 @@ typedef struct HermesDecompiler {
|
||||
bool *function_in_progress; /* Track functions currently on the emit stack */
|
||||
int indent_level;
|
||||
bool inlining_function; /* True when outputting a nested function inline */
|
||||
bool output_truncated; /* Sticky flag: budget exceeded; stop emitting new content */
|
||||
bool truncation_marker_emitted; /* Sticky flag: the truncation comment was already written */
|
||||
HBCDecompOptions options;
|
||||
StringBuffer output;
|
||||
} HermesDecompiler;
|
||||
|
||||
@@ -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;
|
||||
@@ -104,6 +132,10 @@ typedef struct {
|
||||
bool force_dispatch; /* Force switch/case dispatch loop for linear functions */
|
||||
bool inline_closures; /* Inline closure definitions (default: true) */
|
||||
int inline_threshold; /* Max instruction count to inline (0 = no limit, -1 = no inline) */
|
||||
/* Early-stop budgets (0 = unlimited). max_ast_statements caps pass2 AST
|
||||
* growth per function; max_output_bytes caps total emitted output. */
|
||||
int max_ast_statements;
|
||||
int max_output_bytes;
|
||||
} HBCDecompOptions;
|
||||
|
||||
/* Per-instruction details */
|
||||
@@ -216,6 +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.
|
||||
*/
|
||||
@@ -284,11 +324,39 @@ typedef struct {
|
||||
u32 count;
|
||||
} HBCInsns;
|
||||
|
||||
typedef enum {
|
||||
HBC_BINDING_IMPORT = 0,
|
||||
HBC_BINDING_EXPORT = 1
|
||||
} HBCBindingType;
|
||||
|
||||
typedef struct {
|
||||
HBCBindingType type;
|
||||
const char *kind; /* js, native, cjs, global, module */
|
||||
char *name;
|
||||
char *module;
|
||||
u32 function_id;
|
||||
u32 offset;
|
||||
u32 string_id;
|
||||
} HBCBinding;
|
||||
|
||||
typedef struct {
|
||||
HBCBinding *bindings;
|
||||
u32 count;
|
||||
} HBCBindings;
|
||||
|
||||
/**
|
||||
* Free instruction array.
|
||||
*/
|
||||
void hbc_free_insns(HBCInsn *insns, u32 count);
|
||||
|
||||
/**
|
||||
* Scan bytecode/string references for probable JS/native import/export
|
||||
* boundaries. Hermes has no native import table, so these entries are derived
|
||||
* from bytecode operands and CJS metadata.
|
||||
*/
|
||||
Result hbc_scan_bindings(HBC *hbc, HBCBindings *out);
|
||||
void hbc_free_bindings(HBCBindings *bindings);
|
||||
|
||||
/**
|
||||
* Encoding buffer and functions
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
#ifndef LIBHBC_LITERALS_PUBLIC_H
|
||||
#define LIBHBC_LITERALS_PUBLIC_H
|
||||
|
||||
#include <hbc/common.h>
|
||||
#include <hbc/hbc.h>
|
||||
|
||||
/* ============================================================================
|
||||
* Literals API (public)
|
||||
*
|
||||
* Covers the buffer-backed literals produced by:
|
||||
* OP_NewArrayWithBuffer / OP_NewArrayWithBufferLong
|
||||
* OP_NewObjectWithBuffer / OP_NewObjectWithBufferLong
|
||||
*
|
||||
* Responsibilities:
|
||||
* - Format a literal from opcode immediates into a JS-like string.
|
||||
* - Maintain a lazy cache of distinct literals so the disassembler and
|
||||
* decompiler can look up previously-formatted literals in O(1) and avoid
|
||||
* re-walking the SLP buffer on every call.
|
||||
* - Track the set of instruction addresses that reference each literal.
|
||||
* - Enumerate literals from the pool side (scan SLP stream) and from the
|
||||
* code side (walk function bodies).
|
||||
* - Allow resetting the cache to recover from a bad scan.
|
||||
*
|
||||
* Caching policy: entries are keyed by (kind, num_items, primary_id,
|
||||
* secondary_id); formatting happens at registration time and is memoized.
|
||||
* The cache can be cleared at any time.
|
||||
* ============================================================================ */
|
||||
|
||||
typedef enum {
|
||||
HBC_LIT_ARRAY = 0,
|
||||
HBC_LIT_OBJECT = 1
|
||||
} HBCLiteralKind;
|
||||
|
||||
/* One entry in the literal cache. The strings / xref array are owned by the
|
||||
* HBC and freed on hbc_close() or hbc_literals_reset(). */
|
||||
typedef struct {
|
||||
HBCLiteralKind kind;
|
||||
u32 num_items;
|
||||
u32 primary_id; /* array_id (array) or keys/shape id (object) */
|
||||
u32 secondary_id; /* values_id for objects, 0 for arrays */
|
||||
u32 paddr; /* absolute file offset of the literal data in the SLP pool (0 if unknown) */
|
||||
char *formatted; /* cached JS literal text; NULL if not yet formatted */
|
||||
u32 *xref_addrs; /* instruction addresses that construct this literal */
|
||||
u32 xref_count;
|
||||
} HBCLiteralEntry;
|
||||
|
||||
/* Format a literal directly from raw parameters. Does not touch the cache.
|
||||
* Returned string is heap-allocated; caller frees with free(). */
|
||||
Result hbc_literals_format_raw(HBC *hbc, HBCLiteralKind kind, u32 num_items,
|
||||
u32 primary_id, u32 secondary_id, char **out);
|
||||
|
||||
/* Convenience wrapper taking opcode + raw arg3/arg4/arg5 as decoded by
|
||||
* hbc_dec(). Knows the per-opcode mapping; useful as the "give me the string
|
||||
* for these parameters" helper. */
|
||||
Result hbc_literals_format_for_opcode(HBC *hbc, u8 opcode, u32 arg3, u32 arg4,
|
||||
u32 arg5, char **out);
|
||||
|
||||
/* Look up (or create) a cache entry for a literal and return the formatted
|
||||
* text. The returned pointer is owned by the cache — do not free it. */
|
||||
Result hbc_literals_get(HBC *hbc, HBCLiteralKind kind, u32 num_items,
|
||||
u32 primary_id, u32 secondary_id, const char **out_formatted);
|
||||
|
||||
/* Register that `from_addr` references the literal described by the key.
|
||||
* Creates the cache entry (and formats) on first reference. */
|
||||
Result hbc_literals_register(HBC *hbc, HBCLiteralKind kind, u32 num_items,
|
||||
u32 primary_id, u32 secondary_id, u32 from_addr);
|
||||
|
||||
/* Full-binary scan: walk every function body; record every
|
||||
* New{Array,Object}WithBuffer[Long] call site into the cache (with xrefs).
|
||||
* Returns the number of distinct literals found in *out_count. */
|
||||
Result hbc_literals_scan_code(HBC *hbc, u32 *out_count);
|
||||
|
||||
/* Pool-side scan: enumerate group boundaries in the SLP pool for the given
|
||||
* kind. For arrays and object-values it walks the byte stream linearly.
|
||||
* For object-keys on v97+ it enumerates the shape table instead.
|
||||
* Does not populate the cache — this is an inspection helper. Caller frees
|
||||
* *out via free(). */
|
||||
typedef struct {
|
||||
u32 paddr; /* absolute file offset */
|
||||
u32 pool_offset; /* byte offset within the pool */
|
||||
u32 num_items; /* number of values in this group (or shape prop_count) */
|
||||
u8 tag; /* SLP tag 0..7 (for arrays/values); 0xff if not applicable */
|
||||
} HBCPoolGroup;
|
||||
|
||||
Result hbc_literals_scan_pool(HBC *hbc, HBCLiteralKind kind,
|
||||
HBCPoolGroup **out, u32 *out_count);
|
||||
|
||||
/* Enumerate the current cache contents. The returned array is owned by the
|
||||
* HBC and valid until the next cache mutation. */
|
||||
Result hbc_literals_list(HBC *hbc, const HBCLiteralEntry **out,
|
||||
u32 *out_count);
|
||||
|
||||
/* Drop every cached entry and every xref. Fast. Does not affect the SLP
|
||||
* pools themselves. */
|
||||
void hbc_literals_reset(HBC *hbc);
|
||||
|
||||
/* Number of cached entries. 0 before any register / scan call. */
|
||||
u32 hbc_literals_count(HBC *hbc);
|
||||
|
||||
/* Absolute file offset of the named pool, or 0 if the pool is empty/absent. */
|
||||
u32 hbc_get_pool_paddr(HBC *hbc, HBCLiteralKind kind);
|
||||
u32 hbc_get_pool_size(HBC *hbc, HBCLiteralKind kind);
|
||||
|
||||
/* Value pool paddr/size for objects (arrays & object-keys reuse the kind-based
|
||||
* accessors; object values is a distinct pool). */
|
||||
u32 hbc_get_object_values_paddr(HBC *hbc);
|
||||
u32 hbc_get_object_values_size(HBC *hbc);
|
||||
|
||||
/* Global toggle: whether hbc_dec() should inline the formatted literal as a
|
||||
* trailing " ; <text>" comment on the disasm line. Default: off. */
|
||||
void hbc_set_inline_literals(bool on);
|
||||
bool hbc_get_inline_literals(void);
|
||||
|
||||
#endif /* LIBHBC_LITERALS_PUBLIC_H */
|
||||
+17
-1
@@ -239,6 +239,11 @@ struct HBCReader {
|
||||
ShapeTableEntry *object_shapes;
|
||||
size_t object_shape_count;
|
||||
|
||||
/* Absolute file offsets of the SLP pools (0 if the pool is absent). */
|
||||
u32 arrays_paddr;
|
||||
u32 object_keys_paddr;
|
||||
u32 object_values_paddr;
|
||||
|
||||
/* BigInt data */
|
||||
i64 *bigint_values;
|
||||
size_t bigint_count;
|
||||
@@ -258,11 +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;
|
||||
@@ -293,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);
|
||||
|
||||
|
||||
+2
-1
@@ -1,7 +1,7 @@
|
||||
project(
|
||||
'r2hermes',
|
||||
'c',
|
||||
version: '1.2.4',
|
||||
version: '1.2.6',
|
||||
license: 'MIT',
|
||||
default_options: [
|
||||
'c_std=c11',
|
||||
@@ -67,6 +67,7 @@ lib_opcodes_src = files(
|
||||
|
||||
lib_core_src = files(
|
||||
'src/lib/hbc.c',
|
||||
'src/lib/literals_api.c',
|
||||
'src/lib/r2.c'
|
||||
)
|
||||
|
||||
|
||||
+7
-47
@@ -1,66 +1,26 @@
|
||||
# Version
|
||||
# XXX reuse from parent meson.build or wtf
|
||||
version = '1.0.0'
|
||||
# r2hermes XPS integration for radare2.
|
||||
# Included from libr/xps/meson.build when r2hermes sources live under
|
||||
# libr/xps/p/r2hermes/. Appends sources and include paths to the r_core
|
||||
# aggregate so the static plugin is linked into libr_core.
|
||||
|
||||
# Version extraction
|
||||
version_parts = version.split('.')
|
||||
version_major = version_parts[0]
|
||||
version_minor = version_parts.length() > 1 ? version_parts[1] : '0'
|
||||
version_patch = version_parts.length() > 2 ? version_parts[2] : '0'
|
||||
|
||||
# Generate version.h
|
||||
configure_file(
|
||||
input: '../include/hbc/version.h.in',
|
||||
output: 'version.h',
|
||||
configuration: {
|
||||
'VERSION': version,
|
||||
'VERSION_MAJOR': version_major,
|
||||
'VERSION_MINOR': version_minor,
|
||||
'VERSION_PATCH': version_patch,
|
||||
},
|
||||
install_dir: 'libr/hbc',
|
||||
install: true,
|
||||
)
|
||||
|
||||
# Register plugins in the static plugin lists
|
||||
core_plugins += ['r2hermes']
|
||||
|
||||
# Include directories (relative to this meson.build file location)
|
||||
include_dirs = include_directories(join_paths('..', 'include'))
|
||||
r_core_additional_inc += include_dirs
|
||||
r_core_additional_inc += include_directories(join_paths('..', 'include'))
|
||||
|
||||
# Library source files
|
||||
lib_utils_src = files(
|
||||
r_core_additional_sources += files(
|
||||
'../src/lib/utils/string_buffer.c',
|
||||
'../src/lib/utils/buffer_reader.c',
|
||||
)
|
||||
|
||||
lib_parsers_src = files(
|
||||
'../src/lib/parsers/hbc_file_parser.c',
|
||||
'../src/lib/parsers/hbc_bytecode_parser.c',
|
||||
)
|
||||
|
||||
lib_disasm_src = [] # Disassembly sources (if any)
|
||||
|
||||
lib_decompile_src = files(
|
||||
'../src/lib/decompilation/translator.c',
|
||||
'../src/lib/decompilation/token.c',
|
||||
'../src/lib/decompilation/literals.c',
|
||||
'../src/lib/decompilation/decompiler.c',
|
||||
)
|
||||
|
||||
lib_opcodes_src = files(
|
||||
'../src/lib/opcodes/isa.c',
|
||||
'../src/lib/opcodes/encoder.c',
|
||||
'../src/lib/opcodes/decoder.c',
|
||||
)
|
||||
|
||||
lib_core_src = files(
|
||||
'../src/lib/hbc.c',
|
||||
'../src/lib/literals_api.c',
|
||||
'../src/lib/r2.c',
|
||||
'../src/r2/core_hbc_one.c',
|
||||
)
|
||||
|
||||
lib_src = lib_utils_src + lib_parsers_src + lib_disasm_src + lib_decompile_src + lib_opcodes_src + lib_core_src
|
||||
|
||||
r_core_additional_sources += lib_src
|
||||
@@ -149,7 +149,7 @@ static bool u32set_contains(const U32Set *s, u32 v) {
|
||||
if (!s || !s->bitmap || v / 8 >= s->bitmap_size) {
|
||||
return false;
|
||||
}
|
||||
return (s->bitmap[v / 8] &(1 << (v % 8))) != 0;
|
||||
return (s->bitmap[v / 8] & (1 << (v % 8))) != 0;
|
||||
}
|
||||
static Result u32set_add(U32Set *s, u32 v) {
|
||||
if (!s) {
|
||||
@@ -710,6 +710,8 @@ Result _hbc_decompiler_init(HermesDecompiler *decompiler) {
|
||||
decompiler->function_in_progress = NULL;
|
||||
decompiler->indent_level = 0;
|
||||
decompiler->inlining_function = false;
|
||||
decompiler->output_truncated = false;
|
||||
decompiler->truncation_marker_emitted = false;
|
||||
decompiler->hbc = NULL; /* Will be set if using state-based API */
|
||||
decompiler->options.pretty_literals = true;
|
||||
decompiler->options.suppress_comments = false;
|
||||
@@ -883,6 +885,9 @@ Result _hbc_decompile_all_to_buffer(HBCReader *reader, HBCDecompOptions options,
|
||||
if (dec.decompiled_functions[i]) {
|
||||
continue;
|
||||
}
|
||||
if (dec.output_truncated) {
|
||||
break;
|
||||
}
|
||||
Result r = _hbc_decompile_function (&dec, i, NULL, -1, false, false, false);
|
||||
if (r.code != RESULT_SUCCESS) {
|
||||
_hbc_decompiler_cleanup (&dec);
|
||||
@@ -1090,6 +1095,9 @@ Result _hbc_decompile_all_with_state(HBC *hbc, HBCDecompOptions options, StringB
|
||||
if (dec.decompiled_functions[i]) {
|
||||
continue;
|
||||
}
|
||||
if (dec.output_truncated) {
|
||||
break;
|
||||
}
|
||||
Result r = _hbc_decompile_function (&dec, i, NULL, -1, false, false, false);
|
||||
if (r.code != RESULT_SUCCESS) {
|
||||
_hbc_decompiler_cleanup (&dec);
|
||||
@@ -1428,8 +1436,13 @@ Result _hbc_pass2_transform_code(HermesDecompiler *state, DecompiledFunctionBody
|
||||
if (!state || !function_body) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "_hbc_pass2_transform_code args");
|
||||
}
|
||||
/* Translate each parsed instruction into a TokenString line */
|
||||
int ast_budget = state->options.max_ast_statements;
|
||||
for (u32 i = 0; i < function_body->instructions.count; i++) {
|
||||
/* AST-size cap: bail mid-build so pass3/4 and output stay cheap. */
|
||||
if (ast_budget > 0 && function_body->statements_count >= (u32)ast_budget) {
|
||||
state->output_truncated = true;
|
||||
break;
|
||||
}
|
||||
ParsedInstruction *ins = &function_body->instructions.instructions[i];
|
||||
if (!ins->inst) {
|
||||
continue;
|
||||
@@ -1497,14 +1510,16 @@ Result _hbc_pass3_parse_forin_loops(HermesDecompiler *state, DecompiledFunctionB
|
||||
if (!filni || function_body->statements[other].head->type != TOKEN_TYPE_FOR_IN_LOOP_NEXT_ITER) {
|
||||
continue;
|
||||
}
|
||||
int next_value_register = filni->next_value_register;
|
||||
int obj_register = fili->obj_register;
|
||||
|
||||
/* Replace GetPNameList line with a `for (<next> in <obj>)` header */
|
||||
RETURN_IF_ERROR (token_string_clear_tokens (line));
|
||||
RETURN_IF_ERROR (_hbc_token_string_add_token (line, create_raw_token ("for")));
|
||||
RETURN_IF_ERROR (_hbc_token_string_add_token (line, create_left_parenthesis_token ()));
|
||||
RETURN_IF_ERROR (_hbc_token_string_add_token (line, create_left_hand_reg_token (filni->next_value_register)));
|
||||
RETURN_IF_ERROR (_hbc_token_string_add_token (line, create_left_hand_reg_token (next_value_register)));
|
||||
RETURN_IF_ERROR (_hbc_token_string_add_token (line, create_raw_token ("in")));
|
||||
RETURN_IF_ERROR (_hbc_token_string_add_token (line, create_right_hand_reg_token (fili->obj_register)));
|
||||
RETURN_IF_ERROR (_hbc_token_string_add_token (line, create_right_hand_reg_token (obj_register)));
|
||||
RETURN_IF_ERROR (_hbc_token_string_add_token (line, create_right_parenthesis_token ()));
|
||||
|
||||
/* Silence the loop plumbing instructions */
|
||||
@@ -1579,6 +1594,7 @@ Result _hbc_pass4_name_closure_vars(HermesDecompiler *state, DecompiledFunctionB
|
||||
}
|
||||
} else if (tok->type == TOKEN_TYPE_STORE_TO_ENVIRONMENT) {
|
||||
StoreToEnvironmentToken *t = (StoreToEnvironmentToken *)tok;
|
||||
int value_register = t->value_register;
|
||||
Environment *env = envmap_get (function_body, t->env_register);
|
||||
if (!env) {
|
||||
continue;
|
||||
@@ -1600,7 +1616,7 @@ Result _hbc_pass4_name_closure_vars(HermesDecompiler *state, DecompiledFunctionB
|
||||
}
|
||||
RETURN_IF_ERROR (_hbc_token_string_add_token (line, create_raw_token (existing)));
|
||||
RETURN_IF_ERROR (_hbc_token_string_add_token (line, create_assignment_token ()));
|
||||
RETURN_IF_ERROR (_hbc_token_string_add_token (line, create_right_hand_reg_token (t->value_register)));
|
||||
RETURN_IF_ERROR (_hbc_token_string_add_token (line, create_right_hand_reg_token (value_register)));
|
||||
break;
|
||||
} else if (tok->type == TOKEN_TYPE_LOAD_FROM_ENVIRONMENT) {
|
||||
LoadFromEnvironmentToken *t = (LoadFromEnvironmentToken *)tok;
|
||||
@@ -1876,6 +1892,17 @@ Result _hbc_output_code(HermesDecompiler *state, DecompiledFunctionBody *functio
|
||||
(unsigned long long)state->options.function_base,
|
||||
function_body->statements_count);
|
||||
for (u32 si = 0; si < function_body->statements_count; si++) {
|
||||
/* Stop when budget exhausted or earlier phase (pass2) flagged it. */
|
||||
if (state->output_truncated || (state->options.max_output_bytes > 0 && out->length >= (size_t)state->options.max_output_bytes)) {
|
||||
state->output_truncated = true;
|
||||
if (!state->truncation_marker_emitted) {
|
||||
state->truncation_marker_emitted = true;
|
||||
append_indent (out, state->indent_level);
|
||||
_hbc_string_buffer_append (out,
|
||||
"// [output truncated: raise 'hbc.max_ast'/'hbc.max_bytes' (0=unlimited) for full output]\n");
|
||||
}
|
||||
break;
|
||||
}
|
||||
/* Skip dead code */
|
||||
if (dce && dce[si]) {
|
||||
continue;
|
||||
@@ -2193,6 +2220,9 @@ Result _hbc_decompile_function(HermesDecompiler *state, u32 function_id, Environ
|
||||
if (!state || !state->hbc_reader) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "_hbc_decompile_function args");
|
||||
}
|
||||
if (state->output_truncated) {
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
HBCReader *reader = state->hbc_reader;
|
||||
if (function_id >= reader->header.functionCount) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "_hbc_decompile_function bad id");
|
||||
|
||||
@@ -91,7 +91,7 @@ static bool slp_read_u16(const u8 *base, size_t size, size_t *pos, u16 *out) {
|
||||
if (*pos + 2 > size) {
|
||||
return false;
|
||||
}
|
||||
*out = (u16) (base[*pos] | (base[*pos + 1] << 8));
|
||||
*out = (u16) ((u16)base[*pos] | ((u16)base[*pos + 1] << 8));
|
||||
(*pos) += 2;
|
||||
return true;
|
||||
}
|
||||
@@ -99,7 +99,7 @@ static bool slp_read_u32(const u8 *base, size_t size, size_t *pos, u32 *out) {
|
||||
if (*pos + 4 > size) {
|
||||
return false;
|
||||
}
|
||||
*out = (u32) (base[*pos] | (base[*pos + 1] << 8) | (base[*pos + 2] << 16) | (base[*pos + 3] << 24));
|
||||
*out = (u32)base[*pos] | ((u32)base[*pos + 1] << 8) | ((u32)base[*pos + 2] << 16) | ((u32)base[*pos + 3] << 24);
|
||||
(*pos) += 4;
|
||||
return true;
|
||||
}
|
||||
@@ -216,8 +216,8 @@ static Result slp_append_value_js(HBCReader *r, const SLPValue *v, bool for_key,
|
||||
case SLP_ShortStringTag:
|
||||
case SLP_ByteStringTag:
|
||||
{
|
||||
const char *s = (r && r->strings && v->v.str_id < r->header.stringCount)? r->strings[v->v.str_id]: NULL;
|
||||
if (for_key && s && _hbc_is_js_identifier (s)) {
|
||||
const char *s = (r && r->strings && v->v.str_id < r->header.stringCount)? r->strings[v->v.str_id]: NULL;
|
||||
if (for_key && s && _hbc_is_js_identifier (s)) {
|
||||
return _hbc_string_buffer_append (out, s);
|
||||
}
|
||||
if (s) {
|
||||
|
||||
@@ -326,10 +326,10 @@ Result _hbc_token_to_string(Token *token, StringBuffer *buffer) {
|
||||
{
|
||||
FunctionTableIndexToken *t = (FunctionTableIndexToken *)token;
|
||||
/* Try to resolve name if possible */
|
||||
if (t->state && t->state->hbc_reader && t->function_id < t->state->hbc_reader->header.functionCount) {
|
||||
if (t->state && t->state->hbc_reader && t->function_id < t->state->hbc_reader->header.functionCount) {
|
||||
u32 name_id = t->state->hbc_reader->function_headers[t->function_id].functionName;
|
||||
const char *name = (name_id < t->state->hbc_reader->header.stringCount && t->state->hbc_reader->strings)? t->state->hbc_reader->strings[name_id]: NULL;
|
||||
if (name && *name) {
|
||||
const char *name = (name_id < t->state->hbc_reader->header.stringCount && t->state->hbc_reader->strings)? t->state->hbc_reader->strings[name_id]: NULL;
|
||||
if (name && *name) {
|
||||
RETURN_IF_ERROR (_hbc_string_buffer_append (buffer, name));
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
@@ -220,8 +220,10 @@ static Result emit_call_fixed(TokenString *out, const ParsedInstruction *insn, i
|
||||
RETURN_IF_ERROR (add (out, reg_l_safe (insn, 0))); /* dest */
|
||||
RETURN_IF_ERROR (add (out, create_assignment_token ()));
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn, 1))); /* callee */
|
||||
/* Call1..Call4 encode an explicit `this` register before the user arguments. */
|
||||
RETURN_IF_ERROR (add (out, create_raw_token (".call")));
|
||||
RETURN_IF_ERROR (add (out, create_left_parenthesis_token ()));
|
||||
for (int i = 2; i < operand_count; i++) { /* args start at operand 2 */
|
||||
for (int i = 2; i < operand_count; i++) {
|
||||
if (i > 2) {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token (",")));
|
||||
}
|
||||
@@ -574,7 +576,7 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
}
|
||||
case OP_CreateInnerEnvironment:
|
||||
{
|
||||
if (is_operand_register (insn->inst, 0) && is_operand_register (insn->inst, 1)) {
|
||||
if (is_operand_register (insn->inst, 0) && is_operand_register (insn->inst, 1)) {
|
||||
RETURN_IF_ERROR (add (out, create_new_inner_environment_token ((int)get_operand_value (insn_c, 0), (int)get_operand_value (insn_c, 1), (int)get_operand_value (insn_c, 2))));
|
||||
} else {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("/*new_inner_env_invalid*/")));
|
||||
@@ -643,18 +645,18 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 1)));
|
||||
u32 sid = insn->arg4;
|
||||
const char *s = NULL;
|
||||
if (insn->hbc_reader && insn->hbc_reader->strings && sid < insn->hbc_reader->header.stringCount) {
|
||||
if (insn->hbc_reader && insn->hbc_reader->strings && sid < insn->hbc_reader->header.stringCount) {
|
||||
s = insn->hbc_reader->strings[sid];
|
||||
}
|
||||
bool ident = true;
|
||||
if (!s || !*s) {
|
||||
if (!s || !*s) {
|
||||
ident = false;
|
||||
} else {
|
||||
if (! (isalpha ((unsigned char)*s) || *s == '_' || *s == '$')) {
|
||||
if (! (isalpha ((unsigned char)*s) || *s == '_' || *s == '$')) {
|
||||
ident = false;
|
||||
}
|
||||
for (const char *p = s + 1; ident && *p; p++) {
|
||||
ident = (isalnum ((unsigned char)*p) || *p == '_' || *p == '$');
|
||||
for (const char *p = s + 1; ident && *p; p++) {
|
||||
ident = (isalnum ((unsigned char)*p) || *p == '_' || *p == '$');
|
||||
}
|
||||
}
|
||||
if (ident) {
|
||||
@@ -707,8 +709,8 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 0)));
|
||||
u32 sid = (op == OP_PutNewOwnByIdShort)? insn->arg3: (op == OP_PutNewOwnById)? insn->arg3
|
||||
: insn->arg4;
|
||||
const char *s = (insn->hbc_reader && insn->hbc_reader->strings && sid < insn->hbc_reader->header.stringCount)? insn->hbc_reader->strings[sid]: NULL;
|
||||
if (s && _hbc_is_js_identifier (s)) {
|
||||
const char *s = (insn->hbc_reader && insn->hbc_reader->strings && sid < insn->hbc_reader->header.stringCount)? insn->hbc_reader->strings[sid]: NULL;
|
||||
if (s && _hbc_is_js_identifier (s)) {
|
||||
RETURN_IF_ERROR (add (out, create_dot_accessor_token ()));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token (s)));
|
||||
} else {
|
||||
@@ -809,7 +811,7 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
case OP_ResumeGenerator:
|
||||
{
|
||||
/* args: result_out, return_bool_out */
|
||||
if (is_operand_register (insn->inst, 0) && is_operand_register (insn->inst, 1)) {
|
||||
if (is_operand_register (insn->inst, 0) && is_operand_register (insn->inst, 1)) {
|
||||
RETURN_IF_ERROR (add (out, create_resume_generator_token ((int)insn->arg1, (int)insn->arg2)));
|
||||
} else {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("/*resume_gen_invalid*/")));
|
||||
@@ -835,7 +837,7 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
case OP_StoreToEnvironment:
|
||||
case OP_StoreToEnvironmentL:
|
||||
{
|
||||
if (is_operand_register (insn->inst, 0) && is_operand_register (insn->inst, 2)) {
|
||||
if (is_operand_register (insn->inst, 0) && is_operand_register (insn->inst, 2)) {
|
||||
RETURN_IF_ERROR (add (out, create_store_to_environment_token ((int)get_operand_value (insn_c, 0), (int)get_operand_value (insn_c, 1), (int)get_operand_value (insn_c, 2))));
|
||||
} else {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("/*env_store_invalid*/")));
|
||||
@@ -878,7 +880,7 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
return r;
|
||||
}
|
||||
r = _hbc_format_object_literal (insn->hbc_reader, insn->arg2, insn->arg3, insn->arg4, insn->arg5, &sb, LITERALS_PRETTY_AUTO, false);
|
||||
Token *t = create_raw_token ((r.code == RESULT_SUCCESS && sb.data)? sb.data: "{ /*object*/ }");
|
||||
Token *t = create_raw_token ((r.code == RESULT_SUCCESS && sb.data)? sb.data: "{ /*object*/ }");
|
||||
_hbc_string_buffer_free (&sb);
|
||||
if (!t) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "oom");
|
||||
@@ -897,7 +899,7 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
return r;
|
||||
}
|
||||
r = _hbc_format_array_literal (insn->hbc_reader, insn->arg3, insn->arg4, &sb, LITERALS_PRETTY_AUTO, false);
|
||||
Token *t = create_raw_token ((r.code == RESULT_SUCCESS && sb.data)? sb.data: "[ /*array*/ ]");
|
||||
Token *t = create_raw_token ((r.code == RESULT_SUCCESS && sb.data)? sb.data: "[ /*array*/ ]");
|
||||
_hbc_string_buffer_free (&sb);
|
||||
if (!t) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "oom");
|
||||
@@ -926,10 +928,10 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 0)));
|
||||
u32 sid = insn->arg2;
|
||||
const char *s = NULL;
|
||||
if (insn->hbc_reader && insn->hbc_reader->strings && sid < insn->hbc_reader->header.stringCount) {
|
||||
if (insn->hbc_reader && insn->hbc_reader->strings && sid < insn->hbc_reader->header.stringCount) {
|
||||
s = insn->hbc_reader->strings[sid];
|
||||
}
|
||||
if (s && _hbc_is_js_identifier (s)) {
|
||||
if (s && _hbc_is_js_identifier (s)) {
|
||||
RETURN_IF_ERROR (add (out, create_dot_accessor_token ()));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token (s)));
|
||||
} else {
|
||||
@@ -1096,11 +1098,14 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
{
|
||||
RETURN_IF_ERROR (add (out, reg_l_safe (insn_c, 0)));
|
||||
RETURN_IF_ERROR (add (out, create_assignment_token ()));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("fn_")));
|
||||
RETURN_IF_ERROR (add (out, num_token_u32 (insn->arg3)));
|
||||
Token *t = create_function_table_index_token (get_operand_value (insn_c, 2), NULL);
|
||||
if (!t) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "oom fti");
|
||||
}
|
||||
RETURN_IF_ERROR (add (out, t));
|
||||
RETURN_IF_ERROR (add (out, create_left_parenthesis_token ()));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("/*argc:")));
|
||||
RETURN_IF_ERROR (add (out, num_token_u32 (insn->arg2)));
|
||||
RETURN_IF_ERROR (add (out, num_token_u32 (get_operand_value (insn_c, 1))));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("*/")));
|
||||
RETURN_IF_ERROR (add (out, create_right_parenthesis_token ()));
|
||||
break;
|
||||
@@ -1161,10 +1166,10 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 0)));
|
||||
u32 sid = insn->arg3;
|
||||
const char *s = NULL;
|
||||
if (insn->hbc_reader && insn->hbc_reader->strings && sid < insn->hbc_reader->header.stringCount) {
|
||||
if (insn->hbc_reader && insn->hbc_reader->strings && sid < insn->hbc_reader->header.stringCount) {
|
||||
s = insn->hbc_reader->strings[sid];
|
||||
}
|
||||
if (s && _hbc_is_js_identifier (s)) {
|
||||
if (s && _hbc_is_js_identifier (s)) {
|
||||
RETURN_IF_ERROR (add (out, create_dot_accessor_token ()));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token (s)));
|
||||
} else {
|
||||
@@ -1182,10 +1187,10 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 0)));
|
||||
u32 sid = (op == OP_PutNewOwnNEById)? insn->arg3: insn->arg4;
|
||||
const char *s = NULL;
|
||||
if (insn->hbc_reader && insn->hbc_reader->strings && sid < insn->hbc_reader->header.stringCount) {
|
||||
if (insn->hbc_reader && insn->hbc_reader->strings && sid < insn->hbc_reader->header.stringCount) {
|
||||
s = insn->hbc_reader->strings[sid];
|
||||
}
|
||||
if (s && _hbc_is_js_identifier (s)) {
|
||||
if (s && _hbc_is_js_identifier (s)) {
|
||||
RETURN_IF_ERROR (add (out, create_dot_accessor_token ()));
|
||||
RETURN_IF_ERROR (add (out, create_raw_token (s)));
|
||||
} else {
|
||||
@@ -1223,8 +1228,8 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
/* Property list operations */
|
||||
case OP_GetPNameList:
|
||||
{
|
||||
if (is_operand_register (insn->inst, 0) && is_operand_register (insn->inst, 1) &&
|
||||
is_operand_register (insn->inst, 2) && is_operand_register (insn->inst, 3)) {
|
||||
if (is_operand_register (insn->inst, 0) && is_operand_register (insn->inst, 1) &&
|
||||
is_operand_register (insn->inst, 2) && is_operand_register (insn->inst, 3)) {
|
||||
RETURN_IF_ERROR (add (out, create_for_in_loop_init_token ((int)get_operand_value (insn_c, 0), (int)get_operand_value (insn_c, 1), (int)get_operand_value (insn_c, 2), (int)get_operand_value (insn_c, 3))));
|
||||
} else {
|
||||
RETURN_IF_ERROR (add (out, create_raw_token ("/*forin_init_invalid*/")));
|
||||
@@ -1233,8 +1238,8 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
}
|
||||
case OP_GetNextPName:
|
||||
{
|
||||
if (is_operand_register (insn->inst, 0) && is_operand_register (insn->inst, 1) &&
|
||||
is_operand_register (insn->inst, 2) && is_operand_register (insn->inst, 3) &&
|
||||
if (is_operand_register (insn->inst, 0) && is_operand_register (insn->inst, 1) &&
|
||||
is_operand_register (insn->inst, 2) && is_operand_register (insn->inst, 3) &&
|
||||
is_operand_register (insn->inst, 4)) {
|
||||
RETURN_IF_ERROR (add (out, create_for_in_loop_next_iter_token ((int)get_operand_value (insn_c, 0), (int)get_operand_value (insn_c, 1), (int)get_operand_value (insn_c, 2), (int)get_operand_value (insn_c, 3), (int)get_operand_value (insn_c, 4))));
|
||||
} else {
|
||||
@@ -1300,7 +1305,7 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
}
|
||||
default:
|
||||
{
|
||||
if (!insn->inst->name || strcmp (insn->inst->name, "Unknown") == 0) {
|
||||
if (!insn->inst->name || strcmp (insn->inst->name, "Unknown") == 0) {
|
||||
char buf[96];
|
||||
u32 skipped = insn->arg1;
|
||||
if (skipped) {
|
||||
@@ -1318,7 +1323,7 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
|
||||
|
||||
/* Generic compare-jump fallback (covers variants not explicitly handled above). */
|
||||
const char *cmp = jump_cmp_operator (op);
|
||||
if (cmp && is_operand_addr (insn->inst, 0) && is_operand_register (insn->inst, 1) && is_operand_register (insn->inst, 2)) {
|
||||
if (cmp && is_operand_addr (insn->inst, 0) && is_operand_register (insn->inst, 1) && is_operand_register (insn->inst, 2)) {
|
||||
i32 rel = (i32)get_operand_value (insn_c, 0);
|
||||
u32 target = compute_target_address (insn, 0);
|
||||
if (rel > 0) {
|
||||
|
||||
+481
-11
@@ -3,6 +3,7 @@
|
||||
#include <hbc/disasm.h>
|
||||
#include <hbc/decompilation/decompiler.h>
|
||||
#include <hbc/decompilation/literals.h>
|
||||
#include <hbc/literals.h>
|
||||
#include <hbc/hermes_encoder.h>
|
||||
#include <hbc/parser.h>
|
||||
#include <hbc/bytecode.h>
|
||||
@@ -117,7 +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 ();
|
||||
@@ -127,6 +137,7 @@ void hbc_close(HBC *hbc) {
|
||||
if (!hbc) {
|
||||
return;
|
||||
}
|
||||
hbc_literals_reset (hbc);
|
||||
_hbc_reader_cleanup (&hbc->reader);
|
||||
free (hbc);
|
||||
}
|
||||
@@ -237,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 ();
|
||||
@@ -412,7 +651,7 @@ Result hbc_dec(const HBCDecodeCtx *ctx, HBCInsnInfo *out) {
|
||||
u8 opcode = ctx->bytes[0];
|
||||
|
||||
/* Get instruction definition */
|
||||
const Instruction *inst = (opcode < isa.count) ? &isa.instructions[opcode] : NULL;
|
||||
const Instruction *inst = (opcode < isa.count)? &isa.instructions[opcode]: NULL;
|
||||
if (!inst || !inst->name) {
|
||||
out->text = strdup ("unk");
|
||||
out->size = 1;
|
||||
@@ -459,19 +698,19 @@ Result hbc_dec(const HBCDecodeCtx *ctx, HBCInsnInfo *out) {
|
||||
pos += 1;
|
||||
break;
|
||||
case OPERAND_TYPE_ADDR8:
|
||||
operand_values[i] = (u32)(i32)(i8)ctx->bytes[pos];
|
||||
operand_values[i] = (u32) (i32) (i8)ctx->bytes[pos];
|
||||
pos += 1;
|
||||
break;
|
||||
case OPERAND_TYPE_UINT16:
|
||||
operand_values[i] = ctx->bytes[pos] | (ctx->bytes[pos + 1] << 8);
|
||||
operand_values[i] = (u32)ctx->bytes[pos] | ((u32)ctx->bytes[pos + 1] << 8);
|
||||
pos += 2;
|
||||
break;
|
||||
case OPERAND_TYPE_REG32:
|
||||
case OPERAND_TYPE_UINT32:
|
||||
case OPERAND_TYPE_IMM32:
|
||||
case OPERAND_TYPE_ADDR32:
|
||||
operand_values[i] = ctx->bytes[pos] | (ctx->bytes[pos + 1] << 8) |
|
||||
(ctx->bytes[pos + 2] << 16) | (ctx->bytes[pos + 3] << 24);
|
||||
operand_values[i] = (u32)ctx->bytes[pos] | ((u32)ctx->bytes[pos + 1] << 8) |
|
||||
((u32)ctx->bytes[pos + 2] << 16) | ((u32)ctx->bytes[pos + 3] << 24);
|
||||
pos += 4;
|
||||
break;
|
||||
case OPERAND_TYPE_DOUBLE:
|
||||
@@ -564,7 +803,7 @@ Result hbc_dec(const HBCDecodeCtx *ctx, HBCInsnInfo *out) {
|
||||
!strncmp (mnemonic, "new_array_with_buffer", strlen ("new_array_with_buffer"));
|
||||
if (is_literal_op) {
|
||||
char *with_comment = build_literal_comment (&ctx->hbc->reader, mnemonic, operand_values, buf);
|
||||
out->text = with_comment ? with_comment : strdup (buf);
|
||||
out->text = with_comment? with_comment: strdup (buf);
|
||||
} else {
|
||||
out->text = strdup (buf);
|
||||
}
|
||||
@@ -592,6 +831,237 @@ Result hbc_dec(const HBCDecodeCtx *ctx, HBCInsnInfo *out) {
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
const char *name; /* exact match, else match prefix */
|
||||
const char *prefix;
|
||||
const char *kind;
|
||||
const char *module; /* NULL for exports */
|
||||
HBCBindingType type;
|
||||
} BindingHint;
|
||||
|
||||
static const BindingHint g_hints[] = {
|
||||
{ "NativeModules", NULL, "native", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "TurboModuleRegistry", NULL, "native", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "__turboModuleProxy", NULL, "native", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "requireNativeComponent", NULL, "native", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "codegenNativeComponent", NULL, "native", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "NativeEventEmitter", NULL, "native", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "RCTDeviceEventEmitter", NULL, "native", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "HermesInternal", NULL, "native", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "require", NULL, "js", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "metroRequire", NULL, "js", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "__r", NULL, "js", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "importScripts", NULL, "js", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "global", NULL, "global", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "globalThis", NULL, "global", "react-native", HBC_BINDING_IMPORT },
|
||||
{ "exports", NULL, "module", NULL, HBC_BINDING_EXPORT },
|
||||
{ "module", NULL, "module", NULL, HBC_BINDING_EXPORT },
|
||||
{ "__esModule", NULL, "module", NULL, HBC_BINDING_EXPORT },
|
||||
{ "default", NULL, "module", NULL, HBC_BINDING_EXPORT },
|
||||
{ NULL, "RCT", "native", "react-native", HBC_BINDING_IMPORT },
|
||||
{ NULL, "Native", "native", "react-native", HBC_BINDING_IMPORT },
|
||||
};
|
||||
#define HINTS_N (sizeof (g_hints) / sizeof (g_hints[0]))
|
||||
|
||||
static bool str_eq(const char *a, const char *b) {
|
||||
if (a == b) {
|
||||
return true;
|
||||
}
|
||||
return a && b && !strcmp (a, b);
|
||||
}
|
||||
|
||||
static bool is_export_marker(const char *s) {
|
||||
return !strcmp (s, "exports") || !strcmp (s, "module");
|
||||
}
|
||||
|
||||
static bool is_export_store(const char *name) {
|
||||
return strstr (name, "put") && strstr (name, "by_id");
|
||||
}
|
||||
|
||||
static Result binding_add(HBCBindings *out, HBCBindingType type, const char *kind,
|
||||
const char *name, const char *module, u32 function_id, u32 offset, u32 string_id) {
|
||||
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");
|
||||
}
|
||||
out->bindings = nb;
|
||||
HBCBinding *b = &nb[out->count++];
|
||||
*b = (HBCBinding){
|
||||
.type = type, .kind = kind, .name = strdup (name),
|
||||
.module = module? strdup (module): NULL,
|
||||
.function_id = function_id, .offset = offset, .string_id = string_id,
|
||||
};
|
||||
if (!b->name || (module && !b->module)) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "OOM");
|
||||
}
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
static size_t decode_operands(const Instruction *inst, const u8 *bytes, size_t len,
|
||||
u32 values[6]) {
|
||||
size_t pos = 1;
|
||||
memset (values, 0, 6 * sizeof (u32));
|
||||
for (int i = 0; i < 6 && inst->operands[i].operand_type != OPERAND_TYPE_NONE; i++) {
|
||||
OperandType t = inst->operands[i].operand_type;
|
||||
size_t need;
|
||||
switch (t) {
|
||||
case OPERAND_TYPE_REG8:
|
||||
case OPERAND_TYPE_UINT8:
|
||||
need = 1;
|
||||
if (pos + need > len) { return 0; }
|
||||
values[i] = bytes[pos];
|
||||
break;
|
||||
case OPERAND_TYPE_ADDR8:
|
||||
need = 1;
|
||||
if (pos + need > len) { return 0; }
|
||||
values[i] = (u32)(i32)(i8)bytes[pos];
|
||||
break;
|
||||
case OPERAND_TYPE_UINT16:
|
||||
need = 2;
|
||||
if (pos + need > len) { return 0; }
|
||||
values[i] = (u32)bytes[pos] | ((u32)bytes[pos + 1] << 8);
|
||||
break;
|
||||
case OPERAND_TYPE_REG32:
|
||||
case OPERAND_TYPE_UINT32:
|
||||
case OPERAND_TYPE_IMM32:
|
||||
case OPERAND_TYPE_ADDR32:
|
||||
need = 4;
|
||||
if (pos + need > len) { return 0; }
|
||||
values[i] = (u32)bytes[pos] | ((u32)bytes[pos + 1] << 8)
|
||||
| ((u32)bytes[pos + 2] << 16) | ((u32)bytes[pos + 3] << 24);
|
||||
break;
|
||||
case OPERAND_TYPE_DOUBLE:
|
||||
need = 8;
|
||||
if (pos + need > len) { return 0; }
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
pos += need;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
static Result scan_hint_matches(HBCBindings *out, const char *s, u32 fid, u32 off, u32 sid) {
|
||||
for (size_t h = 0; h < HINTS_N; h++) {
|
||||
const BindingHint *hint = &g_hints[h];
|
||||
bool match = hint->name? !strcmp (s, hint->name):
|
||||
!strncmp (s, hint->prefix, strlen (hint->prefix));
|
||||
if (!match) {
|
||||
continue;
|
||||
}
|
||||
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");
|
||||
}
|
||||
memset (out, 0, sizeof (*out));
|
||||
HBCReader *r = &hbc->reader;
|
||||
HBCISA isa = hbc_isa_getv (r->header.version);
|
||||
if (!isa.instructions) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "no isa for version");
|
||||
}
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (u32 fid = 0; fid < r->header.functionCount; fid++) {
|
||||
const u8 *code = NULL;
|
||||
u32 size = 0;
|
||||
if (hbc_get_function_bytecode (hbc, fid, &code, &size).code != RESULT_SUCCESS
|
||||
|| !code || !size) {
|
||||
continue;
|
||||
}
|
||||
HBCFunc fi = { 0 };
|
||||
(void)hbc_get_function_info (hbc, fid, &fi);
|
||||
bool has_export_marker = function_has_export_marker (r, code, size, 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) {
|
||||
continue;
|
||||
}
|
||||
u32 sid = values[i];
|
||||
const char *s = (sid < r->header.stringCount && r->strings)? r->strings[sid]: NULL;
|
||||
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));
|
||||
}
|
||||
}
|
||||
pc += inst->binary_size;
|
||||
}
|
||||
}
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
void hbc_free_bindings(HBCBindings *bindings) {
|
||||
if (!bindings || !bindings->bindings) {
|
||||
return;
|
||||
}
|
||||
for (u32 i = 0; i < bindings->count; i++) {
|
||||
free (bindings->bindings[i].name);
|
||||
free (bindings->bindings[i].module);
|
||||
}
|
||||
free (bindings->bindings);
|
||||
bindings->bindings = NULL;
|
||||
bindings->count = 0;
|
||||
}
|
||||
|
||||
/* Encoding functions */
|
||||
|
||||
Result hbc_enc(
|
||||
|
||||
@@ -3,6 +3,13 @@
|
||||
|
||||
#include <hbc/hbc.h>
|
||||
#include <hbc/parser.h>
|
||||
#include <hbc/literals.h>
|
||||
|
||||
typedef struct {
|
||||
HBCLiteralEntry *entries;
|
||||
u32 count;
|
||||
u32 cap;
|
||||
} HBCLiteralCache;
|
||||
|
||||
struct HBC {
|
||||
HBCReader reader;
|
||||
@@ -11,6 +18,7 @@ struct HBC {
|
||||
u32 string_count;
|
||||
const char **strings;
|
||||
u32 function_count;
|
||||
HBCLiteralCache lit_cache;
|
||||
};
|
||||
|
||||
#endif /* HBC_INTERNAL_H */
|
||||
@@ -0,0 +1,519 @@
|
||||
/* Public literals API: lazy cache, formatting, scanning. See
|
||||
* include/hbc/literals.h for the contract. */
|
||||
|
||||
#include <hbc/literals.h>
|
||||
#include <hbc/opcodes.h>
|
||||
#include <hbc/bytecode.h>
|
||||
#include <hbc/decompilation/literals.h>
|
||||
#include "hbc_internal.h"
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* ---------- Global toggles ---------- */
|
||||
|
||||
static bool g_inline_literals = false;
|
||||
|
||||
void hbc_set_inline_literals(bool on) {
|
||||
g_inline_literals = on;
|
||||
}
|
||||
|
||||
bool hbc_get_inline_literals(void) {
|
||||
return g_inline_literals;
|
||||
}
|
||||
|
||||
/* ---------- Pool accessors ---------- */
|
||||
|
||||
u32 hbc_get_pool_paddr(HBC *hbc, HBCLiteralKind kind) {
|
||||
if (!hbc) {
|
||||
return 0;
|
||||
}
|
||||
const HBCReader *r = &hbc->reader;
|
||||
switch (kind) {
|
||||
case HBC_LIT_ARRAY:
|
||||
return r->arrays? r->arrays_paddr: 0;
|
||||
case HBC_LIT_OBJECT:
|
||||
return r->object_keys? r->object_keys_paddr: 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 hbc_get_pool_size(HBC *hbc, HBCLiteralKind kind) {
|
||||
if (!hbc) {
|
||||
return 0;
|
||||
}
|
||||
const HBCReader *r = &hbc->reader;
|
||||
switch (kind) {
|
||||
case HBC_LIT_ARRAY: return r->header.arrayBufferSize;
|
||||
case HBC_LIT_OBJECT: return r->header.objKeyBufferSize;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32 hbc_get_object_values_paddr(HBC *hbc) {
|
||||
return (hbc && hbc->reader.object_values)? hbc->reader.object_values_paddr: 0;
|
||||
}
|
||||
|
||||
u32 hbc_get_object_values_size(HBC *hbc) {
|
||||
return hbc? hbc->reader.header.objValueBufferSize: 0;
|
||||
}
|
||||
|
||||
/* ---------- Formatting ---------- */
|
||||
|
||||
static Result format_raw_impl(HBCReader *r, HBCLiteralKind kind, u32 num_items,
|
||||
u32 primary_id, u32 secondary_id, char **out) {
|
||||
*out = NULL;
|
||||
StringBuffer sb;
|
||||
Result ir = _hbc_string_buffer_init (&sb, 128);
|
||||
if (ir.code != RESULT_SUCCESS) {
|
||||
return ir;
|
||||
}
|
||||
Result fr;
|
||||
if (kind == HBC_LIT_ARRAY) {
|
||||
fr = _hbc_format_array_literal (r, num_items, primary_id, &sb,
|
||||
LITERALS_PRETTY_NEVER, true);
|
||||
} else {
|
||||
/* For v97+ primary_id is the shape index; the internal formatter
|
||||
* expects that convention already (first arg is "key_count" which is
|
||||
* reinterpreted as shape id when version >= 97). */
|
||||
fr = _hbc_format_object_literal (r, num_items, num_items, primary_id,
|
||||
secondary_id, &sb, LITERALS_PRETTY_NEVER, true);
|
||||
}
|
||||
if (fr.code == RESULT_SUCCESS && sb.data && sb.length > 0) {
|
||||
*out = strdup (sb.data);
|
||||
if (!*out) {
|
||||
fr = ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "oom");
|
||||
}
|
||||
}
|
||||
_hbc_string_buffer_free (&sb);
|
||||
return fr;
|
||||
}
|
||||
|
||||
Result hbc_literals_format_raw(HBC *hbc, HBCLiteralKind kind, u32 num_items,
|
||||
u32 primary_id, u32 secondary_id, char **out) {
|
||||
if (!hbc || !out) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "invalid args");
|
||||
}
|
||||
return format_raw_impl (&hbc->reader, kind, num_items, primary_id,
|
||||
secondary_id, out);
|
||||
}
|
||||
|
||||
Result hbc_literals_format_for_opcode(HBC *hbc, u8 opcode, u32 arg3, u32 arg4,
|
||||
u32 arg5, char **out) {
|
||||
if (!hbc || !out) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "invalid args");
|
||||
}
|
||||
switch (opcode) {
|
||||
case OP_NewArrayWithBuffer:
|
||||
case OP_NewArrayWithBufferLong:
|
||||
return hbc_literals_format_raw (hbc, HBC_LIT_ARRAY, arg3, arg4, 0, out);
|
||||
case OP_NewObjectWithBuffer:
|
||||
case OP_NewObjectWithBufferLong:
|
||||
return hbc_literals_format_raw (hbc, HBC_LIT_OBJECT, arg3, arg4, arg5, out);
|
||||
}
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT,
|
||||
"opcode is not a buffer-literal constructor");
|
||||
}
|
||||
|
||||
/* ---------- Cache ---------- */
|
||||
|
||||
static HBCLiteralEntry *cache_find(HBCLiteralCache *c, HBCLiteralKind kind,
|
||||
u32 num_items, u32 primary_id, u32 secondary_id) {
|
||||
for (u32 i = 0; i < c->count; i++) {
|
||||
HBCLiteralEntry *e = &c->entries[i];
|
||||
if (e->kind == kind && e->num_items == num_items
|
||||
&& e->primary_id == primary_id
|
||||
&& e->secondary_id == secondary_id) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static HBCLiteralEntry *cache_alloc(HBCLiteralCache *c) {
|
||||
if (c->count == c->cap) {
|
||||
u32 nc = c->cap? c->cap * 2: 64;
|
||||
HBCLiteralEntry *ne = realloc (c->entries, nc * sizeof (HBCLiteralEntry));
|
||||
if (!ne) {
|
||||
return NULL;
|
||||
}
|
||||
c->entries = ne;
|
||||
c->cap = nc;
|
||||
}
|
||||
HBCLiteralEntry *e = &c->entries[c->count++];
|
||||
memset (e, 0, sizeof (*e));
|
||||
return e;
|
||||
}
|
||||
|
||||
/* Compute paddr for an entry based on its kind/primary_id. For v97+ objects,
|
||||
* primary_id is a shape index — resolve via the shape table. */
|
||||
static u32 entry_paddr(HBC *hbc, HBCLiteralKind kind, u32 primary_id) {
|
||||
const HBCReader *r = &hbc->reader;
|
||||
if (kind == HBC_LIT_ARRAY) {
|
||||
if (!r->arrays) {
|
||||
return 0;
|
||||
}
|
||||
if (primary_id >= r->header.arrayBufferSize) {
|
||||
return 0;
|
||||
}
|
||||
return r->arrays_paddr + primary_id;
|
||||
}
|
||||
/* object */
|
||||
if (r->header.version >= 97 && r->object_shapes
|
||||
&& primary_id < r->object_shape_count) {
|
||||
u32 key_off = r->object_shapes[primary_id].key_buffer_offset;
|
||||
if (r->object_keys && key_off < r->header.objKeyBufferSize) {
|
||||
return r->object_keys_paddr + key_off;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (!r->object_keys || primary_id >= r->header.objKeyBufferSize) {
|
||||
return 0;
|
||||
}
|
||||
return r->object_keys_paddr + primary_id;
|
||||
}
|
||||
|
||||
static Result cache_get_or_create(HBC *hbc, HBCLiteralKind kind, u32 num_items,
|
||||
u32 primary_id, u32 secondary_id, HBCLiteralEntry **out_entry) {
|
||||
HBCLiteralEntry *e = cache_find (&hbc->lit_cache, kind, num_items,
|
||||
primary_id, secondary_id);
|
||||
if (e) {
|
||||
*out_entry = e;
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
e = cache_alloc (&hbc->lit_cache);
|
||||
if (!e) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "oom");
|
||||
}
|
||||
e->kind = kind;
|
||||
e->num_items = num_items;
|
||||
e->primary_id = primary_id;
|
||||
e->secondary_id = secondary_id;
|
||||
e->paddr = entry_paddr (hbc, kind, primary_id);
|
||||
/* Format now (lazy across calls, but eager per key once we've decided to
|
||||
* cache it). The formatted string is small and reused on every access. */
|
||||
char *txt = NULL;
|
||||
Result fr = format_raw_impl (&hbc->reader, kind, num_items, primary_id,
|
||||
secondary_id, &txt);
|
||||
if (fr.code == RESULT_SUCCESS) {
|
||||
e->formatted = txt;
|
||||
}
|
||||
*out_entry = e;
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
Result hbc_literals_get(HBC *hbc, HBCLiteralKind kind, u32 num_items,
|
||||
u32 primary_id, u32 secondary_id, const char **out_formatted) {
|
||||
if (!hbc || !out_formatted) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "invalid args");
|
||||
}
|
||||
HBCLiteralEntry *e;
|
||||
Result r = cache_get_or_create (hbc, kind, num_items, primary_id,
|
||||
secondary_id, &e);
|
||||
if (r.code != RESULT_SUCCESS) {
|
||||
return r;
|
||||
}
|
||||
*out_formatted = e->formatted;
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
static void xref_add(HBCLiteralEntry *e, u32 from_addr) {
|
||||
/* Keep xrefs as a small sorted-by-insertion array. Linear dedup. */
|
||||
for (u32 i = 0; i < e->xref_count; i++) {
|
||||
if (e->xref_addrs[i] == from_addr) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
u32 *na = realloc (e->xref_addrs, (e->xref_count + 1) * sizeof (u32));
|
||||
if (!na) {
|
||||
return;
|
||||
}
|
||||
e->xref_addrs = na;
|
||||
e->xref_addrs[e->xref_count++] = from_addr;
|
||||
}
|
||||
|
||||
Result hbc_literals_register(HBC *hbc, HBCLiteralKind kind, u32 num_items,
|
||||
u32 primary_id, u32 secondary_id, u32 from_addr) {
|
||||
if (!hbc) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "invalid args");
|
||||
}
|
||||
HBCLiteralEntry *e;
|
||||
Result r = cache_get_or_create (hbc, kind, num_items, primary_id,
|
||||
secondary_id, &e);
|
||||
if (r.code != RESULT_SUCCESS) {
|
||||
return r;
|
||||
}
|
||||
if (from_addr) {
|
||||
xref_add (e, from_addr);
|
||||
}
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
Result hbc_literals_list(HBC *hbc, const HBCLiteralEntry **out, u32 *out_count) {
|
||||
if (!hbc || !out || !out_count) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "invalid args");
|
||||
}
|
||||
*out = hbc->lit_cache.entries;
|
||||
*out_count = hbc->lit_cache.count;
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
u32 hbc_literals_count(HBC *hbc) {
|
||||
return hbc? hbc->lit_cache.count: 0;
|
||||
}
|
||||
|
||||
void hbc_literals_reset(HBC *hbc) {
|
||||
if (!hbc) {
|
||||
return;
|
||||
}
|
||||
HBCLiteralCache *c = &hbc->lit_cache;
|
||||
for (u32 i = 0; i < c->count; i++) {
|
||||
free (c->entries[i].formatted);
|
||||
free (c->entries[i].xref_addrs);
|
||||
}
|
||||
free (c->entries);
|
||||
c->entries = NULL;
|
||||
c->count = 0;
|
||||
c->cap = 0;
|
||||
}
|
||||
|
||||
/* ---------- Code-side scan ---------- */
|
||||
|
||||
static bool is_buffer_op(u8 opcode, HBCLiteralKind *out_kind) {
|
||||
switch (opcode) {
|
||||
case OP_NewArrayWithBuffer:
|
||||
case OP_NewArrayWithBufferLong:
|
||||
*out_kind = HBC_LIT_ARRAY;
|
||||
return true;
|
||||
case OP_NewObjectWithBuffer:
|
||||
case OP_NewObjectWithBufferLong:
|
||||
*out_kind = HBC_LIT_OBJECT;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Decode opcode immediates into (num_items, primary, secondary), version-aware.
|
||||
* Returns false if the opcode is not a buffer-literal constructor or if the
|
||||
* buffer would be read out of bounds. */
|
||||
static bool decode_buffer_op(u8 op, u32 version, const u8 *code, u32 size,
|
||||
u32 pc, u32 inst_size, HBCLiteralKind *kind, u32 *num_items, u32 *primary,
|
||||
u32 *secondary) {
|
||||
if (pc + inst_size > size) {
|
||||
return false;
|
||||
}
|
||||
*secondary = 0;
|
||||
if (op == OP_NewArrayWithBuffer) {
|
||||
/* reg8 + u16 prealloc + u16 num + u16 id (v<97 AND v97+) */
|
||||
*kind = HBC_LIT_ARRAY;
|
||||
*num_items = (u32)code[pc + 4] | ((u32)code[pc + 5] << 8);
|
||||
*primary = (u32)code[pc + 6] | ((u32)code[pc + 7] << 8);
|
||||
return true;
|
||||
}
|
||||
if (op == OP_NewArrayWithBufferLong) {
|
||||
/* reg8 + u16 prealloc + u16 num + u32 id */
|
||||
*kind = HBC_LIT_ARRAY;
|
||||
*num_items = (u32)code[pc + 4] | ((u32)code[pc + 5] << 8);
|
||||
*primary = (u32)code[pc + 6] | ((u32)code[pc + 7] << 8)
|
||||
| ((u32)code[pc + 8] << 16) | ((u32)code[pc + 9] << 24);
|
||||
return true;
|
||||
}
|
||||
if (op == OP_NewObjectWithBuffer) {
|
||||
*kind = HBC_LIT_OBJECT;
|
||||
if (version >= 97) {
|
||||
/* reg8 + u16 shape_id + u16 values_id */
|
||||
*primary = (u32)code[pc + 2] | ((u32)code[pc + 3] << 8);
|
||||
*secondary = (u32)code[pc + 4] | ((u32)code[pc + 5] << 8);
|
||||
*num_items = 0; /* resolved via shape table by the formatter */
|
||||
} else {
|
||||
/* reg8 + u16 prealloc + u16 num + u16 keys + u16 vals */
|
||||
*num_items = (u32)code[pc + 4] | ((u32)code[pc + 5] << 8);
|
||||
*primary = (u32)code[pc + 6] | ((u32)code[pc + 7] << 8);
|
||||
*secondary = (u32)code[pc + 8] | ((u32)code[pc + 9] << 8);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (op == OP_NewObjectWithBufferLong) {
|
||||
*kind = HBC_LIT_OBJECT;
|
||||
if (version >= 97) {
|
||||
/* reg8 + u32 shape_id + u32 values_id */
|
||||
*primary = (u32)code[pc + 2] | ((u32)code[pc + 3] << 8)
|
||||
| ((u32)code[pc + 4] << 16) | ((u32)code[pc + 5] << 24);
|
||||
*secondary = (u32)code[pc + 6] | ((u32)code[pc + 7] << 8)
|
||||
| ((u32)code[pc + 8] << 16) | ((u32)code[pc + 9] << 24);
|
||||
*num_items = 0;
|
||||
} else {
|
||||
/* reg8 + u16 prealloc + u16 num + u32 keys + u32 vals */
|
||||
*num_items = (u32)code[pc + 4] | ((u32)code[pc + 5] << 8);
|
||||
*primary = (u32)code[pc + 6] | ((u32)code[pc + 7] << 8)
|
||||
| ((u32)code[pc + 8] << 16) | ((u32)code[pc + 9] << 24);
|
||||
*secondary = (u32)code[pc + 10] | ((u32)code[pc + 11] << 8)
|
||||
| ((u32)code[pc + 12] << 16) | ((u32)code[pc + 13] << 24);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Result hbc_literals_scan_code(HBC *hbc, u32 *out_count) {
|
||||
if (!hbc) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "invalid args");
|
||||
}
|
||||
u32 nfuncs = hbc->reader.header.functionCount;
|
||||
u32 version = hbc->reader.header.version;
|
||||
HBCISA isa = hbc_isa_getv (version);
|
||||
if (!isa.instructions) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "no isa for version");
|
||||
}
|
||||
for (u32 fid = 0; fid < nfuncs; fid++) {
|
||||
const u8 *code = NULL;
|
||||
u32 size = 0;
|
||||
if (hbc_get_function_bytecode (hbc, fid, &code, &size).code
|
||||
!= RESULT_SUCCESS || !code || size == 0) {
|
||||
continue;
|
||||
}
|
||||
HBCFunc fi;
|
||||
u32 fn_paddr = 0;
|
||||
if (hbc_get_function_info (hbc, fid, &fi).code == RESULT_SUCCESS) {
|
||||
fn_paddr = fi.offset;
|
||||
}
|
||||
u32 pc = 0;
|
||||
while (pc < size) {
|
||||
u8 op = code[pc];
|
||||
const Instruction *ins = (op < isa.count)? &isa.instructions[op]: NULL;
|
||||
if (!ins || !ins->name || ins->binary_size == 0) {
|
||||
break;
|
||||
}
|
||||
HBCLiteralKind kind;
|
||||
u32 num_items = 0, primary = 0, secondary = 0;
|
||||
if (decode_buffer_op (op, version, code, size, pc, ins->binary_size,
|
||||
&kind, &num_items, &primary, &secondary)) {
|
||||
u32 call_addr = fn_paddr + pc;
|
||||
hbc_literals_register (hbc, kind, num_items, primary, secondary,
|
||||
call_addr);
|
||||
}
|
||||
(void)is_buffer_op; /* retained for tools that need the predicate */
|
||||
pc += ins->binary_size;
|
||||
}
|
||||
}
|
||||
if (out_count) {
|
||||
*out_count = hbc->lit_cache.count;
|
||||
}
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
/* ---------- Pool-side scan ---------- */
|
||||
|
||||
static bool slp_skip_group(const u8 *base, size_t size, size_t *pos,
|
||||
u8 *out_tag, u32 *out_length) {
|
||||
if (*pos >= size) {
|
||||
return false;
|
||||
}
|
||||
u8 taglen = base[(*pos)++];
|
||||
u8 tag = (taglen >> 4) & 0x7;
|
||||
u32 length = taglen & 0x0F;
|
||||
if (taglen & 0x80) {
|
||||
if (*pos >= size) {
|
||||
return false;
|
||||
}
|
||||
length = ((u32)(taglen & 0x0F) << 8) | base[(*pos)++];
|
||||
}
|
||||
size_t per_value = 0;
|
||||
switch (tag) {
|
||||
case 0: case 1: case 2: per_value = 0; break; /* null/true/false */
|
||||
case 3: per_value = 8; break; /* number */
|
||||
case 4: case 7: per_value = 4; break; /* longstr/int */
|
||||
case 5: per_value = 2; break; /* shortstr */
|
||||
case 6: per_value = 1; break; /* bytestr */
|
||||
default: return false;
|
||||
}
|
||||
if (*pos + per_value * length > size) {
|
||||
return false;
|
||||
}
|
||||
*pos += per_value * length;
|
||||
if (out_tag) {
|
||||
*out_tag = tag;
|
||||
}
|
||||
if (out_length) {
|
||||
*out_length = length;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Result hbc_literals_scan_pool(HBC *hbc, HBCLiteralKind kind,
|
||||
HBCPoolGroup **out, u32 *out_count) {
|
||||
if (!hbc || !out || !out_count) {
|
||||
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "invalid args");
|
||||
}
|
||||
*out = NULL;
|
||||
*out_count = 0;
|
||||
|
||||
/* Object-keys on v97+: enumerate shape table instead of the raw pool. */
|
||||
if (kind == HBC_LIT_OBJECT && hbc->reader.header.version >= 97
|
||||
&& hbc->reader.object_shapes
|
||||
&& hbc->reader.object_shape_count > 0) {
|
||||
size_t n = hbc->reader.object_shape_count;
|
||||
HBCPoolGroup *arr = calloc (n, sizeof (HBCPoolGroup));
|
||||
if (!arr) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "oom");
|
||||
}
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
arr[i].pool_offset = hbc->reader.object_shapes[i].key_buffer_offset;
|
||||
arr[i].num_items = hbc->reader.object_shapes[i].prop_count;
|
||||
arr[i].paddr = hbc->reader.object_keys_paddr + arr[i].pool_offset;
|
||||
arr[i].tag = 0xff; /* N/A for a shape row */
|
||||
}
|
||||
*out = arr;
|
||||
*out_count = (u32)n;
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
const u8 *base;
|
||||
u32 size;
|
||||
u32 paddr;
|
||||
if (kind == HBC_LIT_ARRAY) {
|
||||
base = hbc->reader.arrays;
|
||||
size = hbc->reader.header.arrayBufferSize;
|
||||
paddr = hbc->reader.arrays_paddr;
|
||||
} else {
|
||||
base = hbc->reader.object_keys;
|
||||
size = hbc->reader.header.objKeyBufferSize;
|
||||
paddr = hbc->reader.object_keys_paddr;
|
||||
}
|
||||
if (!base || !size) {
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
u32 cap = 64;
|
||||
HBCPoolGroup *arr = calloc (cap, sizeof (HBCPoolGroup));
|
||||
if (!arr) {
|
||||
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "oom");
|
||||
}
|
||||
u32 n = 0;
|
||||
size_t pos = 0;
|
||||
while (pos < size) {
|
||||
size_t group_start = pos;
|
||||
u8 tag = 0;
|
||||
u32 length = 0;
|
||||
if (!slp_skip_group (base, size, &pos, &tag, &length)) {
|
||||
break;
|
||||
}
|
||||
if (n == cap) {
|
||||
u32 nc = cap * 2;
|
||||
HBCPoolGroup *na = realloc (arr, nc * sizeof (HBCPoolGroup));
|
||||
if (!na) {
|
||||
break;
|
||||
}
|
||||
arr = na;
|
||||
cap = nc;
|
||||
}
|
||||
arr[n].pool_offset = (u32)group_start;
|
||||
arr[n].paddr = paddr + (u32)group_start;
|
||||
arr[n].num_items = length;
|
||||
arr[n].tag = tag;
|
||||
n++;
|
||||
}
|
||||
*out = arr;
|
||||
*out_count = n;
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
+78
-78
@@ -187,89 +187,89 @@ Result hbc_encoder_parse_instruction(HBCEncoder *encoder, const char *asm_line,
|
||||
bool parse_success = false;
|
||||
|
||||
switch (expected_type) {
|
||||
case OPERAND_TYPE_REG8:
|
||||
case OPERAND_TYPE_REG32:
|
||||
/* Register: rN or RN */
|
||||
if (trim_start[0] == 'r' || trim_start[0] == 'R') {
|
||||
operand_values[operand_count] = (u32)strtoul (trim_start + 1, &endptr, 10);
|
||||
if (*endptr == '\0') {
|
||||
parse_success = true;
|
||||
/* Validate register range */
|
||||
if (expected_type == OPERAND_TYPE_REG8 && operand_values[operand_count] > 255) {
|
||||
return ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, "Register number out of range for REG8");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Allow bare numbers for registers too */
|
||||
operand_values[operand_count] = (u32)strtoul (trim_start, &endptr, 10);
|
||||
if (*endptr == '\0') {
|
||||
parse_success = true;
|
||||
/* Validate register range */
|
||||
if (expected_type == OPERAND_TYPE_REG8 && operand_values[operand_count] > 255) {
|
||||
return ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, "Register number out of range for REG8");
|
||||
}
|
||||
case OPERAND_TYPE_REG8:
|
||||
case OPERAND_TYPE_REG32:
|
||||
/* Register: rN or RN */
|
||||
if (trim_start[0] == 'r' || trim_start[0] == 'R') {
|
||||
operand_values[operand_count] = (u32)strtoul (trim_start + 1, &endptr, 10);
|
||||
if (*endptr == '\0') {
|
||||
parse_success = true;
|
||||
/* Validate register range */
|
||||
if (expected_type == OPERAND_TYPE_REG8 && operand_values[operand_count] > 255) {
|
||||
return ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, "Register number out of range for REG8");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case OPERAND_TYPE_UINT8:
|
||||
case OPERAND_TYPE_UINT16:
|
||||
case OPERAND_TYPE_UINT32:
|
||||
case OPERAND_TYPE_IMM32:
|
||||
case OPERAND_TYPE_ADDR8:
|
||||
case OPERAND_TYPE_ADDR32:
|
||||
/* Immediate or address */
|
||||
if (trim_start[0] == '0' && (trim_start[1] == 'x' || trim_start[1] == 'X')) {
|
||||
/* Hex */
|
||||
operand_values[operand_count] = (u32)strtoul (trim_start, &endptr, 16);
|
||||
if (*endptr == '\0') {
|
||||
parse_success = true;
|
||||
}
|
||||
} else {
|
||||
/* Decimal */
|
||||
operand_values[operand_count] = (u32)strtoul (trim_start, &endptr, 10);
|
||||
if (*endptr == '\0') {
|
||||
parse_success = true;
|
||||
} else {
|
||||
/* Allow bare numbers for registers too */
|
||||
operand_values[operand_count] = (u32)strtoul (trim_start, &endptr, 10);
|
||||
if (*endptr == '\0') {
|
||||
parse_success = true;
|
||||
/* Validate register range */
|
||||
if (expected_type == OPERAND_TYPE_REG8 && operand_values[operand_count] > 255) {
|
||||
return ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, "Register number out of range for REG8");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case OPERAND_TYPE_DOUBLE:
|
||||
/* Parse as double literal */
|
||||
{
|
||||
char *endptr_double;
|
||||
double dval = strtod (trim_start, &endptr_double);
|
||||
if (*endptr_double == '\0') {
|
||||
/* Convert double to IEEE 754 bits */
|
||||
u64 bits;
|
||||
memcpy (&bits, &dval, sizeof (double));
|
||||
operand_values[operand_count] = bits;
|
||||
parse_success = true;
|
||||
} else if (trim_start[0] == '0' && (trim_start[1] == 'x' || trim_start[1] == 'X')) {
|
||||
/* Allow hex for bit patterns */
|
||||
operand_values[operand_count] = (u64)strtoull (trim_start, &endptr_double, 16);
|
||||
if (*endptr_double == '\0') {
|
||||
parse_success = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
/* Unknown type, try to parse as number */
|
||||
if (trim_start[0] == '0' && (trim_start[1] == 'x' || trim_start[1] == 'X')) {
|
||||
operand_values[operand_count] = (u32)strtoul (trim_start, &endptr, 16);
|
||||
if (*endptr == '\0') {
|
||||
parse_success = true;
|
||||
}
|
||||
} else {
|
||||
operand_values[operand_count] = (u32)strtoul (trim_start, &endptr, 10);
|
||||
if (*endptr == '\0') {
|
||||
parse_success = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case OPERAND_TYPE_UINT8:
|
||||
case OPERAND_TYPE_UINT16:
|
||||
case OPERAND_TYPE_UINT32:
|
||||
case OPERAND_TYPE_IMM32:
|
||||
case OPERAND_TYPE_ADDR8:
|
||||
case OPERAND_TYPE_ADDR32:
|
||||
/* Immediate or address */
|
||||
if (trim_start[0] == '0' && (trim_start[1] == 'x' || trim_start[1] == 'X')) {
|
||||
/* Hex */
|
||||
operand_values[operand_count] = (u32)strtoul (trim_start, &endptr, 16);
|
||||
if (*endptr == '\0') {
|
||||
parse_success = true;
|
||||
}
|
||||
} else {
|
||||
/* Decimal */
|
||||
operand_values[operand_count] = (u32)strtoul (trim_start, &endptr, 10);
|
||||
if (*endptr == '\0') {
|
||||
parse_success = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case OPERAND_TYPE_DOUBLE:
|
||||
/* Parse as double literal */
|
||||
{
|
||||
char *endptr_double;
|
||||
double dval = strtod (trim_start, &endptr_double);
|
||||
if (*endptr_double == '\0') {
|
||||
/* Convert double to IEEE 754 bits */
|
||||
u64 bits;
|
||||
memcpy (&bits, &dval, sizeof (double));
|
||||
operand_values[operand_count] = bits;
|
||||
parse_success = true;
|
||||
} else if (trim_start[0] == '0' && (trim_start[1] == 'x' || trim_start[1] == 'X')) {
|
||||
/* Allow hex for bit patterns */
|
||||
operand_values[operand_count] = (u64)strtoull (trim_start, &endptr_double, 16);
|
||||
if (*endptr_double == '\0') {
|
||||
parse_success = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
/* Unknown type, try to parse as number */
|
||||
if (trim_start[0] == '0' && (trim_start[1] == 'x' || trim_start[1] == 'X')) {
|
||||
operand_values[operand_count] = (u32)strtoul (trim_start, &endptr, 16);
|
||||
if (*endptr == '\0') {
|
||||
parse_success = true;
|
||||
}
|
||||
} else {
|
||||
operand_values[operand_count] = (u32)strtoul (trim_start, &endptr, 10);
|
||||
if (*endptr == '\0') {
|
||||
parse_success = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (!parse_success) {
|
||||
return ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, "Invalid operand format");
|
||||
|
||||
@@ -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);
|
||||
@@ -1077,6 +1083,7 @@ Result _hbc_reader_read_arrays(HBCReader *reader) {
|
||||
|
||||
/* Read array buffer */
|
||||
if (reader->header.arrayBufferSize > 0) {
|
||||
reader->arrays_paddr = (u32)reader->file_buffer.position;
|
||||
reader->arrays = (u8 *)malloc (reader->header.arrayBufferSize);
|
||||
if (!reader->arrays) {
|
||||
hbc_debug_printf ("Failed to allocate %u bytes for array buffer\n", reader->header.arrayBufferSize);
|
||||
@@ -1108,6 +1115,7 @@ Result _hbc_reader_read_arrays(HBCReader *reader) {
|
||||
reader->header.objKeyBufferSize = 0;
|
||||
} else {
|
||||
/* Safe to read object keys */
|
||||
reader->object_keys_paddr = (u32)reader->file_buffer.position;
|
||||
reader->object_keys = (u8 *)malloc (reader->header.objKeyBufferSize);
|
||||
if (!reader->object_keys) {
|
||||
fprintf (stderr, "Failed to allocate %u bytes for object key buffer\n", reader->header.objKeyBufferSize);
|
||||
@@ -1149,6 +1157,7 @@ Result _hbc_reader_read_arrays(HBCReader *reader) {
|
||||
}
|
||||
reader->literal_values = reader->arrays;
|
||||
reader->object_values = reader->arrays;
|
||||
reader->object_values_paddr = reader->arrays_paddr;
|
||||
return SUCCESS_RESULT ();
|
||||
}
|
||||
|
||||
@@ -1164,6 +1173,7 @@ Result _hbc_reader_read_arrays(HBCReader *reader) {
|
||||
reader->header.objValueBufferSize = 0;
|
||||
} else {
|
||||
/* Safe to read object values */
|
||||
reader->object_values_paddr = (u32)reader->file_buffer.position;
|
||||
reader->object_values = (u8 *)malloc (reader->header.objValueBufferSize);
|
||||
if (!reader->object_values) {
|
||||
fprintf (stderr, "Failed to allocate %u bytes for object value buffer\n", reader->header.objValueBufferSize);
|
||||
@@ -1574,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));
|
||||
@@ -1602,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 (
|
||||
@@ -1703,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 ();
|
||||
}
|
||||
|
||||
@@ -2025,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 $@
|
||||
|
||||
|
||||
|
||||
+10
-4
@@ -2,11 +2,13 @@
|
||||
|
||||
#include <r_anal.h>
|
||||
#include <hbc/hbc.h>
|
||||
#include <hbc/literals.h>
|
||||
#include <hbc/opcodes.h>
|
||||
#include <hbc/bytecode.h>
|
||||
#include <hbc/parser.h>
|
||||
|
||||
#define MAX_OP_SIZE 16
|
||||
#define HBC_VADDR_BASE 0x10000000
|
||||
#ifndef R_ARCH_SYNTAX_CAMEL
|
||||
#define R_ARCH_SYNTAX_CAMEL 6
|
||||
#endif
|
||||
@@ -169,7 +171,7 @@ static void parse_operands_and_set_ptr(RAnalOp *op, const ut8 *bytes, ut32 size,
|
||||
HBCStringMeta meta;
|
||||
Result meta_result = hbc_get_string_meta (hs->hbc, string_id, &meta);
|
||||
if (meta_result.code == RESULT_SUCCESS) {
|
||||
op->ptr = (st64)meta.offset;
|
||||
op->ptr = (st64)(HBC_VADDR_BASE + meta.offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -185,7 +187,7 @@ static void parse_operands_and_set_ptr(RAnalOp *op, const ut8 *bytes, ut32 size,
|
||||
offset = fi.offset;
|
||||
(void)fi.size;
|
||||
(void)fi.param_count;
|
||||
op->ptr = (st64)offset;
|
||||
op->ptr = (st64)(HBC_VADDR_BASE + offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -231,8 +233,12 @@ static bool decode(RArchSession *s, RAnalOp *op, RArchDecodeMask mask) {
|
||||
.resolve_string_ids = true,
|
||||
.string_tables = &string_tables,
|
||||
.hbc = hs->hbc,
|
||||
};
|
||||
if (mask & R_ARCH_OP_MASK_ESIL && mask & R_ARCH_OP_MASK_DISASM) {
|
||||
};
|
||||
/* Inlining of buffer-literal contents into the disasm line is opt-in —
|
||||
* it makes arch.decode O(n) in literal size and breaks r2's O(1) per-op
|
||||
* disasm assumption. Toggle via `e hbc.inline_buffer_literals=true`
|
||||
* (core plugin updates a process-wide flag). */
|
||||
if ((mask & R_ARCH_OP_MASK_DISASM) && hbc_get_inline_literals ()) {
|
||||
ctx.build_objects = true;
|
||||
}
|
||||
|
||||
|
||||
+169
-169
@@ -5,206 +5,206 @@
|
||||
/* Pseudo transformation rules for Hermes bytecode instructions */
|
||||
static const char *pseudo_rules[] = {
|
||||
/* Mov/Load operations */
|
||||
"mov/2/$1 = $2",
|
||||
"mov_long/2/$1 = $2",
|
||||
"loadi8/2/$1 = (int8)$2",
|
||||
"loadu8/2/$1 = (uint8)$2",
|
||||
"loadi16/2/$1 = (int16)$2",
|
||||
"loadu16/2/$1 = (uint16)$2",
|
||||
"loadi32/2/$1 = (int32)$2",
|
||||
"loadu32/2/$1 = (uint32)$2",
|
||||
"load_const_uint8/2/$1 = $2",
|
||||
"load_const_u_int8/2/$1 = $2",
|
||||
"load_const_int/2/$1 = $2",
|
||||
"load_const_double/2/$1 = $2",
|
||||
"load_const_bigint/2/$1 = $2",
|
||||
"load_const_string/2/$1 = $2",
|
||||
"load_const_string_long_index/2/$1 = $2",
|
||||
"load_const_empty/1/$1 = empty",
|
||||
"load_const_undefined/1/$1 = undefined",
|
||||
"load_const_null/1/$1 = null",
|
||||
"load_const_true/1/$1 = true",
|
||||
"load_const_false/1/$1 = false",
|
||||
"load_const_zero/1/$1 = 0",
|
||||
"mov/2/$1 = $2",
|
||||
"mov_long/2/$1 = $2",
|
||||
"loadi8/2/$1 = (int8)$2",
|
||||
"loadu8/2/$1 = (uint8)$2",
|
||||
"loadi16/2/$1 = (int16)$2",
|
||||
"loadu16/2/$1 = (uint16)$2",
|
||||
"loadi32/2/$1 = (int32)$2",
|
||||
"loadu32/2/$1 = (uint32)$2",
|
||||
"load_const_uint8/2/$1 = $2",
|
||||
"load_const_u_int8/2/$1 = $2",
|
||||
"load_const_int/2/$1 = $2",
|
||||
"load_const_double/2/$1 = $2",
|
||||
"load_const_bigint/2/$1 = $2",
|
||||
"load_const_string/2/$1 = $2",
|
||||
"load_const_string_long_index/2/$1 = $2",
|
||||
"load_const_empty/1/$1 = empty",
|
||||
"load_const_undefined/1/$1 = undefined",
|
||||
"load_const_null/1/$1 = null",
|
||||
"load_const_true/1/$1 = true",
|
||||
"load_const_false/1/$1 = false",
|
||||
"load_const_zero/1/$1 = 0",
|
||||
|
||||
/* Property access - get_by_id has 4 args: dest, obj, cache_idx, prop_name */
|
||||
"get_by_id/4/$1 = $2.$4",
|
||||
"get_by_id_long/4/$1 = $2.$4",
|
||||
"get_by_id_short/4/$1 = $2.$4",
|
||||
"get_by_val/3/$1 = $2[$3]",
|
||||
"put_by_id/4/$1.$4 = $3",
|
||||
"put_by_id_long/4/$1.$4 = $3",
|
||||
"put_by_val/3/$1[$2] = $3",
|
||||
"put_new_own_by_id/3/$1.$3 = $2",
|
||||
"put_new_own_by_id_short/3/$1.$3 = $2",
|
||||
"put_new_own_by_id_long/3/$1.$3 = $2",
|
||||
"put_new_own_ne_by_id/3/$1.$3 = $2",
|
||||
"put_new_own_ne_by_id_long/3/$1.$3 = $2",
|
||||
"try_get_by_id/4/$1 = try_get($2, $4)",
|
||||
"try_get_by_id_long/4/$1 = try_get($2, $4)",
|
||||
"try_put_by_id/4/try_put($1, $4, $3)",
|
||||
"try_put_by_id_long/4/try_put($1, $4, $3)",
|
||||
"del_by_id/3/delete $1.$3",
|
||||
"del_by_id_long/3/delete $1.$3",
|
||||
"del_by_val/3/delete $1[$2]",
|
||||
"get_by_id/4/$1 = $2.$4",
|
||||
"get_by_id_long/4/$1 = $2.$4",
|
||||
"get_by_id_short/4/$1 = $2.$4",
|
||||
"get_by_val/3/$1 = $2[$3]",
|
||||
"put_by_id/4/$1.$4 = $3",
|
||||
"put_by_id_long/4/$1.$4 = $3",
|
||||
"put_by_val/3/$1[$2] = $3",
|
||||
"put_new_own_by_id/3/$1.$3 = $2",
|
||||
"put_new_own_by_id_short/3/$1.$3 = $2",
|
||||
"put_new_own_by_id_long/3/$1.$3 = $2",
|
||||
"put_new_own_ne_by_id/3/$1.$3 = $2",
|
||||
"put_new_own_ne_by_id_long/3/$1.$3 = $2",
|
||||
"try_get_by_id/4/$1 = try_get($2, $4)",
|
||||
"try_get_by_id_long/4/$1 = try_get($2, $4)",
|
||||
"try_put_by_id/4/try_put($1, $4, $3)",
|
||||
"try_put_by_id_long/4/try_put($1, $4, $3)",
|
||||
"del_by_id/3/delete $1.$3",
|
||||
"del_by_id_long/3/delete $1.$3",
|
||||
"del_by_val/3/delete $1[$2]",
|
||||
|
||||
/* Arithmetic operations */
|
||||
"add/3/$1 = $2 + $3",
|
||||
"add_n/3/$1 = $2 + $3",
|
||||
"add_32/3/$1 = (int32)($2 + $3)",
|
||||
"add_empty_string/2/$1 = \"\" + $2",
|
||||
"sub/3/$1 = $2 - $3",
|
||||
"sub_n/3/$1 = $2 - $3",
|
||||
"sub_32/3/$1 = (int32)($2 - $3)",
|
||||
"mul/3/$1 = $2 * $3",
|
||||
"mul_n/3/$1 = $2 * $3",
|
||||
"mul_32/3/$1 = (int32)($2 * $3)",
|
||||
"div/3/$1 = $2 / $3",
|
||||
"div_n/3/$1 = $2 / $3",
|
||||
"divi32/3/$1 = (int32)($2 / $3)",
|
||||
"divu32/3/$1 = (uint32)($2 / $3)",
|
||||
"mod/3/$1 = $2 % $3",
|
||||
"inc/2/$1 = $2 + 1",
|
||||
"dec/2/$1 = $2 - 1",
|
||||
"negate/2/$1 = -$2",
|
||||
"add/3/$1 = $2 + $3",
|
||||
"add_n/3/$1 = $2 + $3",
|
||||
"add_32/3/$1 = (int32)($2 + $3)",
|
||||
"add_empty_string/2/$1 = \"\" + $2",
|
||||
"sub/3/$1 = $2 - $3",
|
||||
"sub_n/3/$1 = $2 - $3",
|
||||
"sub_32/3/$1 = (int32)($2 - $3)",
|
||||
"mul/3/$1 = $2 * $3",
|
||||
"mul_n/3/$1 = $2 * $3",
|
||||
"mul_32/3/$1 = (int32)($2 * $3)",
|
||||
"div/3/$1 = $2 / $3",
|
||||
"div_n/3/$1 = $2 / $3",
|
||||
"divi32/3/$1 = (int32)($2 / $3)",
|
||||
"divu32/3/$1 = (uint32)($2 / $3)",
|
||||
"mod/3/$1 = $2 % $3",
|
||||
"inc/2/$1 = $2 + 1",
|
||||
"dec/2/$1 = $2 - 1",
|
||||
"negate/2/$1 = -$2",
|
||||
|
||||
/* Bitwise operations */
|
||||
"bit_and/3/$1 = $2 & $3",
|
||||
"bit_or/3/$1 = $2 | $3",
|
||||
"bit_xor/3/$1 = $2 ^ $3",
|
||||
"bit_not/2/$1 = ~$2",
|
||||
"lshift/3/$1 = $2 << $3",
|
||||
"rshift/3/$1 = $2 >> $3",
|
||||
"urshift/3/$1 = (unsigned)$2 >> $3",
|
||||
"bit_and/3/$1 = $2 & $3",
|
||||
"bit_or/3/$1 = $2 | $3",
|
||||
"bit_xor/3/$1 = $2 ^ $3",
|
||||
"bit_not/2/$1 = ~$2",
|
||||
"lshift/3/$1 = $2 << $3",
|
||||
"rshift/3/$1 = $2 >> $3",
|
||||
"urshift/3/$1 = (unsigned)$2 >> $3",
|
||||
|
||||
/* Logical operations */
|
||||
"not/2/$1 = !$2",
|
||||
"eq/3/$1 = $2 == $3",
|
||||
"strict_eq/3/$1 = $2 === $3",
|
||||
"neq/3/$1 = $2 != $3",
|
||||
"strict_neq/3/$1 = $2 !== $3",
|
||||
"less/3/$1 = $2 < $3",
|
||||
"greater/3/$1 = $2 > $3",
|
||||
"less_eq/3/$1 = $2 <= $3",
|
||||
"greater_eq/3/$1 = $2 >= $3",
|
||||
"not/2/$1 = !$2",
|
||||
"eq/3/$1 = $2 == $3",
|
||||
"strict_eq/3/$1 = $2 === $3",
|
||||
"neq/3/$1 = $2 != $3",
|
||||
"strict_neq/3/$1 = $2 !== $3",
|
||||
"less/3/$1 = $2 < $3",
|
||||
"greater/3/$1 = $2 > $3",
|
||||
"less_eq/3/$1 = $2 <= $3",
|
||||
"greater_eq/3/$1 = $2 >= $3",
|
||||
|
||||
/* Type operations */
|
||||
"typeof/2/$1 = typeof($2)",
|
||||
"is_in/3/$1 = ($2 in $3)",
|
||||
"instanceof/3/$1 = ($2 instanceof $3)",
|
||||
"to_number/2/$1 = Number($2)",
|
||||
"to_numeric/2/$1 = Numeric($2)",
|
||||
"to_int32/2/$1 = (int32)$2",
|
||||
"typeof/2/$1 = typeof($2)",
|
||||
"is_in/3/$1 = ($2 in $3)",
|
||||
"instanceof/3/$1 = ($2 instanceof $3)",
|
||||
"to_number/2/$1 = Number($2)",
|
||||
"to_numeric/2/$1 = Numeric($2)",
|
||||
"to_int32/2/$1 = (int32)$2",
|
||||
|
||||
/* Object/Array creation */
|
||||
"new_object/1/$1 = {}",
|
||||
"new_object_with_parent/2/$1 = Object.create($2)",
|
||||
"new_object_with_buffer/5/$1 = {/*buffer*/}",
|
||||
"new_object_with_buffer_long/5/$1 = {/*buffer*/}",
|
||||
"new_array/2/$1 = []",
|
||||
"new_array_with_buffer/4/$1 = [/*buffer*/]",
|
||||
"new_array_with_buffer_long/4/$1 = [/*buffer*/]",
|
||||
"create_closure/3/$1 = closure($3)",
|
||||
"create_closure_long_index/3/$1 = closure($3)",
|
||||
"create_generator_closure/3/$1 = gen_closure($3)",
|
||||
"create_generator/3/$1 = generator($3)",
|
||||
"create_regexp/3/$1 = regexp($3)",
|
||||
"new_object/1/$1 = {}",
|
||||
"new_object_with_parent/2/$1 = Object.create($2)",
|
||||
"new_object_with_buffer/5/$1 = {/*buffer*/}",
|
||||
"new_object_with_buffer_long/5/$1 = {/*buffer*/}",
|
||||
"new_array/2/$1 = []",
|
||||
"new_array_with_buffer/4/$1 = [/*buffer*/]",
|
||||
"new_array_with_buffer_long/4/$1 = [/*buffer*/]",
|
||||
"create_closure/3/$1 = closure($3)",
|
||||
"create_closure_long_index/3/$1 = closure($3)",
|
||||
"create_generator_closure/3/$1 = gen_closure($3)",
|
||||
"create_generator/3/$1 = generator($3)",
|
||||
"create_regexp/3/$1 = regexp($3)",
|
||||
|
||||
/* Control flow */
|
||||
"unreachable/0/unreachable",
|
||||
"jmp/1/goto $1",
|
||||
"jmp_long/1/goto $1",
|
||||
"name/3/if ($2) goto $1",
|
||||
"name##_long/2/if ($2) goto $1",
|
||||
"jmp_true/2/if ($2) goto $1",
|
||||
"jmp_true_long/2/if ($2) goto $1",
|
||||
"jmp_false/2/if (!$2) goto $1",
|
||||
"jmp_false_long/2/if (!$2) goto $1",
|
||||
"jmp_undefined/2/if ($2 === undefined) goto $1",
|
||||
"jmp_undefined_long/2/if ($2 === undefined) goto $1",
|
||||
"jless/3/if ($1 < $2) goto $3",
|
||||
"jless_long/3/if ($1 < $2) goto $3",
|
||||
"jless_eq/3/if ($1 <= $2) goto $3",
|
||||
"jless_eq_long/3/if ($1 <= $2) goto $3",
|
||||
"jgreater/3/if ($1 > $2) goto $3",
|
||||
"jgreater_long/3/if ($1 > $2) goto $3",
|
||||
"jgreater_eq/3/if ($1 >= $2) goto $3",
|
||||
"jgreater_eq_long/3/if ($1 >= $2) goto $3",
|
||||
"jequal/3/if ($1 == $2) goto $3",
|
||||
"jequal_long/3/if ($1 == $2) goto $3",
|
||||
"jstrict_equal/3/if ($1 === $2) goto $3",
|
||||
"jstrict_equal_long/3/if ($1 === $2) goto $3",
|
||||
"jnot_equal/3/if ($1 != $2) goto $3",
|
||||
"jnot_equal_long/3/if ($1 != $2) goto $3",
|
||||
"jstrict_not_equal/3/if ($1 !== $2) goto $3",
|
||||
"jstrict_not_equal_long/3/if ($1 !== $2) goto $3",
|
||||
"unreachable/0/unreachable",
|
||||
"jmp/1/goto $1",
|
||||
"jmp_long/1/goto $1",
|
||||
"name/3/if ($2) goto $1",
|
||||
"name##_long/2/if ($2) goto $1",
|
||||
"jmp_true/2/if ($2) goto $1",
|
||||
"jmp_true_long/2/if ($2) goto $1",
|
||||
"jmp_false/2/if (!$2) goto $1",
|
||||
"jmp_false_long/2/if (!$2) goto $1",
|
||||
"jmp_undefined/2/if ($2 === undefined) goto $1",
|
||||
"jmp_undefined_long/2/if ($2 === undefined) goto $1",
|
||||
"jless/3/if ($1 < $2) goto $3",
|
||||
"jless_long/3/if ($1 < $2) goto $3",
|
||||
"jless_eq/3/if ($1 <= $2) goto $3",
|
||||
"jless_eq_long/3/if ($1 <= $2) goto $3",
|
||||
"jgreater/3/if ($1 > $2) goto $3",
|
||||
"jgreater_long/3/if ($1 > $2) goto $3",
|
||||
"jgreater_eq/3/if ($1 >= $2) goto $3",
|
||||
"jgreater_eq_long/3/if ($1 >= $2) goto $3",
|
||||
"jequal/3/if ($1 == $2) goto $3",
|
||||
"jequal_long/3/if ($1 == $2) goto $3",
|
||||
"jstrict_equal/3/if ($1 === $2) goto $3",
|
||||
"jstrict_equal_long/3/if ($1 === $2) goto $3",
|
||||
"jnot_equal/3/if ($1 != $2) goto $3",
|
||||
"jnot_equal_long/3/if ($1 != $2) goto $3",
|
||||
"jstrict_not_equal/3/if ($1 !== $2) goto $3",
|
||||
"jstrict_not_equal_long/3/if ($1 !== $2) goto $3",
|
||||
|
||||
/* Function calls and returns */
|
||||
/* call: result = callee (this, args...) */
|
||||
"call/2/$1($2)",
|
||||
"call1/3/$1 = $2($3)",
|
||||
"call2/4/$1 = $2($4)",
|
||||
"call3/5/$1 = $2($4, $5)",
|
||||
"call4/6/$1 = $2($4, $5, $6)",
|
||||
"call_long/2/$1($2)",
|
||||
"call_builtin/3/$1 = builtin.$2($3)",
|
||||
"call_builtin_long/3/$1 = builtin.$2($3)",
|
||||
"call_direct/3/$1 = $2($3)",
|
||||
"call_direct_long_index/3/$1 = $2($3)",
|
||||
"construct/3/$1 = new $2($3)",
|
||||
"construct_long/3/$1 = new $2($3)",
|
||||
"ret/1/return $1",
|
||||
"ret_undefined/0/return",
|
||||
"call/2/$1($2)",
|
||||
"call1/3/$1 = $2($3)",
|
||||
"call2/4/$1 = $2($4)",
|
||||
"call3/5/$1 = $2($4, $5)",
|
||||
"call4/6/$1 = $2($4, $5, $6)",
|
||||
"call_long/2/$1($2)",
|
||||
"call_builtin/3/$1 = builtin.$2($3)",
|
||||
"call_builtin_long/3/$1 = builtin.$2($3)",
|
||||
"call_direct/3/$1 = $2($3)",
|
||||
"call_direct_long_index/3/$1 = $2($3)",
|
||||
"construct/3/$1 = new $2($3)",
|
||||
"construct_long/3/$1 = new $2($3)",
|
||||
"ret/1/return $1",
|
||||
"ret_undefined/0/return",
|
||||
|
||||
/* Exception handling */
|
||||
"throw/1/throw $1",
|
||||
"throw_if_empty/2/if (empty($1)) throw $2",
|
||||
"catch/2/$1 = catch $2",
|
||||
"throw/1/throw $1",
|
||||
"throw_if_empty/2/if (empty($1)) throw $2",
|
||||
"catch/2/$1 = catch $2",
|
||||
|
||||
/* Parameter handling */
|
||||
"load_param/2/$1 = param[$2]",
|
||||
"load_param_long/2/$1 = param[$2]",
|
||||
"reify_arguments/1/arguments = reify($1)",
|
||||
"get_arguments_length/2/$1 = arguments.length",
|
||||
"get_arguments_prop_by_val/3/$1 = arguments[$2]",
|
||||
"load_param/2/$1 = param[$2]",
|
||||
"load_param_long/2/$1 = param[$2]",
|
||||
"reify_arguments/1/arguments = reify($1)",
|
||||
"get_arguments_length/2/$1 = arguments.length",
|
||||
"get_arguments_prop_by_val/3/$1 = arguments[$2]",
|
||||
|
||||
/* Environment operations */
|
||||
"get_environment/2/$1 = env[$2]",
|
||||
"load_from_environment/3/$1 = $2[$3]",
|
||||
"load_from_environment_l/3/$1 = $2[$3]",
|
||||
"store_to_environment/3/$2[$3] = $1",
|
||||
"store_to_environment_l/3/$2[$3] = $1",
|
||||
"store_np_to_environment/3/$2[$3] = $1",
|
||||
"store_np_to_environment_l/3/$2[$3] = $1",
|
||||
"create_environment/1/$1 = new_env()",
|
||||
"create_inner_environment/1/$1 = new_inner_env()",
|
||||
"get_environment/2/$1 = env[$2]",
|
||||
"load_from_environment/3/$1 = $2[$3]",
|
||||
"load_from_environment_l/3/$1 = $2[$3]",
|
||||
"store_to_environment/3/$2[$3] = $1",
|
||||
"store_to_environment_l/3/$2[$3] = $1",
|
||||
"store_np_to_environment/3/$2[$3] = $1",
|
||||
"store_np_to_environment_l/3/$2[$3] = $1",
|
||||
"create_environment/1/$1 = new_env()",
|
||||
"create_inner_environment/1/$1 = new_inner_env()",
|
||||
|
||||
/* Generator operations */
|
||||
"start_generator/0/start_generator()",
|
||||
"resume_generator/2/$1 = resume_generator($2)",
|
||||
"save_generator/1/save_generator($1)",
|
||||
"complete_generator/0/complete_generator()",
|
||||
"start_generator/0/start_generator()",
|
||||
"resume_generator/2/$1 = resume_generator($2)",
|
||||
"save_generator/1/save_generator($1)",
|
||||
"complete_generator/0/complete_generator()",
|
||||
|
||||
/* Miscellaneous */
|
||||
"debugger/0/debugger",
|
||||
"profile_point/1/profile($1)",
|
||||
"select_object/3/$1 = select($2, $3)",
|
||||
"catch/2/$1 = catch_value($2)",
|
||||
"get_builtin_closure/2/$1 = builtin_closure($2)",
|
||||
"get_global_object/1/$1 = global",
|
||||
"get_new_target/1/$1 = new.target",
|
||||
"get_this_ns/1/$1 = this",
|
||||
"coerce_this_ns/1/$1 = coerce_this()",
|
||||
"debugger/0/debugger",
|
||||
"profile_point/1/profile($1)",
|
||||
"select_object/3/$1 = select($2, $3)",
|
||||
"catch/2/$1 = catch_value($2)",
|
||||
"get_builtin_closure/2/$1 = builtin_closure($2)",
|
||||
"get_global_object/1/$1 = global",
|
||||
"get_new_target/1/$1 = new.target",
|
||||
"get_this_ns/1/$1 = this",
|
||||
"coerce_this_ns/1/$1 = coerce_this()",
|
||||
|
||||
/* Iterator operations */
|
||||
"iterator_begin/2/$1 = iterator_begin($2)",
|
||||
"iterator_next/3/$1 = iterator_next($2, $3)",
|
||||
"iterator_close/2/iterator_close($1, $2)",
|
||||
"get_pname_list/2/$1 = property_names($2)",
|
||||
"get_next_pname/4/$1 = next_property_name($2, $3, $4)",
|
||||
"iterator_begin/2/$1 = iterator_begin($2)",
|
||||
"iterator_next/3/$1 = iterator_next($2, $3)",
|
||||
"iterator_close/2/iterator_close($1, $2)",
|
||||
"get_pname_list/2/$1 = property_names($2)",
|
||||
"get_next_pname/4/$1 = next_property_name($2, $3, $4)",
|
||||
|
||||
/* Switch */
|
||||
"switch_imm/3/switch($1) { cases in $2 }",
|
||||
"switch_imm/3/switch($1) { cases in $2 }",
|
||||
|
||||
NULL
|
||||
};
|
||||
|
||||
+187
-4
@@ -1,7 +1,9 @@
|
||||
/* 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"
|
||||
|
||||
#define HEADER_MAGIC 0x1f1903c103bc1fc6ULL
|
||||
@@ -131,7 +133,7 @@ static ut64 resolve_entrypoint(RBinFile *bf, HBC *hbc) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void fill_info(RBinInfo *ret, const char *file_path, bool has_version, ut32 version) {
|
||||
static void fill_info(RBinInfo *ret, const char *file_path, bool has_version, ut32 version, bool has_source_lines) {
|
||||
ret->file = file_path? strdup (file_path): NULL;
|
||||
ret->bclass = strdup ("Hermes bytecode");
|
||||
ret->rclass = strdup ("hermes");
|
||||
@@ -141,12 +143,15 @@ static void fill_info(RBinInfo *ret, const char *file_path, bool has_version, ut
|
||||
ret->type = strdup ("Hermes bytecode");
|
||||
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) {
|
||||
@@ -155,6 +160,10 @@ static RBinInfo *bininfo(RBinFile *bf) {
|
||||
has_version = true;
|
||||
version = hh.version;
|
||||
}
|
||||
/* Cheap: answered from the DebugInfoHeader parsed at load time.
|
||||
* Reflects file truth (does this file carry source lines?) regardless
|
||||
* of bin.dbginfo — that setting only gates whether we decode them. */
|
||||
has_source_lines = hbc_has_source_lines (hbc);
|
||||
}
|
||||
|
||||
/* Fallback: parse version directly from buffer */
|
||||
@@ -167,10 +176,30 @@ static RBinInfo *bininfo(RBinFile *bf) {
|
||||
}
|
||||
}
|
||||
|
||||
fill_info (ret, bf->file, has_version, version);
|
||||
fill_info (ret, bf->file, has_version, version, has_source_lines);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Helper: append a section if the pool is present. Owns no strings — `name`
|
||||
* is duplicated into a heap-allocated section name via strdup. */
|
||||
static void add_pool_section(RList *list, const char *name, ut64 paddr, ut64 size) {
|
||||
if (!size || !paddr) {
|
||||
return;
|
||||
}
|
||||
RBinSection *s = R_NEW0 (RBinSection);
|
||||
if (!s) {
|
||||
return;
|
||||
}
|
||||
s->name = strdup (name);
|
||||
s->size = size;
|
||||
s->vsize = size;
|
||||
s->paddr = paddr;
|
||||
s->vaddr = HBC_VADDR_BASE + paddr;
|
||||
s->perm = R_PERM_R;
|
||||
s->add = true;
|
||||
r_list_append (list, s);
|
||||
}
|
||||
|
||||
static RList *sections(RBinFile *bf) {
|
||||
RList *sections = r_list_newf ((RListFree)free);
|
||||
if (!sections) {
|
||||
@@ -183,9 +212,25 @@ static RList *sections(RBinFile *bf) {
|
||||
section->vsize = section->size;
|
||||
section->paddr = 0;
|
||||
section->vaddr = HBC_VADDR_BASE;
|
||||
section->perm = R_PERM_R;
|
||||
section->perm = R_PERM_RX;
|
||||
section->add = true;
|
||||
r_list_append (sections, section);
|
||||
|
||||
/* SLP pool subsections — gives `iS` a quick map of where the buffer
|
||||
* literals live, and lets users `s` / `pd @` straight into them. */
|
||||
HBC *hbc = get_hbc (bf);
|
||||
if (hbc) {
|
||||
add_pool_section (sections, "slp.arrays",
|
||||
hbc_get_pool_paddr (hbc, HBC_LIT_ARRAY),
|
||||
hbc_get_pool_size (hbc, HBC_LIT_ARRAY));
|
||||
add_pool_section (sections, "slp.object_keys",
|
||||
hbc_get_pool_paddr (hbc, HBC_LIT_OBJECT),
|
||||
hbc_get_pool_size (hbc, HBC_LIT_OBJECT));
|
||||
add_pool_section (sections, "slp.object_values",
|
||||
hbc_get_object_values_paddr (hbc),
|
||||
hbc_get_object_values_size (hbc));
|
||||
}
|
||||
|
||||
return sections;
|
||||
}
|
||||
|
||||
@@ -210,6 +255,33 @@ static ut64 baddr(RBinFile *bf R_UNUSED) {
|
||||
return HBC_VADDR_BASE;
|
||||
}
|
||||
|
||||
static void append_binding_symbols(RList *symbols, HBC *hbc) {
|
||||
HBCBindings bindings = { 0 };
|
||||
if (hbc_scan_bindings (hbc, &bindings).code != RESULT_SUCCESS) {
|
||||
return;
|
||||
}
|
||||
for (u32 i = 0; i < bindings.count; i++) {
|
||||
HBCBinding *b = &bindings.bindings[i];
|
||||
if (b->type != HBC_BINDING_EXPORT) {
|
||||
continue;
|
||||
}
|
||||
RBinSymbol *sym = R_NEW0 (RBinSymbol);
|
||||
char *nm = r_str_newf ("hbc.export.%s.%s", b->kind? b->kind: "js", b->name);
|
||||
sym->name = r_bin_name_new (nm);
|
||||
r_bin_name_filtered (sym->name, nm);
|
||||
sym->paddr = b->offset;
|
||||
sym->vaddr = HBC_VADDR_BASE + b->offset;
|
||||
sym->size = 1;
|
||||
sym->ordinal = b->string_id;
|
||||
sym->type = R_BIN_TYPE_OBJECT_STR;
|
||||
sym->bind = R_BIN_BIND_GLOBAL_STR;
|
||||
sym->bits = 32;
|
||||
r_list_append (symbols, sym);
|
||||
free (nm);
|
||||
}
|
||||
hbc_free_bindings (&bindings);
|
||||
}
|
||||
|
||||
static RList *symbols(RBinFile *bf) {
|
||||
RList *symbols = r_list_newf ((RListFree)r_bin_symbol_free);
|
||||
HBC *hbc = get_hbc (bf);
|
||||
@@ -263,9 +335,118 @@ static RList *symbols(RBinFile *bf) {
|
||||
free (san);
|
||||
}
|
||||
|
||||
/* SLP pool groups as symbols — one per group discovered by the
|
||||
* pool-side linear scan. Lets users see `lit.arr.* / lit.obj.*` in `is`
|
||||
* without needing to run pd:hLs. The scan is linear in pool size and
|
||||
* runs at RBin load time. */
|
||||
for (int k = 0; k < 2; k++) {
|
||||
HBCLiteralKind kind = k? HBC_LIT_OBJECT: HBC_LIT_ARRAY;
|
||||
const char *prefix = k? "lit.obj.": "lit.arr.";
|
||||
HBCPoolGroup *groups = NULL;
|
||||
u32 n = 0;
|
||||
if (hbc_literals_scan_pool (hbc, kind, &groups, &n).code
|
||||
!= RESULT_SUCCESS || !groups) {
|
||||
continue;
|
||||
}
|
||||
for (u32 i = 0; i < n; i++) {
|
||||
RBinSymbol *sym = R_NEW0 (RBinSymbol);
|
||||
char *nm = r_str_newf ("%s0x%x", prefix, groups[i].paddr);
|
||||
sym->name = r_bin_name_new (nm);
|
||||
r_bin_name_filtered (sym->name, nm);
|
||||
sym->paddr = groups[i].paddr;
|
||||
sym->vaddr = HBC_VADDR_BASE + groups[i].paddr;
|
||||
sym->size = 1;
|
||||
sym->type = R_BIN_TYPE_OBJECT_STR;
|
||||
sym->bits = 32;
|
||||
r_list_append (symbols, sym);
|
||||
free (nm);
|
||||
}
|
||||
free (groups);
|
||||
}
|
||||
|
||||
append_binding_symbols (symbols, hbc);
|
||||
return symbols;
|
||||
}
|
||||
|
||||
static RList *imports(RBinFile *bf) {
|
||||
RList *imports = r_list_newf ((RListFree)r_bin_import_free);
|
||||
HBC *hbc = get_hbc (bf);
|
||||
if (!imports || !hbc) {
|
||||
return imports;
|
||||
}
|
||||
HBCBindings bindings = { 0 };
|
||||
if (hbc_scan_bindings (hbc, &bindings).code != RESULT_SUCCESS) {
|
||||
return imports;
|
||||
}
|
||||
for (u32 i = 0; i < bindings.count; i++) {
|
||||
HBCBinding *b = &bindings.bindings[i];
|
||||
if (b->type != HBC_BINDING_IMPORT) {
|
||||
continue;
|
||||
}
|
||||
RBinImport *imp = R_NEW0 (RBinImport);
|
||||
char *nm = r_str_newf ("%s.%s", b->kind? b->kind: "js", b->name);
|
||||
imp->name = r_bin_name_new (nm);
|
||||
r_bin_name_filtered (imp->name, nm);
|
||||
imp->libname = strdup (b->module? b->module: "hermes");
|
||||
imp->bind = "NONE";
|
||||
imp->type = "FUNC";
|
||||
imp->ordinal = b->string_id;
|
||||
imp->is_imported = true;
|
||||
r_list_append (imports, imp);
|
||||
free (nm);
|
||||
}
|
||||
hbc_free_bindings (&bindings);
|
||||
return imports;
|
||||
}
|
||||
|
||||
static 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(RBinFile *bf) {
|
||||
RList *ret = r_list_newf ((RListFree)r_bin_string_free);
|
||||
HBC *hbc = get_hbc (bf);
|
||||
@@ -304,7 +485,7 @@ static RList *strings(RBinFile *bf) {
|
||||
}
|
||||
|
||||
ptr->paddr = meta.offset;
|
||||
ptr->vaddr = meta.offset;
|
||||
ptr->vaddr = HBC_VADDR_BASE + meta.offset;
|
||||
ptr->size = meta.isUTF16? (meta.length * 2): meta.length;
|
||||
ptr->length = meta.length;
|
||||
ptr->ordinal = i;
|
||||
@@ -329,6 +510,8 @@ const RBinPlugin r_bin_plugin_r2hermes = {
|
||||
.sections = §ions,
|
||||
.baddr = &baddr,
|
||||
.symbols = &symbols,
|
||||
.imports = &imports,
|
||||
.lines = &lines,
|
||||
.strings = &strings,
|
||||
};
|
||||
|
||||
|
||||
+503
-41
@@ -4,8 +4,11 @@
|
||||
|
||||
#include <r_core.h>
|
||||
#include <hbc/hbc.h>
|
||||
#include <hbc/literals.h>
|
||||
#include "utils.inc.c"
|
||||
|
||||
#define HBC_VADDR_BASE 0x10000000ULL
|
||||
|
||||
/* Plugin registration - need these when HBC_CORE_REGISTER_PLUGINS is enabled */
|
||||
#ifdef HBC_CORE_REGISTER_PLUGINS
|
||||
extern const RArchPlugin r_arch_plugin_r2hermes;
|
||||
@@ -139,7 +142,9 @@ static HBCDecompOptions make_decompile_options(RCore *core, bool show_offsets, u
|
||||
.skip_pass4_closure = r_config_get_b (core->config, "hbc.skip_pass4"),
|
||||
.force_dispatch = r_config_get_b (core->config, "hbc.force_dispatch"),
|
||||
.inline_closures = r_config_get_b (core->config, "hbc.inline_closures"),
|
||||
.inline_threshold = r_config_get_i (core->config, "hbc.inline_threshold")
|
||||
.inline_threshold = r_config_get_i (core->config, "hbc.inline_threshold"),
|
||||
.max_ast_statements = r_config_get_i (core->config, "hbc.max_ast"),
|
||||
.max_output_bytes = r_config_get_i (core->config, "hbc.max_bytes")
|
||||
};
|
||||
return opts;
|
||||
}
|
||||
@@ -277,19 +282,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,
|
||||
@@ -355,62 +417,428 @@ static void cmd_file_info(HbcContext *ctx, RCore *core) {
|
||||
}
|
||||
}
|
||||
|
||||
/* JSON output for current function */
|
||||
static void cmd_json(HbcContext *ctx, RCore *core, const char *addr_str) {
|
||||
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) {
|
||||
PJ *pj = r_core_pj_new (core);
|
||||
pj_o (pj);
|
||||
pj_ks (pj, "error", safe_errmsg (res.error_message));
|
||||
pj_end (pj);
|
||||
char *s = pj_drain (pj);
|
||||
r_cons_println (core->cons, s);
|
||||
free (s);
|
||||
R_LOG_ERROR ("%s", safe_errmsg (res.error_message));
|
||||
return;
|
||||
}
|
||||
|
||||
u32 function_id = parse_function_id (addr_str);
|
||||
HBCDecompOptions opts = { .pretty_literals = true, .suppress_comments = false };
|
||||
u32 count = hbc_function_count (ctx->hbc);
|
||||
HBCSourceLineArray lines = { 0 };
|
||||
res = hbc_get_source_lines (ctx->hbc, &lines);
|
||||
if (res.code != RESULT_SUCCESS) {
|
||||
PJ *pj = r_core_pj_new (core);
|
||||
pj_o (pj);
|
||||
pj_ks (pj, "error", "cannot get function count");
|
||||
pj_end (pj);
|
||||
char *s = pj_drain (pj);
|
||||
r_cons_println (core->cons, s);
|
||||
free (s);
|
||||
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);
|
||||
pj_o (pj);
|
||||
pj_kn (pj, "function_id", function_id);
|
||||
pj_k (pj, "decompilation");
|
||||
if (function_id >= count) {
|
||||
pj_null (pj);
|
||||
pj_ks (pj, "error", "function id out of range");
|
||||
pj_kn (pj, "count", count);
|
||||
pj_end (pj);
|
||||
char *s = pj_drain (pj);
|
||||
r_cons_println (core->cons, s);
|
||||
free (s);
|
||||
return;
|
||||
}
|
||||
char *output = NULL;
|
||||
res = hbc_decomp_fn (ctx->hbc, function_id, opts, &output);
|
||||
|
||||
if (res.code == RESULT_SUCCESS && output) {
|
||||
pj_s (pj, output);
|
||||
} else {
|
||||
pj_null (pj);
|
||||
}
|
||||
pj_ks (pj, "code", "");
|
||||
pj_ka (pj, "annotations");
|
||||
pj_end (pj);
|
||||
pj_ka (pj, "errors");
|
||||
pj_s (pj, msg);
|
||||
pj_end (pj);
|
||||
pj_end (pj);
|
||||
char *s = pj_drain (pj);
|
||||
r_cons_println (core->cons, s);
|
||||
free (s);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
size_t start;
|
||||
size_t end;
|
||||
u64 offset;
|
||||
} LineAnn;
|
||||
|
||||
/* JSON output for a function in the codemeta-compatible {code, annotations, errors} shape.
|
||||
* Uses show_offsets=true internally and parses the "0xHHHHHHHH: " line prefixes to
|
||||
* produce per-line offset annotations, then strips the prefix from the emitted code. */
|
||||
static void cmd_json(HbcContext *ctx, RCore *core, const char *addr_str) {
|
||||
Result res = hbc_load_current_binary (ctx, core);
|
||||
if (res.code != RESULT_SUCCESS) {
|
||||
emit_json_error (core, safe_errmsg (res.error_message));
|
||||
return;
|
||||
}
|
||||
|
||||
u32 count = hbc_function_count (ctx->hbc);
|
||||
u32 function_id = 0;
|
||||
const char *as = r_str_trim_head_ro (addr_str);
|
||||
if (*as) {
|
||||
function_id = parse_function_id (addr_str);
|
||||
} else if (find_function_at_offset (ctx, (u32)core->addr, &function_id) != 0) {
|
||||
emit_json_error (core, "No function at current offset");
|
||||
return;
|
||||
}
|
||||
if (function_id >= count) {
|
||||
emit_json_error (core, "function id out of range");
|
||||
return;
|
||||
}
|
||||
|
||||
HBCFunc fi;
|
||||
Result fres = hbc_get_function_info (ctx->hbc, function_id, &fi);
|
||||
u64 func_base = (fres.code == RESULT_SUCCESS)? fi.offset: 0;
|
||||
|
||||
HBCDecompOptions opts = make_decompile_options (core, true, func_base);
|
||||
opts.pretty_literals = true;
|
||||
|
||||
char *output = NULL;
|
||||
res = hbc_decomp_fn (ctx->hbc, function_id, opts, &output);
|
||||
if (res.code != RESULT_SUCCESS || !output) {
|
||||
emit_json_error (core, safe_errmsg (res.error_message));
|
||||
free (output);
|
||||
return;
|
||||
}
|
||||
|
||||
RStrBuf *code_buf = r_strbuf_new ("");
|
||||
LineAnn *anns = NULL;
|
||||
size_t anns_n = 0, anns_cap = 0;
|
||||
|
||||
const char *p = output;
|
||||
while (*p) {
|
||||
const char *eol = strchr (p, '\n');
|
||||
size_t line_len = eol? (size_t) (eol - p): strlen (p);
|
||||
|
||||
bool has_offset = false;
|
||||
u64 line_offset = 0;
|
||||
if (line_len >= 12 && p[0] == '0' && p[1] == 'x' && p[10] == ':' && p[11] == ' ') {
|
||||
bool valid = true;
|
||||
for (int i = 2; i < 10; i++) {
|
||||
if (!isxdigit ((unsigned char)p[i])) {
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (valid) {
|
||||
char hexbuf[9];
|
||||
memcpy (hexbuf, p + 2, 8);
|
||||
hexbuf[8] = 0;
|
||||
line_offset = strtoull (hexbuf, NULL, 16);
|
||||
has_offset = true;
|
||||
}
|
||||
}
|
||||
|
||||
size_t start = r_strbuf_length (code_buf);
|
||||
if (has_offset) {
|
||||
r_strbuf_append_n (code_buf, p + 12, line_len - 12);
|
||||
} else {
|
||||
r_strbuf_append_n (code_buf, p, line_len);
|
||||
}
|
||||
r_strbuf_append (code_buf, "\n");
|
||||
size_t end = r_strbuf_length (code_buf);
|
||||
|
||||
if (has_offset) {
|
||||
if (anns_n == anns_cap) {
|
||||
anns_cap = anns_cap? anns_cap * 2: 64;
|
||||
LineAnn *grown = realloc (anns, anns_cap * sizeof (LineAnn));
|
||||
if (!grown) {
|
||||
break;
|
||||
}
|
||||
anns = grown;
|
||||
}
|
||||
anns[anns_n].start = start;
|
||||
anns[anns_n].end = end;
|
||||
anns[anns_n].offset = line_offset;
|
||||
anns_n++;
|
||||
}
|
||||
|
||||
if (!eol) {
|
||||
break;
|
||||
}
|
||||
p = eol + 1;
|
||||
}
|
||||
|
||||
char *cleaned = r_strbuf_drain (code_buf);
|
||||
|
||||
PJ *pj = r_core_pj_new (core);
|
||||
pj_o (pj);
|
||||
pj_ks (pj, "code", cleaned? cleaned: "");
|
||||
pj_ka (pj, "annotations");
|
||||
for (size_t i = 0; i < anns_n; i++) {
|
||||
pj_o (pj);
|
||||
pj_ks (pj, "type", "offset");
|
||||
pj_kn (pj, "start", anns[i].start);
|
||||
pj_kn (pj, "end", anns[i].end);
|
||||
pj_kn (pj, "offset", anns[i].offset);
|
||||
pj_end (pj);
|
||||
}
|
||||
pj_end (pj);
|
||||
pj_ka (pj, "errors");
|
||||
pj_end (pj);
|
||||
pj_end (pj);
|
||||
char *s = pj_drain (pj);
|
||||
r_cons_println (core->cons, s);
|
||||
free (s);
|
||||
free (cleaned);
|
||||
free (anns);
|
||||
free (output);
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
* Literal-buffer commands (pd:hL*)
|
||||
*
|
||||
* Work against the lazy HBC literal cache. Registration in r2 is additive:
|
||||
* flags (lit.arr.* / lit.obj.*) and CC comments are created at the literal
|
||||
* paddr; code→data xrefs are added for every call site. None of this is
|
||||
* attempted while plain disasm runs — the cache only populates on demand.
|
||||
* ========================================================================== */
|
||||
|
||||
static const char *lit_kind_prefix(HBCLiteralKind k) {
|
||||
return k == HBC_LIT_ARRAY? "lit.arr.": "lit.obj.";
|
||||
}
|
||||
|
||||
/* Register r2 artifacts for one cache entry. Safe to call repeatedly. */
|
||||
static void register_r2_artifacts(RCore *core, const HBCLiteralEntry *e) {
|
||||
if (!e->paddr) {
|
||||
return;
|
||||
}
|
||||
ut64 vaddr = HBC_VADDR_BASE + (ut64)e->paddr;
|
||||
char flag_name[64];
|
||||
snprintf (flag_name, sizeof (flag_name), "%s0x%x", lit_kind_prefix (e->kind), e->paddr);
|
||||
r_flag_set (core->flags, flag_name, vaddr, 1);
|
||||
if (e->formatted && *e->formatted) {
|
||||
r_meta_set_string (core->anal, R_META_TYPE_COMMENT, vaddr, e->formatted);
|
||||
}
|
||||
for (u32 i = 0; i < e->xref_count; i++) {
|
||||
ut64 from = HBC_VADDR_BASE + (ut64)e->xref_addrs[i];
|
||||
r_anal_xrefs_set (core->anal, from, vaddr, R_ANAL_REF_TYPE_DATA);
|
||||
}
|
||||
}
|
||||
|
||||
static void register_all_artifacts(RCore *core, HBC *hbc) {
|
||||
const HBCLiteralEntry *arr = NULL;
|
||||
u32 n = 0;
|
||||
if (hbc_literals_list (hbc, &arr, &n).code != RESULT_SUCCESS) {
|
||||
return;
|
||||
}
|
||||
for (u32 i = 0; i < n; i++) {
|
||||
register_r2_artifacts (core, &arr[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static const char *kind_label(HBCLiteralKind k) {
|
||||
return k == HBC_LIT_ARRAY? "array": "object";
|
||||
}
|
||||
|
||||
/* Make sure the current binary is loaded into the core plugin's HbcContext. */
|
||||
static Result ensure_hbc_loaded(HbcContext *ctx, RCore *core, HBC **out) {
|
||||
Result res = hbc_load_current_binary (ctx, core);
|
||||
if (res.code == RESULT_SUCCESS) {
|
||||
*out = ctx->hbc;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static void cmd_lit_list(HbcContext *ctx, RCore *core, bool as_json) {
|
||||
HBC *hbc = NULL;
|
||||
Result r = ensure_hbc_loaded (ctx, core, &hbc);
|
||||
if (r.code != RESULT_SUCCESS) {
|
||||
R_LOG_ERROR ("%s", safe_errmsg (r.error_message));
|
||||
return;
|
||||
}
|
||||
const HBCLiteralEntry *arr = NULL;
|
||||
u32 n = 0;
|
||||
if (hbc_literals_list (hbc, &arr, &n).code != RESULT_SUCCESS) {
|
||||
R_LOG_ERROR ("list failed");
|
||||
return;
|
||||
}
|
||||
if (n == 0) {
|
||||
r_cons_println (core->cons, "(cache empty — run pd:hLs to scan)");
|
||||
return;
|
||||
}
|
||||
if (as_json) {
|
||||
PJ *pj = r_core_pj_new (core);
|
||||
pj_a (pj);
|
||||
for (u32 i = 0; i < n; i++) {
|
||||
const HBCLiteralEntry *e = &arr[i];
|
||||
pj_o (pj);
|
||||
pj_ks (pj, "kind", kind_label (e->kind));
|
||||
pj_kn (pj, "num_items", e->num_items);
|
||||
pj_kn (pj, "primary_id", e->primary_id);
|
||||
pj_kn (pj, "secondary_id", e->secondary_id);
|
||||
pj_kn (pj, "paddr", e->paddr);
|
||||
pj_kn (pj, "vaddr", HBC_VADDR_BASE + e->paddr);
|
||||
pj_ks (pj, "formatted", e->formatted? e->formatted: "");
|
||||
pj_ka (pj, "xrefs");
|
||||
for (u32 j = 0; j < e->xref_count; j++) {
|
||||
pj_N (pj, HBC_VADDR_BASE + (ut64)e->xref_addrs[j]);
|
||||
}
|
||||
pj_end (pj);
|
||||
pj_end (pj);
|
||||
}
|
||||
pj_end (pj);
|
||||
char *s = pj_drain (pj);
|
||||
r_cons_println (core->cons, s);
|
||||
free (s);
|
||||
return;
|
||||
}
|
||||
r_cons_printf (core->cons, "literals: %u\n", n);
|
||||
for (u32 i = 0; i < n; i++) {
|
||||
const HBCLiteralEntry *e = &arr[i];
|
||||
r_cons_printf (core->cons,
|
||||
"%-6s n=%-4u id=(%u,%u) paddr=0x%08x xrefs=%u %s\n",
|
||||
kind_label (e->kind), e->num_items, e->primary_id,
|
||||
e->secondary_id, e->paddr, e->xref_count,
|
||||
e->formatted? e->formatted: "");
|
||||
}
|
||||
}
|
||||
|
||||
static void cmd_lit_scan_code(HbcContext *ctx, RCore *core) {
|
||||
HBC *hbc = NULL;
|
||||
Result r = ensure_hbc_loaded (ctx, core, &hbc);
|
||||
if (r.code != RESULT_SUCCESS) {
|
||||
R_LOG_ERROR ("%s", safe_errmsg (r.error_message));
|
||||
return;
|
||||
}
|
||||
u32 n = 0;
|
||||
r = hbc_literals_scan_code (hbc, &n);
|
||||
if (r.code != RESULT_SUCCESS) {
|
||||
R_LOG_ERROR ("scan failed: %s", safe_errmsg (r.error_message));
|
||||
return;
|
||||
}
|
||||
register_all_artifacts (core, hbc);
|
||||
r_cons_printf (core->cons, "scanned code, %u distinct literals (flags + xrefs registered)\n", n);
|
||||
}
|
||||
|
||||
static void cmd_lit_scan_pool(HbcContext *ctx, RCore *core, HBCLiteralKind kind) {
|
||||
HBC *hbc = NULL;
|
||||
Result r = ensure_hbc_loaded (ctx, core, &hbc);
|
||||
if (r.code != RESULT_SUCCESS) {
|
||||
R_LOG_ERROR ("%s", safe_errmsg (r.error_message));
|
||||
return;
|
||||
}
|
||||
HBCPoolGroup *groups = NULL;
|
||||
u32 n = 0;
|
||||
r = hbc_literals_scan_pool (hbc, kind, &groups, &n);
|
||||
if (r.code != RESULT_SUCCESS) {
|
||||
R_LOG_ERROR ("pool scan failed: %s", safe_errmsg (r.error_message));
|
||||
return;
|
||||
}
|
||||
r_cons_printf (core->cons, "%s pool: %u groups\n", kind_label (kind), n);
|
||||
for (u32 i = 0; i < n; i++) {
|
||||
r_cons_printf (core->cons,
|
||||
" paddr=0x%08x pool_off=0x%08x n=%-4u tag=%u\n",
|
||||
groups[i].paddr, groups[i].pool_offset, groups[i].num_items,
|
||||
groups[i].tag);
|
||||
}
|
||||
free (groups);
|
||||
}
|
||||
|
||||
static void cmd_lit_reset(HbcContext *ctx, RCore *core) {
|
||||
HBC *hbc = NULL;
|
||||
Result r = ensure_hbc_loaded (ctx, core, &hbc);
|
||||
if (r.code != RESULT_SUCCESS) {
|
||||
R_LOG_ERROR ("%s", safe_errmsg (r.error_message));
|
||||
return;
|
||||
}
|
||||
hbc_literals_reset (hbc);
|
||||
r_cons_println (core->cons, "literal cache reset (r2 flags/comments/xrefs kept — use 'f- lit.*' to drop)");
|
||||
}
|
||||
|
||||
static void cmd_lit_get(HbcContext *ctx, RCore *core, const char *args) {
|
||||
HBC *hbc = NULL;
|
||||
Result r = ensure_hbc_loaded (ctx, core, &hbc);
|
||||
if (r.code != RESULT_SUCCESS) {
|
||||
R_LOG_ERROR ("%s", safe_errmsg (r.error_message));
|
||||
return;
|
||||
}
|
||||
/* Syntax: pd:hLg a <num> <array_id> (array)
|
||||
* pd:hLg o <num> <primary> [<sec>] (object) */
|
||||
char kindc = 0;
|
||||
u64 num = 0, primary = 0, secondary = 0;
|
||||
if (!args || sscanf (args, " %c %" SCNu64 " %" SCNu64 " %" SCNu64,
|
||||
&kindc, &num, &primary, &secondary) < 3) {
|
||||
R_LOG_ERROR ("usage: pd:hLg {a|o} <num_items> <primary_id> [<secondary_id>]");
|
||||
return;
|
||||
}
|
||||
HBCLiteralKind kind = (kindc == 'a')? HBC_LIT_ARRAY: HBC_LIT_OBJECT;
|
||||
char *text = NULL;
|
||||
r = hbc_literals_format_raw (hbc, kind, (u32)num, (u32)primary,
|
||||
(u32)secondary, &text);
|
||||
if (r.code != RESULT_SUCCESS || !text) {
|
||||
R_LOG_ERROR ("format failed: %s", safe_errmsg (r.error_message));
|
||||
free (text);
|
||||
return;
|
||||
}
|
||||
r_cons_println (core->cons, text);
|
||||
free (text);
|
||||
}
|
||||
|
||||
static void cmd_lit_toggle_inline(RCore *core) {
|
||||
bool cur = hbc_get_inline_literals ();
|
||||
bool next = !cur;
|
||||
hbc_set_inline_literals (next);
|
||||
r_config_set_b (core->config, "hbc.inline_buffer_literals", next);
|
||||
r_cons_printf (core->cons, "inline buffer literals: %s\n", next? "on": "off");
|
||||
}
|
||||
|
||||
static const char LIT_HELP[] =
|
||||
"Usage: pd:hL[subcmd]\n"
|
||||
" pd:hL List cached literals\n"
|
||||
" pd:hLj List as JSON\n"
|
||||
" pd:hLs Scan all code; register literals as flags/xrefs/comments\n"
|
||||
" pd:hLp[ao] Scan SLP pool (default: arrays; a=arrays, o=objects)\n"
|
||||
" pd:hLr Reset literal cache (does not remove r2 flags/comments)\n"
|
||||
" pd:hLg <k> <n> <primary> [<sec>]\n"
|
||||
" Format a literal from raw params (k=a|o)\n"
|
||||
" pd:hLi Toggle inline literal comments in disasm\n"
|
||||
" pd:hL? This help\n";
|
||||
|
||||
static void cmd_literals(HbcContext *ctx, RCore *core, const char *arg) {
|
||||
while (*arg && isspace ((unsigned char)*arg)) {
|
||||
arg++;
|
||||
}
|
||||
switch (*arg) {
|
||||
case 0:
|
||||
cmd_lit_list (ctx, core, false);
|
||||
break;
|
||||
case 'j':
|
||||
cmd_lit_list (ctx, core, true);
|
||||
break;
|
||||
case 's':
|
||||
cmd_lit_scan_code (ctx, core);
|
||||
break;
|
||||
case 'p': {
|
||||
char k = arg[1];
|
||||
HBCLiteralKind kind = (k == 'o')? HBC_LIT_OBJECT: HBC_LIT_ARRAY;
|
||||
cmd_lit_scan_pool (ctx, core, kind);
|
||||
break;
|
||||
}
|
||||
case 'r':
|
||||
cmd_lit_reset (ctx, core);
|
||||
break;
|
||||
case 'g':
|
||||
cmd_lit_get (ctx, core, arg + 1);
|
||||
break;
|
||||
case 'i':
|
||||
cmd_lit_toggle_inline (core);
|
||||
break;
|
||||
case '?':
|
||||
r_cons_print (core->cons, LIT_HELP);
|
||||
break;
|
||||
default:
|
||||
R_LOG_ERROR ("Unknown subcommand. Use pd:hL? for help");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Show help */
|
||||
static void cmd_help(RCore *core) {
|
||||
r_cons_print (core->cons,
|
||||
@@ -425,6 +853,8 @@ static void cmd_help(RCore *core) {
|
||||
" 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");
|
||||
}
|
||||
@@ -491,6 +921,12 @@ static bool cmd_handler(RCorePluginSession *s, const char *input) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
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;
|
||||
@@ -502,6 +938,13 @@ static bool cmd_handler(RCorePluginSession *s, const char *input) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool cb_inline_buffer_literals(void *user, void *data) {
|
||||
(void)user;
|
||||
RConfigNode *node = (RConfigNode *)data;
|
||||
hbc_set_inline_literals (node && node->i_value);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool plugin_init(RCorePluginSession *s) {
|
||||
RCore *core = s->core;
|
||||
RConfig *cfg = core->config;
|
||||
@@ -582,6 +1025,25 @@ static bool plugin_init(RCorePluginSession *s) {
|
||||
r_config_node_desc (node, "Max bytecode size (bytes) for closure inlining (0=no limit, -1=never inline)");
|
||||
}
|
||||
|
||||
node = r_config_set_i (cfg, "hbc.max_ast", 5000);
|
||||
if (node) {
|
||||
r_config_node_desc (node, "Abort AST build once N statements per function (0=unlimited)");
|
||||
}
|
||||
|
||||
node = r_config_set_i (cfg, "hbc.max_bytes", 262144);
|
||||
if (node) {
|
||||
r_config_node_desc (node, "Stop decompilation once total output reaches N bytes (0=unlimited)");
|
||||
}
|
||||
|
||||
/* Default off: inlining the SLP literal as a disasm-line comment makes
|
||||
* arch.decode O(n) in literal size. Use pd:hL* to inspect literals
|
||||
* without impacting disasm speed. */
|
||||
hbc_set_inline_literals (false);
|
||||
node = r_config_set_b_cb (cfg, "hbc.inline_buffer_literals", false, cb_inline_buffer_literals);
|
||||
if (node) {
|
||||
r_config_node_desc (node, "Inline SLP buffer literals as comments in disasm (slow; default off)");
|
||||
}
|
||||
|
||||
r_config_lock (cfg, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
+582
-479
File diff suppressed because it is too large.
Load diff
+18
-2
@@ -1,4 +1,4 @@
|
||||
NAME=r2hermes test
|
||||
NAME=r2hermes test (va)
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e asm.sub.varmin=16
|
||||
CMDS=<<EOF
|
||||
@@ -9,7 +9,23 @@ EXPECT=<<EOF
|
||||
hbc
|
||||
;-- entry0:
|
||||
;-- global_0x000000b0:
|
||||
0x000000b0 73000100 load_const_string r0, str.print_123__ ; 0xa2 ; "print(123);"
|
||||
0x100000b0 73000100 load_const_string r0, 0xa2 ; yes.js:1:1 ; str.print_123__
|
||||
; 0x100000a2 ; "print(123);"
|
||||
0x100000b4 5e000000 direct_eval r0, r0, 0 ; yes.js:1:5
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=r2hermes test (no va)
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e asm.sub.varmin=16 -e io.va=false
|
||||
CMDS=<<EOF
|
||||
-a
|
||||
pd 2 @ 0xb0
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
hbc
|
||||
0x000000b0 73000100 load_const_string r0, 0xa2 ; str.print_123__
|
||||
; 0x100000a2
|
||||
0x000000b4 5e000000 direct_eval r0, r0, 0
|
||||
EOF
|
||||
RUN
|
||||
@@ -45,6 +45,7 @@ pd:hi~Functions
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
Functions: 1
|
||||
Functions With Debug Info: 1
|
||||
EOF
|
||||
RUN
|
||||
|
||||
@@ -55,6 +56,7 @@ pd:hi~Strings
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
Strings: 2
|
||||
Overflow Strings: 0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
@@ -65,6 +67,7 @@ pd:hi~Functions
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
Functions: 5388
|
||||
Functions With Debug Info: 0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
@@ -75,6 +78,7 @@ pd:hi~Strings
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
Strings: 6170
|
||||
Overflow Strings: 7
|
||||
EOF
|
||||
RUN
|
||||
|
||||
@@ -178,13 +182,25 @@ EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hj json output
|
||||
NAME=pd:hj json code field
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
pd:hj 0~function_id
|
||||
pd:hj 0~{code}
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
{"function_id":0,"decompilation":"function global() {\n r0 = \"print(123);\";\n r0 = eval (r0);\n return r0;\n}\n\n\n"}
|
||||
function global() {\n r0 = \"print(123);\";\n r0 = eval (r0);\n return r0;\n}\n\n\n
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=pd:hj json annotation offsets
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
pd:hj 0~{annotations[0].type}
|
||||
pd:hj 0~{annotations[0].offset}
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
offset
|
||||
176
|
||||
EOF
|
||||
RUN
|
||||
|
||||
|
||||
+304
-29
@@ -1,4 +1,4 @@
|
||||
NAME=hbc disasm basic instructions
|
||||
NAME=hbc disasm basic instructions (va)
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e asm.sub.varmin=16
|
||||
CMDS=<<EOF
|
||||
@@ -7,7 +7,22 @@ EOF
|
||||
EXPECT=<<EOF
|
||||
;-- entry0:
|
||||
;-- global_0x000000b0:
|
||||
0x000000b0 73000100 load_const_string r0, str.print_123__ ; 0xa2 ; "print(123);"
|
||||
0x100000b0 73000100 load_const_string r0, 0xa2 ; yes.js:1:1 ; str.print_123__
|
||||
; 0x100000a2 ; "print(123);"
|
||||
0x100000b4 5e000000 direct_eval r0, r0, 0 ; yes.js:1:5
|
||||
0x100000b8 5c00 ret r0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc disasm basic instructions (no va)
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e asm.sub.varmin=16 -e io.va=false
|
||||
CMDS=<<EOF
|
||||
pd 3 @ 0xb0
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x000000b0 73000100 load_const_string r0, 0xa2 ; str.print_123__
|
||||
; 0x100000a2
|
||||
0x000000b4 5e000000 direct_eval r0, r0, 0
|
||||
0x000000b8 5c00 ret r0
|
||||
EOF
|
||||
@@ -47,32 +62,66 @@ cpu 96
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc symbols list
|
||||
NAME=hbc symbols list (va)
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e asm.sub.varmin=16
|
||||
CMDS=<<EOF
|
||||
is~FUNC
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0 0x000000b0 0x100000b0 NONE FUNC 10 global_0x000000b0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc symbols list (no va)
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e asm.sub.varmin=16 -e io.va=false
|
||||
CMDS=<<EOF
|
||||
is~FUNC
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0 0x000000b0 0x000000b0 NONE FUNC 10 global_0x000000b0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc entry point
|
||||
NAME=hbc entry point (va)
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e asm.sub.varmin=16
|
||||
CMDS=<<EOF
|
||||
ie~program
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x000000b0 0x100000b0 ---------- ---------- program
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc entry point (no va)
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e asm.sub.varmin=16 -e io.va=false
|
||||
CMDS=<<EOF
|
||||
ie~program
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x000000b0 0x000000b0 ---------- ---------- program
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc disasm ret instruction
|
||||
NAME=hbc disasm ret instruction (va)
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e asm.sub.varmin=16
|
||||
CMDS=<<EOF
|
||||
s 0x100000b8
|
||||
pd 1
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x100000b8 5c00 ret r0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc disasm ret instruction (no va)
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e asm.sub.varmin=16 -e io.va=false
|
||||
CMDS=<<EOF
|
||||
s 0xb8
|
||||
pd 1
|
||||
EOF
|
||||
@@ -81,24 +130,50 @@ EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc disasm load_const_string
|
||||
NAME=hbc disasm load_const_string (va)
|
||||
ARGS=-e asm.sub.varmin=16
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
s 0x100000b0
|
||||
pd 1
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
;-- entry0:
|
||||
;-- global_0x000000b0:
|
||||
0x100000b0 73000100 load_const_string r0, 0xa2 ; yes.js:1:1 ; str.print_123__
|
||||
; 0x100000a2 ; "print(123);"
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc disasm load_const_string (no va)
|
||||
ARGS=-e asm.sub.varmin=16 -e io.va=false
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
CMDS=<<EOF
|
||||
s 0xb0
|
||||
pd 1
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
;-- entry0:
|
||||
;-- global_0x000000b0:
|
||||
0x000000b0 73000100 load_const_string r0, str.print_123__ ; 0xa2 ; "print(123);"
|
||||
0x000000b0 73000100 load_const_string r0, 0xa2 ; str.print_123__
|
||||
; 0x100000a2
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc disasm direct_eval
|
||||
NAME=hbc disasm direct_eval (va)
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e asm.sub.varmin=16
|
||||
CMDS=<<EOF
|
||||
s 0x100000b4
|
||||
pd 1
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x100000b4 5e000000 direct_eval r0, r0, 0 ; yes.js:1:5
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc disasm direct_eval (no va)
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e asm.sub.varmin=16 -e io.va=false
|
||||
CMDS=<<EOF
|
||||
s 0xb4
|
||||
pd 1
|
||||
EOF
|
||||
@@ -107,18 +182,29 @@ EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc sections
|
||||
NAME=hbc sections (va)
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e asm.sub.varmin=16
|
||||
CMDS=<<EOF
|
||||
iS~hermes
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0 0x00000000 0x124 0x00000000 0x124 -r-- 0x0 ---- hermes_bytecode
|
||||
0 0x00000000 0x124 0x10000000 0x124 -r-x 0x0 ---- hermes_bytecode
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc seek and disasm
|
||||
NAME=hbc sections (no va)
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e asm.sub.varmin=16 -e io.va=false
|
||||
CMDS=<<EOF
|
||||
iS~hermes
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0 0x00000000 0x124 0x00000000 0x124 -r-x 0x0 ---- hermes_bytecode
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc seek and disasm (va)
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e asm.sub.varmin=16
|
||||
CMDS=<<EOF
|
||||
@@ -128,16 +214,32 @@ EOF
|
||||
EXPECT=<<EOF
|
||||
;-- entry0:
|
||||
;-- global_0x000000b0:
|
||||
0x000000b0 73000100 load_const_string r0, str.print_123__ ; 0xa2 ; "print(123);"
|
||||
0x000000b4 5e000000 direct_eval r0, r0, 0
|
||||
0x100000b0 73000100 load_const_string r0, 0xa2 ; yes.js:1:1 ; str.print_123__
|
||||
; 0x100000a2 ; "print(123);"
|
||||
0x100000b4 5e000000 direct_eval r0, r0, 0 ; yes.js:1:5
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc print hex bytes
|
||||
NAME=hbc print hex bytes (va)
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e asm.sub.varmin=16
|
||||
CMDS=<<EOF
|
||||
s 0x100000b0
|
||||
-e hex.header=true
|
||||
px 10
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
- offset - B0B1 B2B3 B4B5 B6B7 B8B9 BABB BCBD BEBF 0123456789ABCDEF
|
||||
0x100000b0 7300 0100 5e00 0000 5c00 s...^...\.
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc print hex bytes (no va)
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e asm.sub.varmin=16 -e io.va=false
|
||||
CMDS=<<EOF
|
||||
s 0xb0
|
||||
-e hex.header=true
|
||||
px 10
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
@@ -146,10 +248,23 @@ EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc unreachable padding
|
||||
NAME=hbc unreachable padding (va)
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e asm.sub.varmin=16
|
||||
CMDS=<<EOF
|
||||
s 0x100000ba
|
||||
pd 2
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x100000ba 00 unreachable
|
||||
0x100000bb 00 unreachable
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc unreachable padding (no va)
|
||||
FILE=bins/hbc/bespoke_eval.hbc
|
||||
ARGS=-e asm.sub.varmin=16 -e io.va=false
|
||||
CMDS=<<EOF
|
||||
s 0xba
|
||||
pd 2
|
||||
EOF
|
||||
@@ -169,21 +284,82 @@ EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc get_global_object instruction
|
||||
NAME=hbc imports js require
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
ARGS=-e asm.sub.varmin=16
|
||||
CMDS=<<EOF
|
||||
ii~js.__r
|
||||
ii~?
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
3048 0x00000000 NONE FUNC react-native js.__r
|
||||
58
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc imports native module
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
ARGS=-e asm.sub.varmin=16
|
||||
CMDS=<<EOF
|
||||
ii~native.NativeModules
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
3894 0x00000000 NONE FUNC react-native native.NativeModules
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc exports module symbol
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
ARGS=-e asm.sub.varmin=16
|
||||
CMDS=<<EOF
|
||||
iE~hbc.export.module.RN$AppRegistry
|
||||
iE~?
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
3630 0x00071961 0x10071961 GLOBAL OBJ 1 hbc.export.module.RN$AppRegistry
|
||||
451
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc get_global_object instruction (va)
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
s 0x1003e510
|
||||
pd 1
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
;-- clear_0x0003e510:
|
||||
0x1003e510 3000 get_global_object r0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc get_global_object instruction (no va)
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
ARGS=-e io.va=false
|
||||
CMDS=<<EOF
|
||||
s 0x3e510
|
||||
pd 1
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
;-- clear_0x0003e510:
|
||||
0x0003e510 3000 get_global_object r0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc try_get_by_id instruction
|
||||
NAME=hbc try_get_by_id instruction (va)
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
s 0x1003e512
|
||||
ao~mnemonic
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
mnemonic: try_get_by_id
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc try_get_by_id instruction (no va)
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
ARGS=-e io.va=false
|
||||
CMDS=<<EOF
|
||||
s 0x3e512
|
||||
ao~mnemonic
|
||||
EOF
|
||||
@@ -192,9 +368,21 @@ mnemonic: try_get_by_id
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc get_by_id_short instruction
|
||||
NAME=hbc get_by_id_short instruction (va)
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
s 0x1003e518
|
||||
ao~mnemonic
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
mnemonic: get_by_id_short
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc get_by_id_short instruction (no va)
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
ARGS=-e io.va=false
|
||||
CMDS=<<EOF
|
||||
s 0x3e518
|
||||
ao~mnemonic
|
||||
EOF
|
||||
@@ -203,9 +391,21 @@ mnemonic: get_by_id_short
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc create_this instruction
|
||||
NAME=hbc create_this instruction (va)
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
s 0x1003e51d
|
||||
pd 1
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x1003e51d 6a010100 create_this r1, r1, r0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc create_this instruction (no va)
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
ARGS=-e io.va=false
|
||||
CMDS=<<EOF
|
||||
s 0x3e51d
|
||||
pd 1
|
||||
EOF
|
||||
@@ -214,9 +414,21 @@ EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc mov instruction
|
||||
NAME=hbc mov instruction (va)
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
s 0x1003e521
|
||||
pd 1
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x1003e521 080201 mov r2, r1
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc mov instruction (no va)
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
ARGS=-e io.va=false
|
||||
CMDS=<<EOF
|
||||
s 0x3e521
|
||||
pd 1
|
||||
EOF
|
||||
@@ -225,9 +437,21 @@ EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc construct instruction
|
||||
NAME=hbc construct instruction (va)
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
s 0x1003e524
|
||||
pd 1
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x1003e524 50000001 construct r0, r0, 1
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc construct instruction (no va)
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
ARGS=-e io.va=false
|
||||
CMDS=<<EOF
|
||||
s 0x3e524
|
||||
pd 1
|
||||
EOF
|
||||
@@ -236,9 +460,21 @@ EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc select_object instruction
|
||||
NAME=hbc select_object instruction (va)
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
s 0x1003e528
|
||||
pd 1
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x1003e528 6b000100 select_object r0, r1, r0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc select_object instruction (no va)
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
ARGS=-e io.va=false
|
||||
CMDS=<<EOF
|
||||
s 0x3e528
|
||||
pd 1
|
||||
EOF
|
||||
@@ -247,9 +483,21 @@ EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc get_environment instruction
|
||||
NAME=hbc get_environment instruction (va)
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
s 0x1003e52c
|
||||
pd 1
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x1003e52c 290100 get_environment r1, 0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc get_environment instruction (no va)
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
ARGS=-e io.va=false
|
||||
CMDS=<<EOF
|
||||
s 0x3e52c
|
||||
pd 1
|
||||
EOF
|
||||
@@ -258,9 +506,21 @@ EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc store_to_environment instruction
|
||||
NAME=hbc store_to_environment instruction (va)
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
s 0x1003e52f
|
||||
pd 1
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x1003e52f 2a010100 store_to_environment r1, 1, r0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc store_to_environment instruction (no va)
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
ARGS=-e io.va=false
|
||||
CMDS=<<EOF
|
||||
s 0x3e52f
|
||||
pd 1
|
||||
EOF
|
||||
@@ -269,9 +529,25 @@ EXPECT=<<EOF
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc complete function disasm
|
||||
NAME=hbc complete function disasm (va)
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
CMDS=<<EOF
|
||||
s 0x1003e510
|
||||
pd 3~get_global
|
||||
pd 1 @ 0x1003e51d~create_this
|
||||
pd 1 @ 0x1003e533~ret
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
0x1003e510 3000 get_global_object r0
|
||||
0x1003e51d 6a010100 create_this r1, r1, r0
|
||||
0x1003e533 5c00 ret r0
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=hbc complete function disasm (no va)
|
||||
FILE=bins/hbc/index.android.bundle
|
||||
ARGS=-e io.va=false
|
||||
CMDS=<<EOF
|
||||
s 0x3e510
|
||||
pd 3~get_global
|
||||
pd 1 @ 0x3e51d~create_this
|
||||
@@ -580,4 +856,3 @@ EXPECT=<<EOF
|
||||
store_to_environment r1, 1, r0
|
||||
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