| //------------------------------------------------------------------------------------------------------- |
| // Copyright (C) Microsoft. All rights reserved. |
| // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. |
| //------------------------------------------------------------------------------------------------------- |
| |
| #include "JsrtPch.h" |
| #ifdef ENABLE_SCRIPT_DEBUGGING |
| #include "JsrtDebugManager.h" |
| #include "JsrtDebugEventObject.h" |
| #include "JsrtDebugUtils.h" |
| #include "JsrtDebuggerObject.h" |
| #include "RuntimeDebugPch.h" |
| #include "screrror.h" // For CompileScriptException |
| |
| JsrtDebugManager::JsrtDebugManager(ThreadContext* threadContext) : |
| HostDebugContext(nullptr), |
| threadContext(threadContext), |
| debugEventCallback(nullptr), |
| callbackState(nullptr), |
| resumeAction(BREAKRESUMEACTION_CONTINUE), |
| debugObjectArena(nullptr), |
| debuggerObjectsManager(nullptr), |
| debugDocumentManager(nullptr), |
| stackFrames(nullptr), |
| breakOnExceptionAttributes(JsDiagBreakOnExceptionAttributeUncaught) |
| { |
| Assert(threadContext != nullptr); |
| } |
| |
| JsrtDebugManager::~JsrtDebugManager() |
| { |
| if (this->debuggerObjectsManager != nullptr) |
| { |
| Adelete(this->debugObjectArena, this->debuggerObjectsManager); |
| this->debuggerObjectsManager = nullptr; |
| } |
| |
| if (this->debugDocumentManager != nullptr) |
| { |
| Adelete(this->debugObjectArena, this->debugDocumentManager); |
| this->debugDocumentManager = nullptr; |
| } |
| |
| if (this->debugObjectArena != nullptr) |
| { |
| this->threadContext->GetRecycler()->UnregisterExternalGuestArena(this->debugObjectArena); |
| HeapDelete(this->debugObjectArena); |
| this->debugObjectArena = nullptr; |
| } |
| |
| this->debugEventCallback = nullptr; |
| this->callbackState = nullptr; |
| this->threadContext = nullptr; |
| } |
| |
| void JsrtDebugManager::SetDebugEventCallback(JsDiagDebugEventCallback debugEventCallback, void* callbackState) |
| { |
| Assert(this->debugEventCallback == nullptr); |
| Assert(this->callbackState == nullptr); |
| |
| this->debugEventCallback = debugEventCallback; |
| this->callbackState = callbackState; |
| } |
| |
| void* JsrtDebugManager::GetAndClearCallbackState() |
| { |
| void* currentCallbackState = this->callbackState; |
| |
| this->debugEventCallback = nullptr; |
| this->callbackState = nullptr; |
| |
| return currentCallbackState; |
| } |
| |
| bool JsrtDebugManager::IsDebugEventCallbackSet() const |
| { |
| return this->debugEventCallback != nullptr; |
| } |
| |
| bool JsrtDebugManager::CanHalt(Js::InterpreterHaltState* haltState) |
| { |
| // This is registered as the callback for inline breakpoints. |
| // We decide here if we are at a reasonable stop location that has source code. |
| Assert(haltState->IsValid()); |
| |
| Js::FunctionBody* pCurrentFuncBody = haltState->GetFunction(); |
| int byteOffset = haltState->GetCurrentOffset(); |
| Js::FunctionBody::StatementMap* map = pCurrentFuncBody->GetMatchingStatementMapFromByteCode(byteOffset, false); |
| |
| // Resolve the dummy ret code. |
| return map != nullptr && (!pCurrentFuncBody->GetIsGlobalFunc() || !Js::FunctionBody::IsDummyGlobalRetStatement(&map->sourceSpan)); |
| } |
| |
| void JsrtDebugManager::DispatchHalt(Js::InterpreterHaltState* haltState) |
| { |
| switch (haltState->stopType) |
| { |
| case Js::STOP_BREAKPOINT: /*JsDiagDebugEventBreakpoint*/ |
| case Js::STOP_INLINEBREAKPOINT: /*JsDiagDebugEventDebuggerStatement*/ |
| case Js::STOP_ASYNCBREAK: /*JsDiagDebugEventAsyncBreak*/ |
| this->ReportBreak(haltState); |
| break; |
| case Js::STOP_STEPCOMPLETE: /*JsDiagDebugEventStepComplete*/ |
| this->SetResumeType(BREAKRESUMEACTION_CONTINUE); |
| this->ReportBreak(haltState); |
| break; |
| case Js::STOP_EXCEPTIONTHROW: /*JsDiagDebugEventRuntimeException*/ |
| this->ReportExceptionBreak(haltState); |
| break; |
| case Js::STOP_DOMMUTATIONBREAKPOINT: |
| case Js::STOP_MUTATIONBREAKPOINT: |
| AssertMsg(false, "Not yet handled"); |
| break; |
| default: |
| AssertMsg(false, "Unhandled stop type"); |
| } |
| |
| this->HandleResume(haltState, this->resumeAction); |
| } |
| |
| bool JsrtDebugManager::CanAllowBreakpoints() |
| { |
| return true; |
| } |
| |
| void JsrtDebugManager::CleanupHalt() |
| { |
| } |
| |
| bool JsrtDebugManager::IsInClosedState() |
| { |
| return this->debugEventCallback == nullptr; |
| } |
| |
| bool JsrtDebugManager::IsExceptionReportingEnabled() |
| { |
| return this->GetBreakOnException() != JsDiagBreakOnExceptionAttributeNone; |
| } |
| |
| bool JsrtDebugManager::IsFirstChanceExceptionEnabled() |
| { |
| return (this->GetBreakOnException() & JsDiagBreakOnExceptionAttributeFirstChance) == JsDiagBreakOnExceptionAttributeFirstChance; |
| } |
| |
| HRESULT JsrtDebugManager::DbgRegisterFunction(Js::ScriptContext* scriptContext, Js::FunctionBody* functionBody, DWORD_PTR dwDebugSourceContext, LPCWSTR title) |
| { |
| Js::Utf8SourceInfo* utf8SourceInfo = functionBody->GetUtf8SourceInfo(); |
| |
| if (!utf8SourceInfo->GetIsLibraryCode() && !utf8SourceInfo->HasDebugDocument()) |
| { |
| JsrtDebugDocumentManager* debugDocumentManager = this->GetDebugDocumentManager(); |
| Assert(debugDocumentManager != nullptr); |
| |
| Js::DebugDocument* debugDocument = HeapNewNoThrow(Js::DebugDocument, utf8SourceInfo, functionBody); |
| if (debugDocument != nullptr) |
| { |
| utf8SourceInfo->SetDebugDocument(debugDocument); |
| } |
| |
| // Raising events during the middle of a source reparse allows the host to reenter the |
| // script context and cause memory race conditions. Suppressing these events during a |
| // reparse prevents the issue. Since the host was already expected to call JsDiagGetScripts |
| // once the attach is completed to get the list of parsed scripts, there is no change in |
| // behavior. |
| if (this->debugEventCallback != nullptr && |
| !scriptContext->GetDebugContext()->GetIsReparsingSource()) |
| { |
| JsrtDebugEventObject debugEventObject(scriptContext); |
| Js::DynamicObject* eventDataObject = debugEventObject.GetEventDataObject(); |
| JsrtDebugUtils::AddSourceMetadataToObject(eventDataObject, utf8SourceInfo); |
| |
| this->CallDebugEventCallback(JsDiagDebugEventSourceCompile, eventDataObject, scriptContext, false /*isBreak*/); |
| } |
| } |
| |
| return S_OK; |
| } |
| |
| #if ENABLE_TTD |
| void JsrtDebugManager::ReportScriptCompile_TTD(Js::FunctionBody* body, Js::Utf8SourceInfo* utf8SourceInfo, CompileScriptException* compileException, bool notify) |
| { |
| if(this->debugEventCallback == nullptr) |
| { |
| return; |
| } |
| |
| Js::ScriptContext* scriptContext = utf8SourceInfo->GetScriptContext(); |
| |
| JsrtDebugEventObject debugEventObject(scriptContext); |
| Js::DynamicObject* eventDataObject = debugEventObject.GetEventDataObject(); |
| |
| JsrtDebugDocumentManager* debugDocumentManager = this->GetDebugDocumentManager(); |
| Assert(debugDocumentManager != nullptr); |
| |
| // Create DebugDocument and then report JsDiagDebugEventSourceCompile event |
| Js::DebugDocument* debugDocument = HeapNewNoThrow(Js::DebugDocument, utf8SourceInfo, body); |
| if(debugDocument != nullptr) |
| { |
| utf8SourceInfo->SetDebugDocument(debugDocument); |
| } |
| |
| JsrtDebugUtils::AddSourceMetadataToObject(eventDataObject, utf8SourceInfo); |
| |
| if(notify) |
| { |
| this->CallDebugEventCallback(JsDiagDebugEventSourceCompile, eventDataObject, scriptContext, false /*isBreak*/); |
| } |
| } |
| #endif |
| |
| void JsrtDebugManager::ReportScriptCompile(Js::JavascriptFunction* scriptFunction, Js::Utf8SourceInfo* utf8SourceInfo, CompileScriptException* compileException) |
| { |
| if (this->debugEventCallback != nullptr) |
| { |
| Js::ScriptContext* scriptContext = utf8SourceInfo->GetScriptContext(); |
| |
| JsrtDebugEventObject debugEventObject(scriptContext); |
| Js::DynamicObject* eventDataObject = debugEventObject.GetEventDataObject(); |
| |
| JsDiagDebugEvent jsDiagDebugEvent = JsDiagDebugEventCompileError; |
| |
| if (scriptFunction == nullptr) |
| { |
| // Report JsDiagDebugEventCompileError event |
| JsrtDebugUtils::AddPropertyToObject(eventDataObject, JsrtDebugPropertyId::error, compileException->ei.bstrDescription, ::SysStringLen(compileException->ei.bstrDescription), scriptContext); |
| JsrtDebugUtils::AddPropertyToObject(eventDataObject, JsrtDebugPropertyId::line, compileException->line, scriptContext); |
| JsrtDebugUtils::AddPropertyToObject(eventDataObject, JsrtDebugPropertyId::column, compileException->ichMin - compileException->ichMinLine - 1, scriptContext); // Converted to 0-based |
| JsrtDebugUtils::AddPropertyToObject(eventDataObject, JsrtDebugPropertyId::sourceText, compileException->bstrLine, ::SysStringLen(compileException->bstrLine), scriptContext); |
| } |
| else |
| { |
| JsrtDebugDocumentManager* debugDocumentManager = this->GetDebugDocumentManager(); |
| Assert(debugDocumentManager != nullptr); |
| |
| // Create DebugDocument and then report JsDiagDebugEventSourceCompile event |
| Js::DebugDocument* debugDocument = HeapNewNoThrow(Js::DebugDocument, utf8SourceInfo, scriptFunction->GetFunctionBody()); |
| if (debugDocument != nullptr) |
| { |
| utf8SourceInfo->SetDebugDocument(debugDocument); |
| } |
| |
| jsDiagDebugEvent = JsDiagDebugEventSourceCompile; |
| } |
| |
| JsrtDebugUtils::AddSourceMetadataToObject(eventDataObject, utf8SourceInfo); |
| |
| this->CallDebugEventCallback(jsDiagDebugEvent, eventDataObject, scriptContext, false /*isBreak*/); |
| } |
| } |
| |
| void JsrtDebugManager::ReportBreak(Js::InterpreterHaltState* haltState) |
| { |
| if (this->debugEventCallback != nullptr) |
| { |
| Js::FunctionBody* functionBody = haltState->GetFunction(); |
| Assert(functionBody != nullptr); |
| |
| Js::Utf8SourceInfo* utf8SourceInfo = functionBody->GetUtf8SourceInfo(); |
| int currentByteCodeOffset = haltState->GetCurrentOffset(); |
| Js::ScriptContext* scriptContext = utf8SourceInfo->GetScriptContext(); |
| |
| JsDiagDebugEvent jsDiagDebugEvent = this->GetDebugEventFromStopType(haltState->stopType); |
| |
| JsrtDebugEventObject debugEventObject(scriptContext); |
| |
| Js::DynamicObject* eventDataObject = debugEventObject.GetEventDataObject(); |
| |
| Js::ProbeContainer* probeContainer = scriptContext->GetDebugContext()->GetProbeContainer(); |
| |
| if (jsDiagDebugEvent == JsDiagDebugEventBreakpoint) |
| { |
| UINT bpId = 0; |
| probeContainer->MapProbesUntil([&](int i, Js::Probe* pProbe) |
| { |
| Js::BreakpointProbe* bp = (Js::BreakpointProbe*)pProbe; |
| if (bp->Matches(functionBody, utf8SourceInfo->GetDebugDocument(), currentByteCodeOffset)) |
| { |
| bpId = bp->GetId(); |
| return true; |
| } |
| return false; |
| }); |
| |
| AssertMsg(bpId != 0, "How come we don't have a breakpoint id for JsDiagDebugEventBreakpoint"); |
| |
| JsrtDebugUtils::AddPropertyToObject(eventDataObject, JsrtDebugPropertyId::breakpointId, bpId, scriptContext); |
| } |
| |
| JsrtDebugUtils::AddScriptIdToObject(eventDataObject, utf8SourceInfo); |
| JsrtDebugUtils::AddLineColumnToObject(eventDataObject, functionBody, currentByteCodeOffset); |
| JsrtDebugUtils::AddSourceLengthAndTextToObject(eventDataObject, functionBody, currentByteCodeOffset); |
| |
| this->CallDebugEventCallbackForBreak(jsDiagDebugEvent, eventDataObject, scriptContext); |
| } |
| } |
| |
| void JsrtDebugManager::ReportExceptionBreak(Js::InterpreterHaltState* haltState) |
| { |
| if (this->debugEventCallback != nullptr) |
| { |
| Assert(haltState->stopType == Js::STOP_EXCEPTIONTHROW); |
| |
| Js::Utf8SourceInfo* utf8SourceInfo = haltState->GetFunction()->GetUtf8SourceInfo(); |
| Js::ScriptContext* scriptContext = utf8SourceInfo->GetScriptContext(); |
| |
| JsDiagDebugEvent jsDiagDebugEvent = JsDiagDebugEventRuntimeException; |
| |
| JsrtDebugEventObject debugEventObject(scriptContext); |
| |
| Js::DynamicObject* eventDataObject = debugEventObject.GetEventDataObject(); |
| |
| JsrtDebugUtils::AddScriptIdToObject(eventDataObject, utf8SourceInfo); |
| |
| Js::FunctionBody* functionBody = haltState->topFrame->GetFunction(); |
| |
| Assert(functionBody != nullptr); |
| |
| int currentByteCodeOffset = haltState->topFrame->GetByteCodeOffset(); |
| JsrtDebugUtils::AddLineColumnToObject(eventDataObject, functionBody, currentByteCodeOffset); |
| JsrtDebugUtils::AddSourceLengthAndTextToObject(eventDataObject, functionBody, currentByteCodeOffset); |
| JsrtDebugUtils::AddPropertyToObject(eventDataObject, JsrtDebugPropertyId::uncaught, !haltState->exceptionObject->IsFirstChanceException(), scriptContext); |
| |
| Js::ResolvedObject resolvedObject; |
| resolvedObject.scriptContext = scriptContext; |
| resolvedObject.name = _u("{exception}"); |
| resolvedObject.typeId = Js::TypeIds_Error; |
| resolvedObject.address = nullptr; |
| resolvedObject.obj = scriptContext->GetDebugContext()->GetProbeContainer()->GetExceptionObject(); |
| |
| if (resolvedObject.obj == nullptr) |
| { |
| resolvedObject.obj = resolvedObject.scriptContext->GetLibrary()->GetUndefined(); |
| } |
| |
| JsrtDebuggerObjectBase::CreateDebuggerObject<JsrtDebuggerObjectProperty>(this->GetDebuggerObjectsManager(), resolvedObject, scriptContext, /* forceSetValueProp */ false, [&](Js::Var marshaledObj) |
| { |
| JsrtDebugUtils::AddPropertyToObject(eventDataObject, JsrtDebugPropertyId::exception, marshaledObj, scriptContext); |
| }); |
| |
| this->CallDebugEventCallbackForBreak(jsDiagDebugEvent, eventDataObject, scriptContext); |
| } |
| } |
| |
| void JsrtDebugManager::HandleResume(Js::InterpreterHaltState* haltState, BREAKRESUMEACTION resumeAction) |
| { |
| Assert(resumeAction != BREAKRESUMEACTION_ABORT); |
| |
| Js::ScriptContext* scriptContext = haltState->framePointers->Peek()->GetScriptContext(); |
| |
| scriptContext->GetThreadContext()->GetDebugManager()->stepController.HandleResumeAction(haltState, resumeAction); |
| } |
| |
| void JsrtDebugManager::SetResumeType(BREAKRESUMEACTION resumeAction) |
| { |
| this->resumeAction = resumeAction; |
| } |
| |
| bool JsrtDebugManager::EnableAsyncBreak(Js::ScriptContext* scriptContext) |
| { |
| if (!scriptContext->IsDebugContextInitialized()) |
| { |
| // Although the script context exists, it hasn't been fully initialized yet. |
| return false; |
| } |
| |
| Js::ProbeContainer* probeContainer = scriptContext->GetDebugContext()->GetProbeContainer(); |
| |
| // This can be called when we are already at break |
| if (!probeContainer->IsAsyncActivate()) |
| { |
| probeContainer->AsyncActivate(this); |
| if (Js::Configuration::Global.EnableJitInDebugMode()) |
| { |
| scriptContext->GetThreadContext()->GetDebugManager()->GetDebuggingFlags()->SetForceInterpreter(true); |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| void JsrtDebugManager::CallDebugEventCallback(JsDiagDebugEvent debugEvent, Js::DynamicObject* eventDataObject, Js::ScriptContext* scriptContext, bool isBreak) |
| { |
| class AutoClear |
| { |
| public: |
| AutoClear(JsrtDebugManager* jsrtDebug, void* dispatchHaltFrameAddress) |
| { |
| this->jsrtDebugManager = jsrtDebug; |
| this->jsrtDebugManager->GetThreadContext()->GetDebugManager()->SetDispatchHaltFrameAddress(dispatchHaltFrameAddress); |
| } |
| |
| ~AutoClear() |
| { |
| if (jsrtDebugManager->debuggerObjectsManager != nullptr) |
| { |
| jsrtDebugManager->GetDebuggerObjectsManager()->ClearAll(); |
| } |
| |
| if (jsrtDebugManager->stackFrames != nullptr) |
| { |
| Adelete(jsrtDebugManager->GetDebugObjectArena(), jsrtDebugManager->stackFrames); |
| jsrtDebugManager->stackFrames = nullptr; |
| } |
| this->jsrtDebugManager->GetThreadContext()->GetDebugManager()->SetDispatchHaltFrameAddress(nullptr); |
| this->jsrtDebugManager = nullptr; |
| } |
| private: |
| JsrtDebugManager* jsrtDebugManager; |
| }; |
| |
| auto funcPtr = [&]() |
| { |
| if (isBreak) |
| { |
| void *frameAddress = _AddressOfReturnAddress(); |
| // If we are reporting break we should clear all objects after call returns |
| |
| // Save the frame address, when asking for stack we will only give stack which is under this address |
| // because host can execute javascript after break which should not be part of stack. |
| AutoClear autoClear(this, frameAddress); |
| this->debugEventCallback(debugEvent, eventDataObject, this->callbackState); |
| } |
| else |
| { |
| this->debugEventCallback(debugEvent, eventDataObject, this->callbackState); |
| } |
| }; |
| |
| if (scriptContext->GetThreadContext()->IsScriptActive()) |
| { |
| BEGIN_LEAVE_SCRIPT(scriptContext) |
| { |
| funcPtr(); |
| } |
| END_LEAVE_SCRIPT(scriptContext); |
| } |
| else |
| { |
| funcPtr(); |
| } |
| } |
| |
| void JsrtDebugManager::CallDebugEventCallbackForBreak(JsDiagDebugEvent debugEvent, Js::DynamicObject* eventDataObject, Js::ScriptContext* scriptContext) |
| { |
| AutoSetDispatchHaltFlag autoSetDispatchHaltFlag(scriptContext, scriptContext->GetThreadContext()); |
| |
| this->CallDebugEventCallback(debugEvent, eventDataObject, scriptContext, true /*isBreak*/); |
| |
| for (Js::ScriptContext *tempScriptContext = scriptContext->GetThreadContext()->GetScriptContextList(); |
| tempScriptContext != nullptr && !tempScriptContext->IsClosed(); |
| tempScriptContext = tempScriptContext->next) |
| { |
| tempScriptContext->GetDebugContext()->GetProbeContainer()->AsyncDeactivate(); |
| } |
| |
| if (Js::Configuration::Global.EnableJitInDebugMode()) |
| { |
| scriptContext->GetThreadContext()->GetDebugManager()->GetDebuggingFlags()->SetForceInterpreter(false); |
| } |
| } |
| |
| Js::DynamicObject* JsrtDebugManager::GetScript(Js::Utf8SourceInfo* utf8SourceInfo) |
| { |
| Js::DynamicObject* scriptObject = utf8SourceInfo->GetScriptContext()->GetLibrary()->CreateObject(); |
| JsrtDebugUtils::AddSourceMetadataToObject(scriptObject, utf8SourceInfo); |
| |
| return scriptObject; |
| } |
| |
| Js::JavascriptArray* JsrtDebugManager::GetScripts(Js::ScriptContext* scriptContext) |
| { |
| Js::JavascriptArray* scriptsArray = scriptContext->GetLibrary()->CreateArray(); |
| |
| int index = 0; |
| |
| for (Js::ScriptContext *tempScriptContext = scriptContext->GetThreadContext()->GetScriptContextList(); |
| tempScriptContext != nullptr && !tempScriptContext->IsClosed(); |
| tempScriptContext = tempScriptContext->next) |
| { |
| tempScriptContext->MapScript([&](Js::Utf8SourceInfo* utf8SourceInfo) |
| { |
| if (!utf8SourceInfo->GetIsLibraryCode() && utf8SourceInfo->HasDebugDocument()) |
| { |
| bool isCallerLibraryCode = false; |
| |
| bool isDynamic = utf8SourceInfo->IsDynamic(); |
| |
| if (isDynamic) |
| { |
| // If the code is dynamic (eval or new Function) only return the script if parent is non-library |
| Js::Utf8SourceInfo* callerUtf8SourceInfo = utf8SourceInfo->GetCallerUtf8SourceInfo(); |
| |
| while (callerUtf8SourceInfo != nullptr && !isCallerLibraryCode) |
| { |
| isCallerLibraryCode = callerUtf8SourceInfo->GetIsLibraryCode(); |
| callerUtf8SourceInfo = callerUtf8SourceInfo->GetCallerUtf8SourceInfo(); |
| } |
| } |
| |
| if (!isCallerLibraryCode) |
| { |
| Js::DynamicObject* sourceObj = this->GetScript(utf8SourceInfo); |
| |
| if (sourceObj != nullptr) |
| { |
| Js::Var marshaledObj = Js::CrossSite::MarshalVar(scriptContext, sourceObj); |
| scriptsArray->DirectSetItemAt(index, marshaledObj); |
| index++; |
| } |
| } |
| } |
| }); |
| } |
| |
| return scriptsArray; |
| } |
| |
| Js::DynamicObject* JsrtDebugManager::GetSource(Js::ScriptContext* scriptContext, uint scriptId) |
| { |
| Js::Utf8SourceInfo* utf8SourceInfo = nullptr; |
| |
| for (Js::ScriptContext *tempScriptContext = this->threadContext->GetScriptContextList(); |
| tempScriptContext != nullptr && utf8SourceInfo == nullptr && !tempScriptContext->IsClosed(); |
| tempScriptContext = tempScriptContext->next) |
| { |
| tempScriptContext->MapScript([&](Js::Utf8SourceInfo* sourceInfo) -> bool |
| { |
| if (sourceInfo->IsInDebugMode() && sourceInfo->GetSourceInfoId() == scriptId) |
| { |
| utf8SourceInfo = sourceInfo; |
| return true; |
| } |
| return false; |
| }); |
| } |
| |
| Js::DynamicObject* sourceObject = nullptr; |
| |
| if (utf8SourceInfo != nullptr) |
| { |
| sourceObject = (Js::DynamicObject*)Js::CrossSite::MarshalVar(utf8SourceInfo->GetScriptContext(), scriptContext->GetLibrary()->CreateObject()); |
| |
| JsrtDebugUtils::AddSourceMetadataToObject(sourceObject, utf8SourceInfo); |
| JsrtDebugUtils::AddSourceToObject(sourceObject, utf8SourceInfo); |
| } |
| |
| return sourceObject; |
| } |
| |
| Js::JavascriptArray* JsrtDebugManager::GetStackFrames(Js::ScriptContext* scriptContext) |
| { |
| if (this->stackFrames == nullptr) |
| { |
| this->stackFrames = Anew(this->GetDebugObjectArena(), JsrtDebugStackFrames, this); |
| } |
| |
| return this->stackFrames->StackFrames(scriptContext); |
| } |
| |
| bool JsrtDebugManager::TryGetFrameObjectFromFrameIndex(Js::ScriptContext *scriptContext, uint frameIndex, JsrtDebuggerStackFrame ** debuggerStackFrame) |
| { |
| if (this->stackFrames == nullptr) |
| { |
| this->GetStackFrames(scriptContext); |
| } |
| |
| return this->stackFrames->TryGetFrameObjectFromFrameIndex(frameIndex, debuggerStackFrame); |
| } |
| |
| Js::DynamicObject* JsrtDebugManager::SetBreakPoint(Js::ScriptContext* scriptContext, Js::Utf8SourceInfo* utf8SourceInfo, UINT lineNumber, UINT columnNumber) |
| { |
| Js::DebugDocument* debugDocument = utf8SourceInfo->GetDebugDocument(); |
| if (debugDocument != nullptr && SUCCEEDED(utf8SourceInfo->EnsureLineOffsetCacheNoThrow()) && lineNumber < utf8SourceInfo->GetLineCount()) |
| { |
| charcount_t charPosition = 0; |
| charcount_t byteOffset = 0; |
| utf8SourceInfo->GetCharPositionForLineInfo((charcount_t)lineNumber, &charPosition, &byteOffset); |
| long ibos = charPosition + columnNumber + 1; |
| |
| Js::StatementLocation statement; |
| if (!debugDocument->GetStatementLocation(ibos, &statement)) |
| { |
| return nullptr; |
| } |
| |
| // Don't see a use case for supporting multiple breakpoints at same location. |
| // If a breakpoint already exists, just return that |
| Js::BreakpointProbe* probe = debugDocument->FindBreakpoint(statement); |
| if (probe == nullptr) |
| { |
| probe = debugDocument->SetBreakPoint(statement, BREAKPOINT_ENABLED); |
| |
| if(probe == nullptr) |
| { |
| return nullptr; |
| } |
| |
| this->GetDebugDocumentManager()->AddDocument(probe->GetId(), debugDocument); |
| } |
| |
| probe->GetStatementLocation(&statement); |
| |
| Js::DynamicObject* bpObject = (Js::DynamicObject*)Js::CrossSite::MarshalVar(debugDocument->GetUtf8SourceInfo()->GetScriptContext(), scriptContext->GetLibrary()->CreateObject()); |
| |
| JsrtDebugUtils::AddPropertyToObject(bpObject, JsrtDebugPropertyId::breakpointId, probe->GetId(), scriptContext); |
| JsrtDebugUtils::AddLineColumnToObject(bpObject, statement.function, statement.bytecodeSpan.begin); |
| JsrtDebugUtils::AddScriptIdToObject(bpObject, utf8SourceInfo); |
| |
| return bpObject; |
| } |
| |
| return nullptr; |
| } |
| |
| void JsrtDebugManager::GetBreakpoints(Js::JavascriptArray** bpsArray, Js::ScriptContext* scriptContext) |
| { |
| Js::ScriptContext* arrayScriptContext = (*bpsArray)->GetScriptContext(); |
| Js::ProbeContainer* probeContainer = scriptContext->GetDebugContext()->GetProbeContainer(); |
| |
| probeContainer->MapProbes([&](int i, Js::Probe* pProbe) |
| { |
| Js::BreakpointProbe* bp = (Js::BreakpointProbe*)pProbe; |
| Js::DynamicObject* bpObject = scriptContext->GetLibrary()->CreateObject(); |
| |
| JsrtDebugUtils::AddPropertyToObject(bpObject, JsrtDebugPropertyId::breakpointId, bp->GetId(), scriptContext); |
| JsrtDebugUtils::AddLineColumnToObject(bpObject, bp->GetFunctionBody(), bp->GetBytecodeOffset()); |
| |
| Js::Utf8SourceInfo* utf8SourceInfo = bp->GetDbugDocument()->GetUtf8SourceInfo(); |
| JsrtDebugUtils::AddScriptIdToObject(bpObject, utf8SourceInfo); |
| |
| Js::Var marshaledObj = Js::CrossSite::MarshalVar(arrayScriptContext, bpObject); |
| Js::JavascriptOperators::OP_SetElementI((Js::Var)(*bpsArray), Js::JavascriptNumber::ToVar((*bpsArray)->GetLength(), arrayScriptContext), marshaledObj, arrayScriptContext); |
| }); |
| } |
| |
| #if ENABLE_TTD |
| Js::BreakpointProbe* JsrtDebugManager::SetBreakpointHelper_TTD(int64 desiredBpId, Js::ScriptContext* scriptContext, Js::Utf8SourceInfo* utf8SourceInfo, UINT lineNumber, UINT columnNumber, BOOL* isNewBP) |
| { |
| *isNewBP = FALSE; |
| Js::DebugDocument* debugDocument = utf8SourceInfo->GetDebugDocument(); |
| if(debugDocument != nullptr && SUCCEEDED(utf8SourceInfo->EnsureLineOffsetCacheNoThrow()) && lineNumber < utf8SourceInfo->GetLineCount()) |
| { |
| charcount_t charPosition = 0; |
| charcount_t byteOffset = 0; |
| utf8SourceInfo->GetCharPositionForLineInfo((charcount_t)lineNumber, &charPosition, &byteOffset); |
| long ibos = charPosition + columnNumber + 1; |
| |
| Js::StatementLocation statement; |
| if(!debugDocument->GetStatementLocation(ibos, &statement)) |
| { |
| return nullptr; |
| } |
| |
| // Don't see a use case for supporting multiple breakpoints at same location. |
| // If a breakpoint already exists, just return that |
| Js::BreakpointProbe* probe = debugDocument->FindBreakpoint(statement); |
| TTDAssert(probe == nullptr || desiredBpId == -1, "We shouldn't be resetting this BP unless it was cleared earlier!"); |
| |
| if(probe == nullptr) |
| { |
| probe = debugDocument->SetBreakPoint_TTDWbpId(desiredBpId, statement); |
| *isNewBP = TRUE; |
| |
| this->GetDebugDocumentManager()->AddDocument(probe->GetId(), debugDocument); |
| } |
| |
| return probe; |
| } |
| |
| return nullptr; |
| } |
| #endif |
| |
| JsrtDebuggerObjectsManager* JsrtDebugManager::GetDebuggerObjectsManager() |
| { |
| if (this->debuggerObjectsManager == nullptr) |
| { |
| this->debuggerObjectsManager = Anew(this->GetDebugObjectArena(), JsrtDebuggerObjectsManager, this); |
| } |
| return this->debuggerObjectsManager; |
| } |
| |
| void JsrtDebugManager::ClearDebuggerObjects() |
| { |
| if (this->debuggerObjectsManager != nullptr) |
| { |
| this->debuggerObjectsManager->ClearAll(); |
| } |
| } |
| |
| ArenaAllocator* JsrtDebugManager::GetDebugObjectArena() |
| { |
| if (this->debugObjectArena == nullptr) |
| { |
| this->debugObjectArena = HeapNew(ArenaAllocator, _u("DebugObjectArena"), this->threadContext->GetPageAllocator(), Js::Throw::OutOfMemory); |
| |
| this->threadContext->GetRecycler()->RegisterExternalGuestArena(this->debugObjectArena); |
| } |
| |
| return this->debugObjectArena; |
| } |
| |
| JsrtDebugDocumentManager* JsrtDebugManager::GetDebugDocumentManager() |
| { |
| if (this->debugDocumentManager == nullptr) |
| { |
| this->debugDocumentManager = Anew(this->GetDebugObjectArena(), JsrtDebugDocumentManager, this); |
| } |
| return this->debugDocumentManager; |
| } |
| |
| void JsrtDebugManager::ClearDebugDocument(Js::ScriptContext* scriptContext) |
| { |
| if (this->debugDocumentManager != nullptr) |
| { |
| this->debugDocumentManager->ClearDebugDocument(scriptContext); |
| } |
| } |
| |
| void JsrtDebugManager::ClearBreakpointDebugDocumentDictionary() |
| { |
| if (this->debugDocumentManager != nullptr) |
| { |
| this->debugDocumentManager->ClearBreakpointDebugDocumentDictionary(); |
| } |
| } |
| |
| bool JsrtDebugManager::RemoveBreakpoint(UINT breakpointId) |
| { |
| if (this->debugDocumentManager != nullptr) |
| { |
| return this->GetDebugDocumentManager()->RemoveBreakpoint(breakpointId); |
| } |
| |
| return false; |
| } |
| |
| void JsrtDebugManager::SetBreakOnException(JsDiagBreakOnExceptionAttributes exceptionAttributes) |
| { |
| this->breakOnExceptionAttributes = exceptionAttributes; |
| } |
| |
| JsDiagBreakOnExceptionAttributes JsrtDebugManager::GetBreakOnException() |
| { |
| return this->breakOnExceptionAttributes; |
| } |
| |
| JsDiagDebugEvent JsrtDebugManager::GetDebugEventFromStopType(Js::StopType stopType) |
| { |
| switch (stopType) |
| { |
| case Js::STOP_BREAKPOINT: return JsDiagDebugEventBreakpoint; |
| case Js::STOP_INLINEBREAKPOINT: return JsDiagDebugEventDebuggerStatement; |
| case Js::STOP_STEPCOMPLETE: return JsDiagDebugEventStepComplete; |
| case Js::STOP_EXCEPTIONTHROW: return JsDiagDebugEventRuntimeException; |
| case Js::STOP_ASYNCBREAK: return JsDiagDebugEventAsyncBreak; |
| |
| case Js::STOP_MUTATIONBREAKPOINT: |
| default: |
| Assert("Unhandled stoptype"); |
| break; |
| } |
| |
| return JsDiagDebugEventBreakpoint; |
| } |
| #endif |