Torque3D Documentation / _generateds / telnetDebugger.cpp

telnetDebugger.cpp

Engine/source/console/telnetDebugger.cpp

More...

Public Functions

debuggerConsumer(U32 level, const char * line)
DefineConsoleFunction(dbgDisconnect , void , () , "()" "Forcibly disconnects any attached script debugging <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">client.\n</a>" "@internal Primarily used <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> Torsion and other debugging tools" )
DefineConsoleFunction(dbgIsConnected , bool , () , "()" "Returns true <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a2594a51175f310ed96ad6cd7d6514878">if</a> a script debugging client is connected else return <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">false.\n</a>" "@internal Primarily used <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> Torsion and other debugging tools" )
DefineConsoleFunction(dbgSetParameters , void , (S32 port, const char *password, bool waitForClient) , (false) , "( int port, string password, bool waitForClient )" "Open a debug server port on the specified port, requiring the specified password, " "and optionally waiting <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> the debug client <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">connect.\n</a>" " @internal Primarily used <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> Torsion and other debugging tools" )

Detailed Description

Public Variables

 MODULE_END 
 MODULE_INIT 
 MODULE_SHUTDOWN 
TelnetDebugger * TelDebugger 

Public Functions

debuggerConsumer(U32 level, const char * line)

DefineConsoleFunction(dbgDisconnect , void , () , "()" "Forcibly disconnects any attached script debugging <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">client.\n</a>" "@internal Primarily used <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> Torsion and other debugging tools" )

DefineConsoleFunction(dbgIsConnected , bool , () , "()" "Returns true <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a2594a51175f310ed96ad6cd7d6514878">if</a> a script debugging client is connected else return <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">false.\n</a>" "@internal Primarily used <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> Torsion and other debugging tools" )

DefineConsoleFunction(dbgSetParameters , void , (S32 port, const char *password, bool waitForClient) , (false) , "( int port, string password, bool waitForClient )" "Open a debug server port on the specified port, requiring the specified password, " "and optionally waiting <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> the debug client <a href="/coding/file/cmdgram_8cpp/#cmdgram_8cpp_1a5bafda9519252aa2d0fd038153f77dca">to</a> <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1aeab71244afb687f16d8c4f5ee9d6ef0e">connect.\n</a>" " @internal Primarily used <a href="/coding/file/cmdscan_8cpp/#cmdscan_8cpp_1a2732ab74fa0237854c2ba0f75f88a624">for</a> Torsion and other debugging tools" )

  1
  2//-----------------------------------------------------------------------------
  3// Copyright (c) 2012 GarageGames, LLC
  4//
  5// Permission is hereby granted, free of charge, to any person obtaining a copy
  6// of this software and associated documentation files (the "Software"), to
  7// deal in the Software without restriction, including without limitation the
  8// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  9// sell copies of the Software, and to permit persons to whom the Software is
 10// furnished to do so, subject to the following conditions:
 11//
 12// The above copyright notice and this permission notice shall be included in
 13// all copies or substantial portions of the Software.
 14//
 15// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 16// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 17// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 18// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 19// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 20// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 21// IN THE SOFTWARE.
 22//-----------------------------------------------------------------------------
 23
 24#include "platform/platform.h"
 25#include "console/telnetDebugger.h"
 26
 27#include "core/frameAllocator.h"
 28#include "console/console.h"
 29#include "console/engineAPI.h"
 30#include "core/stringTable.h"
 31#include "console/consoleInternal.h"
 32#include "console/ast.h"
 33#include "console/compiler.h"
 34#include "core/util/journal/process.h"
 35#include "core/module.h"
 36
 37
 38MODULE_BEGIN( TelnetDebugger )
 39
 40   MODULE_INIT
 41   {
 42      TelnetDebugger::create();
 43   }
 44   
 45   MODULE_SHUTDOWN
 46   {
 47      TelnetDebugger::destroy();
 48   }
 49
 50MODULE_END;
 51
 52//
 53// Enhanced TelnetDebugger for Torsion
 54// http://www.sickheadgames.com/torsion
 55//
 56//
 57// Debugger commands:
 58//
 59// CEVAL console line - evaluate the console line
 60//    output: none
 61//
 62// BRKVARSET varName passct expr - NOT IMPLEMENTED!
 63//    output: none
 64//
 65// BRKVARCLR varName - NOT IMPLEMENTED!
 66//    output: none
 67//
 68// BRKSET file line clear passct expr - set a breakpoint on the file,line
 69//        it must pass passct times for it to break and if clear is true, it
 70//        clears when hit
 71//    output: 
 72//
 73// BRKNEXT - stop execution at the next breakable line.
 74//    output: none
 75//
 76// BRKCLR file line - clear a breakpoint on the file,line
 77//    output: none
 78//
 79// BRKCLRALL - clear all breakpoints
 80//    output: none
 81//
 82// CONTINUE - continue execution
 83//    output: RUNNING
 84//
 85// STEPIN - run until next statement
 86//    output: RUNNING
 87//
 88// STEPOVER - run until next break <= current frame
 89//    output: RUNNING
 90//
 91// STEPOUT - run until next break <= current frame - 1
 92//    output: RUNNING
 93//
 94// EVAL tag frame expr - evaluate the expr in the console, on the frame'th stack frame
 95//    output: EVALOUT tag exprResult
 96//
 97// FILELIST - list script files loaded
 98//    output: FILELISTOUT file1 file2 file3 file4 ...
 99//
100// BREAKLIST file - get a list of breakpoint-able lines in the file
101//    output: BREAKLISTOUT file skipBreakPairs skiplinecount breaklinecount skiplinecount breaklinecount ...
102//
103//
104// Other output:
105//
106// BREAK file1 line1 function1 file2 line2 function2 ... - Sent when the debugger hits a 
107//          breakpoint.  It lists out one file/line/function triplet for each stack level.
108//          The first one is the top of the stack.
109//
110// COUT console-output - echo of console output from engine
111//
112// BRKMOV file line newline - sent when a breakpoint is moved to a breakable line.
113//
114// BRKCLR file line - sent when a breakpoint cannot be moved to a breakable line on the client.
115//
116
117DefineConsoleFunction( dbgSetParameters, void, (S32 port, const char * password, bool waitForClient ), (false), "( int port, string password, bool waitForClient )"
118                "Open a debug server port on the specified port, requiring the specified password, "
119            "and optionally waiting for the debug client to connect.\n"
120            "@internal Primarily used for Torsion and other debugging tools")
121{
122   if (TelDebugger)
123   {
124      TelDebugger->setDebugParameters(port, password, waitForClient );
125   }
126}
127
128DefineConsoleFunction( dbgIsConnected, bool, (), , "()"
129                "Returns true if a script debugging client is connected else return false.\n"
130            "@internal Primarily used for Torsion and other debugging tools")
131{
132   return TelDebugger && TelDebugger->isConnected();
133}
134
135DefineConsoleFunction( dbgDisconnect, void, (), , "()"
136                "Forcibly disconnects any attached script debugging client.\n"
137            "@internal Primarily used for Torsion and other debugging tools")
138{
139   if (TelDebugger)
140      TelDebugger->disconnect();
141}
142
143static void debuggerConsumer(U32 level, const char *line)
144{
145   TORQUE_UNUSED(level);
146   if (TelDebugger)
147      TelDebugger->processConsoleLine(line);
148}
149
150TelnetDebugger::TelnetDebugger()
151{
152   Con::addConsumer(debuggerConsumer);
153
154   mAcceptPort = -1;
155   mAcceptSocket = NetSocket::INVALID;
156   mDebugSocket = NetSocket::INVALID;
157
158   mState = NotConnected;
159   mCurPos = 0;
160
161   mBreakpoints = NULL;
162   mBreakOnNextStatement = false;
163   mStackPopBreakIndex = -1;
164   mProgramPaused = false;
165   mWaitForClient = false;
166
167   // Add the version number in a global so that
168   // scripts can detect the presence of the
169   // "enhanced" debugger features.
170   Con::evaluatef( "$dbgVersion = %d;", Version );
171}
172
173TelnetDebugger::Breakpoint **TelnetDebugger::findBreakpoint(StringTableEntry fileName, S32 lineNumber)
174{
175   Breakpoint **walk = &mBreakpoints;
176   Breakpoint *cur;
177   while((cur = *walk) != NULL)
178   {
179      // TODO: This assumes that the OS file names are case 
180      // insensitive... Torque needs a dFilenameCmp() function.
181      if( dStricmp( cur->fileName, fileName ) == 0 && cur->lineNumber == U32(lineNumber))
182         return walk;
183      walk = &cur->next;
184   }
185   return NULL;
186}
187
188
189TelnetDebugger::~TelnetDebugger()
190{
191   Con::removeConsumer(debuggerConsumer);
192
193   if(mAcceptSocket != NetSocket::INVALID)
194      Net::closeSocket(mAcceptSocket);
195   if(mDebugSocket != NetSocket::INVALID)
196      Net::closeSocket(mDebugSocket);
197}
198
199TelnetDebugger *TelDebugger = NULL;
200
201void TelnetDebugger::create()
202{
203   TelDebugger = new TelnetDebugger;
204   Process::notify(TelDebugger, &TelnetDebugger::process, PROCESS_FIRST_ORDER);
205}
206
207void TelnetDebugger::destroy()
208{
209   Process::remove(TelDebugger, &TelnetDebugger::process);
210   delete TelDebugger;
211   TelDebugger = NULL;
212}
213
214void TelnetDebugger::send(const char *str)
215{
216   Net::send(mDebugSocket, (const unsigned char*)str, dStrlen(str));
217}
218
219void TelnetDebugger::disconnect()
220{
221   if ( mDebugSocket != NetSocket::INVALID )
222   {
223      Net::closeSocket(mDebugSocket);
224      mDebugSocket = NetSocket::INVALID;
225   }
226
227   removeAllBreakpoints();
228
229   mState = NotConnected;
230   mProgramPaused = false;
231}
232
233void TelnetDebugger::setDebugParameters(S32 port, const char *password, bool waitForClient)
234{
235   // Don't bail if same port... we might just be wanting to change
236   // the password.
237//   if(port == mAcceptPort)
238//      return;
239
240   if(mAcceptSocket != NetSocket::INVALID)
241   {
242      Net::closeSocket(mAcceptSocket);
243      mAcceptSocket = NetSocket::INVALID;
244   }
245   mAcceptPort = port;
246   if(mAcceptPort != -1 && mAcceptPort != 0)
247   {
248     NetAddress address;
249     Net::getIdealListenAddress(&address);
250     address.port = mAcceptPort;
251
252      mAcceptSocket = Net::openSocket();
253      Net::bindAddress(address, mAcceptSocket);
254      Net::listen(mAcceptSocket, 4);
255
256      Net::setBlocking(mAcceptSocket, false);
257   }
258   dStrncpy(mDebuggerPassword, password, PasswordMaxLength);
259
260   mWaitForClient = waitForClient;
261   if ( !mWaitForClient ) 
262      return;
263
264   // Wait for the client to fully connect.
265   while ( mState != Connected  ) 
266   {
267      Platform::sleep(10);
268      process();
269   }
270
271}
272
273void TelnetDebugger::processConsoleLine(const char *consoleLine)
274{
275   if(mState != NotConnected)
276   {
277      send("COUT ");
278      send(consoleLine);
279      send("\r\n");
280   }
281}
282
283void TelnetDebugger::process()
284{
285   NetAddress address;
286
287   if(mAcceptSocket != NetSocket::INVALID)
288   {
289      // ok, see if we have any new connections:
290      NetSocket newConnection;
291      newConnection = Net::accept(mAcceptSocket, &address);
292
293      if(newConnection != NetSocket::INVALID && mDebugSocket == NetSocket::INVALID)
294      {
295         char buffer[256];
296         Net::addressToString(&address, buffer);
297         Con::printf("Debugger connection from %s", buffer);
298
299         mState = PasswordTry;
300         mDebugSocket = newConnection;
301
302         Net::setBlocking(newConnection, false);
303      }
304      else if(newConnection != NetSocket::INVALID)
305         Net::closeSocket(newConnection);
306   }
307   // see if we have any input to process...
308
309   if(mDebugSocket == NetSocket::INVALID)
310      return;
311
312   checkDebugRecv();
313   if(mDebugSocket == NetSocket::INVALID)
314      removeAllBreakpoints();
315}
316
317void TelnetDebugger::checkDebugRecv()
318{
319   for (;;) 
320   {
321      // Process all the complete commands in the buffer.
322      while ( mCurPos > 0 )
323      {
324         // Remove leading whitespace.
325         while ( mCurPos > 0 && ( mLineBuffer[0] == 0 || mLineBuffer[0] == '\r' || mLineBuffer[0] == '\n' ) )
326         {
327            mCurPos--;
328            dMemmove(mLineBuffer, mLineBuffer + 1, mCurPos);
329         }
330
331         // Look for a complete command.
332         bool gotCmd = false;
333         for(S32 i = 0; i < mCurPos; i++)
334         {
335            if( mLineBuffer[i] == 0 )
336               mLineBuffer[i] = '_';
337
338            else if ( mLineBuffer[i] == '\r' || mLineBuffer[i] == '\n' )
339            {
340               // Send this command to be processed.
341               mLineBuffer[i] = '\n';
342               processLineBuffer(i+1);
343
344               // Remove the command from the buffer.
345               mCurPos -= i + 1;
346               dMemmove(mLineBuffer, mLineBuffer + i + 1, mCurPos);
347
348               gotCmd = true;
349               break;
350            }
351         }
352
353         // If we didn't find a command in this pass
354         // then we have an incomplete buffer.
355         if ( !gotCmd )
356            break;
357      }
358
359      // found no <CR> or <LF>
360      if(mCurPos == MaxCommandSize) // this shouldn't happen
361      {
362         disconnect();
363         return;
364      }
365
366      S32 numBytes;
367      Net::Error err = Net::recv(mDebugSocket, (unsigned char*)(mLineBuffer + mCurPos), MaxCommandSize - mCurPos, &numBytes);
368
369      if((err != Net::NoError && err != Net::WouldBlock) || numBytes == 0)
370      {
371         disconnect();
372         return;
373      }
374      if(err == Net::WouldBlock)
375         return;
376
377      mCurPos += numBytes;
378   }
379}
380
381void TelnetDebugger::executionStopped(CodeBlock *code, U32 lineNumber)
382{
383   if(mProgramPaused)
384      return;
385
386   if(mBreakOnNextStatement)
387   {
388      setBreakOnNextStatement( false );
389      breakProcess();
390      return;
391   }
392
393   Breakpoint **bp = findBreakpoint(code->name, lineNumber);
394   if(!bp)
395      return;
396   
397   Breakpoint *brk = *bp;
398   mProgramPaused = true;
399   Con::evaluatef("$Debug::result = %s;", brk->testExpression);
400   if(Con::getBoolVariable("$Debug::result"))
401   {
402      brk->curCount++;
403      if(brk->curCount >= brk->passCount)
404      {
405         brk->curCount = 0;
406         if(brk->clearOnHit)
407            removeBreakpoint(code->name, lineNumber);
408         breakProcess();
409      }
410   }
411   mProgramPaused = false;
412}
413
414void TelnetDebugger::pushStackFrame()
415{
416   if(mState == NotConnected)
417      return;
418
419   if(mBreakOnNextStatement && mStackPopBreakIndex > -1 && 
420      gEvalState.getStackDepth() > mStackPopBreakIndex)
421      setBreakOnNextStatement( false );
422}
423
424void TelnetDebugger::popStackFrame()
425{
426   if(mState == NotConnected)
427      return;
428
429   if(mStackPopBreakIndex > -1 && gEvalState.getStackDepth()-1 <= mStackPopBreakIndex)
430      setBreakOnNextStatement( true );
431}
432
433void TelnetDebugger::breakProcess()
434{
435   // Send out a break with the full stack.
436   sendBreak();
437
438   mProgramPaused = true;
439   while(mProgramPaused)
440   {
441      Platform::sleep(10);
442      checkDebugRecv();
443      if(mDebugSocket == NetSocket::INVALID)
444      {
445         mProgramPaused = false;
446         removeAllBreakpoints();
447         debugContinue();
448         return;
449      }
450   }
451}
452
453void TelnetDebugger::sendBreak()
454{
455   // echo out the break
456   send("BREAK");
457   char buffer[MaxCommandSize];
458   char scope[MaxCommandSize];
459
460   S32 last = 0;
461
462   for(S32 i = (S32) gEvalState.getStackDepth() - 1; i >= last; i--)
463   {
464      CodeBlock *code = gEvalState.stack[i]->code;
465      const char *file = "<none>";
466      if (code && code->name && code->name[0])
467         file = code->name;
468
469      Namespace *ns = gEvalState.stack[i]->scopeNamespace;
470      scope[0] = 0;
471      if ( ns ) {
472
473         if ( ns->mParent && ns->mParent->mPackage && ns->mParent->mPackage[0] ) {
474            dStrcat( scope, ns->mParent->mPackage );
475            dStrcat( scope, "::" );
476         }
477         if ( ns->mName && ns->mName[0] ) {
478            dStrcat( scope, ns->mName );
479            dStrcat( scope, "::" );
480         }
481      }
482
483      const char *function = gEvalState.stack[i]->scopeName;
484      if ((!function) || (!function[0]))
485         function = "<none>";
486      dStrcat( scope, function );
487
488      U32 line=0, inst;
489      U32 ip = gEvalState.stack[i]->ip;
490      if (code)
491         code->findBreakLine(ip, line, inst);
492      dSprintf(buffer, MaxCommandSize, " %s %d %s", file, line, scope);
493      send(buffer);
494   }
495
496   send("\r\n");
497}
498
499void TelnetDebugger::processLineBuffer(S32 cmdLen)
500{
501   if (mState == PasswordTry)
502   {
503      if(dStrncmp(mLineBuffer, mDebuggerPassword, cmdLen-1))
504      {
505         // failed password:
506         send("PASS WrongPassword.\r\n");
507         disconnect();
508      }
509      else
510      {
511         send("PASS Connected.\r\n");
512         mState = mWaitForClient ? Initialize : Connected;
513      }
514
515      return;
516   }
517   else
518   {
519      char evalBuffer[MaxCommandSize];
520      char varBuffer[MaxCommandSize];
521      char fileBuffer[MaxCommandSize];
522      char clear[MaxCommandSize];
523      S32 passCount, line, frame;
524
525      if(dSscanf(mLineBuffer, "CEVAL %[^\n]", evalBuffer) == 1)
526      {
527         RawData rd;
528         rd.size = dStrlen(evalBuffer) + 1;
529         rd.data = ( S8* ) evalBuffer;
530         Con::smConsoleInput.trigger(rd);
531      }
532      else if(dSscanf(mLineBuffer, "BRKVARSET %s %d %[^\n]", varBuffer, &passCount, evalBuffer) == 3)
533         addVariableBreakpoint(varBuffer, passCount, evalBuffer);
534      else if(dSscanf(mLineBuffer, "BRKVARCLR %s", varBuffer) == 1)
535         removeVariableBreakpoint(varBuffer);
536      else if(dSscanf(mLineBuffer, "BRKSET %s %d %s %d %[^\n]", fileBuffer,&line,&clear,&passCount,evalBuffer) == 5)
537         addBreakpoint(fileBuffer, line, dAtob(clear), passCount, evalBuffer);
538      else if(dSscanf(mLineBuffer, "BRKCLR %s %d", fileBuffer, &line) == 2)
539         removeBreakpoint(fileBuffer, line);
540      else if(!dStrncmp(mLineBuffer, "BRKCLRALL\n", cmdLen))
541         removeAllBreakpoints();
542      else if(!dStrncmp(mLineBuffer, "BRKNEXT\n", cmdLen))
543         debugBreakNext();
544      else if(!dStrncmp(mLineBuffer, "CONTINUE\n", cmdLen))
545         debugContinue();
546      else if(!dStrncmp(mLineBuffer, "STEPIN\n", cmdLen))
547         debugStepIn();
548      else if(!dStrncmp(mLineBuffer, "STEPOVER\n", cmdLen))
549         debugStepOver();
550      else if(!dStrncmp(mLineBuffer, "STEPOUT\n", cmdLen))
551         debugStepOut();
552      else if(dSscanf(mLineBuffer, "EVAL %s %d %[^\n]", varBuffer, &frame, evalBuffer) == 3)
553         evaluateExpression(varBuffer, frame, evalBuffer);
554      else if(!dStrncmp(mLineBuffer, "FILELIST\n", cmdLen))
555         dumpFileList();
556      else if(dSscanf(mLineBuffer, "BREAKLIST %s", fileBuffer) == 1)
557         dumpBreakableList(fileBuffer);
558      else
559      {
560         S32 errorLen = dStrlen(mLineBuffer) + 32; // ~25 in error message, plus buffer
561         FrameTemp<char> errorBuffer(errorLen);
562
563         dSprintf( errorBuffer, errorLen, "DBGERR Invalid command(%s)!\r\n", mLineBuffer );
564         // invalid stuff.
565         send( errorBuffer );
566      }
567   }
568}
569
570void TelnetDebugger::addVariableBreakpoint(const char*, S32, const char*)
571{
572   send("addVariableBreakpoint\r\n");
573}
574
575void TelnetDebugger::removeVariableBreakpoint(const char*)
576{
577   send("removeVariableBreakpoint\r\n");
578}
579
580void TelnetDebugger::addAllBreakpoints(CodeBlock *code)
581{
582   if(mState == NotConnected)
583      return;
584
585   // Find the breakpoints for this code block and attach them.
586   Breakpoint *cur = mBreakpoints;
587   while( cur != NULL )
588   {
589      // TODO: This assumes that the OS file names are case 
590      // insensitive... Torque needs a dFilenameCmp() function.
591      if( dStricmp( cur->fileName, code->name ) == 0 )
592      {
593         cur->code = code;
594
595         // Find the fist breakline starting from and
596         // including the requested breakline.
597         S32 newLine = code->findFirstBreakLine(cur->lineNumber);
598         if (newLine <= 0) 
599         {
600            char buffer[MaxCommandSize];
601            dSprintf(buffer, MaxCommandSize, "BRKCLR %s %d\r\n", cur->fileName, cur->lineNumber);
602            send(buffer);
603
604            Breakpoint *next = cur->next;
605            removeBreakpoint(cur->fileName, cur->lineNumber);            
606            cur = next;
607
608            continue;
609         }
610
611         // If the requested breakline does not match
612         // the actual break line we need to inform
613         // the client.
614         if (newLine != cur->lineNumber)
615         {
616            char buffer[MaxCommandSize];
617
618            // If we already have a line at this breapoint then
619            // tell the client to clear the breakpoint.
620            if ( findBreakpoint(cur->fileName, newLine) ) {
621
622               dSprintf(buffer, MaxCommandSize, "BRKCLR %s %d\r\n", cur->fileName, cur->lineNumber);
623               send(buffer);
624
625               Breakpoint *next = cur->next;
626               removeBreakpoint(cur->fileName, cur->lineNumber);
627               cur = next;
628
629               continue;
630            }
631
632            // We're moving the breakpoint to new line... inform the 
633            // client so it can update it's view.
634            dSprintf(buffer, MaxCommandSize, "BRKMOV %s %d %d\r\n", cur->fileName, cur->lineNumber, newLine);
635            send(buffer);
636            cur->lineNumber = newLine;
637         }
638
639         code->setBreakpoint(cur->lineNumber);
640      }
641
642      cur = cur->next;
643   }
644
645   // Enable all breaks if a break next was set.
646   if (mBreakOnNextStatement)
647       code->setAllBreaks();
648}
649
650void TelnetDebugger::addBreakpoint(const char *fileName, S32 line, bool clear, S32 passCount, const char *evalString)
651{
652   fileName = StringTable->insert(fileName);
653   Breakpoint **bp = findBreakpoint(fileName, line);
654
655   if(bp)
656   {
657      // trying to add the same breakpoint...
658      Breakpoint *brk = *bp;
659      dFree(brk->testExpression);
660      brk->testExpression = dStrdup(evalString);
661      brk->passCount = passCount;
662      brk->clearOnHit = clear;
663      brk->curCount = 0;
664   }
665   else
666   {
667      // Note that if the code block is not already 
668      // loaded it is handled by addAllBreakpoints.
669      CodeBlock* code = CodeBlock::find(fileName);
670      if (code)
671      {
672         // Find the fist breakline starting from and
673         // including the requested breakline.
674         S32 newLine = code->findFirstBreakLine(line);
675         if (newLine <= 0) 
676         {
677            char buffer[MaxCommandSize];
678            dSprintf(buffer, MaxCommandSize, "BRKCLR %s %d\r\n", fileName, line);
679            send(buffer);
680            return;
681         }
682
683         // If the requested breakline does not match
684         // the actual break line we need to inform
685         // the client.
686         if (newLine != line)
687         {
688            char buffer[MaxCommandSize];
689
690            // If we already have a line at this breapoint then
691            // tell the client to clear the breakpoint.
692            if ( findBreakpoint(fileName, newLine) ) {
693               dSprintf(buffer, MaxCommandSize, "BRKCLR %s %d\r\n", fileName, line);
694               send(buffer);
695               return;
696            }
697
698            // We're moving the breakpoint to new line... inform the client.
699            dSprintf(buffer, MaxCommandSize, "BRKMOV %s %d %d\r\n", fileName, line, newLine);
700            send(buffer);
701            line = newLine;
702         }
703
704         code->setBreakpoint(line);
705      }
706
707      Breakpoint *brk = new Breakpoint;
708      brk->code = code;
709      brk->fileName = fileName;
710      brk->lineNumber = line;
711      brk->passCount = passCount;
712      brk->clearOnHit = clear;
713      brk->curCount = 0;
714      brk->testExpression = dStrdup(evalString);
715      brk->next = mBreakpoints;
716      mBreakpoints = brk;
717   }
718}
719
720void TelnetDebugger::removeBreakpointsFromCode(CodeBlock *code)
721{
722   Breakpoint **walk = &mBreakpoints;
723   Breakpoint *cur;
724   while((cur = *walk) != NULL)
725   {
726      if(cur->code == code)
727      {
728         dFree(cur->testExpression);
729         *walk = cur->next;
730         delete walk;
731      }
732      else
733         walk = &cur->next;
734   }
735}
736
737void TelnetDebugger::removeBreakpoint(const char *fileName, S32 line)
738{
739   fileName = StringTable->insert(fileName);
740   Breakpoint **bp = findBreakpoint(fileName, line);
741   if(bp)
742   {
743      Breakpoint *brk = *bp;
744      *bp = brk->next;
745     if ( brk->code )
746          brk->code->clearBreakpoint(brk->lineNumber);
747      dFree(brk->testExpression);
748      delete brk;
749   }
750}
751
752void TelnetDebugger::removeAllBreakpoints()
753{
754   Breakpoint *walk = mBreakpoints;
755   while(walk)
756   {
757      Breakpoint *temp = walk->next;
758     if ( walk->code )
759          walk->code->clearBreakpoint(walk->lineNumber);
760      dFree(walk->testExpression);
761      delete walk;
762      walk = temp;
763   }
764   mBreakpoints = NULL;
765}
766
767void TelnetDebugger::debugContinue()
768{
769   if (mState == Initialize) {
770      mState = Connected;
771      return;
772   }
773
774   setBreakOnNextStatement( false );
775   mStackPopBreakIndex = -1;
776   mProgramPaused = false;
777   send("RUNNING\r\n");
778}
779
780void TelnetDebugger::setBreakOnNextStatement( bool enabled )
781{
782   if ( enabled )
783   {
784      // Apply breaks on all the code blocks.
785      for(CodeBlock *walk = CodeBlock::getCodeBlockList(); walk; walk = walk->nextFile)
786         walk->setAllBreaks();
787      mBreakOnNextStatement = true;
788   } 
789   else if ( !enabled )
790   {
791      // Clear all the breaks on the codeblocks 
792      // then go reapply the breakpoints.
793      for(CodeBlock *walk = CodeBlock::getCodeBlockList(); walk; walk = walk->nextFile)
794         walk->clearAllBreaks();
795      for(Breakpoint *w = mBreakpoints; w; w = w->next)
796     {
797        if ( w->code )
798              w->code->setBreakpoint(w->lineNumber);
799     }
800      mBreakOnNextStatement = false;
801   }
802}
803
804void TelnetDebugger::debugBreakNext()
805{
806   if (mState != Connected)
807      return;
808
809   if ( !mProgramPaused ) 
810      setBreakOnNextStatement( true );
811}
812
813void TelnetDebugger::debugStepIn()
814{
815   // Note that step in is allowed during
816   // the initialize state, so that we can
817   // break on the first script line executed.
818
819   setBreakOnNextStatement( true );
820   mStackPopBreakIndex = -1;
821   mProgramPaused = false;
822
823   // Don't bother sending this to the client
824   // if it's in the initialize state.  It will
825   // just be ignored as the client knows it
826   // is in a running state when it connects.
827   if (mState != Initialize)
828      send("RUNNING\r\n");
829   else 
830      mState = Connected;
831}
832
833void TelnetDebugger::debugStepOver()
834{
835   if (mState != Connected)
836      return;
837
838   setBreakOnNextStatement( true );
839   mStackPopBreakIndex = gEvalState.getStackDepth();
840   mProgramPaused = false;
841   send("RUNNING\r\n");
842}
843
844void TelnetDebugger::debugStepOut()
845{
846   if (mState != Connected)
847      return;
848
849   setBreakOnNextStatement( false );
850   mStackPopBreakIndex = gEvalState.getStackDepth() - 1;
851   if ( mStackPopBreakIndex == 0 )
852      mStackPopBreakIndex = -1;
853   mProgramPaused = false;
854   send("RUNNING\r\n");
855}
856
857void TelnetDebugger::evaluateExpression(const char *tag, S32 frame, const char *evalBuffer)
858{
859   // Make sure we're passing a valid frame to the eval.
860   if ( frame > gEvalState.getStackDepth() )
861      frame = gEvalState.getStackDepth() - 1;
862   if ( frame < 0 )
863      frame = 0;
864
865   // Build a buffer just big enough for this eval.
866   const char* format = "return %s;";
867   dsize_t len = dStrlen( format ) + dStrlen( evalBuffer );
868   char* buffer = new char[ len ];
869   dSprintf( buffer, len, format, evalBuffer );
870
871   // Execute the eval.
872   CodeBlock *newCodeBlock = new CodeBlock();
873   const char* result = newCodeBlock->compileExec( NULL, buffer, false, frame );
874   delete [] buffer;
875   
876   // Create a new buffer that fits the result.
877   format = "EVALOUT %s %s\r\n";
878   len = dStrlen( format ) + dStrlen( tag ) + dStrlen( result );
879   buffer = new char[ len ];
880   dSprintf( buffer, len, format, tag, result[0] ? result : "\"\"" );
881
882   send( buffer );
883   delete [] buffer;
884}
885
886void TelnetDebugger::dumpFileList()
887{
888   send("FILELISTOUT ");
889   for(CodeBlock *walk = CodeBlock::getCodeBlockList(); walk; walk = walk->nextFile)
890   {
891      send(walk->name);
892      if(walk->nextFile)
893         send(" ");
894   }
895   send("\r\n");
896}
897
898void TelnetDebugger::dumpBreakableList(const char *fileName)
899{
900   fileName = StringTable->insert(fileName);
901   CodeBlock *file = CodeBlock::find(fileName);
902   char buffer[MaxCommandSize];
903   if(file)
904   {
905      dSprintf(buffer, MaxCommandSize, "BREAKLISTOUT %s %d", fileName, file->breakListSize >> 1);
906      send(buffer);
907      for(U32 i = 0; i < file->breakListSize; i += 2)
908      {
909         dSprintf(buffer, MaxCommandSize, " %d %d", file->breakList[i], file->breakList[i+1]);
910         send(buffer);
911      }
912      send("\r\n");
913   }
914   else
915      send("DBGERR No such file!\r\n");
916}
917
918
919void TelnetDebugger::clearCodeBlockPointers(CodeBlock *code)
920{
921   Breakpoint **walk = &mBreakpoints;
922   Breakpoint *cur;
923   while((cur = *walk) != NULL)
924   {
925      if(cur->code == code)
926         cur->code = NULL;
927
928      walk = &cur->next;
929   }
930}
931