Compare commits

...
18 Commits
Author SHA1 Message Date
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
31 changed files with 2591 additions and 976 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.6.1
uses: softprops/action-gh-release@v3.0.0
with:
tag_name: ${{ needs.build.outputs.tag }}
files: ./libhbc-${{ needs.build.outputs.tag }}.tgz
+1 -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
View File
@@ -8,3 +8,4 @@ third_party/
test/bins
*.swp
include/hbc/version.h
./bin
+1 -1
View File
@@ -18,7 +18,7 @@ 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
## Object files
-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;
+4
View File
@@ -104,6 +104,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 */
+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 */
+5
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;
+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, " */");
+11 -6
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>
@@ -127,6 +128,7 @@ void hbc_close(HBC *hbc) {
if (!hbc) {
return;
}
hbc_literals_reset (hbc);
_hbc_reader_cleanup (&hbc->reader);
free (hbc);
}
@@ -412,7 +414,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 +457,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 +566,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);
}
+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:
+10 -13
View File
@@ -984,12 +984,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 +997,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 +1077,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 +1109,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 +1151,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 +1167,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 +1338,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;
+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) {
+10 -4
View File
@@ -2,11 +2,13 @@
#include <r_anal.h>
#include <hbc/hbc.h>
#include <hbc/literals.h>
#include <hbc/opcodes.h>
#include <hbc/bytecode.h>
#include <hbc/parser.h>
#define MAX_OP_SIZE 16
#define HBC_VADDR_BASE 0x10000000
#ifndef R_ARCH_SYNTAX_CAMEL
#define R_ARCH_SYNTAX_CAMEL 6
#endif
@@ -169,7 +171,7 @@ static void parse_operands_and_set_ptr(RAnalOp *op, const ut8 *bytes, ut32 size,
HBCStringMeta meta;
Result meta_result = hbc_get_string_meta (hs->hbc, string_id, &meta);
if (meta_result.code == RESULT_SUCCESS) {
op->ptr = (st64)meta.offset;
op->ptr = (st64)(HBC_VADDR_BASE + meta.offset);
}
}
}
@@ -185,7 +187,7 @@ static void parse_operands_and_set_ptr(RAnalOp *op, const ut8 *bytes, ut32 size,
offset = fi.offset;
(void)fi.size;
(void)fi.param_count;
op->ptr = (st64)offset;
op->ptr = (st64)(HBC_VADDR_BASE + offset);
}
}
}
@@ -231,8 +233,12 @@ static bool decode(RArchSession *s, RAnalOp *op, RArchDecodeMask mask) {
.resolve_string_ids = true,
.string_tables = &string_tables,
.hbc = hs->hbc,
};
if (mask & R_ARCH_OP_MASK_ESIL && mask & R_ARCH_OP_MASK_DISASM) {
};
/* Inlining of buffer-literal contents into the disasm line is opt-in —
* it makes arch.decode O(n) in literal size and breaks r2's O(1) per-op
* disasm assumption. Toggle via `e hbc.inline_buffer_literals=true`
* (core plugin updates a process-wide flag). */
if ((mask & R_ARCH_OP_MASK_DISASM) && hbc_get_inline_literals ()) {
ctx.build_objects = true;
}
+169 -169
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
};
+79 -12
View File
@@ -2,6 +2,7 @@
#include <r_bin.h>
#include <hbc/hbc.h>
#include <hbc/literals.h>
#include "utils.inc.c"
#define HEADER_MAGIC 0x1f1903c103bc1fc6ULL
@@ -141,6 +142,7 @@ 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;
}
static RBinInfo *bininfo(RBinFile *bf) {
@@ -171,6 +173,26 @@ static RBinInfo *bininfo(RBinFile *bf) {
return ret;
}
/* Helper: append a section if the pool is present. Owns no strings — `name`
* is duplicated into a heap-allocated section name via strdup. */
static void add_pool_section(RList *list, const char *name, ut64 paddr, ut64 size) {
if (!size || !paddr) {
return;
}
RBinSection *s = R_NEW0 (RBinSection);
if (!s) {
return;
}
s->name = strdup (name);
s->size = size;
s->vsize = size;
s->paddr = paddr;
s->vaddr = HBC_VADDR_BASE + paddr;
s->perm = R_PERM_R;
s->add = true;
r_list_append (list, s);
}
static RList *sections(RBinFile *bf) {
RList *sections = r_list_newf ((RListFree)free);
if (!sections) {
@@ -183,9 +205,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;
}
@@ -263,6 +301,38 @@ 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);
if (!sym) {
continue;
}
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);
}
return symbols;
}
@@ -285,21 +355,18 @@ static RList *strings(RBinFile *bf) {
if (hbc_get_string_meta (hbc, i, &meta).code != RESULT_SUCCESS) {
continue;
}
if (meta.length == 0) {
const size_t str_len = strlen (str);
if (str_len == 0) {
continue;
}
RBinString *ptr = R_NEW0 (RBinString);
if (meta.length >= R_BIN_SIZEOF_STRINGS) {
ptr->string = r_str_ndup (str, R_BIN_SIZEOF_STRINGS - 4);
if (ptr->string) {
char *tmp = r_str_newf ("%s...", ptr->string);
free (ptr->string);
ptr->string = tmp;
}
if (str_len >= R_BIN_SIZEOF_STRINGS) {
char *trunc = r_str_ndup (str, R_BIN_SIZEOF_STRINGS - 4);
ptr->string = r_str_newf ("%s...", trunc? trunc: "");
free (trunc);
} else {
ptr->string = r_str_ndup (str, meta.length);
ptr->string = r_str_ndup (str, str_len);
}
if (!ptr->string) {
free (ptr);
@@ -307,7 +374,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;
+420 -48
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;
}
@@ -355,62 +360,399 @@ 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) {
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);
return;
}
u32 function_id = parse_function_id (addr_str);
HBCDecompOptions opts = { .pretty_literals = true, .suppress_comments = false };
u32 count = hbc_function_count (ctx->hbc);
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);
return;
}
/* 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 +767,7 @@ 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: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 +834,9 @@ 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 '?': /* pd:h? */
cmd_help (core);
break;
@@ -502,6 +848,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 +935,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;
}
+449 -480
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 ; str.print_123__
; 0x100000a2 ; "print(123);"
0x100000b4 5e000000 direct_eval r0, r0, 0
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
+15 -3
View File
@@ -178,13 +178,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
+554 -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 ; str.print_123__
; 0x100000a2 ; "print(123);"
0x100000b4 5e000000 direct_eval r0, r0, 0
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 ; 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
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 ; str.print_123__
; 0x100000a2 ; "print(123);"
0x100000b4 5e000000 direct_eval r0, r0, 0
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,45 @@ EXPECT=<<EOF
EOF
RUN
NAME=hbc get_global_object instruction
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 +331,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 +354,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 +377,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 +400,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 +423,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 +446,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 +469,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 +492,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 +523,7 @@ EXPECT=<<EOF
EOF
RUN
NAME=hbc assembler
NAME=hbc pa ret
FILE=--
CMDS=<<EOF
-a hbc
@@ -294,3 +533,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