Observe that the CDebugMgr class used in the examples below doesn't exist. It is only used as an abstraction to avoid having to write fictional debug routines.
// An example line callback void DebugLineCallback(asIScriptContext *ctx, CDebugMgr *dbg) { // Determine if we have reached a break point const char *scriptSection; int line = ctx->GetLineNumber(0, 0, &scriptSection); asIScriptFunction *function = ctx->GetFunction(); // Now let the debugger check if a breakpoint has been set here if( dbg->IsBreakpoint(scriptSection, line, function) ) { // A break point has been reached so the execution of the script should be suspended ctx->Suspend(); } }
The line callback is set on the context with the following call:
// Set the line callback with the address of the debug manager as parameter ctx->SetLineCallback(asFUNCTION(DebugLineCallback), dbg, asCALL_CDECL);
When the line callback suspends the execution the context's Execute function will return with the code asEXECUTION_SUSPENDED. The application can then go into a special message loop where the debug routines can be handled, e.g. to view the call stack, examine variables, etc. Once the execution should continue, simply call the Execute method again to resume it.
An alternative to suspending the script execution might be to start the message loop directly within the line callback, in which case resuming the execution is done simply by returning from the line callback function. Which is the easiest to implement depends on how you have implemented your application.
Here's an example of how the entire call stack can be printed:
void PrintCallstack(asIScriptContext *ctx) { // Show the call stack for( asUINT n = 0; n < ctx->GetCallstackSize(); n++ ) { asIScriptFunction *func; const char *scriptSection; int line, column; func = ctx->GetFunction(n); line = ctx->GetLineNumber(n, &column, &scriptSection); printf("%s:%s:%d,%d\n", scriptSection, func->GetDeclaration(), line, column); } }
Here is an example for how the variables may be printed:
void PrintVariables(asIScriptContext *ctx, asUINT stackLevel) { asIScriptEngine *engine = ctx->GetEngine(); // First print the this pointer if this is a class method int typeId = ctx->GetThisTypeId(stackLevel); void *varPointer = ctx->GetThisPointer(stackLevel); if( typeId ) { printf(" this = 0x%x\n", varPointer); } // Print the value of each variable, including parameters int numVars = ctx->GetVarCount(stackLevel); for( int n = 0; n < numVars; n++ ) { int typeId = ctx->GetVarTypeId(n, stackLevel); void *varPointer = ctx->GetAddressOfVar(n, stackLevel); if( typeId == asTYPEID_INT32 ) { printf(" %s = %d\n", ctx->GetVarDeclaration(n, stackLevel), *(int*)varPointer); } else if( typeId == asTYPEID_FLOAT ) { printf(" %s = %f\n", ctx->GetVarDeclaration(n, stackLevel), *(float*)varPointer); } else if( typeId & asTYPEID_SCRIPT_OBJECT ) { asIScriptObject *obj = (asIScriptObject*)varPointer; if( obj ) printf(" %s = {...}\n", ctx->GetVarDeclaration(n, stackLevel)); else printf(" %s = <null>\n", ctx->GetVarDeclaration(n, stackLevel)); } else if( typeId == engine->GetTypeIdByDecl("string") ) { string *str = (string*)varPointer; if( str ) printf(" %s = '%s'\n", ctx->GetVarDeclaration(n, stackLevel), str->c_str()); else printf(" %s = <null>\n", ctx->GetVarDeclaration(n, stackLevel)); } else { printf(" %s = {...}\n", ctx->GetVarDeclaration(n, stackLevel)); } } }
The above code is only an example to give an idea of how it can be done. It is not complete and only recognizes a few types. To make it useful it would have to be expanded to recognize all types, and perhaps add some generic way of converting an object to human readable string for printing.
For script objects that conversion can be done by enumerating the members of an object through the asIScriptObject interface.
The debugger may also need to be able to inspect the global variables that the functions access. As the global variables are stored in the module, there is the place to look for them. The asIScriptModule interface can be obtained by querying the module name from the function, and then getting the module pointer from the engine. Once the module is determined the global variables are enumerated much the same way as in the example above, except that the appropriate methods on the asIScriptModule interface is used instead.