| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138 |
- //-------------------------------------------------------------------------------------------------------
- // 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<const Js::PropertyRecord*>::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<DWORD_PTR>(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<ArenaAllocator>, 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<DWORD_PTR, ArenaAllocator>::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<Js::MutationBreakpoint>* 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<Js::MutationBreakpoint>* 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
|