[quickjs] Debugger
This commit is contained in:
parent
c96a1a4979
commit
6fa3e64b8d
3 changed files with 265 additions and 11 deletions
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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 <pthread.h>
|
||||
#include <stdatomic.h>
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue