Compare commits

...
7 Commits
Author SHA1 Message Date
pancake 9821c3f425 fi 2026-04-24 01:05:01 +02:00
pancake 32d1965288 f 2026-04-24 00:47:51 +02:00
pancake 56900a5064 Update tests 2026-04-24 00:45:38 +02:00
pancake d6e807bc1c sclean 2026-04-24 00:38:53 +02:00
pancake 76ec5ad0f8 test 2026-04-24 00:27:20 +02:00
pancake c08dcc3268 SourceLine exposure and strip details in rbininfo 2026-04-24 00:26:26 +02:00
pancake 56c943a4ca Add support for imports and exports via opcode scan 2026-04-24 00:01:56 +02:00
15 changed files with 1092 additions and 39 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
+11 -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,14 @@ TEST_DIR = tests
TEST_SRC = $(wildcard $(TEST_DIR)/*.c)
TEST_BIN = $(BIN_DIR)/run_tests
$(TEST_BIN): $(STATIC_LIB) $(TEST_SRC) | $(shell mkdir -p $(BIN_DIR))
$(CC) $(CFLAGS) $(INCLUDES) -o $@ $(TEST_SRC) -L$(BUILD_DIR) -lhbc
test: $(BIN_FILE) $(TEST_BIN)
$(TEST_BIN)
$(BIN_FILE) sl test/bins/hbc/bespoke_eval.hbc | grep -q '0x000000b4 f=0 +0x4 yes.js:1:5 stmt=0'
$(BIN_FILE) h test/bins/hbc/bespoke_eval.hbc | grep -q 'Source Locations: 13 bytes'
$(BIN_FILE) h test/bins/hbc/index.android.bundle | grep -q 'Source Locations: 0 bytes'
r2:
$(MAKE) clean
+64
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.
*/
@@ -288,11 +324,39 @@ typedef struct {
u32 count;
} HBCInsns;
typedef enum {
HBC_BINDING_IMPORT = 0,
HBC_BINDING_EXPORT = 1
} HBCBindingType;
typedef struct {
HBCBindingType type;
const char *kind; /* js, native, cjs, global, module */
char *name;
char *module;
u32 function_id;
u32 offset;
u32 string_id;
} HBCBinding;
typedef struct {
HBCBinding *bindings;
u32 count;
} HBCBindings;
/**
* Free instruction array.
*/
void hbc_free_insns(HBCInsn *insns, u32 count);
/**
* Scan bytecode/string references for probable JS/native import/export
* boundaries. Hermes has no native import table, so these entries are derived
* from bytecode operands and CJS metadata.
*/
Result hbc_scan_bindings(HBC *hbc, HBCBindings *out);
void hbc_free_bindings(HBCBindings *bindings);
/**
* Encoding buffer and functions
*/
+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);
+473 -5
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 ();
@@ -594,6 +831,237 @@ Result hbc_dec(const HBCDecodeCtx *ctx, HBCInsnInfo *out) {
return SUCCESS_RESULT ();
}
typedef struct {
const char *name; /* exact match, else match prefix */
const char *prefix;
const char *kind;
const char *module; /* NULL for exports */
HBCBindingType type;
} BindingHint;
static const BindingHint g_hints[] = {
{ "NativeModules", NULL, "native", "react-native", HBC_BINDING_IMPORT },
{ "TurboModuleRegistry", NULL, "native", "react-native", HBC_BINDING_IMPORT },
{ "__turboModuleProxy", NULL, "native", "react-native", HBC_BINDING_IMPORT },
{ "requireNativeComponent", NULL, "native", "react-native", HBC_BINDING_IMPORT },
{ "codegenNativeComponent", NULL, "native", "react-native", HBC_BINDING_IMPORT },
{ "NativeEventEmitter", NULL, "native", "react-native", HBC_BINDING_IMPORT },
{ "RCTDeviceEventEmitter", NULL, "native", "react-native", HBC_BINDING_IMPORT },
{ "HermesInternal", NULL, "native", "react-native", HBC_BINDING_IMPORT },
{ "require", NULL, "js", "react-native", HBC_BINDING_IMPORT },
{ "metroRequire", NULL, "js", "react-native", HBC_BINDING_IMPORT },
{ "__r", NULL, "js", "react-native", HBC_BINDING_IMPORT },
{ "importScripts", NULL, "js", "react-native", HBC_BINDING_IMPORT },
{ "global", NULL, "global", "react-native", HBC_BINDING_IMPORT },
{ "globalThis", NULL, "global", "react-native", HBC_BINDING_IMPORT },
{ "exports", NULL, "module", NULL, HBC_BINDING_EXPORT },
{ "module", NULL, "module", NULL, HBC_BINDING_EXPORT },
{ "__esModule", NULL, "module", NULL, HBC_BINDING_EXPORT },
{ "default", NULL, "module", NULL, HBC_BINDING_EXPORT },
{ NULL, "RCT", "native", "react-native", HBC_BINDING_IMPORT },
{ NULL, "Native", "native", "react-native", HBC_BINDING_IMPORT },
};
#define HINTS_N (sizeof (g_hints) / sizeof (g_hints[0]))
static bool str_eq(const char *a, const char *b) {
if (a == b) {
return true;
}
return a && b && !strcmp (a, b);
}
static bool is_export_marker(const char *s) {
return !strcmp (s, "exports") || !strcmp (s, "module");
}
static bool is_export_store(const char *name) {
return strstr (name, "put") && strstr (name, "by_id");
}
static Result binding_add(HBCBindings *out, HBCBindingType type, const char *kind,
const char *name, const char *module, u32 function_id, u32 offset, u32 string_id) {
for (u32 i = 0; i < out->count; i++) {
HBCBinding *b = &out->bindings[i];
if (b->type == type && !strcmp (b->kind, kind)
&& !strcmp (b->name, name) && str_eq (b->module, module)) {
return SUCCESS_RESULT ();
}
}
HBCBinding *nb = realloc (out->bindings, (out->count + 1) * sizeof (HBCBinding));
if (!nb) {
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "OOM");
}
out->bindings = nb;
HBCBinding *b = &nb[out->count++];
*b = (HBCBinding){
.type = type, .kind = kind, .name = strdup (name),
.module = module? strdup (module): NULL,
.function_id = function_id, .offset = offset, .string_id = string_id,
};
if (!b->name || (module && !b->module)) {
return ERROR_RESULT (RESULT_ERROR_MEMORY_ALLOCATION, "OOM");
}
return SUCCESS_RESULT ();
}
static size_t decode_operands(const Instruction *inst, const u8 *bytes, size_t len,
u32 values[6]) {
size_t pos = 1;
memset (values, 0, 6 * sizeof (u32));
for (int i = 0; i < 6 && inst->operands[i].operand_type != OPERAND_TYPE_NONE; i++) {
OperandType t = inst->operands[i].operand_type;
size_t need;
switch (t) {
case OPERAND_TYPE_REG8:
case OPERAND_TYPE_UINT8:
need = 1;
if (pos + need > len) { return 0; }
values[i] = bytes[pos];
break;
case OPERAND_TYPE_ADDR8:
need = 1;
if (pos + need > len) { return 0; }
values[i] = (u32)(i32)(i8)bytes[pos];
break;
case OPERAND_TYPE_UINT16:
need = 2;
if (pos + need > len) { return 0; }
values[i] = (u32)bytes[pos] | ((u32)bytes[pos + 1] << 8);
break;
case OPERAND_TYPE_REG32:
case OPERAND_TYPE_UINT32:
case OPERAND_TYPE_IMM32:
case OPERAND_TYPE_ADDR32:
need = 4;
if (pos + need > len) { return 0; }
values[i] = (u32)bytes[pos] | ((u32)bytes[pos + 1] << 8)
| ((u32)bytes[pos + 2] << 16) | ((u32)bytes[pos + 3] << 24);
break;
case OPERAND_TYPE_DOUBLE:
need = 8;
if (pos + need > len) { return 0; }
break;
default:
return 0;
}
pos += need;
}
return pos;
}
static Result scan_hint_matches(HBCBindings *out, const char *s, u32 fid, u32 off, u32 sid) {
for (size_t h = 0; h < HINTS_N; h++) {
const BindingHint *hint = &g_hints[h];
bool match = hint->name? !strcmp (s, hint->name):
!strncmp (s, hint->prefix, strlen (hint->prefix));
if (!match) {
continue;
}
RETURN_IF_ERROR (binding_add (out, hint->type, hint->kind, s,
hint->module, fid, off, sid));
}
return SUCCESS_RESULT ();
}
static bool function_has_export_marker(HBCReader *r, const u8 *code, u32 size, HBCISA isa) {
for (u32 pc = 0; pc < size; ) {
u8 op = code[pc];
const Instruction *inst = (op < isa.count)? &isa.instructions[op]: NULL;
if (!inst || !inst->name || !inst->binary_size) {
break;
}
u32 values[6];
if (!decode_operands (inst, code + pc, size - pc, values)) {
break;
}
for (int i = 0; i < 6 && inst->operands[i].operand_type != OPERAND_TYPE_NONE; i++) {
if (inst->operands[i].operand_meaning == OPERAND_MEANING_STRING_ID) {
u32 sid = values[i];
const char *s = (sid < r->header.stringCount && r->strings)? r->strings[sid]: NULL;
if (s && is_export_marker (s)) {
return true;
}
}
}
pc += inst->binary_size;
}
return false;
}
Result hbc_scan_bindings(HBC *hbc, HBCBindings *out) {
if (!hbc || !out) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "invalid args");
}
memset (out, 0, sizeof (*out));
HBCReader *r = &hbc->reader;
HBCISA isa = hbc_isa_getv (r->header.version);
if (!isa.instructions) {
return ERROR_RESULT (RESULT_ERROR_INVALID_ARGUMENT, "no isa for version");
}
if (r->header.cjsModuleCount > 0 && r->header.version >= 77 && r->cjs_modules) {
for (u32 i = 0; i < r->header.cjsModuleCount; i++) {
u32 sid = r->cjs_modules[i].symbol_id;
const char *name = (sid < r->header.stringCount && r->strings)? r->strings[sid]: NULL;
if (name && *name) {
RETURN_IF_ERROR (binding_add (out, HBC_BINDING_IMPORT,
"cjs", name, "commonjs", 0, r->cjs_modules[i].offset, sid));
}
}
}
for (u32 fid = 0; fid < r->header.functionCount; fid++) {
const u8 *code = NULL;
u32 size = 0;
if (hbc_get_function_bytecode (hbc, fid, &code, &size).code != RESULT_SUCCESS
|| !code || !size) {
continue;
}
HBCFunc fi = { 0 };
(void)hbc_get_function_info (hbc, fid, &fi);
bool has_export_marker = function_has_export_marker (r, code, size, isa);
for (u32 pc = 0; pc < size; ) {
u8 op = code[pc];
const Instruction *inst = (op < isa.count)? &isa.instructions[op]: NULL;
if (!inst || !inst->name || !inst->binary_size) {
break;
}
u32 values[6];
if (!decode_operands (inst, code + pc, size - pc, values)) {
break;
}
for (int i = 0; i < 6 && inst->operands[i].operand_type != OPERAND_TYPE_NONE; i++) {
if (inst->operands[i].operand_meaning != OPERAND_MEANING_STRING_ID) {
continue;
}
u32 sid = values[i];
const char *s = (sid < r->header.stringCount && r->strings)? r->strings[sid]: NULL;
if (!s || !*s) {
continue;
}
RETURN_IF_ERROR (scan_hint_matches (out, s, fid, fi.offset + pc, sid));
if (has_export_marker && is_export_store (inst->name) && !is_export_marker (s)) {
RETURN_IF_ERROR (binding_add (out, HBC_BINDING_EXPORT,
"module", s, NULL, fid, fi.offset + pc, sid));
}
}
pc += inst->binary_size;
}
}
return SUCCESS_RESULT ();
}
void hbc_free_bindings(HBCBindings *bindings) {
if (!bindings || !bindings->bindings) {
return;
}
for (u32 i = 0; i < bindings->count; i++) {
free (bindings->bindings[i].name);
free (bindings->bindings[i].module);
}
free (bindings->bindings);
bindings->bindings = NULL;
bindings->count = 0;
}
/* Encoding functions */
Result hbc_enc(
+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 $@
+118 -5
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;
}
@@ -248,6 +255,33 @@ static ut64 baddr(RBinFile *bf R_UNUSED) {
return HBC_VADDR_BASE;
}
static void append_binding_symbols(RList *symbols, HBC *hbc) {
HBCBindings bindings = { 0 };
if (hbc_scan_bindings (hbc, &bindings).code != RESULT_SUCCESS) {
return;
}
for (u32 i = 0; i < bindings.count; i++) {
HBCBinding *b = &bindings.bindings[i];
if (b->type != HBC_BINDING_EXPORT) {
continue;
}
RBinSymbol *sym = R_NEW0 (RBinSymbol);
char *nm = r_str_newf ("hbc.export.%s.%s", b->kind? b->kind: "js", b->name);
sym->name = r_bin_name_new (nm);
r_bin_name_filtered (sym->name, nm);
sym->paddr = b->offset;
sym->vaddr = HBC_VADDR_BASE + b->offset;
sym->size = 1;
sym->ordinal = b->string_id;
sym->type = R_BIN_TYPE_OBJECT_STR;
sym->bind = R_BIN_BIND_GLOBAL_STR;
sym->bits = 32;
r_list_append (symbols, sym);
free (nm);
}
hbc_free_bindings (&bindings);
}
static RList *symbols(RBinFile *bf) {
RList *symbols = r_list_newf ((RListFree)r_bin_symbol_free);
HBC *hbc = get_hbc (bf);
@@ -316,9 +350,6 @@ static RList *symbols(RBinFile *bf) {
}
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);
@@ -333,9 +364,89 @@ static RList *symbols(RBinFile *bf) {
free (groups);
}
append_binding_symbols (symbols, hbc);
return symbols;
}
static RList *imports(RBinFile *bf) {
RList *imports = r_list_newf ((RListFree)r_bin_import_free);
HBC *hbc = get_hbc (bf);
if (!imports || !hbc) {
return imports;
}
HBCBindings bindings = { 0 };
if (hbc_scan_bindings (hbc, &bindings).code != RESULT_SUCCESS) {
return imports;
}
for (u32 i = 0; i < bindings.count; i++) {
HBCBinding *b = &bindings.bindings[i];
if (b->type != HBC_BINDING_IMPORT) {
continue;
}
RBinImport *imp = R_NEW0 (RBinImport);
char *nm = r_str_newf ("%s.%s", b->kind? b->kind: "js", b->name);
imp->name = r_bin_name_new (nm);
r_bin_name_filtered (imp->name, nm);
imp->libname = strdup (b->module? b->module: "hermes");
imp->bind = "NONE";
imp->type = "FUNC";
imp->ordinal = b->string_id;
imp->is_imported = true;
r_list_append (imports, imp);
free (nm);
}
hbc_free_bindings (&bindings);
return imports;
}
static R_UNOWNED RList *lines(RBinFile *bf) {
HBC *hbc = get_hbc (bf);
if (!hbc) {
return NULL;
}
/* Respect bin.dbginfo=false — return early so the lazy debug-info parse
* (triggered by hbc_get_source_lines) never runs for users who opted out. */
if (bf->rbin && !bf->rbin->want_dbginfo) {
return NULL;
}
HBCSourceLineArray lines = { 0 };
RList *ret = NULL;
if (hbc_get_source_lines (hbc, &lines).code != RESULT_SUCCESS || !lines.count) {
goto done;
}
ret = r_list_newf (free);
if (!ret) {
goto done;
}
const bool has_al = bf->addrline.al_add && bf->addrline.al_get;
for (u32 i = 0; i < lines.count; i++) {
HBCSourceLine *sl = &lines.lines[i];
ut64 addr = HBC_VADDR_BASE + sl->address;
const char *file = sl->filename? sl->filename: "";
if (has_al) {
bf->addrline.al_add (&bf->addrline, addr, file, NULL, sl->line, sl->column);
}
RBinAddrline *row = R_NEW0 (RBinAddrline);
if (!row) {
continue;
}
const RBinAddrline *stored = has_al? bf->addrline.al_get (&bf->addrline, addr): NULL;
if (stored) {
*row = *stored;
} else {
row->addr = addr;
row->file = UT32_MAX;
row->path = UT32_MAX;
row->line = sl->line;
row->column = sl->column;
}
r_list_append (ret, row);
}
done:
hbc_free_source_lines (&lines);
return ret;
}
static RList *strings(RBinFile *bf) {
RList *ret = r_list_newf ((RListFree)r_bin_string_free);
HBC *hbc = get_hbc (bf);
@@ -399,6 +510,8 @@ const RBinPlugin r_bin_plugin_r2hermes = {
.sections = &sections,
.baddr = &baddr,
.symbols = &symbols,
.imports = &imports,
.lines = &lines,
.strings = &strings,
};
+90
View File
@@ -282,19 +282,76 @@ static void cmd_file_info(HbcContext *ctx, RCore *core) {
" File Length: %u bytes\n"
" Functions: %u\n"
" Strings: %u\n"
" String Kinds: %u\n"
" Overflow Strings: %u\n"
" String Storage Size: %u\n"
" Identifiers: %u\n"
" Global Code Index: %u\n"
" Function Sources: %u\n"
" Debug Info Offset: %u\n"
" BigInts: %u (%u bytes)\n"
" RegExps: %u (%u bytes)\n"
" Literal Value Buffer: %u bytes\n"
" Array Buffer: %u bytes\n"
" Object Key Buffer: %u bytes\n"
" Object Value Buffer: %u bytes\n"
" Object Shapes: %u\n"
" CJS Modules: %u\n"
" Static Builtins: %s\n"
" CJS Modules Statically Resolved: %s\n"
" Has Async: %s\n",
header.version,
header.fileLength,
header.functionCount,
header.stringCount,
header.stringKindCount,
header.overflowStringCount,
header.stringStorageSize,
header.identifierCount,
header.globalCodeIndex,
header.functionSourceCount,
header.debugInfoOffset,
header.bigIntCount,
header.bigIntStorageSize,
header.regExpCount,
header.regExpStorageSize,
header.literalValueBufferSize,
header.arrayBufferSize,
header.objKeyBufferSize,
header.objValueBufferSize,
header.objShapeTableCount,
header.cjsModuleCount,
header.staticBuiltins? "yes": "no",
header.cjsModulesStaticallyResolved? "yes": "no",
header.hasAsync? "yes": "no");
HBCDebugInfo di = { 0 };
if (hbc_get_debug_info (ctx->hbc, &di).code == RESULT_SUCCESS) {
r_cons_printf (cons,
" Debug Info Present: %s\n",
di.has_debug_info? "yes": "no");
if (di.has_debug_info) {
r_cons_printf (cons,
" Debug Files: %u (%u bytes)\n"
" Debug File Regions: %u\n"
" Functions With Debug Info: %u\n"
" Source Locations: %u bytes\n"
" Scope Descriptors: %u bytes\n"
" Textified Callees: %u bytes\n"
" Debug String Table: %u bytes\n"
" Debug Data Size: %u bytes\n",
di.filename_count,
di.filename_storage_size,
di.file_region_count,
di.functions_with_debug_info,
di.source_locations_size,
di.scope_desc_data_size,
di.textified_data_size,
di.string_table_size,
di.debug_data_size);
}
}
char hex[41] = { 0 };
r_hex_bin2str (header.sourceHash, 20, hex);
r_cons_printf (cons,
@@ -360,6 +417,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);
@@ -767,6 +853,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: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");
@@ -837,6 +924,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;
+134
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) {
@@ -795,6 +860,73 @@ static Result cmd_lit(const CliContext *ctx, int argc, char **argv) {
return errorf (RESULT_ERROR_INVALID_ARGUMENT, "unknown lit subcommand: %s", sub);
}
static void json_print_string(const char *s) {
putchar ('"');
for (const char *p = s? s: ""; *p; p++) {
if (*p == '"' || *p == '\\') {
putchar ('\\');
}
if (*p == '\n') {
fputs ("\\n", stdout);
} else if (*p == '\r') {
fputs ("\\r", stdout);
} else if (*p == '\t') {
fputs ("\\t", stdout);
} else {
putchar (*p);
}
}
putchar ('"');
}
static const char *binding_type_name(HBCBindingType type) {
return type == HBC_BINDING_EXPORT? "export": "import";
}
static Result cmd_bind(const CliContext *ctx, int argc, char **argv) {
if (argc != 1) {
return errorf (RESULT_ERROR_INVALID_ARGUMENT, "Usage: bind <input>");
}
HBC *hbc;
RETURN_IF_ERROR (open_hbc (argv[0], &hbc));
HBCBindings bindings = { 0 };
Result r = hbc_scan_bindings (hbc, &bindings);
if (r.code != RESULT_SUCCESS) {
hbc_close (hbc);
return r;
}
if (ctx->flags.json) {
putchar ('[');
for (u32 i = 0; i < bindings.count; i++) {
HBCBinding *b = &bindings.bindings[i];
printf ("%s{\"type\":", i? ",": "");
json_print_string (binding_type_name (b->type));
printf (",\"kind\":");
json_print_string (b->kind);
printf (",\"name\":");
json_print_string (b->name);
printf (",\"module\":");
json_print_string (b->module);
printf (",\"function_id\":%u,\"offset\":%u,\"string_id\":%u}",
b->function_id, b->offset, b->string_id);
}
puts ("]");
} else {
printf ("bindings: %u\n", bindings.count);
for (u32 i = 0; i < bindings.count; i++) {
HBCBinding *b = &bindings.bindings[i];
printf ("%-6s %-7s sid=%-5u fn=%-5u off=0x%08x %s%s%s\n",
binding_type_name (b->type), b->kind? b->kind: "",
b->string_id, b->function_id, b->offset,
b->module? b->module: "", b->module? ":": "",
b->name? b->name: "");
}
}
hbc_free_bindings (&bindings);
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 },
@@ -805,12 +937,14 @@ 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 },
{ "fs", "Find strings by substring", cmd_fs },
{ "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 },
};
#define COMMANDS_N (sizeof (commands) / sizeof (commands[0]))
+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
+4
View File
@@ -45,6 +45,7 @@ pd:hi~Functions
EOF
EXPECT=<<EOF
Functions: 1
Functions With Debug Info: 1
EOF
RUN
@@ -55,6 +56,7 @@ pd:hi~Strings
EOF
EXPECT=<<EOF
Strings: 2
Overflow Strings: 0
EOF
RUN
@@ -65,6 +67,7 @@ pd:hi~Functions
EOF
EXPECT=<<EOF
Functions: 5388
Functions With Debug Info: 0
EOF
RUN
@@ -75,6 +78,7 @@ pd:hi~Strings
EOF
EXPECT=<<EOF
Strings: 6170
Overflow Strings: 7
EOF
RUN
+43 -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
@@ -284,6 +284,43 @@ EXPECT=<<EOF
EOF
RUN
NAME=hbc imports js require
FILE=bins/hbc/index.android.bundle
ARGS=-e asm.sub.varmin=16
CMDS=<<EOF
ii~js.__r
ii~?
EOF
EXPECT=<<EOF
3048 0x00000000 NONE FUNC react-native js.__r
58
EOF
RUN
NAME=hbc imports native module
FILE=bins/hbc/index.android.bundle
ARGS=-e asm.sub.varmin=16
CMDS=<<EOF
ii~native.NativeModules
EOF
EXPECT=<<EOF
3894 0x00000000 NONE FUNC react-native native.NativeModules
EOF
RUN
NAME=hbc exports module symbol
FILE=bins/hbc/index.android.bundle
ARGS=-e asm.sub.varmin=16
CMDS=<<EOF
iE~hbc.export.module.RN$AppRegistry
iE~?
EOF
EXPECT=<<EOF
3630 0x00071961 0x10071961 GLOBAL OBJ 1 hbc.export.module.RN$AppRegistry
451
EOF
RUN
NAME=hbc get_global_object instruction (va)
FILE=bins/hbc/index.android.bundle
CMDS=<<EOF
+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;
}