engine/console/telnetDebugger.cc
2024-01-07 04:36:33 +00:00

558 lines
15 KiB
C++

//-----------------------------------------------------------------------------
// V12 Engine
//
// Copyright (c) 2001 GarageGames.Com
// Portions Copyright (c) 2001 by Sierra Online, 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"
//------------------------------------------------------------
// debugger commands:
// CEVAL console line - evaluate the console line
// output: none
// BRKVARSET varName passct expr
// output: none
// BRKVARCLR varName
// 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: 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:
//
// when the debugger hits a breakpoint, it lists out:
// BREAK file1 line1 fn1 file2 line2 fn2 file3 line3 fn3 file4 line4 fn4 etc.
// where file1 line1 fn1 ... etc is the current call stack.
// COUT echo out a console line
static void cDebugSetParams(SimObject *, S32, const char **argv)
{
TelDebugger->setDebugParameters(dAtoi(argv[1]), argv[2]);
}
static void debuggerConsumer(ConsoleLogEntry::Level level, const char *line)
{
TelDebugger->processConsoleLine(line);
}
TelnetDebugger::TelnetDebugger()
{
Con::addCommand("dbgSetParameters", cDebugSetParams, "dbgSetParameters(port,pass);", 3, 3);
Con::addConsumer(debuggerConsumer);
mAcceptPort = -1;
mAcceptSocket = InvalidSocket;
mDebugSocket = InvalidSocket;
mState = NotConnected;
mBreakpoints = NULL;
mBreakOnNextStatement = false;
mStackPopBreakIndex = -1;
mProgramPaused = false;
}
TelnetDebugger::Breakpoint **TelnetDebugger::findBreakpoint(StringTableEntry fileName, S32 lineNumber)
{
Breakpoint **walk = &mBreakpoints;
Breakpoint *cur;
while((cur = *walk) != NULL)
{
if(cur->code->name == 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::setDebugParameters(S32 port, const char *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);
}
void TelnetDebugger::processConsoleLine(const char *consoleLine)
{
if(mState == Connected)
{
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()
{
S32 checked = false;
for(;;) {
// check for and recv one command:
for(S32 i = 0; i < mCurPos; i++)
{
if(mLineBuffer[i] == '\r' || mLineBuffer[i] == '\n')
{
if(i == 0)
{
mCurPos--;
dMemmove(mLineBuffer, mLineBuffer + 1, mCurPos);
}
else
{
mLineBuffer[i] = '\n';
processLineBuffer(i+1);
mCurPos -= i + 1;
dMemmove(mLineBuffer, mLineBuffer + i + 1, mCurPos);
return;
}
}
else if(mLineBuffer[i] == 0)
mLineBuffer[i] = '_';
}
// found no <CR> or <LF>
if(mCurPos == MaxCommandSize) // this shouldn't happen
{
Net::closeSocket(mDebugSocket);
mDebugSocket = InvalidSocket;
mState = NotConnected;
return;
}
if(checked)
return;
checked = false;
S32 numBytes;
Net::Error err = Net::recv(mDebugSocket, (unsigned char*)(mLineBuffer + mCurPos), MaxCommandSize - mCurPos, &numBytes);
if((err != Net::NoError && err != Net::WouldBlock) || numBytes == 0)
{
Net::closeSocket(mDebugSocket);
mDebugSocket = InvalidSocket;
mState = NotConnected;
return;
}
if(err == Net::WouldBlock)
return;
mCurPos += numBytes;
}
}
void TelnetDebugger::executionStopped(CodeBlock *code, U32 lineNumber)
{
if(mProgramPaused)
return;
if(mBreakOnNextStatement)
{
// loop through all the codeblocks clearing the breaks
for(CodeBlock *walk = codeBlockList; walk; walk = walk->nextFile)
walk->clearAllBreaks();
for(Breakpoint *w = mBreakpoints; w; w = w->next)
w->code->setBreakpoint(w->lineNumber);
breakProcess();
return;
}
Breakpoint **bp = findBreakpoint(code->name, lineNumber);
if(!bp)
return;
Breakpoint *brk = *bp;
mProgramPaused = true;
Con::evaluatef("$dbgResult = %s;", brk->testExpression);
if(Con::getBoolVariable("$dbgResult"))
{
brk->curCount++;
if(brk->curCount >= brk->passCount)
{
brk->curCount = 0;
if(brk->clearOnHit)
removeBreakpoint(code->name, lineNumber);
breakProcess();
}
}
mProgramPaused = false;
}
void TelnetDebugger::popStackFrame()
{
if(mState == NotConnected)
return;
if(U32(mStackPopBreakIndex) == gEvalState.stack.size())
breakOnNextStatement();
}
void TelnetDebugger::breakProcess()
{
mProgramPaused = true;
// echo out the break
send("BREAK");
char buffer[MaxCommandSize];
for(S32 i = (S32) gEvalState.stack.size() - 1; i >= 0; i--)
{
CodeBlock *code = gEvalState.stack[i]->code;
U32 ip = gEvalState.stack[i]->ip;
const char *file = code->name;
const char *scope = gEvalState.stack[i]->scopeName;
if ((! file) || (! file[0]))
file = "N/A";
if ((! scope) || (! scope[0]))
scope = "N/A";
U32 line, inst;
code->findBreakLine(ip, line, inst);
dSprintf(buffer, MaxCommandSize, " %s %d %s", file, line, scope);
send(buffer);
}
send("\r\n");
while(mProgramPaused)
{
checkDebugRecv();
if(mDebugSocket == InvalidSocket)
{
mProgramPaused = false;
removeAllBreakpoints();
debugContinue();
return;
}
}
}
void TelnetDebugger::processLineBuffer(S32 cmdLen)
{
if (mState == PasswordTry)
{
if(dStrncmp(mLineBuffer, mDebuggerPassword, cmdLen-1))
{
// failed password:
send("PASS WrongPassword.\r\n");
Net::closeSocket(mDebugSocket);
mDebugSocket = InvalidSocket;
mState = NotConnected;
}
else
{
send("PASS Connected.\r\n");
mState = Connected;
}
}
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, "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::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
{
CodeBlock *code = CodeBlock::find(fileName);
if(code)
{
Breakpoint *brk = new Breakpoint;
brk->code = code;
code->setBreakpoint(line);
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;
brk->code->clearBreakpoint(brk->lineNumber);
dFree(brk->testExpression);
delete brk;
}
}
void TelnetDebugger::removeAllBreakpoints()
{
Breakpoint *walk = mBreakpoints;
while(walk)
{
Breakpoint *temp = walk->next;
walk->code->clearBreakpoint(walk->lineNumber);
dFree(walk->testExpression);
delete walk;
walk = temp;
}
mBreakpoints = NULL;
}
void TelnetDebugger::debugContinue()
{
mBreakOnNextStatement = false;
mStackPopBreakIndex = -1;
mProgramPaused = false;
send("RUNNING\r\n");
}
void TelnetDebugger::breakOnNextStatement()
{
for(CodeBlock *walk = codeBlockList; walk; walk = walk->nextFile)
walk->setAllBreaks();
mBreakOnNextStatement = true;
}
void TelnetDebugger::debugStepIn()
{
breakOnNextStatement();
mStackPopBreakIndex = -1;
mProgramPaused = false;
send("RUNNING\r\n");
}
void TelnetDebugger::debugStepOver()
{
mBreakOnNextStatement = false;
mStackPopBreakIndex = gEvalState.stack.size();
mProgramPaused = false;
send("RUNNING\r\n");
}
void TelnetDebugger::debugStepOut()
{
mBreakOnNextStatement = false;
mStackPopBreakIndex = gEvalState.stack.size() - 1;
mProgramPaused = false;
send("RUNNING\r\n");
}
void TelnetDebugger::evaluateExpression(const char *tag, S32, const char *evalBuffer)
{
char buffer[MaxCommandSize];
Con::evaluatef("$dbgResult = %s;", evalBuffer);
const char *result = Con::getVariable("$dbgResult");
dSprintf(buffer, MaxCommandSize, "EVALOUT %s %s\r\n", tag, result[0] ? result : "\"\"");
send(buffer);
}
void TelnetDebugger::dumpFileList()
{
send("FILELISTOUT ");
for(CodeBlock *walk = codeBlockList; 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!");
}