Commit 329a03a3 authored by deuce's avatar deuce
Browse files

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.
parent a5d61450
......@@ -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);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment