877 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			C++
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			877 lines
		
	
	
		
			24 KiB
		
	
	
	
		
			C++
		
	
	
		
			Executable File
		
	
	
	
	
| //-----------------------------------------------------------------------------
 | |
| // Torque Game Engine
 | |
| // Copyright (C) GarageGames.com, Inc.
 | |
| //-----------------------------------------------------------------------------
 | |
| 
 | |
| #include "platform/platform.h"
 | |
| #include "console/console.h"
 | |
| #include "console/telnetDebugger.h"
 | |
| #include "platform/event.h"
 | |
| #include "core/stringTable.h"
 | |
| #include "console/consoleInternal.h"
 | |
| #include "console/ast.h"
 | |
| #include "console/compiler.h"
 | |
| #include "platform/gameInterface.h"
 | |
| 
 | |
| 
 | |
| //
 | |
| // Enhanced TelnetDebugger for Torsion
 | |
| // http://www.sickheadgames.com/torsion
 | |
| //
 | |
| //
 | |
| // Debugger commands:
 | |
| //
 | |
| // CEVAL console line - evaluate the console line
 | |
| //    output: none
 | |
| //
 | |
| // BRKVARSET varName passct expr - NOT IMPLEMENTED!
 | |
| //    output: none
 | |
| //
 | |
| // BRKVARCLR varName - NOT IMPLEMENTED!
 | |
| //    output: none
 | |
| //
 | |
| // BRKSET file line clear passct expr - set a breakpoint on the file,line
 | |
| //        it must pass passct times for it to break and if clear is true, it
 | |
| //        clears when hit
 | |
| //    output: 
 | |
| //
 | |
| // BRKNEXT - stop execution at the next breakable line.
 | |
| //    output: none
 | |
| //
 | |
| // BRKCLR file line - clear a breakpoint on the file,line
 | |
| //    output: none
 | |
| //
 | |
| // BRKCLRALL - clear all breakpoints
 | |
| //    output: none
 | |
| //
 | |
| // CONTINUE - continue execution
 | |
| //    output: RUNNING
 | |
| //
 | |
| // STEPIN - run until next statement
 | |
| //    output: RUNNING
 | |
| //
 | |
| // STEPOVER - run until next break <= current frame
 | |
| //    output: RUNNING
 | |
| //
 | |
| // STEPOUT - run until next break <= current frame - 1
 | |
| //    output: RUNNING
 | |
| //
 | |
| // EVAL tag frame expr - evaluate the expr in the console, on the frame'th stack frame
 | |
| //    output: EVALOUT tag exprResult
 | |
| //
 | |
| // FILELIST - list script files loaded
 | |
| //    output: FILELISTOUT file1 file2 file3 file4 ...
 | |
| //
 | |
| // BREAKLIST file - get a list of breakpoint-able lines in the file
 | |
| //    output: BREAKLISTOUT file skipBreakPairs skiplinecount breaklinecount skiplinecount breaklinecount ...
 | |
| //
 | |
| //
 | |
| // Other output:
 | |
| //
 | |
| // BREAK file1 line1 function1 file2 line2 function2 ... - Sent when the debugger hits a 
 | |
| //          breakpoint.  It lists out one file/line/function triplet for each stack level.
 | |
| //          The first one is the top of the stack.
 | |
| //
 | |
| // COUT console-output - echo of console output from engine
 | |
| //
 | |
| // BRKMOV file line newline - sent when a breakpoint is moved to a breakable line.
 | |
| //
 | |
| // BRKCLR file line - sent when a breakpoint cannot be moved to a breakable line on the client.
 | |
| //
 | |
| 
 | |
| 
 | |
| ConsoleFunction( dbgSetParameters, void, 3, 4, "(int port, string password, bool waitForClient)"
 | |
|                 "Open a debug server port on the specified port, requiring the specified password, "
 | |
| 				"and optionally waiting for the debug client to connect.")
 | |
| {
 | |
|    if (TelDebugger)
 | |
| 	   TelDebugger->setDebugParameters(dAtoi(argv[1]), argv[2], argc > 3 ? dAtob(argv[3]) : false );
 | |
| }
 | |
| 
 | |
| ConsoleFunction( dbgIsConnected, bool, 1, 1, "()"
 | |
|                 "Returns true if a script debugging client is connected else return false.")
 | |
| {
 | |
|    return TelDebugger && TelDebugger->isConnected();
 | |
| }
 | |
| 
 | |
| ConsoleFunction( dbgDisconnect, void, 1, 1, "()"
 | |
|                 "Forcibly disconnects any attached script debugging client.")
 | |
| {
 | |
|    if (TelDebugger)
 | |
| 	   TelDebugger->disconnect();
 | |
| }
 | |
| 
 | |
