Compare commits

...
28 Commits
Author SHA1 Message Date
pancake 9821c3f425 fi 2026-04-24 01:05:01 +02:00
pancake 32d1965288 f 2026-04-24 00:47:51 +02:00
pancake 56900a5064 Update tests 2026-04-24 00:45:38 +02:00
pancake d6e807bc1c sclean 2026-04-24 00:38:53 +02:00
pancake 76ec5ad0f8 test 2026-04-24 00:27:20 +02:00
pancake c08dcc3268 SourceLine exposure and strip details in rbininfo 2026-04-24 00:26:26 +02:00
pancake 56c943a4ca Add support for imports and exports via opcode scan 2026-04-24 00:01:56 +02:00
pancake 21231841f5 Release 1.2.6 2026-04-21 17:08:40 +02:00
dependabot[bot] e7985ec5e4 GhActionRelease from 261 to 300 2026-04-21 01:27:08 +02:00
pancake 5f830f3dc8 Simplify xps integration with r2 2026-04-20 19:19:10 +02:00
pancake 74acfedc8c Fix map permissions for the bytecode section 2026-04-20 15:20:32 +02:00
pancake 9fd443304a Refactor the flag parser for the cli tool (-200LOC) 2026-04-20 14:56:25 +02:00
pancake 55460d4253 Use base address for the analysis info to match the virtual mapping 2026-04-20 14:54:34 +02:00
pancake 55eb2dd38d Redeisgned support for buffer literals in cli, api and r2 plugin 2026-04-20 14:22:27 +02:00
pancake 57a6817d9c Make fmt 2026-04-20 00:16:42 +02:00
pancake 2a0ca11c6b Ass maxast option for the decompiler 2026-04-20 00:16:06 +02:00
pancake 55f1658b23 Use r2-6.1.4 in the ci 2026-04-19 23:46:55 +02:00
pancake 40cec248c0 Fix vaddr addressing for sections and symbols 2026-04-19 20:45:53 +02:00
pancake e21259b873 Add codemeta json support for the decompiler output 2026-04-18 11:06:29 +02:00
pancake 04750b84cb Fix call operand semantics and decompiler UAFs 2026-04-17 18:46:28 +02:00
pancake 11284e7081 Release 1.2.4 2026-04-12 23:09:53 +02:00
Ahmeth4n 5ff2a61c2c Operand handling bugs fixed 2026-04-12 14:04:00 +02:00
Ahmethan G. 29c1cadb98 Fix operand order bug in the decompiler 2026-04-12 10:52:14 +02:00
pancake a1d520c845 Reject unexpected extra operands in encoder parser + tests 2026-04-10 16:24:26 +02:00
pancake 40be994134 Bound checks for strings and bigint enumerations 2026-04-10 15:38:11 +02:00
dependabot[bot] c19977865d Bump softprops/action-gh-release from 2.5.0 to 2.6.1 2026-03-28 09:57:56 +01:00
Ahmethan G. 425db85466 Fix inter procedure call xrefs 2026-03-28 09:57:12 +01:00
pancake 2e1f02925a Respect RBinLimit for strings and functions 2026-03-19 19:43:41 +01:00
34 changed files with 3682 additions and 1001 deletions

No files matched your search

