openclonk/src/script/C4AulDebug.cpp

537 lines
14 KiB
C++
Raw Normal View History

/*
* OpenClonk, http://www.openclonk.org
*
* Copyright (c) 2009 Peter Wortmann
2013-01-09 23:23:06 +00:00
* Copyright (c) 2009, 2011-2012 Günther Brammer
* Copyright (c) 2010 Martin Plicht
2011-09-01 14:58:52 +00:00
* Copyright (c) 2010 Benjamin Herr
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
#include <C4Include.h>
#include "C4AulDebug.h"
#include <C4Version.h>
#include <C4GameControl.h>
#include <C4Game.h>
#include <C4MessageInput.h>
#include <C4Log.h>
#include <C4Object.h>
#include "C4AulExec.h"
#ifndef NOAULDEBUG
// *** C4AulDebug
C4AulDebug::C4AulDebug()
2010-03-28 18:58:01 +00:00
: fInit(false), fConnected(false)
{
ZeroMem(&PeerAddr, sizeof PeerAddr);
2010-03-28 18:58:01 +00:00
}
C4AulDebug::~C4AulDebug()
{
for (std::list<StdStrBuf*>::iterator it = StackTrace.begin(); it != StackTrace.end(); it++)
{delete *it;}
if (pDebug == this) pDebug = NULL;
}
bool C4AulDebug::InitDebug(uint16_t iPort, const char *szPassword, const char *szHost, bool fWait)
{
// Create debug object
if (!pDebug) pDebug = new C4AulDebug();
// Initialize
pDebug->SetPassword(szPassword);
pDebug->SetAllowed(szHost);
pDebug->SetEngine(&AulExec);
if (!pDebug->Init(iPort))
{ LogFatal("C4Aul debugger failed to initialize!"); return false; }
// Log
LogF("C4Aul debugger initialized on port %d", iPort);
// Add to application
Application.Add(pDebug);
// Wait for connection
if (fWait)
{
Log("C4Aul debugger waiting for connection...");
while (!pDebug->isConnected())
if (!Application.ScheduleProcs())
return false;
}
// Done
return true;
}
void C4AulDebug::PackPacket(const C4NetIOPacket &rPacket, StdBuf &rOutBuf)
2010-03-28 18:58:01 +00:00
{
// Enlarge buffer
int iSize = rPacket.getSize(),
2010-03-28 18:58:01 +00:00
iPos = rOutBuf.getSize();
rOutBuf.Grow(iSize + 2);
// Write packet
rOutBuf.Write(rPacket, iPos);
// Terminate
uint8_t *pPos = getMBufPtr<uint8_t>(rOutBuf, iPos + iSize);
*pPos = '\r'; *(pPos + 1) = '\n';
2010-03-28 18:58:01 +00:00
}
size_t C4AulDebug::UnpackPacket(const StdBuf &rInBuf, const C4NetIO::addr_t &addr)
2010-03-28 18:58:01 +00:00
{
2010-04-01 21:08:06 +00:00
// Find line separation
2010-03-28 18:58:01 +00:00
const char *pSep = reinterpret_cast<const char *>(memchr(rInBuf.getData(), '\n', rInBuf.getSize()));
if (!pSep)
return 0;
2010-04-01 21:08:06 +00:00
// Check if it's windows-style separation
int iSize = pSep - getBufPtr<char>(rInBuf) + 1,
2010-03-28 18:58:01 +00:00
iLength = iSize - 1;
if (iLength && *(pSep - 1) == '\r')
iLength--;
// Copy the line
StdStrBuf Buf; Buf.Copy(getBufPtr<char>(rInBuf), iLength);
// Password line?
2010-03-28 18:58:01 +00:00
if (fConnected)
ProcessLine(Buf);
2010-03-28 18:58:01 +00:00
else if (!Password.getSize() || Password == Buf)
{
fConnected = true;
2010-03-28 18:58:01 +00:00
SendLine("HLO", "This is " C4ENGINEINFOLONG ", " C4VERSION);
Log("C4Aul debugger connected successfully!");
2010-03-28 18:58:01 +00:00
}
else
C4NetIOTCP::Close(PeerAddr);
// Consume line
return iSize;
2010-03-28 18:58:01 +00:00
}
bool C4AulDebug::OnConn(const C4NetIO::addr_t &AddrPeer, const C4NetIO::addr_t &AddrConnect, const addr_t *pOwnAddr, C4NetIO *pNetIO)
2010-03-28 18:58:01 +00:00
{
assert(pNetIO == this);
// Already have a connection?
2010-03-28 18:58:01 +00:00
if (fConnected) return false;
// Check address
2010-03-28 18:58:01 +00:00
if (AllowedAddr.sin_addr.s_addr)
if (AllowedAddr.sin_addr.s_addr != AddrPeer.sin_addr.s_addr ||
(AllowedAddr.sin_port && AllowedAddr.sin_port != AddrPeer.sin_port))
{
LogF("C4AulDebug blocked connection from %s:%d", inet_ntoa(AddrPeer.sin_addr), htons(AddrPeer.sin_port));
return false;
2010-03-28 18:58:01 +00:00
}
// Log
LogF("C4AulDebug got connection from %s:%d", inet_ntoa(AddrPeer.sin_addr), htons(AddrPeer.sin_port));
// Accept connection
PeerAddr = AddrPeer;
return true;
2010-03-28 18:58:01 +00:00
}
void C4AulDebug::OnDisconn(const C4NetIO::addr_t &AddrPeer, C4NetIO *pNetIO, const char *szReason)
2010-03-28 18:58:01 +00:00
{
LogF("C4AulDebug lost connection (%s)", szReason);
fConnected = false;
eState = DS_Go;
ZeroMem(&PeerAddr, sizeof PeerAddr);
2010-03-28 18:58:01 +00:00
}
void C4AulDebug::OnPacket(const class C4NetIOPacket &rPacket, C4NetIO *pNetIO)
2010-03-28 18:58:01 +00:00
{
// Won't get called
2010-03-28 18:58:01 +00:00
}
bool C4AulDebug::SetAllowed(const char *szHost)
2010-03-28 18:58:01 +00:00
{
// Clear
ZeroMem(&AllowedAddr, sizeof(AllowedAddr));
// No host?
2010-03-28 18:58:01 +00:00
if (!szHost || !*szHost) return true;
// Resolve the address
return ResolveAddress(szHost, &AllowedAddr, 0);
2010-03-28 18:58:01 +00:00
}
bool C4AulDebug::Init(uint16_t iPort)
2010-03-28 18:58:01 +00:00
{
if (fInit) Close();
if (iPort == P_NONE) return false;
// Register self as callback for network events
C4NetIOTCP::SetCallback(this);
2010-03-28 18:58:01 +00:00
// Start listening
2010-03-28 18:58:01 +00:00
if (!C4NetIOTCP::Init(iPort))
return false;
// Okay
fInit = true;
eState = DS_Go;
return true;
2010-03-28 18:58:01 +00:00
}
bool C4AulDebug::Close()
2010-03-28 18:58:01 +00:00
{
if (!fInit) return true;
fInit = fConnected = false;
return C4NetIOTCP::Close();
2010-03-28 18:58:01 +00:00
}
bool C4AulDebug::Close(const addr_t &addr)
2010-03-28 18:58:01 +00:00
{
if (!fInit) return true;
bool success = C4NetIOTCP::Close(addr);
if (success)
fInit = fConnected = false;
return success;
2010-03-28 18:58:01 +00:00
}
void C4AulDebug::OnLog(const char *szLine)
2010-03-28 18:58:01 +00:00
{
if (!fConnected) return;
SendLine("LOG", szLine);
2010-03-28 18:58:01 +00:00
}
void C4AulDebug::ProcessLine(const StdStrBuf &Line)
2010-03-28 18:58:01 +00:00
{
// Get command
StdStrBuf Cmd;
Cmd.CopyUntil(Line.getData(), ' ');
// Get data
const char *szData = Line.getPtr(Cmd.getLength());
2010-03-28 18:58:01 +00:00
if (*szData) szData++;
// Identify command
const char *szCmd = Cmd.getData();
bool fOkay = true;
const char *szAnswer = NULL;
2010-03-28 18:58:01 +00:00
if (SEqualNoCase(szCmd, "HELP"))
{
fOkay = false; szAnswer = "Yeah, like I'm going to explain that /here/";
2010-03-28 18:58:01 +00:00
}
else if (SEqualNoCase(szCmd, "BYE") || SEqualNoCase(szCmd, "QUIT"))
C4NetIOTCP::Close(PeerAddr);
2010-03-28 18:58:01 +00:00
else if (SEqualNoCase(szCmd, "SAY"))
::Control.DoInput(CID_Message, new C4ControlMessage(C4CMT_Normal, szData), CDT_Direct);
2010-03-28 18:58:01 +00:00
else if (SEqualNoCase(szCmd, "CMD"))
::MessageInput.ProcessCommand(szData);
2010-03-28 18:58:01 +00:00
else if (SEqualNoCase(szCmd, "STP") || SEqualNoCase(szCmd, "S"))
eState = DS_Step;
2010-03-28 18:58:01 +00:00
else if (SEqualNoCase(szCmd, "GO") || SEqualNoCase(szCmd, "G"))
eState = DS_Go;
2010-03-28 18:58:01 +00:00
else if (SEqualNoCase(szCmd, "STO") || SEqualNoCase(szCmd, "O"))
eState = DS_StepOver;
2010-03-28 18:58:01 +00:00
else if (SEqualNoCase(szCmd, "STR") || SEqualNoCase(szCmd, "R"))
eState = DS_StepOut;
2010-03-28 18:58:01 +00:00
else if (SEqualNoCase(szCmd, "EXC") || SEqualNoCase(szCmd, "E"))
{
C4AulScriptContext* context = pExec->GetContext(pExec->GetContextDepth()-1);
int32_t objectNum = C4ControlScript::SCOPE_Global;
if (context && context->Obj && context->Obj->GetObject())
objectNum = context->Obj->GetObject()->Number;
::Control.DoInput(CID_Script, new C4ControlScript(szData, objectNum, false, true), CDT_Decide);
}
2010-03-28 18:58:01 +00:00
else if (SEqualNoCase(szCmd, "PSE"))
if (Game.IsPaused())
{
Game.Unpause();
szAnswer = "Game unpaused.";
2010-03-28 18:58:01 +00:00
}
else
2010-03-28 18:58:01 +00:00
{
Game.Pause();
szAnswer = "Game paused.";
2010-03-28 18:58:01 +00:00
}
else if (SEqualNoCase(szCmd, "LST"))
{
for (C4AulScript* script = ScriptEngine.Child0; script; script = script->Next)
2010-03-28 18:58:01 +00:00
{
2010-01-16 01:32:53 +00:00
SendLine(RelativePath(script->ScriptName));
}
}
2010-03-28 18:58:01 +00:00
// toggle breakpoint
else if (SEqualNoCase(szCmd, "TBR"))
{
// FIXME: this doesn't find functions which were included/appended
StdStrBuf scriptPath;
scriptPath.CopyUntil(szData, ':');
const char* lineStart = szData+1+scriptPath.getLength();
int line = strtol(szData+1+scriptPath.getLength(), const_cast<char**>(&lineStart), 10);
2010-03-28 18:58:01 +00:00
C4AulScript* script;
for (script = ScriptEngine.Child0; script; script = script->Next)
{
2010-01-16 01:32:53 +00:00
if (SEqualNoCase(RelativePath(script->ScriptName), scriptPath.getData()))
break;
}
2010-03-28 18:58:01 +00:00
if (script && script->GetScriptHost())
{
C4AulBCC* foundDebugChunk = NULL;
C4ScriptHost * sh = script->GetScriptHost();
const char* scriptText = sh->GetScript();
for (C4String *pFn = sh->GetPropList()->EnumerateOwnFuncs(); pFn; pFn = sh->GetPropList()->EnumerateOwnFuncs(pFn))
{
C4AulScriptFunc *pSFunc = sh->GetPropList()->GetFunc(pFn)->SFunc();
while (pSFunc)
2010-03-28 18:58:01 +00:00
{
for (C4AulBCC* chunk = pSFunc->GetCode(); chunk->bccType != AB_EOFN; chunk++)
{
if (chunk->bccType == AB_DEBUG)
{
int lineOfThisOne = pSFunc->GetLineOfCode(chunk);
if (lineOfThisOne == line)
{
foundDebugChunk = chunk;
goto Done;
}
/*else {
DebugLogF("Debug chunk at %d", lineOfThisOne);
}*/
}
}
pSFunc = pSFunc->OwnerOverloaded ? pSFunc->OwnerOverloaded->SFunc() : 0;
}
}
2010-03-28 18:58:01 +00:00
Done:
if (foundDebugChunk)
{
foundDebugChunk->Par.i = !foundDebugChunk->Par.i; // activate breakpoint
}
else
{
szAnswer = "Can't set breakpoint (wrong line?)";
fOkay = false;
}
}
else
{
fOkay = false;
szAnswer = "Can't find script";
}
2010-03-28 18:58:01 +00:00
}
else if (SEqualNoCase(szCmd, "SST"))
{
std::list<StdStrBuf*>::iterator it = StackTrace.begin();
for (it++; it != StackTrace.end(); it++)
{
SendLine("AT", (*it)->getData());
}
SendLine("EST");
}
else if (SEqualNoCase(szCmd, "VAR"))
{
C4Value *val = NULL;
int varIndex;
C4AulScriptContext* pCtx = pExec->GetContext(pExec->GetContextDepth() - 1);
if (pCtx)
{
if ((varIndex = pCtx->Func->ParNamed.GetItemNr(szData)) != -1)
{
val = &pCtx->Pars[varIndex];
}
else if ((varIndex = pCtx->Func->VarNamed.GetItemNr(szData)) != -1)
{
val = &pCtx->Vars[varIndex];
}
}
const char* typeName = val ? GetC4VName(val->GetType()) : "any";
StdStrBuf output = FormatString("%s %s %s", szData, typeName, val ? val->GetDataString().getData() : "Unknown");
SendLine("VAR", output.getData());
}
else
2010-03-28 18:58:01 +00:00
{
fOkay = false;
szAnswer = "Can't do that";
2010-03-28 18:58:01 +00:00
}
// Send answer
SendLine(fOkay ? "OK" : "ERR", szAnswer);
2010-03-28 18:58:01 +00:00
}
bool C4AulDebug::SendLine(const char *szType, const char *szData)
2010-03-28 18:58:01 +00:00
{
StdStrBuf Line = szData ? FormatString("%s %s", szType, szData) : StdStrBuf(szType);
return Send(C4NetIOPacket(Line.getData(), Line.getSize(), false, PeerAddr));
2010-03-28 18:58:01 +00:00
}
void C4AulDebug::DebugStep(C4AulBCC *pCPos)
2010-03-28 18:58:01 +00:00
{
// Get top context
2010-03-16 11:39:58 +00:00
//C4AulScriptContext *pCtx = pExec->GetContext(pExec->GetContextDepth() - 1);
// Already stopped? Ignore.
// This means we are doing some calculation with suspended script engine.
// We do /not/ want to have recursive suspensions...
2010-03-28 18:58:01 +00:00
if (eState == DS_Stop)
return;
// Have break point?
2010-03-28 18:58:01 +00:00
if (pCPos->Par.i)
eState = DS_Step;
StepPoint(pCPos);
2010-03-28 18:58:01 +00:00
}
void C4AulDebug::DebugStepIn(C4AulBCC *pCPos)
2010-03-28 18:58:01 +00:00
{
2010-03-28 18:58:01 +00:00
}
void C4AulDebug::DebugStepOut(C4AulBCC *pCPos, C4AulScriptContext *pRetCtx, C4Value *pRVal)
2010-03-28 18:58:01 +00:00
{
// Ignore if already suspended, see above.
2010-03-28 18:58:01 +00:00
if (eState == DS_Stop)
return;
// This counts as a regular step point
StepPoint(pCPos, pRetCtx, pRVal);
2010-03-28 18:58:01 +00:00
}
void C4AulDebug::StepPoint(C4AulBCC *pCPos, C4AulScriptContext *pRetCtx, C4Value *pRVal)
2010-03-28 18:58:01 +00:00
{
// Maybe got a command in the meantime?
Execute(0);
2010-03-28 18:58:01 +00:00
// Get current script context
int iCallDepth = pExec->GetContextDepth();
C4AulScriptContext *pCtx = pExec->GetContext(iCallDepth-1);
// When we're stepping out of a script function, the context of the returning
// function hasn't been removed yet.
2010-03-28 18:58:01 +00:00
if (pCtx == pRetCtx)
{
iCallDepth--;
// Check if we are returning to a script function
2010-03-28 18:58:01 +00:00
if (pCtx->Return)
pCtx--;
else
pCtx = NULL;
2010-03-28 18:58:01 +00:00
}
// Stepped out?
2010-03-28 18:58:01 +00:00
if (pRVal && eState != DS_Go && iCallDepth <= iStepCallDepth)
{
StdStrBuf FuncDump = pRetCtx->ReturnDump();
StdStrBuf ReturnDump = pRVal->GetDataString();
SendLine("RET", FormatString("%s = %s", FuncDump.getData(), ReturnDump.getData()).getData());
// Ignore as step point if we didn't "really" step out
2010-03-28 18:58:01 +00:00
if (iCallDepth >= iStepCallDepth) return;
}
// Stop?
2010-03-28 18:58:01 +00:00
switch (eState)
{
// Continue normally
2010-03-28 18:58:01 +00:00
case DS_Go: return;
// Always stop
2010-03-28 18:58:01 +00:00
case DS_Stop: break;
case DS_Step: break;
// Only stop for same level or above
2010-03-28 18:58:01 +00:00
case DS_StepOver:
if (iCallDepth > iStepCallDepth)
return;
break;
// Only stop above
2010-03-28 18:58:01 +00:00
case DS_StepOut:
if (iCallDepth >= iStepCallDepth)
return;
break;
}
// Let's stop here
eState = DS_Stop;
iStepCallDepth = iCallDepth;
Game.HaltCount++;
// No valid stop position? Just continue
2010-03-28 18:58:01 +00:00
if (!pCtx)
{
Game.HaltCount--;
eState = DS_Step;
return;
2010-03-28 18:58:01 +00:00
}
// Signal
2010-03-28 18:58:01 +00:00
if (pCPos && pCPos->bccType == AB_DEBUG && pCPos->Par.i)
SendLine("STP", FormatString("Stopped on breakpoint %d", pCPos->Par.i).getData());
else
SendLine("STP", "Stepped");
// Position
ObtainStackTrace(pCtx, pCPos);
SendLine("POS", StackTrace.front()->getData());
// Suspend until we get some command
2010-03-28 18:58:01 +00:00
while (eState == DS_Stop)
if (!Application.ScheduleProcs())
{
Close();
return;
2010-03-28 18:58:01 +00:00
}
// Do whatever we've been told.
Game.HaltCount--;
2010-03-28 18:58:01 +00:00
}
void C4AulDebug::ControlScriptEvaluated(const char* script, const char* result)
{
SendLine("EVR", FormatString("%s=%s", script, result).getData());
}
2010-01-16 01:32:53 +00:00
const char* C4AulDebug::RelativePath(StdStrBuf &path)
{
const char* p = path.getData();
const char* result = Config.AtRelativePath(p);
if (p != result)
return result;
// try path relative to scenario container
StdStrBuf scenarioContainerPath;
GetParentPath(::Game.ScenarioFile.GetName(), &scenarioContainerPath);
return GetRelativePathS(p, scenarioContainerPath.getData());
2010-01-16 01:32:53 +00:00
}
void C4AulDebug::ObtainStackTrace(C4AulScriptContext* pCtx, C4AulBCC* pCPos)
{
for (std::list<StdStrBuf*>::iterator it = StackTrace.begin(); it != StackTrace.end(); it++)
{delete *it;}
StackTrace.clear();
for (int ctxNum = pExec->GetContextDepth()-1; ctxNum >= 0; ctxNum--)
{
C4AulScriptContext* c = pExec->GetContext(ctxNum);
C4AulBCC* _cpos = c == pCtx ? pCPos : c->CPos;
if (_cpos)
{
StdStrBuf* format = new StdStrBuf(FormatCodePos(c, _cpos));
StackTrace.push_back(format);
}
}
}
StdStrBuf C4AulDebug::FormatCodePos(C4AulScriptContext *pCtx, C4AulBCC *pCPos)
2010-03-28 18:58:01 +00:00
{
if (pCtx->Func->pOrgScript)
return FormatString("%s:%d",
RelativePath(pCtx->Func->pOrgScript->ScriptName),
pCtx->Func->GetLineOfCode(pCPos));
else
return StdStrBuf("(eval)");
2010-03-28 18:58:01 +00:00
}
C4AulDebug * C4AulDebug::pDebug = NULL;
2010-03-16 11:39:58 +00:00
#endif