| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386 |
- //-------------------------------------------------------------------------------------------------------
- // Copyright (C) Microsoft. All rights reserved.
- // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
- //-------------------------------------------------------------------------------------------------------
- #include "RuntimeDebugPch.h"
- #include "Language/InterpreterStackFrame.h"
- #define InvalidScriptId 0xFFFFFFFF
- namespace Js
- {
- InterpreterHaltState::InterpreterHaltState(StopType _stopType, const FunctionBody* _executingFunction, MutationBreakpoint* _activeMutationBP/*= nullptr*/) :
- stopType(_stopType),
- executingFunction(_executingFunction),
- topFrame(nullptr),
- framePointers(nullptr),
- referencedDiagnosticArena(nullptr),
- exceptionObject(nullptr),
- stringBuilder(nullptr),
- activeMutationBP(_activeMutationBP)
- {
- Assert(executingFunction || (stopType == STOP_EXCEPTIONTHROW || stopType == STOP_MUTATIONBREAKPOINT || stopType == STOP_DOMMUTATIONBREAKPOINT));
- }
- FunctionBody* InterpreterHaltState::GetFunction()
- {
- Assert(IsValid());
- return this->topFrame->GetFunction();
- }
- int InterpreterHaltState::GetCurrentOffset()
- {
- Assert(IsValid());
- return this->topFrame->GetByteCodeOffset();
- }
- void InterpreterHaltState::SetCurrentOffset(int offset)
- {
- Assert(IsValid());
- if (this->topFrame->IsInterpreterFrame())
- {
- // For interpreter frames, actual scenarios we need changed offset are: set next in topmost frame, ignore exception.
- // For throw exception we don't need it, but it doesn't hurt because interpreter will ignore the offset
- // and rather just throw the exception.
- this->topFrame->AsInterpreterFrame()->GetReader()->SetCurrentOffset(offset);
- }
- else
- {
- // For native frames, the only scenario we need to record changed offset is when we ignore exception.
- if (this->exceptionObject && this->exceptionObject->IsDebuggerSkip())
- {
- this->exceptionObject->SetByteCodeOffsetAfterDebuggerSkip(offset);
- }
- }
- }
- bool InterpreterHaltState::IsValid() const
- {
- // "executingFunction == nullptr" when dispatching exception or mutation bp.
- return topFrame && (topFrame->GetFunction() == executingFunction || executingFunction == nullptr);
- }
- StepController::StepController()
- : stepType(STEP_NONE),
- byteOffset(0),
- statementMap(NULL),
- frameCountWhenSet(0),
- frameAddrWhenSet((size_t)-1),
- stepCompleteOnInlineBreakpoint(false),
- pActivatedContext(NULL),
- scriptIdWhenSet(InvalidScriptId),
- returnedValueRecordingDepth(0),
- returnedValueList(nullptr)
- {
- }
- bool StepController::IsActive()
- {
- return stepType != STEP_NONE;
- }
- void StepController::Activate(StepType stepType, InterpreterHaltState* haltState)
- {
- this->stepType = stepType;
- this->byteOffset = haltState->GetCurrentOffset();
- this->pActivatedContext = haltState->framePointers->Peek()->GetScriptContext();
- Assert(this->pActivatedContext);
- Js::FunctionBody* functionBody = haltState->GetFunction();
- this->body.Root(functionBody, this->pActivatedContext->GetRecycler());
- this->statementMap = body->GetMatchingStatementMapFromByteCode(byteOffset, false);
- this->frameCountWhenSet = haltState->framePointers->Count();
- if (stepType != STEP_DOCUMENT)
- {
- this->frameAddrWhenSet = (size_t)haltState->framePointers->Peek(0)->GetStackAddress();
- }
- else
- {
- // for doc mode, do not bail out automatically on frame changes
- this->frameAddrWhenSet = (size_t)-1;
- }
- this->scriptIdWhenSet = GetScriptId(functionBody);
- if (this->returnedValueList == nullptr)
- {
- this->returnedValueList = JsUtil::List<ReturnedValue*>::New(this->pActivatedContext->GetRecycler());
- this->pActivatedContext->GetThreadContext()->SetReturnedValueList(this->returnedValueList);
- }
- }
- void StepController::AddToReturnedValueContainer(Js::Var returnValue, Js::JavascriptFunction * function, bool isValueOfReturnStatement)
- {
- if (this->pActivatedContext != nullptr) // This will be null when we execute scripts when on break.
- {
- ReturnedValue *valuePair = RecyclerNew(pActivatedContext->GetRecycler(), ReturnedValue, returnValue, function, isValueOfReturnStatement);
- this->returnedValueList->Add(valuePair);
- }
- }
- void StepController::AddReturnToReturnedValueContainer()
- {
- AddToReturnedValueContainer(nullptr/*returnValue*/, nullptr/*function*/, true/*isValueOfReturnStatement*/);
- }
- void StepController::StartRecordingCall()
- {
- returnedValueRecordingDepth++;
- }
- void StepController::EndRecordingCall(Js::Var returnValue, Js::JavascriptFunction * function)
- {
- if (IsActive() && this->pActivatedContext != nullptr && returnValue != nullptr)
- {
- if (this->pActivatedContext->GetThreadContext()->GetDebugManager()->IsAtDispatchHalt())
- {
- // OS bug 3050302 - Keeping this FatalError for finding other issues where we can record when we are at break
- Js::Throw::FatalInternalError();
- }
- bool isStepOut = stepType == STEP_OUT || stepType == STEP_DOCUMENT;
- // Record when :
- // If step-out/document : we need to record calls only which are already on the stack, that means the recording-depth is zero or negative.
- // if not step-out (step-in and step-over). only for those, which are called from the current call-site or the ones as if we step-out
- if ((!isStepOut && returnedValueRecordingDepth <= 1) || (isStepOut && returnedValueRecordingDepth <= 0))
- {
- // if we are step_document, we should be removing whatever we have collected so-far,
- // since they belong to the current document which is a library code
- if (stepType == STEP_DOCUMENT)
- {
- this->returnedValueList->ClearAndZero();
- }
- AddToReturnedValueContainer(returnValue, function, false/*isValueOfReturnStatement*/);
- }
- }
- returnedValueRecordingDepth--;
- }
- void StepController::ResetReturnedValueList()
- {
- returnedValueRecordingDepth = 0;
- if (this->returnedValueList != nullptr)
- {
- this->returnedValueList->ClearAndZero();
- }
- }
- void StepController::HandleResumeAction(Js::InterpreterHaltState* haltState, BREAKRESUMEACTION resumeAction)
- {
- ResetReturnedValueList();
- switch (resumeAction)
- {
- case BREAKRESUMEACTION_STEP_INTO:
- Activate(Js::STEP_IN, haltState);
- break;
- case BREAKRESUMEACTION_STEP_OVER:
- Activate(Js::STEP_OVER, haltState);
- break;
- case BREAKRESUMEACTION_STEP_OUT:
- Activate(Js::STEP_OUT, haltState);
- break;
- case BREAKRESUMEACTION_STEP_DOCUMENT:
- Activate(Js::STEP_DOCUMENT, haltState);
- break;
- }
- }
- void StepController::Deactivate(InterpreterHaltState* haltState /*=nullptr*/)
- {
- // If we are deactivating the step controller during ProbeContainer close or attach/detach we should clear return value list
- // If we break other than step -> clear the list.
- // If we step in and we land on different function (we are in recording phase the current function) -> clear the list
- if ((haltState == nullptr) || (haltState->stopType != Js::STOP_STEPCOMPLETE || (this->stepType == STEP_IN && this->returnedValueRecordingDepth > 0)))
- {
- ResetReturnedValueList();
- }
- if (this->body)
- {
- Assert(this->pActivatedContext);
- this->body.Unroot(this->pActivatedContext->GetRecycler());
- }
- this->pActivatedContext = NULL;
- stepType = STEP_NONE;
- byteOffset = Js::Constants::NoByteCodeOffset;
- statementMap = NULL;
- frameCountWhenSet = 0;
- scriptIdWhenSet = InvalidScriptId;
- frameAddrWhenSet = (size_t)-1;
- }
- bool StepController::IsStepComplete_AllowingFalsePositives(InterpreterStackFrame * stackFrame)
- {
- Assert(stackFrame);
- if (stepType == STEP_IN)
- {
- return true;
- }
- else if (stepType == STEP_DOCUMENT)
- {
- Assert(stackFrame->GetFunctionBody());
- return GetScriptId(stackFrame->GetFunctionBody()) != this->scriptIdWhenSet;
- }
- // A STEP_OUT or a STEP_OVER has not completed if we are currently deeper on the callstack.
- return this->frameAddrWhenSet <= stackFrame->GetStackAddress();
- }
- bool StepController::IsStepComplete(InterpreterHaltState* haltState, HaltCallback * haltCallback, OpCode originalOpcode)
- {
- int currentFrameCount = haltState->framePointers->Count();
- AssertMsg(currentFrameCount > 0, "In IsStepComplete we must have at least one frame.");
- FunctionBody* body = haltState->framePointers->Peek()->GetJavascriptFunction()->GetFunctionBody();
- bool canPossiblyHalt = haltCallback->CanHalt(haltState);
- OUTPUT_TRACE(Js::DebuggerPhase, _u("StepController::IsStepComplete(): stepType = %d "), stepType);
- uint scriptId = GetScriptId(body);
- AssertMsg(scriptId != InvalidScriptId, "scriptId cannot be 'invalid-reserved'");
- int byteOffset = haltState->GetCurrentOffset();
- bool fCanHalt = false;
- if (this->frameCountWhenSet > currentFrameCount && STEP_DOCUMENT != stepType)
- {
- // all steps match once the frame they started on has popped.
- fCanHalt = canPossiblyHalt;
- }
- else if (STEP_DOCUMENT == stepType)
- {
- OUTPUT_TRACE(Js::DebuggerPhase, _u("StepController::IsStepComplete(): docId when set=%d, currentDocId = %d, can Halt = %d, will halt = %d "), this->scriptIdWhenSet, scriptId, canPossiblyHalt, fCanHalt);
- fCanHalt = (scriptId != this->scriptIdWhenSet) && canPossiblyHalt;
- }
- else if (STEP_IN != stepType && this->frameCountWhenSet < currentFrameCount)
- {
- // Only step into allows the stack to be deeper
- OUTPUT_TRACE(Js::DebuggerPhase, _u("StepController::IsStepComplete(stepType = %d) returning false "), stepType);
- return false;
- }
- else if (STEP_OUT == stepType)
- {
- fCanHalt = this->frameCountWhenSet > currentFrameCount && canPossiblyHalt;
- }
- else if (nullptr != this->statementMap && this->statementMap->isSubexpression && STEP_IN != stepType)
- {
- // Only step into started from subexpression is allowed to stop on another subexpression
- Js::FunctionBody* pCurrentFuncBody = haltState->GetFunction();
- Js::FunctionBody::StatementMap* map = pCurrentFuncBody->GetMatchingStatementMapFromByteCode(byteOffset, false);
- if (nullptr != map && map->isSubexpression) // Execute remaining Subexpressions
- {
- fCanHalt = false;
- }
- else
- {
- Js::FunctionBody::StatementMap* outerMap = pCurrentFuncBody->GetMatchingStatementMapFromByteCode(this->statementMap->byteCodeSpan.begin, true);
- if (nullptr != outerMap && map == outerMap) // Execute the rest of current regular statement
- {
- fCanHalt = false;
- }
- else
- {
- fCanHalt = canPossiblyHalt;
- }
- }
- }
- else
- {
- // Match if we are no longer on the original statement. Stepping means move off current statement.
- if (body != this->body || NULL == this->statementMap ||
- !this->statementMap->byteCodeSpan.Includes(byteOffset))
- {
- fCanHalt = canPossiblyHalt;
- }
- }
- // At this point we are verifying of global return opcode.
- // The global returns are alway added as a zero range begin with zero.
- if (fCanHalt && originalOpcode == OpCode::Ret)
- {
- Js::FunctionBody* pCurrentFuncBody = haltState->GetFunction();
- Js::FunctionBody::StatementMap* map = pCurrentFuncBody->GetMatchingStatementMapFromByteCode(byteOffset, true);
- fCanHalt = !FunctionBody::IsDummyGlobalRetStatement(&map->sourceSpan);
- if (fCanHalt)
- {
- // We are breaking at last line of function, imagine '}'
- AddReturnToReturnedValueContainer();
- }
- }
- OUTPUT_TRACE(Js::DebuggerPhase, _u("StepController::IsStepComplete(stepType = %d) returning %d "), stepType, fCanHalt);
- return fCanHalt;
- }
- bool StepController::ContinueFromInlineBreakpoint()
- {
- bool ret = stepCompleteOnInlineBreakpoint;
- stepCompleteOnInlineBreakpoint = false;
- return ret;
- }
- uint StepController::GetScriptId(_In_ FunctionBody* body)
- {
- // safe value
- uint retValue = BuiltInFunctionsScriptId;
- if (body != nullptr)
- {
- // FYI - Different script blocks within a HTML page will have different source Info ids even though they have the same backing file.
- // It might imply we notify the debugger a bit more than needed - thus can be TODO for performance improvements of the Just-My-Code
- // or step to next document boundary mode.
- AssertMsg(body->GetUtf8SourceInfo() != nullptr, "body->GetUtf8SourceInfo() == nullptr");
- retValue = body->GetUtf8SourceInfo()->GetSourceInfoId();
- }
- return retValue;
- }
- AsyncBreakController::AsyncBreakController()
- : haltCallback(NULL)
- {
- }
- void AsyncBreakController::Activate(HaltCallback* haltCallback)
- {
- InterlockedExchangePointer((PVOID*)&this->haltCallback, haltCallback);
- }
- void AsyncBreakController::Deactivate()
- {
- InterlockedExchangePointer((PVOID*)&this->haltCallback, NULL);
- }
- bool AsyncBreakController::IsBreak()
- {
- return haltCallback != NULL;
- }
- bool AsyncBreakController::IsAtStoppingLocation(InterpreterHaltState* haltState)
- {
- HaltCallback* callback = this->haltCallback;
- if (callback)
- {
- return callback->CanHalt(haltState);
- }
- return false;
- }
- void AsyncBreakController::DispatchAndReset(InterpreterHaltState* haltState)
- {
- HaltCallback* callback = this->haltCallback;
- Deactivate();
- if (callback)
- {
- callback->DispatchHalt(haltState);
- }
- }
- }
|