| //------------------------------------------------------------------------------------------------------- |
| // Copyright (C) Microsoft. All rights reserved. |
| // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. |
| //------------------------------------------------------------------------------------------------------- |
| #include "RuntimeDebugPch.h" |
| #include "Language/JavascriptStackWalker.h" |
| namespace Js |
| { |
| DebugManager::DebugManager(ThreadContext* _pThreadContext, AllocationPolicyManager * allocationPolicyManager) : |
| pCurrentInterpreterLocation(nullptr), |
| secondaryCurrentSourceContext(0), |
| debugSessionNumber(0), |
| pThreadContext(_pThreadContext), |
| isAtDispatchHalt(false), |
| mutationNewValuePid(Js::Constants::NoProperty), |
| mutationPropertyNamePid(Js::Constants::NoProperty), |
| mutationTypePid(Js::Constants::NoProperty), |
| diagnosticPageAllocator(allocationPolicyManager, Js::Configuration::Global.flags, PageAllocatorType_Diag, 0), |
| evalCodeRegistrationCount(0), |
| anonymousCodeRegistrationCount(0), |
| jscriptBlockRegistrationCount(0), |
| isDebuggerAttaching(false), |
| nextBreakPointId(0), |
| localsDisplayFlags(LocalsDisplayFlags_None), |
| dispatchHaltFrameAddress(nullptr) |
| { |
| Assert(_pThreadContext != nullptr); |
| #if DBG |
| // diagnosticPageAllocator may be used in multiple thread, but it's usage is synchronized. |
| diagnosticPageAllocator.SetDisableThreadAccessCheck(); |
| diagnosticPageAllocator.debugName = _u("Diagnostic"); |
| #endif |
| } |
| |
| void DebugManager::Close() |
| { |
| this->diagnosticPageAllocator.Close(); |
| |
| if (this->pConsoleScope) |
| { |
| this->pConsoleScope.Unroot(this->pThreadContext->GetRecycler()); |
| } |
| #if DBG |
| this->pThreadContext->EnsureNoReturnedValueList(); |
| #endif |
| this->pThreadContext = nullptr; |
| } |
| |
| DebugManager::~DebugManager() |
| { |
| Assert(this->pThreadContext == nullptr); |
| } |
| |
| DebuggingFlags* DebugManager::GetDebuggingFlags() |
| { |
| return &this->debuggingFlags; |
| } |
| |
| intptr_t DebugManager::GetDebuggingFlagsAddr() const |
| { |
| return (intptr_t)&this->debuggingFlags; |
| } |
| |
| ReferencedArenaAdapter* DebugManager::GetDiagnosticArena() |
| { |
| if (pCurrentInterpreterLocation) |
| { |
| return pCurrentInterpreterLocation->referencedDiagnosticArena; |
| } |
| return nullptr; |
| } |
| |
| DWORD_PTR DebugManager::AllocateSecondaryHostSourceContext() |
| { |
| Assert(secondaryCurrentSourceContext < ULONG_MAX); |
| return secondaryCurrentSourceContext++; // The context is not valid, use the secondary context for identify the function body for further use. |
| } |
| |
| void DebugManager::SetCurrentInterpreterLocation(InterpreterHaltState* pHaltState) |
| { |
| Assert(pHaltState); |
| Assert(!pCurrentInterpreterLocation); |
| |
| pCurrentInterpreterLocation = pHaltState; |
| |
| AutoAllocatorObjectPtr<ArenaAllocator, HeapAllocator> pDiagArena(HeapNew(ArenaAllocator, _u("DiagHaltState"), this->pThreadContext->GetPageAllocator(), Js::Throw::OutOfMemory), &HeapAllocator::Instance); |
| AutoAllocatorObjectPtr<ReferencedArenaAdapter, HeapAllocator> referencedDiagnosticArena(HeapNew(ReferencedArenaAdapter, pDiagArena), &HeapAllocator::Instance); |
| pCurrentInterpreterLocation->referencedDiagnosticArena = referencedDiagnosticArena; |
| |
| pThreadContext->GetRecycler()->RegisterExternalGuestArena(pDiagArena); |
| debugSessionNumber++; |
| |
| pDiagArena.Detach(); |
| referencedDiagnosticArena.Detach(); |
| } |
| |
| void DebugManager::UnsetCurrentInterpreterLocation() |
| { |
| Assert(pCurrentInterpreterLocation); |
| |
| if (pCurrentInterpreterLocation) |
| { |
| // pCurrentInterpreterLocation->referencedDiagnosticArena could be null if we ran out of memory during SetCurrentInterpreterLocation |
| if (pCurrentInterpreterLocation->referencedDiagnosticArena) |
| { |
| pThreadContext->GetRecycler()->UnregisterExternalGuestArena(pCurrentInterpreterLocation->referencedDiagnosticArena->Arena()); |
| pCurrentInterpreterLocation->referencedDiagnosticArena->DeleteArena(); |
| pCurrentInterpreterLocation->referencedDiagnosticArena->Release(); |
| } |
| |
| pCurrentInterpreterLocation = nullptr; |
| } |
| } |
| |
| bool DebugManager::IsMatchTopFrameStackAddress(DiagStackFrame* frame) const |
| { |
| return (frame != nullptr) && |
| (this->pCurrentInterpreterLocation != nullptr) && |
| (this->pCurrentInterpreterLocation->topFrame != nullptr) && |
| (this->pCurrentInterpreterLocation->topFrame->GetStackAddress() == frame->GetStackAddress()); |
| } |
| |
| #ifdef ENABLE_MUTATION_BREAKPOINT |
| MutationBreakpoint* DebugManager::GetActiveMutationBreakpoint() const |
| { |
| Assert(this->pCurrentInterpreterLocation); |
| return this->pCurrentInterpreterLocation->activeMutationBP; |
| } |
| #endif |
| |
| DynamicObject* DebugManager::GetConsoleScope(ScriptContext* scriptContext) |
| { |
| Assert(scriptContext); |
| |
| if (!this->pConsoleScope) |
| { |
| this->pConsoleScope.Root(scriptContext->GetLibrary()->CreateConsoleScopeActivationObject(), this->pThreadContext->GetRecycler()); |
| } |
| |
| return (DynamicObject*)CrossSite::MarshalVar(scriptContext, (Var)this->pConsoleScope); |
| } |
| |
| FrameDisplay *DebugManager::GetFrameDisplay(ScriptContext* scriptContext, DynamicObject* scopeAtZero, DynamicObject* scopeAtOne) |
| { |
| // The scope chain for console eval looks like: |
| // - dummy empty object - new vars, let, consts, functions get added here |
| // - Active scope object containing all globals visible at this break (if at break) |
| // - Global this object so that existing properties are updated here |
| // - Console-1 Scope - all new globals will go here (like x = 1;) |
| // - NullFrameDisplay |
| |
| FrameDisplay* environment = JavascriptOperators::OP_LdFrameDisplay(this->GetConsoleScope(scriptContext), const_cast<FrameDisplay *>(&NullFrameDisplay), scriptContext); |
| |
| environment = JavascriptOperators::OP_LdFrameDisplay(scriptContext->GetGlobalObject()->ToThis(), environment, scriptContext); |
| |
| if (scopeAtOne != nullptr) |
| { |
| environment = JavascriptOperators::OP_LdFrameDisplay((Var)scopeAtOne, environment, scriptContext); |
| } |
| |
| environment = JavascriptOperators::OP_LdFrameDisplay((Var)scopeAtZero, environment, scriptContext); |
| return environment; |
| } |
| |
| void DebugManager::UpdateConsoleScope(DynamicObject* copyFromScope, ScriptContext* scriptContext) |
| { |
| Assert(copyFromScope != nullptr); |
| DynamicObject* consoleScope = this->GetConsoleScope(scriptContext); |
| |
| uint32 newPropCount = copyFromScope->GetPropertyCount(); |
| for (uint32 i = 0; i < newPropCount; i++) |
| { |
| Js::PropertyId propertyId = copyFromScope->GetPropertyId((Js::PropertyIndex)i); |
| // For deleted properties we won't have a property id |
| if (propertyId != Js::Constants::NoProperty) |
| { |
| PropertyDescriptor propertyDescriptor; |
| BOOL gotPropertyValue = JavascriptOperators::GetOwnPropertyDescriptor(copyFromScope, propertyId, scriptContext, &propertyDescriptor); |
| AssertMsg(gotPropertyValue, "DebugManager::UpdateConsoleScope Should have got valid value?"); |
| |
| OUTPUT_TRACE(Js::ConsoleScopePhase, _u("Adding property '%s'\n"), scriptContext->GetPropertyName(propertyId)->GetBuffer()); |
| |
| BOOL updateSuccess = JavascriptOperators::SetPropertyDescriptor(consoleScope, propertyId, propertyDescriptor); |
| AssertMsg(updateSuccess, "DebugManager::UpdateConsoleScope Unable to update property value. Am I missing a scenario?"); |
| } |
| } |
| |
| OUTPUT_TRACE(Js::ConsoleScopePhase, _u("Number of properties on console scope object after update are %d\n"), consoleScope->GetPropertyCount()); |
| } |
| |
| #if DBG |
| void DebugManager::ValidateDebugAPICall() |
| { |
| Js::JavascriptStackWalker walker(this->pThreadContext->GetScriptEntryExit()->scriptContext); |
| Js::JavascriptFunction* javascriptFunction = nullptr; |
| if (walker.GetCaller(&javascriptFunction)) |
| { |
| if (javascriptFunction != nullptr) |
| { |
| void *topJsFrameAddr = (void *)walker.GetCurrentArgv(); |
| Assert(this->dispatchHaltFrameAddress != nullptr); |
| if (topJsFrameAddr < this->dispatchHaltFrameAddress) |
| { |
| // we found the script frame after the break mode. |
| AssertMsg(false, "There are JavaScript frames between current API and dispatch halt"); |
| } |
| } |
| } |
| } |
| #endif |
| } |
| |
| AutoSetDispatchHaltFlag::AutoSetDispatchHaltFlag(Js::ScriptContext *scriptContext, ThreadContext *threadContext) : |
| m_scriptContext(scriptContext), |
| m_threadContext(threadContext) |
| { |
| Assert(m_scriptContext != nullptr); |
| Assert(m_threadContext != nullptr); |
| |
| Assert(!m_threadContext->GetDebugManager()->IsAtDispatchHalt()); |
| m_threadContext->GetDebugManager()->SetDispatchHalt(true); |
| |
| Assert(!m_scriptContext->GetDebugContext()->GetProbeContainer()->IsPrimaryBrokenToDebuggerContext()); |
| m_scriptContext->GetDebugContext()->GetProbeContainer()->SetIsPrimaryBrokenToDebuggerContext(true); |
| } |
| AutoSetDispatchHaltFlag::~AutoSetDispatchHaltFlag() |
| { |
| Assert(m_threadContext->GetDebugManager()->IsAtDispatchHalt()); |
| m_threadContext->GetDebugManager()->SetDispatchHalt(false); |
| |
| Assert(m_scriptContext->GetDebugContext()->GetProbeContainer()->IsPrimaryBrokenToDebuggerContext()); |
| m_scriptContext->GetDebugContext()->GetProbeContainer()->SetIsPrimaryBrokenToDebuggerContext(false); |
| } |