| static void debuggerConsumer(ConsoleLogEntry::Level level, const char *line)
 | |
| {
 | |
|    level;
 | |
|    if (TelDebugger)
 | |
|       TelDebugger->processConsoleLine(line);
 | |
| }
 | |
| 
 | |
| TelnetDebugger::TelnetDebugger()
 | |
| {
 | |
|    Con::addConsumer(debuggerConsumer);
 | |
| 
 | |
|    mAcceptPort = -1;
 | |
|    mAcceptSocket = InvalidSocket;
 | |
|    mDebugSocket = InvalidSocket;
 | |
| 
 | |
|    mState = NotConnected;
 | |
|    mCurPos = 0;
 | |
| 
 | |
|    mBreakpoints = NULL;
 | |
|    mBreakOnNextStatement = false;
 | |
|    mStackPopBreakIndex = -1;
 | |
|    mProgramPaused = false;
 | |
|    mWaitForClient = false;
 | |
| 
 | |
|    // Add the version number in a global so that
 | |
|    // scripts can detect the presence of the
 | |
|    // "enhanced" debugger features.
 | |
|    Con::evaluatef( "$dbgVersion = %d;", Version );
 | |
| }
 | |
| 
 | |
| TelnetDebugger::Breakpoint **TelnetDebugger::findBreakpoint(StringTableEntry fileName, S32 lineNumber)
 | |
| {
 | |
|    Breakpoint **walk = &mBreakpoints;
 | |
|    Breakpoint *cur;
 | |
|    while((cur = *walk) != NULL)
 | |
|    {
 | |
|       if(cur->fileName == fileName && cur->lineNumber == U32(lineNumber))
 | |
|          return walk;
 | |
|       walk = &cur->next;
 | |
|    }
 | |
|    return NULL;
 | |
| }
 | |
| 
 | |
| 
 | |
| TelnetDebugger::~TelnetDebugger()
 | |
| {
 | |
|    Con::removeConsumer(debuggerConsumer);
 | |
| 
 | |
|    if(mAcceptSocket != InvalidSocket)
 | |
|       Net::closeSocket(mAcceptSocket);
 | |
|    if(mDebugSocket != InvalidSocket)
 | |
|       Net::closeSocket(mDebugSocket);
 | |
| }
 | |
| 
 | |
| TelnetDebugger *TelDebugger = NULL;
 | |
| 
 | |
| void TelnetDebugger::create()
 | |
| {
 | |
|    TelDebugger = new TelnetDebugger;
 | |
| }
 | |
| 
 | |
| void TelnetDebugger::destroy()
 | |
| {
 | |
|    delete TelDebugger;
 | |
|    TelDebugger = NULL;
 | |
| }
 | |
| 
 | |
| void TelnetDebugger::send(const char *str)
 | |
| {
 | |
|    Net::send(mDebugSocket, (const unsigned char*)str, dStrlen(str));
 | |
| }
 | |
| 
 | |
| void TelnetDebugger::disconnect()
 | |
| {
 | |
|    if ( mDebugSocket != InvalidSocket )
 | |
|    {
 | |
|       Net::closeSocket(mDebugSocket);
 | |
|       mDebugSocket = InvalidSocket;
 | |
|    }
 | |
| 
 | |
|    removeAllBreakpoints();
 | |
| 
 | |
|    mState = NotConnected;
 | |
|    mProgramPaused = false;
 | |
| }
 | |
| 
 | |
| void TelnetDebugger::setDebugParameters(S32 port, const char *password, bool waitForClient)
 | |
| {
 | |
|    // Don't bail if same port... we might just be wanting to change
 | |
|    // the password.
 | |
| //   if(port == mAcceptPort)
 | |
| //      return;
 | |
| 
 | |
|    if(mAcceptSocket != InvalidSocket)
 | |
|    {
 | |
|       Net::closeSocket(mAcceptSocket);
 | |
|       mAcceptSocket = InvalidSocket;
 | |
|    }
 | |
|    mAcceptPort = port;
 | |
|    if(mAcceptPort != -1 && mAcceptPort != 0)
 | |
|    {
 | |
|       mAcceptSocket = Net::openSocket();
 | |
|       Net::bind(mAcceptSocket, mAcceptPort);
 | |
|       Net::listen(mAcceptSocket, 4);
 | |
| 
 | |
|       Net::setBlocking(mAcceptSocket, false);
 | |
|    }
 | |
|    dStrncpy(mDebuggerPassword, password, PasswordMaxLength);
 | |
| 
 | |
|    mWaitForClient = waitForClient;
 | |
|    if ( !mWaitForClient ) 
 | |
|       return;
 | |
| 
 | |
|    // Wait for the client to fully connect.
 | |
|    while ( mState != Connected  ) 
 | |
|    {
 | |
|       Platform::sleep(10);
 | |
|       process();
 | |
|    }
 | |
| 
 | |
| }
 | |
