//------------------------------------------------------------------------------------------------------- // 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" #ifdef ENABLE_SCRIPT_DEBUGGING #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 pDiagArena(HeapNew(ArenaAllocator, _u("DiagHaltState"), this->pThreadContext->GetPageAllocator(), Js::Throw::OutOfMemory), &HeapAllocator::Instance); AutoAllocatorObjectPtr 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(&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); } #endif