+ Initial C4Aul debugging support

stable-5.1
Peter Wortmann 2009-04-21 23:44:56 +02:00
parent 1fe837cc34
commit 1d5d8b7d19
13 changed files with 758 additions and 213 deletions

View File

@ -42,6 +42,7 @@ class C4AulScriptFunc;
class C4AulDefFunc;
class C4AulScript;
class C4AulScriptEngine;
class C4AulDebug;
struct C4AulContext;
struct C4AulBCC;
@ -198,6 +199,7 @@ enum C4AulBCCType
AB_FOREACH_NEXT, // foreach: next element
AB_RETURN, // return statement
AB_ERR, // parse error at this position
AB_DEBUG, // debug break
AB_EOFN, // end of function
AB_EOF, // end of file
};
@ -248,13 +250,16 @@ struct C4AulScriptContext : public C4AulContext
C4Value *Return;
C4Value *Pars;
C4Value *Vars;
C4AulScriptFunc *Func;
C4AulFunc *Func;
bool TemporaryScript;
C4ValueList NumVars;
C4AulBCC *CPos;
time_t tTime; // initialized only by profiler if active
C4AulScriptFunc *SFunc();
int ParCnt() const { return Vars - Pars; }
StdStrBuf Dump(StdStrBuf Dump = StdStrBuf(""));
StdStrBuf DumpVerbose(StdStrBuf Dump = StdStrBuf(""));
void dump(StdStrBuf Dump = StdStrBuf(""));
};
@ -298,6 +303,8 @@ class C4AulFunc
};
inline C4AulScriptFunc *C4AulScriptContext::SFunc() { return Func->SFunc(); }
// script function class
class C4AulScriptFunc : public C4AulFunc
{
@ -351,7 +358,6 @@ class C4AulScriptFunc : public C4AulFunc
friend class C4AulScript;
};
// defined function class
class C4AulDefFunc : C4AulFunc
{
@ -518,7 +524,6 @@ class C4AulScript
friend class C4AulParseState;
};
// holds all C4AulScripts
class C4AulScriptEngine : public C4AulScript
{
@ -526,6 +531,7 @@ class C4AulScriptEngine : public C4AulScript
C4AList itbl; // include table
C4AList atbl; // append table
C4AulFuncMap FuncLookUp;
C4AulDebug *pDebug;
public:
int warnCnt, errCnt; // number of warnings/errors
@ -568,6 +574,10 @@ class C4AulScriptEngine : public C4AulScript
BOOL DenumerateVariablePointers();
void UnLink(); // called when a script is being reloaded (clears string table)
bool InitDebug(uint16_t iPort, const char *szPassword, const char *szHost, bool fWait);
C4AulDebug *GetDebugger() const { return pDebug; }
// Compile scenario script data (without strings and constants)
void CompileFunc(StdCompiler *pComp);

View File

@ -0,0 +1,68 @@
#ifndef C4AULDEBUG_H
#define C4AULDEBUG_H
#include "C4Aul.h"
#include "C4NetIO.h"
// manages a debugging interface
class C4AulDebug : public C4NetIOTCP, private C4NetIO::CBClass
{
public:
C4AulDebug();
~C4AulDebug();
private:
bool fInit, fConnected;
class C4AulExec *pExec;
C4NetIO::addr_t PeerAddr, AllowedAddr;
StdCopyStrBuf Password;
enum DebugState
{
DS_Go, // execute until next break point
DS_Stop, // execution stopped
DS_Step, // executing until next step point
DS_StepOver, // executung until next step point on same level or above (or break point)
DS_StepOut // executing until next step point above (or break point)
};
DebugState eState;
int iStepCallDepth;
private:
// Overridden
virtual void PackPacket(const C4NetIOPacket &rPacket, StdBuf &rOutBuf);
virtual size_t UnpackPacket(const StdBuf &rInBuf, const C4NetIO::addr_t &addr);
// Callbacks
bool OnConn(const C4NetIO::addr_t &AddrPeer, const C4NetIO::addr_t &AddrConnect, const addr_t *pOwnAddr, C4NetIO *pNetIO);
void OnDisconn(const C4NetIO::addr_t &AddrPeer, C4NetIO *pNetIO, const char *szReason);
void OnPacket(const class C4NetIOPacket &rPacket, C4NetIO *pNetIO);
public:
bool isConnected() const { return fConnected; }
void SetPassword(const char *szPassword) { Password = szPassword; }
bool SetAllowed(const char *szHost);
void SetEngine(class C4AulExec *pnExec) { pExec = pnExec; }
bool Init(uint16_t iPort);
bool Close();
void OnLog(const char *szLine);
void DebugStep(C4AulBCC *pCPos);
void DebugStepIn(C4AulBCC *pCPos);
void DebugStepOut(C4AulBCC *pCPos, C4AulScriptContext *pRetCtx, C4Value *pRVal);
private:
void StepPoint(C4AulBCC *pCPos, C4AulScriptContext *pRetCtx = NULL, C4Value *pRVal = NULL);
StdStrBuf FormatCodePos(C4AulScriptContext *pCtx, C4AulBCC *pCPos);
void ProcessLine(const StdStrBuf &Line);
bool SendLine(const char *szType, const char *szData = NULL);
};
#endif

View File

@ -0,0 +1,152 @@
#ifndef C4AULEXEC_H
#define C4AULEXEC_H
const int MAX_CONTEXT_STACK = 512;
const int MAX_VALUE_STACK = 1024;
class C4AulExec
{
public:
C4AulExec()
: pCurCtx(Contexts - 1), pCurVal(Values - 1), iTraceStart(-1)
{ }
private:
C4AulScriptContext Contexts[MAX_CONTEXT_STACK];
C4Value Values[MAX_VALUE_STACK];
C4AulScriptContext *pCurCtx;
C4Value *pCurVal;
int iTraceStart;
bool fProfiling;
time_t tDirectExecStart, tDirectExecTotal; // profiler time for DirectExec
C4AulScript *pProfiledScript;
public:
C4Value Exec(C4AulScriptFunc *pSFunc, C4Object *pObj, C4Value pPars[], bool fPassErrors, bool fTemporaryScript = false);
C4Value Exec(C4AulBCC *pCPos, bool fPassErrors);
void StartTrace();
void StartProfiling(C4AulScript *pScript); // resets profling times and starts recording the times
void StopProfiling(); // stop the profiler and displays results
void AbortProfiling() { fProfiling=false; }
inline void StartDirectExec() { if (fProfiling) tDirectExecStart = timeGetTime(); }
inline void StopDirectExec() { if (fProfiling) tDirectExecTotal += timeGetTime() - tDirectExecStart; }
int GetContextDepth() const { return pCurCtx - Contexts + 1; }
C4AulScriptContext *GetContext(int iLevel) { return iLevel >= 0 && iLevel < GetContextDepth() ? Contexts + iLevel : NULL; }
private:
void PushContext(const C4AulScriptContext &rContext);
void PopContext();
void CheckOverflow(int iCnt)
{
if(ValueStackSize() + iCnt > MAX_VALUE_STACK)
throw new C4AulExecError(pCurCtx->Obj, "internal error: value stack overflow!");
}
void PushString(C4String * Str)
{
CheckOverflow(1);
(++pCurVal)->SetString(Str);
}
void PushArray(C4ValueArray * Array)
{
CheckOverflow(1);
(++pCurVal)->SetArray(Array);
}
void PushValue(const C4Value &rVal)
{
CheckOverflow(1);
(++pCurVal)->Set(rVal);
}
void PushValueRef(C4Value &rVal)
{
CheckOverflow(1);
(++pCurVal)->SetRef(&rVal);
}
void PushNullVals(int iCnt)
{
CheckOverflow(iCnt);
pCurVal += iCnt;
}
bool PopValue()
{
if(LocalValueStackSize() < 1)
throw new C4AulExecError(pCurCtx->Obj, "internal error: value stack underflow!");
(pCurVal--)->Set0();
return true;
}
void PopValues(int n)
{
if(LocalValueStackSize() < n)
throw new C4AulExecError(pCurCtx->Obj, "internal error: value stack underflow!");
while(n--)
(pCurVal--)->Set0();
}
void PopValuesUntil(C4Value *pUntilVal)
{
if(pUntilVal < Values - 1)
throw new C4AulExecError(pCurCtx->Obj, "internal error: value stack underflow!");
while(pCurVal > pUntilVal)
(pCurVal--)->Set0();
}
int ContextStackSize() const
{
return pCurCtx - Contexts + 1;
}
int ValueStackSize() const
{
return pCurVal - Values + 1;
}
int LocalValueStackSize() const
{
return ContextStackSize()
? pCurVal - pCurCtx->Vars - pCurCtx->Func->SFunc()->VarNamed.iSize + 1
: pCurVal - Values + 1;
}
void CheckOpPars(int iOpID)
{
// Get parameters
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
// Typecheck parameters
if(!pPar1->ConvertTo(C4ScriptOpMap[iOpID].Type1))
throw new C4AulExecError(pCurCtx->Obj,
FormatString("operator \"%s\" left side: got \"%s\", but expected \"%s\"!",
C4ScriptOpMap[iOpID].Identifier, pPar1->GetTypeInfo(), GetC4VName(C4ScriptOpMap[iOpID].Type1)).getData());
if(!pPar2->ConvertTo(C4ScriptOpMap[iOpID].Type2))
throw new C4AulExecError(pCurCtx->Obj,
FormatString("operator \"%s\" right side: got \"%s\", but expected \"%s\"!",
C4ScriptOpMap[iOpID].Identifier, pPar2->GetTypeInfo(), GetC4VName(C4ScriptOpMap[iOpID].Type2)).getData());
}
void CheckOpPar(int iOpID)
{
// Typecheck parameter
if(!pCurVal->ConvertTo(C4ScriptOpMap[iOpID].Type1))
throw new C4AulExecError(pCurCtx->Obj,
FormatString("operator \"%s\": got \"%s\", but expected \"%s\"!",
C4ScriptOpMap[iOpID].Identifier, pCurVal->GetTypeInfo(), GetC4VName(C4ScriptOpMap[iOpID].Type1)).getData());
}
C4AulBCC *Call(C4AulFunc *pFunc, C4Value *pReturn, C4Value *pPars, C4Object *pObj = NULL, C4Def *pDef = NULL);
};
extern C4AulExec AulExec;
#endif // C4AULEXEC_H

View File

@ -191,7 +191,11 @@ class C4Game
bool DebugMode;
// next mission to be played after this one
StdCopyStrBuf NextMission, NextMissionText, NextMissionDesc;
private:
// debug settings
uint16_t DebugPort; StdStrBuf DebugPassword, DebugHost; bool DebugWait;
public:
// Init and execution
void Default();

View File

@ -18,8 +18,11 @@
#include <C4Include.h>
#include <C4Aul.h>
#include <C4AulExec.h>
#include <C4AulDebug.h>
#ifndef BIG_C4INCLUDE
#include <C4Application.h>
#include <C4Config.h>
#include <C4Def.h>
#include <C4Log.h>
@ -421,6 +424,8 @@ C4AulScriptEngine::C4AulScriptEngine():
GlobalConstNames.Reset();
GlobalConsts.Reset();
GlobalConsts.SetNameList(&GlobalConstNames);
pDebug = NULL;
}
@ -428,6 +433,8 @@ C4AulScriptEngine::~C4AulScriptEngine() { Clear(); }
void C4AulScriptEngine::Clear()
{
// stop debugger
delete pDebug; pDebug = NULL;
// clear inherited
C4AulScript::Clear();
// clear own stuff
@ -490,6 +497,7 @@ BOOL C4AulScriptEngine::DenumerateVariablePointers()
// runtime data only: don't denumerate consts
return TRUE;
}
void C4AulScriptEngine::CompileFunc(StdCompiler *pComp)
{
pComp->Value(mkNamingAdapt(Global, "Globals" , C4ValueList()));
@ -497,6 +505,33 @@ void C4AulScriptEngine::CompileFunc(StdCompiler *pComp)
GlobalNamedDefault.SetNameList(&GlobalNamedNames);
pComp->Value(mkNamingAdapt(GlobalNamed, "GlobalNamed" , GlobalNamedDefault));
}
bool C4AulScriptEngine::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;
}
/*--- C4AulFuncMap ---*/
static const size_t CapacityInc = 1024;