| 
 | |
| void TelnetDebugger::processConsoleLine(const char *consoleLine)
 | |
| {
 | |
|    if(mState != NotConnected)
 | |
|    {
 | |
|       send("COUT ");
 | |
|       send(consoleLine);
 | |
|       send("\r\n");
 | |
|    }
 | |
| }
 | |
| 
 | |
| void TelnetDebugger::process()
 | |
| {
 | |
|    NetAddress address;
 | |
| 
 | |
|    if(mAcceptSocket != InvalidSocket)
 | |
|    {
 | |
|       // ok, see if we have any new connections:
 | |
|       NetSocket newConnection;
 | |
|       newConnection = Net::accept(mAcceptSocket, &address);
 | |
| 
 | |
|       if(newConnection != InvalidSocket && mDebugSocket == InvalidSocket)
 | |
|       {
 | |
|    		Con::printf ("Debugger connection from %i.%i.%i.%i",
 | |
|    				address.netNum[0], address.netNum[1], address.netNum[2], address.netNum[3]);
 | |
| 
 | |
|          mState = PasswordTry;
 | |
|          mDebugSocket = newConnection;
 | |
| 
 | |
|          Net::setBlocking(newConnection, false);
 | |
|       }
 | |
|       else if(newConnection != InvalidSocket)
 | |
|          Net::closeSocket(newConnection);
 | |
|    }
 | |
|    // see if we have any input to process...
 | |
| 
 | |
|    if(mDebugSocket == InvalidSocket)
 | |
|       return;
 | |
| 
 | |
|    checkDebugRecv();
 | |
|    if(mDebugSocket == InvalidSocket)
 | |
|       removeAllBreakpoints();
 | |
| }
 | |
| 
 | |
| void TelnetDebugger::checkDebugRecv()
 | |
| {
 | |
|    for (;;) 
 | |
|    {
 | |
|       // Process all the complete commands in the buffer.
 | |
|       while ( mCurPos > 0 )
 | |
|       {
 | |
|          // Remove leading whitespace.
 | |
|          while ( mCurPos > 0 && ( mLineBuffer[0] == 0 || mLineBuffer[0] == '\r' || mLineBuffer[0] == '\n' ) )
 | |
|          {
 | |
|             mCurPos--;
 | |
|             dMemmove(mLineBuffer, mLineBuffer + 1, mCurPos);
 | |
|          }
 | |
| 
 | |
|          // Look for a complete command.
 | |
|          bool gotCmd = false;
 | |
|          for(S32 i = 0; i < mCurPos; i++)
 | |
|          {
 | |
|             if( mLineBuffer[i] == 0 )
 | |
|                mLineBuffer[i] = '_';
 | |
| 
 | |
|             else if ( mLineBuffer[i] == '\r' || mLineBuffer[i] == '\n' )
 | |
|             {
 | |
|                // Send this command to be processed.
 | |
|                mLineBuffer[i] = '\n';
 | |
|                processLineBuffer(i+1);
 | |
| 
 | |
|                // Remove the command from the buffer.
 | |
|                mCurPos -= i + 1;
 | |
|                dMemmove(mLineBuffer, mLineBuffer + i + 1, mCurPos);
 | |
| 
 | |
|                gotCmd = true;
 | |
|                break;
 | |
|             }
 | |
|          }
 | |
| 
 | |
|          // If we didn't find a command in this pass
 | |
|          // then we have an incomplete buffer.
 | |
|          if ( !gotCmd )
 | |
|             break;
 | |
|       }
 | |
| 
 | |
|       // found no <CR> or <LF>
 | |
|       if(mCurPos == MaxCommandSize) // this shouldn't happen
 | |
|       {
 | |
|          disconnect();
 | |
|          return;
 | |
|       }
 | |
| 
 | |
|       S32 numBytes;
 | |
|       Net::Error err = Net::recv(mDebugSocket, (unsigned char*)(mLineBuffer + mCurPos), MaxCommandSize - mCurPos, &numBytes);
 | |
| 
 | |
|       if((err != Net::NoError && err != Net::WouldBlock) || numBytes == 0)
 | |
|       {
 | |
|          disconnect();
 | |
|          return;
 | |
|       }
 | |
|       if(err == Net::WouldBlock)
 | |
|          return;
 | |
| 
 | |
|       mCurPos += numBytes;
 | |
|    }
 | |
| }
 | |
| 
 | |
| void TelnetDebugger::executionStopped(CodeBlock *code, U32 lineNumber)
 | |
