blob: 72c792a823b3a06609406caf89faee30874ac18c [file]
//-------------------------------------------------------------------------------------------------------
// 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"
#if ENABLE_TTD
namespace TTD
{
TTDebuggerAbortException::TTDebuggerAbortException(uint32 abortCode, int64 optEventTime, int64 optMoveMode, const char16* staticAbortMessage)
: m_abortCode(abortCode), m_optEventTime(optEventTime), m_optMoveMode(optMoveMode), m_staticAbortMessage(staticAbortMessage)
{
;
}
TTDebuggerAbortException::~TTDebuggerAbortException()
{
;
}
TTDebuggerAbortException TTDebuggerAbortException::CreateAbortEndOfLog(const char16* staticMessage)
{
return TTDebuggerAbortException(1, -1, 0, staticMessage);
}
TTDebuggerAbortException TTDebuggerAbortException::CreateTopLevelAbortRequest(int64 targetEventTime, int64 moveMode, const char16* staticMessage)
{
return TTDebuggerAbortException(2, targetEventTime, moveMode, staticMessage);
}
TTDebuggerAbortException TTDebuggerAbortException::CreateUncaughtExceptionAbortRequest(int64 targetEventTime, const char16* staticMessage)
{
return TTDebuggerAbortException(3, targetEventTime, 0, staticMessage);
}
bool TTDebuggerAbortException::IsEndOfLog() const
{
return this->m_abortCode == 1;
}
bool TTDebuggerAbortException::IsEventTimeMove() const
{
return this->m_abortCode == 2;
}
bool TTDebuggerAbortException::IsTopLevelException() const
{
return this->m_abortCode == 3;
}
int64 TTDebuggerAbortException::GetTargetEventTime() const
{
return this->m_optEventTime;
}
int64 TTDebuggerAbortException::GetMoveMode() const
{
return this->m_optMoveMode;
}
const char16* TTDebuggerAbortException::GetStaticAbortMessage() const
{
return this->m_staticAbortMessage;
}
Js::FunctionBody* TTDebuggerSourceLocation::UpdatePostInflateFunctionBody_Helper(Js::FunctionBody* rootBody) const
{
for(uint32 i = 0; i < rootBody->GetNestedCount(); ++i)
{
Js::ParseableFunctionInfo* ipfi = rootBody->GetNestedFunctionForExecution(i);
Js::FunctionBody* ifb = JsSupport::ForceAndGetFunctionBody(ipfi);
if(this->m_functionLine == ifb->GetLineNumber() && this->m_functionColumn == ifb->GetColumnNumber())
{
return ifb;
}
else
{
Js::FunctionBody* found = this->UpdatePostInflateFunctionBody_Helper(ifb);
if(found != nullptr)
{
return found;
}
}
}
return nullptr;
}
TTDebuggerSourceLocation::TTDebuggerSourceLocation()
: m_sourceScriptLogId(TTD_INVALID_LOG_PTR_ID), m_bpId(-1),
m_etime(-1), m_ftime(0), m_ltime(0),
m_topLevelBodyId(0), m_functionLine(0), m_functionColumn(0), m_line(0), m_column(0)
{
;
}
TTDebuggerSourceLocation::TTDebuggerSourceLocation(const TTDebuggerSourceLocation& other)
: m_sourceScriptLogId(other.m_sourceScriptLogId), m_bpId(other.m_bpId),
m_etime(other.m_etime), m_ftime(other.m_ftime), m_ltime(other.m_ltime),
m_topLevelBodyId(other.m_topLevelBodyId), m_functionLine(other.m_functionLine), m_functionColumn(other.m_functionColumn), m_line(other.m_line), m_column(other.m_column)
{
;
}
TTDebuggerSourceLocation::~TTDebuggerSourceLocation()
{
this->Clear();
}
TTDebuggerSourceLocation& TTDebuggerSourceLocation::operator= (const TTDebuggerSourceLocation& other)
{
if(this != &other)
{
this->SetLocationCopy(other);
}
return *this;
}
#if ENABLE_TTD_INTERNAL_DIAGNOSTICS
void TTDebuggerSourceLocation::PrintToConsole() const
{
if(!this->HasValue())
{
wprintf(_u("undef"));
}
else
{
printf("BP: { bpId: %I64i, ctx: %I64u, topId: %I32u, fline: %I32u, fcolumn: %I32u, line: %I32u, column: %I32u }", this->m_bpId, this->m_sourceScriptLogId, this->m_topLevelBodyId, this->m_functionLine, this->m_functionColumn, this->m_line, this->m_column);
if(this->m_etime != -1)
{
printf(" TTDTime: { etime: %I64i, ftime: %I64i, ltime: %I64i }", this->m_etime, this->m_ftime, this->m_ltime);
}
}
}
#endif
void TTDebuggerSourceLocation::Initialize()
{
this->m_sourceScriptLogId = TTD_INVALID_LOG_PTR_ID;
this->m_bpId = -1;
this->m_etime = -1;
this->m_ftime = 0;
this->m_ltime = 0;
this->m_topLevelBodyId = 0;
this->m_functionLine = 0;
this->m_functionColumn = 0;
this->m_line = 0;
this->m_column = 0;
}
bool TTDebuggerSourceLocation::HasValue() const
{
return this->m_sourceScriptLogId != TTD_INVALID_LOG_PTR_ID;
}
bool TTDebuggerSourceLocation::HasTTDTimeValue() const
{
return this->m_etime != -1;
}
void TTDebuggerSourceLocation::Clear()
{
this->m_sourceScriptLogId = TTD_INVALID_LOG_PTR_ID;
this->m_bpId = -1;
this->m_etime = -1;
this->m_ftime = 0;
this->m_ltime = 0;
this->m_topLevelBodyId = 0;
this->m_functionLine = 0;
this->m_functionColumn = 0;
this->m_line = 0;
this->m_column = 0;
}
void TTDebuggerSourceLocation::SetLocationCopy(const TTDebuggerSourceLocation& other)
{
this->m_sourceScriptLogId = other.m_sourceScriptLogId;
this->m_bpId = other.m_bpId;
this->m_etime = other.m_etime;
this->m_ftime = other.m_ftime;
this->m_ltime = other.m_ltime;
this->m_topLevelBodyId = other.m_topLevelBodyId;
this->m_functionLine = other.m_functionLine;
this->m_functionColumn = other.m_functionColumn;
this->m_line = other.m_line;
this->m_column = other.m_column;
}
void TTDebuggerSourceLocation::SetLocationFromFrame(int64 topLevelETime, const SingleCallCounter& callFrame)
{
ULONG srcLine = 0;
LONG srcColumn = -1;
uint32 startOffset = callFrame.Function->GetStatementStartOffset(callFrame.CurrentStatementIndex);
callFrame.Function->GetSourceLineFromStartOffset_TTD(startOffset, &srcLine, &srcColumn);
this->SetLocationFull(topLevelETime, callFrame.FunctionTime, callFrame.LoopTime, callFrame.Function, (uint32)srcLine, (uint32)srcColumn);
}
void TTDebuggerSourceLocation::SetLocationFromFunctionEntryAnyTime(int64 topLevelETime, Js::FunctionBody* body)
{
ULONG srcLine = 0;
LONG srcColumn = -1;
uint32 startOffset = body->GetStatementStartOffset(0);
body->GetSourceLineFromStartOffset_TTD(startOffset, &srcLine, &srcColumn);
this->SetLocationFull(topLevelETime, -1, -1, body, (uint32)srcLine, (uint32)srcColumn);
}
void TTDebuggerSourceLocation::SetLocationFull(int64 etime, int64 ftime, int64 ltime, Js::FunctionBody* body, ULONG line, LONG column)
{
this->m_sourceScriptLogId = body->GetScriptContext()->ScriptContextLogTag;
this->m_bpId = -1;
this->m_etime = etime;
this->m_ftime = ftime;
this->m_ltime = ltime;
this->m_topLevelBodyId = body->GetScriptContext()->TTDContextInfo->FindTopLevelCtrForBody(body);
this->m_functionLine = body->GetLineNumber();
this->m_functionColumn = body->GetColumnNumber();
this->m_line = (uint32)line;
this->m_column = (uint32)column;
}
void TTDebuggerSourceLocation::SetLocationWithBP(int64 bpId, Js::FunctionBody* body, ULONG line, LONG column)
{
this->m_sourceScriptLogId = body->GetScriptContext()->ScriptContextLogTag;
this->m_bpId = bpId;
this->m_etime = -1;
this->m_ftime = -1;
this->m_ltime = -1;
this->m_topLevelBodyId = body->GetScriptContext()->TTDContextInfo->FindTopLevelCtrForBody(body);
this->m_functionLine = body->GetLineNumber();
this->m_functionColumn = body->GetColumnNumber();
this->m_line = (uint32)line;
this->m_column = (uint32)column;
}
int64 TTDebuggerSourceLocation::GetRootEventTime() const
{
return this->m_etime;
}
int64 TTDebuggerSourceLocation::GetFunctionTime() const
{
return this->m_ftime;
}
int64 TTDebuggerSourceLocation::GetLoopTime() const
{
return this->m_ltime;
}
TTD_LOG_PTR_ID TTDebuggerSourceLocation::GetScriptLogTagId() const
{
return this->m_sourceScriptLogId;
}
Js::FunctionBody* TTDebuggerSourceLocation::LoadFunctionBodyIfPossible(Js::ScriptContext* inCtx) const
{
Js::FunctionBody* rootBody = inCtx->TTDContextInfo->FindRootBodyByTopLevelCtr(this->m_topLevelBodyId);
if(rootBody == nullptr)
{
return nullptr;
}
if(this->m_functionLine == rootBody->GetLineNumber() && this->m_functionColumn == rootBody->GetColumnNumber())
{
return rootBody;
}
else
{
return this->UpdatePostInflateFunctionBody_Helper(rootBody);
}
}
int64 TTDebuggerSourceLocation::GetBPId() const
{
return this->m_bpId;
}
uint32 TTDebuggerSourceLocation::GetTopLevelBodyId() const
{
return this->m_topLevelBodyId;
}
uint32 TTDebuggerSourceLocation::GetSourceLine() const
{
return this->m_line;
}
uint32 TTDebuggerSourceLocation::GetSourceColumn() const
{
return this->m_column;
}
bool TTDebuggerSourceLocation::AreSameLocations(const TTDebuggerSourceLocation& sl1, const TTDebuggerSourceLocation& sl2)
{
//first check the code locations
if(sl1.m_topLevelBodyId != sl2.m_topLevelBodyId || sl1.m_line != sl2.m_line || sl1.m_column != sl2.m_column)
{
return false;
}
//next check the time values
if(sl1.m_etime != sl2.m_etime || sl1.m_ftime != sl2.m_ftime || sl1.m_ltime != sl2.m_ltime)
{
return false;
}
return true;
}
bool TTDebuggerSourceLocation::AreSameLocations_PlaceOnly(const TTDebuggerSourceLocation& sl1, const TTDebuggerSourceLocation& sl2)
{
return (sl1.m_topLevelBodyId == sl2.m_topLevelBodyId && sl1.m_line == sl2.m_line && sl1.m_column == sl2.m_column);
}
TTLastReturnLocationInfo::TTLastReturnLocationInfo()
: m_isExceptionFrame(false)
{
this->m_lastFrame = { 0 };
}
void TTLastReturnLocationInfo::SetReturnLocation(const SingleCallCounter& cframe)
{
this->m_isExceptionFrame = false;
this->m_lastFrame = cframe;
}
void TTLastReturnLocationInfo::SetExceptionLocation(const SingleCallCounter& cframe)
{
this->m_isExceptionFrame = true;
this->m_lastFrame = cframe;
}
bool TTLastReturnLocationInfo::IsDefined() const
{
return this->m_lastFrame.Function != nullptr;
}
bool TTLastReturnLocationInfo::IsReturnLocation() const
{
return this->IsDefined() && !this->m_isExceptionFrame;
}
bool TTLastReturnLocationInfo::IsExceptionLocation() const
{
return this->IsDefined() && this->m_isExceptionFrame;
}
const SingleCallCounter& TTLastReturnLocationInfo::GetLocation() const
{
TTDAssert(this->IsDefined(), "Should check this!");
return this->m_lastFrame;
}
void TTLastReturnLocationInfo::Clear()
{
if(this->IsDefined())
{
this->m_isExceptionFrame = false;
this->m_lastFrame = { 0 };
}
}
void TTLastReturnLocationInfo::ClearReturnOnly()
{
if(this->IsDefined() && !this->m_isExceptionFrame)
{
this->Clear();
}
}
void TTLastReturnLocationInfo::ClearExceptionOnly()
{
if(this->IsDefined() && this->m_isExceptionFrame)
{
this->Clear();
}
}
//////////////////
bool ExecutionInfoManager::ShouldSuppressBreakpointsForTimeTravelMove(TTDMode mode)
{
return (mode & TTD::TTDMode::DebuggerSuppressBreakpoints) == TTD::TTDMode::DebuggerSuppressBreakpoints;
}
bool ExecutionInfoManager::ShouldRecordBreakpointsDuringTimeTravelScan(TTDMode mode)
{
return (mode & TTD::TTDMode::DebuggerLogBreakpoints) == TTD::TTDMode::DebuggerLogBreakpoints;
}
ExecutionInfoManager::ExecutionInfoManager()
: m_topLevelCallbackEventTime(-1), m_runningFunctionTimeCtr(0), m_callStack(&HeapAllocator::Instance),
m_debuggerNotifiedTopLevelBodies(&HeapAllocator::Instance),
m_lastReturnLocation(), m_lastExceptionPropagating(false), m_lastExceptionLocation(),
m_breakOnFirstUserCode(false),
m_pendingTTDBP(), m_pendingTTDMoveMode(-1), m_activeBPId(-1), m_shouldRemoveWhenDone(false), m_activeTTDBP(),
m_hitContinueSearchBP(false), m_continueBreakPoint(),
m_unRestoredBreakpoints(&HeapAllocator::Instance)
{
;
}
ExecutionInfoManager::~ExecutionInfoManager()
{
if(this->m_unRestoredBreakpoints.Count() != 0)
{
for(int32 i = 0; i < this->m_unRestoredBreakpoints.Count(); ++i)
{
HeapDelete(this->m_unRestoredBreakpoints.Item(i));
}
}
}
#if ENABLE_BASIC_TRACE || ENABLE_FULL_BC_TRACE
TraceLogger* ExecutionInfoManager::GetTraceLogger()
{
return &(this->m_diagnosticLogger);
}
#endif
const SingleCallCounter& ExecutionInfoManager::GetTopCallCounter() const
{
TTDAssert(this->m_callStack.Count() != 0, "Empty stack!");
return this->m_callStack.Item(this->m_callStack.Count() - 1);
}
SingleCallCounter& ExecutionInfoManager::GetTopCallCounter()
{
TTDAssert(this->m_callStack.Count() != 0, "Empty stack!");
return this->m_callStack.Item(this->m_callStack.Count() - 1);
}
bool ExecutionInfoManager::TryGetTopCallCallerCounter(SingleCallCounter& caller) const
{
if(this->m_callStack.Count() < 2)
{
return false;
}
else
{
caller = this->m_callStack.Item(this->m_callStack.Count() - 2);
return true;
}
}
void ExecutionInfoManager::PushCallEvent(Js::JavascriptFunction* function, uint32 argc, Js::Var* argv, bool isInFinally)
{
//Clear any previous last return frame info
this->m_lastReturnLocation.ClearReturnOnly();
this->m_runningFunctionTimeCtr++;
SingleCallCounter cfinfo;
cfinfo.Function = function->GetFunctionBody();
#if ENABLE_TTD_INTERNAL_DIAGNOSTICS
cfinfo.Name = cfinfo.Function->GetExternalDisplayName();
#endif
cfinfo.FunctionTime = this->m_runningFunctionTimeCtr;
cfinfo.LoopTime = 0;
cfinfo.CurrentStatementIndex = -1;
cfinfo.CurrentStatementLoopTime = 0;
cfinfo.LastStatementIndex = -1;
cfinfo.LastStatementLoopTime = 0;
cfinfo.CurrentStatementBytecodeMin = UINT32_MAX;
cfinfo.CurrentStatementBytecodeMax = UINT32_MAX;
this->m_callStack.Add(cfinfo);
////
//If we are running for debugger then check if we need to set a breakpoint at entry to the first function we execute
if(this->m_breakOnFirstUserCode)
{
this->m_breakOnFirstUserCode = false;
this->m_activeTTDBP.SetLocationFromFunctionEntryAnyTime(this->m_topLevelCallbackEventTime, cfinfo.Function);
this->SetActiveBP_Helper(function->GetFunctionBody()->GetScriptContext()->GetThreadContext()->TTDContext);
}
////
#if ENABLE_BASIC_TRACE || ENABLE_FULL_BC_TRACE
this->m_diagnosticLogger.WriteCall(function, false, argc, argv, function->GetFunctionBody()->GetScriptContext()->GetThreadContext()->TTDLog->GetLastEventTime());
#endif
}
void ExecutionInfoManager::PopCallEvent(Js::JavascriptFunction* function, Js::Var result)
{
this->m_lastReturnLocation.SetReturnLocation(this->m_callStack.Last());
this->m_runningFunctionTimeCtr++;
this->m_callStack.RemoveAtEnd();
#if ENABLE_BASIC_TRACE || ENABLE_FULL_BC_TRACE
this->m_diagnosticLogger.WriteReturn(function, result, function->GetFunctionBody()->GetScriptContext()->GetThreadContext()->TTDLog->GetLastEventTime());
#endif
}
void ExecutionInfoManager::PopCallEventException(Js::JavascriptFunction* function)
{
//If we already have the last return as an exception then just leave it.
//That is where the exception was first rasied, this return is just propagating it in this return.
if(!this->m_lastReturnLocation.IsExceptionLocation())
{
this->m_lastReturnLocation.SetExceptionLocation(this->m_callStack.Last());
}
if(!m_lastExceptionPropagating)
{
this->m_lastExceptionLocation.SetLocationFromFrame(this->m_topLevelCallbackEventTime, this->m_callStack.Last());
this->m_lastExceptionPropagating = true;
}
this->m_runningFunctionTimeCtr++;
this->m_callStack.RemoveAtEnd();
#if ENABLE_BASIC_TRACE || ENABLE_FULL_BC_TRACE
this->m_diagnosticLogger.WriteReturnException(function, function->GetFunctionBody()->GetScriptContext()->GetThreadContext()->TTDLog->GetLastEventTime());
#endif
}
void ExecutionInfoManager::ProcessCatchInfoForLastExecutedStatements()
{
this->m_lastReturnLocation.Clear();
this->m_lastExceptionPropagating = false;
}
void ExecutionInfoManager::SetBreakOnFirstUserCode()
{
this->m_breakOnFirstUserCode = true;
}
void ExecutionInfoManager::SetPendingTTDStepBackMove()
{
this->GetPreviousTimeAndPositionForDebugger(this->m_pendingTTDBP);
this->m_pendingTTDMoveMode = 0;
}
void ExecutionInfoManager::SetPendingTTDStepBackIntoMove()
{
this->GetLastExecutedTimeAndPositionForDebugger(this->m_pendingTTDBP);
this->m_pendingTTDMoveMode = 0;
}
void ExecutionInfoManager::SetPendingTTDReverseContinueMove(uint64 moveflag)
{
this->m_continueBreakPoint.Clear();
this->GetTimeAndPositionForDebugger(this->m_pendingTTDBP);
this->m_pendingTTDMoveMode = moveflag;
}
void ExecutionInfoManager::SetPendingTTDUnhandledException()
{
this->GetLastExceptionTimeAndPositionForDebugger(this->m_pendingTTDBP);
this->m_lastExceptionPropagating = false;
this->m_pendingTTDMoveMode = 0;
}
bool ExecutionInfoManager::ProcessBPInfoPreBreak(Js::FunctionBody* fb, const EventLog* elog)
{
//if we aren't in debug mode then we always trigger BP's
if(!fb->GetScriptContext()->ShouldPerformDebuggerAction())
{
return true;
}
//If we are in debugger mode but are suppressing BP's for movement then suppress them
if(ExecutionInfoManager::ShouldSuppressBreakpointsForTimeTravelMove(elog->GetCurrentTTDMode()))
{
//Check if we need to record the visit to this bp
if(ExecutionInfoManager::ShouldRecordBreakpointsDuringTimeTravelScan(elog->GetCurrentTTDMode()))
{
this->AddCurrentLocationDuringScan(elog->GetCurrentTopLevelEventTime());
}
return false;
}
//If we are in debug mode and don't have an active BP target then we treat BP's as usual
if(!this->m_activeTTDBP.HasValue())
{
return true;
}
//Finally we are in debug mode and we have an active BP target so only break if the BP is satisfied
const SingleCallCounter& cfinfo = this->GetTopCallCounter();
//Make sure we are in the right source code (via top level body ids)
if(this->m_activeTTDBP.GetTopLevelBodyId() != cfinfo.Function->GetScriptContext()->TTDContextInfo->FindTopLevelCtrForBody(cfinfo.Function))
{
return false;
}
ULONG srcLine = 0;
LONG srcColumn = -1;
uint32 startOffset = cfinfo.Function->GetStatementStartOffset(cfinfo.CurrentStatementIndex);
cfinfo.Function->GetSourceLineFromStartOffset_TTD(startOffset, &srcLine, &srcColumn);
bool locationOk = ((uint32)srcLine == this->m_activeTTDBP.GetSourceLine()) & ((uint32)srcColumn == this->m_activeTTDBP.GetSourceColumn());
bool ftimeOk = (this->m_activeTTDBP.GetFunctionTime() == -1) | ((uint64)this->m_activeTTDBP.GetFunctionTime() == cfinfo.FunctionTime);
bool ltimeOk = (this->m_activeTTDBP.GetLoopTime() == -1) | ((uint64)this->m_activeTTDBP.GetLoopTime() == cfinfo.CurrentStatementLoopTime);
return locationOk & ftimeOk & ltimeOk;
}
void ExecutionInfoManager::ProcessBPInfoPostBreak(Js::FunctionBody* fb)
{
if(!fb->GetScriptContext()->ShouldPerformDebuggerAction())
{
return;
}
if(this->m_activeTTDBP.HasValue())
{
this->ClearActiveBPInfo(fb->GetScriptContext()->GetThreadContext()->TTDContext);
}
if(this->m_pendingTTDBP.HasValue())
{
//Reset any step controller logic
fb->GetScriptContext()->GetThreadContext()->GetDebugManager()->stepController.Deactivate();
throw TTD::TTDebuggerAbortException::CreateTopLevelAbortRequest(this->m_pendingTTDBP.GetRootEventTime(), this->m_pendingTTDMoveMode, _u("Reverse operation requested."));
}
}
bool ExecutionInfoManager::IsLocationActiveBPAndNotExplicitBP(const TTDebuggerSourceLocation& current) const
{
//If we shound't remove the active BP when done then it is a user visible one so we just return false
if(!this->m_shouldRemoveWhenDone)
{
return false;
}
//Check to see if the locations are the same
return TTDebuggerSourceLocation::AreSameLocations_PlaceOnly(this->m_activeTTDBP, current);
}
void ExecutionInfoManager::AddCurrentLocationDuringScan(int64 topLevelEventTime)
{
TTDebuggerSourceLocation current;
current.SetLocationFromFrame(topLevelEventTime, this->m_callStack.Last());
if(this->m_activeTTDBP.HasValue() && TTDebuggerSourceLocation::AreSameLocations(this->m_activeTTDBP, current))
{
this->m_hitContinueSearchBP = true;
}
if(!this->m_hitContinueSearchBP && !this->IsLocationActiveBPAndNotExplicitBP(current))
{
this->m_continueBreakPoint.SetLocationCopy(current);
}
}
bool ExecutionInfoManager::TryFindAndSetPreviousBP()
{
if(!this->m_continueBreakPoint.HasValue())
{
return false;
}
else
{
this->m_pendingTTDBP.SetLocationCopy(this->m_continueBreakPoint);
this->m_continueBreakPoint.Clear();
return true;
}
}
//Get the target event time for the pending TTD breakpoint
int64 ExecutionInfoManager::GetPendingTTDBPTargetEventTime() const
{
TTDAssert(this->m_pendingTTDBP.HasValue(), "We did not set the pending TTD breakpoint value?");
return this->m_pendingTTDBP.GetRootEventTime();
}
void ExecutionInfoManager::LoadPreservedBPInfo(ThreadContext* threadContext)
{
while(this->m_unRestoredBreakpoints.Contains(nullptr))
{
this->m_unRestoredBreakpoints.Remove(nullptr);
}
const JsUtil::List<Js::ScriptContext*, HeapAllocator>& ctxs = threadContext->TTDContext->GetTTDContexts();
for(int32 i = 0; i < ctxs.Count(); ++i)
{
Js::ScriptContext* ctx = ctxs.Item(i);
JsUtil::List<uint32, HeapAllocator> bpIdsToDelete(&HeapAllocator::Instance);
Js::ProbeContainer* probeContainer = ctx->GetDebugContext()->GetProbeContainer();
probeContainer->MapProbes([&](int j, Js::Probe* pProbe)
{
Js::BreakpointProbe* bp = (Js::BreakpointProbe*)pProbe;
if((int64)bp->GetId() != this->m_activeBPId)
{
Js::FunctionBody* body = bp->GetFunctionBody();
int32 bpIndex = body->GetEnclosingStatementIndexFromByteCode(bp->GetBytecodeOffset());
ULONG srcLine = 0;
LONG srcColumn = -1;
uint32 startOffset = body->GetStatementStartOffset(bpIndex);
body->GetSourceLineFromStartOffset_TTD(startOffset, &srcLine, &srcColumn);
TTDebuggerSourceLocation* bpLocation = TT_HEAP_NEW(TTDebuggerSourceLocation);
bpLocation->SetLocationWithBP(bp->GetId(), body, srcLine, srcColumn);
this->m_unRestoredBreakpoints.Add(bpLocation);
bpIdsToDelete.Add(bp->GetId());
}
});
for(int32 j = 0; j < bpIdsToDelete.Count(); ++j)
{
ctx->TTDHostCallbackFunctor.pfOnBPDeleteCallback(ctx->TTDHostCallbackFunctor.HostRuntime, bpIdsToDelete.Item(j));
}
//done with last context so release the debug document dictionary info
if(i == ctxs.Count() - 1)
{
ctx->TTDHostCallbackFunctor.pfOnBPClearDocumentCallback(ctx->TTDHostCallbackFunctor.HostRuntime);
}
}
}
void ExecutionInfoManager::AddPreservedBPsForTopLevelLoad(uint32 topLevelBodyId, Js::FunctionBody* body)
{
Js::ScriptContext* ctx = body->GetScriptContext();
Js::Utf8SourceInfo* utf8SourceInfo = body->GetUtf8SourceInfo();
for(int32 i = 0; i < this->m_unRestoredBreakpoints.Count(); ++i)
{
if(this->m_unRestoredBreakpoints.Item(i) == nullptr)
{
continue;
}
const TTDebuggerSourceLocation* bpLocation = this->m_unRestoredBreakpoints.Item(i);
if(bpLocation->GetTopLevelBodyId() == topLevelBodyId)
{
BOOL isNewBP = FALSE;
ctx->TTDHostCallbackFunctor.pfOnBPRegisterCallback(ctx->TTDHostCallbackFunctor.HostRuntime, bpLocation->GetBPId(), ctx, utf8SourceInfo, bpLocation->GetSourceLine(), bpLocation->GetSourceColumn(), &isNewBP);
TTDAssert(isNewBP, "We should be restoring a breapoint we removed previously!");
//Now that it is set we can clear it from our list
this->m_unRestoredBreakpoints.SetItem(i, nullptr);
}
}
if(this->m_activeTTDBP.HasValue() && (this->m_activeBPId == -1) && (this->m_activeTTDBP.GetTopLevelBodyId() == topLevelBodyId))
{
this->SetActiveBP_Helper(body->GetScriptContext()->GetThreadContext()->TTDContext);
}
}
void ExecutionInfoManager::UpdateLoopCountInfo()
{
SingleCallCounter& cfinfo = this->m_callStack.Last();
cfinfo.LoopTime++;
}
void ExecutionInfoManager::UpdateCurrentStatementInfo(uint bytecodeOffset)
{
SingleCallCounter& cfinfo = this->GetTopCallCounter();
if((cfinfo.CurrentStatementBytecodeMin <= bytecodeOffset) & (bytecodeOffset <= cfinfo.CurrentStatementBytecodeMax))
{
return;
}
else
{
Js::FunctionBody* fb = cfinfo.Function;
int32 cIndex = fb->GetEnclosingStatementIndexFromByteCode(bytecodeOffset, true);
TTDAssert(cIndex != -1, "Should always have a mapping.");
//we moved to a new statement
Js::FunctionBody::StatementMap* pstmt = fb->GetStatementMaps()->Item(cIndex);
bool newstmt = (cIndex != cfinfo.CurrentStatementIndex && pstmt->byteCodeSpan.begin <= (int)bytecodeOffset && (int)bytecodeOffset <= pstmt->byteCodeSpan.end);
if(newstmt)
{
cfinfo.LastStatementIndex = cfinfo.CurrentStatementIndex;
cfinfo.LastStatementLoopTime = cfinfo.CurrentStatementLoopTime;
cfinfo.CurrentStatementIndex = cIndex;
cfinfo.CurrentStatementLoopTime = cfinfo.LoopTime;
cfinfo.CurrentStatementBytecodeMin = (uint32)pstmt->byteCodeSpan.begin;
cfinfo.CurrentStatementBytecodeMax = (uint32)pstmt->byteCodeSpan.end;
#if ENABLE_FULL_BC_TRACE
ULONG srcLine = 0;
LONG srcColumn = -1;
uint32 startOffset = cfinfo.Function->GetFunctionBody()->GetStatementStartOffset(cfinfo.CurrentStatementIndex);
cfinfo.Function->GetFunctionBody()->GetSourceLineFromStartOffset_TTD(startOffset, &srcLine, &srcColumn);
this->m_diagnosticLogger.WriteStmtIndex((uint32)srcLine, (uint32)srcColumn);
#endif
}
}
}
void ExecutionInfoManager::GetTimeAndPositionForDebugger(TTDebuggerSourceLocation& sourceLocation) const
{
const SingleCallCounter& cfinfo = this->GetTopCallCounter();
ULONG srcLine = 0;
LONG srcColumn = -1;
uint32 startOffset = cfinfo.Function->GetStatementStartOffset(cfinfo.CurrentStatementIndex);
cfinfo.Function->GetSourceLineFromStartOffset_TTD(startOffset, &srcLine, &srcColumn);
sourceLocation.SetLocationFull(this->m_topLevelCallbackEventTime, cfinfo.FunctionTime, cfinfo.LoopTime, cfinfo.Function, srcLine, srcColumn);
}
#if ENABLE_OBJECT_SOURCE_TRACKING
void ExecutionInfoManager::GetTimeAndPositionForDiagnosticObjectTracking(DiagnosticOrigin& originInfo) const
{
const SingleCallCounter& cfinfo = this->GetTopCallCounter();
ULONG srcLine = 0;
LONG srcColumn = -1;
uint32 startOffset = cfinfo.Function->GetStatementStartOffset(cfinfo.CurrentStatementIndex);
cfinfo.Function->GetSourceLineFromStartOffset_TTD(startOffset, &srcLine, &srcColumn);
SetDiagnosticOriginInformation(originInfo, srcLine, cfinfo.Function->GetFunctionBody()->GetScriptContext()->GetThreadContext()->TTDLog->GetLastEventTime(), cfinfo.FunctionTime, cfinfo.LoopTime);
}
#endif
bool ExecutionInfoManager::GetPreviousTimeAndPositionForDebugger(TTDebuggerSourceLocation& sourceLocation) const
{
bool noPrevious = false;
const SingleCallCounter& cfinfo = this->GetTopCallCounter();
//if we are at the first statement in the function then we want the parents current
Js::FunctionBody* fbody = nullptr;
int32 statementIndex = -1;
uint64 ftime = 0;
uint64 ltime = 0;
if(cfinfo.LastStatementIndex == -1)
{
SingleCallCounter cfinfoCaller = { 0 };
bool hasCaller = this->TryGetTopCallCallerCounter(cfinfoCaller);
//check if we are at the first statement in the callback event
if(!hasCaller)
{
//Set the position info to the current statement and return true
noPrevious = true;
ftime = cfinfo.FunctionTime;
ltime = cfinfo.CurrentStatementLoopTime;
fbody = cfinfo.Function;
statementIndex = cfinfo.CurrentStatementIndex;
}
else
{
ftime = cfinfoCaller.FunctionTime;
ltime = cfinfoCaller.CurrentStatementLoopTime;
fbody = cfinfoCaller.Function;
statementIndex = cfinfoCaller.CurrentStatementIndex;
}
}
else
{
ftime = cfinfo.FunctionTime;
ltime = cfinfo.LastStatementLoopTime;
fbody = cfinfo.Function;
statementIndex = cfinfo.LastStatementIndex;
}
ULONG srcLine = 0;
LONG srcColumn = -1;
uint32 startOffset = fbody->GetStatementStartOffset(statementIndex);
fbody->GetSourceLineFromStartOffset_TTD(startOffset, &srcLine, &srcColumn);
sourceLocation.SetLocationFull(this->m_topLevelCallbackEventTime, ftime, ltime, fbody, srcLine, srcColumn);
return noPrevious;
}
void ExecutionInfoManager::GetLastExecutedTimeAndPositionForDebugger(TTDebuggerSourceLocation& sourceLocation) const
{
const TTLastReturnLocationInfo& cframe = this->m_lastReturnLocation;
if(!cframe.IsDefined())
{
sourceLocation.Clear();
return;
}
else
{
ULONG srcLine = 0;
LONG srcColumn = -1;
uint32 startOffset = cframe.GetLocation().Function->GetStatementStartOffset(cframe.GetLocation().CurrentStatementIndex);
cframe.GetLocation().Function->GetSourceLineFromStartOffset_TTD(startOffset, &srcLine, &srcColumn);
sourceLocation.SetLocationFull(this->m_topLevelCallbackEventTime, cframe.GetLocation().FunctionTime, cframe.GetLocation().CurrentStatementLoopTime, cframe.GetLocation().Function, srcLine, srcColumn);
}
}
void ExecutionInfoManager::GetLastExceptionTimeAndPositionForDebugger(TTDebuggerSourceLocation& sourceLocation) const
{
//If no exception then this will also clear sourceLocation
sourceLocation.SetLocationCopy(this->m_lastExceptionLocation);
}
void ExecutionInfoManager::ResetCallStackForTopLevelCall(int64 topLevelCallbackEventTime)
{
TTDAssert(this->m_callStack.Count() == 0, "We should be at the top-level entry!!!");
this->m_topLevelCallbackEventTime = topLevelCallbackEventTime;
this->m_runningFunctionTimeCtr = 0;
this->m_lastReturnLocation.Clear();
this->m_lastExceptionLocation.Clear();
}
void ExecutionInfoManager::SetBPInfoForActiveSegmentContinueScan(ThreadContextTTD* ttdThreadContext)
{
TTDAssert(this->m_pendingTTDBP.HasValue(), "We should always set this when launching a reverse continue!");
this->m_activeTTDBP.SetLocationCopy(this->m_pendingTTDBP);
this->SetActiveBPInfoAsNeeded(ttdThreadContext);
}
void ExecutionInfoManager::ClearBPInfoForActiveSegmentContinueScan(ThreadContextTTD* ttdThreadContext)
{
TTDAssert(this->m_activeTTDBP.HasValue(), "We should always have this set when we complete the active segment scan.");
this->m_hitContinueSearchBP = false;
this->ClearActiveBPInfo(ttdThreadContext);
}
void ExecutionInfoManager::SetActiveBP_Helper(ThreadContextTTD* ttdThreadContext)
{
Js::ScriptContext* ctx = ttdThreadContext->LookupContextForScriptId(this->m_activeTTDBP.GetScriptLogTagId());
Js::FunctionBody* body = this->m_activeTTDBP.LoadFunctionBodyIfPossible(ctx);
if(body == nullptr)
{
return;
}
Js::Utf8SourceInfo* utf8SourceInfo = body->GetUtf8SourceInfo();
Js::DebugDocument* debugDocument = utf8SourceInfo->GetDebugDocument();
TTDAssert(debugDocument != nullptr, "Something went wrong with our TTD debug breakpoints.");
utf8SourceInfo->EnsureLineOffsetCacheNoThrow();
charcount_t charPosition = 0;
charcount_t byteOffset = 0;
utf8SourceInfo->GetCharPositionForLineInfo(this->m_activeTTDBP.GetSourceLine(), &charPosition, &byteOffset);
long ibos = charPosition + this->m_activeTTDBP.GetSourceColumn() + 1;
Js::StatementLocation statement;
debugDocument->GetStatementLocation(ibos, &statement);
// Don't see a use case for supporting multiple breakpoints at same location.
// If a breakpoint already exists, just return that
Js::BreakpointProbe* probe = debugDocument->FindBreakpoint(statement);
if(probe == nullptr)
{
this->m_shouldRemoveWhenDone = true;
probe = debugDocument->SetBreakPoint(statement, BREAKPOINT_ENABLED);
}
this->m_activeBPId = probe->GetId();
}
void ExecutionInfoManager::SetActiveBPInfoAsNeeded(ThreadContextTTD* ttdThreadContext)
{
if(this->m_pendingTTDBP.HasValue())
{
this->m_activeTTDBP.SetLocationCopy(this->m_pendingTTDBP);
this->SetActiveBP_Helper(ttdThreadContext);
//Finally clear the pending BP info so we don't get confused later
this->m_pendingTTDMoveMode = 0;
this->m_pendingTTDBP.Clear();
}
}
void ExecutionInfoManager::ClearActiveBPInfo(ThreadContextTTD* ttdThreadContext)
{
TTDAssert(this->m_activeTTDBP.HasValue(), "Need to check that we actually have info to clear first.");
Js::ScriptContext* ctx = ttdThreadContext->LookupContextForScriptId(this->m_activeTTDBP.GetScriptLogTagId());
Js::FunctionBody* body = this->m_activeTTDBP.LoadFunctionBodyIfPossible(ctx);
Js::DebugDocument* debugDocument = body->GetUtf8SourceInfo()->GetDebugDocument();
Js::StatementLocation statement;
if(this->m_shouldRemoveWhenDone && debugDocument->FindBPStatementLocation((UINT)this->m_activeBPId, &statement))
{
debugDocument->SetBreakPoint(statement, BREAKPOINT_DELETED);
}
this->m_activeBPId = -1;
this->m_shouldRemoveWhenDone = false;
this->m_activeTTDBP.Clear();
}
void ExecutionInfoManager::ProcessScriptLoad(Js::ScriptContext* ctx, uint32 topLevelBodyId, Js::FunctionBody* body, Js::Utf8SourceInfo* utf8SourceInfo, CompileScriptException* se)
{
bool newScript = !this->m_debuggerNotifiedTopLevelBodies.Contains(topLevelBodyId);
//Only notify of loaded script -- eval and new script is only notified later if we need to hit a bp in it?
if(se != nullptr)
{
ctx->TTDHostCallbackFunctor.pfOnScriptLoadCallback(ctx->TTDHostCallbackFunctor.HostContext, body, utf8SourceInfo, se, newScript);
}
if(newScript)
{
this->m_debuggerNotifiedTopLevelBodies.AddNew(topLevelBodyId);
}
this->AddPreservedBPsForTopLevelLoad(topLevelBodyId, body);
}
void ExecutionInfoManager::ProcessScriptLoad_InflateReuseBody(uint32 topLevelBodyId, Js::FunctionBody* body)
{
this->AddPreservedBPsForTopLevelLoad(topLevelBodyId, body);
}
}
#endif