//------------------------------------------------------------------------------------------------------- // 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/JavascriptFunctionArgIndex.h" #include "Language/InterpreterStackFrame.h" #include "Language/JavascriptStackWalker.h" namespace Js { DiagStackFrame::DiagStackFrame(): isTopFrame(false) { } // Returns whether or not this frame is on the top of the callstack. bool DiagStackFrame::IsTopFrame() { return this->isTopFrame && GetScriptContext()->GetDebugContext()->GetProbeContainer()->IsPrimaryBrokenToDebuggerContext(); } void DiagStackFrame::SetIsTopFrame() { this->isTopFrame = true; } ScriptFunction* DiagStackFrame::GetScriptFunction() { return ScriptFunction::FromVar(GetJavascriptFunction()); } FunctionBody* DiagStackFrame::GetFunction() { return GetJavascriptFunction()->GetFunctionBody(); } ScriptContext* DiagStackFrame::GetScriptContext() { return GetJavascriptFunction()->GetScriptContext(); } PCWSTR DiagStackFrame::GetDisplayName() { return GetFunction()->GetExternalDisplayName(); } bool DiagStackFrame::IsInterpreterFrame() { return false; } InterpreterStackFrame* DiagStackFrame::AsInterpreterFrame() { AssertMsg(FALSE, "AsInterpreterFrame called for non-interpreter frame."); return nullptr; } ArenaAllocator * DiagStackFrame::GetArena() { Assert(GetScriptContext() != NULL); return GetScriptContext()->GetThreadContext()->GetDebugManager()->GetDiagnosticArena()->Arena(); } FrameDisplay * DiagStackFrame::GetFrameDisplay() { FrameDisplay *display = NULL; Assert(this->GetFunction() != NULL); RegSlot frameDisplayReg = this->GetFunction()->GetFrameDisplayRegister(); if (frameDisplayReg != Js::Constants::NoRegister && frameDisplayReg != 0) { display = (FrameDisplay*)this->GetNonVarRegValue(frameDisplayReg); } else { display = this->GetScriptFunction()->GetEnvironment(); } return display; } Var DiagStackFrame::GetScopeObjectFromFrameDisplay(uint index) { FrameDisplay * display = GetFrameDisplay(); return (display != NULL && display->GetLength() > index) ? display->GetItem(index) : NULL; } Var DiagStackFrame::GetRootObject() { Assert(this->GetFunction()); return this->GetFunction()->LoadRootObject(); } BOOL DiagStackFrame::IsStrictMode() { Js::JavascriptFunction* scopeFunction = this->GetJavascriptFunction(); return scopeFunction->IsStrictMode(); } BOOL DiagStackFrame::IsThisAvailable() { Js::JavascriptFunction* scopeFunction = this->GetJavascriptFunction(); return !scopeFunction->IsLambda() || scopeFunction->GetParseableFunctionInfo()->GetCapturesThis(); } Js::Var DiagStackFrame::GetThisFromFrame(Js::IDiagObjectAddress ** ppOutAddress, Js::IDiagObjectModelWalkerBase * localsWalker) { Js::ScriptContext* scriptContext = this->GetScriptContext(); Js::JavascriptFunction* scopeFunction = this->GetJavascriptFunction(); Js::ModuleID moduleId = scopeFunction->IsScriptFunction() ? scopeFunction->GetFunctionBody()->GetModuleID() : 0; Js::Var varThis = scriptContext->GetLibrary()->GetNull(); if (!scopeFunction->IsLambda()) { Js::JavascriptStackWalker::GetThis(&varThis, moduleId, scopeFunction, scriptContext); } else { if (!scopeFunction->GetParseableFunctionInfo()->GetCapturesThis()) { return nullptr; } else { // Emulate Js::JavascriptOperators::OP_GetThisScoped using a locals walker and assigning moduleId object if not found by locals walker if (localsWalker == nullptr) { ArenaAllocator *arena = scriptContext->GetThreadContext()->GetDebugManager()->GetDiagnosticArena()->Arena(); localsWalker = Anew(arena, Js::LocalsWalker, this, Js::FrameWalkerFlags::FW_EnumWithScopeAlso | Js::FrameWalkerFlags::FW_AllowLexicalThis); } bool unused = false; Js::IDiagObjectAddress* address = localsWalker->FindPropertyAddress(Js::PropertyIds::_this, unused); if (ppOutAddress != nullptr) { *ppOutAddress = address; } if (address != nullptr) { varThis = address->GetValue(FALSE); } else if (moduleId == kmodGlobal) { varThis = Js::JavascriptOperators::OP_LdRoot(scriptContext)->ToThis(); } else { varThis = (Var)Js::JavascriptOperators::GetModuleRoot(moduleId, scriptContext); } } } Js::GlobalObject::UpdateThisForEval(varThis, moduleId, scriptContext, this->IsStrictMode()); return varThis; } void DiagStackFrame::TryFetchValueAndAddress(const char16 *source, int sourceLength, Js::ResolvedObject * pOutResolvedObj) { Assert(source); Assert(pOutResolvedObj); Js::ScriptContext* scriptContext = this->GetScriptContext(); Js::PropertyRecord const * propRecord; scriptContext->FindPropertyRecord(source, sourceLength, &propRecord); if (propRecord != nullptr) { ArenaAllocator *arena = scriptContext->GetThreadContext()->GetDebugManager()->GetDiagnosticArena()->Arena(); Js::IDiagObjectModelWalkerBase * localsWalker = Anew(arena, Js::LocalsWalker, this, Js::FrameWalkerFlags::FW_EnumWithScopeAlso); bool isConst = false; pOutResolvedObj->address = localsWalker->FindPropertyAddress(propRecord->GetPropertyId(), isConst); if (pOutResolvedObj->address != nullptr) { pOutResolvedObj->obj = pOutResolvedObj->address->GetValue(FALSE); pOutResolvedObj->isConst = isConst; } } } Js::ScriptFunction* DiagStackFrame::TryGetFunctionForEval(Js::ScriptContext* scriptContext, const char16 *source, int sourceLength, BOOL isLibraryCode /* = FALSE */) { // TODO: pass the real length of the source code instead of wcslen uint32 grfscr = fscrReturnExpression | fscrEval | fscrEvalCode | fscrGlobalCode | fscrConsoleScopeEval; if (!this->IsThisAvailable()) { grfscr |= fscrDebuggerErrorOnGlobalThis; } if (isLibraryCode) { grfscr |= fscrIsLibraryCode; } return scriptContext->GetGlobalObject()->EvalHelper(scriptContext, source, sourceLength, kmodGlobal, grfscr, Js::Constants::EvalCode, FALSE, FALSE, this->IsStrictMode()); } void DiagStackFrame::EvaluateImmediate(const char16 *source, int sourceLength, BOOL isLibraryCode, Js::ResolvedObject * resolvedObject) { this->TryFetchValueAndAddress(source, sourceLength, resolvedObject); if (resolvedObject->obj == nullptr) { Js::ScriptFunction* pfuncScript = this->TryGetFunctionForEval(this->GetScriptContext(), source, sourceLength, isLibraryCode); if (pfuncScript != nullptr) { // Passing the nonuser code state from the enclosing function to the current function. // Treat native library frame (no function body) as non-user code. Js::FunctionBody* body = this->GetFunction(); if (!body || body->IsNonUserCode()) { Js::FunctionBody *pCurrentFuncBody = pfuncScript->GetFunctionBody(); if (pCurrentFuncBody != nullptr) { pCurrentFuncBody->SetIsNonUserCode(true); } } OUTPUT_TRACE(Js::ConsoleScopePhase, _u("EvaluateImmediate strict = %d, libraryCode = %d, source = '%s'\n"), this->IsStrictMode(), isLibraryCode, source); resolvedObject->obj = this->DoEval(pfuncScript); } } } #ifdef ENABLE_MUTATION_BREAKPOINT static void SetConditionalMutationBreakpointVariables(Js::DynamicObject * activeScopeObject, Js::ScriptContext * scriptContext) { // For Conditional Object Mutation Breakpoint user can access the new value, changing property name and mutation type using special variables // $newValue$, $propertyName$ and $mutationType$. Add this variables to activation object. Js::DebugManager* debugManager = scriptContext->GetDebugContext()->GetProbeContainer()->GetDebugManager(); Js::MutationBreakpoint *mutationBreakpoint = debugManager->GetActiveMutationBreakpoint(); if (mutationBreakpoint != nullptr) { if (Js::Constants::NoProperty == debugManager->mutationNewValuePid) { debugManager->mutationNewValuePid = scriptContext->GetOrAddPropertyIdTracked(_u("$newValue$"), 10); } if (Js::Constants::NoProperty == debugManager->mutationPropertyNamePid) { debugManager->mutationPropertyNamePid = scriptContext->GetOrAddPropertyIdTracked(_u("$propertyName$"), 14); } if (Js::Constants::NoProperty == debugManager->mutationTypePid) { debugManager->mutationTypePid = scriptContext->GetOrAddPropertyIdTracked(_u("$mutationType$"), 14); } AssertMsg(debugManager->mutationNewValuePid != Js::Constants::NoProperty, "Should have a valid mutationNewValuePid"); AssertMsg(debugManager->mutationPropertyNamePid != Js::Constants::NoProperty, "Should have a valid mutationPropertyNamePid"); AssertMsg(debugManager->mutationTypePid != Js::Constants::NoProperty, "Should have a valid mutationTypePid"); Js::Var newValue = mutationBreakpoint->GetBreakNewValueVar(); // Incase of MutationTypeDelete we won't have new value if (nullptr != newValue) { activeScopeObject->SetProperty(debugManager->mutationNewValuePid, mutationBreakpoint->GetBreakNewValueVar(), Js::PropertyOperationFlags::PropertyOperation_None, nullptr); } else { activeScopeObject->SetProperty(debugManager->mutationNewValuePid, scriptContext->GetLibrary()->GetUndefined(), Js::PropertyOperationFlags::PropertyOperation_None, nullptr); } // User should not be able to change $propertyName$ and $mutationType$ variables // Since we don't have address for $propertyName$ and $mutationType$ even if user change these varibales it won't be reflected after eval // But declaring these as const to prevent accidental typos by user so that we throw error in case user changes these variables Js::PropertyOperationFlags flags = static_cast(Js::PropertyOperation_SpecialValue | Js::PropertyOperation_AllowUndecl); activeScopeObject->SetPropertyWithAttributes(debugManager->mutationPropertyNamePid, Js::JavascriptString::NewCopySz(mutationBreakpoint->GetBreakPropertyName(), scriptContext), PropertyConstDefaults, nullptr, flags); activeScopeObject->SetPropertyWithAttributes(debugManager->mutationTypePid, Js::JavascriptString::NewCopySz(mutationBreakpoint->GetMutationTypeForConditionalEval(mutationBreakpoint->GetBreakMutationType()), scriptContext), PropertyConstDefaults, nullptr, flags); } } #endif Js::Var DiagStackFrame::DoEval(Js::ScriptFunction* pfuncScript) { Js::Var varResult = nullptr; Js::JavascriptFunction* scopeFunction = this->GetJavascriptFunction(); Js::ScriptContext* scriptContext = this->GetScriptContext(); ArenaAllocator *arena = scriptContext->GetThreadContext()->GetDebugManager()->GetDiagnosticArena()->Arena(); Js::LocalsWalker *localsWalker = Anew(arena, Js::LocalsWalker, this, Js::FrameWalkerFlags::FW_EnumWithScopeAlso | Js::FrameWalkerFlags::FW_AllowLexicalThis | Js::FrameWalkerFlags::FW_AllowSuperReference | Js::FrameWalkerFlags::FW_DontAddGlobalsDirectly); // Store the diag address of a var to the map so that it will be used for editing the value. typedef JsUtil::BaseDictionary PropIdToDiagAddressMap; PropIdToDiagAddressMap * propIdtoDiagAddressMap = Anew(arena, PropIdToDiagAddressMap, arena); // Create one scope object and init all scope properties in it, and push this object in front of the environment. Js::DynamicObject * activeScopeObject = localsWalker->CreateAndPopulateActivationObject(scriptContext, [propIdtoDiagAddressMap](Js::ResolvedObject& resolveObject) { if (!resolveObject.isConst) { propIdtoDiagAddressMap->AddNew(resolveObject.propId, resolveObject.address); } }); if (!activeScopeObject) { activeScopeObject = scriptContext->GetLibrary()->CreateActivationObject(); } #ifdef ENABLE_MUTATION_BREAKPOINT SetConditionalMutationBreakpointVariables(activeScopeObject, scriptContext); #endif #if DBG uint32 countForVerification = activeScopeObject->GetPropertyCount(); #endif // Dummy scope object in the front, so that no new variable will be added to the scope. Js::DynamicObject * dummyObject = scriptContext->GetLibrary()->CreateActivationObject(); // Remove its prototype object so that those item will not be visible to the expression evaluation. dummyObject->SetPrototype(scriptContext->GetLibrary()->GetNull()); Js::DebugManager* debugManager = scriptContext->GetDebugContext()->GetProbeContainer()->GetDebugManager(); Js::FrameDisplay* env = debugManager->GetFrameDisplay(scriptContext, dummyObject, activeScopeObject); pfuncScript->SetEnvironment(env); Js::Var varThis = this->GetThisFromFrame(nullptr, localsWalker); if (varThis == nullptr) { Assert(scopeFunction->IsLambda()); Assert(!scopeFunction->GetParseableFunctionInfo()->GetCapturesThis()); varThis = scriptContext->GetLibrary()->GetNull(); } // If there was no 'this' binding found by the LocalsWalker, let's add the 'this' value we // collected from the frame to the scope. Eval loads 'this' by walking the scope chain so // it has to have some value in the chain or we'll be stuck with the global 'this' binding value. if (!activeScopeObject->HasProperty(Js::PropertyIds::_this)) { activeScopeObject->SetPropertyWithAttributes( Js::PropertyIds::_this, JavascriptOperators::BoxStackInstance(varThis, scriptContext, /* allowStackFunction */ false, /* deepCopy */ false), //The value escapes, box if necessary. PropertyConstDefaults, nullptr); #if DBG countForVerification++; #endif } ThreadContext * threadContext = pfuncScript->GetScriptContext()->GetThreadContext(); BEGIN_SAFE_REENTRANT_CALL(threadContext) { varResult = CALL_FUNCTION(threadContext, pfuncScript, CallInfo(1), varThis); } END_SAFE_REENTRANT_CALL debugManager->UpdateConsoleScope(dummyObject, scriptContext); // We need to find out the edits have been done to the dummy scope object during the eval. We need to apply those mutations to the actual vars. uint32 count = activeScopeObject->GetPropertyCount(); #if DBG Assert(countForVerification == count); #endif for (uint32 i = 0; i < count; i++) { Js::PropertyId propertyId = activeScopeObject->GetPropertyId((Js::PropertyIndex)i); if (propertyId != Js::Constants::NoProperty) { Js::Var value = nullptr; if (Js::JavascriptOperators::GetProperty(activeScopeObject, propertyId, &value, scriptContext)) { Js::IDiagObjectAddress * pAddress = nullptr; if (propIdtoDiagAddressMap->TryGetValue(propertyId, &pAddress)) { Assert(pAddress); if (pAddress->GetValue(FALSE) != value) { pAddress->Set(value); } } } } } return varResult; } Var DiagStackFrame::GetInnerScopeFromRegSlot(RegSlot location) { return GetNonVarRegValue(location); } DiagInterpreterStackFrame::DiagInterpreterStackFrame(InterpreterStackFrame* frame) : m_interpreterFrame(frame) { Assert(m_interpreterFrame != NULL); AssertMsg(m_interpreterFrame->GetScriptContext() && m_interpreterFrame->GetScriptContext()->IsScriptContextInDebugMode(), "This only supports interpreter stack frames running in debug mode."); } JavascriptFunction* DiagInterpreterStackFrame::GetJavascriptFunction() { return m_interpreterFrame->GetJavascriptFunction(); } ScriptContext* DiagInterpreterStackFrame::GetScriptContext() { return m_interpreterFrame->GetScriptContext(); } int DiagInterpreterStackFrame::GetByteCodeOffset() { return m_interpreterFrame->GetReader()->GetCurrentOffset(); } // Address on stack that belongs to current frame. // Currently we only use this to determine which of given frames is above/below another one. DWORD_PTR DiagInterpreterStackFrame::GetStackAddress() { return m_interpreterFrame->GetStackAddress(); } bool DiagInterpreterStackFrame::IsInterpreterFrame() { return true; } InterpreterStackFrame* DiagInterpreterStackFrame::AsInterpreterFrame() { return m_interpreterFrame; } Var DiagInterpreterStackFrame::GetRegValue(RegSlot slotId, bool allowTemp) { return m_interpreterFrame->GetReg(slotId); } Var DiagInterpreterStackFrame::GetNonVarRegValue(RegSlot slotId) { return m_interpreterFrame->GetNonVarReg(slotId); } void DiagInterpreterStackFrame::SetRegValue(RegSlot slotId, Var value) { m_interpreterFrame->SetReg(slotId, value); } Var DiagInterpreterStackFrame::GetArgumentsObject() { return m_interpreterFrame->GetArgumentsObject(); } Var DiagInterpreterStackFrame::CreateHeapArguments() { return m_interpreterFrame->CreateHeapArguments(GetScriptContext()); } FrameDisplay * DiagInterpreterStackFrame::GetFrameDisplay() { return m_interpreterFrame->GetFrameDisplayForNestedFunc(); } Var DiagInterpreterStackFrame::GetInnerScopeFromRegSlot(RegSlot location) { return m_interpreterFrame->InnerScopeFromRegSlot(location); } #if ENABLE_NATIVE_CODEGEN DiagNativeStackFrame::DiagNativeStackFrame( ScriptFunction* function, int byteCodeOffset, void* stackAddr, void *codeAddr) : m_function(function), m_byteCodeOffset(byteCodeOffset), m_stackAddr(stackAddr), m_localVarSlotsOffset(InvalidOffset), m_localVarChangedOffset(InvalidOffset) { Assert(m_stackAddr != NULL); AssertMsg(m_function && m_function->GetScriptContext() && m_function->GetScriptContext()->IsScriptContextInDebugMode(), "This only supports functions in debug mode."); FunctionEntryPointInfo * entryPointInfo = GetFunction()->GetEntryPointFromNativeAddress((DWORD_PTR)codeAddr); if (entryPointInfo) { m_localVarSlotsOffset = entryPointInfo->localVarSlotsOffset; m_localVarChangedOffset = entryPointInfo->localVarChangedOffset; } else { AssertMsg(FALSE, "Failed to get entry point for native address. Most likely the frame is old/gone."); } OUTPUT_TRACE(Js::DebuggerPhase, _u("DiagNativeStackFrame::DiagNativeStackFrame: e.p(addr %p)=%p varOff=%d changedOff=%d\n"), codeAddr, entryPointInfo, m_localVarSlotsOffset, m_localVarChangedOffset); } JavascriptFunction* DiagNativeStackFrame::GetJavascriptFunction() { return m_function; } ScriptContext* DiagNativeStackFrame::GetScriptContext() { return m_function->GetScriptContext(); } int DiagNativeStackFrame::GetByteCodeOffset() { return m_byteCodeOffset; } // Address on stack that belongs to current frame. // Currently we only use this to determine which of given frames is above/below another one. DWORD_PTR DiagNativeStackFrame::GetStackAddress() { return reinterpret_cast(m_stackAddr); } Var DiagNativeStackFrame::GetRegValue(RegSlot slotId, bool allowTemp) { Js::Var *varPtr = GetSlotOffsetLocation(slotId, allowTemp); return (varPtr != NULL) ? *varPtr : NULL; } Var * DiagNativeStackFrame::GetSlotOffsetLocation(RegSlot slotId, bool allowTemp) { Assert(GetFunction() != NULL); int32 slotOffset; if (GetFunction()->GetSlotOffset(slotId, &slotOffset, allowTemp)) { Assert(m_localVarSlotsOffset != InvalidOffset); slotOffset = m_localVarSlotsOffset + slotOffset; // We will have the var offset only (which is always the Var size. With TypeSpecialization, below will change to accommodate double offset. return (Js::Var *)(((char *)m_stackAddr) + slotOffset); } Assert(false); return NULL; } Var DiagNativeStackFrame::GetNonVarRegValue(RegSlot slotId) { return GetRegValue(slotId); } void DiagNativeStackFrame::SetRegValue(RegSlot slotId, Var value) { Js::Var *varPtr = GetSlotOffsetLocation(slotId); Assert(varPtr != NULL); // First assign the value *varPtr = value; Assert(m_localVarChangedOffset != InvalidOffset); // Now change the bit in the stack which tells that current stack values got changed. char *stackOffset = (((char *)m_stackAddr) + m_localVarChangedOffset); Assert(*stackOffset == 0 || *stackOffset == FunctionBody::LocalsChangeDirtyValue); *stackOffset = FunctionBody::LocalsChangeDirtyValue; } Var DiagNativeStackFrame::GetArgumentsObject() { return (Var)((void **)m_stackAddr)[JavascriptFunctionArgIndex_ArgumentsObject]; } Var DiagNativeStackFrame::CreateHeapArguments() { // We would be creating the arguments object if there is no default arguments object present. Assert(GetArgumentsObject() == NULL); CallInfo const * callInfo = (CallInfo const *)&(((void **)m_stackAddr)[JavascriptFunctionArgIndex_CallInfo]); // At the least we will have 'this' by default. Assert(callInfo->Count > 0); // Get the passed parameter's position (which is starting from 'this') Var * inParams = (Var *)&(((void **)m_stackAddr)[JavascriptFunctionArgIndex_This]); return JavascriptOperators::LoadHeapArguments( m_function, callInfo->Count - 1, &inParams[1], GetScriptContext()->GetLibrary()->GetNull(), (PropertyId*)GetScriptContext()->GetLibrary()->GetNull(), GetScriptContext(), /* formalsAreLetDecls */ false); } #endif DiagRuntimeStackFrame::DiagRuntimeStackFrame(JavascriptFunction* function, PCWSTR displayName, void* stackAddr): m_function(function), m_displayName(displayName), m_stackAddr(stackAddr) { } JavascriptFunction* DiagRuntimeStackFrame::GetJavascriptFunction() { return m_function; } PCWSTR DiagRuntimeStackFrame::GetDisplayName() { return m_displayName; } DWORD_PTR DiagRuntimeStackFrame::GetStackAddress() { return reinterpret_cast(m_stackAddr); } int DiagRuntimeStackFrame::GetByteCodeOffset() { return 0; } Var DiagRuntimeStackFrame::GetRegValue(RegSlot slotId, bool allowTemp) { return nullptr; } Var DiagRuntimeStackFrame::GetNonVarRegValue(RegSlot slotId) { return nullptr; } void DiagRuntimeStackFrame::SetRegValue(RegSlot slotId, Var value) { } Var DiagRuntimeStackFrame::GetArgumentsObject() { return nullptr; } Var DiagRuntimeStackFrame::CreateHeapArguments() { return nullptr; } } // namespace Js #endif