mirror of
https://github.com/radareorg/r2hermes.git
synced 2026-11-08 08:49:43 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9821c3f425 | ||
|
|
32d1965288 | ||
|
|
56900a5064 | ||
|
|
d6e807bc1c | ||
|
|
76ec5ad0f8 | ||
|
|
c08dcc3268 |
No files matched your search
+1
-2
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
+12
-1
@@ -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);
|
||||
|
||||
|
||||
+242
-5
@@ -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 ();
|
||||
|
||||
@@ -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
@@ -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 $@
|
||||
|
||||
|
||||
|
||||
+58
-2
@@ -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;
|
||||
}
|
||||
|
||||
@@ -392,6 +399,54 @@ static RList *imports(RBinFile *bf) {
|
||||
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);
|
||||
@@ -456,6 +511,7 @@ const RBinPlugin r_bin_plugin_r2hermes = {
|
||||
.baddr = &baddr,
|
||||
.symbols = &symbols,
|
||||
.imports = &imports,
|
||||
.lines = &lines,
|
||||
.strings = &strings,
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
@@ -872,6 +937,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 },
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+3
-2
@@ -1,13 +1,14 @@
|
||||
test_src = files(
|
||||
# Add test files here as needed
|
||||
'test_debug_info.c',
|
||||
)
|
||||
|
||||
if test_src.length() > 0
|
||||
executable(
|
||||
run_tests = executable(
|
||||
'run_tests',
|
||||
test_src,
|
||||
link_with: libhbc,
|
||||
include_directories: include_dirs,
|
||||
install: false,
|
||||
)
|
||||
test('debug-info', run_tests, args: [meson.project_source_root()])
|
||||
endif
|
||||
@@ -0,0 +1,81 @@
|
||||
#include <hbc/hbc.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define CHECK(x) do { \
|
||||
if (!(x)) { \
|
||||
fprintf (stderr, "check failed: %s:%d: %s\n", __FILE__, __LINE__, #x); \
|
||||
return 1; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static int test_small_debug_info(const char *root) {
|
||||
char path[512];
|
||||
snprintf (path, sizeof (path), "%s/test/bins/hbc/bespoke_eval.hbc", root);
|
||||
|
||||
HBC *hbc = NULL;
|
||||
CHECK (hbc_open (path, &hbc).code == RESULT_SUCCESS);
|
||||
|
||||
HBCDebugInfo di = { 0 };
|
||||
CHECK (hbc_get_debug_info (hbc, &di).code == RESULT_SUCCESS);
|
||||
CHECK (di.has_debug_info);
|
||||
CHECK (di.filename_count == 1);
|
||||
CHECK (di.filename_storage_size == 6);
|
||||
CHECK (di.file_region_count == 1);
|
||||
CHECK (di.functions_with_debug_info == 1);
|
||||
CHECK (di.source_locations_size == 13);
|
||||
CHECK (di.scope_desc_data_size == 3);
|
||||
CHECK (di.textified_data_size == 1);
|
||||
|
||||
HBCSourceLineArray lines = { 0 };
|
||||
CHECK (hbc_get_source_lines (hbc, &lines).code == RESULT_SUCCESS);
|
||||
CHECK (hbc_has_source_lines (hbc));
|
||||
CHECK (lines.count == 2);
|
||||
CHECK (lines.lines[0].address == 0xb0);
|
||||
CHECK (lines.lines[0].function_address == 0);
|
||||
CHECK (lines.lines[0].line == 1);
|
||||
CHECK (lines.lines[0].column == 1);
|
||||
CHECK (!strcmp (lines.lines[0].filename, "yes.js"));
|
||||
CHECK (lines.lines[1].address == 0xb4);
|
||||
CHECK (lines.lines[1].function_address == 4);
|
||||
CHECK (lines.lines[1].line == 1);
|
||||
CHECK (lines.lines[1].column == 5);
|
||||
|
||||
hbc_free_source_lines (&lines);
|
||||
hbc_close (hbc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int test_empty_debug_info(const char *root) {
|
||||
char path[512];
|
||||
snprintf (path, sizeof (path), "%s/test/bins/hbc/index.android.bundle", root);
|
||||
|
||||
HBC *hbc = NULL;
|
||||
CHECK (hbc_open (path, &hbc).code == RESULT_SUCCESS);
|
||||
|
||||
HBCDebugInfo di = { 0 };
|
||||
CHECK (hbc_get_debug_info (hbc, &di).code == RESULT_SUCCESS);
|
||||
CHECK (di.has_debug_info);
|
||||
CHECK (di.filename_count == 0);
|
||||
CHECK (di.file_region_count == 0);
|
||||
CHECK (di.functions_with_debug_info == 0);
|
||||
CHECK (di.source_locations_size == 0);
|
||||
CHECK (!hbc_has_source_lines (hbc));
|
||||
|
||||
HBCSourceLineArray lines = { 0 };
|
||||
CHECK (hbc_get_source_lines (hbc, &lines).code == RESULT_SUCCESS);
|
||||
CHECK (lines.count == 0);
|
||||
|
||||
hbc_free_source_lines (&lines);
|
||||
hbc_close (hbc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const char *root = argc > 1? argv[1]: ".";
|
||||
if (test_small_debug_info (root) || test_empty_debug_info (root)) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
Reference in new issue
Block a user