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");
|
|
}
|