//------------------------------------------------------------------------------------------------------- // 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" #include "Language/InterpreterStackFrame.h" namespace Js { ProbeContainer::ProbeContainer() : diagProbeList(nullptr), pScriptContext(nullptr), debugManager(nullptr), haltCallbackProbe(nullptr), debuggerOptionsCallback(nullptr), pAsyncHaltCallback(nullptr), jsExceptionObject(nullptr), framePointers(nullptr), debugSessionNumber(0), tmpRegCount(0), bytecodeOffset(0), IsNextStatementChanged(false), isThrowInternal(false), forceBypassDebugEngine(false), isPrimaryBrokenToDebuggerContext(false), isForcedToEnterScriptStart(false), registeredFuncContextList(nullptr) { } ProbeContainer::~ProbeContainer() { this->Close(); } void ProbeContainer::Close() { // Probe manager instance may go down early. if (this->pScriptContext) { debugManager = this->pScriptContext->GetThreadContext()->GetDebugManager(); } else { debugManager = nullptr; } if (debugManager != nullptr && debugManager->stepController.pActivatedContext == pScriptContext) { debugManager->stepController.Deactivate(); } #ifdef ENABLE_MUTATION_BREAKPOINT this->RemoveMutationBreakpointListIfNeeded(); #endif pScriptContext = nullptr; debugManager = nullptr; } void ProbeContainer::Initialize(ScriptContext* pScriptContext) { if (!diagProbeList) { ArenaAllocator* global = pScriptContext->AllocatorForDiagnostics(); diagProbeList = ProbeList::New(global); pendingProbeList = ProbeList::New(global); this->pScriptContext = pScriptContext; this->debugManager = this->pScriptContext->GetThreadContext()->GetDebugManager(); this->pinnedPropertyRecords = JsUtil::List::New(this->pScriptContext->GetRecycler()); this->pScriptContext->BindReference((void *)this->pinnedPropertyRecords); } } void ProbeContainer::StartRecordingCall() { Assert(this->pScriptContext->GetDebugContext() && this->pScriptContext->GetDebugContext()->IsDebuggerRecording()); this->debugManager->stepController.StartRecordingCall(); } void ProbeContainer::EndRecordingCall(Js::Var returnValue, Js::JavascriptFunction * function) { Assert(this->pScriptContext->GetDebugContext() && this->pScriptContext->GetDebugContext()->IsDebuggerRecording()); this->debugManager->stepController.EndRecordingCall(returnValue, function); } ReturnedValueList* ProbeContainer::GetReturnedValueList() const { return this->debugManager->stepController.GetReturnedValueList(); } void ProbeContainer::ResetReturnedValueList() { this->debugManager->stepController.ResetReturnedValueList(); } void ProbeContainer::UpdateFramePointers(bool fMatchWithCurrentScriptContext, DWORD_PTR dispatchHaltFrameAddress) { ArenaAllocator* pDiagArena = debugManager->GetDiagnosticArena()->Arena(); framePointers = Anew(pDiagArena, DiagStack, pDiagArena); JavascriptStackWalker walker(pScriptContext, !fMatchWithCurrentScriptContext, nullptr/*returnAddress*/, true/*forceFullWalk*/); DiagStack* tempFramePointers = Anew(pDiagArena, DiagStack, pDiagArena); const bool isLibraryFrameEnabledDebugger = IsLibraryStackFrameSupportEnabled(); walker.WalkUntil([&](JavascriptFunction* func, ushort frameIndex) -> bool { if (isLibraryFrameEnabledDebugger || !func->IsLibraryCode()) { DiagStackFrame* frm = nullptr; InterpreterStackFrame *interpreterFrame = walker.GetCurrentInterpreterFrame(); ScriptContext* frameScriptContext = walker.GetCurrentScriptContext(); Assert(frameScriptContext); if (!fMatchWithCurrentScriptContext && !frameScriptContext->IsScriptContextInDebugMode() && tempFramePointers->Count() == 0) { // this means the top frame is not in the debug mode. We shouldn't be stopping for this break. // This could happen if the exception happens on the diagnosticsScriptEngine. return true; } // Ignore frames which are not in debug mode, which can happen when diag engine calls into user engine under debugger // -- topmost frame is under debugger but some frames could be in non-debug mode as they are from diag engine. if (frameScriptContext->IsScriptContextInDebugMode() && (!fMatchWithCurrentScriptContext || frameScriptContext == pScriptContext)) { if (interpreterFrame) { if (dispatchHaltFrameAddress == 0 || interpreterFrame->GetStackAddress() > dispatchHaltFrameAddress) { frm = Anew(pDiagArena, DiagInterpreterStackFrame, interpreterFrame); } } else { void* stackAddress = walker.GetCurrentArgv(); if (dispatchHaltFrameAddress == 0 || reinterpret_cast(stackAddress) > dispatchHaltFrameAddress) { #if ENABLE_NATIVE_CODEGEN if (func->IsScriptFunction()) { frm = Anew(pDiagArena, DiagNativeStackFrame, ScriptFunction::FromVar(walker.GetCurrentFunction()), walker.GetByteCodeOffset(), stackAddress, walker.GetCurrentCodeAddr()); } else #else Assert(!func->IsScriptFunction()); #endif { frm = Anew(pDiagArena, DiagRuntimeStackFrame, func, walker.GetCurrentNativeLibraryEntryName(), stackAddress); } } } } if (frm) { tempFramePointers->Push(frm); } } return false; }); OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::UpdateFramePointers: detected %d frames (this=%p, fMatchWithCurrentScriptContext=%d)\n"), tempFramePointers->Count(), this, fMatchWithCurrentScriptContext); while (tempFramePointers->Count()) { framePointers->Push(tempFramePointers->Pop()); } } WeakDiagStack * ProbeContainer::GetFramePointers(DWORD_PTR dispatchHaltFrameAddress) { if (framePointers == nullptr || this->debugSessionNumber < debugManager->GetDebugSessionNumber()) { UpdateFramePointers(/*fMatchWithCurrentScriptContext*/true, dispatchHaltFrameAddress); this->debugSessionNumber = debugManager->GetDebugSessionNumber(); if ((framePointers->Count() > 0) && debugManager->IsMatchTopFrameStackAddress(framePointers->Peek(0))) { framePointers->Peek(0)->SetIsTopFrame(); } } ReferencedArenaAdapter* pRefArena = debugManager->GetDiagnosticArena(); return HeapNew(WeakDiagStack,pRefArena,framePointers); } bool ProbeContainer::InitializeLocation(InterpreterHaltState* pHaltState, bool fMatchWithCurrentScriptContext) { Assert(debugManager); debugManager->SetCurrentInterpreterLocation(pHaltState); ArenaAllocator* pDiagArena = debugManager->GetDiagnosticArena()->Arena(); UpdateFramePointers(fMatchWithCurrentScriptContext); pHaltState->framePointers = framePointers; pHaltState->stringBuilder = Anew(pDiagArena, StringBuilder, pDiagArena); if (pHaltState->framePointers->Count() > 0) { pHaltState->topFrame = pHaltState->framePointers->Peek(0); pHaltState->topFrame->SetIsTopFrame(); } OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::InitializeLocation (end): this=%p, pHaltState=%p, fMatch=%d, topFrame=%p\n"), this, pHaltState, fMatchWithCurrentScriptContext, pHaltState->topFrame); return true; } void ProbeContainer::DestroyLocation() { OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::DestroyLocation (start): this=%p, IsNextStatementChanged=%d, haltCallbackProbe=%p\n"), this, this->IsNextStatementChanged, haltCallbackProbe); if (IsNextStatementChanged) { Assert(bytecodeOffset != debugManager->stepController.byteOffset); // Note: when we dispatching an exception bytecodeOffset would be same as pProbeManager->pCurrentInterpreterLocation->GetCurrentOffset(). debugManager->pCurrentInterpreterLocation->SetCurrentOffset(bytecodeOffset); IsNextStatementChanged = false; } framePointers = nullptr; // Reset the exception object. jsExceptionObject = nullptr; Assert(debugManager); debugManager->UnsetCurrentInterpreterLocation(); pinnedPropertyRecords->Reset(); // Guarding if the probe engine goes away when we are sitting at breakpoint. if (haltCallbackProbe) { // The clean up is called here to scriptengine's object to remove all DebugStackFrames haltCallbackProbe->CleanupHalt(); } } bool ProbeContainer::CanDispatchHalt(InterpreterHaltState* pHaltState) { if (!haltCallbackProbe || haltCallbackProbe->IsInClosedState() || debugManager->IsAtDispatchHalt()) { OUTPUT_VERBOSE_TRACE(Js::DebuggerPhase, _u("ProbeContainer::CanDispatchHalt: Not in break mode. pHaltState = %p\n"), pHaltState); return false; } return true; } void ProbeContainer::DispatchStepHandler(InterpreterHaltState* pHaltState, OpCode* pOriginalOpcode) { OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::DispatchStepHandler: start: this=%p, pHaltState=%p, pOriginalOpcode=0x%x\n"), this, pHaltState, pOriginalOpcode); if (!CanDispatchHalt(pHaltState)) { return; } TryFinally([&]() { InitializeLocation(pHaltState); OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::DispatchStepHandler: initialized location: pHaltState=%p, pHaltState->IsValid()=%d\n"), pHaltState, pHaltState->IsValid()); if (pHaltState->IsValid()) // Only proceed if we find a valid top frame and that is the executing function { if (debugManager->stepController.IsStepComplete(pHaltState, haltCallbackProbe, *pOriginalOpcode)) { OpCode oldOpcode = *pOriginalOpcode; pHaltState->GetFunction()->ProbeAtOffset(pHaltState->GetCurrentOffset(), pOriginalOpcode); pHaltState->GetFunction()->CheckAndRegisterFuncToDiag(pScriptContext); debugManager->stepController.Deactivate(pHaltState); haltCallbackProbe->DispatchHalt(pHaltState); if (oldOpcode == OpCode::Break && debugManager->stepController.stepType == STEP_DOCUMENT) { // That means we have delivered the stepping to the debugger, where we had the breakpoint // already, however it is possible that debugger can initiate the step_document. In that // case debugger did not break due to break. So we have break as a breakpoint reason. *pOriginalOpcode = OpCode::Break; } else if (OpCode::Break == *pOriginalOpcode) { debugManager->stepController.stepCompleteOnInlineBreakpoint = true; } } } }, [&](bool) { DestroyLocation(); }); OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::DispatchStepHandler: end: pHaltState=%p\n"), pHaltState); } void ProbeContainer::DispatchAsyncBreak(InterpreterHaltState* pHaltState) { OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::DispatchAsyncBreak: start: this=%p, pHaltState=%p\n"), this, pHaltState); if (!this->pAsyncHaltCallback || !CanDispatchHalt(pHaltState)) { return; } TryFinally([&]() { InitializeLocation(pHaltState, /* We don't need to match script context, stop at any available script function */ false); OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::DispatchAsyncBreak: initialized location: pHaltState=%p, pHaltState->IsValid()=%d\n"), pHaltState, pHaltState->IsValid()); if (pHaltState->IsValid()) { // Activate the current haltCallback with asyncStepController. debugManager->asyncBreakController.Activate(this->pAsyncHaltCallback); if (debugManager->asyncBreakController.IsAtStoppingLocation(pHaltState)) { OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::DispatchAsyncBreak: IsAtStoppingLocation: pHaltState=%p\n"), pHaltState); pHaltState->GetFunction()->CheckAndRegisterFuncToDiag(pScriptContext); debugManager->stepController.Deactivate(pHaltState); debugManager->asyncBreakController.DispatchAndReset(pHaltState); } } }, [&](bool) { DestroyLocation(); }); OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::DispatchAsyncBreak: end: pHaltState=%p\n"), pHaltState); } void ProbeContainer::DispatchInlineBreakpoint(InterpreterHaltState* pHaltState) { OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::DispatchInlineBreakpoint: start: this=%p, pHaltState=%p\n"), this, pHaltState); if (!CanDispatchHalt(pHaltState)) { return; } Assert(pHaltState->stopType == STOP_INLINEBREAKPOINT); TryFinally([&]() { InitializeLocation(pHaltState); OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::DispatchInlineBreakpoint: initialized location: pHaltState=%p, pHaltState->IsValid()=%d\n"), pHaltState, pHaltState->IsValid()); Assert(pHaltState->IsValid()); // The ByteCodeReader should be available at this point, but because of possibility of garbled frame, we shouldn't hit AV if (pHaltState->IsValid()) { #if DBG pHaltState->GetFunction()->MustBeInDebugMode(); #endif // an inline breakpoint is being dispatched deactivate other stopping controllers debugManager->stepController.Deactivate(pHaltState); debugManager->asyncBreakController.Deactivate(); pHaltState->GetFunction()->CheckAndRegisterFuncToDiag(pScriptContext); haltCallbackProbe->DispatchHalt(pHaltState); } }, [&](bool) { DestroyLocation(); }); OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::DispatchInlineBreakpoint: end: pHaltState=%p\n"), pHaltState); } bool ProbeContainer::DispatchExceptionBreakpoint(InterpreterHaltState* pHaltState) { OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::DispatchExceptionBreakpoint: start: this=%p, pHaltState=%p\n"), this, pHaltState); bool fSuccess = false; if (!haltCallbackProbe || haltCallbackProbe->IsInClosedState() || debugManager->IsAtDispatchHalt()) { OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::DispatchExceptionBreakpoint: not in break mode: pHaltState=%p\n"), pHaltState); // Will not be able to handle multiple break-hits. return fSuccess; } Assert(pHaltState->stopType == STOP_EXCEPTIONTHROW); jsExceptionObject = pHaltState->exceptionObject->GetThrownObject(nullptr); // Will store current offset of the bytecode block. int currentOffset = -1; TryFinally([&]() { InitializeLocation(pHaltState, false); OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::DispatchExceptionBreakpoint: initialized location: pHaltState=%p, IsInterpreterFrame=%d\n"), pHaltState, pHaltState->IsValid(), pHaltState->topFrame && pHaltState->topFrame->IsInterpreterFrame()); // The ByteCodeReader should be available at this point, but because of possibility of garbled frame, we shouldn't hit AV if (pHaltState->IsValid() && pHaltState->GetFunction()->GetScriptContext()->IsScriptContextInDebugMode()) { #if DBG pHaltState->GetFunction()->MustBeInDebugMode(); #endif // For interpreter frames, change the current location pointer of bytecode block, as it might be pointing to the next statement on the body. // In order to generated proper binding of break on exception to the statement, the bytecode offset needed to be on the same span // of the statement. // For native frames the offset is always current. // Move back a single byte to ensure that it falls under on the same statement. if (pHaltState->topFrame->IsInterpreterFrame()) { currentOffset = pHaltState->GetCurrentOffset(); Assert(currentOffset > 0); pHaltState->SetCurrentOffset(currentOffset - 1); } // an inline breakpoint is being dispatched deactivate other stopping controllers debugManager->stepController.Deactivate(pHaltState); debugManager->asyncBreakController.Deactivate(); pHaltState->GetFunction()->CheckAndRegisterFuncToDiag(pScriptContext); ScriptContext *pTopFuncContext = pHaltState->GetFunction()->GetScriptContext(); // If the top function's context is different from the current context, that means current frame is not alive anymore and breaking here cannot not happen. // So in that case we will consider the top function's context and break on that context. if (pTopFuncContext != pScriptContext) { OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::DispatchExceptionBreakpoint: top function's context is different from the current context: pHaltState=%p, haltCallbackProbe=%p\n"), pHaltState, pTopFuncContext->GetDebugContext()->GetProbeContainer()->haltCallbackProbe); if (pTopFuncContext->GetDebugContext()->GetProbeContainer()->haltCallbackProbe) { pTopFuncContext->GetDebugContext()->GetProbeContainer()->haltCallbackProbe->DispatchHalt(pHaltState); fSuccess = true; } } else { haltCallbackProbe->DispatchHalt(pHaltState); fSuccess = true; } } }, [&](bool) { // If the next statement has changed, we need to log that to exception object so that it will not try to advance to next statement again. pHaltState->exceptionObject->SetIgnoreAdvanceToNextStatement(IsNextStatementChanged); // Restore the current offset; if (currentOffset != -1 && pHaltState->topFrame->IsInterpreterFrame()) { pHaltState->SetCurrentOffset(currentOffset); } DestroyLocation(); }); OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::DispatchExceptionBreakpoint: end: pHaltState=%p, fSuccess=%d\n"), pHaltState, fSuccess); return fSuccess; } void ProbeContainer::DispatchDOMMutationBreakpoint() { InterpreterHaltState haltState(STOP_DOMMUTATIONBREAKPOINT, /*_executingFunction*/nullptr); OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::DispatchDOMMutationBreakpoint: start: this=%p, pHaltState=%p\n"), this, haltState); if (!CanDispatchHalt(&haltState)) { return; } int currentOffset = -1; TryFinally([&]() { InitializeLocation(&haltState); OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::DispatchDOMMutationBreakpoint: initialized location: pHaltState=%p, pHaltState->IsValid()=%d\n"), haltState, haltState.IsValid()); if (haltState.IsValid()) { if (haltState.topFrame->IsInterpreterFrame()) { currentOffset = haltState.GetCurrentOffset(); Assert(currentOffset > 0); haltState.SetCurrentOffset(currentOffset - 1); } debugManager->stepController.Deactivate(&haltState); debugManager->asyncBreakController.Deactivate(); haltState.GetFunction()->CheckAndRegisterFuncToDiag(pScriptContext); Assert(haltState.GetFunction()->GetScriptContext() == pScriptContext); haltCallbackProbe->DispatchHalt(&haltState); } }, [&](bool) { // Restore the current offset; if (currentOffset != -1 && haltState.topFrame->IsInterpreterFrame()) { haltState.SetCurrentOffset(currentOffset); } DestroyLocation(); }); } void ProbeContainer::DispatchMutationBreakpoint(InterpreterHaltState* pHaltState) { Assert(pHaltState->stopType == STOP_MUTATIONBREAKPOINT); OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::DispatchMutationBreakpoint: start: this=%p, pHaltState=%p\n"), this, pHaltState); if (!CanDispatchHalt(pHaltState)) { return; } // will store Current offset of the bytecode block. int currentOffset = -1; TryFinally([&]() { InitializeLocation(pHaltState); OUTPUT_TRACE(Js::DebuggerPhase, _u("ProbeContainer::DispatchMutationBreakpoint: initialized location: pHaltState=%p, pHaltState->IsValid()=%d\n"), pHaltState, pHaltState->IsValid()); if (pHaltState->IsValid()) { // For interpreter frames, change the current location pointer of bytecode block, as it might be pointing to the next statement on the body. // In order to generated proper binding of mutation statement, the bytecode offset needed to be on the same span of the statement. // For native frames the offset is always current. // Move back a single byte to ensure that it falls under on the same statement. if (pHaltState->topFrame->IsInterpreterFrame()) { currentOffset = pHaltState->GetCurrentOffset(); Assert(currentOffset > 0); pHaltState->SetCurrentOffset(currentOffset - 1); } debugManager->stepController.Deactivate(pHaltState); debugManager->asyncBreakController.Deactivate(); pHaltState->GetFunction()->CheckAndRegisterFuncToDiag(pScriptContext); Assert(pHaltState->GetFunction()->GetScriptContext() == pScriptContext); haltCallbackProbe->DispatchHalt(pHaltState); } }, [&](bool) { // Restore the current offset; if (currentOffset != -1 && pHaltState->topFrame->IsInterpreterFrame()) { pHaltState->SetCurrentOffset(currentOffset); } DestroyLocation(); }); } void ProbeContainer::DispatchProbeHandlers(InterpreterHaltState* pHaltState) { if (!CanDispatchHalt(pHaltState)) { return; } TryFinally([&]() { InitializeLocation(pHaltState); if (pHaltState->IsValid()) { Js::ProbeList * localPendingProbeList = this->pendingProbeList; diagProbeList->Map([pHaltState, localPendingProbeList](int index, Probe * probe) { if (probe->CanHalt(pHaltState)) { localPendingProbeList->Add(probe); } }); if (localPendingProbeList->Count() != 0) { localPendingProbeList->MapUntil([&](int index, Probe * probe) { if (haltCallbackProbe && !haltCallbackProbe->IsInClosedState()) { debugManager->stepController.Deactivate(pHaltState); debugManager->asyncBreakController.Deactivate(); haltCallbackProbe->DispatchHalt(pHaltState); } // If SetNextStatement happened between multiple BPs on same location, IP changed so rest of dispatch are not valid. return this->IsSetNextStatementCalled(); }); } } }, [&](bool) { pendingProbeList->Clear(); DestroyLocation(); }); } void ProbeContainer::UpdateStep(bool fDuringSetupDebugApp/*= false*/) { // This function indicate that when the page is being refreshed and the last action we have done was stepping. // so update the state of the current stepController. if (debugManager) { // Usually we need to be in debug mode to UpdateStep. But during setting up new engine to debug mode we have an // ordering issue and the new engine will enter debug mode after this. So allow non-debug mode if fDuringSetupDebugApp. AssertMsg(fDuringSetupDebugApp || (pScriptContext && pScriptContext->IsScriptContextInDebugMode()), "Why UpdateStep when we are not in debug mode?"); debugManager->stepController.stepType = STEP_IN; } } void ProbeContainer::DeactivateStep() { if (debugManager) { debugManager->stepController.stepType = STEP_NONE; } } void ProbeContainer::InitializeInlineBreakEngine(HaltCallback* probe) { AssertMsg(!haltCallbackProbe || probe == haltCallbackProbe, "Overwrite of Inline bp probe with different probe"); haltCallbackProbe = probe; } void ProbeContainer::UninstallInlineBreakpointProbe(HaltCallback* probe) { haltCallbackProbe = nullptr; } void ProbeContainer::InitializeDebuggerScriptOptionCallback(DebuggerOptionsCallback* debuggerOptionsCallback) { Assert(this->debuggerOptionsCallback == nullptr); this->debuggerOptionsCallback = debuggerOptionsCallback; } void ProbeContainer::UninstallDebuggerScriptOptionCallback() { this->debuggerOptionsCallback = nullptr; } void ProbeContainer::AddProbe(Probe* pProbe) { if (pProbe->Install(nullptr)) { diagProbeList->Add(pProbe); } } void ProbeContainer::RemoveProbe(Probe* pProbe) { if (pProbe->Uninstall(nullptr)) { diagProbeList->Remove(pProbe); } } void ProbeContainer::RemoveAllProbes() { #ifdef ENABLE_MUTATION_BREAKPOINT if (HasMutationBreakpoints()) { ClearMutationBreakpoints(); } #endif for (int i = 0; i < diagProbeList->Count(); i++) { diagProbeList->Item(i)->Uninstall(nullptr); } diagProbeList->Clear(); } // Retrieves the offset of next statement in JavaScript user code for advancing from current statement // (normal flow-control is respected). // Returns true on success, false if it's not possible to get next statement for advance from current. bool ProbeContainer::GetNextUserStatementOffsetForAdvance(Js::FunctionBody* functionBody, ByteCodeReader* reader, int currentOffset, int* nextStatementOffset) { int originalCurrentOffset = currentOffset; while (GetNextUserStatementOffsetHelper(functionBody, currentOffset, FunctionBody::SAT_FromCurrentToNext, nextStatementOffset)) { Js::DebuggerScope *debuggerScope = functionBody->GetDiagCatchScopeObjectAt(currentOffset); if (debuggerScope != nullptr && !debuggerScope->IsOffsetInScope(*nextStatementOffset)) { // Our next statement is not within this catch block, So we cannot just jump to it, we need to return false so the stack unwind will happen. return false; } Assert(currentOffset < *nextStatementOffset); if (IsTmpRegCountIncreased(functionBody, reader, originalCurrentOffset, *nextStatementOffset, true /*restoreOffset*/)) { currentOffset = *nextStatementOffset; } else { return true; } } return false; } // Retrieves the offset of beginning of next statement in JavaScript user code for explicit set next statement // (normal flow-control is not respected, just get start next statement). // Returns true on success, false if it's not possible to get next statement for advance from current. bool ProbeContainer::GetNextUserStatementOffsetForSetNext(Js::FunctionBody* functionBody, int currentOffset, int* nextStatementOffset) { return GetNextUserStatementOffsetHelper(functionBody, currentOffset, FunctionBody::SAT_NextStatementStart, nextStatementOffset); } // Retrieves the offset of beginning of next statement in JavaScript user code for scenario specified by adjType. // Returns true on success, false if it's not possible to get next statement for advance from current. bool ProbeContainer::GetNextUserStatementOffsetHelper( Js::FunctionBody* functionBody, int currentOffset, FunctionBody::StatementAdjustmentType adjType, int* nextStatementOffset) { Assert(functionBody); Assert(nextStatementOffset); FunctionBody::StatementMapList* pStatementMaps = functionBody->GetStatementMaps(); if (pStatementMaps && pStatementMaps->Count() > 1) { for (int index = 0; index < pStatementMaps->Count() - 1; index++) { FunctionBody::StatementMap* pStatementMap = pStatementMaps->Item(index); if (!pStatementMap->isSubexpression && pStatementMap->byteCodeSpan.Includes(currentOffset)) { int nextMapIndex = index; FunctionBody::StatementMap* pNextStatementMap = Js::FunctionBody::GetNextNonSubexpressionStatementMap(pStatementMaps, ++nextMapIndex); if (!pNextStatementMap) { break; } // We are trying to find out the Branch opcode, between current and next statement. Skipping that would give use incorrect execution order. FunctionBody::StatementAdjustmentRecord adjRecord; if (pNextStatementMap->byteCodeSpan.begin > pStatementMap->byteCodeSpan.end && functionBody->GetBranchOffsetWithin(pStatementMap->byteCodeSpan.end, pNextStatementMap->byteCodeSpan.begin, &adjRecord) && (adjRecord.GetAdjustmentType() & adjType)) { Assert(adjRecord.GetByteCodeOffset() > (uint)pStatementMap->byteCodeSpan.end); *nextStatementOffset = adjRecord.GetByteCodeOffset(); } else { *nextStatementOffset = pNextStatementMap->byteCodeSpan.begin; } return true; } } } *nextStatementOffset = -1; return false; } bool ProbeContainer::FetchTmpRegCount(Js::FunctionBody * functionBody, Js::ByteCodeReader * reader, int atOffset, uint32 *pTmpRegCount, Js::OpCode *pOp) { Assert(pTmpRegCount); Assert(pOp); Js::LayoutSize layoutSize; reader->SetCurrentOffset(atOffset); *pOp = reader->ReadOp(layoutSize); if (*pOp == Js::OpCode::Break) { // User might have put breakpoint on the skipped or target statement, get the original opcode; if (functionBody->ProbeAtOffset(atOffset, pOp)) { if (Js::OpCodeUtil::IsPrefixOpcode(*pOp)) { *pOp = reader->ReadPrefixedOp(layoutSize, *pOp); } } } if (*pOp == Js::OpCode::EmitTmpRegCount) { switch (layoutSize) { case Js::SmallLayout: { const unaligned Js::OpLayoutReg1_Small * playout = reader->Reg1_Small(); *pTmpRegCount = (uint32)playout->R0; } break; case Js::MediumLayout: { const unaligned Js::OpLayoutReg1_Medium * playout = reader->Reg1_Medium(); *pTmpRegCount = (uint32)playout->R0; } break; case Js::LargeLayout: { const unaligned Js::OpLayoutReg1_Large * playout = reader->Reg1_Large(); *pTmpRegCount = (uint32)playout->R0; } break; default: Assert(false); __assume(false); } return true; } return false; } // The logic below makes use of number of tmp (temp) registers of A and B. // Set next statement is not allowed. // if numberOfTmpReg(A) < numberOfTmpReg(B) // or if any statement between A and B has number of tmpReg more than the lowest found. // // Get the temp register count for the A // This is a base and will store the lowest tmp reg count we have got yet, while walking the skipped statements. bool ProbeContainer::IsTmpRegCountIncreased(Js::FunctionBody* functionBody, ByteCodeReader* reader, int currentOffset, int nextStmOffset, bool restoreOffset) { Js::FunctionBody::StatementMapList* pStatementMaps = functionBody->GetStatementMaps(); Assert(pStatementMaps && pStatementMaps->Count() > 0); int direction = currentOffset < nextStmOffset ? 1 : -1; int startIndex = functionBody->GetEnclosingStatementIndexFromByteCode(currentOffset, true); uint32 tmpRegCountLowest = 0; // In the native code-gen (or interpreter which created from bailout points) the EmitTmpRegCount is not handled, // so lets calculate it by going through all statements backward from the current offset int index = startIndex; for (; index > 0; index--) { Js::FunctionBody::StatementMap* pStatementMap = pStatementMaps->Item(index); Js::OpCode op; if (!pStatementMap->isSubexpression && FetchTmpRegCount(functionBody, reader, pStatementMap->byteCodeSpan.begin, &tmpRegCountLowest, &op)) { break; } } // Reset to the current offset. reader->SetCurrentOffset(currentOffset); uint32 tmpRegCountOnNext = tmpRegCountLowest; // Will fetch the tmp reg count till the B and skipped statements. Assert(startIndex != -1); index = startIndex + direction; while (index > 0 && index < pStatementMaps->Count()) { Js::FunctionBody::StatementMap* pStatementMap = pStatementMaps->Item(index); if (pStatementMap->isSubexpression) { index += direction; continue; } if (direction == 1) // NOTE: Direction & corresponding condition { if (nextStmOffset < pStatementMap->byteCodeSpan.begin) // check only till nextstatement offset { break; } } Js::OpCode op; FetchTmpRegCount(functionBody, reader, pStatementMap->byteCodeSpan.begin, &tmpRegCountOnNext, &op); if (tmpRegCountOnNext < tmpRegCountLowest) { tmpRegCountLowest = tmpRegCountOnNext; } // On the reverse direction stop only when we find the tmpRegCount info for the setnext or below. if (direction == -1 && (op == Js::OpCode::EmitTmpRegCount)) { if (nextStmOffset >= pStatementMap->byteCodeSpan.begin) { break; } } index += direction; } // On the reverse way if we have reached the first statement, then our tmpRegCountOnNext is 0. if (direction == -1 && index == 0) { tmpRegCountOnNext = 0; } if (restoreOffset) { // Restore back the original IP. reader->SetCurrentOffset(currentOffset); } return (tmpRegCountOnNext > tmpRegCountLowest); } bool ProbeContainer::AdvanceToNextUserStatement(Js::FunctionBody* functionBody, ByteCodeReader* reader) { // Move back a byte to make sure we are within the bounds of // our current statement (See DispatchExceptionBreakpoint) int currentOffset = reader->GetCurrentOffset() - 1; int nextStatementOffset; if (this->GetNextUserStatementOffsetForAdvance(functionBody, reader, currentOffset, &nextStatementOffset)) { reader->SetCurrentOffset(nextStatementOffset); return true; } return false; } void ProbeContainer::SetNextStatementAt(int _bytecodeOffset) { Assert(_bytecodeOffset != debugManager->pCurrentInterpreterLocation->GetCurrentOffset()); this->bytecodeOffset = _bytecodeOffset; Assert(IsNextStatementChanged == false); this->IsNextStatementChanged = true; } void ProbeContainer::AsyncActivate(HaltCallback* haltCallback) { OUTPUT_TRACE(Js::DebuggerPhase, _u("Async break activated\n")); InterlockedExchangePointer((PVOID*)&this->pAsyncHaltCallback, haltCallback); Assert(debugManager); debugManager->asyncBreakController.Activate(haltCallback); } void ProbeContainer::AsyncDeactivate() { InterlockedExchangePointer((PVOID*)&this->pAsyncHaltCallback, nullptr); Assert(debugManager); debugManager->asyncBreakController.Deactivate(); } bool ProbeContainer::IsAsyncActivate() const { return this->pAsyncHaltCallback != nullptr; } void ProbeContainer::PrepDiagForEnterScript() { // This will be called from ParseScriptText. // This is to ensure the every script will call EnterScript back to host once, in-order to synchronize PDM with document. Assert(this->pScriptContext); if (this->pScriptContext->IsScriptContextInDebugMode()) { isForcedToEnterScriptStart = true; } } void ProbeContainer::RegisterContextToDiag(DWORD_PTR context, ArenaAllocator *alloc) { Assert(this->pScriptContext->IsScriptContextInSourceRundownOrDebugMode()); Assert(alloc); if (registeredFuncContextList == nullptr) { registeredFuncContextList = JsUtil::List::New(alloc); } registeredFuncContextList->Add(context); } bool ProbeContainer::IsContextRegistered(DWORD_PTR context) { return registeredFuncContextList != nullptr && registeredFuncContextList->Contains(context); } FunctionBody * ProbeContainer::GetGlobalFunc(ScriptContext* scriptContext, DWORD_PTR secondaryHostSourceContext) { return scriptContext->FindFunction([&secondaryHostSourceContext] (FunctionBody* pFunc) { return ((pFunc->GetSecondaryHostSourceContext() == secondaryHostSourceContext) && pFunc->GetIsGlobalFunc()); }); } bool ProbeContainer::HasAllowedForException(__in JavascriptExceptionObject* exceptionObject) { // We do not want to break on internal exception. if (isThrowInternal) { return false; } bool fIsFirstChance = false; bool fHasAllowed = false; bool fIsInNonUserCode = false; if (this->IsExceptionReportingEnabled() && (debugManager != nullptr)) { fHasAllowed = !debugManager->pThreadContext->HasCatchHandler(); if (!fHasAllowed) { if (IsFirstChanceExceptionEnabled()) { fHasAllowed = fIsFirstChance = true; } // We must determine if the exception is in user code AND if it's first chance as some debuggers // ask for both and filter later. // first validate if the throwing function is NonUserCode function, if not then verify if the exception is being caught in nonuser code. if (exceptionObject && exceptionObject->GetFunctionBody() != nullptr && !exceptionObject->GetFunctionBody()->IsNonUserCode()) { fIsInNonUserCode = IsNonUserCodeSupportEnabled() && !debugManager->pThreadContext->IsUserCode(); } if (!fHasAllowed) { fHasAllowed = fIsInNonUserCode; } } } if (exceptionObject) { exceptionObject->SetIsFirstChance(fIsFirstChance); exceptionObject->SetIsExceptionCaughtInNonUserCode(fIsInNonUserCode); } return fHasAllowed; } bool ProbeContainer::IsExceptionReportingEnabled() { return this->debuggerOptionsCallback == nullptr || this->debuggerOptionsCallback->IsExceptionReportingEnabled(); } bool ProbeContainer::IsFirstChanceExceptionEnabled() { return this->debuggerOptionsCallback != nullptr && this->debuggerOptionsCallback->IsFirstChanceExceptionEnabled(); } // Mentions if the debugger has enabled the support to differentiate the exception kind. bool ProbeContainer::IsNonUserCodeSupportEnabled() { return this->debuggerOptionsCallback != nullptr && this->debuggerOptionsCallback->IsNonUserCodeSupportEnabled(); } // Mentions if the debugger has enabled the support to display library stack frame. bool ProbeContainer::IsLibraryStackFrameSupportEnabled() { return CONFIG_FLAG(LibraryStackFrameDebugger) || (this->debuggerOptionsCallback != nullptr && this->debuggerOptionsCallback->IsLibraryStackFrameSupportEnabled()); } void ProbeContainer::PinPropertyRecord(const Js::PropertyRecord *propertyRecord) { Assert(propertyRecord); this->pinnedPropertyRecords->Add(propertyRecord); } #ifdef ENABLE_MUTATION_BREAKPOINT bool ProbeContainer::HasMutationBreakpoints() { return mutationBreakpointList && !mutationBreakpointList->Empty(); } void ProbeContainer::InsertMutationBreakpoint(MutationBreakpoint *mutationBreakpoint) { Assert(mutationBreakpoint); RecyclerWeakReference* weakBp = nullptr; pScriptContext->GetRecycler()->FindOrCreateWeakReferenceHandle(mutationBreakpoint, &weakBp); Assert(weakBp); // Make sure list is created prior to insertion InitMutationBreakpointListIfNeeded(); if (mutationBreakpointList->Contains(weakBp)) { return; } mutationBreakpointList->Add(weakBp); } void ProbeContainer::ClearMutationBreakpoints() { mutationBreakpointList->Map([=](uint i, RecyclerWeakReference* weakBp) { if (mutationBreakpointList->IsItemValid(i)) { Js::MutationBreakpoint* mutationBreakpoint = weakBp->Get(); if (mutationBreakpoint) { mutationBreakpoint->Reset(); } } }); mutationBreakpointList->ClearAndZero(); } void ProbeContainer::InitMutationBreakpointListIfNeeded() { if (!mutationBreakpointList && Js::MutationBreakpoint::IsFeatureEnabled(pScriptContext)) { Recycler *recycler = pScriptContext->GetRecycler(); mutationBreakpointList.Root(RecyclerNew(recycler, MutationBreakpointList, recycler), recycler); } } void ProbeContainer::RemoveMutationBreakpointListIfNeeded() { if (mutationBreakpointList) { if (HasMutationBreakpoints()) { ClearMutationBreakpoints(); } else { mutationBreakpointList->ClearAndZero(); } mutationBreakpointList.Unroot(pScriptContext->GetRecycler()); } } #endif } // namespace Js. #endif