From 329a03a324b8515338db6b03912b9e167a66de37 Mon Sep 17 00:00:00 2001 From: deuce <> Date: Tue, 23 Oct 2012 05:06:43 +0000 Subject: [PATCH] Make the debugger actually useful. Added backtrace (bt) and up/down commands. Allow specifying breakpoints in file:line format for future load()s. Breakpoints are a small memory leak right now as there's no way to clear them. --- src/sbbs3/jsexec.c | 321 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 274 insertions(+), 47 deletions(-) diff --git a/src/sbbs3/jsexec.c b/src/sbbs3/jsexec.c index 4583a72bb3..251a4c49d1 100644 --- a/src/sbbs3/jsexec.c +++ b/src/sbbs3/jsexec.c @@ -82,13 +82,6 @@ BOOL daemonize=FALSE; #endif char orig_cwd[MAX_PATH+1]; BOOL debugger=FALSE; -enum debug_action { - DEBUG_CONTINUE, - DEBUG_EXIT -}; - -static JSTrapStatus trap_handler(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, jsval closure); -static enum debug_action debug_prompt(JSScript *script); void banner(FILE* fp) { @@ -776,35 +769,203 @@ static const char* js_ext(const char* fname) * This doesn't belong here and is done wrong... * mcmlxxix should love it. */ -static enum debug_action debug_prompt(JSScript *script) +#include <link_list.h> +link_list_t breakpoints; +link_list_t scripts; +enum debug_action { + DEBUG_CONTINUE, + DEBUG_EXIT +}; +static JSTrapStatus trap_handler(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, jsval closure); +static JSTrapStatus throw_handler(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, void *closure); +static enum debug_action debug_prompt(JSContext *cx, JSScript *script); + +struct breakpoint { + uintN line; + char name[]; +}; + +struct cur_script { + JSScript *script; + char *fname; + uintN firstline; + uintN lastline; +}; + +static void newscript_handler(JSContext *cx, + const char *filename, /* URL of script */ + uintN lineno, /* first line */ + JSScript *script, + JSFunction *fun, + void *callerdata) { - char line[1024]; + const char *fname=NULL; + struct cur_script *cs; + list_node_t *node; + struct breakpoint *bp; + jsbytecode *pc; + + cs=(struct cur_script *)malloc(sizeof(struct cur_script)); + if(!cs) { + fputs("Error allocated script struct", stderr); + return; + } + fname=JS_GetScriptFilename(cx, script); + cs->fname=strdup(fname); + if(cs->fname) + fname=getfname(fname); + cs->firstline=lineno; + cs->lastline=lineno+JS_GetScriptLineExtent(cx, script); + cs->script=script; + + // Loop through breakpoints. + for(node=listFirstNode(&breakpoints); node; node=listNextNode(node)) { + bp=(struct breakpoint *)node->data; + if(strcmp(fname, bp->name)==0 || strcmp(cs->fname, bp->name)==0) { + if(bp->line >= cs->firstline && bp->line <= cs->lastline) { + pc=JS_LineNumberToPC(cx, script, bp->line); + if(pc==NULL) { + fprintf(stderr, "NEWSCRIPT: Unable to locate line %u\n", bp->line); + break; + } + if(JS_PCToLineNumber(cx, script, pc)!=bp->line) { + continue; + } + if(!JS_SetTrap(cx, script, pc, trap_handler, JSVAL_VOID)) { + fprintf(stderr, "NEWSCRIPT: Unable to set breakpoint at line %u\n",bp->line); + } + } + } + } + listPushNode(&scripts, cs); +} +static void killscript_handler(JSContext *cx, JSScript *script, void *callerdata) +{ + list_node_t *node; + list_node_t *pnode; + struct cur_script *cs; + + for(node=listFirstNode(&scripts); node; node=listNextNode(node)) { + cs=(struct cur_script *)node->data; + + if(cs->script == script) { + pnode=listPrevNode(node); + free(cs->fname); + listRemoveNode(&scripts, node, TRUE); + } + } +} + +static void init_debugger(JSRuntime *rt, JSContext *cx) +{ + JS_SetDebugMode(cx, JS_TRUE); + JS_SetThrowHook(js_runtime, throw_handler, NULL); + listInit(&breakpoints,LINK_LIST_MUTEX); + listInit(&scripts,LINK_LIST_MUTEX); + JS_SetNewScriptHook(rt, newscript_handler, NULL); + JS_SetDestroyScriptHook(rt, killscript_handler, NULL); +} + +static enum debug_action debug_prompt(JSContext *cx, JSScript *script) +{ + char line[1024]; + const char *fpath; + const char *fname; + JSStackFrame *fp; + JSFunction *fn; + jsbytecode *pc; + char *cp; + jsrefcount rc; + + fp=JS_GetScriptedCaller(cx, NULL); while(1) { - if(JS_IsExceptionPending(js_cx)) + if(JS_IsExceptionPending(cx)) fputs("!", stdout); - fputs("JS> ", stdout); + fpath=JS_GetScriptFilename(cx, script); + if(fpath) { + fname=getfname(fpath); + fputs(fname, stdout); + if(fp) { + pc=JS_GetFramePC(cx, fp); + if(pc) + fprintf(stdout, ":%u",JS_PCToLineNumber(cx, script, pc)); + fn=JS_GetFrameFunction(cx, fp); + if(fn) { + JSString *name; + + name=JS_GetFunctionId(fn); + if(name) { + JSSTRING_TO_STRING(cx, name, cp, NULL); + fprintf(stdout, " %s()", cp); + } + } + } + } + fputs("> ", stdout); + rc=JS_SUSPENDREQUEST(cx); if(fgets(line, sizeof(line), stdin)==NULL) { fputs("Error readin input\n",stderr); + JS_RESUMEREQUEST(cx, rc); continue; } + JS_RESUMEREQUEST(cx, rc); if(strncmp(line, "break ", 6)==0) { - ulong linenum=strtoul(line+6, NULL, 10); - jsbytecode *pc; - + ulong linenum=0; + jsbytecode *pc; + const char *sname; + struct cur_script *cs; + list_node_t *node; + struct breakpoint *bp; + const char *text; + char *num; + + text=line+6; + if(isdigit(*text) && strchr(text,'.')==NULL) { + linenum=strtoul(text, NULL, 10); + text=fpath; + } + else { + num=strchr(text,':'); + if(num) { + *num=0; + num++; + if(!(isdigit(*num))) { + fputs("Unable to parse breakpoint\n",stderr); + continue; + } + linenum=strtoul(num, NULL, 10); + } + } if(linenum==ULONG_MAX) { fputs("Unable to parse line number\n",stderr); continue; } - pc=JS_LineNumberToPC(js_cx, script, linenum); - if(pc==NULL) { - fprintf(stderr, "Unable to locate line %lu\n", linenum); - break; + for(node=listFirstNode(&scripts); node; node=listNextNode(node)) { + cs=(struct cur_script *)node->data; + sname=getfname(cs->fname); + if(strcmp(sname, text)==0 || strcmp(cs->fname, text)==0) { + pc=JS_LineNumberToPC(cx, cs->script, linenum); + if(JS_PCToLineNumber(cx, cs->script, pc)!=linenum) + continue; + if(pc==NULL) { + fprintf(stderr, "Unable to locate line %lu\n", linenum); + continue; + } + if(!JS_SetTrap(cx, cs->script, pc, trap_handler, JSVAL_VOID)) { + fprintf(stderr, "Unable to set breakpoint at line %lu\n",linenum); + } + } } - if(!JS_SetTrap(js_cx, script, pc, trap_handler, JSVAL_VOID)) { - fprintf(stderr, "Unable to set breakpoint at line %lu\n",linenum); + bp=(struct breakpoint *)malloc(sizeof(uintN)+strlen(text)+1); + if(!bp) { + fputs("Unable to allocate breakpoint\n",stderr); + continue; } + bp->line=linenum; + strcpy(bp->name, text); + listPushNode(&breakpoints, bp); continue; } if(strncmp(line, "r", 1)==0) { @@ -814,50 +975,118 @@ static enum debug_action debug_prompt(JSScript *script) strncmp(line, "e ", 2)==0 ) { jsval ret; - JSStackFrame *fp; jsval oldexcept; BOOL has_old=FALSE; int cmdlen=5; if(line[1]==' ') cmdlen=2; - if(JS_IsExceptionPending(js_cx)) { - if(JS_GetPendingException(js_cx, &oldexcept)) + if(JS_IsExceptionPending(cx)) { + if(JS_GetPendingException(cx, &oldexcept)) has_old=TRUE; - JS_ClearPendingException(js_cx); + JS_ClearPendingException(cx); } - fp=JS_GetScriptedCaller(js_cx, NULL); if(!fp) { if(has_old) - JS_SetPendingException(js_cx, oldexcept); + JS_SetPendingException(cx, oldexcept); fputs("Unable to get frame pointer\n", stderr); continue; } - if(!JS_EvaluateInStackFrame(js_cx, fp, line+cmdlen, strlen(line)-cmdlen, "eval-d statement", 1, &ret)) { - if(JS_IsExceptionPending(js_cx)) { - JS_SetErrorReporter(js_cx, js_ErrorReporter); - JS_ReportPendingException(js_cx); - JS_ClearPendingException(js_cx); + if(!JS_EvaluateInStackFrame(cx, fp, line+cmdlen, strlen(line)-cmdlen, "eval-d statement", 1, &ret)) { + if(JS_IsExceptionPending(cx)) { + JS_SetErrorReporter(cx, js_ErrorReporter); + JS_ReportPendingException(cx); + JS_ClearPendingException(cx); } } else { // TODO: Check ret... } if(has_old) - JS_SetPendingException(js_cx, oldexcept); + JS_SetPendingException(cx, oldexcept); continue; } if(strncmp(line, "clear", 5)==0) { - JS_ClearPendingException(js_cx); + JS_ClearPendingException(cx); + continue; + } + if(strncmp(line, "bt", 2)==0 + || strncmp(line, "backtrace", 9)==0) { + JSScript *fs; + JSStackFrame *fpi=NULL; + int fnum=0; + + while(JS_FrameIterator(cx, &fpi)) { + fs=JS_GetFrameScript(cx, fpi); + fname=JS_GetScriptFilename(cx, fs); + if(fname==NULL) + fname="No name"; + fname=getfname(fname); + pc=JS_GetFramePC(cx, fpi); + fprintf(stdout, "%c#%-2u %p ",fpi==fp?'*':' ',fnum,pc); + fn=JS_GetFrameFunction(cx, fpi); + if(fn) { + JSString *name; + + name=JS_GetFunctionId(fn); + if(name) { + JSSTRING_TO_STRING(cx, name, cp, NULL); + fprintf(stdout, "in %s() ", cp); + } + } + fputs("at ",stdout); + fputs(fname, stdout); + if(pc) + fprintf(stdout, ":%u",JS_PCToLineNumber(cx, fs, pc)); + fputs("\n", stdout); + fnum++; + } + continue; + } + if(strncmp(line,"up", 2)==0) { + JSStackFrame *fpn=fp; + + JS_FrameIterator(cx, &fpn); + if(fpn==NULL) { + fputs("No frame above this one...\n",stderr); + continue; + } + fp=fpn; + script=JS_GetFrameScript(cx, fp); + continue; + } + if(strncmp(line,"down", 4)==0) { + JSStackFrame *fpi=NULL; + JSStackFrame *fpp=NULL; + + while(JS_FrameIterator(cx, &fpi)) { + if(fpi==fp) { + if(fpp==NULL) { + fputs("Already at deepest stack frame\n",stderr); + break; + } + fp=fpp; + script=JS_GetFrameScript(cx, fp); + break; + } + fpp=fpi; + } + if(fpi==NULL) + fputs("A strange amd mysterious error occured!\n",stderr); continue; } fputs("Unrecognized command:\n" - "break #### - Sets a breakpoint\n" - "r - Runs the script\n" - "eval <statement> - eval() <statement> in the current frame\n" - "e <statement> - eval() <statement> in the current frame\n" - "clear - Clears pending exceptions (doesn't seem to help)\n" + "break [file:]#### - Sets a breakpoint at line #### in file\n" + " If no file is specified, uses the current one\n" + "r - Runs the script\n" + "eval <statement> - eval() <statement> in the current frame\n" + "e <statement> - eval() <statement> in the current frame\n" + "clear - Clears pending exceptions (doesn't seem to help)\n" + "bt - Prints a backtrace\n" + "backtrace - Alias for bt\n" + "up - Move to the previous stack frame\n" + "down - Move to the next stack frame\n" "\n",stderr); } return DEBUG_CONTINUE; @@ -869,7 +1098,7 @@ static JSTrapStatus trap_handler(JSContext *cx, JSScript *script, jsbytecode *pc fputs("Breakpoint reached\n",stdout); - switch(debug_prompt(script)) { + switch(debug_prompt(cx, script)) { case DEBUG_CONTINUE: return JSTRAP_CONTINUE; case DEBUG_EXIT: @@ -884,7 +1113,7 @@ static JSTrapStatus throw_handler(JSContext *cx, JSScript *script, jsbytecode *p fputs("Exception thrown\n",stdout); - switch(debug_prompt(script)) { + switch(debug_prompt(cx, script)) { case DEBUG_CONTINUE: return JSTRAP_CONTINUE; case DEBUG_EXIT: @@ -1003,6 +1232,8 @@ long js_exec(const char *fname, char** args) fclose(fp); start=xp_timer(); + if(debugger) + init_debugger(js_runtime, js_cx); if((js_script=JS_CompileScript(js_cx, js_glob, js_buf, js_buflen, fname==NULL ? NULL : path, 1))==NULL) { lprintf(LOG_ERR,"!Error compiling script from %s",path); return(-1); @@ -1014,11 +1245,9 @@ long js_exec(const char *fname, char** args) js_PrepareToExecute(js_cx, js_glob, fname==NULL ? NULL : path, orig_cwd); start=xp_timer(); - if((!debugger) || debug_prompt(JS_GetScriptFromObject(js_script))==DEBUG_CONTINUE) { - if(debugger) - JS_SetThrowHook(js_runtime, throw_handler, NULL); - JS_ExecuteScript(js_cx, js_glob, js_script, &rval); - } + if(debugger) + debug_prompt(js_cx, JS_GetScriptFromObject(js_script)); + JS_ExecuteScript(js_cx, js_glob, js_script, &rval); JS_GetProperty(js_cx, js_glob, "exit_code", &rval); if(rval!=JSVAL_VOID && JSVAL_IS_NUMBER(rval)) { char *p; @@ -1319,8 +1548,6 @@ int main(int argc, char **argv, char** environ) } fprintf(statfp,"\n"); - if(debugger) - JS_SetDebugMode(js_cx, JS_TRUE); result=js_exec(module,&argv[argn]); JS_RemoveObjectRoot(js_cx, &js_glob); JS_ENDREQUEST(js_cx); -- GitLab