+2 -2
View File
@@ -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.5.0
uses: softprops/action-gh-release@v3.0.0
with:
tag_name: ${{ needs.build.outputs.tag }}
files: ./libhbc-${{ needs.build.outputs.tag }}.tgz
+1 -1
View File
@@ -1,7 +1,7 @@
name: Meson Build
env:
R2V: 6.0.8
R2V: 6.1.4
on:
push:
+1 -1
View File
@@ -1,7 +1,7 @@
name: CI
env:
R2V: 6.0.8
R2V: 6.1.4
permissions:
contents: write
+1 -1
View File
@@ -1,7 +1,7 @@
name: Windows Build
env:
R2V: 6.0.8
R2V: 6.1.4
on:
push:
+1 -1
View File
@@ -1,5 +1,5 @@
build/
bin/libhbctool
bin/
third_party/
*.d
*.o
+12 -3
View File
@@ -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
-1
View File
@@ -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) \
+2
View File
@@ -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;
+68
View File
@@ -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
*/
+114
View File
@@ -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
View File
@@ -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
View File
@@ -1,7 +1,7 @@
project(
'r2hermes',
'c',
version: '1.2.2',
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
View File
@@ -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
+35 -5
View File
@@ -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");
+4 -4
View File
@@ -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) {
+3 -3
View File
@@ -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 ();
}
+42 -38
View File
@@ -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 (",")));
}
@@ -233,11 +235,10 @@ static Result emit_call_fixed(TokenString *out, const ParsedInstruction *insn, i
static Result emit_call_with_argc(TokenString *out, const ParsedInstruction *insn) {
RETURN_IF_ERROR (add (out, reg_l_safe (insn, 0)));
RETURN_IF_ERROR (add (out, create_assignment_token ()));
RETURN_IF_ERROR (add (out, reg_r_safe (insn, 0)));
RETURN_IF_ERROR (add (out, create_left_parenthesis_token ()));
RETURN_IF_ERROR (add (out, reg_r_safe (insn, 1)));
RETURN_IF_ERROR (add (out, create_left_parenthesis_token ()));
char buf2[32];
snprintf (buf2, sizeof (buf2), ", /*argc:%u*/", (unsigned)insn->arg3);
snprintf (buf2, sizeof (buf2), "/*argc:%u*/", (unsigned)insn->arg3);
RETURN_IF_ERROR (add (out, create_raw_token (buf2)));
return add (out, create_right_parenthesis_token ());
}
@@ -575,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*/")));
@@ -644,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) {
@@ -685,12 +686,12 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
RETURN_IF_ERROR (add (out, reg_l_safe (insn_c, 0)));
RETURN_IF_ERROR (add (out, create_assignment_token ()));
StringBuffer sb;
_hbc_string_buffer_init (&sb, 48);
RETURN_IF_ERROR (_hbc_string_buffer_init (&sb, 48));
_hbc_string_buffer_append (&sb, "try_get(");
char nb[16];
snprintf (nb, sizeof (nb), "r%u", (unsigned)insn->arg2);
_hbc_string_buffer_append (&sb, nb);
_hbc_format_property_from_string_id (insn->hbc_reader, (op == OP_TryGetById)? insn->arg4: insn->arg4, &sb);
_hbc_format_property_from_string_id (insn->hbc_reader, insn->arg4, &sb);
_hbc_string_buffer_append (&sb, ")");
Token *t = create_raw_token (sb.data? sb.data: "try_get(r0)");
_hbc_string_buffer_free (&sb);
@@ -708,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 {
@@ -724,13 +725,13 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
case OP_PutOwnByIndex:
case OP_PutOwnByIndexL:
{
/* obj[idx] = value */
/* obj[idx] = value; operands: obj reg, value reg, literal idx */
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 0)));
RETURN_IF_ERROR (add (out, create_raw_token ("[")));
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 1)));
RETURN_IF_ERROR (add (out, num_token_u32 (insn->arg3)));
RETURN_IF_ERROR (add (out, create_raw_token ("]")));
RETURN_IF_ERROR (add (out, create_assignment_token ()));
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 2)));
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 1)));
break;
}
case OP_PutByVal:
@@ -750,7 +751,7 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
/* rObj["name"] = rVal */
RETURN_IF_ERROR (add (out, reg_r_safe (insn_c, 0)));
RETURN_IF_ERROR (add (out, create_raw_token ("[")));
u32 sid = (op == OP_PutById)? insn->arg4: insn->arg4;
u32 sid = insn->arg4;
RETURN_IF_ERROR (add (out, quoted_string (insn->hbc_reader, sid)));
RETURN_IF_ERROR (add (out, create_raw_token ("]")));
RETURN_IF_ERROR (add (out, create_assignment_token ()));
@@ -810,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*/")));
@@ -836,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*/")));
@@ -879,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");
@@ -898,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");
@@ -927,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 {
@@ -1097,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 ((op == OP_CallDirect)? insn->arg4: insn->arg4)));
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->arg3)));
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;
@@ -1162,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 {
@@ -1183,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 {
@@ -1224,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*/")));
@@ -1234,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 {
@@ -1301,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) {
@@ -1319,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) {
@@ -1340,7 +1344,7 @@ Result _hbc_translate_instruction_to_tokens(const ParsedInstruction *insn_c, Tok
/* Fallback: emit a comment-like raw token with mnemonic */
StringBuffer sb;
_hbc_string_buffer_init (&sb, 64);
RETURN_IF_ERROR (_hbc_string_buffer_init (&sb, 64));
_hbc_string_buffer_append (&sb, "/* ");
_hbc_string_buffer_append (&sb, insn->inst->name);
_hbc_string_buffer_append (&sb, " */");
+484 -11
View File
@@ -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;
@@ -455,20 +694,23 @@ Result hbc_dec(const HBCDecodeCtx *ctx, HBCInsnInfo *out) {
switch (operand_type) {
case OPERAND_TYPE_REG8:
case OPERAND_TYPE_UINT8:
case OPERAND_TYPE_ADDR8:
operand_values[i] = ctx->bytes[pos];
pos += 1;
break;
case OPERAND_TYPE_ADDR8:
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:
@@ -561,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);
}
@@ -589,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(
+8
View File
@@ -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 */
+519
View File
@@ -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 ();
}
+100 -98
View File
@@ -131,7 +131,7 @@ Result hbc_encoder_parse_instruction(HBCEncoder *encoder, const char *asm_line,
}
/* Parse operands separated by commas */
while (*line && operand_count < 6) {
while (*line) {
/* Skip whitespace */
while (*line && isspace (*line)) {
line++;
@@ -177,104 +177,106 @@ Result hbc_encoder_parse_instruction(HBCEncoder *encoder, const char *asm_line,
}
*trim_end = '\0';
/* Parse operand based on expected type */
if (operand_count < 6 && inst->operands[operand_count].operand_type != OPERAND_TYPE_NONE) {
OperandType expected_type = inst->operands[operand_count].operand_type;
char *endptr;
bool parse_success = false;
switch (expected_type) {
case OPERAND_TYPE_REG8:
case OPERAND_TYPE_REG32:
/* Register: rN or RN */
if (trim_start[0] == 'r' || trim_start[0] == 'R') {
operand_values[operand_count] = (u32)strtoul (trim_start + 1, &endptr, 10);
if (*endptr == '\0') {
parse_success = true;
/* Validate register range */
if (expected_type == OPERAND_TYPE_REG8 && operand_values[operand_count] > 255) {
return ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, "Register number out of range for REG8");
}
}
} else {
/* Allow bare numbers for registers too */
operand_values[operand_count] = (u32)strtoul (trim_start, &endptr, 10);
if (*endptr == '\0') {
parse_success = true;
/* Validate register range */
if (expected_type == OPERAND_TYPE_REG8 && operand_values[operand_count] > 255) {
return ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, "Register number out of range for REG8");
}
}
}
break;
case OPERAND_TYPE_UINT8:
case OPERAND_TYPE_UINT16:
case OPERAND_TYPE_UINT32:
case OPERAND_TYPE_IMM32:
case OPERAND_TYPE_ADDR8:
case OPERAND_TYPE_ADDR32:
/* Immediate or address */
if (trim_start[0] == '0' && (trim_start[1] == 'x' || trim_start[1] == 'X')) {
/* Hex */
operand_values[operand_count] = (u32)strtoul (trim_start, &endptr, 16);
if (*endptr == '\0') {
parse_success = true;
}
} else {
/* Decimal */
operand_values[operand_count] = (u32)strtoul (trim_start, &endptr, 10);
if (*endptr == '\0') {
parse_success = true;
}
}
break;
case OPERAND_TYPE_DOUBLE:
/* Parse as double literal */
{
char *endptr_double;
double dval = strtod (trim_start, &endptr_double);
if (*endptr_double == '\0') {
/* Convert double to IEEE 754 bits */
u64 bits;
memcpy (&bits, &dval, sizeof (double));
operand_values[operand_count] = bits;
parse_success = true;
} else if (trim_start[0] == '0' && (trim_start[1] == 'x' || trim_start[1] == 'X')) {
/* Allow hex for bit patterns */
operand_values[operand_count] = (u64)strtoull (trim_start, &endptr_double, 16);
if (*endptr_double == '\0') {
parse_success = true;
}
}
}
break;
default:
/* Unknown type, try to parse as number */
if (trim_start[0] == '0' && (trim_start[1] == 'x' || trim_start[1] == 'X')) {
operand_values[operand_count] = (u32)strtoul (trim_start, &endptr, 16);
if (*endptr == '\0') {
parse_success = true;
}
} else {
operand_values[operand_count] = (u32)strtoul (trim_start, &endptr, 10);
if (*endptr == '\0') {
parse_success = true;
}
}
break;
}
if (!parse_success) {
return ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, "Invalid operand format");
}
operand_count++;
if (operand_count >= 6 || inst->operands[operand_count].operand_type == OPERAND_TYPE_NONE) {
return ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, "Incorrect number of operands");
}
/* Parse operand based on expected type */
OperandType expected_type = inst->operands[operand_count].operand_type;
char *endptr;
bool parse_success = false;
switch (expected_type) {
case OPERAND_TYPE_REG8:
case OPERAND_TYPE_REG32:
/* Register: rN or RN */
if (trim_start[0] == 'r' || trim_start[0] == 'R') {
operand_values[operand_count] = (u32)strtoul (trim_start + 1, &endptr, 10);
if (*endptr == '\0') {
parse_success = true;
/* Validate register range */
if (expected_type == OPERAND_TYPE_REG8 && operand_values[operand_count] > 255) {
return ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, "Register number out of range for REG8");
}
}
} else {
/* Allow bare numbers for registers too */
operand_values[operand_count] = (u32)strtoul (trim_start, &endptr, 10);
if (*endptr == '\0') {
parse_success = true;
/* Validate register range */
if (expected_type == OPERAND_TYPE_REG8 && operand_values[operand_count] > 255) {
return ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, "Register number out of range for REG8");
}
}
}
break;
case OPERAND_TYPE_UINT8:
case OPERAND_TYPE_UINT16:
case OPERAND_TYPE_UINT32:
case OPERAND_TYPE_IMM32:
case OPERAND_TYPE_ADDR8:
case OPERAND_TYPE_ADDR32:
/* Immediate or address */
if (trim_start[0] == '0' && (trim_start[1] == 'x' || trim_start[1] == 'X')) {
/* Hex */
operand_values[operand_count] = (u32)strtoul (trim_start, &endptr, 16);
if (*endptr == '\0') {
parse_success = true;
}
} else {
/* Decimal */
operand_values[operand_count] = (u32)strtoul (trim_start, &endptr, 10);
if (*endptr == '\0') {
parse_success = true;
}
}
break;
case OPERAND_TYPE_DOUBLE:
/* Parse as double literal */
{
char *endptr_double;
double dval = strtod (trim_start, &endptr_double);
if (*endptr_double == '\0') {
/* Convert double to IEEE 754 bits */
u64 bits;
memcpy (&bits, &dval, sizeof (double));
operand_values[operand_count] = bits;
parse_success = true;
} else if (trim_start[0] == '0' && (trim_start[1] == 'x' || trim_start[1] == 'X')) {
/* Allow hex for bit patterns */
operand_values[operand_count] = (u64)strtoull (trim_start, &endptr_double, 16);
if (*endptr_double == '\0') {
parse_success = true;
}
}
}
break;
default:
/* Unknown type, try to parse as number */
if (trim_start[0] == '0' && (trim_start[1] == 'x' || trim_start[1] == 'X')) {
operand_values[operand_count] = (u32)strtoul (trim_start, &endptr, 16);
if (*endptr == '\0') {
parse_success = true;
}
} else {
operand_values[operand_count] = (u32)strtoul (trim_start, &endptr, 10);
if (*endptr == '\0') {
parse_success = true;
}
}
break;
}
if (!parse_success) {
return ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, "Invalid operand format");
}
operand_count++;
/* Move to next operand */
line = operand_end;
if (*line == ',') {
@@ -338,7 +340,7 @@ Result hbc_encoder_encode_instruction(HBCEncoder *encoder, const HBCEncodedInstr
/* Write operands based on their types */
for (int i = 0; i < 6 && inst->operands[i].operand_type != OPERAND_TYPE_NONE; i++) {
OperandType type = inst->operands[i].operand_type;
u32 value = operand_values[i];
u64 value = operand_values[i];
switch (type) {
case OPERAND_TYPE_REG8:
+60 -22
View File
@@ -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);
@@ -984,12 +990,9 @@ Result _hbc_reader_read_string_tables(HBCReader *reader) {
is_utf16 = reader->small_string_table[i].isUTF16;
}
size_t decoded_length = (size_t)length;
size_t available = (size_t)offset > string_storage_size? 0: string_storage_size - (size_t)offset;
if ((size_t)offset > string_storage_size ||
decoded_length == SIZE_MAX ||
(is_utf16 && decoded_length > available / 2) ||
(!is_utf16 && decoded_length > available)) {
/* Overflow-safe bounds check via subtraction */
const u32 bpc = is_utf16? 2: 1;
if (offset > string_storage_size || length > (string_storage_size - offset) / bpc) {
fprintf (stderr, "Warning: String %u offset/length out of bounds, treating as empty string\n", i);
reader->strings[i] = strdup ("");
if (!reader->strings[i]) {
@@ -1000,9 +1003,7 @@ Result _hbc_reader_read_string_tables(HBCReader *reader) {
continue;
}
/* Allocate string buffer (+1 for null terminator) */
size_t buffer_size = decoded_length + 1;
char *str = (char *)malloc (buffer_size);
char *str = (char *)malloc (length * bpc + 1);
if (!str) {
free (string_storage);
reader->string_table_storage = NULL;
@@ -1082,6 +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);
@@ -1113,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);
@@ -1154,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 ();
}
@@ -1169,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);
@@ -1339,10 +1344,8 @@ Result _hbc_reader_read_bigints(HBCReader *reader) {
u32 offset = bigint_table[i].offset;
u32 length = bigint_table[i].length;
/* Check bounds */
size_t bigint_available = (size_t)offset > reader->header.bigIntStorageSize? 0:
(size_t)reader->header.bigIntStorageSize - (size_t)offset;
if ((size_t)offset > reader->header.bigIntStorageSize || (size_t)length > bigint_available) {
/* Overflow-safe bounds check via subtraction */
if (offset > reader->header.bigIntStorageSize || length > reader->header.bigIntStorageSize - offset) {
fprintf (stderr, "Warning: BigInt %u has invalid offset/length (%u+%u exceeds %u)\n", i, offset, length, reader->header.bigIntStorageSize);
/* Use zero for this value */
reader->bigint_values[i] = 0;
@@ -1581,22 +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));
@@ -1609,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 (
@@ -1710,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 ();
}
@@ -2032,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;
}
+4 -7
View File
@@ -363,11 +363,9 @@ Result _hbc_generate_r2_script(const char *input_file, const char *output_file)
u32 length = string_infos[i].length;
bool isUTF16 = string_infos[i].isUTF16;
/* Skip strings with obviously invalid offsets/lengths */
size_t available = (size_t)offset > string_storage_size? 0: string_storage_size - (size_t)offset;
if ((size_t)offset > string_storage_size ||
(isUTF16 && (size_t)length > available / 2) ||
(!isUTF16 && (size_t)length > available)) {
/* Overflow-safe bounds check via subtraction */
const u32 bpc = isUTF16? 2: 1;
if (offset > string_storage_size || length > (string_storage_size - offset) / bpc) {
continue;
}
@@ -433,9 +431,8 @@ Result _hbc_generate_r2_script(const char *input_file, const char *output_file)
snprintf (unique_name, sizeof (unique_name), "%s_%u", sanitized_name[0]? sanitized_name: "str", i);
/* Write the string flag - offset is relative to string storage base */
size_t size = isUTF16? (size_t)length * 2: (size_t)length;
uint32_t addr = (unsigned long) (reader.file_buffer.position - string_storage_size + offset);
fprintf (out, "'f str.%s %zu 0x%08x\n", unique_name, size, addr);
fprintf (out, "'f str.%s %u 0x%08x\n", unique_name, length * bpc, addr);
#if 0
/* Add flag for string length */
if (isUTF16) {
+6 -5
View File
@@ -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 $@
+13 -8
View File
@@ -2,12 +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
@@ -170,9 +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) {
/* Set op->ptr to the file offset of the string.
* meta.offset already includes string_storage_file_offset */
op->ptr = (st64)meta.offset;
op->ptr = (st64)(HBC_VADDR_BASE + meta.offset);
}
}
}
@@ -188,8 +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;
// Set op->ptr to the function address as a file offset
op->ptr = (st64)offset;
op->ptr = (st64)(HBC_VADDR_BASE + offset);
}
}
}
@@ -235,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;
}
@@ -299,6 +301,9 @@ static bool decode(RArchSession *s, RAnalOp *op, RArchDecodeMask mask) {
if (sinfo.is_call) {
op->type = R_ANAL_OP_TYPE_CALL;
op->jump = sinfo.jump_target;
if (!op->jump && op->ptr) {
op->jump = op->ptr;
}
op->fail = op->addr + op->size;
return true;
}
+169 -169
View File
@@ -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
};
+210 -16
View File
@@ -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
@@ -51,6 +53,18 @@ static HBC *get_hbc(RBinFile *bf) {
return hbo? hbo->hbc: NULL;
}
static int bin_limit(RBinFile *bf) {
return bf && bf->rbin? bf->rbin->options.limit: 0;
}
static u32 clamp_count_with_warning(const char *kind, u32 count, int limit) {
if (limit > 0 && count > (u32)limit) {
R_LOG_WARN ("hbc.bin: bin.limit reached for %s (%u > %d)", kind, count, limit);
return (u32)limit;
}
return count;
}
/* Check if entrypoint offset is valid: in bounds and not all zeros */
static bool is_valid_entrypoint(RBuffer *buf, ut64 offset) {
ut64 file_size = r_buf_size (buf);
@@ -119,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");
@@ -129,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) {
@@ -143,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 */
@@ -155,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) {
@@ -171,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;
}
@@ -198,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);
@@ -205,7 +289,8 @@ static RList *symbols(RBinFile *bf) {
return symbols;
}
u32 func_count = hbc_function_count (hbc);
int limit = bin_limit (bf);
u32 func_count = clamp_count_with_warning ("symbols", hbc_function_count (hbc), limit);
for (u32 i = 0; i < func_count; i++) {
HBCFunc fi;
if (hbc_get_function_info (hbc, i, &fi).code != RESULT_SUCCESS) {
@@ -250,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);
@@ -260,7 +454,8 @@ static RList *strings(RBinFile *bf) {
return ret;
}
u32 string_count = hbc_string_count (hbc);
int limit = bin_limit (bf);
u32 string_count = clamp_count_with_warning ("strings", hbc_string_count (hbc), limit);
for (u32 i = 0; i < string_count; i++) {
const char *str = NULL;
if (hbc_get_string (hbc, i, &str).code != RESULT_SUCCESS || !str) {
@@ -271,21 +466,18 @@ static RList *strings(RBinFile *bf) {
if (hbc_get_string_meta (hbc, i, &meta).code != RESULT_SUCCESS) {
continue;
}
if (meta.length == 0) {
const size_t str_len = strlen (str);
if (str_len == 0) {
continue;
}
RBinString *ptr = R_NEW0 (RBinString);
if (meta.length >= R_BIN_SIZEOF_STRINGS) {
ptr->string = r_str_ndup (str, R_BIN_SIZEOF_STRINGS - 4);
if (ptr->string) {
char *tmp = r_str_newf ("%s...", ptr->string);
free (ptr->string);
ptr->string = tmp;
}
if (str_len >= R_BIN_SIZEOF_STRINGS) {
char *trunc = r_str_ndup (str, R_BIN_SIZEOF_STRINGS - 4);
ptr->string = r_str_newf ("%s...", trunc? trunc: "");
free (trunc);
} else {
ptr->string = r_str_ndup (str, meta.length);
ptr->string = r_str_ndup (str, str_len);
}
if (!ptr->string) {
free (ptr);
@@ -293,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;
@@ -318,6 +510,8 @@ const RBinPlugin r_bin_plugin_r2hermes = {
.sections = &sections,
.baddr = &baddr,
.symbols = &symbols,
.imports = &imports,
.lines = &lines,
.strings = &strings,
};
+503 -41
View File
@@ -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
View File
File diff suppressed because it is too large. Load diff
+18 -2
View File
@@ -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
+19 -3
View File
@@ -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
+591 -29
View File
@@ -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
@@ -284,7 +560,7 @@ EXPECT=<<EOF
EOF
RUN
NAME=hbc assembler
NAME=hbc pa ret
FILE=--
CMDS=<<EOF
-a hbc
@@ -294,3 +570,289 @@ EXPECT=<<EOF
5c00
EOF
RUN
NAME=hbc pa get_global_object
FILE=--
CMDS=<<EOF
-a hbc
pa get_global_object r0
EOF
EXPECT=<<EOF
3000
EOF
RUN
NAME=hbc pa mov two regs
FILE=--
CMDS=<<EOF
-a hbc
pa mov r2, r1
EOF
EXPECT=<<EOF
080201
EOF
RUN
NAME=hbc pa construct three operands
FILE=--
CMDS=<<EOF
-a hbc
pa construct r0, r0, 1
EOF
EXPECT=<<EOF
50000001
EOF
RUN
NAME=hbc pa select_object three regs
FILE=--
CMDS=<<EOF
-a hbc
pa select_object r0, r1, r0
EOF
EXPECT=<<EOF
6b000100
EOF
RUN
NAME=hbc pa store_to_environment
FILE=--
CMDS=<<EOF
-a hbc
pa store_to_environment r1, 1, r0
EOF
EXPECT=<<EOF
2a010100
EOF
RUN
NAME=hbc pa rejects extra operand
FILE=--
CMDS=<<EOF
-a hbc
"pa ret r0, r1"
EOF
EXPECT=<<EOF
EOF
RUN
NAME=hbc pa rejects multiple extra operands
FILE=--
CMDS=<<EOF
-a hbc
"pa ret r0, r1, r2"
EOF
EXPECT=<<EOF
EOF
RUN
NAME=hbc pa rejects extra operand on zero-arg instr
FILE=--
CMDS=<<EOF
-a hbc
"pa get_global_object r0, r1"
EOF
EXPECT=<<EOF
EOF
RUN
NAME=hbc pa rejects missing operand
FILE=--
CMDS=<<EOF
-a hbc
pa ret
EOF
EXPECT=<<EOF
EOF
RUN
NAME=hbc pa rejects truncated mov
FILE=--
CMDS=<<EOF
-a hbc
pa mov r2
EOF
EXPECT=<<EOF
EOF
RUN
NAME=hbc pa rejects unknown mnemonic
FILE=--
CMDS=<<EOF
-a hbc
pa bogus_op r0
EOF
EXPECT=<<EOF
EOF
RUN
NAME=hbc pad ret
FILE=--
CMDS=<<EOF
-a hbc
pad 5c00
EOF
EXPECT=<<EOF
ret r0
EOF
RUN
NAME=hbc pad get_global_object
FILE=--
CMDS=<<EOF
-a hbc
pad 3000
EOF
EXPECT=<<EOF
get_global_object r0
EOF
RUN
NAME=hbc pad mov
FILE=--
CMDS=<<EOF
-a hbc
pad 080201
EOF
EXPECT=<<EOF
mov r2, r1
EOF
RUN
NAME=hbc pad construct
FILE=--
CMDS=<<EOF
-a hbc
pad 50000001
EOF
EXPECT=<<EOF
construct r0, r0, 1
EOF
RUN
NAME=hbc pad select_object
FILE=--
CMDS=<<EOF
-a hbc
pad 6b000100
EOF
EXPECT=<<EOF
select_object r0, r1, r0
EOF
RUN
NAME=hbc pad store_to_environment
FILE=--
CMDS=<<EOF
-a hbc
pad 2a010100
EOF
EXPECT=<<EOF
store_to_environment r1, 1, r0
EOF
RUN
NAME=hbc pa+pad roundtrip ret
FILE=--
CMDS=<<EOF
-a hbc
pad `pa ret r0`
EOF
EXPECT=<<EOF
ret r0
EOF
RUN
NAME=hbc pa+pad roundtrip mov
FILE=--
CMDS=<<EOF
-a hbc
pad `pa mov r2, r1`
EOF
EXPECT=<<EOF
mov r2, r1
EOF
RUN
NAME=hbc pa+pad roundtrip construct
FILE=--
CMDS=<<EOF
-a hbc
pad `pa construct r0, r0, 1`
EOF
EXPECT=<<EOF
construct r0, r0, 1
EOF
RUN
NAME=hbc pi ret after wx
FILE=malloc://16
CMDS=<<EOF
-a hbc
wx 5c00
pi 1
EOF
EXPECT=<<EOF
ret r0
EOF
RUN
NAME=hbc pi mov after wx
FILE=malloc://16
CMDS=<<EOF
-a hbc
wx 080201
pi 1
EOF
EXPECT=<<EOF
mov r2, r1
EOF
RUN
NAME=hbc pi construct after wx
FILE=malloc://16
CMDS=<<EOF
-a hbc
wx 50000001
pi 1
EOF
EXPECT=<<EOF
construct r0, r0, 1
EOF
RUN
NAME=hbc pa+wx+pi roundtrip ret
FILE=malloc://16
CMDS=<<EOF
-a hbc
wx `pa ret r0`
pi 1
EOF
EXPECT=<<EOF
ret r0
EOF
RUN
NAME=hbc pa+wx+pi roundtrip mov
FILE=malloc://16
CMDS=<<EOF
-a hbc
wx `pa mov r2, r1`
pi 1
EOF
EXPECT=<<EOF
mov r2, r1
EOF
RUN
NAME=hbc pa+wx+pi roundtrip store_to_environment
FILE=malloc://16
CMDS=<<EOF
-a hbc
wx `pa store_to_environment r1, 1, r0`
pi 1
EOF
EXPECT=<<EOF
store_to_environment r1, 1, r0
EOF
RUN
+3 -2
View File
@@ -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
+81
View File
@@ -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;
}