#include #include #include #include #include #include #include #include #include // Newer versions of haxe compiler will set these too (or might be null for haxe 3.0) static const char **__all_files_fullpath = 0; static const char **__all_classes = 0; #define HXCPP_DEBUG_HASHES namespace hx { // These are emitted elsewhere by the haxe compiler extern const char *__hxcpp_all_files[]; // This global boolean is set whenever there are any breakpoints (normal or // immediate), and can relatively quickly gate debugged threads from making // more expensive breakpoint check calls when there are no breakpoints set. // Note that there is no lock to protect this. Volatile is used to ensure // that within a function call, the value of gShouldCallHandleBreakpoints is // not cached in a register and thus not properly checked within the function // call. volatile bool gShouldCallHandleBreakpoints = false; // This is the event notification handler, as registered by the debugger // thread. // Signature: threadNumber : Int -> status: Int -> Void static Dynamic g_eventNotificationHandler; // This is the function to call to create a new Parameter // Signature: name : String -> value : Dynamic -> Parameter : Dynamic static Dynamic g_newParameterFunction; // This is the function to call to create a new StackFrame // Signature: fileName : String -> lineNumber :Int -> // className : String -> functionName : String -> // StackFrame : Dynamic static Dynamic g_newStackFrameFunction; // This is the function to call to create a new ThreadInfo // Signature: number : Int -> statu s: Int -> breakpoint : Int -> // ThreadInfo : Dynamic static Dynamic g_newThreadInfoFunction; // This is the function to call to add a Parameter to a StackFrame. // Signature: inStackFrame : Dynamic -> inParameter : Dynamic -> Void static Dynamic g_addParameterToStackFrameFunction; // This is the function to call to add a StackFrame to a ThreadInfo. // Signature: inThreadInfo : Dynamic -> inStackFrame : Dynamic -> Void static Dynamic g_addStackFrameToThreadInfoFunction; // This is the thread number of the debugger thread, extracted from // information about the thread that called // __hxcpp_dbg_setEventNotificationHandler static unsigned int g_debugThreadNumber = -1; ExecutionTrace sExecutionTrace = exeTraceOff; // These should implement write and read memory barrier, but since there are // no obvious portable implementations, they are currently left unimplemented static void write_memory_barrier() { // currently unimplemented } static void read_memory_barrier() { // currently unimplemented } const char *_hx_dbg_find_scriptable_class_name(String className); static HxMutex gMutex; static std::map gMap; static std::list gList; class Breakpoints; Breakpoints *ReleaseBreakpointsLocked(Breakpoints *inBreakpoints); class DebuggerContext { public: int mThreadNumber; StackContext *mStackContext; bool mCanStop; int mBreakpoint; // Always 'const' strings (no GC) String mCriticalError; DebugStatus mStatus; int mStepLevel; Breakpoints *mBreakpoints; // Waiting for continue bool mWaiting; HxMutex mWaitMutex; HxSemaphore mWaitSemaphore; int mContinueCount; bool mAttached; DebuggerContext(StackContext *inStack) { mStackContext = inStack; reset(); } ~DebuggerContext() { if (mAttached) detach(); } void attach(StackContext *inStack) { mAttached = true; mStackContext = inStack; mThreadNumber = mStackContext->mThreadId; mStatus = DBG_STATUS_RUNNING; gMutex.Lock(); gList.push_back(this); gMap[mThreadNumber] = this; gMutex.Unlock(); // Note that there is a race condition here. If the debugger is // "detaching" at this exact moment, it might set the event handler to // NULL during this call. So latch the handler variable. This means that // the handler might be called even after the debugger thread has set it // to NULL, but this should generally be harmless. Doing this correctly // would require some sophisticated locking that just doesn't seem worth // it, when the worst that can happen is an extra call to the handler // function milliseconds after it's set to NULL ... Dynamic handler = hx::g_eventNotificationHandler; if (handler != null()) handler(mThreadNumber, hx::THREAD_CREATED); } void detach() { mAttached = false; gMutex.Lock(); gList.remove(this); gMap.erase(mThreadNumber); mBreakpoints = ReleaseBreakpointsLocked(mBreakpoints); gMutex.Unlock(); reset(); Dynamic handler = hx::g_eventNotificationHandler; if (handler != null()) handler(mThreadNumber, hx::THREAD_TERMINATED); } void enable(bool inEnable) { mCanStop = inEnable; } void reset() { mCanStop = false; mBreakpoint = 0; mBreakpoints = 0; mCriticalError = null(); mStatus = DBG_STATUS_INVALID; mStepLevel = 0; mWaiting = false; mContinueCount = 0; mThreadNumber = -1; } // Make best effort to wait until all threads are stopped static void WaitForAllThreadsToStop() { // Make a "best effort" in the face of threads that could do arbitrary // things to make the "break all" not complete successfully. Threads // can hang in system calls indefinitely, and can spawn new threads // continuously that themselves do the same. Don't try to be perfect // and guarantee that all threads have stopped, as that could mean // waiting a long time in pathological cases or even forever in really // pathological cases. Just make a best effort and if the break // doesn't break everything, the user will know when they go to list // all thread stacks and can try the break again. // Copy the thread numbers out. This is because we really don't want // to hold the lock during the entire process as this could block // threads from actually evaluating breakpoints. std::vector threadNumbers; gMutex.Lock(); std::list::iterator iter = gList.begin(); while (iter != gList.end()) { DebuggerContext *stack = *iter++; if (stack->mThreadNumber == g_debugThreadNumber) { continue; } threadNumbers.push_back(stack->mThreadNumber); } gMutex.Unlock(); // Now wait no longer than 2 seconds total for all threads to // be stopped. If any thread times out, then stop immediately. int size = threadNumbers.size(); // Each time slice is 1/10 of a second. Yeah there's some slop here // because no time is accounted for the time spent outside of sem // waiting. If there were good portable time APIs easily available // within hxcpp I'd use them ... int timeSlicesLeft = 20; HxSemaphore timeoutSem; int i = 0; while (i < size) { gMutex.Lock(); DebuggerContext *stack = gMap[threadNumbers[i]]; if (!stack) { // The thread went away while we were working! gMutex.Unlock(); i += 1; continue; } if (stack->mWaiting) { gMutex.Unlock(); i += 1; continue; } gMutex.Unlock(); if (timeSlicesLeft == 0) { // The 2 seconds have expired, give up return; } // Sleep for 1/10 of a second on a semaphore that will never // be Set. timeoutSem.WaitSeconds(0.100); timeSlicesLeft -= 1; // Don't increment i, try the same thread again } } // Continue the thread that is waiting, if it is waiting. Only the // debugger thread should call this. void Continue(int count) { // Paranoia if (count < 1) { count = 1; } mWaitMutex.Lock(); if (mWaiting) { mWaiting = false; mContinueCount = count - 1; mWaitSemaphore.Set(); } mWaitMutex.Unlock(); } void DoBreak(DebugStatus status, int breakpoint, const String *criticalErrorDescription) { // Update status mStatus = status; mBreakpoint = breakpoint; if (criticalErrorDescription) mCriticalError = criticalErrorDescription->makePermanent(); // This thread cannot stop while making the callback mCanStop = false; mWaitMutex.Lock(); mWaiting = true; mWaitMutex.Unlock(); // Call the handler to announce the status. StackFrame *frame = mStackContext->getCurrentStackFrame(); // Record this before g_eventNotificationHandler is run, since it might change in there mStackContext->mDebugger->mStepLevel = mStackContext->getDepth(); g_eventNotificationHandler (mThreadNumber, THREAD_STOPPED, mStackContext->mDebugger->mStepLevel, String(frame->position->className), String(frame->position->functionName), String(frame->position->fileName), frame->lineNumber); if (mWaiting) { // Wait until the debugger thread sets mWaiting to false and signals // the semaphore mWaitMutex.Lock(); while (mWaiting) { mWaitMutex.Unlock(); hx::EnterGCFreeZone(); mWaitSemaphore.Wait(); hx::ExitGCFreeZone(); mWaitMutex.Lock(); } mWaitMutex.Unlock(); } // Save the breakpoint status in the call stack so that queries for // thread info will know the current status of the thread mStatus = DBG_STATUS_RUNNING; mBreakpoint = -1; // Announce the new status Dynamic handler = hx::g_eventNotificationHandler; if (handler!=null()) handler(mThreadNumber, THREAD_STARTED); // Can stop again mCanStop = true; } // Wait for someone to call Continue() on this call stack. Really only // the thread that owns this call stack should call Wait(). void Break(DebugStatus status, int breakpoint, const String *criticalErrorDescription) { // If break status is break immediate, then eliminate any residual // continue count from the last continue. if (status == DBG_STATUS_STOPPED_BREAK_IMMEDIATE) { mContinueCount = 0; } // Else break status is break in breakpoint -- but if there is a // continue count, just decrement the continue count else if (mContinueCount > 0) { mContinueCount -= 1; return; } this->DoBreak(status, breakpoint, 0); } }; DebuggerContext *dbgCtxCreate(StackContext *inStack) { return new DebuggerContext(inStack); } void dbgCtxDestroy(DebuggerContext *ctx) { delete ctx; } void dbgCtxAttach(DebuggerContext *ctx, StackContext *inStack) { ctx->attach(inStack); } void dbgCtxDetach(DebuggerContext *ctx) { ctx->detach(); } void dbgCtxEnable(DebuggerContext *ctx, bool inEnable) { ctx->enable(inEnable); } class Breakpoints { public: static int Hash(int value, const char *inString) { while(*inString) value = value*223 + *inString++; return value; } static int Add(String inFileName, int lineNumber) { // Look up the filename constant const char *fileName = LookupFileName(inFileName); if (!fileName) { return -1; } gMutex.Lock(); int ret = gNextBreakpointNumber++; Breakpoints *newBreakpoints = new Breakpoints(gBreakpoints, ret, fileName, lineNumber); gBreakpoints->RemoveRef(); // Write memory barrier ensures that newBreakpoints values are updated // before gBreakpoints is assigned to it write_memory_barrier(); gBreakpoints = newBreakpoints; // Don't need a write memory barrier here, it's harmless to see // gShouldCallHandleBreakpoints update before gBreakpoints has updated gShouldCallHandleBreakpoints = true; gMutex.Unlock(); return ret; } void RemoveRef() { if (--mRefCount == 0) delete this; } static int Add(String inClassName, String functionName) { // Look up the class name constant const char *className = LookupClassName(inClassName); if (!className) { return -1; } gMutex.Lock(); int ret = gNextBreakpointNumber++; Breakpoints *newBreakpoints = new Breakpoints(gBreakpoints, ret, className, functionName); gBreakpoints->RemoveRef(); // Write memory barrier ensures that newBreakpoints values are updated // before gBreakpoints is assigned to it write_memory_barrier(); gBreakpoints = newBreakpoints; // Don't need a write memory barrier here, it's harmless to see // gShouldCallHandleBreakpoints update before gBreakpoints has updated gShouldCallHandleBreakpoints = true; gMutex.Unlock(); return ret; } static void DeleteAll() { gMutex.Lock(); Breakpoints *newBreakpoints = new Breakpoints(); gBreakpoints->RemoveRef(); // Write memory barrier ensures that newBreakpoints values are updated // before gBreakpoints is assigned to it write_memory_barrier(); gBreakpoints = newBreakpoints; // Don't need a write memory barrier here, it's harmless to see // gShouldCallHandleBreakpoints update before gStepType has updated gShouldCallHandleBreakpoints = (gStepType != STEP_NONE) || (sExecutionTrace==exeTraceLines); gMutex.Unlock(); } static void Delete(int number) { gMutex.Lock(); if (gBreakpoints->HasBreakpoint(number)) { // Replace mBreakpoints with a copy and remove the breakpoint // from it Breakpoints *newBreakpoints = new Breakpoints(gBreakpoints, number); Breakpoints *toRelease = gBreakpoints; gBreakpoints = newBreakpoints; // Write memory barrier ensures that newBreakpoints values are // updated before gBreakpoints is assigned to it write_memory_barrier(); // Only release after gBreakpoints is set toRelease->RemoveRef(); if (gBreakpoints->IsEmpty()) { // Don't need a write memory barrier here, it's harmless to // see gShouldCallHandleBreakpoints update before gStepType // has updated gShouldCallHandleBreakpoints = (gStepType != STEP_NONE) || (sExecutionTrace==exeTraceLines); } } gMutex.Unlock(); } static void BreakNow(bool wait) { gStepType = STEP_INTO; gStepCount = 0; gStepThread = -1; // Won't bother with a write memory barrier here, it's harmless to set // gShouldCallHandleBreakpoints before the step type and step thread // are updated xxx should consider making gStepType and gStepThread // atomic though by putting them into one uint32_t value ... gShouldCallHandleBreakpoints = true; // Wait for all threads to be stopped if (wait) { DebuggerContext::WaitForAllThreadsToStop(); } } static void ContinueThreads(int specialThreadNumber, int continueCount) { gStepType = STEP_NONE; gShouldCallHandleBreakpoints = !gBreakpoints->IsEmpty() || (sExecutionTrace==exeTraceLines); gMutex.Lock(); // All threads get continued, but specialThreadNumber only for count std::list::iterator iter = gList.begin(); while (iter != gList.end()) { DebuggerContext *stack = *iter++; if (stack->mThreadNumber == specialThreadNumber) { stack->Continue(continueCount); } else { stack->Continue(1); } } gMutex.Unlock(); } static void StepThread(int threadNumber, StepType stepType, int stepCount) { // Continue the thread, but set its step first gStepThread = threadNumber; gStepType = stepType; gStepCount = stepCount; gMutex.Lock(); std::list::iterator iter = gList.begin(); while (iter != gList.end()) { DebuggerContext *stack = *iter++; if (stack->mThreadNumber == threadNumber) { gStepLevel = stack->mStackContext->mDebugger->mStepLevel; stack->Continue(1); break; } } gMutex.Unlock(); } // Note that HandleBreakpoints is called immediately after a read memory // barrier by the HX_STACK_LINE macro static void HandleBreakpoints(hx::StackContext *stack) { // This will be set to a valid status if a stop is needed DebugStatus breakStatus = DBG_STATUS_INVALID; int breakpointNumber = -1; // The debug thread never breaks if (stack->mThreadId == g_debugThreadNumber) { return; } if (sExecutionTrace==exeTraceLines) stack->tracePosition(); // Handle possible immediate break if (gStepType == STEP_NONE) { // No stepping } else if (gStepType == STEP_INTO) { if ((gStepThread == -1) || (gStepThread == stack->mThreadId)) { breakStatus = DBG_STATUS_STOPPED_BREAK_IMMEDIATE; } } else { if ((gStepThread == -1) || (gStepThread == stack->mThreadId)) { if (gStepType == STEP_OVER) { if (stack->getDepth() <= gStepLevel) { breakStatus = DBG_STATUS_STOPPED_BREAK_IMMEDIATE; } } else { // (gStepType == STEP_OUT) if (stack->getDepth() < gStepLevel) { breakStatus = DBG_STATUS_STOPPED_BREAK_IMMEDIATE; } } } } // If didn't hit any immediate breakpoints, check for set breakpoints if (breakStatus == DBG_STATUS_INVALID) { Breakpoints *breakpoints = stack->mDebugger->mBreakpoints; // If the current thread has never gotten a reference to // breakpoints, get a reference to the current breakpoints if (!breakpoints) { gMutex.Lock(); // Get break points and ref it breakpoints = gBreakpoints; // This read memory barrier ensures that old values within // gBreakpoints are not seen after gBreakpoints has been set // here read_memory_barrier(); stack->mDebugger->mBreakpoints = breakpoints; breakpoints->AddRef(); gMutex.Unlock(); } // Else if the current thread's breakpoints number is out of date, // release the reference on that and get the new breakpoints. // Note that no locking is done on the reference to gBreakpoints. // A thread calling GetBreakpoints will retain its old breakpoints // until it "sees" a newer gBreakpoints. Without memory barriers, // this could theoretically be indefinitely. else if (breakpoints != gBreakpoints) { gMutex.Lock(); // Release ref on current break points breakpoints->RemoveRef(); // Get new break points and ref it breakpoints = gBreakpoints; // This read memory barrier ensures that old values within // gBreakpoints are not seen after gBreakpoints has been set // here read_memory_barrier(); stack->mDebugger->mBreakpoints = breakpoints; breakpoints->AddRef(); gMutex.Unlock(); } // If there are breakpoints, then may need to break in one if (!breakpoints->IsEmpty()) { StackFrame *frame = stack->getCurrentStackFrame(); if (!breakpoints->QuickRejectClassFunc(frame->position->classFuncHash)) { // Check for class:function breakpoint if this is the // first line of the stack frame if (frame->lineNumber == frame->position->firstLineNumber) breakpointNumber = breakpoints->FindClassFunctionBreakpoint(frame); } // If still haven't hit a break point, check for file:line // breakpoint if (breakpointNumber == -1 && !breakpoints->QuickRejectFileLine(frame->position->fileHash)) breakpointNumber = breakpoints->FindFileLineBreakpoint(frame); if (breakpointNumber != -1) breakStatus = DBG_STATUS_STOPPED_BREAKPOINT; } } // If no breakpoint of any kind was found, then don't break if (breakStatus == DBG_STATUS_INVALID) { return; } // If the thread has been put into no stop mode, it can't stop if (!stack->mDebugger->mCanStop) { return; } // If the break was an immediate break, and there was a step count, // just decrement the step count if (breakStatus == DBG_STATUS_STOPPED_BREAK_IMMEDIATE) { if (gStepCount > 1) { gStepCount -= 1; return; } } // Now break, which will wait until the debugger thread continues // the thread stack->mDebugger->Break(breakStatus, breakpointNumber, 0); } static bool shoudBreakOnLine() { return gBreakpoints->IsEmpty() || gStepType != hx::STEP_NONE; } private: struct Breakpoint { int number; int lineNumber; int hash; bool isFileLine; std::string fileOrClassName; std::string functionName; }; // Creates Breakpoints object with no breakpoints and a zero version Breakpoints() : mRefCount(1), mBreakpointCount(0), mBreakpoints(0) { #ifdef HXCPP_DEBUG_HASHES calcCombinedHash(); #endif } // Copies breakpoints from toCopy and adds a new file:line breakpoint Breakpoints(const Breakpoints *toCopy, int number, const char *fileName, int lineNumber) : mRefCount(1) { mBreakpointCount = toCopy->mBreakpointCount + 1; mBreakpoints = new Breakpoint[mBreakpointCount]; for (int i = 0; i < toCopy->mBreakpointCount; i++) mBreakpoints[i] = toCopy->mBreakpoints[i]; mBreakpoints[toCopy->mBreakpointCount].number = number; mBreakpoints[toCopy->mBreakpointCount].isFileLine = true; mBreakpoints[toCopy->mBreakpointCount].fileOrClassName = fileName; mBreakpoints[toCopy->mBreakpointCount].lineNumber = lineNumber; #ifdef HXCPP_DEBUG_HASHES mBreakpoints[toCopy->mBreakpointCount].hash = Hash(0, fileName); calcCombinedHash(); #else mBreakpoints[toCopy->mBreakpointCount].hash = 0; #endif } // Copies breakpoints from toCopy and adds a new class:function breakpoint Breakpoints(const Breakpoints *toCopy, int number, const char *className, String functionName) : mRefCount(1) { mBreakpointCount = toCopy->mBreakpointCount + 1; mBreakpoints = new Breakpoint[mBreakpointCount]; for (int i = 0; i < toCopy->mBreakpointCount; i++) { mBreakpoints[i] = toCopy->mBreakpoints[i]; } mBreakpoints[toCopy->mBreakpointCount].number = number; mBreakpoints[toCopy->mBreakpointCount].isFileLine = false; mBreakpoints[toCopy->mBreakpointCount].fileOrClassName = className; mBreakpoints[toCopy->mBreakpointCount].functionName = functionName.c_str(); #ifdef HXCPP_DEBUG_HASHES int hash = Hash(0,className); hash = Hash(hash,"."); hash = Hash(hash,functionName.c_str()); //printf("%s.%s -> %08x\n", className, functionName.c_str(), hash ); mBreakpoints[toCopy->mBreakpointCount].hash = hash; calcCombinedHash(); #else mBreakpoints[toCopy->mBreakpointCount].hash = 0; #endif } // Copies breakpoints from toCopy except for number Breakpoints(const Breakpoints *toCopy, int number) : mRefCount(1) { mBreakpointCount = toCopy->mBreakpointCount - 1; if (mBreakpointCount == 0) mBreakpoints = 0; else { mBreakpoints = new Breakpoint[mBreakpointCount]; for(int s = 0, d = 0; s < toCopy->mBreakpointCount; s++) { Breakpoint &other = toCopy->mBreakpoints[s]; if (other.number != number) mBreakpoints[d++] = toCopy->mBreakpoints[s]; } } #ifdef HXCPP_DEBUG_HASHES calcCombinedHash(); #endif } #ifdef HXCPP_DEBUG_HASHES void calcCombinedHash() { int allFileLine = 0; int allClassFunc = 0; for(int i=0;i %08x %08x\n", mNotInAnyFileLine, mNotInAnyClassFunc); } #endif ~Breakpoints() { delete[] mBreakpoints; } void AddRef() { mRefCount += 1; } bool IsEmpty() const { return (mBreakpointCount == 0); } inline bool QuickRejectClassFunc(int inHash) { #ifdef HXCPP_DEBUG_HASHES return inHash & mNotInAnyClassFunc; #else return false; #endif } inline bool QuickRejectFileLine(int inHash) { #ifdef HXCPP_DEBUG_HASHES return inHash & mNotInAnyFileLine; #else return false; #endif } bool HasBreakpoint(int number) const { for (int i = 0; i < mBreakpointCount; i++) { if (number == mBreakpoints[i].number) { return true; } } return false; } int FindFileLineBreakpoint(StackFrame *inFrame) { for (int i = 0; i < mBreakpointCount; i++) { Breakpoint &breakpoint = mBreakpoints[i]; if (breakpoint.isFileLine && #ifdef HXCPP_DEBUG_HASHES breakpoint.hash==inFrame->position->fileHash && #endif (breakpoint.lineNumber == inFrame->lineNumber) && !strcmp(breakpoint.fileOrClassName.c_str(),inFrame->position->fileName) ) return breakpoint.number; } return -1; } int FindClassFunctionBreakpoint(StackFrame *inFrame) { for (int i = 0; i < mBreakpointCount; i++) { Breakpoint &breakpoint = mBreakpoints[i]; if (!breakpoint.isFileLine && #ifdef HXCPP_DEBUG_HASHES breakpoint.hash==inFrame->position->classFuncHash && #endif !strcmp(breakpoint.fileOrClassName.c_str(), inFrame->position->className) && !strcmp(breakpoint.functionName.c_str(), inFrame->position->functionName) ) return breakpoint.number; } return -1; } // Looks up the "interned" version of the name, for faster compares // when evaluating breakpoints static const char *LookupFileName(String fileName) { if (fileName.length == 0) return 0; for (const char **ptr = hx::__hxcpp_all_files; *ptr; ptr++) { if (!strcmp(*ptr, fileName)) return *ptr; } #ifdef HXCPP_SCRIPTABLE Array< ::String> ret = Array_obj< ::String>::__new(); __hxcpp_dbg_getScriptableFiles(ret); for(int i=0;ilength;i++) if (ret[i]==fileName) return (ret[i]).makePermanent().utf8_str(); ret = Array_obj< ::String>::__new(); __hxcpp_dbg_getScriptableFilesFullPath(ret); for(int i=0;ilength;i++) if (ret[i]==fileName) return (ret[i]).makePermanent().utf8_str(); #endif return 0; } static const char *LookupClassName(String className) { if (__all_classes) for (const char **ptr = __all_classes; *ptr; ptr++) { if (!strcmp(*ptr, className.raw_ptr())) return *ptr; } #ifdef HXCPP_SCRIPTABLE Array< ::String> ret = Array_obj< ::String>::__new(); __hxcpp_dbg_getScriptableClasses(ret); for(int i=0;ilength;i++) if (ret[i]==className) return ret[i].makePermanent().raw_ptr(); #endif return 0; } private: int mRefCount; int mBreakpointCount; int mNotInAnyClassFunc; int mNotInAnyFileLine; Breakpoint *mBreakpoints; static int gNextBreakpointNumber; static Breakpoints * volatile gBreakpoints; static StepType gStepType; static int gStepLevel; static int gStepThread; // If -1, all threads are targeted static int gStepCount; }; /* static */ int Breakpoints::gNextBreakpointNumber; /* static */ Breakpoints * volatile Breakpoints::gBreakpoints = new Breakpoints(); /* static */ StepType Breakpoints::gStepType = STEP_NONE; /* static */ int Breakpoints::gStepLevel; /* static */ int Breakpoints::gStepThread = -1; /* static */ int Breakpoints::gStepCount = -1; Breakpoints *ReleaseBreakpointsLocked(Breakpoints *inBreakpoints) { if (inBreakpoints) inBreakpoints->RemoveRef(); return 0; } // Gets a ThreadInfo for a thread static Dynamic GetThreadInfo(int threadNumber, bool unsafe) { if (threadNumber == g_debugThreadNumber) return null(); DebuggerContext *stack = 0; gMutex.Lock(); if (gMap.count(threadNumber) == 0) { gMutex.Unlock(); return null(); } else stack = gMap[threadNumber]; if ((stack->mStatus == DBG_STATUS_RUNNING) && !unsafe) { gMutex.Unlock(); return null(); } // It's safe to release the mutex here, because the stack to be // converted is either for a thread that is not running (and thus // the stack cannot be altered while the conversion is in progress), // or unsafe mode has been invoked gMutex.Unlock(); Dynamic ret = g_newThreadInfoFunction (stack->mThreadNumber, stack->mStatus, stack->mBreakpoint, stack->mCriticalError); int size = stack->mStackContext->getDepth(); for (int i = 0; i < size; i++) { StackFrame *frame = stack->mStackContext->getStackFrame(i); #ifdef HXCPP_STACK_LINE Dynamic info = g_newStackFrameFunction (String(frame->position->fileName), String(frame->lineNumber), String(frame->position->className), String(frame->position->functionName)); #else Dynamic info = g_newStackFrameFunction (String(frame->position->fileName), String(frame->position->firstLineNumber), String(frame->position->className), String(frame->position->functionName)); #endif g_addStackFrameToThreadInfoFunction(ret, info); } return ret; } // Gets a ThreadInfo for each Thread static ::Array GetThreadInfos() { gMutex.Lock(); // Latch the current thread numbers from the current list of call // stacks. std::list threadNumbers; std::list::iterator stack_iter = gList.begin(); while (stack_iter != gList.end()) { DebuggerContext *stack = *stack_iter++; threadNumbers.push_back(stack->mThreadNumber); } gMutex.Unlock(); ::Array ret = Array_obj::__new(); // Now get each thread info std::list::iterator thread_iter = threadNumbers.begin(); while (thread_iter != threadNumbers.end()) { Dynamic info = GetThreadInfo(*thread_iter++, false); if (info != null()) ret->push(info); } return ret; } static ::Array GetStackVariables(int threadNumber, int stackFrameNumber, bool unsafe, Dynamic markThreadNotStopped) { ::Array ret = Array_obj::__new(); gMutex.Lock(); std::list::iterator iter = gList.begin(); while (iter != gList.end()) { DebuggerContext *ctx = *iter++; if (ctx->mThreadNumber == threadNumber) { if ((ctx->mStatus == DBG_STATUS_RUNNING) && !unsafe) { ret->push(markThreadNotStopped); gMutex.Unlock(); return ret; } StackContext *stack = ctx->mStackContext; // Some kind of error signalling here would be nice I guess if (stack->mStackFrames.size() <= stackFrameNumber) { break; } StackVariable *variable = stack->mStackFrames[stackFrameNumber]->variables; while (variable) { ret->push(String(variable->mHaxeName)); variable = variable->mNext; } #ifdef HXCPP_STACK_SCRIPTABLE StackFrame *scriptFrame = stack->mStackFrames[stackFrameNumber]; if (scriptFrame) __hxcpp_dbg_getScriptableVariables(scriptFrame, ret); #endif break; } } gMutex.Unlock(); return ret; } static Dynamic GetVariableValue(int threadNumber, int stackFrameNumber, String name, bool unsafe, Dynamic markNonexistent, Dynamic markThreadNotStopped) { if (threadNumber == g_debugThreadNumber) { return markNonexistent; } DebuggerContext *ctx; gMutex.Lock(); if (gMap.count(threadNumber) == 0) { gMutex.Unlock(); return markNonexistent; } else { ctx = gMap[threadNumber]; } if ((ctx->mStatus == DBG_STATUS_RUNNING) && !unsafe) { gMutex.Unlock(); return markThreadNotStopped; } // Don't need the lock any more, the thread is not running gMutex.Unlock(); StackContext *stack = ctx->mStackContext; // Check to ensure that the stack frame is valid int size = stack->mStackFrames.size(); if ((stackFrameNumber < 0) || (stackFrameNumber >= size)) { return markNonexistent; } const char *nameToFind = name.c_str(); StackVariable *sv = stack->mStackFrames[stackFrameNumber]->variables; while (sv) { if (!strcmp(sv->mHaxeName, nameToFind)) { return (Dynamic) *sv; } sv = sv->mNext; } #ifdef HXCPP_STACK_SCRIPTABLE StackFrame *scriptFrame = stack->mStackFrames[stackFrameNumber]; if (scriptFrame) { Dynamic result; if (__hxcpp_dbg_getScriptableValue(scriptFrame, name, result)) return result; } #endif return markNonexistent; } static Dynamic SetVariableValue(int threadNumber, int stackFrameNumber, String name, Dynamic value, bool unsafe, Dynamic markNonexistent, Dynamic markThreadNotStopped) { if (threadNumber == g_debugThreadNumber) { return null(); } DebuggerContext *ctx; gMutex.Lock(); if (gMap.count(threadNumber) == 0) { gMutex.Unlock(); return null(); } else { ctx = gMap[threadNumber]; } if ((ctx->mStatus == DBG_STATUS_RUNNING) && !unsafe) { gMutex.Unlock(); return markThreadNotStopped; } // Don't need the lock any more, the thread is not running gMutex.Unlock(); StackContext *stack = ctx->mStackContext; // Check to ensure that the stack frame is valid int size = stack->mStackFrames.size(); if ((stackFrameNumber < 0) || (stackFrameNumber >= size)) { return null(); } const char *nameToFind = name.c_str(); if (!strcmp(nameToFind, "this")) { return markNonexistent; } StackVariable *sv = stack->mStackFrames[stackFrameNumber]->variables; while (sv) { if (!strcmp(sv->mHaxeName, nameToFind)) { *sv = value; return (Dynamic) *sv; } sv = sv->mNext; } #ifdef HXCPP_STACK_SCRIPTABLE StackFrame *scriptFrame = stack->mStackFrames[stackFrameNumber]; if (scriptFrame) { if (__hxcpp_dbg_setScriptableValue(scriptFrame, name, value)) { __hxcpp_dbg_getScriptableValue(scriptFrame, name, value); return value; } } #endif return markNonexistent; } static bool CanBeCaught(Dynamic e) { hx::JustGcStackFrame frame; QuickVec &frames = frame.ctx->mStackFrames; for(int i=frames.size()-1; i>=0; i--) { StackCatchable *catchable = frames[i]->catchables; while (catchable) { if (catchable->Catches(e)) { return true; } catchable = catchable->mNext; } } return false; } } // namespace hx void __hxcpp_dbg_setEventNotificationHandler(Dynamic handler) { if (hx::g_eventNotificationHandler != null()) { GCRemoveRoot(&(hx::g_eventNotificationHandler.mPtr)); } hx::g_debugThreadNumber = __hxcpp_GetCurrentThreadNumber(); hx::g_eventNotificationHandler = handler; GCAddRoot(&(hx::g_eventNotificationHandler.mPtr)); } void __hxcpp_dbg_enableCurrentThreadDebugging(bool enable) { hx::JustGcStackFrame frame; frame.ctx->mDebugger->enable(enable); } int __hxcpp_dbg_getCurrentThreadNumber() { return __hxcpp_GetCurrentThreadNumber(); } Array< ::String> __hxcpp_dbg_getFiles() { Array< ::String> ret = Array_obj< ::String>::__new(); for (const char **ptr = hx::__hxcpp_all_files; *ptr; ptr++) { ret->push(String(*ptr)); } #ifdef HXCPP_SCRIPTABLE __hxcpp_dbg_getScriptableFiles(ret); #endif return ret; } Array< ::String> __hxcpp_dbg_getFilesFullPath() { Array< ::String> ret = Array_obj< ::String>::__new(); for (const char **ptr = __all_files_fullpath;ptr && *ptr; ptr++) { ret->push(String(*ptr)); } #ifdef HXCPP_SCRIPTABLE __hxcpp_dbg_getScriptableFilesFullPath(ret); #endif return ret; } Array< ::String> __hxcpp_dbg_getClasses() { Array< ::String> ret = Array_obj< ::String>::__new(); if (__all_classes) { for (const char **ptr = __all_classes; *ptr; ptr++) ret->push(String(*ptr)); } #ifdef HXCPP_SCRIPTABLE __hxcpp_dbg_getScriptableClasses(ret); #endif return ret; } Array __hxcpp_dbg_getThreadInfos() { return hx::GetThreadInfos(); } Dynamic __hxcpp_dbg_getThreadInfo(int threadNumber, bool unsafe) { return hx::GetThreadInfo(threadNumber, unsafe); } int __hxcpp_dbg_addFileLineBreakpoint(String fileName, int lineNumber) { return hx::Breakpoints::Add(fileName, lineNumber); } int __hxcpp_dbg_addClassFunctionBreakpoint(String className, String functionName) { return hx::Breakpoints::Add(className, functionName); } void __hxcpp_dbg_deleteAllBreakpoints() { hx::Breakpoints::DeleteAll(); } void __hxcpp_dbg_deleteBreakpoint(int number) { hx::Breakpoints::Delete(number); } void __hxcpp_dbg_breakNow(bool wait) { hx::Breakpoints::BreakNow(wait); } void __hxcpp_dbg_continueThreads(int specialThreadNumber, int count) { hx::Breakpoints::ContinueThreads(specialThreadNumber, count); } void __hxcpp_dbg_stepThread(int threadNumber, int stepType, int stepCount) { hx::Breakpoints::StepThread(threadNumber, (hx::StepType) stepType, stepCount); } Array __hxcpp_dbg_getStackVariables(int threadNumber, int stackFrameNumber, bool unsafe, Dynamic markThreadNotStopped) { return hx::GetStackVariables(threadNumber, stackFrameNumber, unsafe, markThreadNotStopped); } Dynamic __hxcpp_dbg_getStackVariableValue(int threadNumber, int stackFrameNumber, String name, bool unsafe, Dynamic markNonexistent, Dynamic markThreadNotStopped) { return hx::GetVariableValue(threadNumber, stackFrameNumber, name, unsafe, markNonexistent, markThreadNotStopped); } Dynamic __hxcpp_dbg_setStackVariableValue(int threadNumber, int stackFrameNumber, String name, Dynamic value, bool unsafe, Dynamic markNonexistent, Dynamic markThreadNotStopped) { return hx::SetVariableValue(threadNumber, stackFrameNumber, name, value, unsafe, markNonexistent, markThreadNotStopped); } void __hxcpp_dbg_setNewParameterFunction(Dynamic function) { hx::g_newParameterFunction = function; GCAddRoot(&(hx::g_newParameterFunction.mPtr)); } void __hxcpp_dbg_setNewStackFrameFunction(Dynamic function) { hx::g_newStackFrameFunction = function; GCAddRoot(&(hx::g_newStackFrameFunction.mPtr)); } void __hxcpp_dbg_setNewThreadInfoFunction(Dynamic function) { hx::g_newThreadInfoFunction = function; GCAddRoot(&(hx::g_newThreadInfoFunction.mPtr)); } void __hxcpp_dbg_setAddParameterToStackFrameFunction(Dynamic function) { hx::g_addParameterToStackFrameFunction = function; GCAddRoot(&(hx::g_addParameterToStackFrameFunction.mPtr)); } void __hxcpp_dbg_setAddStackFrameToThreadInfoFunction(Dynamic function) { hx::g_addStackFrameToThreadInfoFunction = function; GCAddRoot(&(hx::g_addStackFrameToThreadInfoFunction.mPtr)); } Dynamic __hxcpp_dbg_checkedThrow(Dynamic toThrow) { if (!hx::CanBeCaught(toThrow)) hx::CriticalError(HX_CSTRING("Uncatchable Throw: ") + toThrow->toString(),true); return hx::Throw(toThrow); } Dynamic __hxcpp_dbg_checkedRethrow(Dynamic toThrow) { if (!hx::CanBeCaught(toThrow)) hx::CriticalError(HX_CSTRING("Uncatchable Throw: ") + toThrow->toString(),true); return hx::Rethrow(toThrow); } void __hxcpp_on_line_changed(hx::StackContext *stack) { hx::Breakpoints::HandleBreakpoints(stack); if (hx::sExecutionTrace==hx::exeTraceLines) stack->tracePosition(); } bool __hxcpp_dbg_fix_critical_error(String inErr) { if (hx::g_eventNotificationHandler != null()) { hx::JustGcStackFrame frame; hx::DebuggerContext *stack = frame.ctx->mDebugger; //if the thread with the critical error is the debugger one, //we don't break as it would block debugging since the debugger thread //is the only one which can wake up application threads. if (stack->mThreadNumber == hx::g_debugThreadNumber) { hx::Throw(HX_CSTRING("Critical Error in the debugger thread")); } stack->DoBreak(hx::DBG_STATUS_STOPPED_CRITICAL_ERROR, -1, &inErr); return true; } return false; } void __hxcpp_execution_trace(int inLevel) { hx::sExecutionTrace = (hx::ExecutionTrace)inLevel; hx::gShouldCallHandleBreakpoints = hx::Breakpoints::shoudBreakOnLine() || (hx::sExecutionTrace==hx::exeTraceLines); } void __hxcpp_set_debugger_info(const char **inAllClasses, const char **inFullPaths) { __all_classes = inAllClasses; __all_files_fullpath = inFullPaths; }