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