THE SIMPLE DEBUGGER
For this simple debugger, it is assumed that the application will be a console-based application so standard C functions such as printf are available. The output from the script and the debugger will be intermixed on this console. That suffices for a simple debugger. The implementation of this debugger will help you to understand how to write debuggers and debugger-like programs to add to your application in a simple way. The ScriptEase debugger is a much more complex example of a full debugger.
This debugger is able to debug multiple scripts, such as a multi-fiber application (described in "Fibers and Threads") simultaneously because it store all information for each context in that context.
The simple debugger function follows. To use it in your application, simply define an seContinueFunc callback in your application and call this function. A ScriptEase ISDK sample application using this debugger can be found as the debug sample.
#define DBG_NEXT SE_MEM("debugger_next_depth")
#define DBG_OFF SE_MEM("debugger_off")
#define BREAK_LIST SE_MEM("debugger breakpoints")
/* A place to save commands between calls */
static sechar commandbuf2[256];
sebool
simple_text_debugger(secontext se)
{
sechar linebuf[256];
sechar commandbuf[256];
sebool is_break = FALSE;
seobject list;
int frame = 0; /* current stack frame */
/* Make sure our breakpoint list object exists and is an
* object
*/
if( seGetType(se,SE_SERVICES,BREAK_LIST)!=SE_TYPE_OBJECT )
{
sePutObject(se,SE_SERVICES,BREAK_LIST,seMakeObject(se));
}
list = seGetObject(se,SE_SERVICES,BREAK_LIST);
sprintf_sechar(linebuf,"%s:%g",
seGetString(se,SE_STACK_INFO(0),SE_SI_FILENAME,NULL),
seGetNumber(se,SE_STACK_INFO(0),SE_SI_LINENUM));
if( seExists(se,list,SE_UNIMEM(linebuf)) )
is_break = TRUE;
/* When executing a print statement, breakpoints should
* not be called, I think. I can see it both ways, but this
* way makes more sense to me.
*/
if(seExists(se,SE_SERVICES,DBG_OFF) )
return TRUE;
if( is_break && seExists(se,SE_SERVICES,DBG_NEXT) )
printf_sechar("\nBreakpoint reached.\n\n");
if(seExists(se,SE_SERVICES,DBG_NEXT) )
{
if(seGetNumber(se,SE_STACK_INFO(0),SE_SI_DEPTH)>
seGetNumber(se,SE_SERVICES,DBG_NEXT) &&
!is_break )
{
/* a higher depth means we've gone into a function, wait
* until we return to this depth or less. However, if
* we reach a breakpoint, we always stop.
*/
return TRUE;
}
/* we've stopped, get rid of the marker */
seDelete(se,SE_SERVICES,DBG_NEXT);
}
do
{
secharptr loc;
printf_sechar(UNISTR("DBG %s> "),linebuf);
fgets_sechar(commandbuf,sizeof(commandbuf)/sizeof(sechar),
stdin);
loc = SECHARPTR_OFFSET(commandbuf,
strlen_sechar(commandbuf)-1);
if(SECHARPTR_GETC(loc)=='\n' )
SECHARPTR_PUTC(loc,'\0');
if( strlen_sechar(commandbuf)>0 )
{
strcpy_sechar(commandbuf2,commandbuf);
}
else
{
/* retrieve last command */
strcpy_sechar(commandbuf,commandbuf2);
}
if( strcmp_sechar(commandbuf,UNISTR("step"))==0 )
{
/* step one statement, including going into functions */
return TRUE;
}
else if( strcmp_sechar(commandbuf,UNISTR("abort"))==0 )
{
/* break the program, note that we retain the
* breakpoints in the services object for more runs,
* users usually want to keep breakpoints around.
*/
seThrow(se,UNISTR(Script aborted by user.));
return FALSE;
}
else if( strcmp_sechar(commandbuf,UNISTR("next"))==0 )
{
/* step to next statement, over any function call */
sePutNumber(se,SE_SERVICES,DBG_NEXT,
seGetNumber(se,SE_STACK_INFO(0),SE_SI_DEPTH));
return TRUE;
}
else if( strcmp_sechar(commandbuf,UNISTR("cont"))==0 )
{
/* keep stepping, since depth never is -1, this does it
*/
sePutNumber(se,SE_SERVICES,DBG_NEXT,-1);
return TRUE;
}
else if( strncmp_sechar(commandbuf,UNISTR("print "),6)==0 )
{
struct seEvalParams params;
/* print an arbitrary expression
* don't debug this evaluation, it doesn't matter what
* we put here, it just must exist
*/
sePutNull(se,SE_SERVICES,DBG_OFF);
/* evaluate the expression */
memset(¶ms,0,sizeof(params));
/* set our scope chain to search in the given frame */
params.scopestart =
seGetObject(se,SE_STACK_INFO(frame),SE_SI_SCOPECHAIN);
params.global =
seGetObject(se,SE_STACK_INFO(frame),SE_SI_GLOBAL);
params.default_this =
seGetObject(se,SE_STACK_INFO(frame),SE_SI_THIS);
/* We are specifying our own scope chain, so no need
* inherit existing ones. We don't need any new
* libraries, because the old ones are in the scope
* chain we are giving.
*/
seEval(se,SECHARPTR_OFFSET(commandbuf,6),SE_TEXT,
NULL,NULL,SE_NO_INHERIT|SE_NO_LIBRARIES,¶ms);
sePutBool(se,SE_RETURN,SE_ERROR,FALSE);
/* turn debugger back on */
seDelete(se,SE_SERVICES,DBG_OFF);
printf_sechar(UNISTR("%s\n"),
seGetString(se,SE_RETURN,SE_VALUE,NULL));
/* In case an error */
sePutBool(se,SE_RETURN,SE_VALUE,FALSE);
continue;
}
else if( strncmp_sechar(commandbuf,UNISTR("break "),6)==0 )
{
/* The presence of the member signals a break point */
sePutNull(se,list,
SE_UNIMEM(SECHARPTR_OFFSET(commandbuf,6)));
continue;
}
else if( strncmp_sechar(commandbuf,UNISTR("remove "),7)==0)
{
/* The presence of the member signals a break point,
* so remove it
*/
seDelete(se,list,
SE_UNIMEM(SECHARPTR_OFFSET(commandbuf,7)));
continue;
}
else if( strcmp_sechar(commandbuf,UNISTR("list"))==0 )
{
seconstcharptr name;
sememcount i;
if( seObjectMemberCount(se,list) )
{
printf("\nBreakpoints:\n");
for( i=0; i < seObjectMemberCount(se,list); i++ )
{
name =
seObjectMemberName(se,list,SE_INDEX(i),NULL);
printf("breakpoint #%d at %s\n",i,name);
}
printf("\n");
}
else
{
printf("\nNo breakpoints.\n\n");
}
continue;
}
else if( strncmp_sechar(commandbuf,UNISTR("frame "),6)==0 )
{
int fr =
strtol_sechar(SECHARPTR_OFFSET(commandbuf,6),NULL,10);
if( fr < 0 ||
fr > seGetNumber(se,SE_STACK_INFO(0),SE_SI_DEPTH) )
{
printf_sechar(UNISTR("\nNo such frame.\n\n"));
}
else
{
frame = fr;
sprintf_sechar(linebuf,"%s:%g",
seGetString(se,SE_STACK_INFO(frame),
SE_SI_FILENAME,NULL),
seGetNumber(se,SE_STACK_INFO(frame),
SE_SI_LINENUM));
}
continue;
}
else if( strcmp_sechar(commandbuf,UNISTR("help"))==0 )
{
/* simple help */
printf_sechar(UNISTR("\n"));
printf_sechar(UNISTR("cont ")
UNISTR("continue running program\n"));
printf_sechar(UNISTR("step ")
UNISTR(step (into functions)\n"));
printf_sechar(UNISTR("next ")
UNISTR(step (over functions)\n"));
printf_sechar(UNISTR("abort ")
UNISTR(stop script execution\n"));
printf_sechar(UNISTR("print <expr> ")
UNISTR(evaluate expression\n"));
printf_sechar(UNISTR("break file:line ")
UNISTR(set breakpoint\n"));
printf_sechar(UNISTR("remove file:line ")
UNISTR(remove breakpoint\n"));
printf_sechar(UNISTR("list ")
UNISTR(list breakpoints\n"));
printf_sechar(UNISTR("frame <num> ")
UNISTR(change stack frame\n"));
printf_sechar(UNISTR("help - ")
UNISTR(list all commands\n\n"));
continue;
}
printf_sechar(UNISTR("Unrecognized command %s, ")
UNISTR("use 'abort' to exit program.\n"),
commandbuf);
} while( 1 );
}
INTEGRATING THE SCRIPTEASE DEBUGGER