#include "tier0/basetypes.h" #include "tier1/strtools.h" #include "tier0/threadtools.h" #include #include #include #include "sqrdbg.h" #include "sqdbgserver.h" #if defined(VSCRIPT_DLL_EXPORT) || defined(VSQUIRREL_TEST) #include "memdbgon.h" #endif #ifndef _UNICODE #define scstrcpy strcpy #else #define scstrcpy wcscpy #endif struct XMLEscape{ const SQChar c; const SQChar *esc; }; #define SQDBG_DEBUG_HOOK _SC("_sqdbg_debug_hook_") #define SQDBG_ERROR_HANDLER _SC("_sqdbg_error_handler_") XMLEscape g_escapes[]={ {_SC('<'),_SC("<")},{'>',_SC(">")},{_SC('&'),_SC("&")},{_SC('\''),_SC("'")},{_SC('\"'),_SC(""")},{_SC('\n'),_SC(""n")},{_SC('\r'),_SC(""r")},{NULL,NULL} }; const SQChar *IntToString(int n) { static SQChar temp[256]; scsprintf(temp,_SC("%d"),n); return temp; } SQInteger debug_hook(HSQUIRRELVM v); SQInteger error_handler(HSQUIRRELVM v); SQInteger beginelement(HSQUIRRELVM v) { SQUserPointer up; const SQChar *name; sq_getuserpointer(v,-1,&up); SQDbgServer *self = (SQDbgServer*)up; sq_getuserpointer(v,-1,&up); sq_getstring(v,2,&name); self->BeginElement(name); return 0; } SQInteger endelement(HSQUIRRELVM v) { SQUserPointer up; const SQChar *name; sq_getuserpointer(v,-1,&up); SQDbgServer *self = (SQDbgServer*)up; sq_getuserpointer(v,-1,&up); sq_getstring(v,2,&name); self->EndElement(name); return 0; } SQInteger attribute(HSQUIRRELVM v) { SQUserPointer up; const SQChar *name,*value; sq_getuserpointer(v,-1,&up); SQDbgServer *self = (SQDbgServer*)up; sq_getuserpointer(v,-1,&up); sq_getstring(v,2,&name); sq_getstring(v,3,&value); self->Attribute(name,value); return 0; } const SQChar *EscapeXMLString(HSQUIRRELVM v,const SQChar *s) { SQChar *temp=sq_getscratchpad(v,((int)scstrlen(s)*6) + sizeof (SQChar)); SQChar *dest=temp; while(*s!=_SC('\0')){ int i=0; bool escaped=false; while(g_escapes[i].esc!=NULL){ if(*s==g_escapes[i].c){ scstrcpy(dest,g_escapes[i].esc); dest+=scstrlen(g_escapes[i].esc); escaped=true; break; } i++; } if(!escaped){*dest=*s;dest++;} s++; } *dest=_SC('\0'); return temp; } SQDbgServer::SQDbgServer(HSQUIRRELVM v) { _ready = false; _nestedcalls = 0; _autoupdate = false; _v = v; _state = eDBG_Running; _accept = INVALID_SOCKET; _endpoint = INVALID_SOCKET; _maxrecursion = 10; sq_resetobject(&_debugroot); sq_resetobject(&_serializefunc); } SQDbgServer::~SQDbgServer() { sq_pushnull(_v); sq_setdebughook(_v); sq_pushnull(_v); sq_seterrorhandler(_v); sq_release(_v,&_debugroot); #ifndef _X360 if(_accept != INVALID_SOCKET) sqdbg_closesocket(_accept); if(_endpoint != INVALID_SOCKET) sqdbg_closesocket(_endpoint); #endif } bool SQDbgServer::Init() { //creates an environment table for the debugger sq_newtable(_v); sq_getstackobj(_v,-1,&_debugroot); sq_addref(_v,&_debugroot); //creates a emptyslot to store the watches sq_pushstring(_v,_SC("watches"),-1); sq_pushnull(_v); sq_createslot(_v,-3); sq_pushstring(_v,_SC("beginelement"),-1); sq_pushuserpointer(_v,this); sq_newclosure(_v,beginelement,1); sq_setparamscheck(_v,2,_SC(".s")); sq_createslot(_v,-3); sq_pushstring(_v,_SC("endelement"),-1); sq_pushuserpointer(_v,this); sq_newclosure(_v,endelement,1); sq_setparamscheck(_v,2,_SC(".s")); sq_createslot(_v,-3); sq_pushstring(_v,_SC("attribute"),-1); sq_pushuserpointer(_v,this); sq_newclosure(_v,attribute,1); sq_setparamscheck(_v,3,_SC(".ss")); sq_createslot(_v,-3); sq_pop(_v,1); //stores debug hook and error handler in the registry sq_pushregistrytable(_v); sq_pushstring(_v,SQDBG_DEBUG_HOOK,-1); sq_pushuserpointer(_v,this); sq_newclosure(_v,debug_hook,1); sq_createslot(_v,-3); sq_pushstring(_v,SQDBG_ERROR_HANDLER,-1); sq_pushuserpointer(_v,this); sq_newclosure(_v,error_handler,1); sq_createslot(_v,-3); sq_pop(_v,1); //sets the error handlers SetErrorHandlers(); return true; } bool SQDbgServer::IsConnected() { #ifndef _GAMECONSOLE if ( _endpoint != INVALID_SOCKET ) { fd_set set; #if defined(_WIN32) || defined(_PS3) set.fd_count = 1; set.fd_array[0] = _endpoint; #else FD_SET( _endpoint, &set ); #endif timeval timeVal = { 0,0 }; if ( select( 0, &set, NULL, NULL, &timeVal ) != SOCKET_ERROR ) { return true; } DevMsg( "Script debugger disconnected\n" ); } #endif return false; } bool SQDbgServer::ReadMsg() { return false; } void SQDbgServer::BusyWait() { while( !ReadMsg() ) ThreadSleep(0); } void SQDbgServer::SendChunk(const SQChar *chunk) { #ifndef _GAMECONSOLE char *buf=NULL; int buf_len=0; #ifdef _UNICODE buf_len=(int)scstrlen(chunk)+1; buf=(char *)sq_getscratchpad(_v,(buf_len)*3); wcstombs((char *)buf,chunk,buf_len); #else buf_len=(int)scstrlen(chunk); buf=(char *)chunk; #endif send(_endpoint,(const char*)buf,(int)strlen((const char *)buf),0); #endif } void SQDbgServer::Terminated() { BeginElement(_SC("terminated")); EndElement(_SC("terminated")); ThreadSleep(200); } void SQDbgServer::Hook(int type,int line,const SQChar *src,const SQChar *func) { switch(_state){ case eDBG_Running: if(type==_SC('l') && _breakpoints.size()) { BreakPointSetItor itr = _breakpoints.find(BreakPoint(line,src)); if(itr != _breakpoints.end()) { Break(line,src,_SC("breakpoint")); BreakExecution(); } } break; case eDBG_Suspended: _nestedcalls=0; case eDBG_StepOver: switch(type){ case _SC('l'): if(_nestedcalls==0) { Break(line,src,_SC("step")); BreakExecution(); } break; case _SC('c'): _nestedcalls++; break; case _SC('r'): if(_nestedcalls==0){ _nestedcalls=0; }else{ _nestedcalls--; } break; } break; case eDBG_StepInto: switch(type){ case _SC('l'): _nestedcalls=0; Break(line,src,_SC("step")); BreakExecution(); break; } break; case eDBG_StepReturn: switch(type){ case _SC('l'): break; case _SC('c'): _nestedcalls++; break; case _SC('r'): if(_nestedcalls==0){ _nestedcalls=0; _state=eDBG_StepOver; }else{ _nestedcalls--; } break; } break; case eDBG_Disabled: break; } } #define MSG_ID(x,y) ((y<<8)|x) //ab Add Breakpoint //rb Remove Breakpoint //sp Suspend void SQDbgServer::ParseMsg(const char *msg) { switch(*((unsigned short *)msg)){ case MSG_ID('a','b'): { BreakPoint bp; if(ParseBreakpoint(msg+3,bp)){ AddBreakpoint(bp); scprintf(_SC("added bp %d %s\n"),bp._line,bp._src.c_str()); } else scprintf(_SC("error parsing add breakpoint")); } break; case MSG_ID('r','b'): { BreakPoint bp; if(ParseBreakpoint(msg+3,bp)){ RemoveBreakpoint(bp); scprintf(_SC("removed bp %d %s\n"),bp._line,bp._src.c_str()); }else scprintf(_SC("error parsing remove breakpoint")); } break; case MSG_ID('g','o'): if(_state!=eDBG_Running){ _state=eDBG_Running; BeginDocument(); BeginElement(_SC("resumed")); EndElement(_SC("resumed")); EndDocument(); // Send(_SC("\r\n")); scprintf(_SC("go (execution resumed)\n")); } break; case MSG_ID('s','p'): if(_state!=eDBG_Suspended){ _state=eDBG_Suspended; scprintf(_SC("suspend\n")); } break; case MSG_ID('s','o'): if(_state==eDBG_Suspended){ _state=eDBG_StepOver; } break; case MSG_ID('s','i'): if(_state==eDBG_Suspended){ _state=eDBG_StepInto; scprintf(_SC("step into\n")); } break; case MSG_ID('s','r'): if(_state==eDBG_Suspended){ _state=eDBG_StepReturn; scprintf(_SC("step return\n")); } break; case MSG_ID('d','i'): if(_state!=eDBG_Disabled){ _state=eDBG_Disabled; scprintf(_SC("disabled\n")); } break; case MSG_ID('a','w'): { Watch w; if(ParseWatch(msg+3,w)) { AddWatch(w); scprintf(_SC("added watch %d %s\n"),w._id,w._exp.c_str()); } else scprintf(_SC("error parsing add watch")); } break; case MSG_ID('r','w'): { int id; if(ParseRemoveWatch(msg+3,id)) { RemoveWatch(id); scprintf(_SC("added watch %d\n"),id); } else scprintf(_SC("error parsing remove watch")); } break; case MSG_ID('t','r'): { scprintf(_SC("terminate from user\n")); #ifndef _X360 sqdbg_closesocket(_endpoint); #endif _endpoint = INVALID_SOCKET; } break; case MSG_ID('r','d'): scprintf(_SC("ready\n")); _ready=true; break; default: scprintf(_SC("unknown packet")); } } /* see copyright notice in sqrdbg.h */ bool SQDbgServer::ParseBreakpoint(const char *msg,BreakPoint &out) { static char stemp[MAX_BP_PATH]; char *ep=NULL; out._line=strtoul(msg,&ep,16); if(ep==msg || (*ep)!=':')return false; char *dest=stemp; ep++; while((*ep)!='\n' && (*ep)!='\0') { *dest=*ep; dest++;ep++; } *dest='\0'; dest++; *dest='\0'; #ifdef _UNICODE int len=(int)strlen(stemp); SQChar *p=sq_getscratchpad(_v,(SQInteger)(mbstowcs(NULL,stemp,len)+2)*sizeof(SQChar)); size_t destlen=mbstowcs(p,stemp,len); p[destlen]=_SC('\0'); out._src=( V_strrchr( p, '/' ) ) ? V_strrchr( p, '/' ) + 1 : p; #else out._src=( V_strrchr( stemp, '/' ) ) ? V_strrchr( stemp, '/' ) + 1 : stemp; #endif return true; } bool SQDbgServer::ParseWatch(const char *msg,Watch &out) { char *ep=NULL; out._id=strtoul(msg,&ep,16); if(ep==msg || (*ep)!=':')return false; //char *dest=out._src; ep++; while((*ep)!='\n' && (*ep)!='\0') { out._exp.append(1,*ep); ep++; } return true; } bool SQDbgServer::ParseRemoveWatch(const char *msg,int &id) { char *ep=NULL; id=strtoul(msg,&ep,16); if(ep==msg)return false; return true; } void SQDbgServer::BreakExecution() { _state=eDBG_Suspended; while(_state==eDBG_Suspended){ if(SQ_FAILED(sq_rdbg_update(this))) { extern bool g_bSqDbgTerminateScript; g_bSqDbgTerminateScript = true; return; } ThreadSleep(10); } } //COMMANDS void SQDbgServer::AddBreakpoint(BreakPoint &bp) { _breakpoints.insert(bp); BeginDocument(); BeginElement(_SC("addbreakpoint")); Attribute(_SC("line"),IntToString(bp._line)); Attribute(_SC("src"),bp._src.c_str()); EndElement(_SC("addbreakpoint")); EndDocument(); } void SQDbgServer::AddWatch(Watch &w) { _watches.insert(w); } void SQDbgServer::RemoveWatch(int id) { WatchSetItor itor=_watches.find(Watch(id,_SC(""))); if(itor==_watches.end()){ BeginDocument(); BeginElement(_SC("error")); Attribute(_SC("desc"),_SC("the watch does not exists")); EndElement(_SC("error")); EndDocument(); } else{ _watches.erase(itor); scprintf(_SC("removed watch %d\n"),id); } } void SQDbgServer::RemoveBreakpoint(BreakPoint &bp) { BreakPointSetItor itor=_breakpoints.find(bp); if(itor==_breakpoints.end()){ BeginDocument(); BeginElement(_SC("break")); Attribute(_SC("desc"),_SC("the breakpoint doesn't exists")); EndElement(_SC("break")); EndDocument(); } else{ BeginDocument(); BeginElement(_SC("removebreakpoint")); Attribute(_SC("line"),IntToString(bp._line)); Attribute(_SC("src"),bp._src.c_str()); EndElement(_SC("removebreakpoint")); EndDocument(); _breakpoints.erase(itor); } } void SQDbgServer::Break(int line,const SQChar *src,const SQChar *type,const SQChar *error) { if(!error){ BeginDocument(); BeginElement(_SC("break")); Attribute(_SC("line"),IntToString(line)); Attribute(_SC("src"),src); Attribute(_SC("type"),type); SerializeState(); EndElement(_SC("break")); EndDocument(); }else{ BeginDocument(); BeginElement(_SC("break")); Attribute(_SC("line"),IntToString(line)); Attribute(_SC("src"),src); Attribute(_SC("type"),type); Attribute(_SC("error"),error); SerializeState(); EndElement(_SC("break")); EndDocument(); } } void SQDbgServer::SerializeState() { sq_pushnull(_v); sq_setdebughook(_v); sq_pushnull(_v); sq_seterrorhandler(_v); const SQChar *sz; sq_pushobject(_v,_serializefunc); sq_pushobject(_v,_debugroot); sq_pushstring(_v,_SC("watches"),-1); sq_newtable(_v); for(WatchSetItor i=_watches.begin(); i!=_watches.end(); ++i) { sq_pushinteger(_v,i->_id); sq_pushstring(_v,i->_exp.c_str(),(int)i->_exp.length()); sq_createslot(_v,-3); } sq_rawset(_v,-3); if(SQ_SUCCEEDED(sq_call(_v,1,SQTrue,SQTrue))){ if(SQ_SUCCEEDED(sqstd_getblob(_v,-1,(SQUserPointer*)&sz))) SendChunk(sz); } sq_pop(_v,2); SetErrorHandlers(); } void SQDbgServer::SetErrorHandlers() { sq_pushregistrytable(_v); sq_pushstring(_v,SQDBG_DEBUG_HOOK,-1); sq_rawget(_v,-2); sq_setdebughook(_v); sq_pushstring(_v,SQDBG_ERROR_HANDLER,-1); sq_rawget(_v,-2); sq_seterrorhandler(_v); sq_pop(_v,1); } void SQDbgServer::BeginElement(const SQChar *name) { _xmlcurrentement++; // for ( int i = 0; i < _xmlcurrentement; i++ ) // { // printf(" "); // } // printf( "BeginElement( %s )\n", name ); XMLElementState *self = &xmlstate[_xmlcurrentement]; scstrcpy(self->name,name); self->haschildren = false; if(_xmlcurrentement > 0) { XMLElementState *parent = &xmlstate[_xmlcurrentement-1]; if(!parent->haschildren) { SendChunk(_SC(">")); // closes the parent tag parent->haschildren = true; } } _scratchstring.resize(2+scstrlen(name)); scsprintf(&_scratchstring[0],_SC("<%s"),name); SendChunk(&_scratchstring[0]); } void SQDbgServer::Attribute(const SQChar *name,const SQChar *value) { XMLElementState *self = &xmlstate[_xmlcurrentement]; Assert(!self->haschildren); //cannot have attributes if already has children const SQChar *escval = escape_xml(value); _scratchstring.resize(5+scstrlen(name)+scstrlen(escval)); scsprintf(&_scratchstring[0],_SC(" %s=\"%s\""),name,escval); SendChunk(&_scratchstring[0]); } void SQDbgServer::EndElement(const SQChar *name) { // for ( int i = 0; i < _xmlcurrentement; i++ ) // { // printf(" "); // } // printf( "EndElement( %s )\n", name ); XMLElementState *self = &xmlstate[_xmlcurrentement]; Assert(scstrcmp(self->name,name) == 0); if(self->haschildren) { _scratchstring.resize(4+scstrlen(name)); scsprintf(&_scratchstring[0],_SC(""),name); SendChunk(&_scratchstring[0]); } else { SendChunk(_SC("/>")); } _xmlcurrentement--; } void SQDbgServer::EndDocument() { SendChunk(_SC("\r\n")); } //this can be done much better/faster(do we need that?) const SQChar *SQDbgServer::escape_xml(const SQChar *s) { SQChar *temp=sq_getscratchpad(_v,((int)scstrlen(s)*6) + sizeof(SQChar)); SQChar *dest=temp; while(*s!=_SC('\0')){ int i=0; bool escaped=false; while(g_escapes[i].esc!=NULL){ if(*s==g_escapes[i].c){ scstrcpy(dest,g_escapes[i].esc); dest+=scstrlen(g_escapes[i].esc); escaped=true; break; } i++; } if(!escaped){*dest=*s;dest++;} s++; } *dest=_SC('\0'); return temp; }