Pyrogenesis  13997
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
ThreadDebugger.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 "ThreadDebugger.h"
21 #include "lib/utf8.h"
22 #include "ps/CLogger.h"
23 
24 #include <map>
25 #include <queue>
26 
27 // Hooks
28 
30 static JSTrapStatus ThrowHandler_(JSContext* cx, JSScript* script, jsbytecode* pc, jsval* rval, void* closure)
31 {
32  CScopeLock lock(ThrowHandlerMutex);
33  CThreadDebugger* pThreadDebugger = (CThreadDebugger*) closure;
34  return pThreadDebugger->ThrowHandler(cx, script, pc, rval);
35 }
36 
38 static JSTrapStatus TrapHandler_(JSContext* cx, JSScript* script, jsbytecode* pc, jsval* rval, jsval closure)
39 {
40  CScopeLock lock(TrapHandlerMutex);
41  CThreadDebugger* pThreadDebugger = (CThreadDebugger*) JSVAL_TO_PRIVATE(closure);
42  jsval val = JSVAL_NULL;
43  return pThreadDebugger->TrapHandler(cx, script, pc, rval, val);
44 }
45 
47 JSTrapStatus StepHandler_(JSContext* cx, JSScript* script, jsbytecode* pc, jsval* rval, void* closure)
48 {
49  CScopeLock lock(StepHandlerMutex);
50  CThreadDebugger* pThreadDebugger = (CThreadDebugger*) closure;
51  jsval val = JSVAL_VOID;
52  return pThreadDebugger->StepHandler(cx, script, pc, rval, &val);
53 }
54 
56 JSTrapStatus StepIntoHandler_(JSContext* cx, JSScript* script, jsbytecode* pc, jsval* rval, void* closure)
57 {
58  CScopeLock lock(StepIntoHandlerMutex);
59  CThreadDebugger* pThreadDebugger = (CThreadDebugger*) closure;
60  return pThreadDebugger->StepIntoHandler(cx, script, pc, rval, NULL);
61 }
62 
64 void NewScriptHook_(JSContext* cx, const char* filename, unsigned lineno, JSScript* script, JSFunction* fun, void* callerdata)
65 {
66  CScopeLock lock(NewScriptHookMutex);
67  CThreadDebugger* pThreadDebugger = (CThreadDebugger*) callerdata;
68  return pThreadDebugger->NewScriptHook(cx, filename, lineno, script, fun, NULL);
69 }
70 
72 void DestroyScriptHook_(JSContext* cx, JSScript* script, void* callerdata)
73 {
74  CScopeLock lock(DestroyScriptHookMutex);
75  CThreadDebugger* pThreadDebugger = (CThreadDebugger*) callerdata;
76  return pThreadDebugger->DestroyScriptHook(cx, script);
77 }
78 
80 JSTrapStatus StepOutHandler_(JSContext* cx, JSScript* script, jsbytecode* pc, jsval* rval, void* closure)
81 {
82  CScopeLock lock(StepOutHandlerMutex);
83  CThreadDebugger* pThreadDebugger = (CThreadDebugger*) closure;
84  return pThreadDebugger->StepOutHandler(cx, script, pc, rval, NULL);
85 }
86 
88 JSTrapStatus CheckForBreakRequestHandler_(JSContext* cx, JSScript* script, jsbytecode* pc, jsval* rval, void* closure)
89 {
90  CScopeLock lock(CheckForBreakRequestHandlerMutex);
91  CThreadDebugger* pThreadDebugger = (CThreadDebugger*) closure;
92  return pThreadDebugger->CheckForBreakRequestHandler(cx, script, pc, rval, NULL);
93 }
94 
96 static void* CallHook_(JSContext* cx, JSStackFrame* fp, JSBool before, JSBool* UNUSED(ok), void* closure)
97 {
98  CScopeLock lock(CallHookMutex);
99  CThreadDebugger* pThreadDebugger = (CThreadDebugger*) closure;
100  if (before)
101  {
102  JSScript* script;
103  script = JS_GetFrameScript(cx, fp);
104  const char* fileName = JS_GetScriptFilename(cx, script);
105  uint lineno = JS_GetScriptBaseLineNumber(cx, script);
106  JSFunction* fun = JS_GetFrameFunction(cx, fp);
107  pThreadDebugger->ExecuteHook(cx, fileName, lineno, script, fun, closure);
108  }
109 
110  return closure;
111 }
112 
113 /// ThreadDebugger_impl
114 
116 {
120 };
121 
123 {
124  jsbytecode* pBytecode;
125  JSScript* pScript;
128 };
129 
131 {
133 public:
134 
137 
142 
143  // This member could actually be used by other threads via CompareScriptInterfacePtr(), but that should be safe
146  // We store the pointer on the heap because the stack frame becomes invalid in certain cases
147  // and spidermonkey throws errors if it detects a pointer on the stack.
148  // We only use the pointer for comparing it with the current stack pointer and we don't try to access it, so it
149  // shouldn't be a problem.
150  JSStackFrame** m_pLastBreakFrame;
152 
153  /// shared between multiple mongoose threads and one scriptinterface thread
154  std::string m_BreakFileName;
158 
159  std::queue<StackInfoRequest> m_StackInfoRequests;
160 
161  std::map<std::string, std::map<uint, trapLocation> > m_LineToPCMap;
162  std::list<CActiveBreakPoint*> m_ActiveBreakPoints;
163  std::map<STACK_INFO, std::map<uint, std::string> > m_StackFrameData;
164  std::string m_Callstack;
165 
166  /// shared between multiple mongoose threads (initialization may be an exception)
167  std::string m_Name;
168  uint m_ID;
169 };
170 
172  : m_NextDbgCmd(DBG_CMD_NONE)
173  , m_pScriptInterface(NULL)
174  , m_pDebuggingServer(NULL)
175  , m_pLastBreakFrame(new JSStackFrame*)
176  , m_IsInBreak(false)
177 { }
178 
180 {
181  delete m_pLastBreakFrame;
182 }
183 
184 /// CThreadDebugger
185 
187 {
188  CScopeLock lock(m->m_Mutex);
189  std::list<CActiveBreakPoint*>::iterator itr=m->m_ActiveBreakPoints.begin();
190  while (itr != m->m_ActiveBreakPoints.end())
191  {
192  if ((*itr)->m_ToRemove)
193  {
194  ClearTrap((*itr));
195  // Remove the breakpoint
196  delete (*itr);
197  itr = m->m_ActiveBreakPoints.erase(itr);
198  }
199  else
200  ++itr;
201  }
202 }
203 
205 {
206  ENSURE(activeBreakPoint->m_Script != NULL && activeBreakPoint->m_Pc != NULL);
207  JSTrapHandler prevHandler;
208  jsval prevClosure;
209  JS_ClearTrap(m->m_pScriptInterface->GetContext(), activeBreakPoint->m_Script, activeBreakPoint->m_Pc, &prevHandler, &prevClosure);
210  activeBreakPoint->m_Script = NULL;
211  activeBreakPoint->m_Pc = NULL;
212 }
213 
215 {
216  std::list<CBreakPoint>* pBreakPoints = NULL;
217  double breakPointsLockID;
218  breakPointsLockID = m->m_pDebuggingServer->AquireBreakPointAccess(&pBreakPoints);
219  std::list<CBreakPoint>::iterator itr = pBreakPoints->begin();
220  while (itr != pBreakPoints->end())
221  {
222  if (CheckIfMappingPresent((*itr).m_Filename, (*itr).m_UserLine))
223  {
224  // We must not set a new trap if we already have set a trap for this line of code.
225  // For lines without source code it's possible to have breakpoints set that actually refer to another line
226  // that contains code. This situation is possible if the line containing the sourcecode already has a breakpoint
227  // set and the user sets another one by setting a breakpoint on a line directly above without sourcecode.
228  bool trapAlreadySet = false;
229  {
230  CScopeLock lock(m->m_Mutex);
231  std::list<CActiveBreakPoint*>::iterator itr1;
232  for (itr1 = m->m_ActiveBreakPoints.begin(); itr1 != m->m_ActiveBreakPoints.end(); ++itr1)
233  {
234  if ((*itr1)->m_ActualLine == (*itr).m_UserLine)
235  trapAlreadySet = true;
236  }
237  }
238 
239  if (!trapAlreadySet)
240  {
241  CActiveBreakPoint* pActiveBreakPoint = new CActiveBreakPoint((*itr));
242  SetNewTrap(pActiveBreakPoint, (*itr).m_Filename, (*itr).m_UserLine);
243  {
244  CScopeLock lock(m->m_Mutex);
245  m->m_ActiveBreakPoints.push_back(pActiveBreakPoint);
246  }
247  itr = pBreakPoints->erase(itr);
248  continue;
249  }
250  }
251  ++itr;
252  }
253  m->m_pDebuggingServer->ReleaseBreakPointAccess(breakPointsLockID);
254 }
255 
256 bool CThreadDebugger::CheckIfMappingPresent(std::string filename, uint line)
257 {
258  bool isPresent = (m->m_LineToPCMap.end() != m->m_LineToPCMap.find(filename) && m->m_LineToPCMap[filename].end() != m->m_LineToPCMap[filename].find(line));
259  return isPresent;
260 }
261 
262 void CThreadDebugger::SetNewTrap(CActiveBreakPoint* activeBreakPoint, std::string filename, uint line)
263 {
264  ENSURE(activeBreakPoint->m_Script == NULL); // The trap must not be set already!
265  ENSURE(CheckIfMappingPresent(filename, line)); // You have to check if the mapping exists before calling this function!
266 
267  jsbytecode* pc = m->m_LineToPCMap[filename][line].pBytecode;
268  JSScript* script = m->m_LineToPCMap[filename][line].pScript;
269  activeBreakPoint->m_Script = script;
270  activeBreakPoint->m_Pc = pc;
271  ENSURE(script != NULL && pc != NULL);
272  activeBreakPoint->m_ActualLine = JS_PCToLineNumber(m->m_pScriptInterface->GetContext(), script, pc);
273 
274  JS_SetTrap(m->m_pScriptInterface->GetContext(), script, pc, TrapHandler_, PRIVATE_TO_JSVAL(this));
275 }
276 
277 
279  m(new ThreadDebugger_impl())
280 {
281 }
282 
284 {
285  // Clear all Traps and Breakpoints that are marked for removal
287 
288  // Return all breakpoints to the associated CDebuggingServer
290 
291  // Remove all the hooks because they store a pointer to this object
292  JS_SetExecuteHook(m->m_pScriptInterface->GetRuntime(), NULL, NULL);
293  JS_SetCallHook(m->m_pScriptInterface->GetRuntime(), NULL, NULL);
294  JS_SetNewScriptHook(m->m_pScriptInterface->GetRuntime(), NULL, NULL);
295  JS_SetDestroyScriptHook(m->m_pScriptInterface->GetRuntime(), NULL, NULL);
296 }
297 
298 void CThreadDebugger::ReturnActiveBreakPoints(jsbytecode* pBytecode)
299 {
300  CScopeLock lock(m->m_ActiveBreakpointsMutex);
301  std::list<CActiveBreakPoint*>::iterator itr;
302  itr = m->m_ActiveBreakPoints.begin();
303  while (itr != m->m_ActiveBreakPoints.end())
304  {
305  // Breakpoints marked for removal should be deleted instead of returned!
306  if ( ((*itr)->m_Pc == pBytecode || pBytecode == NULL) && !(*itr)->m_ToRemove )
307  {
308  std::list<CBreakPoint>* pBreakPoints;
309  double breakPointsLockID = m->m_pDebuggingServer->AquireBreakPointAccess(&pBreakPoints);
310  CBreakPoint breakPoint;
311  breakPoint.m_UserLine = (*itr)->m_UserLine;
312  breakPoint.m_Filename = (*itr)->m_Filename;
313  // All active breakpoints should have a trap set
314  ClearTrap((*itr));
315  pBreakPoints->push_back(breakPoint);
316  delete (*itr);
317  itr = m->m_ActiveBreakPoints.erase(itr);
318  m->m_pDebuggingServer->ReleaseBreakPointAccess(breakPointsLockID);
319  }
320  else
321  ++itr;
322  }
323 }
324 
325 void CThreadDebugger::Initialize(uint id, std::string name, ScriptInterface* pScriptInterface, CDebuggingServer* pDebuggingServer)
326 {
327  ENSURE(id != 0);
328  m->m_ID = id;
329  m->m_Name = name;
330  m->m_pScriptInterface = pScriptInterface;
331  m->m_pDebuggingServer = pDebuggingServer;
332  JS_SetExecuteHook(m->m_pScriptInterface->GetRuntime(), CallHook_, (void*)this);
333  JS_SetCallHook(m->m_pScriptInterface->GetRuntime(), CallHook_, (void*)this);
334  JS_SetNewScriptHook(m->m_pScriptInterface->GetRuntime(), NewScriptHook_, (void*)this);
335  JS_SetDestroyScriptHook(m->m_pScriptInterface->GetRuntime(), DestroyScriptHook_, (void*)this);
336  JS_SetThrowHook(m->m_pScriptInterface->GetRuntime(), ThrowHandler_, (void*)this);
337 
338  if (m->m_pDebuggingServer->GetSettingSimultaneousThreadBreak())
339  {
340  // Setup a handler to check for break-requests from the DebuggingServer regularly
341  JS_SetInterrupt(m->m_pScriptInterface->GetRuntime(), CheckForBreakRequestHandler_, (void*)this);
342  }
343 }
344 
345 JSTrapStatus CThreadDebugger::StepHandler(JSContext* cx, JSScript* script, jsbytecode* pc, jsval* rval, void* UNUSED(closure))
346 {
347  // We break in two conditions
348  // 1. We are in the same frame but on a different line
349  // Note: On loops for example, we can go a few lines up again without leaving the current stack frame, so it's not necessarily
350  // a higher line number.
351  // 2. We are in a different Frame and m_pLastBreakFrame is not a parent of the current frame (because we stepped out of the function)
352  uint line = JS_PCToLineNumber(cx, script, pc);
353  JSStackFrame* iter = NULL;
354  JSStackFrame* pStackFrame;
355  pStackFrame = JS_FrameIterator(m->m_pScriptInterface->GetContext(), &iter);
356  uint lastBreakLine = GetLastBreakLine() ;
357  jsval val = JSVAL_VOID;
358  if ((*m->m_pLastBreakFrame == pStackFrame && lastBreakLine != line) ||
359  (*m->m_pLastBreakFrame != pStackFrame && !CurrentFrameIsChildOf(*m->m_pLastBreakFrame)))
360  return BreakHandler(cx, script, pc, rval, val, BREAK_SRC_INTERRUP);
361  else
362  return JSTRAP_CONTINUE;
363 }
364 
365 JSTrapStatus CThreadDebugger::StepIntoHandler(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, void* UNUSED(closure))
366 {
367  // We break when we are on the same stack frame but not on the same line
368  // or when we are on another stack frame.
369  uint line = JS_PCToLineNumber(cx, script, pc);
370  JSStackFrame* iter = NULL;
371  JSStackFrame* pStackFrame;
372  pStackFrame = JS_FrameIterator(m->m_pScriptInterface->GetContext(), &iter);
373  uint lastBreakLine = GetLastBreakLine();
374 
375  jsval val = JSVAL_VOID;
376  if ((*m->m_pLastBreakFrame == pStackFrame && lastBreakLine != line) || *m->m_pLastBreakFrame != pStackFrame)
377  return BreakHandler(cx, script, pc, rval, val, BREAK_SRC_INTERRUP);
378  else
379  return JSTRAP_CONTINUE;
380 }
381 
382 JSTrapStatus CThreadDebugger::StepOutHandler(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, void* UNUSED(closure))
383 {
384  // We break when we are in a different Frame and m_pLastBreakFrame is not a parent of the current frame
385  // (because we stepped out of the function)
386  JSStackFrame* iter = NULL;
387  JSStackFrame* pStackFrame;
388  pStackFrame = JS_FrameIterator(m->m_pScriptInterface->GetContext(), &iter);
389  if (pStackFrame != *m->m_pLastBreakFrame && !CurrentFrameIsChildOf(*m->m_pLastBreakFrame))
390  {
391  jsval val = JSVAL_VOID;
392  return BreakHandler(cx, script, pc, rval, val, BREAK_SRC_INTERRUP);
393  }
394  else
395  return JSTRAP_CONTINUE;
396 }
397 
398 bool CThreadDebugger::CurrentFrameIsChildOf(JSStackFrame* pParentFrame)
399 {
400  JSStackFrame* iter = NULL;
401  JSStackFrame* fp = JS_FrameIterator(m->m_pScriptInterface->GetContext(), &iter);
402  // Get the first parent Frame
403  fp = JS_FrameIterator(m->m_pScriptInterface->GetContext(), &iter);
404  while (fp)
405  {
406  if (fp == pParentFrame)
407  return true;
408  fp = JS_FrameIterator(m->m_pScriptInterface->GetContext(), &iter);
409  }
410  return false;
411 }
412 
413 JSTrapStatus CThreadDebugger::CheckForBreakRequestHandler(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, void* UNUSED(closure))
414 {
415  jsval val = JSVAL_VOID;
416  if (m->m_pDebuggingServer->GetBreakRequestedByThread() || m->m_pDebuggingServer->GetBreakRequestedByUser())
417  return BreakHandler(cx, script, pc, rval, val, BREAK_SRC_INTERRUP);
418  else
419  return JSTRAP_CONTINUE;
420 }
421 
422 JSTrapStatus CThreadDebugger::TrapHandler(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, jsval UNUSED(closure))
423 {
424  jsval val = JSVAL_NULL;
425  return BreakHandler(cx, script, pc, rval, val, BREAK_SRC_TRAP);
426 }
427 
428 JSTrapStatus CThreadDebugger::ThrowHandler(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval)
429 {
430  jsval jsexception;
431  JS_GetPendingException(cx, &jsexception);
432  if (JSVAL_IS_STRING(jsexception))
433  {
434  std::string str(JS_EncodeString(cx, JSVAL_TO_STRING(jsexception)));
435  if (str == "Breakpoint" || m->m_pDebuggingServer->GetSettingBreakOnException())
436  {
437  if (str == "Breakpoint")
438  JS_ClearPendingException(cx);
439  jsval val = JSVAL_NULL;
440  return BreakHandler(cx, script, pc, rval, val, BREAK_SRC_EXCEPTION);
441  }
442  }
443  return JSTRAP_CONTINUE;
444 }
445 
446 JSTrapStatus CThreadDebugger::BreakHandler(JSContext* cx, JSScript* script, jsbytecode* pc, jsval* UNUSED(rval), jsval UNUSED(closure), BREAK_SRC breakSrc)
447 {
448  uint line = JS_PCToLineNumber(cx, script, pc);
449  std::string filename(JS_GetScriptFilename(cx, script));
450 
451  SetIsInBreak(true);
452  SaveCallstack();
453  SetLastBreakLine(line);
454  SetBreakFileName(filename);
455  *m->m_pLastBreakFrame = NULL;
456 
457  if (breakSrc == BREAK_SRC_INTERRUP)
458  {
459  JS_ClearInterrupt(m->m_pScriptInterface->GetRuntime(), NULL, NULL);
460  JS_SetSingleStepMode(cx, script, false);
461  }
462 
463  if (m->m_pDebuggingServer->GetSettingSimultaneousThreadBreak())
464  {
465  m->m_pDebuggingServer->SetBreakRequestedByThread(true);
466  }
467 
468  // Wait until the user continues the execution
469  while (1)
470  {
471  DBGCMD nextDbgCmd = GetNextDbgCmd();
472 
473  while (!m->m_StackInfoRequests.empty())
474  {
475  StackInfoRequest request = m->m_StackInfoRequests.front();
476  SaveStackFrameData(request.requestType, request.nestingLevel);
477  SDL_SemPost(request.semaphore);
478  m->m_StackInfoRequests.pop();
479  }
480 
481  if (nextDbgCmd == DBG_CMD_NONE)
482  {
483  // Wait a while before checking for new m_NextDbgCmd again.
484  // We don't want this loop to take 100% of a CPU core for each thread that is in break mode.
485  // On the other hande we don't want the debugger to become unresponsive.
486  SDL_Delay(100);
487  }
488  else if (nextDbgCmd == DBG_CMD_SINGLESTEP || nextDbgCmd == DBG_CMD_STEPINTO || nextDbgCmd == DBG_CMD_STEPOUT)
489  {
490  JSStackFrame* iter = NULL;
491  *m->m_pLastBreakFrame = JS_FrameIterator(m->m_pScriptInterface->GetContext(), &iter);
492 
493  if (!JS_SetSingleStepMode(cx, script, true))
494  LOGERROR(L"JS_SetSingleStepMode returned false!"); // TODO: When can this happen?
495  else
496  {
497  if (nextDbgCmd == DBG_CMD_SINGLESTEP)
498  {
499  JS_SetInterrupt(m->m_pScriptInterface->GetRuntime(), StepHandler_, this);
500  break;
501  }
502  else if (nextDbgCmd == DBG_CMD_STEPINTO)
503  {
504  JS_SetInterrupt(m->m_pScriptInterface->GetRuntime(), StepIntoHandler_, this);
505  break;
506  }
507  else if (nextDbgCmd == DBG_CMD_STEPOUT)
508  {
509  JS_SetInterrupt(m->m_pScriptInterface->GetRuntime(), StepOutHandler_, this);
510  break;
511  }
512  }
513  }
514  else if (nextDbgCmd == DBG_CMD_CONTINUE)
515  {
516  if (!JS_SetSingleStepMode(cx, script, true))
517  LOGERROR(L"JS_SetSingleStepMode returned false!"); // TODO: When can this happen?
518  else
519  {
520  // Setup a handler to check for break-requests from the DebuggingServer regularly
521  JS_SetInterrupt(m->m_pScriptInterface->GetRuntime(), CheckForBreakRequestHandler_, this);
522  }
523  break;
524  }
525  else
526  debug_warn("Invalid DBGCMD found in CThreadDebugger::BreakHandler!");
527  }
529  SetAllNewTraps();
531  SetIsInBreak(false);
532  SetBreakFileName(std::string());
533 
534  // All saved stack data becomes invalid
535  {
536  CScopeLock lock(m->m_Mutex);
537  m->m_StackFrameData.clear();
538  }
539 
540  return JSTRAP_CONTINUE;
541 }
542 
543 void CThreadDebugger::NewScriptHook(JSContext* cx, const char* filename, unsigned lineno, JSScript* script, JSFunction* UNUSED(fun), void* UNUSED(callerdata))
544 {
545  uint scriptExtent = JS_GetScriptLineExtent (cx, script);
546  std::string stringFileName(filename);
547  if (stringFileName.empty())
548  return;
549 
550  for (uint line = lineno; line < scriptExtent + lineno; ++line)
551  {
552  // If we already have a mapping for this line, we check if the current scipt is more deeply nested.
553  // If it isn't more deeply nested, we don't overwrite the previous mapping
554  // The most deeply nested script is always the one that must be used!
555  uint firstLine = 0;
556  uint lastLine = 0;
557  jsbytecode* oldPC = NULL;
558  if (CheckIfMappingPresent(stringFileName, line))
559  {
560  firstLine = m->m_LineToPCMap[stringFileName][line].firstLineInFunction;
561  lastLine = m->m_LineToPCMap[stringFileName][line].lastLineInFunction;
562 
563  // If an entry nested equally is present too, we must overwrite it.
564  // The same script(function) can trigger a NewScriptHook multiple times without DestroyScriptHooks between these
565  // calls. In this case the old script becomes invalid.
566  if (lineno < firstLine || scriptExtent + lineno > lastLine)
567  continue;
568  else
569  oldPC = m->m_LineToPCMap[stringFileName][line].pBytecode;
570 
571  }
572  jsbytecode* pc = JS_LineNumberToPC (cx, script, line);
573  m->m_LineToPCMap[stringFileName][line].pBytecode = pc;
574  m->m_LineToPCMap[stringFileName][line].pScript = script;
575  m->m_LineToPCMap[stringFileName][line].firstLineInFunction = lineno;
576  m->m_LineToPCMap[stringFileName][line].lastLineInFunction = lineno + scriptExtent;
577 
578  // If we are replacing a script, the associated traps become invalid
579  if (lineno == firstLine && scriptExtent + lineno == lastLine)
580  {
582  SetAllNewTraps();
583  }
584  }
585 }
586 
587 void CThreadDebugger::DestroyScriptHook(JSContext* cx, JSScript* script)
588 {
589  uint scriptExtent = JS_GetScriptLineExtent (cx, script);
590  uint baseLine = JS_GetScriptBaseLineNumber(cx, script);
591 
592  char* pStr = NULL;
593  pStr = (char*)JS_GetScriptFilename(cx, script);
594  if (pStr != NULL)
595  {
596  std::string fileName(pStr);
597 
598  for (uint line = baseLine; line < scriptExtent + baseLine; ++line)
599  {
600  if (CheckIfMappingPresent(fileName, line))
601  {
602  if (m->m_LineToPCMap[fileName][line].pScript == script)
603  {
604  ReturnActiveBreakPoints(m->m_LineToPCMap[fileName][line].pBytecode);
605  m->m_LineToPCMap[fileName].erase(line);
606  if (m->m_LineToPCMap[fileName].empty())
607  m->m_LineToPCMap.erase(fileName);
608  }
609  }
610  }
611  }
612 }
613 
614 void CThreadDebugger::ExecuteHook(JSContext* UNUSED(cx), const char* UNUSED(filename), unsigned UNUSED(lineno), JSScript* UNUSED(script), JSFunction* UNUSED(fun), void* UNUSED(callerdata))
615 {
616  // Search all breakpoints that have no trap set yet
617  {
618  PROFILE2("ExecuteHook");
619  SetAllNewTraps();
620  }
621  return;
622 }
623 
624 bool CThreadDebugger::ToggleBreakPoint(std::string filename, uint userLine)
625 {
626  CScopeLock lock(m->m_Mutex);
627  std::list<CActiveBreakPoint*>::iterator itr;
628  for (itr = m->m_ActiveBreakPoints.begin(); itr != m->m_ActiveBreakPoints.end(); ++itr)
629  {
630  if ((*itr)->m_UserLine == userLine && (*itr)->m_Filename == filename)
631  {
632  (*itr)->m_ToRemove = !(*itr)->m_ToRemove;
633  return true;
634  }
635  }
636  return false;
637 }
638 
639 void CThreadDebugger::GetCallstack(std::stringstream& response)
640 {
641  CScopeLock lock(m->m_Mutex);
642  response << m->m_Callstack;
643 }
644 
646 {
647  ENSURE(GetIsInBreak());
648 
649  CScopeLock lock(m->m_Mutex);
650 
651  JSStackFrame *fp;
652  JSStackFrame *iter = 0;
653  jsint counter = 0;
654 
655  JSObject* jsArray;
656  jsArray = JS_NewArrayObject(m->m_pScriptInterface->GetContext(), 0, 0);
657  JSString* functionID;
658 
659  fp = JS_FrameIterator(m->m_pScriptInterface->GetContext(), &iter);
660 
661  while (fp)
662  {
663  JSFunction* fun = 0;
664  fun = JS_GetFrameFunction(m->m_pScriptInterface->GetContext(), fp);
665  if (NULL == fun)
666  functionID = JS_NewStringCopyZ(m->m_pScriptInterface->GetContext(), "null");
667  else
668  {
669  functionID = JS_GetFunctionId(fun);
670  if (NULL == functionID)
671  functionID = JS_NewStringCopyZ(m->m_pScriptInterface->GetContext(), "anonymous");
672  }
673 
674  JSBool ret = JS_DefineElement(m->m_pScriptInterface->GetContext(), jsArray, counter, STRING_TO_JSVAL(functionID), NULL, NULL, 0);
675  ENSURE(ret);
676  fp = JS_FrameIterator(m->m_pScriptInterface->GetContext(), &iter);
677  counter++;
678  }
679 
680  m->m_Callstack.clear();
681  m->m_Callstack = m->m_pScriptInterface->StringifyJSON(OBJECT_TO_JSVAL(jsArray), false).c_str();
682 }
683 
684 void CThreadDebugger::GetStackFrameData(std::stringstream& response, uint nestingLevel, STACK_INFO stackInfoKind)
685 {
686  // If the data is not yet cached, request it and wait until it's ready.
687  bool dataCached = false;
688  {
689  CScopeLock lock(m->m_Mutex);
690  dataCached = (!m->m_StackFrameData.empty() && m->m_StackFrameData[stackInfoKind].end() != m->m_StackFrameData[stackInfoKind].find(nestingLevel));
691  }
692 
693  if (!dataCached)
694  {
695  SDL_sem* semaphore = SDL_CreateSemaphore(0);
696  AddStackInfoRequest(stackInfoKind, nestingLevel, semaphore);
697  SDL_SemWait(semaphore);
698  SDL_DestroySemaphore(semaphore);
699  }
700 
701  CScopeLock lock(m->m_Mutex);
702  {
703  response.str(std::string());
704  response << m->m_StackFrameData[stackInfoKind][nestingLevel];
705  }
706 }
707 
708 void CThreadDebugger::AddStackInfoRequest(STACK_INFO requestType, uint nestingLevel, SDL_sem* semaphore)
709 {
710  StackInfoRequest request;
711  request.requestType = requestType;
712  request.semaphore = semaphore;
713  request.nestingLevel = nestingLevel;
714  m->m_StackInfoRequests.push(request);
715 }
716 
717 void CThreadDebugger::SaveStackFrameData(STACK_INFO stackInfo, uint nestingLevel)
718 {
719  ENSURE(GetIsInBreak());
720 
721  CScopeLock lock(m->m_Mutex);
722  JSStackFrame *iter = 0;
723  uint counter = 0;
724  jsval val;
725 
726  if (stackInfo == STACK_INFO_GLOBALOBJECT)
727  {
728  JSObject* obj;
729  obj = JS_GetGlobalForScopeChain(m->m_pScriptInterface->GetContext());
730  m->m_StackFrameData[stackInfo][nestingLevel] = StringifyCyclicJSON(OBJECT_TO_JSVAL(obj), false);
731  }
732  else
733  {
734  JSStackFrame *fp = JS_FrameIterator(m->m_pScriptInterface->GetContext(), &iter);
735  while (fp)
736  {
737  if (counter == nestingLevel)
738  {
739  if (stackInfo == STACK_INFO_LOCALS)
740  {
741  JSObject* obj;
742  obj = JS_GetFrameCallObject(m->m_pScriptInterface->GetContext(), fp);
743  //obj = JS_GetFrameScopeChain(m->m_pScriptInterface->GetContext(), fp);
744  m->m_StackFrameData[stackInfo][nestingLevel] = StringifyCyclicJSON(OBJECT_TO_JSVAL(obj), false);
745  }
746  else if (stackInfo == STACK_INFO_THIS)
747  {
748  if (JS_GetFrameThis(m->m_pScriptInterface->GetContext(), fp, &val))
749  m->m_StackFrameData[stackInfo][nestingLevel] = StringifyCyclicJSON(val, false);
750  else
751  m->m_StackFrameData[stackInfo][nestingLevel] = "";
752  }
753  }
754 
755  counter++;
756  fp = JS_FrameIterator(m->m_pScriptInterface->GetContext(), &iter);
757  }
758  }
759 }
760 
761 
762 /*
763  * TODO: This is very hacky and ugly and should be improved.
764  * It replaces cyclic references with a notification that cyclic references are not supported.
765  * It would be better to create a format that supports cyclic references and allows the UI to display them correctly.
766  * Unfortunately this seems to require writing (or embedding) a new serializer to JSON or something similar.
767  *
768  * Some things about the implementation which aren't optimal:
769  * 1. It uses global variables (they are limited to a namespace though).
770  * 2. It has to work around a bug in Spidermonkey.
771  * 3. It copies code from CScriptInterface. I did this to separate it cleanly because the debugger should not affect
772  * the rest of the game and because this part of code should be replaced anyway in the future.
773  */
774 
775 namespace CyclicRefWorkaround
776 {
777  std::set<JSObject*> g_ProcessedObjects;
778  jsval g_LastKey;
779  jsval g_LastValue;
781  uint g_countSameKeys = 0;
782 
783  struct Stringifier
784  {
785  static JSBool callback(const jschar* buf, uint32 len, void* data)
786  {
787  utf16string str(buf, buf+len);
788  std::wstring strw(str.begin(), str.end());
789 
790  Status err; // ignore Unicode errors
791  static_cast<Stringifier*>(data)->stream << utf8_from_wstring(strw, &err);
792  return JS_TRUE;
793  }
794 
795  std::stringstream stream;
796  };
797 
798  JSBool replacer(JSContext* cx, uintN UNUSED(argc), jsval* vp)
799  {
800  jsval value = JS_ARGV(cx, vp)[1];
801  jsval key = JS_ARGV(cx, vp)[0];
802  if (g_LastKey == key)
803  g_countSameKeys++;
804  else
805  g_countSameKeys = 0;
806 
807  if (JSVAL_IS_OBJECT(value))
808  {
809  // Work around a spidermonkey bug that causes replacer to be called twice with the same key:
810  // https://bugzilla.mozilla.org/show_bug.cgi?id=636079
811  // TODO: Remove the workaround as soon as we upgrade to a newer version of Spidermonkey.
812 
813  if (g_ProcessedObjects.end() == g_ProcessedObjects.find(JSVAL_TO_OBJECT(value)))
814  {
815  g_ProcessedObjects.insert(JSVAL_TO_OBJECT(value));
816  }
818  {
820  jsval ret = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, "Debugger: object removed from output because of cyclic reference."));
821  JS_SET_RVAL(cx, vp, ret);
822  g_LastKey = key;
823  g_LastValue = value;
824  return JS_TRUE;
825  }
826  }
827  g_LastKey = key;
828  g_LastValue = value;
830  JS_SET_RVAL(cx, vp, JS_ARGV(cx, vp)[1]);
831  return JS_TRUE;
832  }
833 }
834 
835 std::string CThreadDebugger::StringifyCyclicJSON(jsval obj, bool indent)
836 {
839  CyclicRefWorkaround::g_LastKey = JSVAL_VOID;
840 
841  JSObject* pGlob = JSVAL_TO_OBJECT(m->m_pScriptInterface->GetGlobalObject());
842  JSFunction* fun = JS_DefineFunction(m->m_pScriptInterface->GetContext(), pGlob, "replacer", CyclicRefWorkaround::replacer, 0, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
843  JSObject* replacer = JS_GetFunctionObject(fun);
844  if (!JS_Stringify(m->m_pScriptInterface->GetContext(), &obj, replacer, indent ? INT_TO_JSVAL(2) : JSVAL_VOID, &CyclicRefWorkaround::Stringifier::callback, &str))
845  {
846  LOGERROR(L"StringifyJSON failed");
847  jsval exec;
848  jsval execString;
849  if (JS_GetPendingException(m->m_pScriptInterface->GetContext(), &exec))
850  {
851  if (JSVAL_IS_OBJECT(exec))
852  {
853  JS_GetProperty(m->m_pScriptInterface->GetContext(), JSVAL_TO_OBJECT(exec), "message", &execString);
854 
855  if (JSVAL_IS_STRING(execString))
856  {
857  std::string strExec = JS_EncodeString(m->m_pScriptInterface->GetContext(), JSVAL_TO_STRING(execString));
858  LOGERROR(L"Error: %hs", strExec.c_str());
859  }
860  }
861 
862  }
863  JS_ClearPendingException(m->m_pScriptInterface->GetContext());
864  return std::string();
865  }
866 
867  return str.stream.str();
868 }
869 
870 
872 {
873  return (pScriptInterface == m->m_pScriptInterface);
874 }
875 
876 
878 {
879  CScopeLock lock(m->m_Mutex);
880  return m->m_BreakFileName;
881 }
882 
883 void CThreadDebugger::SetBreakFileName(std::string breakFileName)
884 {
885  CScopeLock lock(m->m_Mutex);
886  m->m_BreakFileName = breakFileName;
887 }
888 
890 {
891  CScopeLock lock(m->m_Mutex);
892  return m->m_LastBreakLine;
893 }
894 
896 {
897  CScopeLock lock(m->m_Mutex);
898  m->m_LastBreakLine = breakLine;
899 }
900 
902 {
903  CScopeLock lock(m->m_IsInBreakMutex);
904  return m->m_IsInBreak;
905 }
906 
907 void CThreadDebugger::SetIsInBreak(bool isInBreak)
908 {
909  CScopeLock lock(m->m_IsInBreakMutex);
910  m->m_IsInBreak = isInBreak;
911 }
912 
914 {
915  CScopeLock lock(m->m_NextDbgCmdMutex);
916  m->m_NextDbgCmd = dbgCmd;
917 }
918 
920 {
921  CScopeLock lock(m->m_NextDbgCmdMutex);
922  return m->m_NextDbgCmd;
923 }
924 
926 {
927  CScopeLock lock(m->m_Mutex);
928  return m->m_Name;
929 }
930 
932 {
933  CScopeLock lock(m->m_Mutex);
934  return m->m_ID;
935 }
936 
void SDL_sem
Definition: wsdl.h:109
CMutex NewScriptHookMutex
std::auto_ptr< ThreadDebugger_impl > m
std::string m_Name
shared between multiple mongoose threads (initialization may be an exception)
void ReturnActiveBreakPoints(jsbytecode *pBytecode)
JSBool replacer(JSContext *cx, uintN argc, jsval *vp)
CMutex ThrowHandlerMutex
#define UNUSED(param)
mark a function parameter as unused and avoid the corresponding compiler warning. ...
STACK_INFO
JSScript * m_Script
void SetLastBreakLine(uint breakLine)
#define LOGERROR
Definition: CLogger.h:35
bool CurrentFrameIsChildOf(JSStackFrame *pParentFrame)
JSTrapStatus StepOutHandler(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, void *closure)
void SDL_Delay(Uint32 ms)
Definition: wsdl.cpp:1457
CMutex CallHookMutex
JSTrapStatus ThrowHandler(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval)
Hook to capture exceptions and breakpoints in code (throw &quot;Breakpoint&quot;;)
std::string utf8_from_wstring(const std::wstring &src, Status *err)
opposite of wstring_from_utf8
Definition: utf8.cpp:208
static ICounter * counter
Definition: whrt.cpp:96
JSTrapStatus StepHandler_(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, void *closure)
CMutex TrapHandlerMutex
static JSTrapStatus ThrowHandler_(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, void *closure)
Locks a CMutex over this object&#39;s lifetime.
Definition: ThreadUtil.h:73
CMutex StepOutHandlerMutex
std::string m_BreakFileName
shared between multiple mongoose threads and one scriptinterface thread
JSTrapStatus StepIntoHandler(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, void *closure)
bool CheckIfMappingPresent(std::string filename, uint line)
Checks if a mapping for the specified filename and line number exists in this CThreadDebugger&#39;s conte...
void SetAllNewTraps()
Checks if a mapping exists for each breakpoint in the list of breakpoints that aren&#39;t set yet...
A non-recursive mutual exclusion lock.
Definition: ThreadUtil.h:45
JSTrapStatus StepOutHandler_(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, void *closure)
void ClearTrap(CActiveBreakPoint *activeBreakPoint)
std::map< std::string, std::map< uint, trapLocation > > m_LineToPCMap
bool CompareScriptInterfacePtr(ScriptInterface *pScriptInterface) const
Compares the object&#39;s associated scriptinterface with the pointer passed as parameter.
JSTrapStatus CheckForBreakRequestHandler(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, void *closure)
This is an interrup-hook that can be called multiple times per line of code and is used to break into...
std::string m_Filename
JSTrapStatus CheckForBreakRequestHandler_(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, void *closure)
void SetNextDbgCmd(DBGCMD dbgCmd)
void Initialize(uint id, std::string name, ScriptInterface *pScriptInterface, CDebuggingServer *pDebuggingServer)
Initialize the object (required before using the object!).
JSStackFrame ** m_pLastBreakFrame
JSTrapStatus StepIntoHandler_(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, void *closure)
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
#define PROFILE2(region)
Starts timing from now until the end of the current scope.
Definition: Profiler2.h:446
CMutex CheckForBreakRequestHandlerMutex
void NewScriptHook_(JSContext *cx, const char *filename, unsigned lineno, JSScript *script, JSFunction *fun, void *callerdata)
ScriptInterface * m_pScriptInterface
static JSBool callback(const jschar *buf, uint32 len, void *data)
void DestroyScriptHook(JSContext *cx, JSScript *script)
This hook makes sure that invalid mappings between filename plus line-number and jsbytecode points ge...
bool ToggleBreakPoint(std::string filename, uint userLine)
Toggle a breakpoint if it&#39;s active in this threadDebugger object.
jsbytecode * m_Pc
JSScript * pScript
void GetStackFrameData(std::stringstream &response, uint nestingLevel, STACK_INFO stackInfoKind)
void SaveStackFrameData(STACK_INFO stackInfo, uint nestingLevel)
std::list< CActiveBreakPoint * > m_ActiveBreakPoints
pthread_key_t key
Definition: wpthread.cpp:140
std::set< JSObject * > g_ProcessedObjects
JSTrapStatus BreakHandler(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, jsval closure, BREAK_SRC breakSrc)
All other hooks call this one if the execution should be paused.
void ClearTrapsToRemove()
Used only in the scriptinterface&#39;s thread.
i64 Status
Error handling system.
Definition: status.h:171
CMutex StepHandlerMutex
void NewScriptHook(JSContext *cx, const char *filename, unsigned lineno, JSScript *script, JSFunction *fun, void *callerdata)
This hook is used to update the mapping between filename plus line-numbers and jsbytecode pointers...
std::string StringifyCyclicJSON(jsval obj, bool indent)
SDL_sem * SDL_CreateSemaphore(int cnt)
Definition: wsdl.cpp:1414
void ExecuteHook(JSContext *cx, const char *filename, unsigned lineno, JSScript *script, JSFunction *fun, void *callerdata)
The callback function which gets executed for each new script that gets loaded and each function insi...
std::basic_string< utf16_t, utf16_traits > utf16string
Definition: utf16string.h:109
CMutex StepIntoHandlerMutex
std::string GetBreakFileName()
static void * CallHook_(JSContext *cx, JSStackFrame *fp, JSBool before, JSBool *ok, void *closure)
void GetCallstack(std::stringstream &response)
void SetNewTrap(CActiveBreakPoint *activeBreakPoint, std::string filename, uint line)
Sets a new trap and stores the information in the CActiveBreakPoint pointer Make sure that a mapping ...
std::string GetName()
NONCOPYABLE(ThreadDebugger_impl)
std::queue< StackInfoRequest > m_StackInfoRequests
DBGCMD
Abstraction around a SpiderMonkey JSContext.
CDebuggingServer * m_pDebuggingServer
#define debug_warn(expr)
display the error dialog with the given text.
Definition: debug.h:324
void SetBreakFileName(std::string breakFileName)
STACK_INFO requestType
jsbytecode * pBytecode
JSTrapStatus TrapHandler(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, jsval closure)
Simply calls BreakHandler with BREAK_SRC_TRAP.
std::map< STACK_INFO, std::map< uint, std::string > > m_StackFrameData
int SDL_SemPost(SDL_sem *sem)
Definition: wsdl.cpp:1426
static JSTrapStatus TrapHandler_(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, jsval closure)
void SetIsInBreak(bool isInBreak)
ThreadDebugger_impl.
int SDL_SemWait(SDL_sem *sem)
Definition: wsdl.cpp:1432
void DestroyScriptHook_(JSContext *cx, JSScript *script, void *callerdata)
JSTrapStatus StepHandler(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, void *closure)
CMutex DestroyScriptHookMutex
void AddStackInfoRequest(STACK_INFO requestType, uint nestingLevel, SDL_sem *semaphore)
BREAK_SRC