| {
 | |
|    if(mProgramPaused)
 | |
|       return;
 | |
| 
 | |
|    if(mBreakOnNextStatement)
 | |
|    {
 | |
|       setBreakOnNextStatement( false );
 | |
|       breakProcess();
 | |
|       return;
 | |
|    }
 | |
| 
 | |
|    Breakpoint **bp = findBreakpoint(code->name, lineNumber);
 | |
|    if(!bp)
 | |
|       return;
 | |
|    
 | |
|    Breakpoint *brk = *bp;
 | |
|    mProgramPaused = true;
 | |
|    Con::evaluatef("$Debug::result = %s;", brk->testExpression);
 | |
|    if(Con::getBoolVariable("$Debug::result"))
 | |
|    {
 | |
|       brk->curCount++;
 | |
|       if(brk->curCount >= brk->passCount)
 | |
|       {
 | |
|          brk->curCount = 0;
 | |
|          if(brk->clearOnHit)
 | |
|             removeBreakpoint(code->name, lineNumber);
 | |
|          breakProcess();
 | |
|       }
 | |
|    }
 | |
|    mProgramPaused = false;
 | |
| }
 | |
| 
 | |
| void TelnetDebugger::pushStackFrame()
 | |
| {
 | |
|    if(mState == NotConnected)
 | |
|       return;
 | |
| 
 | |
|    if(mBreakOnNextStatement && mStackPopBreakIndex > -1 && 
 | |
|       gEvalState.stack.size() > mStackPopBreakIndex)
 | |
|       setBreakOnNextStatement( false );
 | |
| }
 | |
| 
 | |
| void TelnetDebugger::popStackFrame()
 | |
| {
 | |
|    if(mState == NotConnected)
 | |
|       return;
 | |
| 
 | |
|    if(mStackPopBreakIndex > -1 && gEvalState.stack.size()-1 <= mStackPopBreakIndex)
 | |
|       setBreakOnNextStatement( true );
 | |
| }
 | |
| 
 | |
| void TelnetDebugger::breakProcess()
 | |
| {
 | |
|    // Send out a break with the full stack.
 | |
|    sendBreak();
 | |
| 
 | |
|    mProgramPaused = true;
 | |
|    while(mProgramPaused)
 | |
|    {
 | |
|       Platform::sleep(10);
 | |
|       checkDebugRecv();
 | |
|       if(mDebugSocket == InvalidSocket)
 | |
|       {
 | |
|          mProgramPaused = false;
 | |
|          removeAllBreakpoints();
 | |
|          debugContinue();
 | |
|          return;
 | |
|       }
 | |
|    }
 | |
| }
 | |
| 
 | |
| void TelnetDebugger::sendBreak()
 | |
| {
 | |
|    // echo out the break
 | |
|    send("BREAK");
 | |
|    char buffer[MaxCommandSize];
 | |
|    char scope[MaxCommandSize];
 | |
| 
 | |
|    S32 last = 0;
 | |
| 
 | |
|    for(S32 i = (S32) gEvalState.stack.size() - 1; i >= last; i--)
 | |
|    {
 | |
|       CodeBlock *code = gEvalState.stack[i]->code;
 | |
|       const char *file = "<none>";
 | |
|       if (code && code->name && code->name[0])
 | |
|          file = code->name;
 | |
| 
 | |
|       Namespace *ns = gEvalState.stack[i]->scopeNamespace;
 | |
|       scope[0] = 0;
 | |
|       if ( ns ) {
 | |
|          
 | |
|          if ( ns->mParent && ns->mParent->mPackage && ns->mParent->mPackage[0] ) {
 | |
|             dStrcat( scope, ns->mParent->mPackage );
 | |
|             dStrcat( scope, "::" );
 | |
|          }
 | |
|          if ( ns->mName && ns->mName[0] ) {
 | |
|             dStrcat( scope, ns->mName );
 | |
|             dStrcat( scope, "::" );
 | |
|          }
 | |
|       }
 | |
| 
 | |
|       const char *function = gEvalState.stack[i]->scopeName;
 | |
|       if ((!function) || (!function[0]))
 | |
|          function = "<none>";
 | |
|       dStrcat( scope, function );
 | |
| 
 | |
|       U32 line=0, inst;
 | |
|       U32 ip = gEvalState.stack[i]->ip;
 | |
|       if (code)
 | |
|       code->findBreakLine(ip, line, inst);
 | |
|       dSprintf(buffer, MaxCommandSize, " %s %d %s", file, line, scope);
 | |
|       send(buffer);
 | |
|    }
 | |
| 
 | |
|    send("\r\n");
 | |
| }
 | |
| 
 | |
| void TelnetDebugger::processLineBuffer(S32 cmdLen)
 | |
