[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 */
|
/* must be the last non short and non temporary opcode */
|
||||||
DEF( nop, 1, 0, 0, none)
|
DEF( nop, 1, 0, 0, none)
|
||||||
|
|
||||||
|
DEF( breakpoint, 1, 0, 1, none)
|
||||||
|
|
||||||
/* temporary opcodes: never emitted in the final bytecode */
|
/* temporary opcodes: never emitted in the final bytecode */
|
||||||
|
|
||||||
def( enter_scope, 3, 0, 0, u16) /* emitted in phase 1, removed in phase 2 */
|
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 */
|
/* test the GC by forcing it before each object allocation */
|
||||||
//#define FORCE_GC_AT_MALLOC
|
//#define FORCE_GC_AT_MALLOC
|
||||||
|
|
||||||
|
#define CONFIG_DEBUGGER
|
||||||
|
|
||||||
#ifdef CONFIG_ATOMICS
|
#ifdef CONFIG_ATOMICS
|
||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#include <stdatomic.h>
|
#include <stdatomic.h>
|
||||||
|
|
@ -289,6 +291,9 @@ struct JSRuntime {
|
||||||
JSModuleLoaderFunc *module_loader_func;
|
JSModuleLoaderFunc *module_loader_func;
|
||||||
void *module_loader_opaque;
|
void *module_loader_opaque;
|
||||||
|
|
||||||
|
JSMapSourceFunc *map_source_func;
|
||||||
|
void *map_source_opaque;
|
||||||
|
|
||||||
BOOL can_block : 8; /* TRUE if Atomics.wait can block */
|
BOOL can_block : 8; /* TRUE if Atomics.wait can block */
|
||||||
/* used to allocate, free and clone SharedArrayBuffers */
|
/* used to allocate, free and clone SharedArrayBuffers */
|
||||||
JSSharedArrayBufferFunctions sab_funcs;
|
JSSharedArrayBufferFunctions sab_funcs;
|
||||||
|
|
@ -306,8 +311,14 @@ struct JSRuntime {
|
||||||
uint32_t operator_count;
|
uint32_t operator_count;
|
||||||
#endif
|
#endif
|
||||||
void *user_opaque;
|
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 {
|
struct JSClass {
|
||||||
|
|
@ -317,7 +328,7 @@ struct JSClass {
|
||||||
JSClassGCMark *gc_mark;
|
JSClassGCMark *gc_mark;
|
||||||
JSClassCall *call;
|
JSClassCall *call;
|
||||||
/* pointers for exotic behavior, can be NULL if none are present */
|
/* pointers for exotic behavior, can be NULL if none are present */
|
||||||
const JSClassExoticMethods *exotic;
|
const JSClassExoticMethods *exotic;
|
||||||
};
|
};
|
||||||
|
|
||||||
#define JS_MODE_STRICT (1 << 0)
|
#define JS_MODE_STRICT (1 << 0)
|
||||||
|
|
@ -997,6 +1008,20 @@ enum OPCodeEnum {
|
||||||
OP_TEMP_END,
|
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 int JS_InitAtoms(JSRuntime *rt);
|
||||||
static JSAtom __JS_NewAtomInit(JSRuntime *rt, const char *str, int len,
|
static JSAtom __JS_NewAtomInit(JSRuntime *rt, const char *str, int len,
|
||||||
int atom_type);
|
int atom_type);
|
||||||
|
|
@ -1253,6 +1278,9 @@ static JSValue JS_InstantiateFunctionListItem2(JSContext *ctx, JSObject *p,
|
||||||
JSAtom atom, void *opaque);
|
JSAtom atom, void *opaque);
|
||||||
void JS_SetUncatchableError(JSContext *ctx, JSValueConst val, BOOL flag);
|
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_arguments_exotic_methods;
|
||||||
static const JSClassExoticMethods js_string_exotic_methods;
|
static const JSClassExoticMethods js_string_exotic_methods;
|
||||||
static const JSClassExoticMethods js_proxy_exotic_methods;
|
static const JSClassExoticMethods js_proxy_exotic_methods;
|
||||||
|
|
@ -1636,6 +1664,10 @@ JSRuntime *JS_NewRuntime2(const JSMallocFunctions *mf, void *opaque)
|
||||||
#endif
|
#endif
|
||||||
init_list_head(&rt->job_list);
|
init_list_head(&rt->job_list);
|
||||||
|
|
||||||
|
#ifdef CONFIG_DEBUGGER
|
||||||
|
init_list_head(&rt->breakpoint_list);
|
||||||
|
#endif
|
||||||
|
|
||||||
if (JS_InitAtoms(rt))
|
if (JS_InitAtoms(rt))
|
||||||
goto fail;
|
goto fail;
|
||||||
|
|
||||||
|
|
@ -16227,10 +16259,14 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj,
|
||||||
size_t alloca_size;
|
size_t alloca_size;
|
||||||
|
|
||||||
#if !DIRECT_DISPATCH
|
#if !DIRECT_DISPATCH
|
||||||
#define SWITCH(pc) switch (opcode = *pc++)
|
#define SWITCH(pc) \
|
||||||
#define CASE(op) case op
|
opcode = *pc++; \
|
||||||
#define DEFAULT default
|
resume_breakpoint: \
|
||||||
#define BREAK break
|
switch (opcode)
|
||||||
|
#define RESUME_BREAKPOINT(op) opcode = op; goto resume_breakpoint;
|
||||||
|
#define CASE(op) case op
|
||||||
|
#define DEFAULT default
|
||||||
|
#define BREAK break
|
||||||
#else
|
#else
|
||||||
static const void * const dispatch_table[256] = {
|
static const void * const dispatch_table[256] = {
|
||||||
#define DEF(id, size, n_pop, n_push, f) && case_OP_ ## id,
|
#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"
|
#include "quickjs-opcode.h"
|
||||||
[ OP_COUNT ... 255 ] = &&case_default
|
[ OP_COUNT ... 255 ] = &&case_default
|
||||||
};
|
};
|
||||||
#define SWITCH(pc) goto *dispatch_table[opcode = *pc++];
|
#define SWITCH(pc) goto *dispatch_table[opcode = *pc++];
|
||||||
#define CASE(op) case_ ## op
|
#define RESUME_BREAKPOINT(op) opcode = op; goto *dispatch_table[opcode];
|
||||||
#define DEFAULT case_default
|
#define CASE(op) case_ ## op
|
||||||
#define BREAK SWITCH(pc)
|
#define DEFAULT case_default
|
||||||
|
#define BREAK SWITCH(pc)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (js_poll_interrupts(caller_ctx))
|
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]);
|
JS_FreeValue(ctx, sp[-1]);
|
||||||
sp[-1] = JS_FALSE;
|
sp[-1] = JS_FALSE;
|
||||||
BREAK;
|
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):
|
CASE(OP_invalid):
|
||||||
DEFAULT:
|
DEFAULT:
|
||||||
JS_ThrowInternalError(ctx, "invalid opcode: pc=%u opcode=0x%02x",
|
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:
|
case OP_nop:
|
||||||
/* remove erased code */
|
/* remove erased code */
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OP_set_class_name:
|
case OP_set_class_name:
|
||||||
/* only used during parsing */
|
/* only used during parsing */
|
||||||
break;
|
break;
|
||||||
|
|
@ -54086,3 +54131,182 @@ void JS_SetSourceMapFunc(JSRuntime *rt, JSMapSourceFunc *map_func, void *opaque)
|
||||||
rt->map_source_func = map_func;
|
rt->map_source_func = map_func;
|
||||||
rt->map_source_opaque = opaque;
|
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);
|
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_unlikely
|
||||||
#undef js_force_inline
|
#undef js_force_inline
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue