Torque3D Documentation / _generateds / telnetDebugger.cpp

telnetDebugger.cpp

Engine/source/console/telnetDebugger.cpp

More...

Public Functions

debuggerConsumer(U32 level, const char * line)
DefineEngineFunction(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" )
DefineEngineFunction(dbgIsConnected , bool , () , "()" "Returns true <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">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" )
DefineEngineFunction(dbgSetParameters , void , (S32 port, const char *password, bool waitForClient) , (false) , "( int port, string password, bool waitForClient )" "Open <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</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)

DefineEngineFunction(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" )

DefineEngineFunction(dbgIsConnected , bool , () , "()" "Returns true <a href="/coding/file/tsmeshintrinsics_8cpp/#tsmeshintrinsics_8cpp_1a4e4fa7e3399708e0777b5308db01278c">if</a> <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">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" )

DefineEngineFunction(dbgSetParameters , void , (S32 port, const char *password, bool waitForClient) , (false) , "( int port, string password, bool waitForClient )" "Open <a href="/coding/file/pointer_8h/#pointer_8h_1aeeddce917cf130d62c370b8f216026dd">a</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
117DefineEngineFunction( 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
128DefineEngineFunction( 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
135DefineEngineFunction( 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   dStrncpy(mDebuggerPassword, "", PasswordMaxLength);
168   dStrncpy(mLineBuffer, "", sizeof(mLineBuffer));
169   
170   // Add the version number in a global so that
171   // scripts can detect the presence of the
172   // "enhanced" debugger features.
173   Con::evaluatef( "$dbgVersion = %d;", Version );
174}
175
176TelnetDebugger::Breakpoint **TelnetDebugger::findBreakpoint(StringTableEntry fileName, S32 lineNumber)
177{
178   Breakpoint **walk = &mBreakpoints;
179   Breakpoint *cur;
180   while((cur = *walk) != NULL)
181   {
182      // TODO: This assumes that the OS file names are case 
183      // insensitive... Torque needs a dFilenameCmp() function.
184      if( dStricmp( cur->fileName, fileName ) == 0 && cur->lineNumber == U32(lineNumber))
185         return walk;
186      walk = &cur->next;
187   }
188   return NULL;
189}
190
191
192TelnetDebugger::~TelnetDebugger()
193{
194   Con::removeConsumer(debuggerConsumer);
195
196   if(mAcceptSocket != NetSocket::INVALID)
197      Net::closeSocket(mAcceptSocket);
198   if(mDebugSocket != NetSocket::INVALID)
199      Net::closeSocket(mDebugSocket);
200}
201
202TelnetDebugger *TelDebugger = NULL;
203
204void TelnetDebugger::create()
205{
206   TelDebugger = new TelnetDebugger;
207   Process::notify(TelDebugger, &TelnetDebugger::process, PROCESS_FIRST_ORDER);
208}
209
210void TelnetDebugger::destroy()
211{
212   Process::remove(TelDebugger, &TelnetDebugger::process);
213   delete TelDebugger;
214   TelDebugger = NULL;
215}
216
217void TelnetDebugger::send(const char *str)
218{
219   Net::send(mDebugSocket, (const unsigned char*)str, dStrlen(str));
220}
221
222void TelnetDebugger::disconnect()
223{
224   if ( mDebugSocket != NetSocket::INVALID )
225   {
226      Net::closeSocket(mDebugSocket);
227      mDebugSocket = NetSocket::INVALID;
228   }
229
230   removeAllBreakpoints();
231
232   mState = NotConnected;
233   mProgramPaused = false;
234}
235
236void TelnetDebugger::setDebugParameters(S32 port, const char *password, bool waitForClient)
237{
238   // Don't bail if same port... we might just be wanting to change
239   // the password.
240//   if(port == mAcceptPort)
241//      return;
242
243   if(mAcceptSocket != NetSocket::INVALID)
244   {
245      Net::closeSocket(mAcceptSocket);
246      mAcceptSocket = NetSocket::INVALID;
247   }
248   mAcceptPort = port;
249   if(mAcceptPort != -1 && mAcceptPort != 0)
250   {
251     NetAddress address;
252     Net::getIdealListenAddress(&address);
253     address.port = mAcceptPort;
254
255      mAcceptSocket = Net::openSocket();
256      Net::bindAddress(address, mAcceptSocket);
257      Net::listen(mAcceptSocket, 4);
258
259      Net::setBlocking(mAcceptSocket, false);
260   }
261   dStrncpy(mDebuggerPassword, password, PasswordMaxLength);
262
263   mWaitForClient = waitForClient;
264   if ( !mWaitForClient ) 
265      return;
266
267   // Wait for the client to fully connect.
268   while ( mState != Connected  ) 
269   {
270      Platform::sleep(10);
271      process();
272   }
273
274}
275
276void TelnetDebugger::processConsoleLine(const char *consoleLine)
277{
278   if(mState != NotConnected)
279   {
280      send("COUT ");
281      send(consoleLine);
282      send("\r\n");
283   }
284}
285
286void TelnetDebugger::process()
287{
288   NetAddress address;
289
290   if(mAcceptSocket != NetSocket::INVALID)
291   {
292      // ok, see if we have any new connections:
293      NetSocket newConnection;
294      newConnection = Net::accept(mAcceptSocket, &address);
295
296      if(newConnection != NetSocket::INVALID && mDebugSocket == NetSocket::INVALID)
297      {
298         char buffer[256];
299         Net::addressToString(&address, buffer);
300         Con::printf("Debugger connection from %s", buffer);
301
302         mState = PasswordTry;
303         mDebugSocket = newConnection;
304
305         Net::setBlocking(newConnection, false);
306      }
307      else if(newConnection != NetSocket::INVALID)
308         Net::closeSocket(newConnection);
309   }
310   // see if we have any input to process...
311
312   if(mDebugSocket == NetSocket::INVALID)
313      return;
314
315   checkDebugRecv();
316   if(mDebugSocket == NetSocket::INVALID)
317      removeAllBreakpoints();
318}
319
320void TelnetDebugger::checkDebugRecv()
321{
322   for (;;) 
323   {
324      // Process all the complete commands in the buffer.
325      while ( mCurPos > 0 )
326      {
327         // Remove leading whitespace.
328         while ( mCurPos > 0 && ( mLineBuffer[0] == 0 || mLineBuffer[0] == '\r' || mLineBuffer[0] == '\n' ) )
329         {
330            mCurPos--;
331            dMemmove(mLineBuffer, mLineBuffer + 1, mCurPos);
332         }
333
334         // Look for a complete command.
335         bool gotCmd = false;
336         for(S32 i = 0; i < mCurPos; i++)
337         {
338            if( mLineBuffer[i] == 0 )
339               mLineBuffer[i] = '_';
340
341            else if ( mLineBuffer[i] == '\r' || mLineBuffer[i] == '\n' )
342            {
343               // Send this command to be processed.
344               mLineBuffer[i] = '\n';
345               processLineBuffer(i+1);
346
347               // Remove the command from the buffer.
348               mCurPos -= i + 1;
349               dMemmove(mLineBuffer, mLineBuffer + i + 1, mCurPos);
350
351               gotCmd = true;
352               break;
353            }
354         }
355
356         // If we didn't find a command in this pass
357         // then we have an incomplete buffer.
358         if ( !gotCmd )
359            break;
360      }
361
362      // found no <CR> or <LF>
363      if(mCurPos == MaxCommandSize) // this shouldn't happen
364      {
365         disconnect();
366         return;
367      }
368
369      S32 numBytes;
370      Net::Error err = Net::recv(mDebugSocket, (unsigned char*)(mLineBuffer + mCurPos), MaxCommandSize - mCurPos, &numBytes);
371
372      if((err != Net::NoError && err != Net::WouldBlock) || numBytes == 0)
373      {
374         disconnect();
375         return;
376      }
377      if(err == Net::WouldBlock)
378         return;
379
380      mCurPos += numBytes;
381   }
382}
383
384void TelnetDebugger::executionStopped(CodeBlock *code, U32 lineNumber)
385{
386   if(mProgramPaused)
387      return;
388
389   if(mBreakOnNextStatement)
390   {
391      setBreakOnNextStatement( false );
392      breakProcess();
393      return;
394   }
395
396   Breakpoint **bp = findBreakpoint(code->name, lineNumber);
397   if(!bp)
398      return;
399   
400   Breakpoint *brk = *bp;
401   mProgramPaused = true;
402   Con::evaluatef("$Debug::result = %s;", brk->testExpression);
403   if(Con::getBoolVariable("$Debug::result"))
404   {
405      brk->curCount++;
406      if(brk->curCount >= brk->passCount)
407      {
408         brk->curCount = 0;
409         if(brk->clearOnHit)
410            removeBreakpoint(code->name, lineNumber);
411         breakProcess();
412      }
413   }
414   mProgramPaused = false;
415}
416
417void TelnetDebugger::pushStackFrame()
418{
419   if(mState == NotConnected)
420      return;
421
422   if(mBreakOnNextStatement && mStackPopBreakIndex > -1 && 
423      gEvalState.getStackDepth() > mStackPopBreakIndex)
424      setBreakOnNextStatement( false );
425}
426
427void TelnetDebugger::popStackFrame()
428{
429   if(mState == NotConnected)
430      return;
431
432   if(mStackPopBreakIndex > -1 && gEvalState.getStackDepth()-1 <= mStackPopBreakIndex)
433      setBreakOnNextStatement( true );
434}
435
436void TelnetDebugger::breakProcess()
437{
438   // Send out a break with the full stack.
439   sendBreak();
440
441   mProgramPaused = true;
442   while(mProgramPaused)
443   {
444      Platform::sleep(10);
445      checkDebugRecv();
446      if(mDebugSocket == NetSocket::INVALID)
447      {
448         mProgramPaused = false;
449         removeAllBreakpoints();
450         debugContinue();
451         return;
452      }
453   }
454}
455
456void TelnetDebugger::sendBreak()
457{
458   // echo out the break
459   send("BREAK");
460   char buffer[MaxCommandSize];
461   char scope[MaxCommandSize];
462
463   S32 last = 0;
464
465   for(S32 i = (S32) gEvalState.getStackDepth() - 1; i >= last; i--)
466   {
467      CodeBlock *code = gEvalState.stack[i]->code;
468      const char *file = "<none>";
469      if (code && code->name && code->name[0])
470         file = code->name;
471
472      Namespace *ns = gEvalState.stack[i]->scopeNamespace;
473      scope[0] = 0;
474      if ( ns ) {
475
476         if ( ns->mParent && ns->mParent->mPackage && ns->mParent->mPackage[0] ) {
477            dStrcat( scope, ns->mParent->mPackage, MaxCommandSize );
478            dStrcat( scope, "::", MaxCommandSize );
479         }
480         if ( ns->mName && ns->mName[0] ) {
481            dStrcat( scope, ns->mName, MaxCommandSize );
482            dStrcat( scope, "::", MaxCommandSize );
483         }
484      }
485
486      const char *function = gEvalState.stack[i]->scopeName;
487      if ((!function) || (!function[0]))
488         function = "<none>";
489      dStrcat( scope, function, MaxCommandSize );
490
491      U32 line=0, inst;
492      U32 ip = gEvalState.stack[i]->ip;
493      if (code)
494         code->findBreakLine(ip, line, inst);
495      dSprintf(buffer, MaxCommandSize, " %s %d %s", file, line, scope);
496      send(buffer);
497   }
498
499   send("\r\n");
500}
501
502void TelnetDebugger::processLineBuffer(S32 cmdLen)
503{
504   if (mState == PasswordTry)
505   {
506      if(dStrncmp(mLineBuffer, mDebuggerPassword, cmdLen-1))
507      {
508         // failed password:
509         send("PASS WrongPassword.\r\n");
510         disconnect();
511      }
512      else
513      {
514         send("PASS Connected.\r\n");
515         mState = mWaitForClient ? Initialize : Connected;
516      }
517
518      return;
519   }
520   else
521   {
522      char evalBuffer[MaxCommandSize];
523      char varBuffer[MaxCommandSize];
524      char fileBuffer[MaxCommandSize];
525      char clear[MaxCommandSize];
526      S32 passCount, line, frame;
527
528      if(dSscanf(mLineBuffer, "CEVAL %[^\n]", evalBuffer) == 1)
529      {
530         RawData rd;
531         rd.size = dStrlen(evalBuffer) + 1;
532         rd.data = ( S8* ) evalBuffer;
533         Con::smConsoleInput.trigger(rd);
534      }
535      else if(dSscanf(mLineBuffer, "BRKVARSET %s %d %[^\n]", varBuffer, &passCount, evalBuffer) == 3)
536         addVariableBreakpoint(varBuffer, passCount, evalBuffer);
537      else if(dSscanf(mLineBuffer, "BRKVARCLR %s", varBuffer) == 1)
538         removeVariableBreakpoint(varBuffer);
539      else if(dSscanf(mLineBuffer, "BRKSET %s %d %s %d %[^\n]", fileBuffer,&line,&clear,&passCount,evalBuffer) == 5)
540         addBreakpoint(fileBuffer, line, dAtob(clear), passCount, evalBuffer);
541      else if(dSscanf(mLineBuffer, "BRKCLR %s %d", fileBuffer, &line) == 2)
542         removeBreakpoint(fileBuffer, line);
543      else if(!dStrncmp(mLineBuffer, "BRKCLRALL\n", cmdLen))
544         removeAllBreakpoints();
545      else if(!dStrncmp(mLineBuffer, "BRKNEXT\n", cmdLen))
546         debugBreakNext();
547      else if(!dStrncmp(mLineBuffer, "CONTINUE\n", cmdLen))
548         debugContinue();
549      else if(!dStrncmp(mLineBuffer, "STEPIN\n", cmdLen))
550         debugStepIn();
551      else if(!dStrncmp(mLineBuffer, "STEPOVER\n", cmdLen))
552         debugStepOver();
553      else if(!dStrncmp(mLineBuffer, "STEPOUT\n", cmdLen))
554         debugStepOut();
555      else if(dSscanf(mLineBuffer, "EVAL %s %d %[^\n]", varBuffer, &frame, evalBuffer) == 3)
556         evaluateExpression(varBuffer, frame, evalBuffer);
557      else if(!dStrncmp(mLineBuffer, "FILELIST\n", cmdLen))
558         dumpFileList();
559      else if(dSscanf(mLineBuffer, "BREAKLIST %s", fileBuffer) == 1)
560         dumpBreakableList(fileBuffer);
561      else
562      {
563         S32 errorLen = dStrlen(mLineBuffer) + 32; // ~25 in error message, plus buffer
564         FrameTemp<char> errorBuffer(errorLen);
565
566         dSprintf( errorBuffer, errorLen, "DBGERR Invalid command(%s)!\r\n", mLineBuffer );
567         // invalid stuff.
568         send( errorBuffer );
569      }
570   }
571}
572
573void TelnetDebugger::addVariableBreakpoint(const char*, S32, const char*)
574{
575   send("addVariableBreakpoint\r\n");
576}
577
578void TelnetDebugger::removeVariableBreakpoint(const char*)
579{
580   send("removeVariableBreakpoint\r\n");
581}
582
583void TelnetDebugger::addAllBreakpoints(CodeBlock *code)
584{
585   if(mState == NotConnected)
586      return;
587
588   // Find the breakpoints for this code block and attach them.
589   Breakpoint *cur = mBreakpoints;
590   while( cur != NULL )
591   {
592      // TODO: This assumes that the OS file names are case 
593      // insensitive... Torque needs a dFilenameCmp() function.
594      if( dStricmp( cur->fileName, code->name ) == 0 )
595      {
596         cur->code = code;
597
598         // Find the fist breakline starting from and
599         // including the requested breakline.
600         S32 newLine = code->findFirstBreakLine(cur->lineNumber);
601         if (newLine <= 0) 
602         {
603            char buffer[MaxCommandSize];
604            dSprintf(buffer, MaxCommandSize, "BRKCLR %s %d\r\n", cur->fileName, cur->lineNumber);
605            send(buffer);
606
607            Breakpoint *next = cur->next;
608            removeBreakpoint(cur->fileName, cur->lineNumber);            
609            cur = next;
610
611            continue;
612         }
613
614         // If the requested breakline does not match
615         // the actual break line we need to inform
616         // the client.
617         if (newLine != cur->lineNumber)
618         {
619            char buffer[MaxCommandSize];
620
621            // If we already have a line at this breapoint then
622            // tell the client to clear the breakpoint.
623            if ( findBreakpoint(cur->fileName, newLine) ) {
624
625               dSprintf(buffer, MaxCommandSize, "BRKCLR %s %d\r\n", cur->fileName, cur->lineNumber);
626               send(buffer);
627
628               Breakpoint *next = cur->next;
629               removeBreakpoint(cur->fileName, cur->lineNumber);
630               cur = next;
631
632               continue;
633            }
634
635            // We're moving the breakpoint to new line... inform the 
636            // client so it can update it's view.
637            dSprintf(buffer, MaxCommandSize, "BRKMOV %s %d %d\r\n", cur->fileName, cur->lineNumber, newLine);
638            send(buffer);
639            cur->lineNumber = newLine;
640         }
641
642         code->setBreakpoint(cur->lineNumber);
643      }
644
645      cur = cur->next;
646   }
647
648   // Enable all breaks if a break next was set.
649   if (mBreakOnNextStatement)
650       code->setAllBreaks();
651}
652
653void TelnetDebugger::addBreakpoint(const char *fileName, S32 line, bool clear, S32 passCount, const char *evalString)
654{
655   fileName = StringTable->insert(fileName);
656   Breakpoint **bp = findBreakpoint(fileName, line);
657
658   if(bp)
659   {
660      // trying to add the same breakpoint...
661      Breakpoint *brk = *bp;
662      dFree(brk->testExpression);
663      brk->testExpression = dStrdup(evalString);
664      brk->passCount = passCount;
665      brk->clearOnHit = clear;
666      brk->curCount = 0;
667   }
668   else
669   {
670      // Note that if the code block is not already 
671      // loaded it is handled by addAllBreakpoints.
672      CodeBlock* code = CodeBlock::find(fileName);
673      if (code)
674      {
675         // Find the fist breakline starting from and
676         // including the requested breakline.
677         S32 newLine = code->findFirstBreakLine(line);
678         if (newLine <= 0) 
679         {
680            char buffer[MaxCommandSize];
681            dSprintf(buffer, MaxCommandSize, "BRKCLR %s %d\r\n", fileName, line);
682            send(buffer);
683            return;
684         }
685
686         // If the requested breakline does not match
687         // the actual break line we need to inform
688         // the client.
689         if (newLine != line)
690         {
691            char buffer[MaxCommandSize];
692
693            // If we already have a line at this breapoint then
694            // tell the client to clear the breakpoint.
695            if ( findBreakpoint(fileName, newLine) ) {
696               dSprintf(buffer, MaxCommandSize, "BRKCLR %s %d\r\n", fileName, line);
697               send(buffer);
698               return;
699            }
700
701            // We're moving the breakpoint to new line... inform the client.
702            dSprintf(buffer, MaxCommandSize, "BRKMOV %s %d %d\r\n", fileName, line, newLine);
703            send(buffer);
704            line = newLine;
705         }
706
707         code->setBreakpoint(line);
708      }
709
710      Breakpoint *brk = new Breakpoint;
711      brk->code = code;
712      brk->fileName = fileName;
713      brk->lineNumber = line;
714      brk->passCount = passCount;
715      brk->clearOnHit = clear;
716      brk->curCount = 0;
717      brk->testExpression = dStrdup(evalString);
718      brk->next = mBreakpoints;
719      mBreakpoints = brk;
720   }
721}
722
723void TelnetDebugger::removeBreakpointsFromCode(CodeBlock *code)
724{
725   Breakpoint **walk = &mBreakpoints;
726   Breakpoint *cur;
727   while((cur = *walk) != NULL)
728   {
729      if(cur->code == code)
730      {
731         dFree(cur->testExpression);
732         *walk = cur->next;
733         delete walk;
734      }
735      else
736         walk = &cur->next;
737   }
738}
739
740void TelnetDebugger::removeBreakpoint(const char *fileName, S32 line)
741{
742   fileName = StringTable->insert(fileName);
743   Breakpoint **bp = findBreakpoint(fileName, line);
744   if(bp)
745   {
746      Breakpoint *brk = *bp;
747      *bp = brk->next;
748     if ( brk->code )
749          brk->code->clearBreakpoint(brk->lineNumber);
750      dFree(brk->testExpression);
751      delete brk;
752   }
753}
754
755void TelnetDebugger::removeAllBreakpoints()
756{
757   Breakpoint *walk = mBreakpoints;
758   while(walk)
759   {
760      Breakpoint *temp = walk->next;
761     if ( walk->code )
762          walk->code->clearBreakpoint(walk->lineNumber);
763      dFree(walk->testExpression);
764      delete walk;
765      walk = temp;
766   }
767   mBreakpoints = NULL;
768}
769
770void TelnetDebugger::debugContinue()
771{
772   if (mState == Initialize) {
773      mState = Connected;
774      return;
775   }
776
777   setBreakOnNextStatement( false );
778   mStackPopBreakIndex = -1;
779   mProgramPaused = false;
780   send("RUNNING\r\n");
781}
782
783void TelnetDebugger::setBreakOnNextStatement( bool enabled )
784{
785   if ( enabled )
786   {
787      // Apply breaks on all the code blocks.
788      for(CodeBlock *walk = CodeBlock::getCodeBlockList(); walk; walk = walk->nextFile)
789         walk->setAllBreaks();
790      mBreakOnNextStatement = true;
791   } 
792   else if ( !enabled )
793   {
794      // Clear all the breaks on the codeblocks 
795      // then go reapply the breakpoints.
796      for(CodeBlock *walk = CodeBlock::getCodeBlockList(); walk; walk = walk->nextFile)
797         walk->clearAllBreaks();
798      for(Breakpoint *w = mBreakpoints; w; w = w->next)
799     {
800        if ( w->code )
801              w->code->setBreakpoint(w->lineNumber);
802     }
803      mBreakOnNextStatement = false;
804   }
805}
806
807void TelnetDebugger::debugBreakNext()
808{
809   if (mState != Connected)
810      return;
811
812   if ( !mProgramPaused ) 
813      setBreakOnNextStatement( true );
814}
815
816void TelnetDebugger::debugStepIn()
817{
818   // Note that step in is allowed during
819   // the initialize state, so that we can
820   // break on the first script line executed.
821
822   setBreakOnNextStatement( true );
823   mStackPopBreakIndex = -1;
824   mProgramPaused = false;
825
826   // Don't bother sending this to the client
827   // if it's in the initialize state.  It will
828   // just be ignored as the client knows it
829   // is in a running state when it connects.
830   if (mState != Initialize)
831      send("RUNNING\r\n");
832   else 
833      mState = Connected;
834}
835
836void TelnetDebugger::debugStepOver()
837{
838   if (mState != Connected)
839      return;
840
841   setBreakOnNextStatement( true );
842   mStackPopBreakIndex = gEvalState.getStackDepth();
843   mProgramPaused = false;
844   send("RUNNING\r\n");
845}
846
847void TelnetDebugger::debugStepOut()
848{
849   if (mState != Connected)
850      return;
851
852   setBreakOnNextStatement( false );
853   mStackPopBreakIndex = gEvalState.getStackDepth() - 1;
854   if ( mStackPopBreakIndex == 0 )
855      mStackPopBreakIndex = -1;
856   mProgramPaused = false;
857   send("RUNNING\r\n");
858}
859
860void TelnetDebugger::evaluateExpression(const char *tag, S32 frame, const char *evalBuffer)
861{
862   // Make sure we're passing a valid frame to the eval.
863   if ( frame > gEvalState.getStackDepth() )
864      frame = gEvalState.getStackDepth() - 1;
865   if ( frame < 0 )
866      frame = 0;
867
868   // Build a buffer just big enough for this eval.
869   const char* format = "return %s;";
870   dsize_t len = dStrlen( format ) + dStrlen( evalBuffer );
871   char* buffer = new char[ len ];
872   dSprintf( buffer, len, format, evalBuffer );
873
874   // Execute the eval.
875   CodeBlock *newCodeBlock = new CodeBlock();
876   const char* result = newCodeBlock->compileExec( NULL, buffer, false, frame );
877   delete [] buffer;
878   
879   // Create a new buffer that fits the result.
880   format = "EVALOUT %s %s\r\n";
881   len = dStrlen( format ) + dStrlen( tag ) + dStrlen( result );
882   buffer = new char[ len ];
883   dSprintf( buffer, len, format, tag, result[0] ? result : "\"\"" );
884
885   send( buffer );
886   delete [] buffer;
887}
888
889void TelnetDebugger::dumpFileList()
890{
891   send("FILELISTOUT ");
892   for(CodeBlock *walk = CodeBlock::getCodeBlockList(); walk; walk = walk->nextFile)
893   {
894      send(walk->name);
895      if(walk->nextFile)
896         send(" ");
897   }
898   send("\r\n");
899}
900
901void TelnetDebugger::dumpBreakableList(const char *fileName)
902{
903   fileName = StringTable->insert(fileName);
904   CodeBlock *file = CodeBlock::find(fileName);
905   char buffer[MaxCommandSize];
906   if(file)
907   {
908      dSprintf(buffer, MaxCommandSize, "BREAKLISTOUT %s %d", fileName, file->breakListSize >> 1);
909      send(buffer);
910      for(U32 i = 0; i < file->breakListSize; i += 2)
911      {
912         dSprintf(buffer, MaxCommandSize, " %d %d", file->breakList[i], file->breakList[i+1]);
913         send(buffer);
914      }
915      send("\r\n");
916   }
917   else
918      send("DBGERR No such file!\r\n");
919}
920
921
922void TelnetDebugger::clearCodeBlockPointers(CodeBlock *code)
923{
924   Breakpoint **walk = &mBreakpoints;
925   Breakpoint *cur;
926   while((cur = *walk) != NULL)
927   {
928      if(cur->code == code)
929         cur->code = NULL;
930
931      walk = &cur->next;
932   }
933}
934