123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975 |
- //-----------------------------------------------------------------------------
- // Copyright (c) 2012 GarageGames, LLC
- //
- // Permission is hereby granted, free of charge, to any person obtaining a copy
- // of this software and associated documentation files (the "Software"), to
- // deal in the Software without restriction, including without limitation the
- // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
- // sell copies of the Software, and to permit persons to whom the Software is
- // furnished to do so, subject to the following conditions:
- //
- // The above copyright notice and this permission notice shall be included in
- // all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
- // IN THE SOFTWARE.
- //-----------------------------------------------------------------------------
- #include "platform/platform.h"
- #include "console/telnetDebugger.h"
- #include "core/frameAllocator.h"
- #include "console/console.h"
- #include "console/engineAPI.h"
- #include "core/stringTable.h"
- #include "console/consoleInternal.h"
- #include "console/ast.h"
- #include "console/compiler.h"
- #include "core/util/journal/process.h"
- #include "core/module.h"
- MODULE_BEGIN( TelnetDebugger )
- MODULE_INIT
- {
- TelnetDebugger::create();
- }
-
- MODULE_SHUTDOWN
- {
- TelnetDebugger::destroy();
- }
- MODULE_END;
- //
- // 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.
- //
- DefineEngineFunction( 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 for the debug client to connect.\n"
- "@internal Primarily used for Torsion and other debugging tools")
- {
- if (TelDebugger)
- {
- TelDebugger->setDebugParameters(port, password, waitForClient );
- }
- }
- DefineEngineFunction( dbgIsConnected, bool, (), , "()"
- "Returns true if a script debugging client is connected else return false.\n"
- "@internal Primarily used for Torsion and other debugging tools")
- {
- return TelDebugger && TelDebugger->isConnected();
- }
- DefineEngineFunction( dbgDisconnect, void, (), , "()"
- "Forcibly disconnects any attached script debugging client.\n"
- "@internal Primarily used for Torsion and other debugging tools")
- {
- if (TelDebugger)
- TelDebugger->disconnect();
- }
- static void debuggerConsumer(U32 level, const char *line)
- {
- TORQUE_UNUSED(level);
- if (TelDebugger)
- TelDebugger->processConsoleLine(line);
- }
- TelnetDebugger::TelnetDebugger()
- {
- Con::addConsumer(debuggerConsumer);
- mAcceptPort = -1;
- mAcceptSocket = NetSocket::INVALID;
- mDebugSocket = NetSocket::INVALID;
- mState = NotConnected;
- mCurPos = 0;
- mBreakpoints = NULL;
- mBreakOnNextStatement = false;
- mStackPopBreakIndex = -1;
- mProgramPaused = false;
- mWaitForClient = false;
- dStrncpy(mDebuggerPassword, "", PasswordMaxLength);
- dStrncpy(mLineBuffer, "", sizeof(mLineBuffer));
-
- // 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)
- {
- // TODO: This assumes that the OS file names are case
- // insensitive... Torque needs a dFilenameCmp() function.
- if( dStricmp( cur->fileName, fileName ) == 0 && cur->lineNumber == U32(lineNumber))
- return walk;
- walk = &cur->next;
- }
- return NULL;
- }
- TelnetDebugger::~TelnetDebugger()
- {
- Con::removeConsumer(debuggerConsumer);
- if(mAcceptSocket != NetSocket::INVALID)
- Net::closeSocket(mAcceptSocket);
- if(mDebugSocket != NetSocket::INVALID)
- Net::closeSocket(mDebugSocket);
- }
- TelnetDebugger *TelDebugger = NULL;
- void TelnetDebugger::create()
- {
- TelDebugger = new TelnetDebugger;
- Process::notify(TelDebugger, &TelnetDebugger::process, PROCESS_FIRST_ORDER);
- }
- void TelnetDebugger::destroy()
- {
- Process::remove(TelDebugger, &TelnetDebugger::process);
- delete TelDebugger;
- TelDebugger = NULL;
- }
- void TelnetDebugger::send(const char *str)
- {
- Net::send(mDebugSocket, (const unsigned char*)str, dStrlen(str));
- }
- void TelnetDebugger::disconnect()
- {
- if ( mDebugSocket != NetSocket::INVALID )
- {
- Net::closeSocket(mDebugSocket);
- mDebugSocket = NetSocket::INVALID;
- }
- 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 != NetSocket::INVALID)
- {
- Net::closeSocket(mAcceptSocket);
- mAcceptSocket = NetSocket::INVALID;
- }
- mAcceptPort = port;
- if(mAcceptPort != -1 && mAcceptPort != 0)
- {
- NetAddress address;
- Net::getIdealListenAddress(&address);
- address.port = mAcceptPort;
- mAcceptSocket = Net::openSocket();
- Net::bindAddress(address, mAcceptSocket);
- 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 != NetSocket::INVALID)
- {
- // ok, see if we have any new connections:
- NetSocket newConnection;
- newConnection = Net::accept(mAcceptSocket, &address);
- if(newConnection != NetSocket::INVALID && mDebugSocket == NetSocket::INVALID)
- {
- char buffer[256];
- Net::addressToString(&address, buffer);
- Con::printf("Debugger connection from %s", buffer);
- mState = PasswordTry;
- mDebugSocket = newConnection;
- Net::setBlocking(newConnection, false);
- }
- else if(newConnection != NetSocket::INVALID)
- Net::closeSocket(newConnection);
- }
- // see if we have any input to process...
- if(mDebugSocket == NetSocket::INVALID)
- return;
- checkDebugRecv();
- if(mDebugSocket == NetSocket::INVALID)
- 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.getStackDepth() > mStackPopBreakIndex)
- setBreakOnNextStatement( false );
- }
- void TelnetDebugger::popStackFrame()
- {
- if(mState == NotConnected)
- return;
- if(mStackPopBreakIndex > -1 && gEvalState.getStackDepth()-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 == NetSocket::INVALID)
- {
- 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.getStackDepth() - 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, MaxCommandSize );
- dStrcat( scope, "::", MaxCommandSize );
- }
- if ( ns->mName && ns->mName[0] ) {
- dStrcat( scope, ns->mName, MaxCommandSize );
- dStrcat( scope, "::", MaxCommandSize );
- }
- }
- const char *function = gEvalState.stack[i]->scopeName;
- if ((!function) || (!function[0]))
- function = "<none>";
- dStrcat( scope, function, MaxCommandSize );
- 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)
- {
- RawData rd;
- rd.size = dStrlen(evalBuffer) + 1;
- rd.data = ( S8* ) evalBuffer;
- Con::smConsoleInput.trigger(rd);
- }
- 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
- {
- S32 errorLen = dStrlen(mLineBuffer) + 32; // ~25 in error message, plus buffer
- FrameTemp<char> errorBuffer(errorLen);
- dSprintf( errorBuffer, errorLen, "DBGERR Invalid command(%s)!\r\n", mLineBuffer );
- // invalid stuff.
- send( errorBuffer );
- }
- }
- }
- 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;
- // Find the breakpoints for this code block and attach them.
- Breakpoint *cur = mBreakpoints;
- while( cur != NULL )
- {
- // TODO: This assumes that the OS file names are case
- // insensitive... Torque needs a dFilenameCmp() function.
- if( dStricmp( cur->fileName, code->name ) == 0 )
- {
- cur->code = code;
- // Find the fist breakline starting from and
- // including the requested breakline.
- S32 newLine = code->findFirstBreakLine(cur->lineNumber);
- if (newLine <= 0)
- {
- char buffer[MaxCommandSize];
- dSprintf(buffer, MaxCommandSize, "BRKCLR %s %d\r\n", cur->fileName, cur->lineNumber);
- send(buffer);
- Breakpoint *next = cur->next;
- removeBreakpoint(cur->fileName, cur->lineNumber);
- cur = next;
- continue;
- }
- // If the requested breakline does not match
- // the actual break line we need to inform
- // the client.
- if (newLine != cur->lineNumber)
- {
- char buffer[MaxCommandSize];
- // If we already have a line at this breapoint then
- // tell the client to clear the breakpoint.
- if ( findBreakpoint(cur->fileName, newLine) ) {
- dSprintf(buffer, MaxCommandSize, "BRKCLR %s %d\r\n", cur->fileName, cur->lineNumber);
- send(buffer);
- Breakpoint *next = cur->next;
- removeBreakpoint(cur->fileName, cur->lineNumber);
- cur = next;
- 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);
- }
- cur = cur->next;
- }
- // Enable all breaks if a break next was set.
- if (mBreakOnNextStatement)
- code->setAllBreaks();
- }
- 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.getStackDepth();
- mProgramPaused = false;
- send("RUNNING\r\n");
- }
- void TelnetDebugger::debugStepOut()
- {
- if (mState != Connected)
- return;
- setBreakOnNextStatement( false );
- mStackPopBreakIndex = gEvalState.getStackDepth() - 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.getStackDepth() )
- frame = gEvalState.getStackDepth() - 1;
- if ( frame < 0 )
- frame = 0;
- // Local variables use their own memory management and can't be queried by just executing
- // TorqueScript, we have to go digging into the interpreter.
- S32 evalBufferLen = dStrlen(evalBuffer);
- bool isEvaluatingLocalVariable = evalBufferLen > 0 && evalBuffer[0] == '%';
- if (isEvaluatingLocalVariable)
- {
- // See calculation of current frame in pushing a reference frame for console exec, we need access
- // to the proper scope.
- //frame = gEvalState.getTopOfStack() - frame - 1;
- S32 stackIndex = gEvalState.getTopOfStack() - frame - 1;
- const char* format = "EVALOUT %s %s\r\n";
- gEvalState.pushDebugFrame(stackIndex);
- Dictionary& stackFrame = gEvalState.getCurrentFrame();
- StringTableEntry functionName = stackFrame.scopeName;
- StringTableEntry namespaceName = stackFrame.scopeNamespace->mName;
- StringTableEntry varToLookup = StringTable->insert(evalBuffer);
- S32 registerId = stackFrame.code->variableRegisterTable.lookup(namespaceName, functionName, varToLookup);
- if (registerId == -1)
- {
- // ERROR, can't read the variable!
- send("EVALOUT \"\" \"\"");
- return;
- }
- const char* varResult = gEvalState.getLocalStringVariable(registerId);
- gEvalState.popFrame();
- S32 len = dStrlen(format) + dStrlen(tag) + dStrlen(varResult);
- char* buffer = new char[len];
- dSprintf(buffer, len, format, tag, varResult[0] ? varResult : "\"\"");
- send(buffer);
- delete[] buffer;
- return;
- }
- // 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();
- ConsoleValue 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.getString() );
- buffer = new char[ len ];
- dSprintf( buffer, len, format, tag, result.getString()[0] ? result.getString() : "\"\"" );
- 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");
- }
- void TelnetDebugger::clearCodeBlockPointers(CodeBlock *code)
- {
- Breakpoint **walk = &mBreakpoints;
- Breakpoint *cur;
- while((cur = *walk) != NULL)
- {
- if(cur->code == code)
- cur->code = NULL;
- walk = &cur->next;
- }
- }
|