View File

@ -0,0 +1,329 @@
#include "C4Include.h"
#include "C4AulDebug.h"
#include "C4AulExec.h"
#ifndef BIG_C4INCLUDE
#include "C4Control.h"
#include "C4Game.h"
#endif
// *** C4AulDebug
C4AulDebug::C4AulDebug()
: fInit(false), fConnected(false)
{
ZeroMem(&PeerAddr, sizeof PeerAddr);
}
C4AulDebug::~C4AulDebug()
{
}
void C4AulDebug::PackPacket(const C4NetIOPacket &rPacket, StdBuf &rOutBuf)
{
// Enlarge buffer
int iSize = rPacket.getSize(),
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';
}
size_t C4AulDebug::UnpackPacket(const StdBuf &rInBuf, const C4NetIO::addr_t &addr)
{
// Find line seperation
const char *pSep = reinterpret_cast<const char *>(memchr(rInBuf.getData(), '\n', rInBuf.getSize()));
if(!pSep)
return 0;
// Check if it's windows-style seperation
int iSize = pSep - getBufPtr<char>(rInBuf) + 1,
iLength = iSize - 1;
if(iLength && *(pSep - 1) == '\r')
iLength--;
// Copy the line
StdStrBuf Buf; Buf.Copy(getBufPtr<char>(rInBuf), iLength);
// Password line?
if(fConnected)
ProcessLine(Buf);
else if(!Password.getSize() || Password == Buf)
{
fConnected = true;
SendLine("HLO", "This is " C4ENGINEINFOLONG ", " C4VERSION);
Log("C4Aul debugger connected successfully!");
}
else
C4NetIOTCP::Close(PeerAddr);
// Consume line
return iSize;
}
bool C4AulDebug::OnConn(const C4NetIO::addr_t &AddrPeer, const C4NetIO::addr_t &AddrConnect, const addr_t *pOwnAddr, C4NetIO *pNetIO)
{
assert(pNetIO == this);
// Already have a connection?
if(fConnected) return false;
// Check address
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;
}
// Log
LogF("C4AulDebug got connection from %s:%d", inet_ntoa(AddrPeer.sin_addr), htons(AddrPeer.sin_port));
// Accept connection
PeerAddr = AddrPeer;
return true;
}
void C4AulDebug::OnDisconn(const C4NetIO::addr_t &AddrPeer, C4NetIO *pNetIO, const char *szReason)
{
LogF("C4AulDebug lost connection (%s)", szReason);
fConnected = false;
eState = DS_Go;
ZeroMem(&PeerAddr, sizeof PeerAddr);
}
void C4AulDebug::OnPacket(const class C4NetIOPacket &rPacket, C4NetIO *pNetIO)
{
// Won't get called
}
bool C4AulDebug::SetAllowed(const char *szHost)
{
// Clear
ZeroMem(&AllowedAddr, sizeof(AllowedAddr));
// No host?
if(!szHost || !*szHost) return true;
// Resolve the address
return ResolveAddress(szHost, &AllowedAddr, 0);
}
bool C4AulDebug::Init(uint16_t iPort)
{
if(fInit) Close();
if(iPort == P_NONE) return false;
// Register self as callback for network events
C4NetIOTCP::SetCallback(this);
// Start listening
if(!C4NetIOTCP::Init(iPort))
return false;
// Okay
fInit = true;
eState = DS_Go;
return true;
}
bool C4AulDebug::Close()
{
if(!fInit) return true;
fInit = fConnected = false;
return C4NetIOTCP::Close();
}
void C4AulDebug::OnLog(const char *szLine)
{
if(!fConnected) return;
SendLine("LOG", szLine);
}
void C4AulDebug::ProcessLine(const StdStrBuf &Line)
{
// Get command
StdStrBuf Cmd;
Cmd.CopyUntil(Line.getData(), ' ');
// Get data
const char *szData = Line.getPtr(Cmd.getLength());
if(*szData) szData++;
// Identify command
const char *szCmd = Cmd.getData();
bool fOkay = true;
const char *szAnswer = NULL;
if(SEqualNoCase(szCmd, "HELP"))
{
fOkay = false; szAnswer = "Yeah, like I'm going to explain that /here/";
}
else if(SEqualNoCase(szCmd, "BYE") || SEqualNoCase(szCmd, "QUIT"))
C4NetIOTCP::Close(PeerAddr);
else if(SEqualNoCase(szCmd, "SAY"))
Game.Control.DoInput(CID_Message, new C4ControlMessage(C4CMT_Normal, szData), CDT_Direct);
else if(SEqualNoCase(szCmd, "CMD"))
Game.MessageInput.ProcessCommand(szData);
else if(SEqualNoCase(szCmd, "STP") || SEqualNoCase(szCmd, "S"))
eState = DS_Step;
else if(SEqualNoCase(szCmd, "GO") || SEqualNoCase(szCmd, "G"))
eState = DS_Go;
else if(SEqualNoCase(szCmd, "STO") || SEqualNoCase(szCmd, "O"))
eState = DS_StepOver;
else if(SEqualNoCase(szCmd, "STR") || SEqualNoCase(szCmd, "R"))
eState = DS_StepOut;
else if(SEqualNoCase(szCmd, "EXC") || SEqualNoCase(szCmd, "E"))
Game.Control.DoInput(CID_Script, new C4ControlScript(szData, C4ControlScript::SCOPE_Console, false), CDT_Decide);
else if(SEqualNoCase(szCmd, "PSE"))
if(Game.IsPaused())
{
Game.Unpause();
szAnswer = "Game unpaused.";
}
else
{
Game.Pause();
szAnswer = "Game paused.";
}
else
{
fOkay = false;
szAnswer = "Can't do that";
}
// Send answer
SendLine(fOkay ? "OK" : "ERR", szAnswer);
}
bool C4AulDebug::SendLine(const char *szType, const char *szData)
{
StdStrBuf Line = szData ? FormatString("%s %s", szType, szData) : StdStrBuf(szType);
return Send(C4NetIOPacket(Line.getData(), Line.getSize(), false, PeerAddr));
}
void C4AulDebug::DebugStep(C4AulBCC *pCPos)
{
// Get top context
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...
if(eState == DS_Stop)
return;
// Have break point?
if(pCPos->Par.i)
eState = DS_Step;
StepPoint(pCPos);
}
void C4AulDebug::DebugStepIn(C4AulBCC *pCPos)
{
}
void C4AulDebug::DebugStepOut(C4AulBCC *pCPos, C4AulScriptContext *pRetCtx, C4Value *pRVal)
{
// Ignore if already suspended, see above.
if(eState == DS_Stop)
return;
// This counts as a regular step point
StepPoint(pCPos, pRetCtx, pRVal);
}
void C4AulDebug::StepPoint(C4AulBCC *pCPos, C4AulScriptContext *pRetCtx, C4Value *pRVal)
{
// Maybe got a command in the meantime?
Execute(0);
// 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.
if(pCtx == pRetCtx)
{
iCallDepth--;
// Check if we are returning to a script function
if(pCtx->Return)
pCtx--;
else
pCtx = NULL;
}
// Stepped out?
if(pRVal && eState != DS_Go && iCallDepth <= iStepCallDepth)
{
StdStrBuf FuncDump = pRetCtx->Dump();
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
if(iCallDepth >= iStepCallDepth) return;
}
// Stop?
switch(eState)
{
// Continue normally
case DS_Go: return;
// Always stop
case DS_Stop: break;
case DS_Step: break;
// Only stop for same level or above
case DS_StepOver:
if(iCallDepth > iStepCallDepth)
return;
break;
// Only stop above
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
if(!pCtx)
{
Game.HaltCount--;
eState = DS_Step;
return;
}
// Signal
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
SendLine("POS", FormatCodePos(pCtx, pCPos).getData());
// Suspend until we get some command
while(eState == DS_Stop)
if(!Application.ScheduleProcs())
{
Close();
return;
}
// Do whatever we've been told.
Game.HaltCount--;
}
StdStrBuf C4AulDebug::FormatCodePos(C4AulScriptContext *pCtx, C4AulBCC *pCPos)
{
// Get position in script
const char *szScript = pCtx->SFunc()->pOrgScript->GetScript();
int iLine = SGetLine(szScript, pCPos->SPos);
// Format
return FormatString("%s:%d", Config.AtRelativePath(pCtx->Func->Owner->ScriptName.getData()), iLine);
}

View File

@ -18,6 +18,8 @@
#include <C4Include.h>
#include <C4Aul.h>
#include <C4AulExec.h>
#include <C4AulDebug.h>
#ifndef BIG_C4INCLUDE
#include <C4Object.h>
@ -26,6 +28,8 @@
#include <C4Wrappers.h>
#endif
C4AulExec AulExec;
C4AulExecError::C4AulExecError(C4Object *pObj, const char *szError) : cObj(pObj)
{
// direct error message string
@ -46,38 +50,35 @@ void C4AulExecError::show()
#endif
}
const int MAX_CONTEXT_STACK = 512;
const int MAX_VALUE_STACK = 1024;
void C4AulScriptContext::dump(StdStrBuf Dump)
{
StdStrBuf C4AulScriptContext::Dump(StdStrBuf Dump)
{
bool fDirectExec = !*Func->Name;
if(!fDirectExec)
{
{
// Function name
Dump.Append(Func->Name);
// Parameters
Dump.AppendChar('(');
int iNullPars = 0;
for(int i = 0; i < C4AUL_MAX_Par; i++)
for(int i = 0; i < Func->GetParCount(); i++)
if(Pars + i < Vars)
if(!Pars[i])
iNullPars++;
else
{
{
if(i > iNullPars)
Dump.AppendChar(',');
// Insert missing null parameters
while(iNullPars > 0)
{
{
Dump.Append("0,");
iNullPars--;
}
}
// Insert parameter
Dump.Append(Pars[i].GetDataString());
}
}
Dump.AppendChar(')');
}
}
else
Dump.Append(Func->Owner->ScriptName);
// Context
@ -85,192 +86,26 @@ void C4AulScriptContext::dump(StdStrBuf Dump)
Dump.AppendFormat(" (obj %s)", C4VObj(Obj).GetDataString().getData());
else if(Func->Owner->Def != NULL)
Dump.AppendFormat(" (def %s)", Func->Owner->Def->Name.getData());
return Dump;
}
StdStrBuf C4AulScriptContext::DumpVerbose(StdStrBuf Dump)
{
bool fDirectExec = !*Func->Name;
// Dump function and context
Dump = this->Dump(Dump);
// Script
if(!fDirectExec && Func->Owner)
if(!fDirectExec && Func->Owner && SFunc())
Dump.AppendFormat(" (%s:%d)",
Func->pOrgScript->ScriptName.getData(),
SGetLine(Func->pOrgScript->GetScript(), CPos ? CPos->SPos : Func->Script));
// Log it
DebugLog(Dump.getData());
}
SFunc()->pOrgScript->ScriptName.getData(),
SGetLine(SFunc()->pOrgScript->GetScript(), CPos ? CPos->SPos : SFunc()->Script));
return Dump;
}
class C4AulExec
{
public:
C4AulExec()
: pCurCtx(Contexts - 1), pCurVal(Values - 1), iTraceStart(-1)
{ }
private:
C4AulScriptContext Contexts[MAX_CONTEXT_STACK];
C4Value Values[MAX_VALUE_STACK];
C4AulScriptContext *pCurCtx;
C4Value *pCurVal;
int iTraceStart;
bool fProfiling;
time_t tDirectExecStart, tDirectExecTotal; // profiler time for DirectExec
C4AulScript *pProfiledScript;
public:
C4Value Exec(C4AulScriptFunc *pSFunc, C4Object *pObj, C4Value pPars[], bool fPassErrors, bool fTemporaryScript = false);
C4Value Exec(C4AulBCC *pCPos, bool fPassErrors);
void StartTrace();
void StartProfiling(C4AulScript *pScript); // resets profling times and starts recording the times
void StopProfiling(); // stop the profiler and displays results
void AbortProfiling() { fProfiling=false; }
inline void StartDirectExec() { if (fProfiling) tDirectExecStart = timeGetTime(); }
inline void StopDirectExec() { if (fProfiling) tDirectExecTotal += timeGetTime() - tDirectExecStart; }
private:
void PushContext(const C4AulScriptContext &rContext)
{
if(pCurCtx >= Contexts + MAX_CONTEXT_STACK - 1)
throw new C4AulExecError(pCurCtx->Obj, "context stack overflow!");
*++pCurCtx = rContext;
// Trace?
if(iTraceStart >= 0)
{
StdStrBuf Buf("T");
Buf.AppendChars('>', ContextStackSize() - iTraceStart);
pCurCtx->dump(Buf);
}
// Profiler: Safe time to measure difference afterwards
if (fProfiling) pCurCtx->tTime = timeGetTime();
}
void PopContext()
{
if(pCurCtx < Contexts)
throw new C4AulExecError(pCurCtx->Obj, "context stack underflow!");
// Profiler adding up times
if (fProfiling)
{
time_t dt = timeGetTime() - pCurCtx->tTime;
if (dt && pCurCtx->Func)
pCurCtx->Func->tProfileTime += dt;
}
// Trace done?
if(iTraceStart >= 0)
{
if(ContextStackSize() <= iTraceStart)
{
iTraceStart = -1;
}
}
if(pCurCtx->TemporaryScript)
delete pCurCtx->Func->Owner;
pCurCtx--;
}
void CheckOverflow(int iCnt)
{
if(ValueStackSize() + iCnt > MAX_VALUE_STACK)
throw new C4AulExecError(pCurCtx->Obj, "internal error: value stack overflow!");
}
void PushString(C4String * Str)
{
CheckOverflow(1);
(++pCurVal)->SetString(Str);
}
void PushArray(C4ValueArray * Array)
{
CheckOverflow(1);
(++pCurVal)->SetArray(Array);
}
void PushValue(const C4Value &rVal)
{
CheckOverflow(1);
(++pCurVal)->Set(rVal);
}
void PushValueRef(C4Value &rVal)
{
CheckOverflow(1);
(++pCurVal)->SetRef(&rVal);
}
void PushNullVals(int iCnt)
{
CheckOverflow(iCnt);
pCurVal += iCnt;
}
bool PopValue()
{
if(LocalValueStackSize() < 1)
throw new C4AulExecError(pCurCtx->Obj, "internal error: value stack underflow!");
(pCurVal--)->Set0();
return true;
}
void PopValues(int n)
{
if(LocalValueStackSize() < n)
throw new C4AulExecError(pCurCtx->Obj, "internal error: value stack underflow!");
while(n--)
(pCurVal--)->Set0();
}
void PopValuesUntil(C4Value *pUntilVal)
{
if(pUntilVal < Values - 1)
throw new C4AulExecError(pCurCtx->Obj, "internal error: value stack underflow!");
while(pCurVal > pUntilVal)
(pCurVal--)->Set0();
}
int ContextStackSize() const
{
return pCurCtx - Contexts + 1;
}
int ValueStackSize() const
{
return pCurVal - Values + 1;
}
int LocalValueStackSize() const
{
return ContextStackSize()
? pCurVal - pCurCtx->Vars - pCurCtx->Func->VarNamed.iSize + 1
: pCurVal - Values + 1;
}
void CheckOpPars(int iOpID)
{
// Get parameters
C4Value *pPar1 = pCurVal - 1, *pPar2 = pCurVal;
// Typecheck parameters
if(!pPar1->ConvertTo(C4ScriptOpMap[iOpID].Type1))
throw new C4AulExecError(pCurCtx->Obj,
FormatString("operator \"%s\" left side: got \"%s\", but expected \"%s\"!",
C4ScriptOpMap[iOpID].Identifier, pPar1->GetTypeInfo(), GetC4VName(C4ScriptOpMap[iOpID].Type1)).getData());
if(!pPar2->ConvertTo(C4ScriptOpMap[iOpID].Type2))
throw new C4AulExecError(pCurCtx->Obj,
FormatString("operator \"%s\" right side: got \"%s\", but expected \"%s\"!",
C4ScriptOpMap[iOpID].Identifier, pPar2->GetTypeInfo(), GetC4VName(C4ScriptOpMap[iOpID].Type2)).getData());
}
void CheckOpPar(int iOpID)
{
// Typecheck parameter
if(!pCurVal->ConvertTo(C4ScriptOpMap[iOpID].Type1))
throw new C4AulExecError(pCurCtx->Obj,
FormatString("operator \"%s\": got \"%s\", but expected \"%s\"!",
C4ScriptOpMap[iOpID].Identifier, pCurVal->GetTypeInfo(), GetC4VName(C4ScriptOpMap[iOpID].Type1)).getData());
}
C4AulBCC *Call(C4AulFunc *pFunc, C4Value *pReturn, C4Value *pPars, C4Object *pObj = NULL, C4Def *pDef = NULL);
};
C4AulExec AulExec;
void C4AulScriptContext::dump(StdStrBuf Dump)
{
DebugLog(DumpVerbose(Dump).getData());
}
C4Value C4AulExec::Exec(C4AulScriptFunc *pSFunc, C4Object *pObj, C4Value *pnPars, bool fPassErrors, bool fTemporaryScript)
{
@ -312,6 +147,10 @@ C4Value C4AulExec::Exec(C4AulScriptFunc *pSFunc, C4Object *pObj, C4Value *pnPars
C4Value C4AulExec::Exec(C4AulBCC *pCPos, bool fPassErrors)
{
// Debugger pointer
C4AulDebug * const pDebug = Game.ScriptEngine.GetDebugger();
pDebug->DebugStepIn(pCPos);
// Save start context
C4AulScriptContext *pOldCtx = pCurCtx;
@ -821,7 +660,8 @@ C4Value C4AulExec::Exec(C4AulBCC *pCPos, bool fPassErrors)
case AB_RETURN:
{
// Resolve reference
if(!pCurCtx->Func->SFunc()->bReturnRef)
C4AulFunc *pFunc = pCurCtx->Func;
if(!pFunc->SFunc()->bReturnRef)
pCurVal->Deref();
// Trace
@ -832,8 +672,12 @@ C4Value C4AulExec::Exec(C4AulBCC *pCPos, bool fPassErrors)
LogF("%s%s returned %s", Buf.getData(), pCurCtx->Func->Name, pCurVal->GetDataString().getData());
}
// External call?
// Notify debugger
C4Value *pReturn = pCurCtx->Return;
if(pDebug)
pDebug->DebugStepOut(pReturn ? (pCurCtx-1)->CPos + 1 : NULL, pCurCtx, pCurVal);
// External call?
if(!pReturn)
{
// Get return value and stop executing.
@ -1011,6 +855,10 @@ C4Value C4AulExec::Exec(C4AulBCC *pCPos, bool fPassErrors)
break;
}
case AB_DEBUG:
if(pDebug) pDebug->DebugStep(pCPos);
break;
default:
assert(false);
}
@ -1094,6 +942,10 @@ C4AulBCC *C4AulExec::Call(C4AulFunc *pFunc, C4Value *pReturn, C4Value *pPars, C4
ctx.CPos = NULL;
PushContext(ctx);
// Notify debugger
if(C4AulDebug *pDebug = Game.ScriptEngine.GetDebugger())
pDebug->DebugStepIn(pSFunc->Code);
// Jump to code
return pSFunc->Code;
}
@ -1106,7 +958,6 @@ C4AulBCC *C4AulExec::Call(C4AulFunc *pFunc, C4Value *pReturn, C4Value *pPars, C4
CallCtx.Def = pDef;
CallCtx.Caller = pCurCtx;
#ifdef DEBUGREC_SCRIPT
if (Game.FrameCounter >= DEBUGREC_START_FRAME)
{
@ -1139,20 +990,43 @@ C4AulBCC *C4AulExec::Call(C4AulFunc *pFunc, C4Value *pReturn, C4Value *pPars, C4
AddDbgRec(RCT_AulFunc, sCallText.getData(), sCallText.getLength()+1);
}
#endif
// Execute
#ifdef _DEBUG
C4AulScriptContext *pCtx = pCurCtx;
#endif
if(pReturn > pCurVal)
PushValue(pFunc->Exec(&CallCtx, pPars, true));
else
pReturn->Set(pFunc->Exec(&CallCtx, pPars, true));
#endif
C4Value RVal(pFunc->Exec(&CallCtx, pPars, true));
#ifdef _DEBUG
assert(pCtx == pCurCtx);
#endif
// Remove parameters from stack
PopValuesUntil(pReturn);
// Notify debugger
if(C4AulDebug *pDebug = Game.ScriptEngine.GetDebugger())
{
// Make dummy context
C4AulScriptContext ctx;
ctx.Obj = pObj;
ctx.Def = pDef;
ctx.Caller = pCurCtx;
ctx.Return = pReturn;
ctx.Pars = pPars;
ctx.Vars = pPars + pFunc->GetParCount();
ctx.Func = pFunc;
ctx.TemporaryScript = false;
ctx.CPos = NULL;
pDebug->DebugStepOut(pCurCtx->CPos + 1, &ctx, &RVal);
}
// Save return value onto stack
if(pReturn > pCurVal)
PushValue(pFunc->Exec(&CallCtx, pPars, true));
else
{
pReturn->Set(pFunc->Exec(&CallCtx, pPars, true));
// Remove parameters from stack
PopValuesUntil(pReturn);
}
// Continue
return NULL;
@ -1198,6 +1072,46 @@ void C4AulExec::StopProfiling()
Profiler.Show();
}
void C4AulExec::PushContext(const C4AulScriptContext &rContext)
{
if(pCurCtx >= Contexts + MAX_CONTEXT_STACK - 1)
throw new C4AulExecError(pCurCtx->Obj, "context stack overflow!");
*++pCurCtx = rContext;
// Trace?
if(iTraceStart >= 0)
{
StdStrBuf Buf("T");
Buf.AppendChars('>', ContextStackSize() - iTraceStart);
pCurCtx->dump(Buf);
}
// Profiler: Safe time to measure difference afterwards
if (fProfiling) pCurCtx->tTime = timeGetTime();
}
void C4AulExec::PopContext()
{
if(pCurCtx < Contexts)
throw new C4AulExecError(pCurCtx->Obj, "context stack underflow!");
// Profiler adding up times
if (fProfiling)
{
time_t dt = timeGetTime() - pCurCtx->tTime;
if (dt && pCurCtx->Func)
pCurCtx->SFunc()->tProfileTime += dt;
}
// Trace done?
if(iTraceStart >= 0)
{
if(ContextStackSize() <= iTraceStart)
{
iTraceStart = -1;
}
}
if(pCurCtx->TemporaryScript)
delete pCurCtx->Func->Owner;
pCurCtx--;
}
void C4AulProfiler::StartProfiling(C4AulScript *pScript)
{
AulExec.StartProfiling(pScript);

View File

@ -1102,6 +1102,7 @@ void C4AulParseState::AddBCC(C4AulBCCType eType, intptr_t X)
case AB_EOF:
case AB_JUMP:
case AB_CALLNS:
case AB_DEBUG:
break;
case AB_STACK:
@ -1777,6 +1778,7 @@ void C4AulParseState::Parse_Function()
C4AulBCC * CPos = a->GetCodeByPos(Max(a->GetCodePos() - 1,0));
if (!CPos || CPos->bccType != AB_RETURN || fJump)
{
AddBCC(AB_DEBUG);
AddBCC(AB_INT);
AddBCC(AB_RETURN);
}
@ -1819,6 +1821,7 @@ void C4AulParseState::Parse_Block()
}
void C4AulParseState::Parse_Statement()
{
AddBCC(AB_DEBUG);
switch (TokenType)
{
// do we have a block start?

View File

@ -835,7 +835,6 @@ const char* C4Config::AtDataReadPath(const char *szFilename, bool fPreferWorkdir
const char* C4Config::AtDataReadPathCore(const char *szFilename, bool fPreferWorkdir)
{
const char *szPath;
if (fPreferWorkdir && FileExists(szFilename))
{
SCopy(GetWorkingDirectory(),AtPathFilename,_MAX_PATH-1);

View File

@ -1706,6 +1706,10 @@ void C4Game::Default()
pNetworkStatistics = NULL;
iMusicLevel = 100;
PlayList.Clear();
DebugPort = 0;
DebugPassword.Clear();
DebugHost.Clear();
DebugWait = false;
}
void C4Game::Evaluate()
@ -2475,6 +2479,11 @@ BOOL C4Game::LinkScriptEngine()
// Set name list for globals
ScriptEngine.GlobalNamed.SetNameList(&ScriptEngine.GlobalNamedNames);
// Activate debugger if requested
if(DebugPort)
if(!ScriptEngine.InitDebug(DebugPort, DebugPassword.getData(), DebugHost.getData(), DebugWait))
return false;
return TRUE;
}
@ -2967,6 +2976,15 @@ void C4Game::ParseCommandLine(const char *szCmdLine)
// additional read-only data path
if (SEqual2NoCase(szParameter, "/data:"))
Config.General.AddAdditionalDataPath(szParameter + 6);
// debug options
if (SEqual2NoCase(szParameter, "/debug:"))
DebugPort = atoi(szParameter + 7);
if (SEqual2NoCase(szParameter, "/debugpass:"))
DebugPassword = szParameter + 11;
if (SEqual2NoCase(szParameter, "/debughost:"))
DebugHost = szParameter + 11;
if (SEqual2NoCase(szParameter, "/debugwait"))
DebugWait = true;
#ifdef _DEBUG
// debug configs
if (SEqualNoCase(szParameter, "/host"))

View File

@ -19,6 +19,7 @@
#include <C4Include.h>
#include <C4Log.h>
#include <C4AulDebug.h>
#ifndef BIG_C4INCLUDE
#include <C4Console.h>
@ -149,6 +150,10 @@ bool Log(const char *szMessage)
if (iDisableLog) return true;
// security
if(!szMessage) return false;
// Pass on to debugger
if(C4AulDebug *pDebug = Game.ScriptEngine.GetDebugger())
pDebug->OnLog(szMessage);
// Pass on to console
Console.Out(szMessage);
// pass on to lobby

View File

@ -1405,7 +1405,7 @@ static long FnGetBase(C4AulContext *cthr, C4Object *pObj)
static C4ID FnGetMenu(C4AulContext *cthr, C4Object *pObj)
{
if (!pObj) pObj=cthr->Obj; if (!pObj) return C4ID(-1);
if (!pObj) pObj=cthr->Obj; if (!pObj) return C4ID(0);
if (pObj->Menu && pObj->Menu->IsActive())
return pObj->Menu->GetIdentification();
return C4MN_None;
@ -4637,7 +4637,7 @@ static C4Value FnEval(C4AulContext *cthr, C4Value *strScript_C4V)
// execute script in the same object
enum C4AulScript::Strict Strict = C4AulScript::MAXSTRICT;
if (cthr->Caller)
Strict = cthr->Caller->Func->pOrgScript->Strict;
Strict = cthr->Caller->SFunc()->pOrgScript->Strict;
if (cthr->Obj)
return cthr->Obj->Def->Script.DirectExec(cthr->Obj, FnStringPar(strScript_C4V->getStr()), "eval", true, Strict);
else if (cthr->Def)
@ -4715,7 +4715,7 @@ static C4Value FnVarN(C4AulContext *cthr, C4Value *strName_C4V)
if(!cthr->Caller) return C4VNull;
// find variable
int32_t iID = cthr->Caller->Func->VarNamed.GetItemNr(strName);
int32_t iID = cthr->Caller->SFunc()->VarNamed.GetItemNr(strName);
if(iID < 0)
return C4VNull;

View File

@ -27,6 +27,7 @@
AFFAA14E0C2A896200444B95 /* C4ChatDlg.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AFFAA14B0C2A896200444B95 /* C4ChatDlg.cpp */; };
AFFAA14F0C2A896200444B95 /* C4InteractiveThread.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AFFAA14C0C2A896200444B95 /* C4InteractiveThread.cpp */; };
AFFAA1500C2A896200444B95 /* C4Network2IRC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AFFAA14D0C2A896200444B95 /* C4Network2IRC.cpp */; };
D0AEFAD70FBF8EC400E2229D /* C4AulDebug.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D0AEFAD60FBF8EC400E2229D /* C4AulDebug.cpp */; };
D406269A0BB9440C00815E5D /* c4group_ng.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D40626990BB9440C00815E5D /* c4group_ng.cpp */; };
D40626A00BB9477A00815E5D /* C4Config.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D439CADC0B8CB129001D508B /* C4Config.cpp */; };
D40626A10BB9477A00815E5D /* C4Group.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D439CAF80B8CB12A001D508B /* C4Group.cpp */; };
@ -284,6 +285,9 @@
AFFAA14B0C2A896200444B95 /* C4ChatDlg.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = C4ChatDlg.cpp; sourceTree = "<group>"; };
AFFAA14C0C2A896200444B95 /* C4InteractiveThread.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = C4InteractiveThread.cpp; sourceTree = "<group>"; };
AFFAA14D0C2A896200444B95 /* C4Network2IRC.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = C4Network2IRC.cpp; sourceTree = "<group>"; };
D0AEFAA10FBF8E5500E2229D /* C4AulDebug.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = C4AulDebug.h; sourceTree = "<group>"; };
D0AEFAA20FBF8E5500E2229D /* C4AulExec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = C4AulExec.h; sourceTree = "<group>"; };
D0AEFAD60FBF8EC400E2229D /* C4AulDebug.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = C4AulDebug.cpp; sourceTree = "<group>"; };
D40626910BB9418400815E5D /* c4group */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = c4group; sourceTree = BUILT_PRODUCTS_DIR; };
D40626990BB9440C00815E5D /* c4group_ng.cpp */ = {isa = PBXFileReference; fileEncoding = 12; lastKnownFileType = sourcecode.cpp.cpp; name = c4group_ng.cpp; path = ../group/c4group_ng.cpp; sourceTree = SOURCE_ROOT; };
D40626AA0BB947A700815E5D /* C4Update.cpp */ = {isa = PBXFileReference; fileEncoding = 12; lastKnownFileType = sourcecode.cpp.cpp; path = C4Update.cpp; sourceTree = "<group>"; };
@ -886,6 +890,7 @@
D439CAD10B8CB129001D508B /* src */ = {
isa = PBXGroup;
children = (
D0AEFAD60FBF8EC400E2229D /* C4AulDebug.cpp */,
3562890E0DA62F9000FB1482 /* C4MainMenu.cpp */,
3562890F0DA62F9000FB1482 /* C4ObjectMenu.cpp */,
3588FB750DA0FFD2001544C0 /* C4FileSelDlg.cpp */,
@ -1068,6 +1073,8 @@
D45B48A40D6B333A001A7AE2 /* inc */ = {
isa = PBXGroup;
children = (
D0AEFAA10FBF8E5500E2229D /* C4AulDebug.h */,
D0AEFAA20FBF8E5500E2229D /* C4AulExec.h */,
356289130DA62FA600FB1482 /* C4MainMenu.h */,
356288E60DA62CAD00FB1482 /* C4ObjectMenu.h */,
3588FB730DA0FFBE001544C0 /* C4FileSelDlg.h */,
@ -1515,6 +1522,7 @@
356289100DA62F9000FB1482 /* C4MainMenu.cpp in Sources */,
356289110DA62F9000FB1482 /* C4ObjectMenu.cpp in Sources */,
3514CB3A0DBCCE5F00534809 /* OpenURL.cpp in Sources */,
D0AEFAD70FBF8EC400E2229D /* C4AulDebug.cpp in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};