JavascriptDebuggingServer: javascript_debugging_v1.0.diff
File javascript_debugging_v1.0.diff, 74.8 KB (added by , 11 years ago) |
---|
-
build/premake/premake4.lua
545 545 "boost", 546 546 "spidermonkey", 547 547 "valgrind", 548 "sdl", 548 549 } 549 550 setup_static_lib_project("scriptinterface", source_dirs, extern_libs, {}) 550 551 -
source/graphics/MapGenerator.cpp
63 63 void* CMapGeneratorWorker::RunThread(void *data) 64 64 { 65 65 debug_SetThreadName("MapGenerator"); 66 g_Profiler2.RegisterCurrentThread("MapGenerator"); 66 67 67 68 CMapGeneratorWorker* self = static_cast<CMapGeneratorWorker*>(data); 68 69 -
source/gui/scripting/JSInterface_IGUIObject.cpp
75 75 if (propName == "constructor" || 76 76 propName == "prototype" || 77 77 propName == "toString" || 78 propName == "toJSON" || 78 79 propName == "focus" || 79 80 propName == "blur" || 80 81 propName == "getComputedSize" -
source/ps/ConfigDB.h
80 80 static VfsPath m_ConfigFile[]; 81 81 82 82 public: 83 CConfigDB(); 84 83 85 // NOTE: Construct the Singleton Object *after* JavaScript init, so that 84 // the JS interface can be registered. 85 CConfigDB(); 86 // the JS interface can be registered. ConfigDB (C++) needs to be initialized before 87 // The ScriptInterface because the ScriptInterface requires some configuration information too. 88 void RegisterJSConfigDB(); 86 89 87 90 /** 88 91 * Attempt to find a config variable with the given name; will search -
source/ps/ConfigDB.cpp
210 210 211 211 CConfigDB::CConfigDB() 212 212 { 213 } 214 215 void CConfigDB::RegisterJSConfigDB() 216 { 213 217 g_ScriptingHost.DefineCustomObjectType(&ConfigDB_JS::Class, ConfigDB_JS::Construct, 0, ConfigDB_JS::Props, ConfigDB_JS::Funcs, NULL, NULL); 214 218 g_ScriptingHost.DefineCustomObjectType(&ConfigNamespace_JS::Class, ConfigNamespace_JS::Construct, 0, NULL, ConfigNamespace_JS::Funcs, NULL, NULL); 215 219 JSObject *js_ConfigDB = g_ScriptingHost.CreateCustomObject("ConfigDB"); -
source/ps/GameSetup/GameSetup.cpp
84 84 #include "renderer/ModelRenderer.h" 85 85 #include "scripting/ScriptingHost.h" 86 86 #include "scripting/ScriptGlue.h" 87 #include "scriptinterface/DebuggingServer.h" 87 88 #include "scriptinterface/ScriptInterface.h" 88 89 #include "scriptinterface/ScriptStats.h" 89 90 #include "simulation2/Simulation2.h" … … 692 693 693 694 TIMER_BEGIN(L"shutdown ScriptingHost"); 694 695 delete &g_ScriptingHost; 696 delete g_DebuggingServer; 695 697 TIMER_END(L"shutdown ScriptingHost"); 696 698 697 699 TIMER_BEGIN(L"shutdown ConfigDB"); … … 886 888 CSoundManager::CreateSoundManager(); 887 889 #endif 888 890 889 InitScripting(); // before GUI890 891 891 // g_ConfigDB, command line args, globals 892 892 CONFIG_Init(args); 893 894 // before scripting 895 if (g_JSDebuggerEnabled) 896 g_DebuggingServer = new CDebuggingServer(); 893 897 898 InitScripting(); // before GUI 899 900 g_ConfigDB.RegisterJSConfigDB(); // after scripting 901 894 902 // Optionally start profiler HTTP output automatically 895 903 // (By default it's only enabled by a hotkey, for security/performance) 896 904 bool profilerHTTPEnable = false; -
source/ps/GameSetup/Config.cpp
21 21 22 22 #include "ps/ConfigDB.h" 23 23 #include "ps/CConsole.h" 24 #include "ps/CLogger.h" 24 25 #include "ps/GameSetup/CmdLineArgs.h" 25 26 #include "lib/timer.h" 26 27 #include "soundmanager/SoundManager.h" … … 60 61 bool g_Quickstart = false; 61 62 bool g_DisableAudio = false; 62 63 64 bool g_JSDebuggerEnabled = false; 65 bool g_ScriptProfilingEnabled = false; 66 63 67 // flag to switch on drawing terrain overlays 64 68 bool g_ShowPathfindingOverlay = false; 65 69 … … 127 131 g_SoundManager->SetMemoryUsage(bufferSize, bufferCount); 128 132 } 129 133 #endif // CONFIG2_AUDIO 134 135 CFG_GET_VAL("jsdebugger.enable", Bool, g_JSDebuggerEnabled); 136 CFG_GET_VAL("profiler2.script.enable", Bool, g_ScriptProfilingEnabled); 137 138 // Script Debugging and profiling does not make sense together because of the hooks 139 // that reduce performance a lot - and it wasn't tested if it even works together. 140 if (g_JSDebuggerEnabled && g_ScriptProfilingEnabled) 141 LOGERROR(L"Enabling both script profiling and script debugging is not supported!"); 130 142 } 131 143 132 144 -
source/ps/GameSetup/Config.h
82 82 extern bool g_Quickstart; 83 83 extern bool g_DisableAudio; 84 84 85 extern bool g_JSDebuggerEnabled; 86 extern bool g_ScriptProfilingEnabled; 87 85 88 extern CStrW g_CursorName; 86 89 87 90 class CmdLineArgs; -
source/scriptinterface/ScriptInterface.h
43 43 // TODO: what's a good default? 44 44 #define DEFAULT_RUNTIME_SIZE 16 * 1024 * 1024 45 45 46 47 #ifdef NDEBUG48 #define ENABLE_SCRIPT_PROFILING 049 #else50 #define ENABLE_SCRIPT_PROFILING 151 #endif52 53 46 struct ScriptInterface_impl; 54 47 55 48 class ScriptRuntime; 56 49 50 class CDebuggingServer; 51 57 52 /** 58 53 * Abstraction around a SpiderMonkey JSContext. 59 54 * … … 244 239 * Stringify to a JSON string, UTF-8 encoded. Returns an empty string on error. 245 240 */ 246 241 std::string StringifyJSON(jsval obj, bool indent = true); 247 242 248 243 /** 249 244 * Report the given error message through the JS error reporting mechanism, 250 245 * and throw a JS exception. (Callers can check IsPendingException, and must -
source/scriptinterface/NativeWrapperDefns.h
14 14 * You should have received a copy of the GNU General Public License 15 15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. 16 16 */ 17 #include "ps/GameSetup/Config.h" 17 18 18 19 // (NativeWrapperDecls.h set up a lot of the macros we use here) 19 20 20 21 21 // ScriptInterface_NativeWrapper<T>::call(cx, rval, fptr, args...) will call fptr(cbdata, args...), 22 22 // and if T != void then it will store the result in rval: 23 23 … … 76 76 // ScriptInterface_impl::Register stores the name in a reserved slot. 77 77 // (TODO: this doesn't work for functions registered via InterfaceScripted.h. 78 78 // Maybe we should do some interned JS_GetFunctionId thing.) 79 #if ENABLE_SCRIPT_PROFILING80 79 #define SCRIPT_PROFILE \ 81 ENSURE(JSVAL_IS_OBJECT(JS_CALLEE(cx, vp)) && JS_ObjectIsFunction(cx, JSVAL_TO_OBJECT(JS_CALLEE(cx, vp))));\82 const char* name = "(unknown)";\83 jsval nameval; \84 if (JS_GetReservedSlot(cx, JSVAL_TO_OBJECT(JS_CALLEE(cx, vp)), 0, &nameval)\85 && !JSVAL_IS_VOID(nameval))\86 name = static_cast<const char*>(JSVAL_TO_PRIVATE(nameval));\87 CProfileSampleScript profile(name);88 #else 89 #define SCRIPT_PROFILE 90 #endif 80 if (g_ScriptProfilingEnabled) \ 81 { \ 82 ENSURE(JSVAL_IS_OBJECT(JS_CALLEE(cx, vp)) && JS_ObjectIsFunction(cx, JSVAL_TO_OBJECT(JS_CALLEE(cx, vp)))); \ 83 const char* name = "(unknown)"; \ 84 jsval nameval; \ 85 if (JS_GetReservedSlot(cx, JSVAL_TO_OBJECT(JS_CALLEE(cx, vp)), 0, &nameval) \ 86 && !JSVAL_IS_VOID(nameval)) \ 87 name = static_cast<const char*>(JSVAL_TO_PRIVATE(nameval)); \ 88 CProfileSampleScript profile(name); \ 89 } 91 90 92 91 // JSFastNative-compatible function that wraps the function identified in the template argument list 93 92 #define OVERLOADS(z, i, data) \ -
source/scriptinterface/ThreadDebugger.h
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 #ifndef INCLUDED_THREADDEBUGGER 19 #define INCLUDED_THREADDEBUGGER 20 21 #include "DebuggingServer.h" 22 #include "ScriptInterface.h" 23 #include "scriptinterface/ScriptExtraHeaders.h" 24 25 26 // These Breakpoint classes are not implemented threadsafe. The class using the Breakpoints is responsible to make sure that 27 // only one thread accesses the Breakpoint at a time 28 class CBreakPoint 29 { 30 public: 31 CBreakPoint() { m_UserLine = 0; m_Filename = ""; } 32 uint m_UserLine; 33 std::string m_Filename; 34 }; 35 36 // Only use this with one ScriptInterface/CThreadDebugger! 37 class CActiveBreakPoint : public CBreakPoint 38 { 39 public: 40 CActiveBreakPoint() : 41 CBreakPoint() 42 { 43 Initialize(); 44 } 45 46 CActiveBreakPoint(CBreakPoint breakPoint) 47 { 48 m_Filename = breakPoint.m_Filename; 49 m_UserLine = breakPoint.m_UserLine; 50 Initialize(); 51 } 52 53 void Initialize() 54 { 55 m_ToRemove = false; 56 m_Script = NULL; 57 m_Pc = NULL; 58 m_ActualLine = m_UserLine; 59 } 60 61 uint m_ActualLine; 62 JSScript* m_Script; 63 jsbytecode* m_Pc; 64 bool m_ToRemove; 65 }; 66 67 enum BREAK_SRC { BREAK_SRC_TRAP, BREAK_SRC_INTERRUP, BREAK_SRC_EXCEPTION }; 68 69 70 class CThreadDebugger 71 { 72 public: 73 CThreadDebugger(); 74 ~CThreadDebugger(); 75 76 /** @brief Initialize the object (required before using the object!). 77 * 78 * @param id A unique identifier greater than 0 for the object inside its CDebuggingServer object. 79 * @param name A name that will be can be displayed by the UI to identify the thread. 80 * @param pScriptInterface Pointer to a scriptinterface. All Hooks, breakpoint traps etc. will be registered in this 81 * scriptinterface and will be called by the thread this scriptinterface is running in. 82 * @param pDebuggingServer Pointer to the DebuggingServer object this Object should belong to. 83 * 84 * @return Return value. 85 */ 86 void Initialize(uint id, std::string name, ScriptInterface* pScriptInterface, CDebuggingServer* pDebuggingServer); 87 88 89 // A bunch of hooks used to get information from spidermonkey. 90 // These hooks are used internally only but have to be public because they need to be accessible from the global hook functions. 91 // Spidermonkey requires function pointers as hooks, which only works if the functions are global or static (not part of an object). 92 // These global functions in ThreadDebugger.cpp are just wrappers for the following member functions. 93 94 /** Simply calls BreakHandler with BREAK_SRC_TRAP */ 95 JSTrapStatus TrapHandler(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, jsval closure); 96 /** Hook to capture exceptions and breakpoints in code (throw "Breakpoint";) */ 97 JSTrapStatus ThrowHandler(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval); 98 /** All other hooks call this one if the execution should be paused. It puts the program in a wait-loop and 99 * waits for weak-up events (continue, step etc.). */ 100 JSTrapStatus BreakHandler(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, jsval closure, BREAK_SRC breakSrc); 101 JSTrapStatus StepHandler(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, void *closure); 102 JSTrapStatus StepIntoHandler(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, void *closure); 103 JSTrapStatus StepOutHandler(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, void *closure); 104 /** This is an interrup-hook that can be called multiple times per line of code and is used to break into the execution 105 * without previously setting a breakpoint and to break other threads when one thread triggers a breakpoint */ 106 JSTrapStatus CheckForBreakRequestHandler(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, void *closure); 107 /** The callback function which gets executed for each new script that gets loaded and each function inside a script. 108 * We use it for "Execute-Hooks" and "Call-Hooks" in terms of Spidermonkey. 109 * This hook actually sets the traps (Breakpoints) "on the fly" that have been defined by the user previously. 110 */ 111 void ExecuteHook(JSContext *cx, const char *filename, unsigned lineno, JSScript *script, JSFunction *fun, void *callerdata); 112 /** This hook is used to update the mapping between filename plus line-numbers and jsbytecode pointers */ 113 void NewScriptHook(JSContext *cx, const char *filename, unsigned lineno, JSScript *script, JSFunction *fun, void *callerdata); 114 /** This hook makes sure that invalid mappings between filename plus line-number and jsbytecode points get deleted */ 115 void DestroyScriptHook(JSContext *cx, JSScript *script); 116 117 118 void ClearTrap(CActiveBreakPoint* activeBreakPoint); 119 120 /** @brief Checks if a mapping for the specified filename and line number exists in this CThreadDebugger's context 121 */ 122 bool CheckIfMappingPresent(std::string filename, uint line); 123 124 /** @brief Checks if a mapping exists for each breakpoint in the list of breakpoints that aren't set yet. 125 * If there is a mapping, it removes the breakpoint from the list of unset breakpoints (from CDebuggingServer), 126 * adds it to the list of active breakpoints (CThreadDebugger) and sets a trap. 127 * Threading: m_Mutex is locked in this call 128 */ 129 void SetAllNewTraps(); 130 131 /** @brief Sets a new trap and stores the information in the CActiveBreakPoint pointer 132 * Make sure that a mapping exists before calling this function 133 * Threading: Locking m_Mutex is required by the callee 134 */ 135 void SetNewTrap(CActiveBreakPoint* activeBreakPoint, std::string filename, uint line); 136 137 /** @brief Toggle a breakpoint if it's active in this threadDebugger object. 138 * Threading: Locking m_Mutex is required by the callee 139 * 140 * @param filename full vfs path to the script filename 141 * @param userLine linenumber where the USER set the breakpoint (UserLine) 142 * 143 * @return true if the breakpoint's state was changed 144 */ 145 bool ToggleBreakPoint(std::string filename, uint userLine); 146 147 148 void GetCallstack(std::stringstream& response); 149 void GetStackFrameData(std::stringstream& response, uint nestingLevel, STACK_INFO stackInfoKind); 150 151 /** @brief Compares the object's associated scriptinterface with the pointer passed as parameter. 152 * @return true if equal 153 */ 154 bool CompareScriptInterfacePtr(ScriptInterface* pScriptInterface); 155 156 // Getter/Setters for members that need to be threadsafe 157 std::string GetBreakFileName(); 158 bool GetIsInBreak(); 159 uint GetLastBreakLine(); 160 std::string GetName(); 161 uint GetID(); 162 void ContinueExecution(); 163 void SetNextDbgCmd(DBGCMD dbgCmd); 164 DBGCMD GetNextDbgCmd(); 165 // The callee is responsible for locking m_Mutex 166 void AddStackInfoRequest(STACK_INFO requestType, uint nestingLevel, SDL_sem* semaphore); 167 168 169 private: 170 // Getters/Setters for members that need to be threadsafe 171 void SetBreakFileName(std::string breakFileName); 172 void SetLastBreakLine(uint breakLine); 173 void SetIsInBreak(bool isInBreak); 174 175 // Other threadsafe functions 176 void SaveCallstack(); 177 178 CMutex m_Mutex; 179 CMutex m_ActiveBreakpointsMutex; 180 CMutex m_NextDbgCmdMutex; 181 CMutex m_IsInBreakMutex; 182 183 /// Used only in the scriptinterface's thread. 184 void ClearTrapsToRemove(); 185 bool CurrentFrameIsChildOf(JSStackFrame* pParentFrame); 186 void ReturnActiveBreakPoints(jsbytecode* pBytecode); 187 void SaveStackFrameData(STACK_INFO stackInfo, uint nestingLevel); 188 std::string StringifyCyclicJSON(jsval obj, bool indent); 189 // This member could actually be used by other threads via CompareScriptInterfacePtr(), but that should be safe 190 ScriptInterface* m_pScriptInterface; 191 CDebuggingServer* m_pDebuggingServer; 192 // We store the pointer on the heap because the stack frame becomes invalid in certain cases 193 // and spidermonkey throws errors if it detects a pointer on the stack. 194 // We only use the pointer for comparing it with the current stack pointer and we don't try to access it, so it 195 // shouldn't be a problem. 196 JSStackFrame** m_pLastBreakFrame; 197 uint m_ObjectReferenceID; 198 199 /// shared between multiple mongoose threads and one scriptinterface thread 200 std::string m_BreakFileName; 201 uint m_LastBreakLine; 202 bool m_IsInBreak; 203 DBGCMD m_NextDbgCmd; 204 205 206 struct StackInfoRequest 207 { 208 STACK_INFO requestType; 209 uint nestingLevel; 210 SDL_sem* semaphore; 211 212 }; 213 std::queue<StackInfoRequest> m_StackInfoRequests; 214 215 struct trapLocation 216 { 217 jsbytecode* pBytecode; 218 JSScript* pScript; 219 uint firstLineInFunction; 220 uint lastLineInFunction; 221 }; 222 std::map<std::string, std::map<uint, trapLocation> > m_LineToPCMap; 223 std::list<CActiveBreakPoint*> m_ActiveBreakPoints; 224 std::map<STACK_INFO, std::map<uint, std::string> > m_StackFrameData; 225 std::string m_Callstack; 226 227 /// shared between multiple mongoose threads (initialization may be an exception) 228 std::string m_Name; 229 uint m_ID; 230 231 }; 232 233 #endif // INCLUDED_THREADDEBUGGER -
source/scriptinterface/ScriptInterface.cpp
18 18 #include "precompiled.h" 19 19 20 20 #include "ScriptInterface.h" 21 #include "DebuggingServer.h" 21 22 #include "ScriptStats.h" 22 23 #include "AutoRooters.h" 23 24 … … 73 74 m_rt = JS_NewRuntime(runtimeSize); 74 75 ENSURE(m_rt); // TODO: error handling 75 76 76 #if ENABLE_SCRIPT_PROFILING 77 // Profiler isn't thread-safe, so only enable this on the main thread 78 if (ThreadUtil::IsMainThread()) 77 if (g_ScriptProfilingEnabled) 79 78 { 80 if (CProfileManager::IsInitialised()) 79 // Profiler isn't thread-safe, so only enable this on the main thread 80 if (ThreadUtil::IsMainThread()) 81 81 { 82 JS_SetExecuteHook(m_rt, jshook_script, this); 83 JS_SetCallHook(m_rt, jshook_function, this); 82 if (CProfileManager::IsInitialised()) 83 { 84 JS_SetExecuteHook(m_rt, jshook_script, this); 85 JS_SetCallHook(m_rt, jshook_function, this); 86 } 84 87 } 85 88 } 86 #endif87 89 88 90 JS_SetExtraGCRoots(m_rt, jshook_trace, this); 89 91 } … … 100 102 101 103 private: 102 104 103 #if ENABLE_SCRIPT_PROFILING 105 104 106 static void* jshook_script(JSContext* UNUSED(cx), JSStackFrame* UNUSED(fp), JSBool before, JSBool* UNUSED(ok), void* closure) 105 107 { 106 108 if (before) … … 212 214 213 215 return closure; 214 216 } 215 #endif216 217 217 218 static void jshook_trace(JSTracer* trc, void* data) 218 219 { … … 493 494 options |= JSOPTION_XML; // "ECMAScript for XML support: parse <!-- --> as a token" 494 495 options |= JSOPTION_VAROBJFIX; // "recommended" (fixes variable scoping) 495 496 496 // Enable method JIT, unless script profiling is enabled (since profiling497 // Enable method JIT, unless script profiling/debugging is enabled (since profiling/debugging 497 498 // hooks are incompatible with the JIT) 498 #if !ENABLE_SCRIPT_PROFILING 499 options |= JSOPTION_METHODJIT; 499 // TODO: Verify what exactly is incompatible 500 if (!g_ScriptProfilingEnabled && !g_JSDebuggerEnabled) 501 { 502 options |= JSOPTION_METHODJIT; 500 503 501 // Some other JIT flags to experiment with:502 options |= JSOPTION_JIT;503 options |= JSOPTION_PROFILING;504 #endif 504 // Some other JIT flags to experiment with: 505 options |= JSOPTION_JIT; 506 options |= JSOPTION_PROFILING; 507 } 505 508 506 509 JS_SetOptions(m_cx, options); 507 510 … … 557 560 if (!func) 558 561 return; 559 562 560 #if ENABLE_SCRIPT_PROFILING 561 // Store the function name in a slot, so we can pass it to the profiler. 563 if (g_ScriptProfilingEnabled) 564 { 565 // Store the function name in a slot, so we can pass it to the profiler. 562 566 563 // Use a flyweight std::string because we can't assume the caller has564 // a correctly-aligned string and that its lifetime is long enough565 typedef boost::flyweight<566 std::string,567 boost::flyweights::no_tracking568 // can't use no_locking; Register might be called in threads569 > LockedStringFlyweight;567 // Use a flyweight std::string because we can't assume the caller has 568 // a correctly-aligned string and that its lifetime is long enough 569 typedef boost::flyweight< 570 std::string, 571 boost::flyweights::no_tracking 572 // can't use no_locking; Register might be called in threads 573 > LockedStringFlyweight; 570 574 571 LockedStringFlyweight fw(name);572 JS_SetReservedSlot(m_cx, JS_GetFunctionObject(func), 0, PRIVATE_TO_JSVAL((void*)fw.get().c_str()));573 #endif 575 LockedStringFlyweight fw(name); 576 JS_SetReservedSlot(m_cx, JS_GetFunctionObject(func), 0, PRIVATE_TO_JSVAL((void*)fw.get().c_str())); 577 } 574 578 } 575 579 576 580 ScriptInterface::ScriptInterface(const char* nativeScopeName, const char* debugName, const shared_ptr<ScriptRuntime>& runtime) : … … 582 586 if (g_ScriptStatsTable) 583 587 g_ScriptStatsTable->Add(this, debugName); 584 588 } 589 590 if (g_JSDebuggerEnabled && g_DebuggingServer != NULL) 591 { 592 if(!JS_SetDebugMode(GetContext(), true)) 593 LOGERROR(L"Failed to set Spidermonkey to debug mode!"); 594 else 595 g_DebuggingServer->RegisterScriptinterface(debugName, this); 596 } 585 597 } 586 598 587 599 ScriptInterface::~ScriptInterface() … … 591 603 if (g_ScriptStatsTable) 592 604 g_ScriptStatsTable->Remove(this); 593 605 } 606 607 // Unregister from the Debugger class 608 if (g_JSDebuggerEnabled && g_DebuggingServer != NULL) 609 g_DebuggingServer->UnRegisterScriptinterface(this); 594 610 } 595 611 596 612 void ScriptInterface::ShutDown() … … 1075 1091 { 1076 1092 JS_ClearPendingException(m->m_cx); 1077 1093 LOGERROR(L"StringifyJSON failed"); 1094 JS_ClearPendingException(m->m_cx); 1078 1095 return ""; 1079 1096 } 1080 1097 1081 1098 return str.stream.str(); 1082 1099 } 1083 1100 1101 1084 1102 std::wstring ScriptInterface::ToString(jsval obj, bool pretty) 1085 1103 { 1086 1104 if (JSVAL_IS_VOID(obj)) -
source/scriptinterface/DebuggingServer.h
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 #ifndef INCLUDED_DEBUGGINGSERVER 19 #define INCLUDED_DEBUGGINGSERVER 20 21 #include "third_party/mongoose/mongoose.h" 22 #include "ScriptInterface.h" 23 24 #include "lib/external_libraries/libsdl.h" 25 26 class CBreakPoint; 27 class CThreadDebugger; 28 29 enum DBGCMD { DBG_CMD_NONE=0, DBG_CMD_CONTINUE, DBG_CMD_SINGLESTEP, DBG_CMD_STEPINTO, DBG_CMD_STEPOUT }; 30 enum STACK_INFO { STACK_INFO_LOCALS=0, STACK_INFO_THIS, STACK_INFO_GLOBALOBJECT }; 31 32 class CDebuggingServer 33 { 34 public: 35 CDebuggingServer(); 36 ~CDebuggingServer(); 37 38 /** @brief Register a new ScriptInerface for debugging the scripts it executes 39 * 40 * @param name A name for the ScriptInterface (will be sent to the debugging client an probably displayed to the user) 41 * @param pScriptInterface A pointer to the ScriptInterface. This pointer must stay valid until UnRegisterScriptInterface ist called! 42 */ 43 void RegisterScriptinterface(std::string name, ScriptInterface* pScriptInterface); 44 45 /** @brief Unregister a ScriptInerface that was previously registered using RegisterScriptinterface. 46 * 47 * @param pScriptInterface A pointer to the ScriptInterface 48 */ 49 void UnRegisterScriptinterface(ScriptInterface* pScriptInterface); 50 51 52 // Mongoose callback when request comes from a client 53 void* MgDebuggingServerCallback(mg_event event, struct mg_connection *conn, const struct mg_request_info *request_info); 54 55 /** @brief Aquire exclusive read and write access to the list of breakpoints. 56 * 57 * @param breakPoints A pointer to the list storing all breakpoints. 58 * 59 * @return A number you need to pass to ReleaseBreakPointAccess(). 60 * 61 * Make sure to call ReleaseBreakPointAccess after you don't need access any longer. 62 * Using this function you get exclusive access and other threads won't be able to access the breakpoints until you call ReleaseBreakPointAccess! 63 */ 64 double AquireBreakPointAccess(std::list<CBreakPoint>** breakPoints); 65 66 /** @brief See AquireBreakPointAccess(). You must not access the pointer returend by AquireBreakPointAccess() any longer after you call this function. 67 * 68 * @param breakPointsLockID The number you got when aquiring the access. It's used to make sure that this function never gets 69 * used by the wrong thread. 70 */ 71 void ReleaseBreakPointAccess(double breakPointsLockID); 72 73 74 /// Called from multiple Mongoose threads and multiple ScriptInterface threads 75 bool GetBreakRequestedByThread(); 76 bool GetBreakRequestedByUser(); 77 // Should other threads be stopped as soon as possible after a breakpoint is triggered in a thread 78 bool GetSettingSimultaneousThreadBreak(); 79 // Should the debugger break on any JS-Exception? If set to false, it will only break when the exceptions text is "Breakpoint". 80 bool GetSettingBreakOnException(); 81 void SetBreakRequestedByThread(bool Enabled); 82 void SetBreakRequestedByUser(bool Enabled); 83 84 private: 85 static const char* header400; 86 87 /// Webserver helper function (can be called by multiple mongooser threads) 88 bool GetWebArgs(struct mg_connection *conn, const struct mg_request_info* request_info, std::string argName, uint& arg); 89 bool GetWebArgs(struct mg_connection *conn, const struct mg_request_info* request_info, std::string argName, std::string& arg); 90 91 /// Functions that are made available via http (can be called by multiple mongoose threads) 92 void GetThreadDebuggerStatus(std::stringstream& response); 93 void ToggleBreakPoint(std::string filename, uint line); 94 void GetAllCallstacks(std::stringstream& response); 95 void GetStackFrameData(std::stringstream& response, uint nestingLevel, uint threadDebuggerID, STACK_INFO stackInfoKind); 96 bool SetNextDbgCmd(uint threadDebuggerID, DBGCMD dbgCmd); 97 void SetSettingSimultaneousThreadBreak(bool Enabled); 98 void SetSettingBreakOnException(bool Enabled); 99 100 /** @brief Returns a list of the full vfs paths to all files with the extension .js found in the vfs root 101 * 102 * @param response This will contain the list as JSON array. 103 */ 104 void EnumVfsJSFiles(std::stringstream& response); 105 106 /** @brief Get the content of a .js file loaded into vfs 107 * 108 * @param filename A full vfs path (as returned by EnumVfsJSFiles). 109 * @param response This will contain the contents of the requested file. 110 */ 111 void GetFile(std::string filename, std::stringstream& response); 112 113 /// Shared between multiple mongoose threads 114 115 116 bool m_SettingSimultaneousThreadBreak; 117 bool m_SettingBreakOnException; 118 119 /// Shared between multiple scriptinterface threads 120 uint m_LastThreadDebuggerID; 121 122 /// Shared between multiple scriptinerface threads and multiple mongoose threads 123 std::list<CThreadDebugger*> m_ThreadDebuggers; 124 125 // The CThreadDebuggers will check this value and break the thread if it's true. 126 // This only works for JS code, so C++ code will not break until it executes JS-code again. 127 bool m_BreakRequestedByThread; 128 bool m_BreakRequestedByUser; 129 130 // The breakpoint is uniquely identified using filename an line-number. 131 // Since the filename is the whole vfs path it should really be unique. 132 std::list<CBreakPoint> m_BreakPoints; 133 134 /// Used for controlling access to m_BreakPoints 135 SDL_sem* m_BreakPointsSem; 136 double m_BreakPointsLockID; 137 138 /// Mutexes used to ensure thread-safety. Currently we just use one Mutex (m_Mutex) and if we detect possible sources 139 /// of deadlocks, we use the second mutex for some members to avoid it. 140 CMutex m_Mutex; 141 CMutex m_Mutex1; 142 143 /// Not important for this class' thread-safety 144 void EnableHTTP(); 145 mg_context* m_MgContext; 146 }; 147 148 extern CDebuggingServer* g_DebuggingServer; 149 150 151 #endif // INCLUDED_DEBUGGINGSERVER -
source/scriptinterface/ThreadDebugger.cpp
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 // Hooks 25 26 CMutex ThrowHandlerMutex; 27 static JSTrapStatus ThrowHandler_(JSContext* cx, JSScript* script, jsbytecode* pc, jsval* rval, void* closure) 28 { 29 CScopeLock lock(ThrowHandlerMutex); 30 CThreadDebugger* pThreadDebugger = (CThreadDebugger*) closure; 31 return pThreadDebugger->ThrowHandler(cx, script, pc, rval); 32 } 33 34 CMutex TrapHandlerMutex; 35 static JSTrapStatus TrapHandler_(JSContext* cx, JSScript* script, jsbytecode* pc, jsval* rval, jsval closure) 36 { 37 CScopeLock lock(TrapHandlerMutex); 38 CThreadDebugger* pThreadDebugger = (CThreadDebugger*) JSVAL_TO_PRIVATE(closure); 39 jsval val = JSVAL_NULL; 40 return pThreadDebugger->TrapHandler(cx, script, pc, rval, val); 41 } 42 43 CMutex StepHandlerMutex; 44 JSTrapStatus StepHandler_(JSContext* cx, JSScript* script, jsbytecode* pc, jsval* rval, void* closure) 45 { 46 CScopeLock lock(StepHandlerMutex); 47 CThreadDebugger* pThreadDebugger = (CThreadDebugger*) closure; 48 jsval val = JSVAL_VOID; 49 return pThreadDebugger->StepHandler(cx, script, pc, rval, &val); 50 } 51 52 CMutex StepIntoHandlerMutex; 53 JSTrapStatus StepIntoHandler_(JSContext* cx, JSScript* script, jsbytecode* pc, jsval* rval, void* closure) 54 { 55 CScopeLock lock(StepIntoHandlerMutex); 56 CThreadDebugger* pThreadDebugger = (CThreadDebugger*) closure; 57 return pThreadDebugger->StepIntoHandler(cx, script, pc, rval, NULL); 58 } 59 60 CMutex NewScriptHookMutex; 61 void NewScriptHook_(JSContext* cx, const char* filename, unsigned lineno, JSScript* script, JSFunction* fun, void* callerdata) 62 { 63 CScopeLock lock(NewScriptHookMutex); 64 CThreadDebugger* pThreadDebugger = (CThreadDebugger*) callerdata; 65 return pThreadDebugger->NewScriptHook(cx, filename, lineno, script, fun, NULL); 66 } 67 68 CMutex DestroyScriptHookMutex; 69 void DestroyScriptHook_(JSContext* cx, JSScript* script, void* callerdata) 70 { 71 CScopeLock lock(DestroyScriptHookMutex); 72 CThreadDebugger* pThreadDebugger = (CThreadDebugger*) callerdata; 73 return pThreadDebugger->DestroyScriptHook(cx, script); 74 } 75 76 CMutex StepOutHandlerMutex; 77 JSTrapStatus StepOutHandler_(JSContext* cx, JSScript* script, jsbytecode* pc, jsval* rval, void* closure) 78 { 79 CScopeLock lock(StepOutHandlerMutex); 80 CThreadDebugger* pThreadDebugger = (CThreadDebugger*) closure; 81 return pThreadDebugger->StepOutHandler(cx, script, pc, rval, NULL); 82 } 83 84 CMutex CheckForBreakRequestHandlerMutex; 85 JSTrapStatus CheckForBreakRequestHandler_(JSContext* cx, JSScript* script, jsbytecode* pc, jsval* rval, void* closure) 86 { 87 CScopeLock lock(CheckForBreakRequestHandlerMutex); 88 CThreadDebugger* pThreadDebugger = (CThreadDebugger*) closure; 89 return pThreadDebugger->CheckForBreakRequestHandler(cx, script, pc, rval, NULL); 90 } 91 92 CMutex CallHookMutex; 93 static void* CallHook_(JSContext* cx, JSStackFrame* fp, JSBool before, JSBool* UNUSED(ok), void* closure) 94 { 95 CScopeLock lock(CallHookMutex); 96 CThreadDebugger* pThreadDebugger = (CThreadDebugger*) closure; 97 if (before) 98 { 99 JSScript* script; 100 script = JS_GetFrameScript(cx, fp); 101 const char* fileName = JS_GetScriptFilename(cx, script); 102 uint lineno = JS_GetScriptBaseLineNumber(cx, script); 103 JSFunction* fun = JS_GetFrameFunction(cx, fp); 104 pThreadDebugger->ExecuteHook(cx, fileName, lineno, script, fun, closure); 105 } 106 107 return closure; 108 } 109 110 /// CThreadDebugger 111 112 void CThreadDebugger::ClearTrapsToRemove() 113 { 114 CScopeLock lock(m_Mutex); 115 std::list<CActiveBreakPoint*>::iterator itr=m_ActiveBreakPoints.begin(); 116 while (itr != m_ActiveBreakPoints.end()) 117 { 118 if ((*itr)->m_ToRemove) 119 { 120 ClearTrap((*itr)); 121 // Remove the breakpoint 122 delete (*itr); 123 itr = m_ActiveBreakPoints.erase(itr); 124 125 } 126 else 127 itr++; 128 } 129 } 130 131 void CThreadDebugger::ClearTrap(CActiveBreakPoint* activeBreakPoint) 132 { 133 ENSURE(activeBreakPoint->m_Script != NULL && activeBreakPoint->m_Pc != NULL); 134 JSTrapHandler prevHandler; 135 jsval prevClosure; 136 JS_ClearTrap(m_pScriptInterface->GetContext(), activeBreakPoint->m_Script, activeBreakPoint->m_Pc, &prevHandler, &prevClosure); 137 activeBreakPoint->m_Script = NULL; 138 activeBreakPoint->m_Pc = NULL; 139 } 140 141 void CThreadDebugger::SetAllNewTraps() 142 { 143 std::list<CBreakPoint>* pBreakPoints = NULL; 144 double breakPointsLockID; 145 breakPointsLockID = m_pDebuggingServer->AquireBreakPointAccess(&pBreakPoints); 146 std::list<CBreakPoint>::iterator itr = pBreakPoints->begin(); 147 while (itr != pBreakPoints->end()) 148 { 149 if (CheckIfMappingPresent((*itr).m_Filename, (*itr).m_UserLine)) 150 { 151 // We must not set a new trap if we already have set a trap for this line of code. 152 // For lines without source code it's possible to have breakpoints set that actually refer to another line 153 // that contains code. This situation is possible if the line containing the sourcecode already has a breakpoint 154 // set and the user sets another one by setting a breakpoint on a line directly above without sourcecode. 155 bool trapAlreadySet = false; 156 { 157 CScopeLock lock(m_Mutex); 158 std::list<CActiveBreakPoint*>::iterator itr1; 159 for ( itr1 = m_ActiveBreakPoints.begin(); itr1 != m_ActiveBreakPoints.end(); itr1++) 160 { 161 if ((*itr1)->m_ActualLine == (*itr).m_UserLine) 162 trapAlreadySet = true; 163 } 164 } 165 166 if (!trapAlreadySet) 167 { 168 CActiveBreakPoint* pActiveBreakPoint = new CActiveBreakPoint((*itr)); 169 SetNewTrap(pActiveBreakPoint, (*itr).m_Filename, (*itr).m_UserLine); 170 { 171 CScopeLock lock(m_Mutex); 172 m_ActiveBreakPoints.push_back(pActiveBreakPoint); 173 } 174 itr = pBreakPoints->erase(itr); 175 continue; 176 } 177 } 178 itr++; 179 } 180 m_pDebuggingServer->ReleaseBreakPointAccess(breakPointsLockID); 181 } 182 183 bool CThreadDebugger::CheckIfMappingPresent(std::string filename, uint line) 184 { 185 bool isPresent = (m_LineToPCMap.end() != m_LineToPCMap.find(filename) && m_LineToPCMap[filename].end() != m_LineToPCMap[filename].find(line)); 186 return isPresent; 187 } 188 189 void CThreadDebugger::SetNewTrap(CActiveBreakPoint* activeBreakPoint, std::string filename, uint line) 190 { 191 ENSURE(activeBreakPoint->m_Script == NULL); // The trap must not be set already! 192 ENSURE(CheckIfMappingPresent(filename, line)); // You have to check if the mapping exists before calling this function! 193 194 jsbytecode* pc = m_LineToPCMap[filename][line].pBytecode; 195 JSScript* script = m_LineToPCMap[filename][line].pScript; 196 activeBreakPoint->m_Script = script; 197 activeBreakPoint->m_Pc = pc; 198 ENSURE(script != NULL && pc != NULL); 199 activeBreakPoint->m_ActualLine = JS_PCToLineNumber(m_pScriptInterface->GetContext(), script, pc); 200 201 JS_SetTrap(m_pScriptInterface->GetContext(), script, pc, TrapHandler_, PRIVATE_TO_JSVAL(this)); 202 } 203 204 205 CThreadDebugger::CThreadDebugger() 206 { 207 m_NextDbgCmd = DBG_CMD_NONE; 208 m_IsInBreak = false; 209 m_pLastBreakFrame = new JSStackFrame*; 210 } 211 212 CThreadDebugger::~CThreadDebugger() 213 { 214 // Clear all Traps and Breakpoints that are marked for removal 215 ClearTrapsToRemove(); 216 217 // Return all breakpoints to the associated CDebuggingServer 218 ReturnActiveBreakPoints(NULL); 219 220 // Remove all the hooks because they store a pointer to this object 221 JS_SetExecuteHook(m_pScriptInterface->GetRuntime(), NULL, NULL); 222 JS_SetCallHook(m_pScriptInterface->GetRuntime(), NULL, NULL); 223 JS_SetNewScriptHook(m_pScriptInterface->GetRuntime(), NULL, NULL); 224 JS_SetDestroyScriptHook(m_pScriptInterface->GetRuntime(), NULL, NULL); 225 226 delete m_pLastBreakFrame; 227 } 228 229 void CThreadDebugger::ReturnActiveBreakPoints(jsbytecode* pBytecode) 230 { 231 CScopeLock lock(m_ActiveBreakpointsMutex); 232 std::list<CActiveBreakPoint*>::iterator itr; 233 itr = m_ActiveBreakPoints.begin(); 234 while (itr != m_ActiveBreakPoints.end()) 235 { 236 // Breakpoints marked for removal should be deleted instead of returned! 237 if ( ((*itr)->m_Pc == pBytecode || pBytecode == NULL) && !(*itr)->m_ToRemove ) 238 { 239 std::list<CBreakPoint>* pBreakPoints; 240 double breakPointsLockID = m_pDebuggingServer->AquireBreakPointAccess(&pBreakPoints); 241 CBreakPoint breakPoint; 242 breakPoint.m_UserLine = (*itr)->m_UserLine; 243 breakPoint.m_Filename = (*itr)->m_Filename; 244 // All active breakpoints should have a trap set 245 ClearTrap((*itr)); 246 pBreakPoints->push_back(breakPoint); 247 delete (*itr); 248 itr=m_ActiveBreakPoints.erase(itr); 249 m_pDebuggingServer->ReleaseBreakPointAccess(breakPointsLockID); 250 } 251 else 252 itr++; 253 } 254 } 255 256 void CThreadDebugger::Initialize(uint id, std::string name, ScriptInterface* pScriptInterface, CDebuggingServer* pDebuggingServer) 257 { 258 ENSURE(id != 0); 259 m_ID = id; 260 m_Name = name; 261 m_pScriptInterface = pScriptInterface; 262 m_pDebuggingServer = pDebuggingServer; 263 JS_SetExecuteHook(m_pScriptInterface->GetRuntime(), CallHook_, (void*)this); 264 JS_SetCallHook(m_pScriptInterface->GetRuntime(), CallHook_, (void*)this); 265 JS_SetNewScriptHook(m_pScriptInterface->GetRuntime(), NewScriptHook_, (void*)this); 266 JS_SetDestroyScriptHook(m_pScriptInterface->GetRuntime(), DestroyScriptHook_, (void*)this); 267 JS_SetThrowHook(m_pScriptInterface->GetRuntime(), ThrowHandler_, (void*)this); 268 269 if (m_pDebuggingServer->GetSettingSimultaneousThreadBreak()) 270 { 271 // Setup a handler to check for break-requests from the DebuggingServer regularly 272 JS_SetInterrupt(m_pScriptInterface->GetRuntime(), CheckForBreakRequestHandler_, (void*)this); 273 } 274 } 275 276 JSTrapStatus CThreadDebugger::StepHandler(JSContext* cx, JSScript* script, jsbytecode* pc, jsval* rval, void* UNUSED(closure)) 277 { 278 // We break in two conditions 279 // 1. We are in the same frame but on a different line 280 // Note: On loops for example, we can go a few lines up again without leaving the current stack frame, so it's not necessarily 281 // a higher line number. 282 // 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) 283 uint line = JS_PCToLineNumber(cx, script, pc); 284 JSStackFrame* iter = NULL; 285 JSStackFrame* pStackFrame; 286 pStackFrame = JS_FrameIterator(m_pScriptInterface->GetContext(), &iter); 287 uint lastBreakLine = GetLastBreakLine() ; 288 jsval val = JSVAL_VOID; 289 if ((*m_pLastBreakFrame == pStackFrame && lastBreakLine != line) || 290 (*m_pLastBreakFrame != pStackFrame && !CurrentFrameIsChildOf(*m_pLastBreakFrame))) 291 return BreakHandler(cx, script, pc, rval, val, BREAK_SRC_INTERRUP); 292 else 293 return JSTRAP_CONTINUE; 294 } 295 296 JSTrapStatus CThreadDebugger::StepIntoHandler(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, void* UNUSED(closure)) 297 { 298 // We break when we are on the same stack frame but not on the same line 299 // or when we are on another stack frame. 300 uint line = JS_PCToLineNumber(cx, script, pc); 301 JSStackFrame* iter = NULL; 302 JSStackFrame* pStackFrame; 303 pStackFrame = JS_FrameIterator(m_pScriptInterface->GetContext(), &iter); 304 uint lastBreakLine = GetLastBreakLine(); 305 306 jsval val = JSVAL_VOID; 307 if ((*m_pLastBreakFrame == pStackFrame && lastBreakLine != line) || *m_pLastBreakFrame != pStackFrame) 308 return BreakHandler(cx, script, pc, rval, val, BREAK_SRC_INTERRUP); 309 else 310 return JSTRAP_CONTINUE; 311 } 312 313 JSTrapStatus CThreadDebugger::StepOutHandler(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, void* UNUSED(closure)) 314 { 315 // We break when we are in a different Frame and m_pLastBreakFrame is not a parent of the current frame 316 // (because we stepped out of the function) 317 JSStackFrame* iter = NULL; 318 JSStackFrame* pStackFrame; 319 pStackFrame = JS_FrameIterator(m_pScriptInterface->GetContext(), &iter); 320 if (pStackFrame != *m_pLastBreakFrame && !CurrentFrameIsChildOf(*m_pLastBreakFrame)) 321 { 322 jsval val = JSVAL_VOID; 323 return BreakHandler(cx, script, pc, rval, val, BREAK_SRC_INTERRUP); 324 } 325 else 326 return JSTRAP_CONTINUE; 327 } 328 329 bool CThreadDebugger::CurrentFrameIsChildOf(JSStackFrame* pParentFrame) 330 { 331 JSStackFrame* iter = NULL; 332 JSStackFrame* fp = JS_FrameIterator(m_pScriptInterface->GetContext(), &iter); 333 // Get the first parent Frame 334 fp = JS_FrameIterator(m_pScriptInterface->GetContext(), &iter); 335 while (fp) 336 { 337 if (fp == pParentFrame) 338 return true; 339 fp = JS_FrameIterator(m_pScriptInterface->GetContext(), &iter); 340 } 341 return false; 342 } 343 344 JSTrapStatus CThreadDebugger::CheckForBreakRequestHandler(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, void* UNUSED(closure)) 345 { 346 jsval val = JSVAL_VOID; 347 if (m_pDebuggingServer->GetBreakRequestedByThread() || m_pDebuggingServer->GetBreakRequestedByUser()) 348 return BreakHandler(cx, script, pc, rval, val, BREAK_SRC_INTERRUP); 349 else 350 return JSTRAP_CONTINUE; 351 } 352 353 JSTrapStatus CThreadDebugger::TrapHandler(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval, jsval UNUSED(closure)) 354 { 355 jsval val = JSVAL_NULL; 356 return BreakHandler(cx, script, pc, rval, val, BREAK_SRC_TRAP); 357 } 358 359 JSTrapStatus CThreadDebugger::ThrowHandler(JSContext *cx, JSScript *script, jsbytecode *pc, jsval *rval) 360 { 361 jsval jsexception; 362 JS_GetPendingException(cx, &jsexception); 363 if (JSVAL_IS_STRING(jsexception)) 364 { 365 std::string str(JS_EncodeString(cx, JSVAL_TO_STRING(jsexception))); 366 if (str == "Breakpoint" || m_pDebuggingServer->GetSettingBreakOnException()) 367 { 368 if (str == "Breakpoint") 369 JS_ClearPendingException(cx); 370 jsval val = JSVAL_NULL; 371 return BreakHandler(cx, script, pc, rval, val, BREAK_SRC_EXCEPTION); 372 } 373 } 374 return JSTRAP_CONTINUE; 375 } 376 377 JSTrapStatus CThreadDebugger::BreakHandler(JSContext* cx, JSScript* script, jsbytecode* pc, jsval* UNUSED(rval), jsval UNUSED(closure), BREAK_SRC breakSrc) 378 { 379 uint line = JS_PCToLineNumber(cx, script, pc); 380 std::string filename(JS_GetScriptFilename(cx, script)); 381 382 SetIsInBreak(true); 383 SaveCallstack(); 384 SetLastBreakLine(line); 385 SetBreakFileName(filename); 386 *m_pLastBreakFrame = NULL; 387 388 if (breakSrc == BREAK_SRC_INTERRUP) 389 { 390 JS_ClearInterrupt(m_pScriptInterface->GetRuntime(), NULL, NULL); 391 JS_SetSingleStepMode(cx, script, false); 392 } 393 394 if (m_pDebuggingServer->GetSettingSimultaneousThreadBreak()) 395 { 396 m_pDebuggingServer->SetBreakRequestedByThread(true); 397 } 398 399 // Wait until the user continues the execution 400 while (1) 401 { 402 DBGCMD nextDbgCmd = GetNextDbgCmd(); 403 404 while (!m_StackInfoRequests.empty()) 405 { 406 StackInfoRequest request = m_StackInfoRequests.front(); 407 SaveStackFrameData(request.requestType, request.nestingLevel); 408 SDL_SemPost(request.semaphore); 409 m_StackInfoRequests.pop(); 410 } 411 412 if (nextDbgCmd == DBG_CMD_NONE) 413 { 414 // Wait a while before checking for new m_NextDbgCmd again. 415 // We don't want this loop to take 100% of a CPU core for each thread that is in break mode. 416 // On the other hande we don't want the debugger to become unresponsive. 417 SDL_Delay(100); 418 } 419 else if (nextDbgCmd == DBG_CMD_SINGLESTEP || nextDbgCmd == DBG_CMD_STEPINTO || nextDbgCmd == DBG_CMD_STEPOUT) 420 { 421 JSStackFrame* iter = NULL; 422 *m_pLastBreakFrame = JS_FrameIterator(m_pScriptInterface->GetContext(), &iter); 423 424 if (!JS_SetSingleStepMode(cx, script, true)) 425 LOGERROR(L"JS_SetSingleStepMode returned false!"); // TODO: When can this happen? 426 else 427 { 428 if (nextDbgCmd == DBG_CMD_SINGLESTEP) 429 { 430 JS_SetInterrupt(m_pScriptInterface->GetRuntime(), StepHandler_, this); 431 break; 432 } 433 else if (nextDbgCmd == DBG_CMD_STEPINTO) 434 { 435 JS_SetInterrupt(m_pScriptInterface->GetRuntime(), StepIntoHandler_, this); 436 break; 437 } 438 else if (nextDbgCmd == DBG_CMD_STEPOUT) 439 { 440 JS_SetInterrupt(m_pScriptInterface->GetRuntime(), StepOutHandler_, this); 441 break; 442 } 443 } 444 } 445 else if (nextDbgCmd == DBG_CMD_CONTINUE) 446 { 447 if (!JS_SetSingleStepMode(cx, script, true)) 448 LOGERROR(L"JS_SetSingleStepMode returned false!"); // TODO: When can this happen? 449 else 450 { 451 // Setup a handler to check for break-requests from the DebuggingServer regularly 452 JS_SetInterrupt(m_pScriptInterface->GetRuntime(), CheckForBreakRequestHandler_, this); 453 } 454 break; 455 } 456 else 457 debug_warn("Invalid DBGCMD found in CThreadDebugger::BreakHandler!"); 458 } 459 ClearTrapsToRemove(); 460 SetAllNewTraps(); 461 SetNextDbgCmd(DBG_CMD_NONE); 462 SetIsInBreak(false); 463 SetBreakFileName(""); 464 465 // All saved stack data becomes invalid 466 { 467 CScopeLock lock(m_Mutex); 468 m_StackFrameData.clear(); 469 } 470 471 return JSTRAP_CONTINUE; 472 } 473 474 void CThreadDebugger::NewScriptHook(JSContext* cx, const char* filename, unsigned lineno, JSScript* script, JSFunction* UNUSED(fun), void* UNUSED(callerdata)) 475 { 476 uint scriptExtent = JS_GetScriptLineExtent (cx, script); 477 std::string stringFileName(filename); 478 if (stringFileName == "") 479 return; 480 481 for (uint line = lineno; line < scriptExtent + lineno; ++line) 482 { 483 // If we already have a mapping for this line, we check if the current scipt is more deeply nested. 484 // If it isn't more deeply nested, we don't overwrite the previous mapping 485 // The most deeply nested script is always the one that must be used! 486 uint firstLine = 0; 487 uint lastLine = 0; 488 jsbytecode* oldPC = NULL; 489 if (CheckIfMappingPresent(stringFileName, line)) 490 { 491 firstLine = m_LineToPCMap[stringFileName][line].firstLineInFunction; 492 lastLine = m_LineToPCMap[stringFileName][line].lastLineInFunction; 493 494 // If an entry nested equally is present too, we must overwrite it. 495 // The same script(function) can trigger a NewScriptHook multiple times without DestroyScriptHooks between these 496 // calls. In this case the old script becomes invalid. 497 if (lineno < firstLine || scriptExtent + lineno > lastLine) 498 continue; 499 else 500 oldPC = m_LineToPCMap[stringFileName][line].pBytecode; 501 502 } 503 jsbytecode* pc = JS_LineNumberToPC (cx, script, line); 504 m_LineToPCMap[stringFileName][line].pBytecode = pc; 505 m_LineToPCMap[stringFileName][line].pScript = script; 506 m_LineToPCMap[stringFileName][line].firstLineInFunction = lineno; 507 m_LineToPCMap[stringFileName][line].lastLineInFunction = lineno + scriptExtent; 508 509 // If we are replacing a script, the associated traps become invalid 510 if (lineno == firstLine && scriptExtent + lineno == lastLine) 511 { 512 ReturnActiveBreakPoints(oldPC); 513 SetAllNewTraps(); 514 } 515 } 516 } 517 518 void CThreadDebugger::DestroyScriptHook(JSContext* cx, JSScript* script) 519 { 520 uint scriptExtent = JS_GetScriptLineExtent (cx, script); 521 uint baseLine = JS_GetScriptBaseLineNumber(cx, script); 522 523 char* pStr = NULL; 524 pStr = (char*)JS_GetScriptFilename(cx, script); 525 if (pStr != NULL) 526 { 527 std::string fileName(pStr); 528 529 for (uint line = baseLine; line < scriptExtent + baseLine; ++line) 530 { 531 if (CheckIfMappingPresent(fileName, line)) 532 { 533 if (m_LineToPCMap[fileName][line].pScript == script) 534 { 535 ReturnActiveBreakPoints(m_LineToPCMap[fileName][line].pBytecode); 536 m_LineToPCMap[fileName].erase(line); 537 if (m_LineToPCMap[fileName].empty()) 538 m_LineToPCMap.erase(fileName); 539 } 540 } 541 } 542 } 543 } 544 545 void CThreadDebugger::ExecuteHook(JSContext* UNUSED(cx), const char* UNUSED(filename), unsigned UNUSED(lineno), JSScript* UNUSED(script), JSFunction* UNUSED(fun), void* UNUSED(callerdata)) 546 { 547 // Search all breakpoints that have no trap set yet 548 { 549 PROFILE2("ExecuteHook"); 550 SetAllNewTraps(); 551 } 552 return; 553 } 554 555 bool CThreadDebugger::ToggleBreakPoint(std::string filename, uint userLine) 556 { 557 CScopeLock lock(m_Mutex); 558 std::list<CActiveBreakPoint*>::iterator itr; 559 for (itr = m_ActiveBreakPoints.begin(); itr != m_ActiveBreakPoints.end(); itr++) 560 { 561 if ((*itr)->m_UserLine == userLine && (*itr)->m_Filename == filename) 562 { 563 (*itr)->m_ToRemove = !(*itr)->m_ToRemove; 564 return true; 565 } 566 } 567 return false; 568 } 569 570 void CThreadDebugger::GetCallstack(std::stringstream& response) 571 { 572 CScopeLock lock(m_Mutex); 573 response << m_Callstack; 574 } 575 576 void CThreadDebugger::SaveCallstack() 577 { 578 ENSURE(GetIsInBreak()); 579 580 CScopeLock lock(m_Mutex); 581 582 JSStackFrame *fp; 583 JSStackFrame *iter = 0; 584 std::string functionName; 585 jsint counter = 0; 586 587 JSObject* jsArray; 588 jsArray = JS_NewArrayObject(m_pScriptInterface->GetContext(), 0, 0); 589 JSString* functionID; 590 591 fp = JS_FrameIterator(m_pScriptInterface->GetContext(), &iter); 592 593 while (fp) 594 { 595 JSFunction* fun = 0; 596 fun = JS_GetFrameFunction(m_pScriptInterface->GetContext(), fp); 597 if (NULL == fun) 598 functionID = JS_NewStringCopyZ(m_pScriptInterface->GetContext(), "null"); 599 else 600 { 601 functionID = JS_GetFunctionId(fun); 602 if (NULL == functionID) 603 functionID = JS_NewStringCopyZ(m_pScriptInterface->GetContext(), "anonymous"); 604 } 605 606 JSBool ret = JS_DefineElement(m_pScriptInterface->GetContext(), jsArray, counter, STRING_TO_JSVAL(functionID), NULL, NULL, 0); 607 ENSURE(ret); 608 fp = JS_FrameIterator(m_pScriptInterface->GetContext(), &iter); 609 counter++; 610 } 611 612 m_Callstack = ""; 613 m_Callstack = m_pScriptInterface->StringifyJSON(OBJECT_TO_JSVAL(jsArray), false).c_str(); 614 } 615 616 void CThreadDebugger::GetStackFrameData(std::stringstream& response, uint nestingLevel, STACK_INFO stackInfoKind) 617 { 618 // If the data is not yet cached, request it and wait until it's ready. 619 bool dataCached = false; 620 { 621 CScopeLock lock(m_Mutex); 622 dataCached = (!m_StackFrameData.empty() && m_StackFrameData[stackInfoKind].end() != m_StackFrameData[stackInfoKind].find(nestingLevel)); 623 } 624 625 if (!dataCached) 626 { 627 SDL_sem* semaphore = SDL_CreateSemaphore(0); 628 AddStackInfoRequest(stackInfoKind, nestingLevel, semaphore); 629 SDL_SemWait(semaphore); 630 SDL_DestroySemaphore(semaphore); 631 } 632 633 CScopeLock lock(m_Mutex); 634 { 635 response.str(""); 636 response << m_StackFrameData[stackInfoKind][nestingLevel]; 637 } 638 } 639 640 void CThreadDebugger::AddStackInfoRequest(STACK_INFO requestType, uint nestingLevel, SDL_sem* semaphore) 641 { 642 StackInfoRequest request; 643 request.requestType = requestType; 644 request.semaphore = semaphore; 645 request.nestingLevel = nestingLevel; 646 m_StackInfoRequests.push(request); 647 } 648 649 void CThreadDebugger::SaveStackFrameData(STACK_INFO stackInfo, uint nestingLevel) 650 { 651 ENSURE(GetIsInBreak()); 652 653 CScopeLock lock(m_Mutex); 654 JSStackFrame *fp; 655 JSStackFrame *iter = 0; 656 uint counter = 0; 657 jsval val; 658 659 if (stackInfo == STACK_INFO_GLOBALOBJECT) 660 { 661 JSObject* obj; 662 obj = JS_GetGlobalForScopeChain(m_pScriptInterface->GetContext()); 663 m_StackFrameData[stackInfo][nestingLevel] = StringifyCyclicJSON(OBJECT_TO_JSVAL(obj), false); 664 } 665 else 666 { 667 fp = JS_FrameIterator(m_pScriptInterface->GetContext(), &iter); 668 while (fp) 669 { 670 if (counter == nestingLevel) 671 { 672 if (stackInfo == STACK_INFO_LOCALS) 673 { 674 JSObject* obj; 675 obj = JS_GetFrameCallObject(m_pScriptInterface->GetContext(), fp); 676 //obj = JS_GetFrameScopeChain(m_pScriptInterface->GetContext(), fp); 677 m_StackFrameData[stackInfo][nestingLevel] = StringifyCyclicJSON(OBJECT_TO_JSVAL(obj), false); 678 } 679 else if (stackInfo == STACK_INFO_THIS) 680 { 681 if (JS_GetFrameThis(m_pScriptInterface->GetContext(), fp, &val)) 682 { 683 m_StackFrameData[stackInfo][nestingLevel] = StringifyCyclicJSON(val, false); 684 } 685 else 686 m_StackFrameData[stackInfo][nestingLevel] = ""; 687 } 688 } 689 690 counter++; 691 fp = JS_FrameIterator(m_pScriptInterface->GetContext(), &iter); 692 } 693 } 694 } 695 696 697 /* 698 * TODO: This is very hacky and ugly and should be improved. 699 * It replaces cyclic references with a notification that cyclic references are not supported. 700 * It would be better to create a format that supports cyclic references and allows the UI to display them correctly. 701 * Unfortunately this seems to require writing (or embedding) a new serializer to JSON or something similar. 702 * 703 * Some things about the implementation which aren't optimal: 704 * 1. It uses globabl variables (they are limited to a namespace though) 705 * 2. It has to work around a bug in Spidermonkey. 706 * 3. It copies code from CScriptInterface. I did this to separate it cleanly because the debugger should not affect 707 * the rest of the game and because this part of code should be replaced anyway in the future. 708 */ 709 710 namespace CyclicRefWorkaround 711 { 712 std::set<JSObject*> g_ProcessedObjects; 713 jsval g_LastKey; 714 jsval g_LastValue; 715 bool g_RecursionDetectedInPrevReplacer = false; 716 uint g_countSameKeys = 0; 717 718 struct Stringifier 719 { 720 static JSBool callback(const jschar* buf, uint32 len, void* data) 721 { 722 utf16string str(buf, buf+len); 723 std::wstring strw(str.begin(), str.end()); 724 725 Status err; // ignore Unicode errors 726 static_cast<Stringifier*>(data)->stream << utf8_from_wstring(strw, &err); 727 return JS_TRUE; 728 } 729 730 std::stringstream stream; 731 }; 732 733 JSBool replacer(JSContext* cx, uintN UNUSED(argc), jsval* vp) 734 { 735 jsval value = JS_ARGV(cx, vp)[1]; 736 jsval key = JS_ARGV(cx, vp)[0]; 737 if (g_LastKey == key) 738 g_countSameKeys++; 739 else 740 g_countSameKeys = 0; 741 742 if (JSVAL_IS_OBJECT(value)) 743 { 744 // Work around a spidermonkey bug that causes replacer to be called twice with the same key: 745 // https://bugzilla.mozilla.org/show_bug.cgi?id=636079 746 // TODO: Remove the workaround as soon as we upgrade to a newer version of Spidermonkey. 747 748 if (g_ProcessedObjects.end() == g_ProcessedObjects.find(JSVAL_TO_OBJECT(value))) 749 { 750 g_ProcessedObjects.insert(JSVAL_TO_OBJECT(value)); 751 } 752 else if (g_countSameKeys %2 == 0 || g_RecursionDetectedInPrevReplacer) 753 { 754 g_RecursionDetectedInPrevReplacer = true; 755 jsval ret = STRING_TO_JSVAL(JS_NewStringCopyZ(cx, "Debugger: object removed from output because of cyclic reference.")); 756 JS_SET_RVAL(cx, vp, ret); 757 g_LastKey = key; 758 g_LastValue = value; 759 return JS_TRUE; 760 } 761 } 762 g_LastKey = key; 763 g_LastValue = value; 764 g_RecursionDetectedInPrevReplacer = false; 765 JS_SET_RVAL(cx, vp, JS_ARGV(cx, vp)[1]); 766 return JS_TRUE; 767 } 768 } 769 770 std::string CThreadDebugger::StringifyCyclicJSON(jsval obj, bool indent) 771 { 772 CyclicRefWorkaround::Stringifier str; 773 CyclicRefWorkaround::g_ProcessedObjects.clear(); 774 CyclicRefWorkaround::g_LastKey = JSVAL_VOID; 775 776 JSObject* pGlob = JSVAL_TO_OBJECT(m_pScriptInterface->GetGlobalObject()); 777 JSFunction* fun = JS_DefineFunction(m_pScriptInterface->GetContext(), pGlob, "replacer", CyclicRefWorkaround::replacer, 0, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT); 778 JSObject* replacer = JS_GetFunctionObject(fun); 779 if (!JS_Stringify(m_pScriptInterface->GetContext(), &obj, replacer, indent ? INT_TO_JSVAL(2) : JSVAL_VOID, &CyclicRefWorkaround::Stringifier::callback, &str)) 780 { 781 LOGERROR(L"StringifyJSON failed"); 782 jsval exec; 783 jsval execString; 784 if (JS_GetPendingException(m_pScriptInterface->GetContext(), &exec)) 785 { 786 if (JSVAL_IS_OBJECT(exec)) 787 { 788 JS_GetProperty(m_pScriptInterface->GetContext(), JSVAL_TO_OBJECT(exec), "message", &execString); 789 790 if (JSVAL_IS_STRING(execString)) 791 { 792 std::string strExec = JS_EncodeString(m_pScriptInterface->GetContext(), JSVAL_TO_STRING(execString)); 793 LOGERROR(L"Error: %hs", strExec.c_str()); 794 } 795 } 796 797 } 798 JS_ClearPendingException(m_pScriptInterface->GetContext()); 799 return ""; 800 } 801 802 return str.stream.str(); 803 } 804 805 806 bool CThreadDebugger::CompareScriptInterfacePtr(ScriptInterface* pScriptInterface) 807 { 808 return (pScriptInterface == m_pScriptInterface); 809 } 810 811 812 std::string CThreadDebugger::GetBreakFileName() 813 { 814 CScopeLock lock(m_Mutex); 815 return m_BreakFileName; 816 } 817 818 void CThreadDebugger::SetBreakFileName(std::string breakFileName) 819 { 820 CScopeLock lock(m_Mutex); 821 m_BreakFileName = breakFileName; 822 } 823 824 uint CThreadDebugger::GetLastBreakLine() 825 { 826 CScopeLock lock(m_Mutex); 827 return m_LastBreakLine; 828 } 829 830 void CThreadDebugger::SetLastBreakLine(uint breakLine) 831 { 832 CScopeLock lock(m_Mutex); 833 m_LastBreakLine = breakLine; 834 } 835 836 bool CThreadDebugger::GetIsInBreak() 837 { 838 CScopeLock lock(m_IsInBreakMutex); 839 return m_IsInBreak; 840 } 841 842 void CThreadDebugger::SetIsInBreak(bool isInBreak) 843 { 844 CScopeLock lock(m_IsInBreakMutex); 845 m_IsInBreak = isInBreak; 846 } 847 848 void CThreadDebugger::SetNextDbgCmd(DBGCMD dbgCmd) 849 { 850 CScopeLock lock(m_NextDbgCmdMutex); 851 m_NextDbgCmd = dbgCmd; 852 } 853 854 DBGCMD CThreadDebugger::GetNextDbgCmd() 855 { 856 CScopeLock lock(m_NextDbgCmdMutex); 857 return m_NextDbgCmd; 858 } 859 860 std::string CThreadDebugger::GetName() 861 { 862 CScopeLock lock(m_Mutex); 863 return m_Name; 864 } 865 866 uint CThreadDebugger::GetID() 867 { 868 CScopeLock lock(m_Mutex); 869 return m_ID; 870 } 871 -
source/scriptinterface/DebuggingServer.cpp
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" 24 #include "scripting/JSConversions.h" 25 26 CDebuggingServer* g_DebuggingServer = NULL; 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(""); 37 std::stringstream stream; 38 uint nbrCallstacksWritten = 0; 39 std::list<CThreadDebugger*>::iterator itr; 40 if (m_ThreadDebuggers.size() > 0) 41 { 42 response << "["; 43 for (itr = m_ThreadDebuggers.begin(); itr != m_ThreadDebuggers.end(); itr++) 44 { 45 if ((*itr)->GetIsInBreak()) 46 { 47 stream.str(""); 48 std::string str = stream.str(); 49 (*itr)->GetCallstack(stream); 50 str = stream.str(); 51 if (stream.str() != "") 52 { 53 if (nbrCallstacksWritten != 0) 54 response << ","; 55 response << "{" << "\"ThreadDebuggerID\" : " << (*itr)->GetID() << ", \"CallStack\" : " << stream.str() << "}"; 56 nbrCallstacksWritten++; 57 } 58 59 } 60 } 61 response << "]"; 62 } 63 } 64 65 void CDebuggingServer::GetStackFrameData(std::stringstream& response, uint nestingLevel, uint threadDebuggerID, STACK_INFO stackInfoKind) 66 { 67 CScopeLock lock(m_Mutex); 68 response.str(""); 69 std::stringstream stream; 70 std::list<CThreadDebugger*>::iterator itr; 71 for (itr = m_ThreadDebuggers.begin(); itr != m_ThreadDebuggers.end(); itr++) 72 { 73 if ( (*itr)->GetID() == threadDebuggerID && (*itr)->GetIsInBreak()) 74 { 75 (*itr)->GetStackFrameData(stream, nestingLevel, stackInfoKind); 76 if (stream.str() != "") 77 { 78 response << stream.str(); 79 } 80 } 81 } 82 } 83 84 85 CDebuggingServer::CDebuggingServer() : 86 m_MgContext(NULL) 87 { 88 m_BreakPointsSem = SDL_CreateSemaphore(0); 89 ENSURE(m_BreakPointsSem); 90 SDL_SemPost(m_BreakPointsSem); 91 m_LastThreadDebuggerID = 0; // Next will be 1, 0 is reserved 92 m_BreakRequestedByThread = false; 93 m_BreakRequestedByUser = false; 94 m_SettingSimultaneousThreadBreak = true; 95 m_SettingBreakOnException = true; 96 97 EnableHTTP(); 98 LOGWARNING(L"Javascript debugging webserver enabled."); 99 } 100 101 CDebuggingServer::~CDebuggingServer() 102 { 103 SDL_DestroySemaphore(m_BreakPointsSem); 104 if (m_MgContext) 105 { 106 mg_stop(m_MgContext); 107 m_MgContext = NULL; 108 } 109 } 110 111 bool CDebuggingServer::SetNextDbgCmd(uint threadDebuggerID, DBGCMD dbgCmd) 112 { 113 CScopeLock lock(m_Mutex); 114 std::list<CThreadDebugger*>::iterator itr; 115 for (itr = m_ThreadDebuggers.begin(); itr != m_ThreadDebuggers.end(); itr++) 116 { 117 if ( (*itr)->GetID() == threadDebuggerID || threadDebuggerID == 0) 118 { 119 if (DBG_CMD_NONE == (*itr)->GetNextDbgCmd() && (*itr)->GetIsInBreak()) 120 { 121 SetBreakRequestedByThread(false); 122 SetBreakRequestedByUser(false); 123 (*itr)->SetNextDbgCmd(dbgCmd); 124 } 125 } 126 } 127 return true; 128 } 129 130 void CDebuggingServer::SetBreakRequestedByThread(bool Enabled) 131 { 132 CScopeLock lock(m_Mutex1); 133 m_BreakRequestedByThread = Enabled; 134 } 135 136 bool CDebuggingServer::GetBreakRequestedByThread() 137 { 138 CScopeLock lock(m_Mutex1); 139 return m_BreakRequestedByThread; 140 } 141 142 void CDebuggingServer::SetBreakRequestedByUser(bool Enabled) 143 { 144 CScopeLock lock(m_Mutex1); 145 m_BreakRequestedByUser = Enabled; 146 } 147 148 bool CDebuggingServer::GetBreakRequestedByUser() 149 { 150 CScopeLock lock(m_Mutex1); 151 return m_BreakRequestedByUser; 152 } 153 154 void CDebuggingServer::SetSettingBreakOnException(bool Enabled) 155 { 156 CScopeLock lock(m_Mutex); 157 m_SettingBreakOnException = Enabled; 158 } 159 160 void CDebuggingServer::SetSettingSimultaneousThreadBreak(bool Enabled) 161 { 162 CScopeLock lock(m_Mutex1); 163 m_SettingSimultaneousThreadBreak = Enabled; 164 } 165 166 bool CDebuggingServer::GetSettingBreakOnException() 167 { 168 CScopeLock lock(m_Mutex); 169 return m_SettingBreakOnException; 170 } 171 172 bool CDebuggingServer::GetSettingSimultaneousThreadBreak() 173 { 174 CScopeLock lock(m_Mutex1); 175 return m_SettingSimultaneousThreadBreak; 176 } 177 178 179 static Status AddFileResponse(const VfsPath& pathname, const FileInfo& UNUSED(fileInfo), const uintptr_t cbData) 180 { 181 std::vector<std::string>& templates = *(std::vector<std::string>*)cbData; 182 std::wstring str(pathname.string()); 183 templates.push_back(std::string(str.begin(), str.end())); 184 return INFO::OK; 185 } 186 187 void CDebuggingServer::EnumVfsJSFiles(std::stringstream& response) 188 { 189 VfsPath path = L""; 190 VfsPaths pathnames; 191 response.str(""); 192 193 std::vector<std::string> templates; 194 vfs::ForEachFile(g_VFS, "", AddFileResponse, (uintptr_t)&templates, L"*.js", vfs::DIR_RECURSIVE); 195 196 std::vector<std::string>::iterator itr; 197 response << "["; 198 for (itr = templates.begin(); itr != templates.end(); itr++) 199 { 200 if (itr != templates.begin()) 201 response << ","; 202 response << "\"" << *itr << "\""; 203 } 204 response << "]"; 205 } 206 207 void CDebuggingServer::GetFile(std::string filename, std::stringstream& response) 208 { 209 CVFSFile file; 210 if (file.Load(g_VFS, filename) != PSRETURN_OK) 211 { 212 response << "Failed to load the file contents"; 213 return; 214 } 215 216 std::string code = file.DecodeUTF8(); // assume it's UTF-8 217 response << code; 218 } 219 220 221 static void* MgDebuggingServerCallback_(mg_event event, struct mg_connection *conn, const struct mg_request_info *request_info) 222 { 223 CDebuggingServer* debuggingServer = (CDebuggingServer*)request_info->user_data; 224 ENSURE(debuggingServer); 225 return debuggingServer->MgDebuggingServerCallback(event, conn, request_info); 226 } 227 228 void* CDebuggingServer::MgDebuggingServerCallback(mg_event event, struct mg_connection *conn, const struct mg_request_info *request_info) 229 { 230 void* handled = (void*)""; // arbitrary non-NULL pointer to indicate successful handling 231 232 const char* header200 = 233 "HTTP/1.1 200 OK\r\n" 234 "Access-Control-Allow-Origin: *\r\n" // TODO: not great for security 235 "Content-Type: text/plain; charset=utf-8\r\n\r\n"; 236 237 const char* header404 = 238 "HTTP/1.1 404 Not Found\r\n" 239 "Content-Type: text/plain; charset=utf-8\r\n\r\n" 240 "Unrecognised URI"; 241 242 switch (event) 243 { 244 case MG_NEW_REQUEST: 245 { 246 std::stringstream stream; 247 std::string uri = request_info->uri; 248 249 if (uri == "/GetThreadDebuggerStatus") 250 { 251 GetThreadDebuggerStatus(stream); 252 } 253 else if (uri == "/EnumVfsJSFiles") 254 { 255 EnumVfsJSFiles(stream); 256 } 257 else if (uri == "/GetAllCallstacks") 258 { 259 GetAllCallstacks(stream); 260 } 261 else if (uri == "/Continue") 262 { 263 uint threadDebuggerID; 264 if (!GetWebArgs(conn, request_info, "threadDebuggerID", threadDebuggerID)) 265 return handled; 266 // TODO: handle the return value 267 SetNextDbgCmd(threadDebuggerID, DBG_CMD_CONTINUE); 268 } 269 else if (uri == "/Break") 270 { 271 SetBreakRequestedByUser(true); 272 } 273 else if (uri == "/SetSettingSimultaneousThreadBreak") 274 { 275 std::string strEnabled; 276 bool bEnabled = false; 277 if (!GetWebArgs(conn, request_info, "enabled", strEnabled)) 278 return handled; 279 // TODO: handle the return value 280 if (strEnabled == "true") 281 bEnabled = true; 282 else if (strEnabled == "false") 283 bEnabled = false; 284 else 285 return handled; // TODO: return an error state 286 SetSettingSimultaneousThreadBreak(bEnabled); 287 } 288 else if (uri == "/GetSettingSimultaneousThreadBreak") 289 { 290 stream << "{ \"Enabled\" : " << (GetSettingSimultaneousThreadBreak() ? "true" : "false") << " } "; 291 } 292 else if (uri == "/SetSettingBreakOnException") 293 { 294 std::string strEnabled; 295 bool bEnabled = false; 296 if (!GetWebArgs(conn, request_info, "enabled", strEnabled)) 297 return handled; 298 // TODO: handle the return value 299 if (strEnabled == "true") 300 bEnabled = true; 301 else if (strEnabled == "false") 302 bEnabled = false; 303 else 304 return handled; // TODO: return an error state 305 SetSettingBreakOnException(bEnabled); 306 } 307 else if (uri == "/GetSettingBreakOnException") 308 { 309 stream << "{ \"Enabled\" : " << (GetSettingBreakOnException() ? "true" : "false") << " } "; 310 } 311 else if (uri == "/Step") 312 { 313 uint threadDebuggerID; 314 if (!GetWebArgs(conn, request_info, "threadDebuggerID", threadDebuggerID)) 315 return handled; 316 // TODO: handle the return value 317 SetNextDbgCmd(threadDebuggerID, DBG_CMD_SINGLESTEP); 318 } 319 else if (uri == "/StepInto") 320 { 321 uint threadDebuggerID; 322 if (!GetWebArgs(conn, request_info, "threadDebuggerID", threadDebuggerID)) 323 return handled; 324 // TODO: handle the return value 325 SetNextDbgCmd(threadDebuggerID, DBG_CMD_STEPINTO); 326 } 327 else if (uri == "/StepOut") 328 { 329 uint threadDebuggerID; 330 if (!GetWebArgs(conn, request_info, "threadDebuggerID", threadDebuggerID)) 331 return handled; 332 // TODO: handle the return value 333 SetNextDbgCmd(threadDebuggerID, DBG_CMD_STEPOUT); 334 } 335 else if (uri == "/GetStackFrame") 336 { 337 uint nestingLevel; 338 uint threadDebuggerID; 339 if (!GetWebArgs(conn, request_info, "nestingLevel", nestingLevel) || 340 !GetWebArgs(conn, request_info, "threadDebuggerID", threadDebuggerID)) 341 { 342 return handled; 343 } 344 GetStackFrameData(stream, nestingLevel, threadDebuggerID, STACK_INFO_LOCALS); 345 } 346 else if (uri == "/GetStackFrameThis") 347 { 348 uint nestingLevel; 349 uint threadDebuggerID; 350 if (!GetWebArgs(conn, request_info, "nestingLevel", nestingLevel) || 351 !GetWebArgs(conn, request_info, "threadDebuggerID", threadDebuggerID)) 352 { 353 return handled; 354 } 355 GetStackFrameData(stream, nestingLevel, threadDebuggerID, STACK_INFO_THIS); 356 } 357 else if (uri == "/GetCurrentGlobalObject") 358 { 359 uint threadDebuggerID; 360 if (!GetWebArgs(conn, request_info, "threadDebuggerID", threadDebuggerID)) 361 { 362 return handled; 363 } 364 GetStackFrameData(stream, 0, threadDebuggerID, STACK_INFO_GLOBALOBJECT); 365 } 366 else if (uri == "/ToggleBreakpoint") 367 { 368 std::string filename; 369 uint line; 370 if (!GetWebArgs(conn, request_info, "filename", filename) || 371 !GetWebArgs(conn, request_info, "line", line)) 372 { 373 return handled; 374 } 375 ToggleBreakPoint(filename, line); 376 } 377 else if (uri == "/GetFile") 378 { 379 std::string filename; 380 if (!GetWebArgs(conn, request_info, "filename", filename)) 381 return handled; 382 GetFile(filename, stream); 383 } 384 else 385 { 386 mg_printf(conn, "%s", header404); 387 return handled; 388 } 389 390 mg_printf(conn, "%s", header200); 391 std::string str = stream.str(); 392 mg_write(conn, str.c_str(), str.length()); 393 return handled; 394 } 395 396 case MG_HTTP_ERROR: 397 return NULL; 398 399 case MG_EVENT_LOG: 400 // Called by Mongoose's cry() 401 LOGERROR(L"Mongoose error: %hs", request_info->log_message); 402 return NULL; 403 404 case MG_INIT_SSL: 405 return NULL; 406 407 default: 408 debug_warn(L"Invalid Mongoose event type"); 409 return NULL; 410 } 411 }; 412 413 void CDebuggingServer::EnableHTTP() 414 { 415 // Ignore multiple enablings 416 if (m_MgContext) 417 return; 418 419 const char *options[] = { 420 "listening_ports", "127.0.0.1:9000", // bind to localhost for security 421 "num_threads", "6", // enough for the browser's parallel connection limit 422 NULL 423 }; 424 m_MgContext = mg_start(MgDebuggingServerCallback_, this, options); 425 ENSURE(m_MgContext); 426 } 427 428 bool CDebuggingServer::GetWebArgs(struct mg_connection *conn, const struct mg_request_info* request_info, std::string argName, uint& arg) 429 { 430 if (!request_info->query_string) 431 { 432 mg_printf(conn, "%s (no query string)", header400); 433 return false; 434 } 435 436 char buf[256]; 437 438 int len = mg_get_var(request_info->query_string, strlen(request_info->query_string), argName.c_str(), buf, ARRAY_SIZE(buf)); 439 if (len < 0) 440 { 441 mg_printf(conn, "%s (no '%s')", header400, argName.c_str()); 442 return false; 443 } 444 arg = atoi(buf); 445 return true; 446 } 447 448 bool CDebuggingServer::GetWebArgs(struct mg_connection *conn, const struct mg_request_info* request_info, std::string argName, std::string& arg) 449 { 450 if (!request_info->query_string) 451 { 452 mg_printf(conn, "%s (no query string)", header400); 453 return false; 454 } 455 456 char buf[256]; 457 int len = mg_get_var(request_info->query_string, strlen(request_info->query_string), argName.c_str(), buf, ARRAY_SIZE(buf)); 458 if (len < 0) 459 { 460 mg_printf(conn, "%s (no '%s')", header400, argName.c_str()); 461 return false; 462 } 463 arg = buf; 464 return true; 465 } 466 467 void CDebuggingServer::RegisterScriptinterface(std::string name, ScriptInterface* pScriptInterface) 468 { 469 CScopeLock lock(m_Mutex); 470 CThreadDebugger* pThreadDebugger = new CThreadDebugger; 471 // ThreadID 0 is reserved 472 pThreadDebugger->Initialize(++m_LastThreadDebuggerID, name, pScriptInterface, this); 473 m_ThreadDebuggers.push_back(pThreadDebugger); 474 } 475 476 void CDebuggingServer::UnRegisterScriptinterface(ScriptInterface* pScriptInterface) 477 { 478 CScopeLock lock(m_Mutex); 479 std::list<CThreadDebugger*>::iterator itr; 480 for (itr = m_ThreadDebuggers.begin(); itr != m_ThreadDebuggers.end(); itr++) 481 { 482 if ((*itr)->CompareScriptInterfacePtr(pScriptInterface)) 483 { 484 delete (*itr); 485 m_ThreadDebuggers.erase(itr); 486 break; 487 } 488 } 489 } 490 491 492 void CDebuggingServer::GetThreadDebuggerStatus(std::stringstream& response) 493 { 494 CScopeLock lock(m_Mutex); 495 response.str(""); 496 std::list<CThreadDebugger*>::iterator itr; 497 498 response << "["; 499 for (itr = m_ThreadDebuggers.begin(); itr != m_ThreadDebuggers.end(); itr++) 500 { 501 if (itr == m_ThreadDebuggers.begin()) 502 response << "{ "; 503 else 504 response << ",{ "; 505 506 response << "\"ThreadDebuggerID\" : " << (*itr)->GetID() << ","; 507 response << "\"ScriptInterfaceName\" : \"" << (*itr)->GetName() << "\","; 508 response << "\"ThreadInBreak\" : " << ((*itr)->GetIsInBreak() ? "true" : "false") << ","; 509 response << "\"BreakFileName\" : \"" << (*itr)->GetBreakFileName() << "\","; 510 response << "\"BreakLine\" : " << (*itr)->GetLastBreakLine(); 511 response << " }"; 512 } 513 response << "]"; 514 } 515 516 void CDebuggingServer::ToggleBreakPoint(std::string filename, uint line) 517 { 518 // First, pass the message to all associated CThreadDebugger objects and check if one returns true (handled); 519 { 520 CScopeLock lock(m_Mutex); 521 std::list<CThreadDebugger*>::iterator itr; 522 for (itr = m_ThreadDebuggers.begin(); itr != m_ThreadDebuggers.end(); itr++) 523 { 524 if ((*itr)->ToggleBreakPoint(filename, line)) 525 return; 526 } 527 } 528 529 // If the breakpoint isn't handled yet search the breakpoints registered in this class 530 std::list<CBreakPoint>* pBreakPoints = NULL; 531 double breakPointsLockID = AquireBreakPointAccess(&pBreakPoints); 532 std::list<CBreakPoint>::iterator itr; 533 534 // If set, delete 535 bool deleted = false; 536 for (itr = pBreakPoints->begin(); itr != pBreakPoints->end(); itr++) 537 { 538 if ((*itr).m_Filename == filename && (*itr).m_UserLine == line) 539 { 540 itr = pBreakPoints->erase(itr); 541 deleted = true; 542 break; 543 } 544 } 545 546 // If not set, set 547 if (!deleted) 548 { 549 CBreakPoint bP; 550 bP.m_Filename = filename; 551 bP.m_UserLine = line; 552 pBreakPoints->push_back(bP); 553 } 554 555 ReleaseBreakPointAccess(breakPointsLockID); 556 return; 557 } 558 559 double CDebuggingServer::AquireBreakPointAccess(std::list<CBreakPoint>** breakPoints) 560 { 561 int ret; 562 ret = SDL_SemWait(m_BreakPointsSem); 563 ENSURE(0 == ret); 564 (*breakPoints) = &m_BreakPoints; 565 m_BreakPointsLockID = timer_Time(); 566 return m_BreakPointsLockID; 567 } 568 569 void CDebuggingServer::ReleaseBreakPointAccess(double breakPointsLockID) 570 { 571 ENSURE(m_BreakPointsLockID == breakPointsLockID); 572 SDL_SemPost(m_BreakPointsSem); 573 } -
binaries/data/config/default.cfg
290 290 hotkey.profile2.enable = "F11" ; Enable HTTP/GPU modes for new profiler 291 291 292 292 profiler2.http.autoenable = false ; Enable HTTP server output at startup (default off for security/performance) 293 profiler2.script.enable = false ; Enable Javascript profiling. Needs to be set before startup and can't be changed later. (default off for performance) 293 294 profiler2.gpu.autoenable = false ; Enable GPU timing at startup (default off for performance/compatibility) 294 295 profiler2.gpu.arb.enable = true ; Allow GL_ARB_timer_query timing mode when available 295 296 profiler2.gpu.ext.enable = true ; Allow GL_EXT_timer_query timing mode when available 296 297 profiler2.gpu.intel.enable = true ; Allow GL_INTEL_performance_queries timing mode when available 297 298 299 ; > JS DEBUGGER 300 jsdebugger.enable = false ; Enable Javascript debugging (default off for security/performance) 301 298 302 ; > QUICKSAVE 299 303 hotkey.quicksave = "Shift+F5" 300 304 hotkey.quickload = "Shift+F8"