Pyrogenesis  13997
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
DebuggingServer.cpp
Go to the documentation of this file.
1 /* Copyright (C) 2013 Wildfire Games.
2  * This file is part of 0 A.D.
3  *
4  * 0 A.D. is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * 0 A.D. is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include "precompiled.h"
19 
20 #include "DebuggingServer.h"
21 #include "ThreadDebugger.h"
22 #include "ps/CLogger.h"
23 #include "ps/Filesystem.h"
25 
27 
28 const char* CDebuggingServer::header400 =
29  "HTTP/1.1 400 Bad Request\r\n"
30  "Content-Type: text/plain; charset=utf-8\r\n\r\n"
31  "Invalid request";
32 
33 void CDebuggingServer::GetAllCallstacks(std::stringstream& response)
34 {
35  CScopeLock lock(m_Mutex);
36  response.str(std::string());
37  std::stringstream stream;
38  uint nbrCallstacksWritten = 0;
39  std::list<CThreadDebugger*>::iterator itr;
40  if (!m_ThreadDebuggers.empty())
41  {
42  response << "[";
43  for (itr = m_ThreadDebuggers.begin(); itr != m_ThreadDebuggers.end(); ++itr)
44  {
45  if ((*itr)->GetIsInBreak())
46  {
47  stream.str(std::string());
48  (*itr)->GetCallstack(stream);
49  if ((int)stream.tellp() != 0)
50  {
51  if (nbrCallstacksWritten != 0)
52  response << ",";
53  response << "{" << "\"ThreadDebuggerID\" : " << (*itr)->GetID() << ", \"CallStack\" : " << stream.str() << "}";
54  nbrCallstacksWritten++;
55  }
56 
57  }
58  }
59  response << "]";
60  }
61 }
62 
63 void CDebuggingServer::GetStackFrameData(std::stringstream& response, uint nestingLevel, uint threadDebuggerID, STACK_INFO stackInfoKind)
64 {
65  CScopeLock lock(m_Mutex);
66  response.str(std::string());
67  std::stringstream stream;
68  std::list<CThreadDebugger*>::iterator itr;
69  for (itr = m_ThreadDebuggers.begin(); itr != m_ThreadDebuggers.end(); ++itr)
70  {
71  if ((*itr)->GetID() == threadDebuggerID && (*itr)->GetIsInBreak())
72  {
73  (*itr)->GetStackFrameData(stream, nestingLevel, stackInfoKind);
74  if ((int)stream.tellp() != 0)
75  {
76  response << stream.str();
77  }
78  }
79  }
80 }
81 
82 
84  m_MgContext(NULL)
85 {
89  m_LastThreadDebuggerID = 0; // Next will be 1, 0 is reserved
91  m_BreakRequestedByUser = false;
94 
95  EnableHTTP();
96  LOGWARNING(L"Javascript debugging webserver enabled.");
97 }
98 
100 {
102  if (m_MgContext)
103  {
105  m_MgContext = NULL;
106  }
107 }
108 
109 bool CDebuggingServer::SetNextDbgCmd(uint threadDebuggerID, DBGCMD dbgCmd)
110 {
111  CScopeLock lock(m_Mutex);
112  std::list<CThreadDebugger*>::iterator itr;
113  for (itr = m_ThreadDebuggers.begin(); itr != m_ThreadDebuggers.end(); ++itr)
114  {
115  if ((*itr)->GetID() == threadDebuggerID || threadDebuggerID == 0)
116  {
117  if (DBG_CMD_NONE == (*itr)->GetNextDbgCmd() && (*itr)->GetIsInBreak())
118  {
121  (*itr)->SetNextDbgCmd(dbgCmd);
122  }
123  }
124  }
125  return true;
126 }
127 
129 {
130  CScopeLock lock(m_Mutex1);
131  m_BreakRequestedByThread = Enabled;
132 }
133 
135 {
136  CScopeLock lock(m_Mutex1);
138 }
139 
141 {
142  CScopeLock lock(m_Mutex1);
143  m_BreakRequestedByUser = Enabled;
144 }
145 
147 {
148  CScopeLock lock(m_Mutex1);
149  return m_BreakRequestedByUser;
150 }
151 
153 {
154  CScopeLock lock(m_Mutex);
155  m_SettingBreakOnException = Enabled;
156 }
157 
159 {
160  CScopeLock lock(m_Mutex1);
162 }
163 
165 {
166  CScopeLock lock(m_Mutex);
168 }
169 
171 {
172  CScopeLock lock(m_Mutex1);
174 }
175 
176 
177 static Status AddFileResponse(const VfsPath& pathname, const CFileInfo& UNUSED(fileInfo), const uintptr_t cbData)
178 {
179  std::vector<std::string>& templates = *(std::vector<std::string>*)cbData;
180  std::wstring str(pathname.string());
181  templates.push_back(std::string(str.begin(), str.end()));
182  return INFO::OK;
183 }
184 
185 void CDebuggingServer::EnumVfsJSFiles(std::stringstream& response)
186 {
187  VfsPath path = L"";
188  VfsPaths pathnames;
189  response.str(std::string());
190 
191  std::vector<std::string> templates;
192  vfs::ForEachFile(g_VFS, "", AddFileResponse, (uintptr_t)&templates, L"*.js", vfs::DIR_RECURSIVE);
193 
194  std::vector<std::string>::iterator itr;
195  response << "[";
196  for (itr = templates.begin(); itr != templates.end(); ++itr)
197  {
198  if (itr != templates.begin())
199  response << ",";
200  response << "\"" << *itr << "\"";
201  }
202  response << "]";
203 }
204 
205 void CDebuggingServer::GetFile(std::string filename, std::stringstream& response)
206 {
207  CVFSFile file;
208  if (file.Load(g_VFS, filename) != PSRETURN_OK)
209  {
210  response << "Failed to load the file contents";
211  return;
212  }
213 
214  std::string code = file.DecodeUTF8(); // assume it's UTF-8
215  response << code;
216 }
217 
218 
219 static void* MgDebuggingServerCallback_(mg_event event, struct mg_connection *conn, const struct mg_request_info *request_info)
220 {
221  CDebuggingServer* debuggingServer = (CDebuggingServer*)request_info->user_data;
222  ENSURE(debuggingServer);
223  return debuggingServer->MgDebuggingServerCallback(event, conn, request_info);
224 }
225 
226 void* CDebuggingServer::MgDebuggingServerCallback(mg_event event, struct mg_connection *conn, const struct mg_request_info *request_info)
227 {
228  void* handled = (void*)""; // arbitrary non-NULL pointer to indicate successful handling
229 
230  const char* header200 =
231  "HTTP/1.1 200 OK\r\n"
232  "Access-Control-Allow-Origin: *\r\n" // TODO: not great for security
233  "Content-Type: text/plain; charset=utf-8\r\n\r\n";
234 
235  const char* header404 =
236  "HTTP/1.1 404 Not Found\r\n"
237  "Content-Type: text/plain; charset=utf-8\r\n\r\n"
238  "Unrecognised URI";
239 
240  switch (event)
241  {
242  case MG_NEW_REQUEST:
243  {
244  std::stringstream stream;
245  std::string uri = request_info->uri;
246 
247  if (uri == "/GetThreadDebuggerStatus")
248  {
249  GetThreadDebuggerStatus(stream);
250  }
251  else if (uri == "/EnumVfsJSFiles")
252  {
253  EnumVfsJSFiles(stream);
254  }
255  else if (uri == "/GetAllCallstacks")
256  {
257  GetAllCallstacks(stream);
258  }
259  else if (uri == "/Continue")
260  {
261  uint threadDebuggerID;
262  if (!GetWebArgs(conn, request_info, "threadDebuggerID", threadDebuggerID))
263  return handled;
264  // TODO: handle the return value
265  SetNextDbgCmd(threadDebuggerID, DBG_CMD_CONTINUE);
266  }
267  else if (uri == "/Break")
268  {
270  }
271  else if (uri == "/SetSettingSimultaneousThreadBreak")
272  {
273  std::string strEnabled;
274  bool bEnabled = false;
275  if (!GetWebArgs(conn, request_info, "enabled", strEnabled))
276  return handled;
277  // TODO: handle the return value
278  if (strEnabled == "true")
279  bEnabled = true;
280  else if (strEnabled == "false")
281  bEnabled = false;
282  else
283  return handled; // TODO: return an error state
285  }
286  else if (uri == "/GetSettingSimultaneousThreadBreak")
287  {
288  stream << "{ \"Enabled\" : " << (GetSettingSimultaneousThreadBreak() ? "true" : "false") << " } ";
289  }
290  else if (uri == "/SetSettingBreakOnException")
291  {
292  std::string strEnabled;
293  bool bEnabled = false;
294  if (!GetWebArgs(conn, request_info, "enabled", strEnabled))
295  return handled;
296  // TODO: handle the return value
297  if (strEnabled == "true")
298  bEnabled = true;
299  else if (strEnabled == "false")
300  bEnabled = false;
301  else
302  return handled; // TODO: return an error state
303  SetSettingBreakOnException(bEnabled);
304  }
305  else if (uri == "/GetSettingBreakOnException")
306  {
307  stream << "{ \"Enabled\" : " << (GetSettingBreakOnException() ? "true" : "false") << " } ";
308  }
309  else if (uri == "/Step")
310  {
311  uint threadDebuggerID;
312  if (!GetWebArgs(conn, request_info, "threadDebuggerID", threadDebuggerID))
313  return handled;
314  // TODO: handle the return value
315  SetNextDbgCmd(threadDebuggerID, DBG_CMD_SINGLESTEP);
316  }
317  else if (uri == "/StepInto")
318  {
319  uint threadDebuggerID;
320  if (!GetWebArgs(conn, request_info, "threadDebuggerID", threadDebuggerID))
321  return handled;
322  // TODO: handle the return value
323  SetNextDbgCmd(threadDebuggerID, DBG_CMD_STEPINTO);
324  }
325  else if (uri == "/StepOut")
326  {
327  uint threadDebuggerID;
328  if (!GetWebArgs(conn, request_info, "threadDebuggerID", threadDebuggerID))
329  return handled;
330  // TODO: handle the return value
331  SetNextDbgCmd(threadDebuggerID, DBG_CMD_STEPOUT);
332  }
333  else if (uri == "/GetStackFrame")
334  {
335  uint nestingLevel;
336  uint threadDebuggerID;
337  if (!GetWebArgs(conn, request_info, "nestingLevel", nestingLevel) ||
338  !GetWebArgs(conn, request_info, "threadDebuggerID", threadDebuggerID))
339  {
340  return handled;
341  }
342  GetStackFrameData(stream, nestingLevel, threadDebuggerID, STACK_INFO_LOCALS);
343  }
344  else if (uri == "/GetStackFrameThis")
345  {
346  uint nestingLevel;
347  uint threadDebuggerID;
348  if (!GetWebArgs(conn, request_info, "nestingLevel", nestingLevel) ||
349  !GetWebArgs(conn, request_info, "threadDebuggerID", threadDebuggerID))
350  {
351  return handled;
352  }
353  GetStackFrameData(stream, nestingLevel, threadDebuggerID, STACK_INFO_THIS);
354  }
355  else if (uri == "/GetCurrentGlobalObject")
356  {
357  uint threadDebuggerID;
358  if (!GetWebArgs(conn, request_info, "threadDebuggerID", threadDebuggerID))
359  {
360  return handled;
361  }
362  GetStackFrameData(stream, 0, threadDebuggerID, STACK_INFO_GLOBALOBJECT);
363  }
364  else if (uri == "/ToggleBreakpoint")
365  {
366  std::string filename;
367  uint line;
368  if (!GetWebArgs(conn, request_info, "filename", filename) ||
369  !GetWebArgs(conn, request_info, "line", line))
370  {
371  return handled;
372  }
373  ToggleBreakPoint(filename, line);
374  }
375  else if (uri == "/GetFile")
376  {
377  std::string filename;
378  if (!GetWebArgs(conn, request_info, "filename", filename))
379  return handled;
380  GetFile(filename, stream);
381  }
382  else
383  {
384  mg_printf(conn, "%s", header404);
385  return handled;
386  }
387 
388  mg_printf(conn, "%s", header200);
389  std::string str = stream.str();
390  mg_write(conn, str.c_str(), str.length());
391  return handled;
392  }
393 
394  case MG_HTTP_ERROR:
395  return NULL;
396 
397  case MG_EVENT_LOG:
398  // Called by Mongoose's cry()
399  LOGERROR(L"Mongoose error: %hs", request_info->log_message);
400  return NULL;
401 
402  case MG_INIT_SSL:
403  return NULL;
404 
405  default:
406  debug_warn(L"Invalid Mongoose event type");
407  return NULL;
408  }
409 };
410 
412 {
413  // Ignore multiple enablings
414  if (m_MgContext)
415  return;
416 
417  const char *options[] = {
418  "listening_ports", "127.0.0.1:9000", // bind to localhost for security
419  "num_threads", "6", // enough for the browser's parallel connection limit
420  NULL
421  };
424 }
425 
426 bool CDebuggingServer::GetWebArgs(struct mg_connection *conn, const struct mg_request_info* request_info, std::string argName, uint& arg)
427 {
428  if (!request_info->query_string)
429  {
430  mg_printf(conn, "%s (no query string)", header400);
431  return false;
432  }
433 
434  char buf[256];
435 
436  int len = mg_get_var(request_info->query_string, strlen(request_info->query_string), argName.c_str(), buf, ARRAY_SIZE(buf));
437  if (len < 0)
438  {
439  mg_printf(conn, "%s (no '%s')", header400, argName.c_str());
440  return false;
441  }
442  arg = atoi(buf);
443  return true;
444 }
445 
446 bool CDebuggingServer::GetWebArgs(struct mg_connection *conn, const struct mg_request_info* request_info, std::string argName, std::string& arg)
447 {
448  if (!request_info->query_string)
449  {
450  mg_printf(conn, "%s (no query string)", header400);
451  return false;
452  }
453 
454  char buf[256];
455  int len = mg_get_var(request_info->query_string, strlen(request_info->query_string), argName.c_str(), buf, ARRAY_SIZE(buf));
456  if (len < 0)
457  {
458  mg_printf(conn, "%s (no '%s')", header400, argName.c_str());
459  return false;
460  }
461  arg = buf;
462  return true;
463 }
464 
465 void CDebuggingServer::RegisterScriptinterface(std::string name, ScriptInterface* pScriptInterface)
466 {
467  CScopeLock lock(m_Mutex);
468  CThreadDebugger* pThreadDebugger = new CThreadDebugger;
469  // ThreadID 0 is reserved
470  pThreadDebugger->Initialize(++m_LastThreadDebuggerID, name, pScriptInterface, this);
471  m_ThreadDebuggers.push_back(pThreadDebugger);
472 }
473 
475 {
476  CScopeLock lock(m_Mutex);
477  std::list<CThreadDebugger*>::iterator itr;
478  for (itr = m_ThreadDebuggers.begin(); itr != m_ThreadDebuggers.end(); ++itr)
479  {
480  if ((*itr)->CompareScriptInterfacePtr(pScriptInterface))
481  {
482  delete (*itr);
483  m_ThreadDebuggers.erase(itr);
484  break;
485  }
486  }
487 }
488 
489 
490 void CDebuggingServer::GetThreadDebuggerStatus(std::stringstream& response)
491 {
492  CScopeLock lock(m_Mutex);
493  response.str(std::string());
494  std::list<CThreadDebugger*>::iterator itr;
495 
496  response << "[";
497  for (itr = m_ThreadDebuggers.begin(); itr != m_ThreadDebuggers.end(); ++itr)
498  {
499  if (itr == m_ThreadDebuggers.begin())
500  response << "{ ";
501  else
502  response << ",{ ";
503 
504  response << "\"ThreadDebuggerID\" : " << (*itr)->GetID() << ",";
505  response << "\"ScriptInterfaceName\" : \"" << (*itr)->GetName() << "\",";
506  response << "\"ThreadInBreak\" : " << ((*itr)->GetIsInBreak() ? "true" : "false") << ",";
507  response << "\"BreakFileName\" : \"" << (*itr)->GetBreakFileName() << "\",";
508  response << "\"BreakLine\" : " << (*itr)->GetLastBreakLine();
509  response << " }";
510  }
511  response << "]";
512 }
513 
514 void CDebuggingServer::ToggleBreakPoint(std::string filename, uint line)
515 {
516  // First, pass the message to all associated CThreadDebugger objects and check if one returns true (handled);
517  {
518  CScopeLock lock(m_Mutex);
519  std::list<CThreadDebugger*>::iterator itr;
520  for (itr = m_ThreadDebuggers.begin(); itr != m_ThreadDebuggers.end(); ++itr)
521  {
522  if ((*itr)->ToggleBreakPoint(filename, line))
523  return;
524  }
525  }
526 
527  // If the breakpoint isn't handled yet search the breakpoints registered in this class
528  std::list<CBreakPoint>* pBreakPoints = NULL;
529  double breakPointsLockID = AquireBreakPointAccess(&pBreakPoints);
530 
531  // If set, delete
532  bool deleted = false;
533  for (std::list<CBreakPoint>::iterator itr = pBreakPoints->begin(); itr != pBreakPoints->end(); ++itr)
534  {
535  if ((*itr).m_Filename == filename && (*itr).m_UserLine == line)
536  {
537  itr = pBreakPoints->erase(itr);
538  deleted = true;
539  break;
540  }
541  }
542 
543  // If not set, set
544  if (!deleted)
545  {
546  CBreakPoint bP;
547  bP.m_Filename = filename;
548  bP.m_UserLine = line;
549  pBreakPoints->push_back(bP);
550  }
551 
552  ReleaseBreakPointAccess(breakPointsLockID);
553  return;
554 }
555 
556 double CDebuggingServer::AquireBreakPointAccess(std::list<CBreakPoint>** breakPoints)
557 {
558  int ret;
560  ENSURE(ret == 0);
561  (*breakPoints) = &m_BreakPoints;
563  return m_BreakPointsLockID;
564 }
565 
566 void CDebuggingServer::ReleaseBreakPointAccess(double breakPointsLockID)
567 {
568  ENSURE(m_BreakPointsLockID == breakPointsLockID);
570 }
CDebuggingServer * g_DebuggingServer
CStr DecodeUTF8() const
Returns contents of a UTF-8 encoded file as a string with optional BOM removed.
Definition: Filesystem.cpp:153
static const char * header400
#define UNUSED(param)
mark a function parameter as unused and avoid the corresponding compiler warning. ...
std::list< CBreakPoint > m_BreakPoints
STACK_INFO
const Status OK
Definition: status.h:386
#define LOGERROR
Definition: CLogger.h:35
bool SetNextDbgCmd(uint threadDebuggerID, DBGCMD dbgCmd)
bool GetSettingBreakOnException()
const PSRETURN PSRETURN_OK
Definition: Errors.h:103
Reads a file, then gives read-only access to the contents.
Definition: Filesystem.h:69
void RegisterScriptinterface(std::string name, ScriptInterface *pScriptInterface)
Register a new ScriptInerface for debugging the scripts it executes.
char * uri
Definition: mongoose.h:38
Locks a CMutex over this object&#39;s lifetime.
Definition: ThreadUtil.h:73
bool GetWebArgs(struct mg_connection *conn, const struct mg_request_info *request_info, std::string argName, uint &arg)
Webserver helper function (can be called by multiple mongooser threads)
char * log_message
Definition: mongoose.h:42
CMutex m_Mutex
Mutexes used to ensure thread-safety.
void * MgDebuggingServerCallback(mg_event event, struct mg_connection *conn, const struct mg_request_info *request_info)
std::list< CThreadDebugger * > m_ThreadDebuggers
Shared between multiple scriptinerface threads and multiple mongoose threads.
void GetThreadDebuggerStatus(std::stringstream &response)
Functions that are made available via http (can be called by multiple mongoose threads) ...
SDL_sem * m_BreakPointsSem
Used for controlling access to m_BreakPoints.
uint m_LastThreadDebuggerID
Shared between multiple scriptinterface threads.
std::string m_Filename
#define ARRAY_SIZE(name)
void Initialize(uint id, std::string name, ScriptInterface *pScriptInterface, CDebuggingServer *pDebuggingServer)
Initialize the object (required before using the object!).
void ReleaseBreakPointAccess(double breakPointsLockID)
See AquireBreakPointAccess().
#define LOGWARNING
Definition: CLogger.h:34
void SDL_DestroySemaphore(SDL_sem *sem)
Definition: wsdl.cpp:1420
#define ENSURE(expr)
ensure the expression &lt;expr&gt; evaluates to non-zero.
Definition: debug.h:282
static Status AddFileResponse(const VfsPath &pathname, const CFileInfo &fileInfo, const uintptr_t cbData)
void EnableHTTP()
Not important for this class&#39; thread-safety.
void GetStackFrameData(std::stringstream &response, uint nestingLevel, uint threadDebuggerID, STACK_INFO stackInfoKind)
void SetBreakRequestedByThread(bool Enabled)
Definition: path.h:75
const String & string() const
Definition: path.h:123
int mg_get_var(const char *buf, size_t buf_len, const char *name, char *dst, size_t dst_len)
Definition: mongoose.cpp:1497
mg_event
Definition: mongoose.h:55
void EnumVfsJSFiles(std::stringstream &response)
Returns a list of the full vfs paths to all files with the extension .js found in the vfs root...
mg_context * m_MgContext
i64 Status
Error handling system.
Definition: status.h:171
bool m_SettingSimultaneousThreadBreak
Shared between multiple mongoose threads.
char * query_string
Definition: mongoose.h:40
static void * MgDebuggingServerCallback_(mg_event event, struct mg_connection *conn, const struct mg_request_info *request_info)
double timer_Time()
Definition: timer.cpp:98
SDL_sem * SDL_CreateSemaphore(int cnt)
Definition: wsdl.cpp:1414
int mg_printf(struct mg_connection *conn, const char *fmt,...)
Definition: mongoose.cpp:1450
double AquireBreakPointAccess(std::list< CBreakPoint > **breakPoints)
Aquire exclusive read and write access to the list of breakpoints.
int mg_write(struct mg_connection *conn, const void *buf, size_t len)
Definition: mongoose.cpp:1445
Status ForEachFile(const PIVFS &fs, const VfsPath &startPath, FileCallback cb, uintptr_t cbData, const wchar_t *pattern, size_t flags)
call back for each file in a directory tree
Definition: vfs_util.cpp:58
PSRETURN Load(const PIVFS &vfs, const VfsPath &filename)
Returns either PSRETURN_OK or PSRETURN_CVFSFile_LoadFailed.
Definition: Filesystem.cpp:117
bool GetSettingSimultaneousThreadBreak()
DBGCMD
void * user_data
Definition: mongoose.h:36
std::vector< VfsPath > VfsPaths
Definition: vfs_path.h:42
Abstraction around a SpiderMonkey JSContext.
#define debug_warn(expr)
display the error dialog with the given text.
Definition: debug.h:324
void ToggleBreakPoint(std::string filename, uint line)
struct mg_context * mg_start(mg_callback_t user_callback, void *user_data, const char **options)
Definition: mongoose.cpp:4219
int SDL_SemPost(SDL_sem *sem)
Definition: wsdl.cpp:1426
PIVFS g_VFS
Definition: Filesystem.cpp:30
void GetAllCallstacks(std::stringstream &response)
void SetSettingBreakOnException(bool Enabled)
int SDL_SemWait(SDL_sem *sem)
Definition: wsdl.cpp:1432
void SetBreakRequestedByUser(bool Enabled)
void mg_stop(struct mg_context *ctx)
Definition: mongoose.cpp:4205
void UnRegisterScriptinterface(ScriptInterface *pScriptInterface)
Unregister a ScriptInerface that was previously registered using RegisterScriptinterface.
void GetFile(std::string filename, std::stringstream &response)
Get the content of a .js file loaded into vfs.
bool GetBreakRequestedByThread()
Called from multiple Mongoose threads and multiple ScriptInterface threads.
void SetSettingSimultaneousThreadBreak(bool Enabled)