| {
 | |
|    if (mState == PasswordTry)
 | |
|    {
 | |
|       if(dStrncmp(mLineBuffer, mDebuggerPassword, cmdLen-1))
 | |
|       {
 | |
|          // failed password:
 | |
|          send("PASS WrongPassword.\r\n");
 | |
|          disconnect();
 | |
|       }
 | |
|       else
 | |
|       {
 | |
|          send("PASS Connected.\r\n");
 | |
|          mState = mWaitForClient ? Initialize : Connected;
 | |
|       }
 | |
| 
 | |
|       return;
 | |
|    }
 | |
|    else
 | |
|    {
 | |
|       char evalBuffer[MaxCommandSize];
 | |
|       char varBuffer[MaxCommandSize];
 | |
|       char fileBuffer[MaxCommandSize];
 | |
|       char clear[MaxCommandSize];
 | |
|       S32 passCount, line, frame;
 | |
| 
 | |
|       if(dSscanf(mLineBuffer, "CEVAL %[^\n]", evalBuffer) == 1)
 | |
|       {
 | |
|          ConsoleEvent postEvent;
 | |
|          dStrcpy(postEvent.data, evalBuffer);
 | |
|          postEvent.size = ConsoleEventHeaderSize + dStrlen(evalBuffer) + 1;
 | |
|          Game->postEvent(postEvent);
 | |
|       }
 | |
|       else if(dSscanf(mLineBuffer, "BRKVARSET %s %d %[^\n]", varBuffer, &passCount, evalBuffer) == 3)
 | |
|          addVariableBreakpoint(varBuffer, passCount, evalBuffer);
 | |
|       else if(dSscanf(mLineBuffer, "BRKVARCLR %s", varBuffer) == 1)
 | |
|          removeVariableBreakpoint(varBuffer);
 | |
|       else if(dSscanf(mLineBuffer, "BRKSET %s %d %s %d %[^\n]", fileBuffer,&line,&clear,&passCount,evalBuffer) == 5)
 | |
|          addBreakpoint(fileBuffer, line, dAtob(clear), passCount, evalBuffer);
 | |
|       else if(dSscanf(mLineBuffer, "BRKCLR %s %d", fileBuffer, &line) == 2)
 | |
|          removeBreakpoint(fileBuffer, line);
 | |
|       else if(!dStrncmp(mLineBuffer, "BRKCLRALL\n", cmdLen))
 | |
|          removeAllBreakpoints();
 | |
|       else if(!dStrncmp(mLineBuffer, "BRKNEXT\n", cmdLen))
 | |
|          debugBreakNext();
 | |
|       else if(!dStrncmp(mLineBuffer, "CONTINUE\n", cmdLen))
 | |
|          debugContinue();
 | |
|       else if(!dStrncmp(mLineBuffer, "STEPIN\n", cmdLen))
 | |
|          debugStepIn();
 | |
|       else if(!dStrncmp(mLineBuffer, "STEPOVER\n", cmdLen))
 | |
|          debugStepOver();
 | |
|       else if(!dStrncmp(mLineBuffer, "STEPOUT\n", cmdLen))
 | |
|          debugStepOut();
 | |
|       else if(dSscanf(mLineBuffer, "EVAL %s %d %[^\n]", varBuffer, &frame, evalBuffer) == 3)
 | |
|          evaluateExpression(varBuffer, frame, evalBuffer);
 | |
|       else if(!dStrncmp(mLineBuffer, "FILELIST\n", cmdLen))
 | |
|          dumpFileList();
 | |
|       else if(dSscanf(mLineBuffer, "BREAKLIST %s", fileBuffer) == 1)
 | |
|          dumpBreakableList(fileBuffer);
 | |
|       else
 | |
|       {
 | |
|          // invalid stuff.
 | |
|          send("DBGERR Invalid command!\r\n");
 | |
|       }
 | |
|    }
 | |
| }
 | |
| 
 | |
| void TelnetDebugger::addVariableBreakpoint(const char*, S32, const char*)
 | |
| {
 | |
|    send("addVariableBreakpoint\r\n");
 | |
| }
 | |
| 
 | |
| void TelnetDebugger::removeVariableBreakpoint(const char*)
 | |
| {
 | |
|    send("removeVariableBreakpoint\r\n");
 | |
| }
 | |
| 
 | |
| void TelnetDebugger::addAllBreakpoints(CodeBlock *code)
 | |
