c76ff0bff54aa93f24dc5e94bcfe885067a1c243
[supertux.git] / src / squirrel / sqdbg / sqdbgserver.cpp
1 #include <squirrel.h>\r
2 #include <assert.h>\r
3 #include <sqstdblob.h>\r
4 #include "sqrdbg.h"\r
5 #include "sqdbgserver.h"\r
6 \r
7 \r
8 #ifndef _UNICODE\r
9 #define scstrcpy strcpy\r
10 #else\r
11 #define scstrcpy wcscpy\r
12 #endif\r
13 struct XMLEscape{\r
14         const SQChar c;\r
15         const SQChar *esc;\r
16 };\r
17 \r
18 #define SQDBG_DEBUG_HOOK _SC("_sqdbg_debug_hook_")\r
19 #define SQDBG_ERROR_HANDLER _SC("_sqdbg_error_handler_")\r
20 \r
21 XMLEscape g_escapes[]={\r
22         {_SC('<'),_SC("&lt;")},{'>',_SC("&gt;")},{_SC('&'),_SC("&amp;")},{_SC('\''),_SC("&apos;")},{_SC('\"'),_SC("&quot;")},{_SC('\n'),_SC("&quot;n")},{_SC('\r'),_SC("&quot;r")},{0, NULL}\r
23 };\r
24 \r
25 const SQChar *IntToString(int n)\r
26 {\r
27         static SQChar temp[256];\r
28         scsprintf(temp,_SC("%d"),n);\r
29         return temp;\r
30 }\r
31 \r
32 int debug_hook(HSQUIRRELVM v);\r
33 int error_handler(HSQUIRRELVM v);\r
34 \r
35 int beginelement(HSQUIRRELVM v)\r
36 {\r
37         SQUserPointer up;\r
38         const SQChar *name;\r
39         sq_getuserpointer(v,-1,&up);\r
40         SQDbgServer *self = (SQDbgServer*)up;\r
41         sq_getuserpointer(v,-1,&up);\r
42         sq_getstring(v,2,&name);\r
43         self->BeginElement(name);\r
44         return 0;\r
45 }\r
46 \r
47 int endelement(HSQUIRRELVM v)\r
48 {\r
49         SQUserPointer up;\r
50         const SQChar *name;\r
51         sq_getuserpointer(v,-1,&up);\r
52         SQDbgServer *self = (SQDbgServer*)up;\r
53         sq_getuserpointer(v,-1,&up);\r
54         sq_getstring(v,2,&name);\r
55         self->EndElement(name);\r
56         return 0;\r
57 }\r
58 \r
59 int attribute(HSQUIRRELVM v)\r
60 {\r
61         SQUserPointer up;\r
62         const SQChar *name,*value;\r
63         sq_getuserpointer(v,-1,&up);\r
64         SQDbgServer *self = (SQDbgServer*)up;\r
65         sq_getuserpointer(v,-1,&up);\r
66         sq_getstring(v,2,&name);\r
67         sq_getstring(v,3,&value);\r
68         self->Attribute(name,value);\r
69         return 0;\r
70 }\r
71 \r
72 const SQChar *EscapeXMLString(HSQUIRRELVM v,const SQChar *s)\r
73 {\r
74         \r
75         SQChar *temp=sq_getscratchpad(v,((int)scstrlen(s)*6) + sizeof(SQChar));\r
76         SQChar *dest=temp;\r
77         while(*s!=_SC('\0')){\r
78                 int i=0;\r
79                 bool escaped=false;\r
80                 while(g_escapes[i].esc!=NULL){\r
81                         if(*s==g_escapes[i].c){\r
82                                 scstrcpy(dest,g_escapes[i].esc);\r
83                                 dest+=scstrlen(g_escapes[i].esc);\r
84                                 escaped=true;\r
85                                 break;\r
86                         }\r
87                         i++;\r
88                 }\r
89                 if(!escaped){*dest=*s;*dest++;}\r
90                 *s++;\r
91         }\r
92         *dest=_SC('\0');\r
93         return temp;\r
94 }\r
95 \r
96 SQDbgServer::SQDbgServer(HSQUIRRELVM v)\r
97 {\r
98         _ready = false;\r
99         _nestedcalls = 0;\r
100         _autoupdate = false;\r
101         _v = v;\r
102         _state = eDBG_Running;\r
103         _accept = INVALID_SOCKET;\r
104         _endpoint = INVALID_SOCKET;\r
105         _maxrecursion = 10;\r
106         sq_resetobject(&_debugroot);\r
107 }\r
108 \r
109 SQDbgServer::~SQDbgServer()\r
110 {\r
111         sq_release(_v,&_debugroot);\r
112         if(_accept != INVALID_SOCKET)\r
113                 sqdbg_closesocket(_accept);\r
114         if(_endpoint != INVALID_SOCKET)\r
115                 sqdbg_closesocket(_endpoint);\r
116 }\r
117 \r
118 bool SQDbgServer::Init()\r
119 {\r
120         //creates  an environment table for the debugger\r
121         \r
122         sq_newtable(_v);\r
123         sq_getstackobj(_v,-1,&_debugroot);\r
124         sq_addref(_v,&_debugroot);\r
125 \r
126         //creates a emptyslot to store the watches\r
127         sq_pushstring(_v,_SC("watches"),-1);\r
128         sq_pushnull(_v);\r
129         sq_createslot(_v,-3);\r
130 \r
131         sq_pushstring(_v,_SC("beginelement"),-1);\r
132         sq_pushuserpointer(_v,this);\r
133         sq_newclosure(_v,beginelement,1);\r
134         sq_setparamscheck(_v,2,_SC(".s"));\r
135         sq_createslot(_v,-3);\r
136 \r
137         sq_pushstring(_v,_SC("endelement"),-1);\r
138         sq_pushuserpointer(_v,this);\r
139         sq_newclosure(_v,endelement,1);\r
140         sq_setparamscheck(_v,2,_SC(".s"));\r
141         sq_createslot(_v,-3);\r
142 \r
143         sq_pushstring(_v,_SC("attribute"),-1);\r
144         sq_pushuserpointer(_v,this);\r
145         sq_newclosure(_v,attribute,1);\r
146         sq_setparamscheck(_v,3,_SC(".ss"));\r
147         sq_createslot(_v,-3);\r
148 \r
149         sq_pop(_v,1);\r
150 \r
151         //stores debug hook and error handler in the registry\r
152         sq_pushregistrytable(_v);\r
153 \r
154         sq_pushstring(_v,SQDBG_DEBUG_HOOK,-1);\r
155         sq_pushuserpointer(_v,this);\r
156         sq_newclosure(_v,debug_hook,1);\r
157         sq_createslot(_v,-3);\r
158         \r
159         sq_pushstring(_v,SQDBG_ERROR_HANDLER,-1);\r
160         sq_pushuserpointer(_v,this);\r
161         sq_newclosure(_v,error_handler,1);\r
162         sq_createslot(_v,-3);\r
163 \r
164         \r
165         sq_pop(_v,1);\r
166 \r
167         //sets the error handlers\r
168         SetErrorHandlers();\r
169         return true;\r
170 }\r
171 \r
172 bool SQDbgServer::ReadMsg()\r
173 {\r
174         return false;\r
175 }\r
176 \r
177 void SQDbgServer::BusyWait()\r
178 {\r
179         while( !ReadMsg() )\r
180                 sleep(0);\r
181 }\r
182 \r
183 void SQDbgServer::SendChunk(const SQChar *chunk)\r
184 {\r
185         char *buf=NULL;\r
186         int buf_len=0;\r
187 #ifdef _UNICODE\r
188         buf_len=(int)scstrlen(chunk)+1;\r
189         buf=(char *)sq_getscratchpad(_v,(buf_len)*3);\r
190         wcstombs((char *)buf,chunk,buf_len);\r
191 #else\r
192         buf_len=(int)scstrlen(chunk);\r
193         buf=(char *)chunk;\r
194 #endif\r
195         send(_endpoint,(const char*)buf,(int)strlen((const char *)buf),0);\r
196 }\r
197 \r
198 \r
199 void SQDbgServer::Terminated()\r
200 {\r
201         BeginElement(_SC("terminated"));\r
202         EndElement(_SC("terminated"));\r
203         ::usleep(200);\r
204 }\r
205 \r
206 void SQDbgServer::Hook(int type,int line,const SQChar *src,const SQChar *func)\r
207 {\r
208         switch(_state){\r
209         case eDBG_Running:\r
210                 if(type==_SC('l') && _breakpoints.size()) {\r
211                         BreakPointSetItor itr = _breakpoints.find(BreakPoint(line,src));\r
212                         if(itr != _breakpoints.end()) {\r
213                                 Break(line,src,_SC("breakpoint"));\r
214                                 BreakExecution();\r
215                         }\r
216                 }\r
217                 break;\r
218         case eDBG_Suspended:\r
219                 _nestedcalls=0;\r
220         case eDBG_StepOver:\r
221                 switch(type){\r
222                 case _SC('l'):\r
223                         if(_nestedcalls==0) {\r
224                                 Break(line,src,_SC("step"));\r
225                                 BreakExecution();\r
226                         }\r
227                         break;\r
228                 case _SC('c'):\r
229                         _nestedcalls++;\r
230                         break;\r
231                 case _SC('r'):\r
232                         if(_nestedcalls==0){\r
233                                 _nestedcalls=0;\r
234                                 \r
235                         }else{\r
236                                 _nestedcalls--;\r
237                         }\r
238                         break;\r
239                 }\r
240                 break;\r
241         case eDBG_StepInto:\r
242                 switch(type){\r
243                 case _SC('l'):\r
244                         _nestedcalls=0;\r
245                         Break(line,src,_SC("step"));\r
246                         BreakExecution();\r
247                         break;\r
248                 \r
249                 }\r
250                 break;\r
251         case eDBG_StepReturn:\r
252                 switch(type){\r
253                 case _SC('l'):\r
254                         break;\r
255                 case _SC('c'):\r
256                         _nestedcalls++;\r
257                         break;\r
258                 case _SC('r'):\r
259                         if(_nestedcalls==0){\r
260                                 _nestedcalls=0;\r
261                                 _state=eDBG_StepOver;\r
262                         }else{\r
263                                 _nestedcalls--;\r
264                         }\r
265                         \r
266                         break;\r
267                 }\r
268                 break;\r
269         case eDBG_Disabled:\r
270                 break;\r
271         }\r
272 }\r
273 \r
274 \r
275 #define MSG_ID(x,y) ((y<<8)|x)\r
276 //ab Add Breakpoint\r
277 //rb Remove Breakpoint\r
278 //sp Suspend\r
279 void SQDbgServer::ParseMsg(const char *msg)\r
280 {\r
281         \r
282         switch(*((unsigned short *)msg)){\r
283                 case MSG_ID('a','b'): {\r
284                         BreakPoint bp;\r
285                         if(ParseBreakpoint(msg+3,bp)){\r
286                                 AddBreakpoint(bp);\r
287                                 scprintf(_SC("added bp %d %s\n"),bp._line,bp._src.c_str());\r
288                         }\r
289                         else\r
290                                 scprintf(_SC("error parsing add breakpoint"));\r
291                                                          }\r
292                         break;\r
293                 case MSG_ID('r','b'): {\r
294                         BreakPoint bp;\r
295                         if(ParseBreakpoint(msg+3,bp)){\r
296                                 RemoveBreakpoint(bp);\r
297                                 scprintf(_SC("removed bp %d %s\n"),bp._line,bp._src.c_str());\r
298                         }else\r
299                                 scprintf(_SC("error parsing remove breakpoint"));\r
300                                                         }\r
301                         break;\r
302                 case MSG_ID('g','o'):\r
303                         if(_state!=eDBG_Running){\r
304                                 _state=eDBG_Running;\r
305                                 BeginDocument();\r
306                                         BeginElement(_SC("resumed"));\r
307                                         EndElement(_SC("resumed"));\r
308                                 EndDocument();\r
309 //                              Send(_SC("<resumed/>\r\n"));\r
310                                 scprintf(_SC("go (execution resumed)\n"));\r
311                         }\r
312                         break;\r
313                 case MSG_ID('s','p'):\r
314                         if(_state!=eDBG_Suspended){\r
315                                 _state=eDBG_Suspended;\r
316                                 scprintf(_SC("suspend\n"));\r
317                         }\r
318                         break;\r
319                 case MSG_ID('s','o'):\r
320                         if(_state==eDBG_Suspended){\r
321                                 _state=eDBG_StepOver;\r
322                         }\r
323                         break;\r
324                 case MSG_ID('s','i'):\r
325                         if(_state==eDBG_Suspended){\r
326                                 _state=eDBG_StepInto;\r
327                                 scprintf(_SC("step into\n"));\r
328                         }\r
329                         break;\r
330                 case MSG_ID('s','r'):\r
331                         if(_state==eDBG_Suspended){\r
332                                 _state=eDBG_StepReturn;\r
333                                 scprintf(_SC("step return\n"));\r
334                         }\r
335                         break;\r
336                 case MSG_ID('d','i'):\r
337                         if(_state!=eDBG_Disabled){\r
338                                 _state=eDBG_Disabled;\r
339                                 scprintf(_SC("disabled\n"));\r
340                         }\r
341                         break;\r
342                 case MSG_ID('a','w'): {\r
343                         Watch w;\r
344                         if(ParseWatch(msg+3,w))\r
345                         {\r
346                                 AddWatch(w);\r
347                                 scprintf(_SC("added watch %d %s\n"),w._id,w._exp.c_str());\r
348                         }\r
349                         else\r
350                                 scprintf(_SC("error parsing add watch"));\r
351                                                                 }\r
352                         break;\r
353                 case MSG_ID('r','w'): {\r
354                         int id;\r
355                         if(ParseRemoveWatch(msg+3,id))\r
356                         {\r
357                                 RemoveWatch(id);\r
358                                 scprintf(_SC("added watch %d\n"),id);\r
359                         }\r
360                         else\r
361                                 scprintf(_SC("error parsing remove watch"));\r
362                                                                 }\r
363                         break;\r
364                 case MSG_ID('t','r'):\r
365                         scprintf(_SC("terminate from user\n"));\r
366                         break;\r
367                 case MSG_ID('r','d'):\r
368                         scprintf(_SC("ready\n"));\r
369                         _ready=true;\r
370                         break;\r
371                 default:\r
372                         scprintf(_SC("unknown packet"));\r
373 \r
374         }\r
375 }\r
376 \r
377 /*\r
378         see copyright notice in sqrdbg.h\r
379 */\r
380 bool SQDbgServer::ParseBreakpoint(const char *msg,BreakPoint &out)\r
381 {\r
382         static char stemp[MAX_BP_PATH];\r
383         char *ep=NULL;\r
384         out._line=strtoul(msg,&ep,16);\r
385         if(ep==msg || (*ep)!=':')return false;\r
386         \r
387         char *dest=stemp;\r
388         ep++;\r
389         while((*ep)!='\n' && (*ep)!='\0')\r
390         {\r
391                 *dest=*ep;\r
392                 *dest++;*ep++;\r
393         }\r
394         *dest='\0';\r
395         *dest++;\r
396         *dest='\0';\r
397 #ifdef _UNICODE\r
398         int len=(int)strlen(stemp);\r
399         SQChar *p=sq_getscratchpad(_v,(SQInteger)(mbstowcs(NULL,stemp,len)+2)*sizeof(SQChar));\r
400         size_t destlen=mbstowcs(p,stemp,len);\r
401         p[destlen]=_SC('\0');\r
402         out._src=p;\r
403 #else\r
404         out._src=stemp;\r
405 #endif\r
406         return true;\r
407 }\r
408 \r
409 bool SQDbgServer::ParseWatch(const char *msg,Watch &out)\r
410 {\r
411         char *ep=NULL;\r
412         out._id=strtoul(msg,&ep,16);\r
413         if(ep==msg || (*ep)!=':')return false;\r
414 \r
415         //char *dest=out._src;\r
416         ep++;\r
417         while((*ep)!='\n' && (*ep)!='\0')\r
418         {\r
419                 out._exp.append(1,*ep);\r
420                 *ep++;\r
421         }\r
422         return true;\r
423 }\r
424 \r
425 bool SQDbgServer::ParseRemoveWatch(const char *msg,int &id)\r
426 {\r
427         char *ep=NULL;\r
428         id=strtoul(msg,&ep,16);\r
429         if(ep==msg)return false;\r
430         return true;\r
431 }\r
432 \r
433 \r
434 void SQDbgServer::BreakExecution()\r
435 {\r
436         _state=eDBG_Suspended;\r
437         while(_state==eDBG_Suspended){\r
438                 if(SQ_FAILED(sq_rdbg_update(this)))\r
439                         exit(0);\r
440                 usleep(10);\r
441         }\r
442 }\r
443 \r
444 //COMMANDS\r
445 void SQDbgServer::AddBreakpoint(BreakPoint &bp)\r
446 {\r
447         _breakpoints.insert(bp);\r
448         BeginDocument();\r
449                 BeginElement(_SC("addbreakpoint"));\r
450                         Attribute(_SC("line"),IntToString(bp._line));\r
451                         Attribute(_SC("src"),bp._src.c_str());\r
452                 EndElement(_SC("addbreakpoint"));\r
453         EndDocument();\r
454 }\r
455 \r
456 void SQDbgServer::AddWatch(Watch &w)\r
457 {\r
458         _watches.insert(w);\r
459 }\r
460 \r
461 void SQDbgServer::RemoveWatch(int id)\r
462 {\r
463         WatchSetItor itor=_watches.find(Watch(id,_SC("")));\r
464         if(itor==_watches.end()){\r
465                 BeginDocument();\r
466                 BeginElement(_SC("error"));\r
467                         Attribute(_SC("desc"),_SC("the watch does not exists"));\r
468                 EndElement(_SC("error"));\r
469         EndDocument();\r
470         }\r
471         else{\r
472                 _watches.erase(itor);\r
473                 scprintf(_SC("removed watch %d\n"),id);\r
474         }\r
475 }\r
476 \r
477 void SQDbgServer::RemoveBreakpoint(BreakPoint &bp)\r
478 {\r
479         BreakPointSetItor itor=_breakpoints.find(bp);\r
480         if(itor==_breakpoints.end()){\r
481                 BeginDocument();\r
482                         BeginElement(_SC("break"));\r
483                                 Attribute(_SC("desc"),_SC("the breakpoint doesn't exists"));\r
484                         EndElement(_SC("break"));\r
485                 EndDocument();\r
486         }\r
487         else{\r
488                 BeginDocument();\r
489                         BeginElement(_SC("removebreakpoint"));\r
490                                 Attribute(_SC("line"),IntToString(bp._line));\r
491                                 Attribute(_SC("src"),bp._src.c_str());\r
492                         EndElement(_SC("removebreakpoint"));\r
493                 EndDocument();\r
494                 _breakpoints.erase(itor);\r
495         }\r
496 }\r
497 \r
498 void SQDbgServer::Break(int line,const SQChar *src,const SQChar *type,const SQChar *error)\r
499 {\r
500         if(!error){\r
501                 BeginDocument();\r
502                         BeginElement(_SC("break"));\r
503                                 Attribute(_SC("line"),IntToString(line));\r
504                                 Attribute(_SC("src"),src);\r
505                                 Attribute(_SC("type"),type);\r
506                                 SerializeState();\r
507                         EndElement(_SC("break"));\r
508                 EndDocument();\r
509         }else{\r
510                 BeginDocument();\r
511                         BeginElement(_SC("break"));\r
512                                 Attribute(_SC("line"),IntToString(line));\r
513                                 Attribute(_SC("src"),src);\r
514                                 Attribute(_SC("type"),type);\r
515                                 Attribute(_SC("error"),error);\r
516                                 SerializeState();\r
517                         EndElement(_SC("break"));\r
518                 EndDocument();\r
519         }\r
520 }\r
521 \r
522 void SQDbgServer::SerializeState()\r
523 {\r
524         sq_pushnull(_v);\r
525         sq_setdebughook(_v);\r
526         sq_pushnull(_v);\r
527         sq_seterrorhandler(_v);\r
528         const SQChar *sz;\r
529         sq_pushobject(_v,_serializefunc);\r
530         sq_pushobject(_v,_debugroot);\r
531         sq_pushstring(_v,_SC("watches"),-1);\r
532         sq_newtable(_v);\r
533         for(WatchSetItor i=_watches.begin(); i!=_watches.end(); ++i)\r
534         {\r
535                 sq_pushinteger(_v,i->_id);\r
536                 sq_pushstring(_v,i->_exp.c_str(),(int)i->_exp.length());\r
537                 sq_createslot(_v,-3);\r
538         }\r
539         sq_rawset(_v,-3);\r
540         if(SQ_SUCCEEDED(sq_call(_v,1,SQTrue,SQTrue))){\r
541                 if(SQ_SUCCEEDED(sqstd_getblob(_v,-1,(SQUserPointer*)&sz)))\r
542                         SendChunk(sz);\r
543         }\r
544         sq_pop(_v,2);\r
545         \r
546         SetErrorHandlers();\r
547 }\r
548 \r
549 \r
550 void SQDbgServer::SetErrorHandlers()\r
551 {\r
552         sq_pushregistrytable(_v);\r
553         sq_pushstring(_v,SQDBG_DEBUG_HOOK,-1);\r
554         sq_rawget(_v,-2);\r
555         sq_setdebughook(_v);\r
556         sq_pushstring(_v,SQDBG_ERROR_HANDLER,-1);\r
557         sq_rawget(_v,-2);\r
558         sq_seterrorhandler(_v);\r
559         sq_pop(_v,1);\r
560 }\r
561 \r
562 void SQDbgServer::BeginElement(const SQChar *name)\r
563 {\r
564         _xmlcurrentement++;\r
565         XMLElementState *self = &xmlstate[_xmlcurrentement];\r
566         scstrcpy(self->name,name);\r
567         self->haschildren = false;\r
568         if(_xmlcurrentement > 0) {\r
569                 XMLElementState *parent = &xmlstate[_xmlcurrentement-1];\r
570                 if(!parent->haschildren) {\r
571                         SendChunk(_SC(">")); // closes the parent tag\r
572                         parent->haschildren = true;\r
573                 }\r
574         }\r
575         _scratchstring.resize(2+scstrlen(name));\r
576         scsprintf(&_scratchstring[0],_SC("<%s"),name);\r
577         SendChunk(&_scratchstring[0]);\r
578 }\r
579 \r
580 void SQDbgServer::Attribute(const SQChar *name,const SQChar *value)\r
581 {\r
582         XMLElementState *self = &xmlstate[_xmlcurrentement];\r
583         assert(!self->haschildren); //cannot have attributes if already has children\r
584         const SQChar *escval = escape_xml(value);\r
585         _scratchstring.resize(5+scstrlen(name)+scstrlen(escval));\r
586         scsprintf(&_scratchstring[0],_SC(" %s=\"%s\""),name,escval);\r
587         SendChunk(&_scratchstring[0]);\r
588 }\r
589 \r
590 void SQDbgServer::EndElement(const SQChar *name)\r
591 {\r
592         XMLElementState *self = &xmlstate[_xmlcurrentement];\r
593         assert(scstrcmp(self->name,name) == 0);\r
594         if(self->haschildren) {\r
595                 _scratchstring.resize(4+scstrlen(name));\r
596                 scsprintf(&_scratchstring[0],_SC("</%s>"),name);\r
597                 SendChunk(&_scratchstring[0]);\r
598                 \r
599         }\r
600         else {\r
601                 SendChunk(_SC("/>"));\r
602         }\r
603         _xmlcurrentement--;\r
604 }\r
605 \r
606 void SQDbgServer::EndDocument()\r
607 {\r
608         SendChunk(_SC("\r\n"));\r
609 }\r
610 \r
611 //this can be done much better/faster(do we need that?)\r
612 const SQChar *SQDbgServer::escape_xml(const SQChar *s)\r
613 {\r
614         SQChar *temp=sq_getscratchpad(_v,((int)scstrlen(s)*6) + sizeof(SQChar));\r
615         SQChar *dest=temp;\r
616         while(*s!=_SC('\0')){\r
617                 int i=0;\r
618                 bool escaped=false;\r
619                 while(g_escapes[i].esc!=NULL){\r
620                         if(*s==g_escapes[i].c){\r
621                                 scstrcpy(dest,g_escapes[i].esc);\r
622                                 dest+=scstrlen(g_escapes[i].esc);\r
623                                 escaped=true;\r
624                                 break;\r
625                         }\r
626                         i++;\r
627                 }\r
628                 if(!escaped){*dest=*s;*dest++;}\r
629                 *s++;\r
630         }\r
631         *dest=_SC('\0');\r
632         return temp;\r
633         \r
634 }\r