diff --git a/oden-js-sys/quickjs/quickjs-opcode.h b/oden-js-sys/quickjs/quickjs-opcode.h index c731a14a..a99c5666 100644 --- a/oden-js-sys/quickjs/quickjs-opcode.h +++ b/oden-js-sys/quickjs/quickjs-opcode.h @@ -263,6 +263,8 @@ DEF( math_mod, 1, 2, 1, none) /* must be the last non short and non temporary opcode */ DEF( nop, 1, 0, 0, none) +DEF( breakpoint, 1, 0, 1, none) + /* temporary opcodes: never emitted in the final bytecode */ def( enter_scope, 3, 0, 0, u16) /* emitted in phase 1, removed in phase 2 */ diff --git a/oden-js-sys/quickjs/quickjs.c b/oden-js-sys/quickjs/quickjs.c index 4d62fd6c..40624b9a 100644 --- a/oden-js-sys/quickjs/quickjs.c +++ b/oden-js-sys/quickjs/quickjs.c @@ -109,6 +109,8 @@ /* test the GC by forcing it before each object allocation */ //#define FORCE_GC_AT_MALLOC +#define CONFIG_DEBUGGER + #ifdef CONFIG_ATOMICS #include #include @@ -289,6 +291,9 @@ struct JSRuntime { JSModuleLoaderFunc *module_loader_func; void *module_loader_opaque; + JSMapSourceFunc *map_source_func; + void *map_source_opaque; + BOOL can_block : 8; /* TRUE if Atomics.wait can block */ /* used to allocate, free and clone SharedArrayBuffers */ JSSharedArrayBufferFunctions sab_funcs; @@ -306,8 +311,14 @@ struct JSRuntime { uint32_t operator_count; #endif void *user_opaque; - JSMapSourceFunc *map_source_func; - void *map_source_opaque; + +#ifdef CONFIG_DEBUGGER + struct list_head breakpoint_list; + JSDebugCallbackFunc *debug_callback; + void *debug_opaque; + int stepping_pc; + uint8_t stepping_byte; +#endif }; struct JSClass { @@ -317,7 +328,7 @@ struct JSClass { JSClassGCMark *gc_mark; JSClassCall *call; /* pointers for exotic behavior, can be NULL if none are present */ - const JSClassExoticMethods *exotic; + const JSClassExoticMethods *exotic; }; #define JS_MODE_STRICT (1 << 0) @@ -997,6 +1008,20 @@ enum OPCodeEnum { OP_TEMP_END, }; +typedef struct JSBreakpoint { + JSRuntime *rt; + JSAtom file; + int line; + + struct list_head link; + + int ref_count; + JSFunctionBytecode *function; + int code_offset; + uint8_t replaced_byte; + JS_BOOL enabled; +} JSBreakpoint; + static int JS_InitAtoms(JSRuntime *rt); static JSAtom __JS_NewAtomInit(JSRuntime *rt, const char *str, int len, int atom_type); @@ -1253,6 +1278,9 @@ static JSValue JS_InstantiateFunctionListItem2(JSContext *ctx, JSObject *p, JSAtom atom, void *opaque); void JS_SetUncatchableError(JSContext *ctx, JSValueConst val, BOOL flag); +static JSBreakpoint *find_existing_breakpoint(JSContext *ctx, JSFunctionBytecode *func, int pc); +static int js_handle_breakpoint(JSContext *ctx, JSFunctionBytecode *b, int code_offset); + static const JSClassExoticMethods js_arguments_exotic_methods; static const JSClassExoticMethods js_string_exotic_methods; static const JSClassExoticMethods js_proxy_exotic_methods; @@ -1636,6 +1664,10 @@ JSRuntime *JS_NewRuntime2(const JSMallocFunctions *mf, void *opaque) #endif init_list_head(&rt->job_list); +#ifdef CONFIG_DEBUGGER + init_list_head(&rt->breakpoint_list); +#endif + if (JS_InitAtoms(rt)) goto fail; @@ -16227,10 +16259,14 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, size_t alloca_size; #if !DIRECT_DISPATCH -#define SWITCH(pc) switch (opcode = *pc++) -#define CASE(op) case op -#define DEFAULT default -#define BREAK break +#define SWITCH(pc) \ + opcode = *pc++; \ + resume_breakpoint: \ + switch (opcode) +#define RESUME_BREAKPOINT(op) opcode = op; goto resume_breakpoint; +#define CASE(op) case op +#define DEFAULT default +#define BREAK break #else static const void * const dispatch_table[256] = { #define DEF(id, size, n_pop, n_push, f) && case_OP_ ## id, @@ -16242,10 +16278,11 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, #include "quickjs-opcode.h" [ OP_COUNT ... 255 ] = &&case_default }; -#define SWITCH(pc) goto *dispatch_table[opcode = *pc++]; -#define CASE(op) case_ ## op -#define DEFAULT case_default -#define BREAK SWITCH(pc) +#define SWITCH(pc) goto *dispatch_table[opcode = *pc++]; +#define RESUME_BREAKPOINT(op) opcode = op; goto *dispatch_table[opcode]; +#define CASE(op) case_ ## op +#define DEFAULT case_default +#define BREAK SWITCH(pc) #endif if (js_poll_interrupts(caller_ctx)) @@ -18667,6 +18704,13 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj, JS_FreeValue(ctx, sp[-1]); sp[-1] = JS_FALSE; BREAK; + CASE(OP_breakpoint): + { + int code_offset = (int)(pc - b->byte_code_buf - 1); + int next_op = js_handle_breakpoint(ctx, b, code_offset); + RESUME_BREAKPOINT(next_op); + } + BREAK; CASE(OP_invalid): DEFAULT: JS_ThrowInternalError(ctx, "invalid opcode: pc=%u opcode=0x%02x", @@ -31142,6 +31186,7 @@ static __exception int resolve_variables(JSContext *ctx, JSFunctionDef *s) case OP_nop: /* remove erased code */ break; + case OP_set_class_name: /* only used during parsing */ break; @@ -54086,3 +54131,182 @@ void JS_SetSourceMapFunc(JSRuntime *rt, JSMapSourceFunc *map_func, void *opaque) rt->map_source_func = map_func; rt->map_source_opaque = opaque; } + +/* Debugger */ + +static BOOL find_pc_offset(JSFunctionBytecode *b, int target_line, int *break_pc, int *actual_line) +{ + const uint8_t *p_end, *p; + int new_line_num, line_num, new_pc, pc, v, ret; + unsigned int op; + + if (!b->has_debug || !b->debug.pc2line_buf) { + /* function was stripped */ + return FALSE; + } + if (target_line < b->debug.line_num) { + /* this line is before this function even starts */ + return FALSE; + } + + p = b->debug.pc2line_buf; + p_end = p + b->debug.pc2line_len; + pc = 0; + line_num = b->debug.line_num; + while (p < p_end) { + op = *p++; + if (op == 0) { + uint32_t val; + ret = get_leb128(&val, p, p_end); + if (ret < 0) + goto fail; + new_pc = pc + val; + p += ret; + ret = get_sleb128(&v, p, p_end); + if (ret < 0) { + fail: + /* should never happen */ + return FALSE; + } + p += ret; + new_line_num = line_num + v; + } else { + op -= PC2LINE_OP_FIRST; + new_pc = pc + (op / PC2LINE_RANGE); + new_line_num = line_num + (op % PC2LINE_RANGE) + PC2LINE_BASE; + } + if (new_line_num > target_line) { + *break_pc = pc; + *actual_line = line_num; + return TRUE; + } + line_num = new_line_num; + pc = new_pc; + } + return FALSE; +} + +static JSBreakpoint *find_existing_breakpoint(JSContext *ctx, JSFunctionBytecode *func, int pc) { + struct list_head *el; + list_for_each(el, &ctx->rt->breakpoint_list) { + JSBreakpoint *bp = list_entry(el, JSBreakpoint, link); + if (bp->function == func && bp->code_offset == pc) { + return JS_DupBreakpoint(bp); + } + } + return NULL; +} + +typedef struct JSDebugContext { + JSContext *ctx; +} JSDebugContext; + +static int js_handle_breakpoint(JSContext *ctx, JSFunctionBytecode *b, int code_offset) { + int next_op; + JSResumeMode resume = JS_RESUME_MODE_CONTINUE; + JSRuntime *rt = ctx->rt; + JSBreakpoint *bp = find_existing_breakpoint(ctx, b, code_offset); + if (bp) { + if (bp->enabled) { + if (rt->debug_callback) { + JSDebugContext dctx; + dctx.ctx = ctx; + + resume = rt->debug_callback(&dctx, JS_BREAK_BREAKPOINT, bp, rt->debug_opaque); + } + } + next_op = bp->replaced_byte; + } else { + assert(code_offset == rt->stepping_pc); + rt->stepping_pc = -1; + next_op = rt->stepping_byte; + b->byte_code_buf[code_offset] = rt->stepping_byte; + + if (rt->debug_callback) { + JSDebugContext dctx; + dctx.ctx = ctx; + + resume = rt->debug_callback(&dctx, JS_BREAK_STEP, NULL, rt->debug_opaque); + } + } + + return next_op; +} + +JSBreakpoint *JS_SetBreakpoint(JSContext *ctx, JSAtom file, int line) +{ + struct list_head *el; + list_for_each(el, &ctx->rt->gc_obj_list) { + JSGCObjectHeader *gp = list_entry(el, JSGCObjectHeader, link); + if (gp->gc_obj_type == JS_GC_OBJ_TYPE_FUNCTION_BYTECODE) { + JSFunctionBytecode *func = (JSFunctionBytecode *)gp; + if (func->debug.filename == file) { + int break_pc, actual_line; + if (find_pc_offset(func, line, &break_pc, &actual_line)) { + uint8_t code = func->byte_code_buf[break_pc]; + if (code == OP_breakpoint) { + return find_existing_breakpoint(ctx, func, break_pc); + } + + JSBreakpoint *breakpoint = js_mallocz(ctx, sizeof(JSBreakpoint)); + if (!breakpoint) { + return NULL; + } + breakpoint->ref_count = 1; + + breakpoint->rt = ctx->rt; + breakpoint->file = JS_DupAtom(ctx, file); + breakpoint->line = actual_line; + + list_add_tail(&breakpoint->link, &ctx->rt->breakpoint_list); + breakpoint->function = func; + func->header.ref_count++; + + breakpoint->code_offset = break_pc; + breakpoint->enabled = TRUE; + breakpoint->replaced_byte = code; + func->byte_code_buf[break_pc] = OP_breakpoint; + + return breakpoint; + } + } + } + } + + return NULL; +} + +JSBreakpoint *JS_DupBreakpoint(JSBreakpoint *breakpoint) +{ + breakpoint->ref_count += 1; + return breakpoint; +} + +void JS_DeleteBreakpoint(JSBreakpoint *breakpoint) +{ + breakpoint->ref_count -= 1; + if (breakpoint->ref_count == 0) { + breakpoint->function->byte_code_buf[breakpoint->code_offset] = breakpoint->replaced_byte; + list_del(&breakpoint->link); + JS_FreeValueRT(breakpoint->rt, JS_MKPTR(JS_TAG_FUNCTION_BYTECODE, breakpoint->function)); + JS_FreeAtomRT(breakpoint->rt, breakpoint->file); + } +} + +JS_BOOL JS_GetBreakpointEnabled(JSBreakpoint *breakpoint) +{ + return breakpoint->enabled; +} + +void JS_SetBreakpointEnabled(JSBreakpoint *breakpoint, JS_BOOL enabled) +{ + breakpoint->enabled = enabled; +} + +void JS_GetBreakpointLocation(JSBreakpoint *breakpoint, JSAtom *file, int *line) +{ + *file = JS_DupAtomRT(breakpoint->rt, breakpoint->file); + *line = breakpoint->line; +} + + diff --git a/oden-js-sys/quickjs/quickjs.h b/oden-js-sys/quickjs/quickjs.h index 90c578ae..a5409d2f 100644 --- a/oden-js-sys/quickjs/quickjs.h +++ b/oden-js-sys/quickjs/quickjs.h @@ -1050,6 +1050,34 @@ typedef void JSMapSourceFunc(JSContext *ctx, void *opaque, JSAtom file, int line void JS_SetSourceMapFunc(JSRuntime *rt, JSMapSourceFunc *map_func, void *opaque); +/* Debugger support */ + +typedef struct JSDebugContext JSDebugContext; +JSContext *JS_DebugGetContext(JSDebugContext *ctx); + +typedef enum JSResumeMode { + JS_RESUME_MODE_CONTINUE, + JS_RESUME_MODE_STEP_OVER, + JS_RESUME_MODE_STEP_INTO, +} JSResumeMode; + +typedef enum JSBreakReason { + JS_BREAK_BREAKPOINT, + JS_BREAK_STEP, +} JSBreakReason; + +typedef struct JSBreakpoint JSBreakpoint; + +typedef JSResumeMode JSDebugCallbackFunc(JSDebugContext *ctx, JSBreakReason reason, JSBreakpoint *breakpoint, void *opaque); +void JS_SetDebugCallbackFunc(JSRuntime *rt, JSDebugCallbackFunc *bp_func, void *opqaue); + +JSBreakpoint *JS_SetBreakpoint(JSContext *ctx, JSAtom file, int line); +JSBreakpoint *JS_DupBreakpoint(JSBreakpoint *breakpoint); +void JS_DeleteBreakpoint(JSBreakpoint *breakpoint); +JS_BOOL JS_GetBreakpointEnabled(JSBreakpoint *breakpoint); +void JS_SetBreakpointEnabled(JSBreakpoint *breakpoint, JS_BOOL enabled); +void JS_GetBreakpointLocation(JSBreakpoint *breakpoint, JSAtom *file, int *line); + #undef js_unlikely #undef js_force_inline