| {
 | |
|    if(mState == NotConnected)
 | |
|       return;
 | |
|    
 | |
|    char buffer[MaxCommandSize];
 | |
|    dSprintf(buffer, MaxCommandSize, "LOAD %s\r\n", code->name);
 | |
|    send(buffer);
 | |
| 
 | |
|    // Find the breakpoints for this code block and attach them.
 | |
|    Breakpoint **walk = &mBreakpoints;
 | |
|    Breakpoint *cur;
 | |
|    while((cur = *walk) != NULL)
 | |
|    {
 | |
|       if(cur->fileName == code->name)
 | |
| 	   {
 | |
|          cur->code = code;
 | |
| 
 | |
|          // Find the fist breakline starting from and
 | |
|          // including the requested breakline.
 | |
|          S32 newLine = code->findFirstBreakLine(cur->lineNumber);
 | |
|          if (newLine <= 0) 
 | |
|          {
 | |
|             walk = &cur->next;
 | |
| 
 | |
|             dSprintf(buffer, MaxCommandSize, "BRKCLR %s %d\r\n", cur->fileName, cur->lineNumber);
 | |
|             send(buffer);
 | |
| 
 | |
|             removeBreakpoint(cur->fileName, cur->lineNumber);
 | |
|             continue;
 | |
|          }
 | |
| 
 | |
|          // If the requested breakline does not match
 | |
|          // the actual break line we need to inform
 | |
|          // the client.
 | |
|          if (newLine != cur->lineNumber)
 | |
|          {
 | |
|             // If we already have a line at this breapoint then
 | |
|             // tell the client to clear the breakpoint.
 | |
|             if ( findBreakpoint(cur->fileName, newLine) ) {
 | |
| 
 | |
|                walk = &cur->next;
 | |
| 
 | |
|                dSprintf(buffer, MaxCommandSize, "BRKCLR %s %d\r\n", cur->fileName, cur->lineNumber);
 | |
|                send(buffer);
 | |
| 
 | |
|                removeBreakpoint(cur->fileName, cur->lineNumber);
 | |
|                continue;
 | |
|             }
 | |
| 
 | |
|             // We're moving the breakpoint to new line... inform the 
 | |
|             // client so it can update it's view.
 | |
|             dSprintf(buffer, MaxCommandSize, "BRKMOV %s %d %d\r\n", cur->fileName, cur->lineNumber, newLine);
 | |
|             send(buffer);
 | |
|             cur->lineNumber = newLine;
 | |
|          }
 | |
| 
 | |
|          code->setBreakpoint(cur->lineNumber);
 | |
|       }
 | |
| 
 | |
|       walk = &cur->next;
 | |
|    }
 | |
| 
 | |
|    // Enable all breaks if a break next was set.
 | |
|    if (mBreakOnNextStatement)
 | |
|        code->setAllBreaks();
 | |
| }
 | |
| 
 | |
| void TelnetDebugger::clearCodeBlockPointers(CodeBlock *code)
 | |
| {
 | |
|    Breakpoint **walk = &mBreakpoints;
 | |
|    Breakpoint *cur;
 | |
|    
 | |
|    while((cur = *walk) != NULL)
 | |
|    {
 | |
|       if(cur->code == code)
 | |
|          cur->code = NULL;
 | |
| 
 | |
|       walk = &cur->next;
 | |
|    }
 | |
| }
 | |
| 
 | |
| void TelnetDebugger::addBreakpoint(const char *fileName, S32 line, bool clear, S32 passCount, const char *evalString)
 | |
| {
 | |
|    fileName = StringTable->insert(fileName);
 | |
|    Breakpoint **bp = findBreakpoint(fileName, line);
 | |
| 
 | |
|    if(bp)
 | |
|    {
 | |
|       // trying to add the same breakpoint...
 | |
|       Breakpoint *brk = *bp;
 | |
|       dFree(brk->testExpression);
 | |
|       brk->testExpression = dStrdup(evalString);
 | |
|       brk->passCount = passCount;
 | |
|       brk->clearOnHit = clear;
 | |
|       brk->curCount = 0;
 | |
|    }
 | |
|    else
 | |
|    {
 | |
|       // Note that if the code block is not already 
 | |
|       // loaded it is handled by addAllBreakpoints.
 | |
|       CodeBlock* code = CodeBlock::find(fileName);
 | |
|       if (code)
 | |
|       {
 | |
|          // Find the fist breakline starting from and
 | |
|          // including the requested breakline.
 | |
|          S32 newLine = code->findFirstBreakLine(line);
 | |
|          if (newLine <= 0) 
 | |
|          {
 | |
|             char buffer[MaxCommandSize];
 | |
|             dSprintf(buffer, MaxCommandSize, "BRKCLR %s %d\r\n", fileName, line);
 | |
|             send(buffer);
 | |
|             return;
 | |
|          }
 | |
| 
 | |
|          // If the requested breakline does not match
 | |
|          // the actual break line we need to inform
 | |
|          // the client.
 | |
|          if (newLine != line)
 | |
|          {
 | |
|             char buffer[MaxCommandSize];
 | |
| 
 | |
|             // If we already have a line at this breapoint then
 | |
|             // tell the client to clear the breakpoint.
 | |
|             if ( findBreakpoint(fileName, newLine) ) {
 | |
|                dSprintf(buffer, MaxCommandSize, "BRKCLR %s %d\r\n", fileName, line);
 | |
|                send(buffer);
 | |
|                return;
 | |
|             }
 | |
| 
 | |
|             // We're moving the breakpoint to new line... inform the client.
 | |
|             dSprintf(buffer, MaxCommandSize, "BRKMOV %s %d %d\r\n", fileName, line, newLine);
 | |
|             send(buffer);
 | |
|             line = newLine;
 | |
|          }
 | |
| 
 | |
|          code->setBreakpoint(line);
 | |
|       }
 | |
| 
 | |
|       Breakpoint *brk = new Breakpoint;
 | |
|       brk->code = code;
 | |
|       brk->fileName = fileName;
 | |
|       brk->lineNumber = line;
 | |
|       brk->passCount = passCount;
 | |
|       brk->clearOnHit = clear;
 | |
|       brk->curCount = 0;
 | |
|       brk->testExpression = dStrdup(evalString);
 | |
|       brk->next = mBreakpoints;
 | |
|       mBreakpoints = brk;
 | |
|    }
 | |
| }
 | |
