OpenClonk
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros
C4AulDebug.cpp
Go to the documentation of this file.
1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 2009-2016, The OpenClonk Team and contributors
5  *
6  * Distributed under the terms of the ISC license; see accompanying file
7  * "COPYING" for details.
8  *
9  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
10  * See accompanying file "TRADEMARK" for details.
11  *
12  * To redistribute this file separately, substitute the full license texts
13  * for the above references.
14  */
15 
16 #include "C4Include.h"
17 #include "script/C4AulDebug.h"
18 
19 #include "C4Version.h"
20 #include "control/C4GameControl.h"
21 #include "game/C4Application.h"
22 #include "gui/C4MessageInput.h"
23 #include "object/C4Def.h"
24 #include "object/C4Object.h"
25 #include "script/C4AulExec.h"
26 
27 #ifndef NOAULDEBUG
28 
29 // *** C4AulDebug
30 
32  : fInit(false), fConnected(false)
33 {
34 }
35 
37 {
38  for (auto & it : StackTrace)
39  delete it;
40  if (pDebug == this) pDebug = nullptr;
41 }
42 
43 bool C4AulDebug::InitDebug(const char *szPassword, const char *szHost)
44 {
45  // Create debug object
46  if (!pDebug) pDebug = new C4AulDebug();
47  // Initialize
48  pDebug->SetPassword(szPassword);
49  pDebug->SetAllowed(szHost);
50  pDebug->SetEngine(&AulExec);
51  return true;
52 }
53 
54 bool C4AulDebug::Listen(uint16_t iPort, bool fWait)
55 {
56  if (!Init(iPort))
57  { LogFatal("C4Aul debugger failed to initialize!"); return false; }
58  // Log
59  LogF("C4Aul debugger initialized on port %d", iPort);
60  // Add to application
61  Application.Add(this);
62  // Wait for connection
63  if (fWait)
64  {
65  Log("C4Aul debugger waiting for connection...");
66  while (!isConnected())
68  return false;
69  }
70  // Done
71  return true;
72 }
73 
74 void C4AulDebug::PackPacket(const C4NetIOPacket &rPacket, StdBuf &rOutBuf)
75 {
76  // Enlarge buffer
77  int iSize = rPacket.getSize(),
78  iPos = rOutBuf.getSize();
79  rOutBuf.Grow(iSize + 2);
80  // Write packet
81  rOutBuf.Write(rPacket, iPos);
82  // Terminate
83  uint8_t *pPos = getMBufPtr<uint8_t>(rOutBuf, iPos + iSize);
84  *pPos = '\r'; *(pPos + 1) = '\n';
85 }
86 
87 size_t C4AulDebug::UnpackPacket(const StdBuf &rInBuf, const C4NetIO::addr_t &addr)
88 {
89  // Find line separation
90  const char *pSep = reinterpret_cast<const char *>(memchr(rInBuf.getData(), '\n', rInBuf.getSize()));
91  if (!pSep)
92  return 0;
93  // Check if it's windows-style separation
94  int iSize = pSep - getBufPtr<char>(rInBuf) + 1,
95  iLength = iSize - 1;
96  if (iLength && *(pSep - 1) == '\r')
97  iLength--;
98  // Copy the line
99  StdStrBuf Buf; Buf.Copy(getBufPtr<char>(rInBuf), iLength);
100  // Password line?
101  if (fConnected)
102  {
103  ProcessLineResult result = ProcessLine(Buf);
104  // Send answer
105  SendLine(result.okay ? "OK" : "ERR", result.answer.length() > 0 ? result.answer.c_str() : nullptr);
106  }
107  else if (!Password.getSize() || Password == Buf)
108  {
109  fConnected = true;
110  SendLine("HLO", "This is " C4ENGINECAPTION ", " C4VERSION);
111  Log("C4Aul debugger connected successfully!");
112  }
113  else
114  C4NetIOTCP::Close(PeerAddr);
115  // Consume line
116  return iSize;
117 }
118 
119 bool C4AulDebug::OnConn(const C4NetIO::addr_t &AddrPeer, const C4NetIO::addr_t &AddrConnect, const addr_t *pOwnAddr, C4NetIO *pNetIO)
120 {
121  assert(pNetIO == this);
122  // Already have a connection?
123  if (fConnected) return false;
124  // Check address
125  if (!AllowedAddr.IsNull())
126  if (AllowedAddr.GetHost() != AddrPeer.GetHost() ||
127  (AllowedAddr.GetPort() && AllowedAddr.GetPort() != AddrPeer.GetPort()))
128  {
129  LogF("C4AulDebug blocked connection from %s", AddrPeer.ToString().getData());
130  return false;
131  }
132  // Log
133  LogF("C4AulDebug got connection from %s", AddrPeer.ToString().getData());
134  // Accept connection
135  PeerAddr = AddrPeer;
136  return true;
137 }
138 
139 void C4AulDebug::OnDisconn(const C4NetIO::addr_t &AddrPeer, C4NetIO *pNetIO, const char *szReason)
140 {
141  LogF("C4AulDebug lost connection (%s)", szReason);
142  fConnected = false;
143  eState = DS_Go;
144  PeerAddr.Clear();
145 }
146 
147 void C4AulDebug::OnPacket(const class C4NetIOPacket &rPacket, C4NetIO *pNetIO)
148 {
149  // Won't get called
150 }
151 
152 bool C4AulDebug::SetAllowed(const char *szHost)
153 {
154  // Clear
155  AllowedAddr.Clear();
156  // No host?
157  if (!szHost || !*szHost) return true;
158  // Resolve the address
159  AllowedAddr.SetAddress(StdStrBuf(szHost));
160  return !AllowedAddr.IsNull();
161 }
162 
163 bool C4AulDebug::Init(uint16_t iPort)
164 {
165  if (fInit) Close();
166  if (iPort == EndpointAddress::IPPORT_NONE) return false;
167 
168  // Register self as callback for network events
170 
171  // Start listening
172  if (!C4NetIOTCP::Init(iPort))
173  return false;
174 
175  // Okay
176  fInit = true;
177  eState = DS_Go;
178  return true;
179 }
180 
182 {
183  if (!fInit) return true;
184  fInit = fConnected = false;
185  return C4NetIOTCP::Close();
186 }
187 
188 bool C4AulDebug::Close(const addr_t &addr)
189 {
190  if (!fInit) return true;
191  bool success = C4NetIOTCP::Close(addr);
192  if (success)
193  fInit = fConnected = false;
194  return success;
195 }
196 
197 void C4AulDebug::OnLog(const char *szLine)
198 {
199  if (!fConnected) return;
200  SendLine("LOG", szLine);
201 }
202 
203 C4AulDebug::ProcessLineResult C4AulDebug::ProcessLine(const StdStrBuf &Line)
204 {
205  // Get command
206  StdStrBuf Cmd;
207  Cmd.CopyUntil(Line.getData(), ' ');
208  // Get data
209  const char *szData = Line.getPtr(Cmd.getLength());
210  if (*szData) szData++;
211  // Identify command
212  const char *szCmd = Cmd.getData();
213  if (SEqualNoCase(szCmd, "HELP"))
214  return ProcessLineResult(false, "Yeah, like I'm going to explain that /here/");
215  else if (SEqualNoCase(szCmd, "BYE") || SEqualNoCase(szCmd, "QUIT"))
216  C4NetIOTCP::Close(PeerAddr);
217  else if (SEqualNoCase(szCmd, "SAY"))
219  else if (SEqualNoCase(szCmd, "CMD"))
221  else if (SEqualNoCase(szCmd, "STP") || SEqualNoCase(szCmd, "S"))
222  eState = DS_Step;
223  else if (SEqualNoCase(szCmd, "GO") || SEqualNoCase(szCmd, "G"))
224  eState = DS_Go;
225  else if (SEqualNoCase(szCmd, "STO") || SEqualNoCase(szCmd, "O"))
226  eState = DS_StepOver;
227  else if (SEqualNoCase(szCmd, "STR") || SEqualNoCase(szCmd, "R"))
228  eState = DS_StepOut;
229  else if (SEqualNoCase(szCmd, "EXC") || SEqualNoCase(szCmd, "E"))
230  {
231  C4AulScriptContext* context = pExec->GetContext(pExec->GetContextDepth()-1);
232  int32_t objectNum = C4ControlScript::SCOPE_Global;
233  if (context && context->Obj && context->Obj->GetObject())
234  objectNum = context->Obj->GetObject()->Number;
235  ::Control.DoInput(CID_Script, new C4ControlScript(szData, objectNum, true), CDT_Decide);
236  }
237  else if (SEqualNoCase(szCmd, "PSE"))
238  if (Game.IsPaused())
239  {
240  Game.Unpause();
241  return ProcessLineResult(true, "Game unpaused.");
242  }
243  else
244  {
245  Game.Pause();
246  return ProcessLineResult(true, "Game paused.");
247  }
248  else if (SEqualNoCase(szCmd, "LST"))
249  {
250  for (C4ScriptHost* script = ScriptEngine.Child0; script; script = script->Next)
251  {
252  SendLine(RelativePath(script->ScriptName));
253  }
254  }
255 
256  // toggle breakpoint
257  else if (SEqualNoCase(szCmd, "TBR"))
258  {
259  using namespace std;
260  // FIXME: this doesn't find functions which were included/appended
261  string scriptPath = szData;
262  size_t colonPos = scriptPath.find(':');
263  if (colonPos == string::npos)
264  return ProcessLineResult(false, "Missing line in breakpoint request");
265  int line = atoi(&scriptPath[colonPos+1]);
266  scriptPath.erase(colonPos);
267 
268  C4ScriptHost *script;
269  for (script = ScriptEngine.Child0; script; script = script->Next)
270  {
271  if (SEqualNoCase(RelativePath(script->ScriptName), scriptPath.c_str()))
272  break;
273  }
274 
275  auto sh = script;
276  if (sh)
277  {
278  C4AulBCC * found = nullptr;
279  for (auto script = ::ScriptEngine.Child0; script; script = script->Next)
280  for (C4PropList *props = script->GetPropList(); props; props = props->GetPrototype())
281  for (auto fname = props->EnumerateOwnFuncs(); fname; fname = props->EnumerateOwnFuncs(fname))
282  {
283  C4Value val;
284  if (!props->GetPropertyByS(fname, &val)) continue;
285  auto func = val.getFunction();
286  if (!func) continue;
287  auto sfunc = func->SFunc();
288  if (!sfunc) continue;
289  if (sfunc->pOrgScript != sh) continue;
290  for (auto chunk = sfunc->GetCode(); chunk->bccType != AB_EOFN; chunk++)
291  {
292  if (chunk->bccType == AB_DEBUG)
293  {
294  int lineOfThisOne = sfunc->GetLineOfCode(chunk);
295  if (lineOfThisOne == line)
296  {
297  found = chunk;
298  goto Found;
299  }
300  }
301  }
302  }
303  Found:
304  if (found)
305  found->Par.i = !found->Par.i; // activate breakpoint
306  else
307  return ProcessLineResult(false, "Can't set breakpoint (wrong line?)");
308  }
309  else
310  return ProcessLineResult(false, "Can't find script");
311  }
312  else if (SEqualNoCase(szCmd, "SST"))
313  {
314  std::list<StdStrBuf*>::iterator it = StackTrace.begin();
315  for (it++; it != StackTrace.end(); it++)
316  {
317  SendLine("AT", (*it)->getData());
318  }
319  SendLine("EST");
320  }
321  else if (SEqualNoCase(szCmd, "VAR"))
322  {
323 
324  C4Value *val = nullptr;
325  int varIndex;
326  C4AulScriptContext* pCtx = pExec->GetContext(pExec->GetContextDepth() - 1);
327  if (pCtx)
328  {
329  if ((varIndex = pCtx->Func->ParNamed.GetItemNr(szData)) != -1)
330  {
331  val = &pCtx->Pars[varIndex];
332  }
333  else if ((varIndex = pCtx->Func->VarNamed.GetItemNr(szData)) != -1)
334  {
335  val = &pCtx->Pars[pCtx->Func->GetParCount() + varIndex];
336  }
337  }
338  const char* typeName = val ? GetC4VName(val->GetType()) : "any";
339  StdStrBuf output = FormatString("%s %s %s", szData, typeName, val ? val->GetDataString().getData() : "Unknown");
340  SendLine("VAR", output.getData());
341  }
342  else
343  return ProcessLineResult(false, "Can't do that");
344 
345  return ProcessLineResult(true, "");
346 }
347 
348 bool C4AulDebug::SendLine(const char *szType, const char *szData)
349 {
350  StdStrBuf Line = szData ? FormatString("%s %s", szType, szData) : StdStrBuf(szType);
351  return Send(C4NetIOPacket(Line.getData(), Line.getSize(), false, PeerAddr));
352 }
353 
354 void C4AulDebug::DebugStep(C4AulBCC *pCPos, C4Value* stackTop)
355 {
356  // Already stopped? Ignore.
357  // This means we are doing some calculation with suspended script engine.
358  // We do /not/ want to have recursive suspensions...
359  if (eState == DS_Stop)
360  return;
361 
362  // Have break point?
363  if (pCPos->Par.i)
364  eState = DS_Step;
365 
366  int iCallDepth = pExec->GetContextDepth();
367  // Stop?
368  switch (eState)
369  {
370  // Continue normally
371  case DS_Go: return;
372 
373  // Always stop
374  case DS_Stop: break;
375  case DS_Step: break;
376 
377  // Only stop for same level or above
378  case DS_StepOver:
379  if (iCallDepth > iStepCallDepth)
380  return;
381  break;
382 
383  // Only stop above
384  case DS_StepOut:
385  if (iCallDepth >= iStepCallDepth)
386  return;
387  break;
388  }
389 
390  // Get current script context
391  C4AulScriptContext *pCtx = pExec->GetContext(iCallDepth-1);
392 
393  if (!fConnected)
394  {
395  // not connected anymore? nevermind
396  eState = DS_Go;
397  return;
398  }
399 
400  // Maybe got a command in the meantime?
401  Execute(0);
402 
403  // Let's stop here
404  eState = DS_Stop;
405  iStepCallDepth = iCallDepth;
406  Game.HaltCount++;
407 
408  // No valid stop position? Just continue
409  if (!pCtx)
410  {
411  Game.HaltCount--;
412  eState = DS_Step;
413  return;
414  }
415 
416  // Signal
417  if (pCPos && pCPos->bccType == AB_DEBUG && pCPos->Par.i)
418  SendLine("STP", FormatString("Stopped on breakpoint %d", pCPos->Par.i).getData());
419  else
420  SendLine("STP", "Stepped");
421 
422  // Position
423  ObtainStackTrace(pCtx, pCPos);
424  SendLine("POS", StackTrace.front()->getData());
425 
426  // Suspend until we get some command
427  while (fConnected && eState == DS_Stop)
428  if (!Application.ScheduleProcs())
429  {
430  Close();
431  return;
432  }
433 
434  // Do whatever we've been told.
435  Game.HaltCount--;
436 }
437 
438 void C4AulDebug::ControlScriptEvaluated(const char* script, const char* result)
439 {
440  SendLine("EVR", FormatString("%s=%s", script, result).getData());
441 }
442 
443 const char* C4AulDebug::RelativePath(StdStrBuf &path)
444 {
445  const char* p = path.getData();
446  const char* result = Config.AtRelativePath(p);
447  if (p != result)
448  return result;
449  // try path relative to scenario container
450  StdStrBuf scenarioContainerPath;
451  GetParentPath(::Game.ScenarioFile.GetName(), &scenarioContainerPath);
452  return GetRelativePathS(p, scenarioContainerPath.getData());
453 }
454 
455 void C4AulDebug::ObtainStackTrace(C4AulScriptContext* pCtx, C4AulBCC* pCPos)
456 {
457  for (auto & it : StackTrace)
458  delete it;
459  StackTrace.clear();
460  for (int ctxNum = pExec->GetContextDepth()-1; ctxNum >= 0; ctxNum--)
461  {
462  C4AulScriptContext* c = pExec->GetContext(ctxNum);
463  C4AulBCC* _cpos = c == pCtx ? pCPos : c->CPos;
464  if (_cpos)
465  {
466  StdStrBuf* format = new StdStrBuf(FormatCodePos(c, _cpos));
467  StackTrace.push_back(format);
468  }
469  }
470 }
471 
472 StdStrBuf C4AulDebug::FormatCodePos(C4AulScriptContext *pCtx, C4AulBCC *pCPos)
473 {
474  if (pCtx->Func->pOrgScript)
475  return FormatString("%s:%d",
476  RelativePath(pCtx->Func->pOrgScript->ScriptName),
477  pCtx->Func->GetLineOfCode(pCPos));
478  else
479  return StdStrBuf("(eval)");
480 }
481 
482 C4AulDebug * C4AulDebug::pDebug = nullptr;
483 
484 #endif
C4Value * Pars
Definition: C4AulExec.h:45
bool Execute(int iMaxTime=TO_INF, pollfd *readyfds=nullptr) override
Definition: C4NetIO.cpp:914
const char * getData() const
Definition: StdBuf.h:442
bool Send(const C4NetIOPacket &rPacket) override
Definition: C4NetIO.cpp:1248
const void * getData() const
Definition: StdBuf.h:99
C4Config Config
Definition: C4Config.cpp:833
Definition: StdBuf.h:29
C4Game Game
Definition: C4Globals.cpp:52
C4AulScriptEngine ScriptEngine
Definition: C4Globals.cpp:43
C4ValueMapNames ParNamed
union C4AulBCC::@81 Par
void SetEngine(class C4AulExec *pnExec)
Definition: C4AulDebug.h:73
void SetCallback(CBClass *pnCallback) override
Definition: C4NetIO.h:486
bool SEqualNoCase(const char *szStr1, const char *szStr2, int iLen)
Definition: Standard.cpp:207
StdStrBuf ToString(int flags=0) const
Definition: C4NetIO.cpp:599
C4ScriptHost * pOrgScript
C4MessageInput MessageInput
C4AulExec AulExec
Definition: C4AulExec.cpp:29
void OnLog(const char *szLine)
Definition: C4AulDebug.cpp:197
StdStrBuf GetDataString(int depth=10, const class C4PropListStatic *ignore_reference_parent=nullptr) const
Definition: C4Value.cpp:131
virtual C4PropListStatic * GetPropList()
Definition: C4ScriptHost.h:50
void Grow(size_t iGrow)
Definition: StdBuf.h:171
void SetAddress(const sockaddr *addr)
Definition: C4NetIO.cpp:364
bool Listen(uint16_t iPort, bool fWait)
Definition: C4AulDebug.cpp:54
void Add(StdSchedulerProc *pProc)
bool SetAllowed(const char *szHost)
Definition: C4AulDebug.cpp:152
C4AulScriptFunc * Func
Definition: C4AulExec.h:46
size_t getSize() const
Definition: StdBuf.h:101
void SetPassword(const char *szPassword)
Definition: C4AulDebug.h:71
virtual C4Object * GetObject()
Definition: C4PropList.cpp:667
bool Pause()
Definition: C4Game.cpp:950
bool GetParentPath(const char *szFilename, char *szBuffer)
Definition: StdFile.cpp:189
int GetParCount() const override
void ControlScriptEvaluated(const char *script, const char *result)
Definition: C4AulDebug.cpp:438
C4AulScriptContext * GetContext(int iLevel)
Definition: C4AulExec.h:88
int GetLineOfCode(C4AulBCC *bcc)
C4V_Type GetType() const
Definition: C4Value.h:161
const char * GetName() const
Definition: C4Group.cpp:1845
C4AulBCC * CPos
Definition: C4AulExec.h:47
C4GameControl Control
C4ValueMapNames VarNamed
C4PropList * GetPrototype() const
Definition: C4PropList.h:81
HostAddress GetHost() const
Definition: C4NetIO.h:154
void DoInput(C4PacketType eCtrlType, C4ControlPacket *pPkt, C4ControlDeliveryType eDelivery)
C4Group ScenarioFile
Definition: C4Game.h:86
bool Unpause()
Definition: C4Game.cpp:976
bool ProcessCommand(const char *szCommand)
bool Init(uint16_t iPort) override
Definition: C4AulDebug.cpp:163
bool LogFatal(const char *szMessage)
Definition: C4Log.cpp:227
static bool InitDebug(const char *szPassword, const char *szHost)
Definition: C4AulDebug.cpp:43
const char * GetC4VName(const C4V_Type Type)
Definition: C4Value.cpp:32
~C4AulDebug() override
Definition: C4AulDebug.cpp:36
bool Close() override
Definition: C4NetIO.cpp:852
void CopyUntil(const char *szString, char cUntil)
Definition: StdBuf.h:613
int32_t GetItemNr(const char *strName) const
Definition: C4ValueMap.cpp:459
void Write(const void *pnData, size_t inSize, size_t iAt=0)
Definition: StdBuf.h:153
bool IsPaused()
Definition: C4Game.cpp:1002
bool Init(uint16_t iPort=addr_t::IPPORT_NONE) override
Definition: C4NetIO.cpp:806
void DebugStep(C4AulBCC *pCPos, C4Value *stackTop)
Definition: C4AulDebug.cpp:354
size_t getSize() const
Definition: StdBuf.h:444
static const uint16_t IPPORT_NONE
Definition: C4NetIO.h:136
bool Close() override
Definition: C4AulDebug.cpp:181
bool Log(const char *szMessage)
Definition: C4Log.cpp:192
const char * getPtr(size_t i) const
Definition: StdBuf.h:448
C4PropList * Obj
Definition: C4AulExec.h:43
C4AulBCCType bccType
bool IsNull() const
Definition: C4NetIO.cpp:503
size_t getLength() const
Definition: StdBuf.h:445
int32_t HaltCount
Definition: C4Game.h:112
bool ScheduleProcs(int iTimeout=1000/36)
const char * AtRelativePath(const char *szFilename)
Definition: C4Config.cpp:656
int GetContextDepth() const
Definition: C4AulExec.h:87
virtual C4AulScriptFunc * SFunc()
Definition: C4AulFunc.h:65
bool LogF(const char *strMessage,...)
Definition: C4Log.cpp:250
uint16_t GetPort() const
Definition: C4NetIO.cpp:542
void Copy()
Definition: StdBuf.h:467
StdCopyStrBuf ScriptName
Definition: C4ScriptHost.h:57
C4AulFunc * getFunction() const
Definition: C4Value.h:119
C4ScriptHost * Next
Definition: C4ScriptHost.h:77
C4ScriptHost * Child0
Definition: C4Aul.h:121
bool isConnected() const
Definition: C4AulDebug.h:69
C4Application Application
Definition: C4Globals.cpp:44
const char * GetRelativePathS(const char *strPath, const char *strRelativeTo)
Definition: StdFile.cpp:211
int iSize
Definition: TstC4NetIO.cpp:32
StdStrBuf FormatString(const char *szFmt,...)
Definition: StdBuf.cpp:270