JavascriptDebuggingServer: javascript_debugging_v1.0.diff

File javascript_debugging_v1.0.diff, 74.8 KB (added by Yves, 11 years ago)
  • build/premake/premake4.lua

     
    545545        "boost",
    546546        "spidermonkey",
    547547        "valgrind",
     548        "sdl",
    548549    }
    549550    setup_static_lib_project("scriptinterface", source_dirs, extern_libs, {})
    550551
  • source/graphics/MapGenerator.cpp

     
    6363void* CMapGeneratorWorker::RunThread(void *data)
    6464{
    6565    debug_SetThreadName("MapGenerator");
     66    g_Profiler2.RegisterCurrentThread("MapGenerator");
    6667
    6768    CMapGeneratorWorker* self = static_cast<CMapGeneratorWorker*>(data);
    6869
  • source/gui/scripting/JSInterface_IGUIObject.cpp

     
    7575    if (propName == "constructor" ||
    7676        propName == "prototype"   ||
    7777        propName == "toString"    ||
     78        propName == "toJSON"      ||
    7879        propName == "focus"       ||
    7980        propName == "blur"        ||
    8081        propName == "getComputedSize"
  • source/ps/ConfigDB.h

     
    8080    static VfsPath m_ConfigFile[];
    8181
    8282public:
     83    CConfigDB();
     84   
    8385    // 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();
    8689
    8790    /**
    8891     * Attempt to find a config variable with the given name; will search
  • source/ps/ConfigDB.cpp

     
    210210
    211211CConfigDB::CConfigDB()
    212212{
     213}
     214
     215void CConfigDB::RegisterJSConfigDB()
     216{
    213217    g_ScriptingHost.DefineCustomObjectType(&ConfigDB_JS::Class, ConfigDB_JS::Construct, 0, ConfigDB_JS::Props, ConfigDB_JS::Funcs, NULL, NULL);
    214218    g_ScriptingHost.DefineCustomObjectType(&ConfigNamespace_JS::Class, ConfigNamespace_JS::Construct, 0, NULL, ConfigNamespace_JS::Funcs, NULL, NULL);
    215219    JSObject *js_ConfigDB = g_ScriptingHost.CreateCustomObject("ConfigDB");
  • source/ps/GameSetup/GameSetup.cpp

     
    8484#include "renderer/ModelRenderer.h"
    8585#include "scripting/ScriptingHost.h"
    8686#include "scripting/ScriptGlue.h"
     87#include "scriptinterface/DebuggingServer.h"
    8788#include "scriptinterface/ScriptInterface.h"
    8889#include "scriptinterface/ScriptStats.h"
    8990#include "simulation2/Simulation2.h"
     
    692693
    693694    TIMER_BEGIN(L"shutdown ScriptingHost");
    694695    delete &g_ScriptingHost;
     696    delete g_DebuggingServer;
    695697    TIMER_END(L"shutdown ScriptingHost");
    696698
    697699    TIMER_BEGIN(L"shutdown ConfigDB");
     
    886888    CSoundManager::CreateSoundManager();
    887889#endif
    888890
    889     InitScripting();    // before GUI
    890 
    891891    // g_ConfigDB, command line args, globals
    892892    CONFIG_Init(args);
     893   
     894    // before scripting
     895    if (g_JSDebuggerEnabled)
     896        g_DebuggingServer = new CDebuggingServer();
    893897
     898    InitScripting();    // before GUI
     899   
     900    g_ConfigDB.RegisterJSConfigDB();    // after scripting
     901
    894902    // Optionally start profiler HTTP output automatically
    895903    // (By default it's only enabled by a hotkey, for security/performance)
    896904    bool profilerHTTPEnable = false;
  • source/ps/GameSetup/Config.cpp

     
    2121
    2222#include "ps/ConfigDB.h"
    2323#include "ps/CConsole.h"
     24#include "ps/CLogger.h"
    2425#include "ps/GameSetup/CmdLineArgs.h"
    2526#include "lib/timer.h"
    2627#include "soundmanager/SoundManager.h"
     
    6061bool g_Quickstart = false;
    6162bool g_DisableAudio = false;
    6263
     64bool g_JSDebuggerEnabled = false;
     65bool g_ScriptProfilingEnabled = false;
     66
    6367// flag to switch on drawing terrain overlays
    6468bool g_ShowPathfindingOverlay = false;
    6569
     
    127131        g_SoundManager->SetMemoryUsage(bufferSize, bufferCount);
    128132    }
    129133#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!");
    130142}
    131143
    132144
  • source/ps/GameSetup/Config.h

     
    8282extern bool g_Quickstart;
    8383extern bool g_DisableAudio;
    8484
     85extern bool g_JSDebuggerEnabled;
     86extern bool g_ScriptProfilingEnabled;
     87
    8588extern CStrW g_CursorName;
    8689
    8790class CmdLineArgs;
  • source/scriptinterface/ScriptInterface.h

     
    4343// TODO: what's a good default?
    4444#define DEFAULT_RUNTIME_SIZE 16 * 1024 * 1024
    4545
    46 
    47 #ifdef NDEBUG
    48 #define ENABLE_SCRIPT_PROFILING 0
    49 #else
    50 #define ENABLE_SCRIPT_PROFILING 1
    51 #endif
    52 
    5346struct ScriptInterface_impl;
    5447
    5548class ScriptRuntime;
    5649
     50class CDebuggingServer;
     51
    5752/**
    5853 * Abstraction around a SpiderMonkey JSContext.
    5954 *
     
    244239     * Stringify to a JSON string, UTF-8 encoded. Returns an empty string on error.
    245240     */
    246241    std::string StringifyJSON(jsval obj, bool indent = true);
    247 
     242   
    248243    /**
    249244     * Report the given error message through the JS error reporting mechanism,
    250245     * and throw a JS exception. (Callers can check IsPendingException, and must
  • source/scriptinterface/NativeWrapperDefns.h

     
    1414 * You should have received a copy of the GNU General Public License
    1515 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
    1616 */
     17 #include "ps/GameSetup/Config.h"
    1718
    1819// (NativeWrapperDecls.h set up a lot of the macros we use here)
    1920
    20 
    2121// ScriptInterface_NativeWrapper<T>::call(cx, rval, fptr, args...) will call fptr(cbdata, args...),
    2222// and if T != void then it will store the result in rval:
    2323
     
    7676// ScriptInterface_impl::Register stores the name in a reserved slot.
    7777// (TODO: this doesn't work for functions registered via InterfaceScripted.h.
    7878// Maybe we should do some interned JS_GetFunctionId thing.)
    79 #if ENABLE_SCRIPT_PROFILING
    8079#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    }
    9190
    9291// JSFastNative-compatible function that wraps the function identified in the template argument list
    9392#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
     28class CBreakPoint
     29{
     30public:
     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!
     37class CActiveBreakPoint : public CBreakPoint
     38{
     39public:
     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
     67enum BREAK_SRC { BREAK_SRC_TRAP, BREAK_SRC_INTERRUP, BREAK_SRC_EXCEPTION };
     68
     69
     70class CThreadDebugger
     71{
     72public:
     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   
     169private:
     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

     
    1818#include "precompiled.h"
    1919
    2020#include "ScriptInterface.h"
     21#include "DebuggingServer.h"
    2122#include "ScriptStats.h"
    2223#include "AutoRooters.h"
    2324
     
    7374        m_rt = JS_NewRuntime(runtimeSize);
    7475        ENSURE(m_rt); // TODO: error handling
    7576
    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)
    7978        {
    80             if (CProfileManager::IsInitialised())
     79            // Profiler isn't thread-safe, so only enable this on the main thread
     80            if (ThreadUtil::IsMainThread())
    8181            {
    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                }
    8487            }
    8588        }
    86 #endif
    8789
    8890        JS_SetExtraGCRoots(m_rt, jshook_trace, this);
    8991    }
     
    100102
    101103private:
    102104
    103 #if ENABLE_SCRIPT_PROFILING
     105
    104106    static void* jshook_script(JSContext* UNUSED(cx), JSStackFrame* UNUSED(fp), JSBool before, JSBool* UNUSED(ok), void* closure)
    105107    {
    106108        if (before)
     
    212214
    213215        return closure;
    214216    }
    215 #endif
    216217
    217218    static void jshook_trace(JSTracer* trc, void* data)
    218219    {
     
    493494    options |= JSOPTION_XML; // "ECMAScript for XML support: parse <!-- --> as a token"
    494495    options |= JSOPTION_VAROBJFIX; // "recommended" (fixes variable scoping)
    495496
    496     // Enable method JIT, unless script profiling is enabled (since profiling
     497    // Enable method JIT, unless script profiling/debugging is enabled (since profiling/debugging
    497498    // 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;
    500503
    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    }
    505508
    506509    JS_SetOptions(m_cx, options);
    507510
     
    557560    if (!func)
    558561        return;
    559562
    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.
    562566
    563     // Use a flyweight std::string because we can't assume the caller has
    564     // a correctly-aligned string and that its lifetime is long enough
    565     typedef boost::flyweight<
    566         std::string,
    567         boost::flyweights::no_tracking
    568         // can't use no_locking; Register might be called in threads
    569     > 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;
    570574
    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    }
    574578}
    575579
    576580ScriptInterface::ScriptInterface(const char* nativeScopeName, const char* debugName, const shared_ptr<ScriptRuntime>& runtime) :
     
    582586        if (g_ScriptStatsTable)
    583587            g_ScriptStatsTable->Add(this, debugName);
    584588    }
     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    }
    585597}
    586598
    587599ScriptInterface::~ScriptInterface()
     
    591603        if (g_ScriptStatsTable)
    592604            g_ScriptStatsTable->Remove(this);
    593605    }
     606   
     607    // Unregister from the Debugger class
     608    if (g_JSDebuggerEnabled && g_DebuggingServer != NULL)
     609        g_DebuggingServer->UnRegisterScriptinterface(this);
    594610}
    595611
    596612void ScriptInterface::ShutDown()
     
    10751091    {
    10761092        JS_ClearPendingException(m->m_cx);
    10771093        LOGERROR(L"StringifyJSON failed");
     1094        JS_ClearPendingException(m->m_cx);
    10781095        return "";
    10791096    }
    10801097
    10811098    return str.stream.str();
    10821099}
    10831100
     1101
    10841102std::wstring ScriptInterface::ToString(jsval obj, bool pretty)
    10851103{
    10861104    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
     26class CBreakPoint;
     27class CThreadDebugger;
     28
     29enum DBGCMD { DBG_CMD_NONE=0, DBG_CMD_CONTINUE, DBG_CMD_SINGLESTEP, DBG_CMD_STEPINTO, DBG_CMD_STEPOUT };
     30enum STACK_INFO { STACK_INFO_LOCALS=0, STACK_INFO_THIS, STACK_INFO_GLOBALOBJECT };
     31
     32class CDebuggingServer
     33{
     34public:
     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   
     84private:
     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
     148extern 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
     26CMutex ThrowHandlerMutex;
     27static 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
     34CMutex TrapHandlerMutex;
     35static 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
     43CMutex StepHandlerMutex;
     44JSTrapStatus 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
     52CMutex StepIntoHandlerMutex;
     53JSTrapStatus 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
     60CMutex NewScriptHookMutex;
     61void 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
     68CMutex DestroyScriptHookMutex;
     69void 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
     76CMutex StepOutHandlerMutex;
     77JSTrapStatus 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
     84CMutex CheckForBreakRequestHandlerMutex;
     85JSTrapStatus 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
     92CMutex CallHookMutex;
     93static 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
     112void 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
     131void 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
     141void 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
     183bool 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
     189void 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
     205CThreadDebugger::CThreadDebugger()
     206{
     207    m_NextDbgCmd = DBG_CMD_NONE;
     208    m_IsInBreak = false;
     209    m_pLastBreakFrame = new JSStackFrame*;
     210}
     211
     212CThreadDebugger::~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
     229void 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
     256void 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
     276JSTrapStatus 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
     296JSTrapStatus 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
     313JSTrapStatus 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
     329bool 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
     344JSTrapStatus 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
     353JSTrapStatus 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
     359JSTrapStatus 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
     377JSTrapStatus 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
     474void 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
     518void 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
     545void 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
     555bool 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
     570void CThreadDebugger::GetCallstack(std::stringstream& response)
     571{
     572    CScopeLock lock(m_Mutex);
     573    response << m_Callstack;
     574}
     575
     576void 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
     616void 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
     640void 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
     649void 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 
     770std::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
     806bool CThreadDebugger::CompareScriptInterfacePtr(ScriptInterface* pScriptInterface)
     807{
     808    return (pScriptInterface == m_pScriptInterface);
     809}
     810
     811
     812std::string CThreadDebugger::GetBreakFileName()
     813{
     814    CScopeLock lock(m_Mutex);
     815    return m_BreakFileName;
     816}
     817
     818void CThreadDebugger::SetBreakFileName(std::string breakFileName)
     819{
     820    CScopeLock lock(m_Mutex);
     821    m_BreakFileName = breakFileName;
     822}
     823
     824uint CThreadDebugger::GetLastBreakLine()
     825{
     826    CScopeLock lock(m_Mutex);
     827    return m_LastBreakLine;
     828}
     829
     830void CThreadDebugger::SetLastBreakLine(uint breakLine)
     831{
     832    CScopeLock lock(m_Mutex);
     833    m_LastBreakLine = breakLine;
     834}
     835
     836bool CThreadDebugger::GetIsInBreak()
     837{
     838    CScopeLock lock(m_IsInBreakMutex);
     839    return m_IsInBreak;
     840}
     841
     842void CThreadDebugger::SetIsInBreak(bool isInBreak)
     843{
     844    CScopeLock lock(m_IsInBreakMutex);
     845    m_IsInBreak = isInBreak;
     846}
     847
     848void CThreadDebugger::SetNextDbgCmd(DBGCMD dbgCmd)
     849{
     850    CScopeLock lock(m_NextDbgCmdMutex);
     851        m_NextDbgCmd = dbgCmd;
     852}
     853
     854DBGCMD CThreadDebugger::GetNextDbgCmd()
     855{
     856    CScopeLock lock(m_NextDbgCmdMutex);
     857    return m_NextDbgCmd;
     858}
     859
     860std::string CThreadDebugger::GetName()
     861{
     862    CScopeLock lock(m_Mutex);
     863    return m_Name;
     864}
     865
     866uint 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
     26CDebuggingServer* g_DebuggingServer = NULL;
     27
     28const 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
     33void 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
     65void 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
     85CDebuggingServer::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
     101CDebuggingServer::~CDebuggingServer()
     102{
     103    SDL_DestroySemaphore(m_BreakPointsSem);
     104    if (m_MgContext)
     105    {
     106        mg_stop(m_MgContext);
     107        m_MgContext = NULL;
     108    }
     109}
     110
     111bool 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
     130void CDebuggingServer::SetBreakRequestedByThread(bool Enabled)
     131{
     132    CScopeLock lock(m_Mutex1);
     133    m_BreakRequestedByThread = Enabled;
     134}
     135
     136bool CDebuggingServer::GetBreakRequestedByThread()
     137{
     138    CScopeLock lock(m_Mutex1);
     139    return m_BreakRequestedByThread;
     140}
     141
     142void CDebuggingServer::SetBreakRequestedByUser(bool Enabled)
     143{
     144    CScopeLock lock(m_Mutex1);
     145    m_BreakRequestedByUser = Enabled;
     146}
     147
     148bool CDebuggingServer::GetBreakRequestedByUser()
     149{
     150    CScopeLock lock(m_Mutex1);
     151    return m_BreakRequestedByUser;
     152}
     153
     154void CDebuggingServer::SetSettingBreakOnException(bool Enabled)
     155{
     156    CScopeLock lock(m_Mutex);
     157    m_SettingBreakOnException = Enabled;
     158}
     159
     160void CDebuggingServer::SetSettingSimultaneousThreadBreak(bool Enabled)
     161{
     162    CScopeLock lock(m_Mutex1);
     163    m_SettingSimultaneousThreadBreak = Enabled;
     164}
     165
     166bool CDebuggingServer::GetSettingBreakOnException()
     167{
     168    CScopeLock lock(m_Mutex);
     169    return m_SettingBreakOnException;
     170}
     171
     172bool CDebuggingServer::GetSettingSimultaneousThreadBreak()
     173{
     174    CScopeLock lock(m_Mutex1);
     175    return m_SettingSimultaneousThreadBreak;
     176}
     177
     178
     179static 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
     187void 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
     207void 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
     221static 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
     228void* 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
     413void 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
     428bool 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
     448bool 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
     467void 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
     476void 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
     492void 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
     516void 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
     559double 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
     569void CDebuggingServer::ReleaseBreakPointAccess(double breakPointsLockID)
     570{
     571    ENSURE(m_BreakPointsLockID == breakPointsLockID);
     572    SDL_SemPost(m_BreakPointsSem);
     573}
  • binaries/data/config/default.cfg

     
    290290hotkey.profile2.enable = "F11"              ; Enable HTTP/GPU modes for new profiler
    291291
    292292profiler2.http.autoenable = false           ; Enable HTTP server output at startup (default off for security/performance)
     293profiler2.script.enable = false             ; Enable Javascript profiling. Needs to be set before startup and can't be changed later. (default off for performance)
    293294profiler2.gpu.autoenable = false            ; Enable GPU timing at startup (default off for performance/compatibility)
    294295profiler2.gpu.arb.enable = true             ; Allow GL_ARB_timer_query timing mode when available
    295296profiler2.gpu.ext.enable = true             ; Allow GL_EXT_timer_query timing mode when available
    296297profiler2.gpu.intel.enable = true           ; Allow GL_INTEL_performance_queries timing mode when available
    297298
     299; > JS DEBUGGER
     300jsdebugger.enable = false                   ; Enable Javascript debugging (default off for security/performance)           
     301
    298302; > QUICKSAVE
    299303hotkey.quicksave = "Shift+F5"
    300304hotkey.quickload = "Shift+F8"