| 
 | |
| void TelnetDebugger::removeBreakpointsFromCode(CodeBlock *code)
 | |
| {
 | |
|    Breakpoint **walk = &mBreakpoints;
 | |
|    Breakpoint *cur;
 | |
|    while((cur = *walk) != NULL)
 | |
|    {
 | |
|       if(cur->code == code)
 | |
|       {
 | |
|          dFree(cur->testExpression);
 | |
|          *walk = cur->next;
 | |
|          delete walk;
 | |
|       }
 | |
|       else
 | |
|          walk = &cur->next;
 | |
|    }
 | |
| }
 | |
| 
 | |
| void TelnetDebugger::removeBreakpoint(const char *fileName, S32 line)
 | |
| {
 | |
|    fileName = StringTable->insert(fileName);
 | |
|    Breakpoint **bp = findBreakpoint(fileName, line);
 | |
|    if(bp)
 | |
|    {
 | |
|       Breakpoint *brk = *bp;
 | |
|       *bp = brk->next;
 | |
| 	  if ( brk->code )
 | |
|           brk->code->clearBreakpoint(brk->lineNumber);
 | |
|       dFree(brk->testExpression);
 | |
|       delete brk;
 | |
|    }
 | |
| }
 | |
| 
 | |
| void TelnetDebugger::removeAllBreakpoints()
 | |
| {
 | |
|    Breakpoint *walk = mBreakpoints;
 | |
|    while(walk)
 | |
|    {
 | |
|       Breakpoint *temp = walk->next;
 | |
| 	  if ( walk->code )
 | |
|           walk->code->clearBreakpoint(walk->lineNumber);
 | |
|       dFree(walk->testExpression);
 | |
|       delete walk;
 | |
|       walk = temp;
 | |
|    }
 | |
|    mBreakpoints = NULL;
 | |
| }
 | |
| 
 | |
| void TelnetDebugger::debugContinue()
 | |
| {
 | |
|    if (mState == Initialize) {
 | |
|       mState = Connected;
 | |
|       return;
 | |
|    }
 | |
| 
 | |
|    setBreakOnNextStatement( false );
 | |
|    mStackPopBreakIndex = -1;
 | |
|    mProgramPaused = false;
 | |
|    send("RUNNING\r\n");
 | |
| }
 | |
| 
 | |
| void TelnetDebugger::setBreakOnNextStatement( bool enabled )
 | |
| {
 | |
|    if ( enabled )
 | |
|    {
 | |
|       // Apply breaks on all the code blocks.
 | |
|       for(CodeBlock *walk = CodeBlock::getCodeBlockList(); walk; walk = walk->nextFile)
 | |
|          walk->setAllBreaks();
 | |
|       mBreakOnNextStatement = true;
 | |
|    } 
 | |
|    else if ( !enabled )
 | |
|    {
 | |
|       // Clear all the breaks on the codeblocks 
 | |
|       // then go reapply the breakpoints.
 | |
|       for(CodeBlock *walk = CodeBlock::getCodeBlockList(); walk; walk = walk->nextFile)
 | |
|          walk->clearAllBreaks();
 | |
|       for(Breakpoint *w = mBreakpoints; w; w = w->next)
 | |
| 	  {
 | |
| 		  if ( w->code )
 | |
|               w->code->setBreakpoint(w->lineNumber);
 | |
| 	  }
 | |
|       mBreakOnNextStatement = false;
 | |
|    }
 | |
| }
 | |
| 
 | |
| void TelnetDebugger::debugBreakNext()
 | |
