add function disassembler

This commit is contained in:
2025-06-23 21:28:15 -05:00
parent 42087910ab
commit dca3ede464
3 changed files with 414 additions and 4 deletions

View File

@@ -74,6 +74,14 @@ JSC_CCALL(js_compile_unblob,
return JS_ReadObject(js, data, size, JS_READ_OBJ_BYTECODE);
)
JSC_CCALL(js_disassemble,
return js_debugger_fn_bytecode(js, argv[0]);
)
JSC_CCALL(js_fn_info,
return js_debugger_fn_info(js, argv[0]);
)
static const JSCFunctionListEntry js_js_funcs[] = {
MIST_FUNC_DEF(os, calc_mem, 0),
MIST_FUNC_DEF(os, mem_limit, 1),
@@ -85,6 +93,8 @@ static const JSCFunctionListEntry js_js_funcs[] = {
MIST_FUNC_DEF(js, eval_compile, 1),
MIST_FUNC_DEF(js, compile_blob, 1),
MIST_FUNC_DEF(js, compile_unblob, 1),
MIST_FUNC_DEF(js, disassemble, 1),
MIST_FUNC_DEF(js, fn_info, 1),
};
JSValue js_js_use(JSContext *js) {

View File

@@ -90,7 +90,7 @@
32: dump line number table
64: dump compute_stack_size
*/
//#define DUMP_BYTECODE (1)
#define DUMP_BYTECODE (1)
/* dump the occurence of the automatic GC */
//#define DUMP_GC
/* dump objects freed by the garbage collector */
@@ -37002,14 +37002,413 @@ JSValue js_debugger_fn_info(JSContext *ctx, JSValue fn)
goto done;
JSFunctionBytecode *b = f->u.func.function_bytecode;
if (b->has_debug) {
JS_SetPropertyStr(ctx, ret, "filename", JS_AtomToString(ctx, b->debug.filename));
// JS_SetPropertyStr(ctx, ret, "line", JS_NewInt32(ctx,b->debug.line_num));
char atom_buf[ATOM_GET_STR_BUF_SIZE];
const char *str;
int i;
// Function name
if (b->func_name != JS_ATOM_NULL) {
str = JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf), b->func_name);
JS_SetPropertyStr(ctx, ret, "name", JS_NewString(ctx, str));
}
// File location info
if (b->has_debug && b->debug.filename != JS_ATOM_NULL) {
int line_num, col_num;
str = JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf), b->debug.filename);
line_num = find_line_num(ctx, b, -1, &col_num);
JS_SetPropertyStr(ctx, ret, "filename", JS_NewString(ctx, str));
JS_SetPropertyStr(ctx, ret, "line", JS_NewInt32(ctx, line_num));
JS_SetPropertyStr(ctx, ret, "column", JS_NewInt32(ctx, col_num));
}
// Arguments
if (b->arg_count && b->vardefs) {
JSValue args_array = JS_NewArray(ctx);
for (i = 0; i < b->arg_count; i++) {
str = JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf), b->vardefs[i].var_name);
JS_SetPropertyUint32(ctx, args_array, i, JS_NewString(ctx, str));
}
JS_SetPropertyStr(ctx, ret, "args", args_array);
}
// Local variables
if (b->var_count && b->vardefs) {
JSValue locals_array = JS_NewArray(ctx);
for (i = 0; i < b->var_count; i++) {
JSVarDef *vd = &b->vardefs[b->arg_count + i];
JSValue local_obj = JS_NewObject(ctx);
str = JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf), vd->var_name);
JS_SetPropertyStr(ctx, local_obj, "name", JS_NewString(ctx, str));
const char *var_type =
vd->var_kind == JS_VAR_CATCH ? "catch" :
(vd->var_kind == JS_VAR_FUNCTION_DECL ||
vd->var_kind == JS_VAR_NEW_FUNCTION_DECL) ? "function" :
vd->is_const ? "const" :
vd->is_lexical ? "let" : "var";
JS_SetPropertyStr(ctx, local_obj, "type", JS_NewString(ctx, var_type));
JS_SetPropertyStr(ctx, local_obj, "index", JS_NewInt32(ctx, i));
if (vd->scope_level) {
JS_SetPropertyStr(ctx, local_obj, "scope_level", JS_NewInt32(ctx, vd->scope_level));
JS_SetPropertyStr(ctx, local_obj, "scope_next", JS_NewInt32(ctx, vd->scope_next));
}
JS_SetPropertyUint32(ctx, locals_array, i, local_obj);
}
JS_SetPropertyStr(ctx, ret, "locals", locals_array);
}
// Closure variables
if (b->closure_var_count) {
JSValue closure_array = JS_NewArray(ctx);
for (i = 0; i < b->closure_var_count; i++) {
JSClosureVar *cv = &b->closure_var[i];
JSValue closure_obj = JS_NewObject(ctx);
str = JS_AtomGetStr(ctx, atom_buf, sizeof(atom_buf), cv->var_name);
JS_SetPropertyStr(ctx, closure_obj, "name", JS_NewString(ctx, str));
JS_SetPropertyStr(ctx, closure_obj, "is_local", JS_NewBool(ctx, cv->is_local));
JS_SetPropertyStr(ctx, closure_obj, "is_arg", JS_NewBool(ctx, cv->is_arg));
JS_SetPropertyStr(ctx, closure_obj, "var_idx", JS_NewInt32(ctx, cv->var_idx));
const char *var_type = cv->is_const ? "const" : cv->is_lexical ? "let" : "var";
JS_SetPropertyStr(ctx, closure_obj, "type", JS_NewString(ctx, var_type));
JS_SetPropertyUint32(ctx, closure_array, i, closure_obj);
}
JS_SetPropertyStr(ctx, ret, "closure_vars", closure_array);
}
// Stack size
JS_SetPropertyStr(ctx, ret, "stack_size", JS_NewInt32(ctx, b->stack_size));
done:
return ret;
}
// Opcode names array for debugger
static const char *opcode_names[] = {
#define FMT(f)
#define DEF(id, size, n_pop, n_push, f) #id,
#define def(id, size, n_pop, n_push, f)
#include "quickjs-opcode.h"
#undef def
#undef DEF
#undef FMT
};
JSValue js_debugger_fn_bytecode(JSContext *ctx, JSValue fn)
{
JSObject *f = JS_VALUE_GET_OBJ(fn);
if (!f || !js_class_has_bytecode(f->class_id))
return JS_NULL;
JSFunctionBytecode *b = f->u.func.function_bytecode;
JSValue ret = JS_NewArray(ctx);
const uint8_t *tab = b->byte_code_buf;
int len = b->byte_code_len;
int pos = 0;
int idx = 0;
BOOL use_short_opcodes = TRUE;
char opcode_str[256];
while (pos < len) {
int op = tab[pos];
const JSOpCode *oi;
if (use_short_opcodes)
oi = &short_opcode_info(op);
else
oi = &opcode_info[op];
int size = oi->size;
if (pos + size > len) {
break;
}
if (op >= sizeof(opcode_names)/sizeof(opcode_names[0])) {
snprintf(opcode_str, sizeof(opcode_str), "unknown");
} else {
const char *opcode_name = opcode_names[op];
snprintf(opcode_str, sizeof(opcode_str), "%s", opcode_name);
// Add arguments based on opcode format
int arg_pos = pos + 1;
switch(oi->fmt) {
case OP_FMT_none_int:
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %d", op - OP_push_0);
break;
case OP_FMT_npopx:
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %d", op - OP_call0);
break;
case OP_FMT_u8:
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %u", get_u8(tab + arg_pos));
break;
case OP_FMT_i8:
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %d", get_i8(tab + arg_pos));
break;
case OP_FMT_u16:
case OP_FMT_npop:
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %u", get_u16(tab + arg_pos));
break;
case OP_FMT_npop_u16:
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %u %u", get_u16(tab + arg_pos), get_u16(tab + arg_pos + 2));
break;
case OP_FMT_i16:
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %d", get_i16(tab + arg_pos));
break;
case OP_FMT_i32:
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %d", get_i32(tab + arg_pos));
break;
case OP_FMT_u32:
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %u", get_u32(tab + arg_pos));
break;
#if SHORT_OPCODES
case OP_FMT_label8:
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %u", get_i8(tab + arg_pos) + arg_pos);
break;
case OP_FMT_label16:
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %u", get_i16(tab + arg_pos) + arg_pos);
break;
case OP_FMT_const8:
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %u", get_u8(tab + arg_pos));
break;
#endif
case OP_FMT_const:
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %u", get_u32(tab + arg_pos));
break;
case OP_FMT_label:
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %u", get_u32(tab + arg_pos) + arg_pos);
break;
case OP_FMT_label_u16:
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %u %u", get_u32(tab + arg_pos) + arg_pos, get_u16(tab + arg_pos + 4));
break;
case OP_FMT_atom:
{
JSAtom atom = get_u32(tab + arg_pos);
const char *atom_str = JS_AtomToCString(ctx, atom);
if (atom_str) {
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %s", atom_str);
JS_FreeCString(ctx, atom_str);
} else {
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %u", atom);
}
}
break;
case OP_FMT_atom_u8:
{
JSAtom atom = get_u32(tab + arg_pos);
const char *atom_str = JS_AtomToCString(ctx, atom);
if (atom_str) {
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %s %d", atom_str, get_u8(tab + arg_pos + 4));
JS_FreeCString(ctx, atom_str);
} else {
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %u %d", atom, get_u8(tab + arg_pos + 4));
}
}
break;
case OP_FMT_atom_u16:
{
JSAtom atom = get_u32(tab + arg_pos);
const char *atom_str = JS_AtomToCString(ctx, atom);
if (atom_str) {
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %s %d", atom_str, get_u16(tab + arg_pos + 4));
JS_FreeCString(ctx, atom_str);
} else {
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %u %d", atom, get_u16(tab + arg_pos + 4));
}
}
break;
case OP_FMT_atom_label_u8:
case OP_FMT_atom_label_u16:
{
JSAtom atom = get_u32(tab + arg_pos);
int addr = get_u32(tab + arg_pos + 4);
int extra = (oi->fmt == OP_FMT_atom_label_u8) ?
get_u8(tab + arg_pos + 8) : get_u16(tab + arg_pos + 8);
const char *atom_str = JS_AtomToCString(ctx, atom);
if (atom_str) {
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %s %u %u", atom_str, addr + arg_pos + 4, extra);
JS_FreeCString(ctx, atom_str);
} else {
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %u %u %u", atom, addr + arg_pos + 4, extra);
}
}
break;
case OP_FMT_none_loc:
{
int idx = (op - OP_get_loc0) % 4;
if (idx < b->var_count) {
const char *var_name = JS_AtomToCString(ctx, b->vardefs[idx].var_name);
if (var_name) {
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %d: %s", idx, var_name);
JS_FreeCString(ctx, var_name);
} else {
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %d", idx);
}
} else {
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %d", idx);
}
}
break;
case OP_FMT_loc8:
{
int idx = get_u8(tab + arg_pos);
if (idx < b->var_count) {
const char *var_name = JS_AtomToCString(ctx, b->vardefs[idx].var_name);
if (var_name) {
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %u: %s", idx, var_name);
JS_FreeCString(ctx, var_name);
} else {
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %u", idx);
}
} else {
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %u", idx);
}
}
break;
case OP_FMT_loc:
{
int idx = get_u16(tab + arg_pos);
if (idx < b->var_count) {
const char *var_name = JS_AtomToCString(ctx, b->vardefs[idx].var_name);
if (var_name) {
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %u: %s", idx, var_name);
JS_FreeCString(ctx, var_name);
} else {
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %u", idx);
}
} else {
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %u", idx);
}
}
break;
case OP_FMT_none_arg:
{
int idx = (op - OP_get_arg0) % 4;
if (idx < b->arg_count) {
const char *arg_name = JS_AtomToCString(ctx, b->vardefs[idx].var_name);
if (arg_name) {
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %d: %s", idx, arg_name);
JS_FreeCString(ctx, arg_name);
} else {
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %d", idx);
}
} else {
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %d", idx);
}
}
break;
case OP_FMT_arg:
{
int idx = get_u16(tab + arg_pos);
if (idx < b->arg_count) {
const char *arg_name = JS_AtomToCString(ctx, b->vardefs[idx].var_name);
if (arg_name) {
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %u: %s", idx, arg_name);
JS_FreeCString(ctx, arg_name);
} else {
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %u", idx);
}
} else {
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %u", idx);
}
}
break;
case OP_FMT_none_var_ref:
{
int idx = (op - OP_get_var_ref0) % 4;
if (idx < b->closure_var_count) {
const char *var_name = JS_AtomToCString(ctx, b->closure_var[idx].var_name);
if (var_name) {
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %d: %s", idx, var_name);
JS_FreeCString(ctx, var_name);
} else {
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %d", idx);
}
} else {
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %d", idx);
}
}
break;
case OP_FMT_var_ref:
{
int idx = get_u16(tab + arg_pos);
if (idx < b->closure_var_count) {
const char *var_name = JS_AtomToCString(ctx, b->closure_var[idx].var_name);
if (var_name) {
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %u: %s", idx, var_name);
JS_FreeCString(ctx, var_name);
} else {
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %u", idx);
}
} else {
snprintf(opcode_str + strlen(opcode_str), sizeof(opcode_str) - strlen(opcode_str),
" %u", idx);
}
}
break;
default:
break;
}
}
JSValue js_opcode_str = JS_NewString(ctx, opcode_str);
JS_SetPropertyUint32(ctx, ret, idx++, js_opcode_str);
pos += size;
}
return ret;
}
JSValue js_debugger_local_variables(JSContext *ctx, int stack_index) {
JSValue ret = JS_NewObject(ctx);

View File

@@ -1005,6 +1005,7 @@ JSValue js_debugger_closure_variables(JSContext *ctx, JSValue fn);
JSValue js_debugger_local_variables(JSContext *ctx, int stack_index);
JSValue js_debugger_build_backtrace(JSContext *ctx, const uint8_t *cur_pc);
JSValue js_debugger_fn_info(JSContext *ctx, JSValue fn);
JSValue js_debugger_fn_bytecode(JSContext *js, JSValue fn);
#undef js_unlikely
#undef js_force_inline