Compare commits

...
6 Commits
Author SHA1 Message Date
pancake 9c57cba8b5 Release 1.2.8 2026-04-28 16:33:08 +02:00
pancake 9fb8ec1393 Add ih/iH header dumping support 2026-04-24 01:55:48 +02:00
pancake 02ee8c6dea Bottleneck found and got 26x speedup loading times 2026-04-24 01:46:00 +02:00
pancake c9384a7f3c Add pd:he to list all functions with evals 2026-04-24 01:31:52 +02:00
pancake d73ec29dc8 Add the ability to list cjs modules and show them as il libs 2026-04-24 01:24:05 +02:00
pancake bfae644555 SourceLine exposure and strip details in rbininfo
* Honors bin.dbginfo for loding faster
* Uses this info to set the stripped field
2026-04-24 01:12:41 +02:00
18 changed files with 1602 additions and 99 deletions

No files matched your search

+1 -2
View File
@@ -1,5 +1,5 @@
build/
bin/libhbctool
bin/
third_party/
*.d
*.o
@@ -8,4 +8,3 @@ third_party/
test/bins
*.swp
include/hbc/version.h
./bin
+12 -2
View File
@@ -20,6 +20,7 @@ OPCODES_SRC = $(filter-out $(SRC_DIR)/opcodes/%.inc.c,$(wildcard $(SRC_DIR)/opco
LIB_SRC = $(UTILS_SRC) $(PARSERS_SRC) $(DISASM_SRC) $(DECOMPILE_SRC) $(OPCODES_SRC) \
$(SRC_DIR)/hbc.c $(SRC_DIR)/literals_api.c $(SRC_DIR)/opcodes/encoder.c $(SRC_DIR)/opcodes/decoder.c $(SRC_DIR)/r2.c
MAIN_SRC = src/tool/libhbctool.c
HBC_HEADERS = $(shell find include -name '*.h')
## Object files
LIB_OBJ = $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/%.o,$(LIB_SRC))
@@ -55,11 +56,11 @@ $(STATIC_LIB): $(LIB_OBJ)
$(BIN_FILE): $(STATIC_LIB) $(MAIN_OBJ) | $(shell mkdir -p $(BIN_DIR))
$(CC) $(CFLAGS) -o $@ $(MAIN_OBJ) -L$(BUILD_DIR) -lhbc
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c | $(VH)
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c $(HBC_HEADERS) | $(VH)
@mkdir -p $(dir $@)
$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@
$(MAIN_OBJ): $(MAIN_SRC) | $(VH)
$(MAIN_OBJ): $(MAIN_SRC) $(HBC_HEADERS) | $(VH)
$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@
clean:
@@ -72,6 +73,15 @@ TEST_DIR = tests
TEST_SRC = $(wildcard $(TEST_DIR)/*.c)
TEST_BIN = $(BIN_DIR)/run_tests
$(TEST_BIN): $(STATIC_LIB) $(TEST_SRC) | $(shell mkdir -p $(BIN_DIR))
$(CC) $(CFLAGS) $(INCLUDES) -o $@ $(TEST_SRC) -L$(BUILD_DIR) -lhbc
test: $(BIN_FILE) $(TEST_BIN)
$(TEST_BIN)
$(BIN_FILE) sl test/bins/hbc/bespoke_eval.hbc | grep -q '0x000000b4 f=0 +0x4 yes.js:1:5 stmt=0'
$(BIN_FILE) h test/bins/hbc/bespoke_eval.hbc | grep -q 'Source Locations: 13 bytes'
$(BIN_FILE) h test/bins/hbc/index.android.bundle | grep -q 'Source Locations: 0 bytes'
$(BIN_FILE) libs test/bins/hbc/index.android.bundle | grep -q 'react-native'
r2:
$(MAKE) clean
+59
View File
@@ -63,6 +63,34 @@ typedef struct {
u32 param_count; // Number of parameters
} HBCFunc;
typedef struct {
u32 function_id;
u32 address;
u32 function_address;
u32 line;
u32 column;
u32 statement;
const char *filename;
} HBCSourceLine;
typedef struct {
HBCSourceLine *lines;
u32 count;
} HBCSourceLineArray;
typedef struct {
bool has_debug_info;
u32 filename_count;
u32 filename_storage_size;
u32 file_region_count;
u32 source_locations_size;
u32 scope_desc_data_size;
u32 textified_data_size;
u32 string_table_size;
u32 debug_data_size;
u32 functions_with_debug_info;
} HBCDebugInfo;
/* String table data */
typedef struct {
u32 string_count;
@@ -220,6 +248,14 @@ Result hbc_get_string_tables(HBC *hbc, HBCStrs *out);
*/
Result hbc_get_function_source(HBC *hbc, u32 function_id, const char **out_src);
/**
* Get decoded source-line records from debug information.
*/
Result hbc_get_source_lines(HBC *hbc, HBCSourceLineArray *out);
void hbc_free_source_lines(HBCSourceLineArray *arr);
bool hbc_has_source_lines(HBC *hbc);
Result hbc_get_debug_info(HBC *hbc, HBCDebugInfo *out);
/**
* Get the raw bytecode bytes for a function.
*/
@@ -308,6 +344,22 @@ typedef struct {
u32 count;
} HBCBindings;
typedef struct {
char *name;
char *path;
char *version;
const char *kind; /* cjs, package, js, native, global */
u32 function_id;
u32 offset;
u32 string_id;
bool inferred;
} HBCModule;
typedef struct {
HBCModule *modules;
u32 count;
} HBCModules;
/**
* Free instruction array.
*/
@@ -321,6 +373,13 @@ void hbc_free_insns(HBCInsn *insns, u32 count);
Result hbc_scan_bindings(HBC *hbc, HBCBindings *out);
void hbc_free_bindings(HBCBindings *bindings);
/**
* List probable bundled modules/libraries. Hermes bytecode has no SBOM table,
* so versions are only populated when they are explicitly preserved in strings.
*/
Result hbc_list_modules(HBC *hbc, HBCModules *out);
void hbc_free_modules(HBCModules *modules);
/**
* Encoding buffer and functions
*/
+12 -1
View File
@@ -263,11 +263,21 @@ struct HBCReader {
FunctionSourceEntry *function_sources;
size_t function_source_count;
/* Debug info */
/* Debug info — split into two phases:
* debug_info_header_loaded: cheap parse of the DebugInfoHeader fields
* only (a handful of u32s), populated eagerly at load time so callers
* can report accurate "has source lines" / "stripped" without paying
* for the full parse.
* debug_info_loaded: full parse of filenames, file regions, and the
* sources / scope / textified / string-table storage blobs. Populated
* lazily on first hbc_get_source_lines / hbc_get_debug_info call. */
bool debug_info_header_loaded;
bool debug_info_loaded;
DebugInfoHeader debug_info_header;
OffsetLengthPair *debug_string_table;
u8 *debug_string_storage;
size_t debug_string_storage_size;
char **debug_filenames;
DebugFileRegion *debug_file_regions;
size_t debug_file_region_count;
u8 *sources_data_storage;
@@ -298,6 +308,7 @@ Result _hbc_reader_read_bigints(HBCReader *reader);
Result _hbc_reader_read_regexp(HBCReader *reader);
Result _hbc_reader_read_cjs_modules(HBCReader *reader);
Result _hbc_reader_read_function_sources(HBCReader *reader);
Result _hbc_reader_read_debug_info_header(HBCReader *reader);
Result _hbc_reader_read_debug_info(HBCReader *reader);
Result _hbc_reader_read_whole_file(HBCReader *reader, const char *filename);
+1 -1
View File
@@ -1,7 +1,7 @@
project(
'r2hermes',
'c',
version: '1.2.6',
version: '1.2.8',
license: 'MIT',
default_options: [
'c_std=c11',
+486 -55
View File
@@ -118,7 +118,16 @@ Result hbc_open_from_memory(const u8 *data, size_t size, HBC **out) {
hbc_close (hbc);
return res;
}
(void)_hbc_reader_read_debug_info;
/* Read only the DebugInfoHeader eagerly — a handful of u32s — so
* hbc_has_source_lines() can answer accurately without committing to the
* full body parse. The filename, file region and sources/scope/textified
* storage blobs are read lazily on first hbc_get_source_lines /
* hbc_get_debug_info call. */
res = _hbc_reader_read_debug_info_header (&hbc->reader);
if (res.code != RESULT_SUCCESS) {
hbc_close (hbc);
return res;
}
*out = hbc;
return SUCCESS_RESULT ();
@@ -239,10 +248,238 @@ Result hbc_get_function_source(HBC *hbc, u32 function_id, const char **out_src)
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Function ID out of range");
}
*out_src = NULL;
if (hbc->reader.function_sources && function_id < hbc->reader.header.functionSourceCount) {
u32 string_id = hbc->reader.function_sources[function_id].string_id;
if (string_id < hbc->reader.header.stringCount) {
*out_src = hbc->reader.strings[string_id];
if (hbc->reader.function_sources) {
for (u32 i = 0; i < hbc->reader.header.functionSourceCount; i++) {
if (hbc->reader.function_sources[i].function_id != function_id) {
continue;
}
u32 string_id = hbc->reader.function_sources[i].string_id;
if (string_id < hbc->reader.header.stringCount) {
*out_src = hbc->reader.strings[string_id];
}
break;
}
}
return SUCCESS_RESULT ();
}
static bool read_sleb128(const u8 *data, size_t size, size_t *pos, int64_t *out) {
uint64_t result = 0;
unsigned shift = 0;
u8 byte = 0;
do {
if (*pos >= size || shift > 63) {
return false;
}
byte = data[(*pos)++];
if (shift == 63 && (byte & 0x7e)) {
return false;
}
result |= ((uint64_t)(byte & 0x7f)) << shift;
shift += 7;
} while (byte & 0x80);
if ((shift < 64) && (byte & 0x40)) {
result |= (~0ULL) << shift;
}
*out = (int64_t)result;
return true;
}
static const char *debug_filename_at(HBCReader *reader, u32 index) {
const u32 count = reader->debug_info_header.filename_count;
if (index >= count || !reader->debug_string_table || !reader->debug_string_storage) {
return NULL;
}
if (!reader->debug_filenames) {
reader->debug_filenames = (char **)calloc (count, sizeof (char *));
if (!reader->debug_filenames) {
return NULL;
}
}
if (!reader->debug_filenames[index]) {
const OffsetLengthPair *entry = &reader->debug_string_table[index];
const size_t storage_size = reader->debug_string_storage_size;
if ((size_t)entry->offset > storage_size || (size_t)entry->length > storage_size - entry->offset) {
return NULL;
}
char *filename = (char *)malloc ((size_t)entry->length + 1);
if (!filename) {
return NULL;
}
memcpy (filename, reader->debug_string_storage + entry->offset, entry->length);
filename[entry->length] = '\0';
reader->debug_filenames[index] = filename;
}
return reader->debug_filenames[index];
}
static const char *debug_filename_for_source_offset(HBCReader *reader, u32 source_offset) {
const DebugFileRegion *best = NULL;
for (u32 i = 0; i < reader->debug_file_region_count; i++) {
const DebugFileRegion *region = &reader->debug_file_regions[i];
if (region->from_address <= source_offset && (!best || region->from_address >= best->from_address)) {
best = region;
}
}
return best? debug_filename_at (reader, best->filename_id): NULL;
}
static Result append_source_line(HBCSourceLineArray *arr, u32 *cap, const HBCSourceLine *line) {
if (arr->count >= *cap) {
u32 next = *cap? (*cap * 2): 64;
HBCSourceLine *lines = (HBCSourceLine *)realloc (arr->lines, (size_t)next * sizeof (HBCSourceLine));
if (!lines) {
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "Failed to allocate source lines");
}
arr->lines = lines;
*cap = next;
}
arr->lines[arr->count++] = *line;
return SUCCESS_RESULT ();
}
#define PARSE_FAIL(msg) do { res = ERROR_RESULT (RESULT_ERROR_PARSING_FAILED, msg); goto fail; } while (0)
Result hbc_get_source_lines(HBC *hbc, HBCSourceLineArray *out) {
if (!hbc || !out) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Invalid arguments");
}
memset (out, 0, sizeof (*out));
HBCReader *reader = &hbc->reader;
RETURN_IF_ERROR (_hbc_reader_read_debug_info (reader));
const u8 *data = reader->sources_data_storage;
const size_t size = reader->sources_data_storage_size;
if (!data || !size) {
return SUCCESS_RESULT ();
}
Result res = SUCCESS_RESULT ();
u32 cap = 0;
size_t pos = 0;
while (pos < size) {
const size_t source_offset = pos;
int64_t function_id = 0, line = 0, column = 0;
if (source_offset > UINT32_MAX
|| !read_sleb128 (data, size, &pos, &function_id)
|| !read_sleb128 (data, size, &pos, &line)
|| !read_sleb128 (data, size, &pos, &column)) {
PARSE_FAIL ("Invalid source location header");
}
if (function_id < 0 || function_id >= (int64_t)reader->header.functionCount
|| line < 0 || line > UINT32_MAX || column < 0 || column > UINT32_MAX) {
PARSE_FAIL ("Source location header out of range");
}
HBCFunc fi = { 0 };
(void)hbc_get_function_info (hbc, (u32)function_id, &fi);
HBCSourceLine sl = {
.function_id = (u32)function_id,
.address = fi.offset,
.line = (u32)line,
.column = (u32)column,
.filename = debug_filename_for_source_offset (reader, (u32)source_offset)
};
res = append_source_line (out, &cap, &sl);
if (res.code != RESULT_SUCCESS) {
goto fail;
}
int64_t address = 0, statement = 0;
for (;;) {
int64_t adelta = 0;
if (!read_sleb128 (data, size, &pos, &adelta)) {
PARSE_FAIL ("Invalid source location address delta");
}
if (adelta == -1) {
break;
}
int64_t ldelta = 0, cdelta = 0, scope = 0, env = 0, sdelta = 0;
if (adelta < 0
|| !read_sleb128 (data, size, &pos, &ldelta)
|| !read_sleb128 (data, size, &pos, &cdelta)
|| !read_sleb128 (data, size, &pos, &scope)
|| !read_sleb128 (data, size, &pos, &env)) {
PARSE_FAIL ("Invalid source location entry");
}
if ((ldelta & 1) && !read_sleb128 (data, size, &pos, &sdelta)) {
PARSE_FAIL ("Invalid source location statement delta");
}
ldelta = (ldelta - (ldelta & 1)) / 2;
address += adelta;
line += ldelta;
column += cdelta;
statement += sdelta;
if (address < 0 || address > UINT32_MAX || (uint64_t)fi.offset + (uint64_t)address > UINT32_MAX
|| line < 0 || line > UINT32_MAX || column < 0 || column > UINT32_MAX
|| statement < 0 || statement > UINT32_MAX) {
PARSE_FAIL ("Source location entry out of range");
}
sl.address = fi.offset + (u32)address;
sl.function_address = (u32)address;
sl.line = (u32)line;
sl.column = (u32)column;
sl.statement = (u32)statement;
res = append_source_line (out, &cap, &sl);
if (res.code != RESULT_SUCCESS) {
goto fail;
}
}
}
return SUCCESS_RESULT ();
fail:
hbc_free_source_lines (out);
return res;
}
#undef PARSE_FAIL
void hbc_free_source_lines(HBCSourceLineArray *arr) {
if (!arr) {
return;
}
free (arr->lines);
memset (arr, 0, sizeof (*arr));
}
/* Describes the file, not the runtime preference: true iff the file carries
* a non-empty source-locations blob. Relies on the DebugInfoHeader parsed at
* load time, so callers pay no body-parse cost for this query. */
bool hbc_has_source_lines(HBC *hbc) {
if (!hbc || hbc->reader.header.debugInfoOffset == 0) {
return false;
}
if (_hbc_reader_read_debug_info_header (&hbc->reader).code != RESULT_SUCCESS) {
return false;
}
return hbc->reader.debug_info_header.scope_desc_data_offset > 0;
}
Result hbc_get_debug_info(HBC *hbc, HBCDebugInfo *out) {
if (!hbc || !out) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Invalid arguments");
}
memset (out, 0, sizeof (*out));
HBCReader *reader = &hbc->reader;
out->has_debug_info = reader->header.debugInfoOffset != 0;
if (!out->has_debug_info) {
return SUCCESS_RESULT ();
}
RETURN_IF_ERROR (_hbc_reader_read_debug_info (reader));
out->filename_count = reader->debug_info_header.filename_count;
out->filename_storage_size = reader->debug_info_header.filename_storage_size;
out->file_region_count = reader->debug_info_header.file_region_count;
out->source_locations_size = (u32)reader->sources_data_storage_size;
out->scope_desc_data_size = (u32)reader->scope_desc_data_storage_size;
out->textified_data_size = (u32)reader->textified_data_storage_size;
out->string_table_size = (u32)reader->string_table_storage_size;
out->debug_data_size = reader->debug_info_header.debug_data_size;
if (reader->function_headers) {
for (u32 i = 0; i < reader->header.functionCount; i++) {
if (reader->function_headers[i].hasDebugInfo) {
out->functions_with_debug_info++;
}
}
}
return SUCCESS_RESULT ();
@@ -626,12 +863,10 @@ static const BindingHint g_hints[] = {
};
#define HINTS_N (sizeof (g_hints) / sizeof (g_hints[0]))
static bool str_eq(const char *a, const char *b) {
if (a == b) {
return true;
}
return a && b && !strcmp (a, b);
}
/* Per-sid dedup flags for hbc_scan_bindings; avoids O(N^2) strcmp dedup. */
#define SEEN_CJS 0x1
#define SEEN_IMP 0x2
#define SEEN_EXP 0x4
static bool is_export_marker(const char *s) {
return !strcmp (s, "exports") || !strcmp (s, "module");
@@ -643,13 +878,6 @@ static bool is_export_store(const char *name) {
static Result binding_add(HBCBindings *out, HBCBindingType type, const char *kind,
const char *name, const char *module, u32 function_id, u32 offset, u32 string_id) {
for (u32 i = 0; i < out->count; i++) {
HBCBinding *b = &out->bindings[i];
if (b->type == type && !strcmp (b->kind, kind)
&& !strcmp (b->name, name) && str_eq (b->module, module)) {
return SUCCESS_RESULT ();
}
}
HBCBinding *nb = realloc (out->bindings, (out->count + 1) * sizeof (HBCBinding));
if (!nb) {
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "OOM");
@@ -712,7 +940,11 @@ static size_t decode_operands(const Instruction *inst, const u8 *bytes, size_t l
return pos;
}
static Result scan_hint_matches(HBCBindings *out, const char *s, u32 fid, u32 off, u32 sid) {
static Result scan_hint_matches(HBCBindings *out, const char *s, u32 fid, u32 off, u32 sid, u8 *flag) {
/* Dedup identical (kind,module) across hints (pointers are stable literals).
* Set SEEN_EXP when adding an EXPORT so caller's put_by_id skips this sid. */
const void *added[HINTS_N * 2];
u32 added_n = 0;
for (size_t h = 0; h < HINTS_N; h++) {
const BindingHint *hint = &g_hints[h];
bool match = hint->name? !strcmp (s, hint->name):
@@ -720,37 +952,27 @@ static Result scan_hint_matches(HBCBindings *out, const char *s, u32 fid, u32 of
if (!match) {
continue;
}
bool dup = false;
for (u32 a = 0; a < added_n; a += 2) {
if (added[a] == hint->kind && added[a + 1] == hint->module) {
dup = true;
break;
}
}
if (dup) {
continue;
}
added[added_n++] = hint->kind;
added[added_n++] = hint->module;
if (hint->type == HBC_BINDING_EXPORT) {
*flag |= SEEN_EXP;
}
RETURN_IF_ERROR (binding_add (out, hint->type, hint->kind, s,
hint->module, fid, off, sid));
}
return SUCCESS_RESULT ();
}
static bool function_has_export_marker(HBCReader *r, const u8 *code, u32 size, HBCISA isa) {
for (u32 pc = 0; pc < size; ) {
u8 op = code[pc];
const Instruction *inst = (op < isa.count)? &isa.instructions[op]: NULL;
if (!inst || !inst->name || !inst->binary_size) {
break;
}
u32 values[6];
if (!decode_operands (inst, code + pc, size - pc, values)) {
break;
}
for (int i = 0; i < 6 && inst->operands[i].operand_type != OPERAND_TYPE_NONE; i++) {
if (inst->operands[i].operand_meaning == OPERAND_MEANING_STRING_ID) {
u32 sid = values[i];
const char *s = (sid < r->header.stringCount && r->strings)? r->strings[sid]: NULL;
if (s && is_export_marker (s)) {
return true;
}
}
}
pc += inst->binary_size;
}
return false;
}
Result hbc_scan_bindings(HBC *hbc, HBCBindings *out) {
if (!hbc || !out) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "invalid args");
@@ -761,16 +983,29 @@ Result hbc_scan_bindings(HBC *hbc, HBCBindings *out) {
if (!isa.instructions) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "no isa for version");
}
u32 sc = r->header.stringCount;
u8 *seen = sc? calloc (sc, 1): NULL;
if (sc && !seen) {
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "OOM");
}
Result res = SUCCESS_RESULT ();
if (r->header.cjsModuleCount > 0 && r->header.version >= 77 && r->cjs_modules) {
for (u32 i = 0; i < r->header.cjsModuleCount; i++) {
u32 sid = r->cjs_modules[i].symbol_id;
const char *name = (sid < r->header.stringCount && r->strings)? r->strings[sid]: NULL;
if (name && *name) {
RETURN_IF_ERROR (binding_add (out, HBC_BINDING_IMPORT,
"cjs", name, "commonjs", 0, r->cjs_modules[i].offset, sid));
const char *name = (sid < sc && r->strings)? r->strings[sid]: NULL;
if (!name || !*name || (seen[sid] & SEEN_CJS)) {
continue;
}
seen[sid] |= SEEN_CJS;
res = binding_add (out, HBC_BINDING_IMPORT, "cjs", name,
"commonjs", 0, r->cjs_modules[i].offset, sid);
if (res.code != RESULT_SUCCESS) {
goto done;
}
}
}
struct { u32 sid, off; } *cand = NULL;
u32 cand_cap = 0;
for (u32 fid = 0; fid < r->header.functionCount; fid++) {
const u8 *code = NULL;
u32 size = 0;
@@ -780,7 +1015,8 @@ Result hbc_scan_bindings(HBC *hbc, HBCBindings *out) {
}
HBCFunc fi = { 0 };
(void)hbc_get_function_info (hbc, fid, &fi);
bool has_export_marker = function_has_export_marker (r, code, size, isa);
u32 cand_n = 0;
bool has_marker = false;
for (u32 pc = 0; pc < size; ) {
u8 op = code[pc];
const Instruction *inst = (op < isa.count)? &isa.instructions[op]: NULL;
@@ -791,25 +1027,72 @@ Result hbc_scan_bindings(HBC *hbc, HBCBindings *out) {
if (!decode_operands (inst, code + pc, size - pc, values)) {
break;
}
bool is_store = is_export_store (inst->name);
for (int i = 0; i < 6 && inst->operands[i].operand_type != OPERAND_TYPE_NONE; i++) {
if (inst->operands[i].operand_meaning != OPERAND_MEANING_STRING_ID) {
continue;
}
u32 sid = values[i];
const char *s = (sid < r->header.stringCount && r->strings)? r->strings[sid]: NULL;
if (sid >= sc || !r->strings) {
continue;
}
const char *s = r->strings[sid];
if (!s || !*s) {
continue;
}
RETURN_IF_ERROR (scan_hint_matches (out, s, fid, fi.offset + pc, sid));
if (has_export_marker && is_export_store (inst->name) && !is_export_marker (s)) {
RETURN_IF_ERROR (binding_add (out, HBC_BINDING_EXPORT,
"module", s, NULL, fid, fi.offset + pc, sid));
bool marker = is_export_marker (s);
if (marker) {
has_marker = true;
}
if (!(seen[sid] & SEEN_IMP)) {
seen[sid] |= SEEN_IMP;
res = scan_hint_matches (out, s, fid, fi.offset + pc, sid, &seen[sid]);
if (res.code != RESULT_SUCCESS) {
goto done_scan;
}
}
if (is_store && !marker && !(seen[sid] & SEEN_EXP)) {
if (cand_n == cand_cap) {
u32 nc = cand_cap? cand_cap * 2: 16;
void *nb = realloc (cand, nc * sizeof (*cand));
if (!nb) {
res = ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "OOM");
goto done_scan;
}
cand = nb;
cand_cap = nc;
}
cand[cand_n].sid = sid;
cand[cand_n].off = fi.offset + pc;
cand_n++;
}
}
pc += inst->binary_size;
}
if (!has_marker) {
continue;
}
for (u32 k = 0; k < cand_n; k++) {
u32 sid = cand[k].sid;
if (seen[sid] & SEEN_EXP) {
continue;
}
seen[sid] |= SEEN_EXP;
res = binding_add (out, HBC_BINDING_EXPORT, "module",
r->strings[sid], NULL, fid, cand[k].off, sid);
if (res.code != RESULT_SUCCESS) {
goto done_scan;
}
}
}
return SUCCESS_RESULT ();
done_scan:
free (cand);
done:
free (seen);
if (res.code != RESULT_SUCCESS) {
hbc_free_bindings (out);
}
return res;
}
void hbc_free_bindings(HBCBindings *bindings) {
@@ -825,6 +1108,154 @@ void hbc_free_bindings(HBCBindings *bindings) {
bindings->count = 0;
}
static char *dup_range(const char *s, size_t n) {
char *ret = (char *)malloc (n + 1);
if (!ret) {
return NULL;
}
memcpy (ret, s, n);
ret[n] = 0;
return ret;
}
static char *package_from_path(const char *s) {
const char *nm = strstr (s, "node_modules/");
if (nm) {
const char *p = nm + strlen ("node_modules/");
const char *end;
if (*p == '@') {
const char *scope_end = strchr (p, '/');
if (!scope_end) {
return strdup (p);
}
end = strchr (scope_end + 1, '/');
} else {
end = strchr (p, '/');
}
return end? dup_range (p, (size_t)(end - p)): strdup (p);
}
if (s[0] == '@') {
const char *scope_end = strchr (s, '/');
if (scope_end) {
const char *end = strchr (scope_end + 1, '/');
return end? dup_range (s, (size_t)(end - s)): strdup (s);
}
}
return NULL;
}
static bool module_eq(const HBCModule *m, const char *kind, const char *name) {
return m->kind && kind && !strcmp (m->kind, kind)
&& m->name && name && !strcmp (m->name, name);
}
static Result module_add(HBCModules *out, const char *kind, const char *name,
const char *path, const char *version, u32 function_id, u32 offset,
u32 string_id, bool inferred) {
if (!name || !*name) {
return SUCCESS_RESULT ();
}
for (u32 i = 0; i < out->count; i++) {
if (module_eq (&out->modules[i], kind, name)) {
return SUCCESS_RESULT ();
}
}
HBCModule *nm = realloc (out->modules, (out->count + 1) * sizeof (HBCModule));
if (!nm) {
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "OOM");
}
out->modules = nm;
HBCModule *m = &nm[out->count++];
*m = (HBCModule){
.name = strdup (name),
.path = path? strdup (path): NULL,
.version = version? strdup (version): NULL,
.kind = kind,
.function_id = function_id,
.offset = offset,
.string_id = string_id,
.inferred = inferred,
};
if (!m->name || (path && !m->path) || (version && !m->version)) {
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "OOM");
}
return SUCCESS_RESULT ();
}
Result hbc_list_modules(HBC *hbc, HBCModules *out) {
if (!hbc || !out) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "invalid args");
}
memset (out, 0, sizeof (*out));
HBCReader *r = &hbc->reader;
if (r->header.cjsModuleCount > 0 && r->header.version >= 77 && r->cjs_modules) {
for (u32 i = 0; i < r->header.cjsModuleCount; i++) {
u32 sid = r->cjs_modules[i].symbol_id;
const char *s = (sid < r->header.stringCount && r->strings)? r->strings[sid]: NULL;
if (!s || !*s) {
continue;
}
char *pkg = package_from_path (s);
if (pkg) {
Result res = module_add (out, "package", pkg, s, NULL, 0,
r->cjs_modules[i].offset, sid, true);
free (pkg);
if (res.code != RESULT_SUCCESS) {
hbc_free_modules (out);
return res;
}
} else {
Result res = module_add (out, "cjs", s, s, NULL, 0,
r->cjs_modules[i].offset, sid, false);
if (res.code != RESULT_SUCCESS) {
hbc_free_modules (out);
return res;
}
}
}
}
HBCBindings bindings = { 0 };
Result res = hbc_scan_bindings (hbc, &bindings);
if (res.code != RESULT_SUCCESS) {
hbc_free_modules (out);
return res;
}
for (u32 i = 0; i < bindings.count; i++) {
HBCBinding *b = &bindings.bindings[i];
if (b->type != HBC_BINDING_IMPORT) {
continue;
}
const char *name = b->module? b->module: b->name;
if (!name || !*name || !strcmp (name, "commonjs")) {
name = b->name;
}
res = module_add (out, b->kind? b->kind: "js", name,
b->name, NULL, b->function_id, b->offset, b->string_id, true);
if (res.code != RESULT_SUCCESS) {
hbc_free_bindings (&bindings);
hbc_free_modules (out);
return res;
}
}
hbc_free_bindings (&bindings);
return SUCCESS_RESULT ();
}
void hbc_free_modules(HBCModules *modules) {
if (!modules || !modules->modules) {
return;
}
for (u32 i = 0; i < modules->count; i++) {
free (modules->modules[i].name);
free (modules->modules[i].path);
free (modules->modules[i].version);
}
free (modules->modules);
modules->modules = NULL;
modules->count = 0;
}
/* Encoding functions */
Result hbc_enc(
+50 -9
View File
@@ -81,6 +81,12 @@ Result _hbc_reader_cleanup(HBCReader *reader) {
/* Clean up debug info */
free (reader->debug_string_table);
free (reader->debug_string_storage);
if (reader->debug_filenames) {
for (u32 i = 0; i < reader->debug_info_header.filename_count; i++) {
free (reader->debug_filenames[i]);
}
free (reader->debug_filenames);
}
free (reader->debug_file_regions);
free (reader->sources_data_storage);
free (reader->scope_desc_data_storage);
@@ -1578,22 +1584,31 @@ Result _hbc_reader_read_function_sources(HBCReader *reader) {
return SUCCESS_RESULT ();
}
/* Read debug information */
Result _hbc_reader_read_debug_info(HBCReader *reader) {
/* Size of the on-disk DebugInfoHeader block, in bytes. */
static u32 debug_info_header_bytes(u32 version) {
/* v<91: 5 u32s (filename_count, filename_storage_size, file_region_count,
* scope_desc_data_offset, debug_data_size).
* v>=91: 7 u32s (adds textified_data_offset, string_table_offset). */
return (version >= 91? 7u: 5u) * sizeof (u32);
}
/* Read just the DebugInfoHeader fields (no storage blobs, no allocations).
* Cheap enough to run eagerly at load time so "has source lines" is knowable
* without committing to a full debug-info parse. Idempotent. */
Result _hbc_reader_read_debug_info_header(HBCReader *reader) {
if (!reader) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Reader is NULL");
}
if (reader->header.debugInfoOffset == 0) {
if (reader->debug_info_header_loaded) {
return SUCCESS_RESULT ();
}
if (reader->header.version >= 97) {
if (reader->header.debugInfoOffset == 0) {
reader->debug_info_header_loaded = true;
return SUCCESS_RESULT ();
}
/* Seek to debug info offset */
RETURN_IF_ERROR (_hbc_buffer_reader_seek (&reader->file_buffer, reader->header.debugInfoOffset));
/* Read debug info header */
RETURN_IF_ERROR (_hbc_buffer_reader_read_u32 (&reader->file_buffer, &reader->debug_info_header.filename_count));
RETURN_IF_ERROR (_hbc_buffer_reader_read_u32 (&reader->file_buffer, &reader->debug_info_header.filename_storage_size));
RETURN_IF_ERROR (_hbc_buffer_reader_read_u32 (&reader->file_buffer, &reader->debug_info_header.file_region_count));
@@ -1606,6 +1621,30 @@ Result _hbc_reader_read_debug_info(HBCReader *reader) {
RETURN_IF_ERROR (_hbc_buffer_reader_read_u32 (&reader->file_buffer, &reader->debug_info_header.debug_data_size));
reader->debug_info_header_loaded = true;
return SUCCESS_RESULT ();
}
/* Read the full debug-info body (filenames, file regions, sources/scope/
* textified/string-table storage blobs). Builds on the cheap header parse.
* Idempotent: subsequent calls after a successful load are no-ops. */
Result _hbc_reader_read_debug_info(HBCReader *reader) {
if (!reader) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "Reader is NULL");
}
if (reader->debug_info_loaded) {
return SUCCESS_RESULT ();
}
RETURN_IF_ERROR (_hbc_reader_read_debug_info_header (reader));
if (reader->header.debugInfoOffset == 0) {
reader->debug_info_loaded = true;
return SUCCESS_RESULT ();
}
/* Seek past the already-parsed header to the start of the body. */
RETURN_IF_ERROR (_hbc_buffer_reader_seek (&reader->file_buffer,
reader->header.debugInfoOffset + debug_info_header_bytes (reader->header.version)));
/* Allocate debug string table */
if (reader->debug_info_header.filename_count > 0) {
reader->debug_string_table = (OffsetLengthPair *)malloc (
@@ -1707,6 +1746,7 @@ Result _hbc_reader_read_debug_info(HBCReader *reader) {
reader->string_table_storage_size = string_table_size;
}
reader->debug_info_loaded = true;
return SUCCESS_RESULT ();
}
@@ -2029,10 +2069,11 @@ Result _hbc_reader_read_whole_file(HBCReader *reader, const char *filename) {
}
}
/* Read debug info */
result = _hbc_reader_read_debug_info (reader);
/* Read only the DebugInfoHeader eagerly (cheap). The body is parsed lazily
* via _hbc_reader_read_debug_info() on first API use. */
result = _hbc_reader_read_debug_info_header (reader);
if (result.code != RESULT_SUCCESS) {
fprintf (stderr, "Error reading debug info: %s\n", result.error_message);
fprintf (stderr, "Error reading debug info header: %s\n", result.error_message);
return result;
}
+6 -5
View File
@@ -33,6 +33,7 @@ BIN_SRC = bin_hbc.c
ASM_SRC = asm_hbc.c
CORE_SRC = core_hbc.c
CORE_ONE_SRC = core_hbc_one.c
HBC_HEADERS = $(shell find ../../include -name '*.h') utils.inc.c
# Object files
ARCH_OBJ = arch_hbc.o
@@ -107,19 +108,19 @@ $(CORE_ONE_PLUGIN): $(CORE_ONE_OBJ) $(HERMES_LIB)
$(CC) $(LDFLAGS) -o $@ $(CORE_ONE_OBJ) $(HERMES_LIB)
# Compile object files
$(ARCH_OBJ): $(ARCH_SRC)
$(ARCH_OBJ): $(ARCH_SRC) $(HBC_HEADERS)
$(CC) $(CFLAGS) -c $< -o $@
$(BIN_OBJ): $(BIN_SRC)
$(BIN_OBJ): $(BIN_SRC) $(HBC_HEADERS)
$(CC) $(CFLAGS) -c $< -o $@
$(ASM_OBJ): $(ASM_SRC)
$(ASM_OBJ): $(ASM_SRC) $(HBC_HEADERS)
$(CC) $(CFLAGS) -c $< -o $@
$(CORE_OBJ): $(CORE_SRC)
$(CORE_OBJ): $(CORE_SRC) $(HBC_HEADERS)
$(CC) $(CFLAGS) -c $< -o $@
$(CORE_ONE_OBJ): $(CORE_ONE_SRC)
$(CORE_ONE_OBJ): $(CORE_ONE_SRC) $(HBC_HEADERS)
$(CC) $(CFLAGS) -c $< -o $@
+322 -13
View File
@@ -1,6 +1,7 @@
/* radare2 - LGPL - Copyright 2025-2026 - pancake */
#include <r_bin.h>
#include <r_bin_dwarf.h>
#include <hbc/hbc.h>
#include <hbc/literals.h>
#include "utils.inc.c"
@@ -132,7 +133,7 @@ static ut64 resolve_entrypoint(RBinFile *bf, HBC *hbc) {
return 0;
}
static void fill_info(RBinInfo *ret, const char *file_path, bool has_version, ut32 version) {
static void fill_info(RBinInfo *ret, const char *file_path, bool has_version, ut32 version, bool has_source_lines) {
ret->file = file_path? strdup (file_path): NULL;
ret->bclass = strdup ("Hermes bytecode");
ret->rclass = strdup ("hermes");
@@ -143,12 +144,14 @@ static void fill_info(RBinInfo *ret, const char *file_path, bool has_version, ut
ret->machine = strdup ("Hermes VM");
ret->cpu = has_version? r_str_newf ("%u", version): strdup ("unknown");
ret->has_va = true;
ret->dbg_info = has_source_lines? R_BIN_DBG_LINENUMS: R_BIN_DBG_STRIPPED;
}
static RBinInfo *bininfo(RBinFile *bf) {
RBinInfo *ret = R_NEW0 (RBinInfo);
bool has_version = false;
ut32 version = 0;
bool has_source_lines = false;
HBC *hbc = get_hbc (bf);
if (hbc) {
@@ -157,6 +160,10 @@ static RBinInfo *bininfo(RBinFile *bf) {
has_version = true;
version = hh.version;
}
/* Cheap: answered from the DebugInfoHeader parsed at load time.
* Reflects file truth (does this file carry source lines?) regardless
* of bin.dbginfo — that setting only gates whether we decode them. */
has_source_lines = hbc_has_source_lines (hbc);
}
/* Fallback: parse version directly from buffer */
@@ -169,7 +176,7 @@ static RBinInfo *bininfo(RBinFile *bf) {
}
}
fill_info (ret, bf->file, has_version, version);
fill_info (ret, bf->file, has_version, version, has_source_lines);
return ret;
}
@@ -213,15 +220,9 @@ static RList *sections(RBinFile *bf) {
* literals live, and lets users `s` / `pd @` straight into them. */
HBC *hbc = get_hbc (bf);
if (hbc) {
add_pool_section (sections, "slp.arrays",
hbc_get_pool_paddr (hbc, HBC_LIT_ARRAY),
hbc_get_pool_size (hbc, HBC_LIT_ARRAY));
add_pool_section (sections, "slp.object_keys",
hbc_get_pool_paddr (hbc, HBC_LIT_OBJECT),
hbc_get_pool_size (hbc, HBC_LIT_OBJECT));
add_pool_section (sections, "slp.object_values",
hbc_get_object_values_paddr (hbc),
hbc_get_object_values_size (hbc));
add_pool_section (sections, "slp.arrays", hbc_get_pool_paddr (hbc, HBC_LIT_ARRAY), hbc_get_pool_size (hbc, HBC_LIT_ARRAY));
add_pool_section (sections, "slp.object_keys", hbc_get_pool_paddr (hbc, HBC_LIT_OBJECT), hbc_get_pool_size (hbc, HBC_LIT_OBJECT));
add_pool_section (sections, "slp.object_values", hbc_get_object_values_paddr (hbc), hbc_get_object_values_size (hbc));
}
return sections;
@@ -337,8 +338,7 @@ static RList *symbols(RBinFile *bf) {
const char *prefix = k? "lit.obj.": "lit.arr.";
HBCPoolGroup *groups = NULL;
u32 n = 0;
if (hbc_literals_scan_pool (hbc, kind, &groups, &n).code
!= RESULT_SUCCESS || !groups) {
if (hbc_literals_scan_pool (hbc, kind, &groups, &n).code != RESULT_SUCCESS || !groups) {
continue;
}
for (u32 i = 0; i < n; i++) {
@@ -392,6 +392,85 @@ static RList *imports(RBinFile *bf) {
return imports;
}
static bool list_has_string(RList *list, const char *name) {
RListIter *iter;
const char *it;
r_list_foreach (list, iter, it) {
if (it && name && !strcmp (it, name)) {
return true;
}
}
return false;
}
static RList *libs(RBinFile *bf) {
RList *libs = r_list_newf (free);
HBC *hbc = get_hbc (bf);
if (!libs || !hbc) {
return libs;
}
HBCModules modules = { 0 };
if (hbc_list_modules (hbc, &modules).code != RESULT_SUCCESS) {
return libs;
}
for (u32 i = 0; i < modules.count; i++) {
HBCModule *m = &modules.modules[i];
if (m->name && *m->name && !list_has_string (libs, m->name)) {
r_list_append (libs, strdup (m->name));
}
}
hbc_free_modules (&modules);
return libs;
}
static R_UNOWNED RList *lines(RBinFile *bf) {
HBC *hbc = get_hbc (bf);
if (!hbc) {
return NULL;
}
/* Respect bin.dbginfo=false — return early so the lazy debug-info parse
*(triggered by hbc_get_source_lines) never runs for users who opted out. */
if (bf->rbin && !bf->rbin->want_dbginfo) {
return NULL;
}
HBCSourceLineArray lines = { 0 };
RList *ret = NULL;
if (hbc_get_source_lines (hbc, &lines).code != RESULT_SUCCESS || !lines.count) {
goto done;
}
ret = r_list_newf (free);
if (!ret) {
goto done;
}
const bool has_al = bf->addrline.al_add && bf->addrline.al_get;
for (u32 i = 0; i < lines.count; i++) {
HBCSourceLine *sl = &lines.lines[i];
ut64 addr = HBC_VADDR_BASE + sl->address;
const char *file = sl->filename? sl->filename: "";
if (has_al) {
bf->addrline.al_add (&bf->addrline, addr, file, NULL, sl->line, sl->column);
}
RBinAddrline *row = R_NEW0 (RBinAddrline);
if (!row) {
continue;
}
const RBinAddrline *stored = has_al? bf->addrline.al_get (&bf->addrline, addr): NULL;
if (stored) {
*row = *stored;
} else {
row->addr = addr;
row->file = UT32_MAX;
row->path = UT32_MAX;
row->line = sl->line;
row->column = sl->column;
}
r_list_append (ret, row);
}
done:
hbc_free_source_lines (&lines);
return ret;
}
static RList *strings(RBinFile *bf) {
RList *ret = r_list_newf ((RListFree)r_bin_string_free);
HBC *hbc = get_hbc (bf);
@@ -440,6 +519,232 @@ static RList *strings(RBinFile *bf) {
return ret;
}
static void hbc_source_hash_hex(const HBCHeader *hh, char *dst, size_t dstsz) {
if (!dst || dstsz < 41) {
return;
}
r_hex_bin2str (hh->sourceHash, 20, dst);
}
static void hbc_header_print_u32(RStrBuf *sb, ut64 off, const char *name, ut32 value) {
r_strbuf_appendf (sb, "0x%08" PFMT64x " %-29s 0x%08x\n", off, name, value);
}
static void hbc_header_print_bool(RStrBuf *sb, ut64 off, const char *name, bool value) {
r_strbuf_appendf (sb, "0x%08" PFMT64x " %-29s %s\n", off, name, value? "true": "false");
}
static char *header(RBinFile *bf, int mode) {
HBC *hbc = get_hbc (bf);
if (!hbc) {
return NULL;
}
HBCHeader hh;
if (hbc_get_header (hbc, &hh).code != RESULT_SUCCESS) {
return NULL;
}
char hash[41] = { 0 };
hbc_source_hash_hex (&hh, hash, sizeof (hash));
RStrBuf *sb = r_strbuf_new ("");
if (!sb) {
return NULL;
}
if (mode == R_MODE_EQUAL) {
r_strbuf_appendf (sb, "magic=0x%016" PFMT64x "\n", hh.magic);
r_strbuf_appendf (sb, "version=%u\n", hh.version);
r_strbuf_appendf (sb, "sourceHash=%s\n", hash);
r_strbuf_appendf (sb, "fileLength=%u\n", hh.fileLength);
r_strbuf_appendf (sb, "globalCodeIndex=%u\n", hh.globalCodeIndex);
r_strbuf_appendf (sb, "functionCount=%u\n", hh.functionCount);
r_strbuf_appendf (sb, "stringKindCount=%u\n", hh.stringKindCount);
r_strbuf_appendf (sb, "identifierCount=%u\n", hh.identifierCount);
r_strbuf_appendf (sb, "stringCount=%u\n", hh.stringCount);
r_strbuf_appendf (sb, "overflowStringCount=%u\n", hh.overflowStringCount);
r_strbuf_appendf (sb, "stringStorageSize=%u\n", hh.stringStorageSize);
r_strbuf_appendf (sb, "bigIntCount=%u\n", hh.bigIntCount);
r_strbuf_appendf (sb, "bigIntStorageSize=%u\n", hh.bigIntStorageSize);
r_strbuf_appendf (sb, "regExpCount=%u\n", hh.regExpCount);
r_strbuf_appendf (sb, "regExpStorageSize=%u\n", hh.regExpStorageSize);
r_strbuf_appendf (sb, "literalValueBufferSize=%u\n", hh.literalValueBufferSize);
r_strbuf_appendf (sb, "arrayBufferSize=%u\n", hh.arrayBufferSize);
r_strbuf_appendf (sb, "objKeyBufferSize=%u\n", hh.objKeyBufferSize);
r_strbuf_appendf (sb, "objValueBufferSize=%u\n", hh.objValueBufferSize);
r_strbuf_appendf (sb, "objShapeTableCount=%u\n", hh.objShapeTableCount);
r_strbuf_appendf (sb, "numStringSwitchImms=%u\n", hh.numStringSwitchImms);
r_strbuf_appendf (sb, "segmentID=%u\n", hh.segmentID);
r_strbuf_appendf (sb, "cjsModuleCount=%u\n", hh.cjsModuleCount);
r_strbuf_appendf (sb, "functionSourceCount=%u\n", hh.functionSourceCount);
r_strbuf_appendf (sb, "debugInfoOffset=%u\n", hh.debugInfoOffset);
r_strbuf_appendf (sb, "staticBuiltins=%s\n", hh.staticBuiltins? "true": "false");
r_strbuf_appendf (sb, "cjsModulesStaticallyResolved=%s\n", hh.cjsModulesStaticallyResolved? "true": "false");
r_strbuf_appendf (sb, "hasAsync=%s\n", hh.hasAsync? "true": "false");
return r_strbuf_drain (sb);
}
if (mode == R_MODE_JSON) {
PJ *pj = pj_new ();
if (!pj) {
r_strbuf_free (sb);
return NULL;
}
pj_o (pj);
pj_kn (pj, "magic", hh.magic);
pj_kn (pj, "version", hh.version);
pj_ks (pj, "sourceHash", hash);
pj_kn (pj, "fileLength", hh.fileLength);
pj_kn (pj, "globalCodeIndex", hh.globalCodeIndex);
pj_kn (pj, "functionCount", hh.functionCount);
pj_kn (pj, "stringKindCount", hh.stringKindCount);
pj_kn (pj, "identifierCount", hh.identifierCount);
pj_kn (pj, "stringCount", hh.stringCount);
pj_kn (pj, "overflowStringCount", hh.overflowStringCount);
pj_kn (pj, "stringStorageSize", hh.stringStorageSize);
pj_kn (pj, "bigIntCount", hh.bigIntCount);
pj_kn (pj, "bigIntStorageSize", hh.bigIntStorageSize);
pj_kn (pj, "regExpCount", hh.regExpCount);
pj_kn (pj, "regExpStorageSize", hh.regExpStorageSize);
pj_kn (pj, "literalValueBufferSize", hh.literalValueBufferSize);
pj_kn (pj, "arrayBufferSize", hh.arrayBufferSize);
pj_kn (pj, "objKeyBufferSize", hh.objKeyBufferSize);
pj_kn (pj, "objValueBufferSize", hh.objValueBufferSize);
pj_kn (pj, "objShapeTableCount", hh.objShapeTableCount);
pj_kn (pj, "numStringSwitchImms", hh.numStringSwitchImms);
pj_kn (pj, "segmentID", hh.segmentID);
pj_kn (pj, "cjsModuleCount", hh.cjsModuleCount);
pj_kn (pj, "functionSourceCount", hh.functionSourceCount);
pj_kn (pj, "debugInfoOffset", hh.debugInfoOffset);
pj_kb (pj, "staticBuiltins", hh.staticBuiltins);
pj_kb (pj, "cjsModulesStaticallyResolved", hh.cjsModulesStaticallyResolved);
pj_kb (pj, "hasAsync", hh.hasAsync);
pj_end (pj);
r_strbuf_free (sb);
return pj_drain (pj);
}
ut64 off = 0;
r_strbuf_append (sb, "Hermes bytecode header:\n");
r_strbuf_appendf (sb, "0x%08" PFMT64x " %-29s 0x%016" PFMT64x "\n", off, "magic", hh.magic);
off += 8;
hbc_header_print_u32 (sb, off, "version", hh.version);
off += 4;
r_strbuf_appendf (sb, "0x%08" PFMT64x " %-29s %s\n", off, "sourceHash", hash);
off += 20;
hbc_header_print_u32 (sb, off, "fileLength", hh.fileLength);
off += 4;
hbc_header_print_u32 (sb, off, "globalCodeIndex", hh.globalCodeIndex);
off += 4;
hbc_header_print_u32 (sb, off, "functionCount", hh.functionCount);
off += 4;
hbc_header_print_u32 (sb, off, "stringKindCount", hh.stringKindCount);
off += 4;
hbc_header_print_u32 (sb, off, "identifierCount", hh.identifierCount);
off += 4;
hbc_header_print_u32 (sb, off, "stringCount", hh.stringCount);
off += 4;
hbc_header_print_u32 (sb, off, "overflowStringCount", hh.overflowStringCount);
off += 4;
hbc_header_print_u32 (sb, off, "stringStorageSize", hh.stringStorageSize);
off += 4;
if (hh.version >= 87) {
hbc_header_print_u32 (sb, off, "bigIntCount", hh.bigIntCount);
off += 4;
hbc_header_print_u32 (sb, off, "bigIntStorageSize", hh.bigIntStorageSize);
off += 4;
}
hbc_header_print_u32 (sb, off, "regExpCount", hh.regExpCount);
off += 4;
hbc_header_print_u32 (sb, off, "regExpStorageSize", hh.regExpStorageSize);
off += 4;
hbc_header_print_u32 (sb, off, "literalValueBufferSize", hh.literalValueBufferSize);
off += 4;
hbc_header_print_u32 (sb, off, "objKeyBufferSize", hh.objKeyBufferSize);
off += 4;
if (hh.version >= 97) {
hbc_header_print_u32 (sb, off, "objShapeTableCount", hh.objShapeTableCount);
off += 4;
} else {
hbc_header_print_u32 (sb, off, "objValueBufferSize", hh.objValueBufferSize);
off += 4;
}
if (hh.version >= 99) {
hbc_header_print_u32 (sb, off, "numStringSwitchImms", hh.numStringSwitchImms);
off += 4;
}
hbc_header_print_u32 (sb, off, "segmentID", hh.segmentID);
off += 4;
hbc_header_print_u32 (sb, off, "cjsModuleCount", hh.cjsModuleCount);
off += 4;
if (hh.version >= 84) {
hbc_header_print_u32 (sb, off, "functionSourceCount", hh.functionSourceCount);
off += 4;
}
hbc_header_print_u32 (sb, off, "debugInfoOffset", hh.debugInfoOffset);
off += 4;
hbc_header_print_bool (sb, off, "staticBuiltins", hh.staticBuiltins);
hbc_header_print_bool (sb, off, "cjsModulesStaticallyResolved", hh.cjsModulesStaticallyResolved);
hbc_header_print_bool (sb, off, "hasAsync", hh.hasAsync);
return r_strbuf_drain (sb);
}
static void hbc_fields_append_u32(RList *ret, ut64 *off, const char *name, ut32 value) {
r_list_append (ret, r_bin_field_new (*off, *off, value, 4, name, NULL, "x", false));
*off += 4;
}
static RList *fields(RBinFile *bf) {
RList *ret = r_list_new ();
HBC *hbc = get_hbc (bf);
if (!ret || !hbc) {
return ret;
}
HBCHeader hh;
if (hbc_get_header (hbc, &hh).code != RESULT_SUCCESS) {
return ret;
}
char hash[41] = { 0 };
hbc_source_hash_hex (&hh, hash, sizeof (hash));
ut64 off = 0;
r_list_append (ret, r_bin_field_new (off, off, hh.magic, 8, "magic", NULL, "q", false));
off += 8;
hbc_fields_append_u32 (ret, &off, "version", hh.version);
r_list_append (ret, r_bin_field_new (off, off, 0, 20, "sourceHash", hash, "s", false));
off += 20;
hbc_fields_append_u32 (ret, &off, "fileLength", hh.fileLength);
hbc_fields_append_u32 (ret, &off, "globalCodeIndex", hh.globalCodeIndex);
hbc_fields_append_u32 (ret, &off, "functionCount", hh.functionCount);
hbc_fields_append_u32 (ret, &off, "stringKindCount", hh.stringKindCount);
hbc_fields_append_u32 (ret, &off, "identifierCount", hh.identifierCount);
hbc_fields_append_u32 (ret, &off, "stringCount", hh.stringCount);
hbc_fields_append_u32 (ret, &off, "overflowStringCount", hh.overflowStringCount);
hbc_fields_append_u32 (ret, &off, "stringStorageSize", hh.stringStorageSize);
if (hh.version >= 87) {
hbc_fields_append_u32 (ret, &off, "bigIntCount", hh.bigIntCount);
hbc_fields_append_u32 (ret, &off, "bigIntStorageSize", hh.bigIntStorageSize);
}
hbc_fields_append_u32 (ret, &off, "regExpCount", hh.regExpCount);
hbc_fields_append_u32 (ret, &off, "regExpStorageSize", hh.regExpStorageSize);
hbc_fields_append_u32 (ret, &off, "literalValueBufferSize", hh.literalValueBufferSize);
hbc_fields_append_u32 (ret, &off, "objKeyBufferSize", hh.objKeyBufferSize);
if (hh.version >= 97) {
hbc_fields_append_u32 (ret, &off, "objShapeTableCount", hh.objShapeTableCount);
} else {
hbc_fields_append_u32 (ret, &off, "objValueBufferSize", hh.objValueBufferSize);
}
if (hh.version >= 99) {
hbc_fields_append_u32 (ret, &off, "numStringSwitchImms", hh.numStringSwitchImms);
}
hbc_fields_append_u32 (ret, &off, "segmentID", hh.segmentID);
hbc_fields_append_u32 (ret, &off, "cjsModuleCount", hh.cjsModuleCount);
if (hh.version >= 84) {
hbc_fields_append_u32 (ret, &off, "functionSourceCount", hh.functionSourceCount);
}
hbc_fields_append_u32 (ret, &off, "debugInfoOffset", hh.debugInfoOffset);
ut32 flags = (hh.staticBuiltins? 1: 0) | (hh.cjsModulesStaticallyResolved? 2: 0) | (hh.hasAsync? 4: 0);
r_list_append (ret, r_bin_field_new (off, off, flags, 1, "flags", NULL, "b", false));
r_list_append (ret, r_bin_field_new (off, off, hh.staticBuiltins? 1: 0, 1, "staticBuiltins", NULL, "b", false));
r_list_append (ret, r_bin_field_new (off, off, hh.cjsModulesStaticallyResolved? 1: 0, 1, "cjsModulesStaticallyResolved", NULL, "b", false));
r_list_append (ret, r_bin_field_new (off, off, hh.hasAsync? 1: 0, 1, "hasAsync", NULL, "b", false));
return ret;
}
const RBinPlugin r_bin_plugin_r2hermes = {
.meta = {
.name = "hbc.bin",
@@ -456,7 +761,11 @@ const RBinPlugin r_bin_plugin_r2hermes = {
.baddr = &baddr,
.symbols = &symbols,
.imports = &imports,
.libs = &libs,
.lines = &lines,
.strings = &strings,
.header = &header,
.fields = &fields,
};
#ifndef R2_PLUGIN_INCORE
+144
View File
@@ -5,6 +5,7 @@
#include <r_core.h>
#include <hbc/hbc.h>
#include <hbc/literals.h>
#include <hbc/opcodes.h>
#include "utils.inc.c"
#define HBC_VADDR_BASE 0x10000000ULL
@@ -260,6 +261,55 @@ static void cmd_list_functions(HbcContext *ctx, RCore *core) {
}
}
/* List functions containing direct eval instructions */
static void cmd_list_eval_functions(HbcContext *ctx, RCore *core) {
Result res = hbc_load_current_binary (ctx, core);
if (res.code != RESULT_SUCCESS) {
R_LOG_ERROR ("%s", safe_errmsg (res.error_message));
return;
}
HBCHeader header;
res = hbc_get_header (ctx->hbc, &header);
if (res.code != RESULT_SUCCESS) {
R_LOG_ERROR ("reading header: %s", safe_errmsg (res.error_message));
return;
}
HBCISA isa = hbc_isa_getv (header.version);
if (!isa.instructions) {
R_LOG_ERROR ("No ISA for Hermes bytecode version %u", header.version);
return;
}
const u32 count = hbc_function_count (ctx->hbc);
for (u32 fid = 0; fid < count; fid++) {
HBCFunc info;
if (hbc_get_function_info (ctx->hbc, fid, &info).code != RESULT_SUCCESS) {
continue;
}
const u8 *code = NULL;
u32 size = 0;
if (hbc_get_function_bytecode (ctx->hbc, fid, &code, &size).code != RESULT_SUCCESS || !code) {
continue;
}
for (u32 pc = 0; pc < size; ) {
const u8 op = code[pc];
const Instruction *inst = (op < isa.count)? &isa.instructions[op]: NULL;
if (!inst || !inst->name || !inst->binary_size) {
break;
}
if (hbc_canonical_opcode_from_name (inst->name) == OP_DirectEval) {
r_cons_printf (core->cons,
"0x%08"PFMT64x" f=%u +0x%x func=0x%08"PFMT64x" name=%s\n",
(ut64)HBC_VADDR_BASE + info.offset + pc, fid, pc,
(ut64)HBC_VADDR_BASE + info.offset,
r_str_get_fail (info.name, "unknown"));
}
pc += inst->binary_size;
}
}
}
/* Show file information */
static void cmd_file_info(HbcContext *ctx, RCore *core) {
Result res = hbc_load_current_binary (ctx, core);
@@ -282,19 +332,76 @@ static void cmd_file_info(HbcContext *ctx, RCore *core) {
" File Length: %u bytes\n"
" Functions: %u\n"
" Strings: %u\n"
" String Kinds: %u\n"
" Overflow Strings: %u\n"
" String Storage Size: %u\n"
" Identifiers: %u\n"
" Global Code Index: %u\n"
" Function Sources: %u\n"
" Debug Info Offset: %u\n"
" BigInts: %u (%u bytes)\n"
" RegExps: %u (%u bytes)\n"
" Literal Value Buffer: %u bytes\n"
" Array Buffer: %u bytes\n"
" Object Key Buffer: %u bytes\n"
" Object Value Buffer: %u bytes\n"
" Object Shapes: %u\n"
" CJS Modules: %u\n"
" Static Builtins: %s\n"
" CJS Modules Statically Resolved: %s\n"
" Has Async: %s\n",
header.version,
header.fileLength,
header.functionCount,
header.stringCount,
header.stringKindCount,
header.overflowStringCount,
header.stringStorageSize,
header.identifierCount,
header.globalCodeIndex,
header.functionSourceCount,
header.debugInfoOffset,
header.bigIntCount,
header.bigIntStorageSize,
header.regExpCount,
header.regExpStorageSize,
header.literalValueBufferSize,
header.arrayBufferSize,
header.objKeyBufferSize,
header.objValueBufferSize,
header.objShapeTableCount,
header.cjsModuleCount,
header.staticBuiltins? "yes": "no",
header.cjsModulesStaticallyResolved? "yes": "no",
header.hasAsync? "yes": "no");
HBCDebugInfo di = { 0 };
if (hbc_get_debug_info (ctx->hbc, &di).code == RESULT_SUCCESS) {
r_cons_printf (cons,
" Debug Info Present: %s\n",
di.has_debug_info? "yes": "no");
if (di.has_debug_info) {
r_cons_printf (cons,
" Debug Files: %u (%u bytes)\n"
" Debug File Regions: %u\n"
" Functions With Debug Info: %u\n"
" Source Locations: %u bytes\n"
" Scope Descriptors: %u bytes\n"
" Textified Callees: %u bytes\n"
" Debug String Table: %u bytes\n"
" Debug Data Size: %u bytes\n",
di.filename_count,
di.filename_storage_size,
di.file_region_count,
di.functions_with_debug_info,
di.source_locations_size,
di.scope_desc_data_size,
di.textified_data_size,
di.string_table_size,
di.debug_data_size);
}
}
char hex[41] = { 0 };
r_hex_bin2str (header.sourceHash, 20, hex);
r_cons_printf (cons,
@@ -360,6 +467,35 @@ static void cmd_file_info(HbcContext *ctx, RCore *core) {
}
}
static void cmd_source_lines(HbcContext *ctx, RCore *core, const char *arg) {
Result res = hbc_load_current_binary (ctx, core);
if (res.code != RESULT_SUCCESS) {
R_LOG_ERROR ("%s", safe_errmsg (res.error_message));
return;
}
HBCSourceLineArray lines = { 0 };
res = hbc_get_source_lines (ctx->hbc, &lines);
if (res.code != RESULT_SUCCESS) {
R_LOG_ERROR ("source lines: %s", safe_errmsg (res.error_message));
hbc_free_source_lines (&lines);
return;
}
const char *as = r_str_trim_head_ro (arg);
const bool filter = *as != '\0';
const ut32 filter_id = filter? parse_function_id (as): 0;
for (u32 i = 0; i < lines.count; i++) {
const HBCSourceLine *sl = &lines.lines[i];
if (filter && sl->function_id != filter_id) {
continue;
}
r_cons_printf (core->cons, "0x%08x f=%u +0x%x %s:%u:%u stmt=%u\n",
sl->address, sl->function_id, sl->function_address,
sl->filename? sl->filename: "",
sl->line, sl->column, sl->statement);
}
hbc_free_source_lines (&lines);
}
/* Emit a JSON error envelope compatible with the {code, annotations, errors} format */
static void emit_json_error(RCore *core, const char *msg) {
PJ *pj = r_core_pj_new (core);
@@ -762,11 +898,13 @@ static void cmd_help(RCore *core) {
" pd:h - Decompile function at current offset (or all if not in function)\n"
" pd:hc [id] - Decompile function by id\n"
" pd:ha - Decompile all functions\n"
" pd:he - List functions containing eval instructions\n"
" pd:hf - List all functions\n"
" pd:hi - Show file information\n"
" pd:hj [id] - JSON output for function\n"
" pd:ho [id] - Decompile with offsets (addresses) per statement\n"
" pd:hoa - Decompile all with offsets\n"
" pd:hs [id] - List source-line information\n"
" pd:hL[?] - SLP literal cache: list/scan/reset (see pd:hL?)\n"
" pd:h? - Show this help\n"
"\nNote: r2 comments (CC command) are automatically inlined in decompiler output.\n");
@@ -804,6 +942,9 @@ static bool cmd_handler(RCorePluginSession *s, const char *input) {
case 'c': /* pd:hc [id] */
cmd_decompile_function_ex (ctx, core, arg + 1, false);
break;
case 'e': /* pd:he */
cmd_list_eval_functions (ctx, core);
break;
case 'f': /* pd:hf */
cmd_list_functions (ctx, core);
break;
@@ -837,6 +978,9 @@ static bool cmd_handler(RCorePluginSession *s, const char *input) {
case 'L': /* pd:hL* — literal cache / buffer-literal commands */
cmd_literals (ctx, core, arg + 1);
break;
case 's': /* pd:hs [id] */
cmd_source_lines (ctx, core, arg + 1);
break;
case '?': /* pd:h? */
cmd_help (core);
break;
+128 -1
View File
@@ -26,6 +26,8 @@ typedef struct {
typedef Result (*CommandHandler)(const CliContext *ctx, int argc, char **argv);
typedef struct { const char *name; const char *help; CommandHandler handler; } Command;
static void json_print_string(const char *s);
static const struct { const char *lname; char sname; size_t offset; } flag_table[] = {
{ "json", 'j', offsetof (CliFlags, json) },
{ "verbose", 'v', offsetof (CliFlags, verbose) },
@@ -402,6 +404,21 @@ static Result cmd_h(const CliContext *ctx, int argc, char **argv) {
fprintf (out, " Static Builtins: %s\n", hh.staticBuiltins? "Yes": "No");
fprintf (out, " CJS Modules Statically Resolved: %s\n", hh.cjsModulesStaticallyResolved? "Yes": "No");
fprintf (out, " Has Async: %s\n", hh.hasAsync? "Yes": "No");
HBCDebugInfo di = { 0 };
if (hbc_get_debug_info (hbc, &di).code == RESULT_SUCCESS) {
fprintf (out, " Debug Info:\n");
fprintf (out, " Present: %s\n", di.has_debug_info? "Yes": "No");
if (di.has_debug_info) {
fprintf (out, " Files: %u (%u bytes)\n", di.filename_count, di.filename_storage_size);
fprintf (out, " File Regions: %u\n", di.file_region_count);
fprintf (out, " Functions With Debug Info: %u\n", di.functions_with_debug_info);
fprintf (out, " Source Locations: %u bytes\n", di.source_locations_size);
fprintf (out, " Scope Descriptors: %u bytes\n", di.scope_desc_data_size);
fprintf (out, " Textified Callees: %u bytes\n", di.textified_data_size);
fprintf (out, " Debug String Table: %u bytes\n", di.string_table_size);
fprintf (out, " Debug Data Size: %u bytes\n", di.debug_data_size);
}
}
if (out != stdout) {
fclose (out);
}
@@ -429,6 +446,54 @@ static Result cmd_f(const CliContext *ctx, int argc, char **argv) {
return SUCCESS_RESULT ();
}
static Result cmd_sl(const CliContext *ctx, int argc, char **argv) {
if (argc < 1 || argc > 2) {
return errorf (RESULT_ERROR_INVALID_ARGUMENT, "Usage: sl <input> [function_id]");
}
HBC *hbc;
RETURN_IF_ERROR (open_hbc (argv[0], &hbc));
HBCSourceLineArray lines = { 0 };
Result r = hbc_get_source_lines (hbc, &lines);
if (r.code != RESULT_SUCCESS) {
hbc_close (hbc);
return r;
}
const bool filter = argc == 2;
const u32 filter_id = filter? (u32)strtoul (argv[1], NULL, 0): 0;
const bool json = ctx->flags.json;
bool first = true;
if (json) {
putchar ('[');
}
for (u32 i = 0; i < lines.count; i++) {
const HBCSourceLine *sl = &lines.lines[i];
if (filter && sl->function_id != filter_id) {
continue;
}
if (json) {
printf ("%s{\"function\":%u,\"address\":%u,\"offset\":%u,\"line\":%u,\"column\":%u,\"statement\":%u,\"file\":",
first? "": ",",
sl->function_id, sl->address, sl->function_address,
sl->line, sl->column, sl->statement);
json_print_string (sl->filename);
putchar ('}');
first = false;
} else {
printf ("0x%08x f=%u +0x%x %s:%u:%u stmt=%u\n",
sl->address, sl->function_id, sl->function_address,
sl->filename? sl->filename: "",
sl->line, sl->column, sl->statement);
}
}
if (json) {
puts ("]");
}
hbc_free_source_lines (&lines);
hbc_close (hbc);
return SUCCESS_RESULT ();
}
static Result cmd_cmp(const CliContext *ctx, int argc, char **argv) {
(void)ctx;
if (argc < 1 || argc > 2) {
@@ -814,6 +879,14 @@ static void json_print_string(const char *s) {
putchar ('"');
}
static void json_print_nullable_string(const char *s) {
if (s) {
json_print_string (s);
} else {
fputs ("null", stdout);
}
}
static const char *binding_type_name(HBCBindingType type) {
return type == HBC_BINDING_EXPORT? "export": "import";
}
@@ -862,6 +935,58 @@ static Result cmd_bind(const CliContext *ctx, int argc, char **argv) {
return SUCCESS_RESULT ();
}
static Result cmd_libs(const CliContext *ctx, int argc, char **argv) {
if (argc != 1) {
return errorf (RESULT_ERROR_INVALID_ARGUMENT, "Usage: libs <input>");
}
HBC *hbc;
RETURN_IF_ERROR (open_hbc (argv[0], &hbc));
HBCModules modules = { 0 };
Result r = hbc_list_modules (hbc, &modules);
if (r.code != RESULT_SUCCESS) {
hbc_close (hbc);
return r;
}
if (ctx->flags.json) {
putchar ('[');
for (u32 i = 0; i < modules.count; i++) {
HBCModule *m = &modules.modules[i];
printf ("%s{\"kind\":", i? ",": "");
json_print_string (m->kind);
printf (",\"name\":");
json_print_string (m->name);
printf (",\"path\":");
json_print_nullable_string (m->path);
printf (",\"version\":");
json_print_nullable_string (m->version);
printf (",\"function_id\":%u,\"offset\":%u,\"string_id\":%u,\"inferred\":%s}",
m->function_id, m->offset, m->string_id, m->inferred? "true": "false");
}
puts ("]");
} else {
printf ("libs: %u\n", modules.count);
for (u32 i = 0; i < modules.count; i++) {
HBCModule *m = &modules.modules[i];
printf ("%-8s sid=%-5u fn=%-5u off=0x%08x %-30s",
m->kind? m->kind: "", m->string_id, m->function_id,
m->offset, m->name? m->name: "");
if (m->version && *m->version) {
printf (" version=%s", m->version);
}
if (m->path && *m->path && (!m->name || strcmp (m->path, m->name))) {
printf (" path=%s", m->path);
}
if (m->inferred) {
printf (" inferred");
}
putchar ('\n');
}
}
hbc_free_modules (&modules);
hbc_close (hbc);
return SUCCESS_RESULT ();
}
static const Command commands[] = {
{ "d", "Disassemble a Hermes bytecode file", cmd_d },
{ "c", "Decompile a Hermes bytecode file", cmd_c },
@@ -872,6 +997,7 @@ static const Command commands[] = {
{ "v", "Validate file and show details", cmd_v },
{ "r", "Generate an r2 script with function flags",cmd_r },
{ "f", "Dump first N function headers", cmd_f },
{ "sl", "List source-line information", cmd_sl },
{ "cmp", "Compare first N funcs with parser.txt", cmd_cmp },
{ "cf", "Compare one function vs Python disasm", cmd_cf },
{ "s", "Print a string by index", cmd_s },
@@ -879,6 +1005,7 @@ static const Command commands[] = {
{ "sm", "Show string entry metadata", cmd_sm },
{ "lit", "SLP buffer literals: list, get, pool, xrefs", cmd_lit },
{ "bind", "List probable imports/exports/native bindings", cmd_bind },
{ "libs", "List probable bundled modules/libraries", cmd_libs },
};
#define COMMANDS_N (sizeof (commands) / sizeof (commands[0]))
@@ -888,7 +1015,7 @@ static void print_usage(const char *program_name) {
printf (" %-5s %s\n", commands[i].name, commands[i].help);
}
printf ("\nOptions (may appear anywhere after the command):\n");
printf (" --json, -j JSON output (lit list)\n");
printf (" --json, -j JSON output (lit list, bind, libs)\n");
printf (" --asmsyntax CPU-like asm syntax (dis)\n");
printf (" --verbose, -v Verbose output\n");
printf (" --bytecode, -b Include bytecode bytes\n");
+2 -2
View File
@@ -9,9 +9,9 @@ EXPECT=<<EOF
hbc
;-- entry0:
;-- global_0x000000b0:
0x100000b0 73000100 load_const_string r0, 0xa2 ; str.print_123__
0x100000b0 73000100 load_const_string r0, 0xa2 ; yes.js:1:1 ; str.print_123__
; 0x100000a2 ; "print(123);"
0x100000b4 5e000000 direct_eval r0, r0, 0
0x100000b4 5e000000 direct_eval r0, r0, 0 ; yes.js:1:5
EOF
RUN
+34
View File
@@ -18,6 +18,16 @@ EXPECT=<<EOF
EOF
RUN
NAME=pd:h help eval command
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
pd:h?~pd:he
EOF
EXPECT=<<EOF
pd:he - List functions containing eval instructions
EOF
RUN
NAME=pd:hi file info small
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
@@ -45,6 +55,7 @@ pd:hi~Functions
EOF
EXPECT=<<EOF
Functions: 1
Functions With Debug Info: 1
EOF
RUN
@@ -55,6 +66,7 @@ pd:hi~Strings
EOF
EXPECT=<<EOF
Strings: 2
Overflow Strings: 0
EOF
RUN
@@ -65,6 +77,7 @@ pd:hi~Functions
EOF
EXPECT=<<EOF
Functions: 5388
Functions With Debug Info: 0
EOF
RUN
@@ -75,6 +88,7 @@ pd:hi~Strings
EOF
EXPECT=<<EOF
Strings: 6170
Overflow Strings: 7
EOF
RUN
@@ -122,6 +136,26 @@ EXPECT=<<EOF
EOF
RUN
NAME=pd:he list eval sites
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
pd:he
EOF
EXPECT=<<EOF
0x100000b4 f=0 +0x4 func=0x100000b0 name=global
EOF
RUN
NAME=pd:he list eval sites large
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
pd:he
EOF
EXPECT=<<EOF
0x100d166b f=4953 +0x28 func=0x100d1643 name=onPress
EOF
RUN
NAME=pd:hc decompile function return
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
+6 -6
View File
@@ -7,9 +7,9 @@ EOF
EXPECT=<<EOF
;-- entry0:
;-- global_0x000000b0:
0x100000b0 73000100 load_const_string r0, 0xa2 ; str.print_123__
0x100000b0 73000100 load_const_string r0, 0xa2 ; yes.js:1:1 ; str.print_123__
; 0x100000a2 ; "print(123);"
0x100000b4 5e000000 direct_eval r0, r0, 0
0x100000b4 5e000000 direct_eval r0, r0, 0 ; yes.js:1:5
0x100000b8 5c00 ret r0
EOF
RUN
@@ -140,7 +140,7 @@ EOF
EXPECT=<<EOF
;-- entry0:
;-- global_0x000000b0:
0x100000b0 73000100 load_const_string r0, 0xa2 ; str.print_123__
0x100000b0 73000100 load_const_string r0, 0xa2 ; yes.js:1:1 ; str.print_123__
; 0x100000a2 ; "print(123);"
EOF
RUN
@@ -166,7 +166,7 @@ s 0x100000b4
pd 1
EOF
EXPECT=<<EOF
0x100000b4 5e000000 direct_eval r0, r0, 0
0x100000b4 5e000000 direct_eval r0, r0, 0 ; yes.js:1:5
EOF
RUN
@@ -214,9 +214,9 @@ EOF
EXPECT=<<EOF
;-- entry0:
;-- global_0x000000b0:
0x100000b0 73000100 load_const_string r0, 0xa2 ; str.print_123__
0x100000b0 73000100 load_const_string r0, 0xa2 ; yes.js:1:1 ; str.print_123__
; 0x100000a2 ; "print(123);"
0x100000b4 5e000000 direct_eval r0, r0, 0
0x100000b4 5e000000 direct_eval r0, r0, 0 ; yes.js:1:5
EOF
RUN
+83
View File
@@ -0,0 +1,83 @@
NAME=hbc ih header fields
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
ih~version
ih~functionCount
ih~debugInfoOffset
EOF
EXPECT=<<EOF
0x00000008 0x00000008 0x00000060 version
0x00000028 0x00000028 0x00000001 functionCount
0x00000068 0x00000068 0x000000c8 debugInfoOffset
EOF
RUN
NAME=hbc ihq header fields
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
ihq~version
ihq~functionCount
ihq~debugInfoOffset
EOF
EXPECT=<<EOF
0x00000008 0x00000060 version
0x00000028 0x00000001 functionCount
0x00000068 0x000000c8 debugInfoOffset
EOF
RUN
NAME=hbc ihj header fields
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
ihj~{}~version
ihj~{}~functionCount
ihj~{}~debugInfoOffset
EOF
EXPECT=<<EOF
"name": "version",
"name": "functionCount",
"name": "debugInfoOffset",
EOF
RUN
NAME=hbc iH ascii header
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
iH~version
iH~functionCount
iH~debugInfoOffset
EOF
EXPECT=<<EOF
0x00000008 version 0x00000060
0x00000028 functionCount 0x00000001
0x00000068 debugInfoOffset 0x000000c8
EOF
RUN
NAME=hbc iH key values
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
iH=~version
iH=~functionCount
iH=~debugInfoOffset
EOF
EXPECT=<<EOF
version=96
functionCount=1
debugInfoOffset=200
EOF
RUN
NAME=hbc iH json
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
iHj~version
iHj~functionCount
iHj~debugInfoOffset
EOF
EXPECT=<<EOF
{"magic":2240826417119764422,"version":96,"sourceHash":"a97c8302dab90bec7184a9183b22b43badd57a65","fileLength":292,"globalCodeIndex":0,"functionCount":1,"stringKindCount":1,"identifierCount":0,"stringCount":2,"overflowStringCount":0,"stringStorageSize":17,"bigIntCount":0,"bigIntStorageSize":0,"regExpCount":0,"regExpStorageSize":0,"literalValueBufferSize":0,"arrayBufferSize":0,"objKeyBufferSize":0,"objValueBufferSize":0,"objShapeTableCount":0,"numStringSwitchImms":0,"segmentID":0,"cjsModuleCount":0,"functionSourceCount":0,"debugInfoOffset":200,"staticBuiltins":false,"cjsModulesStaticallyResolved":false,"hasAsync":false}
{"magic":2240826417119764422,"version":96,"sourceHash":"a97c8302dab90bec7184a9183b22b43badd57a65","fileLength":292,"globalCodeIndex":0,"functionCount":1,"stringKindCount":1,"identifierCount":0,"stringCount":2,"overflowStringCount":0,"stringStorageSize":17,"bigIntCount":0,"bigIntStorageSize":0,"regExpCount":0,"regExpStorageSize":0,"literalValueBufferSize":0,"arrayBufferSize":0,"objKeyBufferSize":0,"objValueBufferSize":0,"objShapeTableCount":0,"numStringSwitchImms":0,"segmentID":0,"cjsModuleCount":0,"functionSourceCount":0,"debugInfoOffset":200,"staticBuiltins":false,"cjsModulesStaticallyResolved":false,"hasAsync":false}
{"magic":2240826417119764422,"version":96,"sourceHash":"a97c8302dab90bec7184a9183b22b43badd57a65","fileLength":292,"globalCodeIndex":0,"functionCount":1,"stringKindCount":1,"identifierCount":0,"stringCount":2,"overflowStringCount":0,"stringStorageSize":17,"bigIntCount":0,"bigIntStorageSize":0,"regExpCount":0,"regExpStorageSize":0,"literalValueBufferSize":0,"arrayBufferSize":0,"objKeyBufferSize":0,"objValueBufferSize":0,"objShapeTableCount":0,"numStringSwitchImms":0,"segmentID":0,"cjsModuleCount":0,"functionSourceCount":0,"debugInfoOffset":200,"staticBuiltins":false,"cjsModulesStaticallyResolved":false,"hasAsync":false}
EOF
RUN
+172
View File
@@ -0,0 +1,172 @@
NAME=pd:hs help entry
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
pd:h?~pd:hs
EOF
EXPECT=<<EOF
pd:hs [id] - List source-line information
EOF
RUN
NAME=pd:hs source lines small
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
pd:hs
EOF
EXPECT=<<EOF
0x000000b0 f=0 +0x0 yes.js:1:1 stmt=0
0x000000b4 f=0 +0x4 yes.js:1:5 stmt=0
EOF
RUN
NAME=pd:hs source lines filter
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
pd:hs 0
EOF
EXPECT=<<EOF
0x000000b0 f=0 +0x0 yes.js:1:1 stmt=0
0x000000b4 f=0 +0x4 yes.js:1:5 stmt=0
EOF
RUN
NAME=pd:hs source lines filter miss
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
pd:hs 99
EOF
EXPECT=<<EOF
EOF
RUN
NAME=pd:hs empty source lines
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
pd:hs
EOF
EXPECT=<<EOF
EOF
RUN
NAME=pd:hi debug info present
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
pd:hi~Debug Info Present
EOF
EXPECT=<<EOF
Debug Info Present: yes
EOF
RUN
NAME=pd:hi source locations size
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
pd:hi~Source Locations
EOF
EXPECT=<<EOF
Source Locations: 13 bytes
EOF
RUN
NAME=pd:hi debug files count
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
pd:hi~Debug Files
EOF
EXPECT=<<EOF
Debug Files: 1 (6 bytes)
EOF
RUN
NAME=iI linenum exposed (debug info)
FILE=bins/hbc/bespoke_eval.hbc
CMDS=<<EOF
iI~linenum
EOF
EXPECT=<<EOF
linenum true
EOF
RUN
NAME=iI linenum stripped
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
iI~linenum
EOF
EXPECT=<<EOF
linenum false
EOF
RUN
NAME=iI linenum honours file truth with bin.dbginfo=false
FILE=bins/hbc/bespoke_eval.hbc
ARGS=-e bin.dbginfo=false
CMDS=<<EOF
iI~linenum,stripped
EOF
EXPECT=<<EOF
linenum true
stripped false
EOF
RUN
NAME=iI stripped file still stripped with bin.dbginfo=false
FILE=bins/hbc/index.android.bundle
ARGS=-e bin.dbginfo=false
CMDS=<<EOF
iI~linenum,stripped
EOF
EXPECT=<<EOF
linenum false
stripped true
EOF
RUN
NAME=iI stripped file stripped with bin.dbginfo=true
FILE=bins/hbc/index.android.bundle
ARGS=-e bin.dbginfo=true
CMDS=<<EOF
iI~linenum,stripped
EOF
EXPECT=<<EOF
linenum false
stripped true
EOF
RUN
NAME=CL populated with bin.dbginfo=true
FILE=bins/hbc/bespoke_eval.hbc
ARGS=-e bin.dbginfo=true
CMDS=<<EOF
CL
EOF
EXPECT=<<EOF
file: yes.js
line: 1
colu: 1
addr: 0x100000b0
file: yes.js
line: 1
colu: 5
addr: 0x100000b4
EOF
RUN
NAME=CL empty with bin.dbginfo=false
FILE=bins/hbc/bespoke_eval.hbc
ARGS=-e bin.dbginfo=false
CMDS=<<EOF
CL
EOF
EXPECT=<<EOF
EOF
RUN
NAME=CL empty on stripped file
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
CL
EOF
EXPECT=<<EOF
EOF
RUN
+3 -2
View File
@@ -1,13 +1,14 @@
test_src = files(
# Add test files here as needed
'test_debug_info.c',
)
if test_src.length() > 0
executable(
run_tests = executable(
'run_tests',
test_src,
link_with: libhbc,
include_directories: include_dirs,
install: false,
)
test('debug-info', run_tests, args: [meson.project_source_root()])
endif
+81
View File
@@ -0,0 +1,81 @@
#include <hbc/hbc.h>
#include <stdio.h>
#include <string.h>
#define CHECK(x) do { \
if (!(x)) { \
fprintf (stderr, "check failed: %s:%d: %s\n", __FILE__, __LINE__, #x); \
return 1; \
} \
} while (0)
static int test_small_debug_info(const char *root) {
char path[512];
snprintf (path, sizeof (path), "%s/test/bins/hbc/bespoke_eval.hbc", root);
HBC *hbc = NULL;
CHECK (hbc_open (path, &hbc).code == RESULT_SUCCESS);
HBCDebugInfo di = { 0 };
CHECK (hbc_get_debug_info (hbc, &di).code == RESULT_SUCCESS);
CHECK (di.has_debug_info);
CHECK (di.filename_count == 1);
CHECK (di.filename_storage_size == 6);
CHECK (di.file_region_count == 1);
CHECK (di.functions_with_debug_info == 1);
CHECK (di.source_locations_size == 13);
CHECK (di.scope_desc_data_size == 3);
CHECK (di.textified_data_size == 1);
HBCSourceLineArray lines = { 0 };
CHECK (hbc_get_source_lines (hbc, &lines).code == RESULT_SUCCESS);
CHECK (hbc_has_source_lines (hbc));
CHECK (lines.count == 2);
CHECK (lines.lines[0].address == 0xb0);
CHECK (lines.lines[0].function_address == 0);
CHECK (lines.lines[0].line == 1);
CHECK (lines.lines[0].column == 1);
CHECK (!strcmp (lines.lines[0].filename, "yes.js"));
CHECK (lines.lines[1].address == 0xb4);
CHECK (lines.lines[1].function_address == 4);
CHECK (lines.lines[1].line == 1);
CHECK (lines.lines[1].column == 5);
hbc_free_source_lines (&lines);
hbc_close (hbc);
return 0;
}
static int test_empty_debug_info(const char *root) {
char path[512];
snprintf (path, sizeof (path), "%s/test/bins/hbc/index.android.bundle", root);
HBC *hbc = NULL;
CHECK (hbc_open (path, &hbc).code == RESULT_SUCCESS);
HBCDebugInfo di = { 0 };
CHECK (hbc_get_debug_info (hbc, &di).code == RESULT_SUCCESS);
CHECK (di.has_debug_info);
CHECK (di.filename_count == 0);
CHECK (di.file_region_count == 0);
CHECK (di.functions_with_debug_info == 0);
CHECK (di.source_locations_size == 0);
CHECK (!hbc_has_source_lines (hbc));
HBCSourceLineArray lines = { 0 };
CHECK (hbc_get_source_lines (hbc, &lines).code == RESULT_SUCCESS);
CHECK (lines.count == 0);
hbc_free_source_lines (&lines);
hbc_close (hbc);
return 0;
}
int main(int argc, char **argv) {
const char *root = argc > 1? argv[1]: ".";
if (test_small_debug_info (root) || test_empty_debug_info (root)) {
return 1;
}
return 0;
}