| {
 | |
|    if (mState != Connected)
 | |
|       return;
 | |
| 
 | |
|    if ( !mProgramPaused ) 
 | |
|       setBreakOnNextStatement( true );
 | |
| }
 | |
| 
 | |
| void TelnetDebugger::debugStepIn()
 | |
| {
 | |
|    // Note that step in is allowed during
 | |
|    // the initialize state, so that we can
 | |
|    // break on the first script line executed.
 | |
| 
 | |
|    setBreakOnNextStatement( true );
 | |
|    mStackPopBreakIndex = -1;
 | |
|    mProgramPaused = false;
 | |
| 
 | |
|    // Don't bother sending this to the client
 | |
|    // if it's in the initialize state.  It will
 | |
|    // just be ignored as the client knows it
 | |
|    // is in a running state when it connects.
 | |
|    if (mState != Initialize)
 | |
|       send("RUNNING\r\n");
 | |
|    else 
 | |
|       mState = Connected;
 | |
| }
 | |
| 
 | |
| void TelnetDebugger::debugStepOver()
 | |
| {
 | |
|    if (mState != Connected)
 | |
|       return;
 | |
| 
 | |
|    setBreakOnNextStatement( true );
 | |
|    mStackPopBreakIndex = gEvalState.stack.size();
 | |
|    mProgramPaused = false;
 | |
|    send("RUNNING\r\n");
 | |
| }
 | |
| 
 | |
| void TelnetDebugger::debugStepOut()
 | |
| {
 | |
|    if (mState != Connected)
 | |
|       return;
 | |
| 
 | |
|    setBreakOnNextStatement( false );
 | |
|    mStackPopBreakIndex = gEvalState.stack.size() - 1;
 | |
|    if ( mStackPopBreakIndex == 0 )
 | |
| 	   mStackPopBreakIndex = -1;
 | |
|    mProgramPaused = false;
 | |
|    send("RUNNING\r\n");
 | |
| }
 | |
| 
 | |
| void TelnetDebugger::evaluateExpression(const char *tag, S32 frame, const char *evalBuffer)
 | |
| {
 | |
|    // Make sure we're passing a valid frame to the eval.
 | |
|    if ( frame > gEvalState.stack.size() )
 | |
|       frame = gEvalState.stack.size() - 1;
 | |
|    if ( frame < 0 )
 | |
|       frame = 0;
 | |
| 
 | |
|    // Build a buffer just big enough for this eval.
 | |
|    const char* format = "return %s;";
 | |
|    dsize_t len = dStrlen( format ) + dStrlen( evalBuffer );
 | |
|    char* buffer = new char[ len ];
 | |
|    dSprintf( buffer, len, format, evalBuffer );
 | |
| 
 | |
|    // Execute the eval.
 | |
|    CodeBlock *newCodeBlock = new CodeBlock();
 | |
|    const char* result = newCodeBlock->compileExec( NULL, buffer, false, frame );
 | |
|    delete [] buffer;
 | |
|    
 | |
|    // Create a new buffer that fits the result.
 | |
|    format = "EVALOUT %s %s\r\n";
 | |
|    len = dStrlen( format ) + dStrlen( tag ) + dStrlen( result );
 | |
|    buffer = new char[ len ];
 | |
|    dSprintf( buffer, len, format, tag, result[0] ? result : "\"\"" );
 | |
| 
 | |
|    send( buffer );
 | |
|    delete [] buffer;
 | |
| }
 | |
| 
 | |
| void TelnetDebugger::dumpFileList()
 | |
| {
 | |
|    send("FILELISTOUT ");
 | |
|    for(CodeBlock *walk = CodeBlock::getCodeBlockList(); walk; walk = walk->nextFile)
 | |
|    {
 | |
|       send(walk->name);
 | |
|       if(walk->nextFile)
 | |
|          send(" ");
 | |
|    }
 | |
|    send("\r\n");
 | |
| }
 | |
| 
 | |
| void TelnetDebugger::dumpBreakableList(const char *fileName)
 | |
| {
 | |
|    fileName = StringTable->insert(fileName);
 | |
|    CodeBlock *file = CodeBlock::find(fileName);
 | |
|    char buffer[MaxCommandSize];
 | |
|    if(file)
 | |
|    {
 | |
|       dSprintf(buffer, MaxCommandSize, "BREAKLISTOUT %s %d", fileName, file->breakListSize >> 1);
 | |
|       send(buffer);
 | |
|       for(U32 i = 0; i < file->breakListSize; i += 2)
 | |
|       {
 | |
|          dSprintf(buffer, MaxCommandSize, " %d %d", file->breakList[i], file->breakList[i+1]);
 | |
|          send(buffer);
 | |
|       }
 | |
|       send("\r\n");
 | |
|    }
 | |
|    else
 | |
|       send("DBGERR No such file!\r\n");
 | |
| }
 | 
