| //------------------------------------------------------------------------------------------------------- |
| // Copyright (C) Microsoft. All rights reserved. |
| // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. |
| //------------------------------------------------------------------------------------------------------- |
| #pragma once |
| |
| #if ENABLE_TTD |
| |
| namespace TTD |
| { |
| //An exception class for controlled aborts from the runtime to the toplevel TTD control loop |
| class TTDebuggerAbortException |
| { |
| private: |
| //An integer code to describe the reason for the abort -- 0 invalid, 1 end of log, 2 request etime move, 3 uncaught exception (propagate to top-level) |
| const uint32 m_abortCode; |
| |
| //An optional target event time -- intent is interpreted based on the abort code |
| const int64 m_optEventTime; |
| |
| //An optional move mode value -- should be built by host we just propagate it |
| const int64 m_optMoveMode; |
| |
| //An optional -- and static string message to include |
| const char16* m_staticAbortMessage; |
| |
| TTDebuggerAbortException(uint32 abortCode, int64 optEventTime, int64 optMoveMode, const char16* staticAbortMessage); |
| |
| public: |
| ~TTDebuggerAbortException(); |
| |
| static TTDebuggerAbortException CreateAbortEndOfLog(const char16* staticMessage); |
| static TTDebuggerAbortException CreateTopLevelAbortRequest(int64 targetEventTime, int64 moveMode, const char16* staticMessage); |
| static TTDebuggerAbortException CreateUncaughtExceptionAbortRequest(int64 targetEventTime, const char16* staticMessage); |
| |
| bool IsEndOfLog() const; |
| bool IsEventTimeMove() const; |
| bool IsTopLevelException() const; |
| |
| int64 GetTargetEventTime() const; |
| int64 GetMoveMode() const; |
| |
| const char16* GetStaticAbortMessage() const; |
| }; |
| |
| //A struct for tracking time events in a single method |
| struct SingleCallCounter |
| { |
| Js::FunctionBody* Function; |
| |
| #if ENABLE_TTD_INTERNAL_DIAGNOSTICS |
| const char16* Name; //only added for debugging can get rid of later. |
| #endif |
| |
| uint64 FunctionTime; //The function time when the function was called |
| uint64 LoopTime; //The current loop taken time for the function |
| |
| int32 LastStatementIndex; //The previously executed statement |
| uint64 LastStatementLoopTime; //The previously executed statement |
| |
| int32 CurrentStatementIndex; //The currently executing statement |
| uint64 CurrentStatementLoopTime; //The currently executing statement |
| |
| //bytecode range of the current stmt |
| uint32 CurrentStatementBytecodeMin; |
| uint32 CurrentStatementBytecodeMax; |
| }; |
| |
| //A class to represent a source location |
| class TTDebuggerSourceLocation |
| { |
| private: |
| //The script context logid the breakpoint goes with |
| TTD_LOG_PTR_ID m_sourceScriptLogId; |
| |
| //The id of the breakpoint (or -1 if no id is associated) |
| int64 m_bpId; |
| |
| //The time aware parts of this location |
| int64 m_etime; //-1 indicates an INVALID location |
| int64 m_ftime; //-1 indicates any ftime is OK |
| int64 m_ltime; //-1 indicates any ltime is OK |
| |
| //The top-level body this source location is contained in |
| uint32 m_topLevelBodyId; |
| |
| //The position of the function in the document |
| uint32 m_functionLine; |
| uint32 m_functionColumn; |
| |
| //The location in the fnuction |
| uint32 m_line; |
| uint32 m_column; |
| |
| //Update the specific body of this location from the root body and line number info |
| Js::FunctionBody* UpdatePostInflateFunctionBody_Helper(Js::FunctionBody* rootBody) const; |
| |
| public: |
| TTDebuggerSourceLocation(); |
| TTDebuggerSourceLocation(const TTDebuggerSourceLocation& other); |
| ~TTDebuggerSourceLocation(); |
| |
| TTDebuggerSourceLocation& operator= (const TTDebuggerSourceLocation& other); |
| |
| #if ENABLE_TTD_INTERNAL_DIAGNOSTICS |
| void PrintToConsole() const; |
| #endif |
| |
| void Initialize(); |
| |
| bool HasValue() const; |
| bool HasTTDTimeValue() const; |
| void Clear(); |
| |
| void SetLocationCopy(const TTDebuggerSourceLocation& other); |
| void SetLocationFromFrame(int64 topLevelETime, const SingleCallCounter& callFrame); |
| void SetLocationFromFunctionEntryAnyTime(int64 topLevelETime, Js::FunctionBody* body); |
| void SetLocationFull(int64 etime, int64 ftime, int64 ltime, Js::FunctionBody* body, ULONG line, LONG column); |
| void SetLocationWithBP(int64 bpId, Js::FunctionBody* body, ULONG line, LONG column); |
| |
| int64 GetRootEventTime() const; |
| int64 GetFunctionTime() const; |
| int64 GetLoopTime() const; |
| |
| TTD_LOG_PTR_ID GetScriptLogTagId() const; |
| Js::FunctionBody* LoadFunctionBodyIfPossible(Js::ScriptContext* ctx) const; |
| |
| int64 GetBPId() const; |
| uint32 GetTopLevelBodyId() const; |
| |
| uint32 GetSourceLine() const; |
| uint32 GetSourceColumn() const; |
| |
| //return true if the two locations refer to the same time/place |
| static bool AreSameLocations(const TTDebuggerSourceLocation& sl1, const TTDebuggerSourceLocation& sl2); |
| static bool AreSameLocations_PlaceOnly(const TTDebuggerSourceLocation& sl1, const TTDebuggerSourceLocation& sl2); |
| }; |
| |
| //A by value class representing the state of the last returned from location in execution (return x or exception) |
| class TTLastReturnLocationInfo |
| { |
| private: |
| bool m_isExceptionFrame; |
| SingleCallCounter m_lastFrame; |
| |
| public: |
| TTLastReturnLocationInfo(); |
| |
| void SetReturnLocation(const SingleCallCounter& cframe); |
| void SetExceptionLocation(const SingleCallCounter& cframe); |
| |
| bool IsDefined() const; |
| bool IsReturnLocation() const; |
| bool IsExceptionLocation() const; |
| const SingleCallCounter& GetLocation() const; |
| |
| void Clear(); |
| void ClearReturnOnly(); |
| void ClearExceptionOnly(); |
| }; |
| |
| ////////////////// |
| |
| //This class manages exeuction info for our replay execution including |
| //call-stack, current statements, breakpoints, loaded code, etc. |
| class ExecutionInfoManager |
| { |
| private: |
| //Stash the current top-level event time here |
| int64 m_topLevelCallbackEventTime; |
| |
| //A counter (per event dispatch) which holds the current value for the function counter |
| uint64 m_runningFunctionTimeCtr; |
| |
| //Array of call counters (used as stack) |
| JsUtil::List<SingleCallCounter, HeapAllocator> m_callStack; |
| |
| //A set containing every top level body that we have notified the debugger of (so we don't tell them multiple times) |
| JsUtil::BaseHashSet<uint32, HeapAllocator> m_debuggerNotifiedTopLevelBodies; |
| |
| //The most recently executed statement before return -- normal return or exception |
| //We clear this after executing any following statements so this can be used for: |
| // - Step back to uncaught exception |
| // - Step to last statement in previous event |
| // - Step back *into* possible if either case is true |
| TTLastReturnLocationInfo m_lastReturnLocation; |
| |
| //Always keep the last exception location as well -- even if it is caught |
| bool m_lastExceptionPropagating; |
| TTDebuggerSourceLocation m_lastExceptionLocation; |
| |
| //A flag indicating if we want to break on the entry to the user code |
| bool m_breakOnFirstUserCode; |
| |
| //A pending TTDBP we want to set and move to |
| TTDebuggerSourceLocation m_pendingTTDBP; |
| int64 m_pendingTTDMoveMode; |
| |
| //The bp we are actively moving to in TT mode |
| int64 m_activeBPId; |
| bool m_shouldRemoveWhenDone; |
| TTDebuggerSourceLocation m_activeTTDBP; |
| |
| //The last breakpoint seen in the most recent scan |
| bool m_hitContinueSearchBP; |
| TTDebuggerSourceLocation m_continueBreakPoint; |
| |
| //Used to preserve breakpoints accross inflate operations |
| JsUtil::List<TTDebuggerSourceLocation*, HeapAllocator> m_unRestoredBreakpoints; |
| |
| #if ENABLE_BASIC_TRACE || ENABLE_FULL_BC_TRACE |
| TraceLogger m_diagnosticLogger; |
| #endif |
| |
| //A special check to see if we are in the process of a time-travel move and do not want to stop at any breakpoints |
| static bool ShouldSuppressBreakpointsForTimeTravelMove(TTDMode mode); |
| |
| //A special check to see if we are in the process of a time-travel move and do not want to stop at any breakpoints |
| static bool ShouldRecordBreakpointsDuringTimeTravelScan(TTDMode mode); |
| |
| public: |
| ExecutionInfoManager(); |
| ~ExecutionInfoManager(); |
| |
| #if ENABLE_BASIC_TRACE || ENABLE_FULL_BC_TRACE |
| //Get the trace logger for this |
| TraceLogger* GetTraceLogger(); |
| #endif |
| |
| //get the top call counter from the stack |
| const SingleCallCounter& GetTopCallCounter() const; |
| SingleCallCounter& GetTopCallCounter(); |
| |
| //get the caller for the top call counter that is user code from the stack (e.g. stack -2) |
| bool TryGetTopCallCallerCounter(SingleCallCounter& caller) const; |
| |
| //Log a function call |
| void PushCallEvent(Js::JavascriptFunction* function, uint32 argc, Js::Var* argv, bool isInFinally); |
| |
| //Log a function return in normal case and exception |
| void PopCallEvent(Js::JavascriptFunction* function, Js::Var result); |
| void PopCallEventException(Js::JavascriptFunction* function); |
| |
| void ProcessCatchInfoForLastExecutedStatements(); |
| |
| //Set that we want to break on the execution of the first user code |
| void SetBreakOnFirstUserCode(); |
| |
| //Set the requested breakpoint and move mode in debugger stepping |
| void SetPendingTTDStepBackMove(); |
| void SetPendingTTDStepBackIntoMove(); |
| void SetPendingTTDReverseContinueMove(uint64 moveflag); |
| void SetPendingTTDUnhandledException(); |
| |
| //Process the breakpoint info as we enter a break statement and return true if we actually want to break |
| bool ProcessBPInfoPreBreak(Js::FunctionBody* fb, const EventLog* elog); |
| |
| //Process the breakpoint info as we resume from a break statement |
| void ProcessBPInfoPostBreak(Js::FunctionBody* fb); |
| |
| //When scanning we don't want to add the active BP as a seen breakpoint *unless* is it is also explicitly added as a breakpoint by the user |
| bool IsLocationActiveBPAndNotExplicitBP(const TTDebuggerSourceLocation& current) const; |
| |
| //When scanning add the current location as a BP location |
| void AddCurrentLocationDuringScan(int64 topLevelEventTime); |
| |
| //After a scan set the pending BP to the earliest breakpoint before the given current pending BP location and return true |
| //If no such BP location then return false |
| bool TryFindAndSetPreviousBP(); |
| |
| //Get the target event time for the pending TTD breakpoint |
| int64 GetPendingTTDBPTargetEventTime() const; |
| |
| //Load and restore all the breakpoints in the manager before and after we create new script contexts |
| void LoadPreservedBPInfo(ThreadContext* threadContext); |
| |
| //When we load in a top level function we need to add any breakpoints associated with it |
| void AddPreservedBPsForTopLevelLoad(uint32 topLevelBodyId, Js::FunctionBody* body); |
| |
| //Update the loop count information |
| void UpdateLoopCountInfo(); |
| |
| // |
| //TODO: This is not great performance wise |
| // |
| //For debugging we currently brute force track the current/last source statements executed |
| void UpdateCurrentStatementInfo(uint bytecodeOffset); |
| |
| //Get the current time/position info for the debugger -- all out arguments are optional (nullptr if you don't care) |
| void GetTimeAndPositionForDebugger(TTDebuggerSourceLocation& sourceLocation) const; |
| |
| #if ENABLE_OBJECT_SOURCE_TRACKING |
| void GetTimeAndPositionForDiagnosticObjectTracking(DiagnosticOrigin& originInfo) const; |
| #endif |
| |
| //Get the previous statement time/position for the debugger -- return false if this is the first statement of the event handler |
| bool GetPreviousTimeAndPositionForDebugger(TTDebuggerSourceLocation& sourceLocation) const; |
| |
| //Get the last (uncaught or just caught) exception time/position for the debugger -- if the last return action was an exception and we have not made any additional calls |
| //Otherwise get the last statement executed call time/position for the debugger |
| void GetLastExecutedTimeAndPositionForDebugger(TTDebuggerSourceLocation& sourceLocation) const; |
| void GetLastExceptionTimeAndPositionForDebugger(TTDebuggerSourceLocation& sourceLocation) const; |
| |
| //Ensure the call stack is clear and counters are zeroed appropriately |
| void ResetCallStackForTopLevelCall(int64 topLevelCallbackEventTime); |
| |
| //Setup and tear down info on the bp location in the active interval the reverse continue was invoked from |
| //We don't want to find a BP after the current statement so we need to track this |
| void SetBPInfoForActiveSegmentContinueScan(ThreadContextTTD* ttdThreadContext); |
| void ClearBPInfoForActiveSegmentContinueScan(ThreadContextTTD* ttdThreadContext); |
| |
| //Transfer the pending bp info to the active BP info (and set the BP) as needed to make sure BP is hit in reverse step operations |
| void SetActiveBP_Helper(ThreadContextTTD* ttdThreadContext); |
| void SetActiveBPInfoAsNeeded(ThreadContextTTD* ttdThreadContext); |
| void ClearActiveBPInfo(ThreadContextTTD* ttdThreadContext); |
| |
| //After loading script process it to (1) notify the debugger if needed and (2) set any breakpoints we need |
| void ProcessScriptLoad(Js::ScriptContext* ctx, uint32 topLevelBodyId, Js::FunctionBody* body, Js::Utf8SourceInfo* utf8SourceInfo, CompileScriptException* se); |
| void ProcessScriptLoad_InflateReuseBody(uint32 topLevelBodyId, Js::FunctionBody* body); |
| }; |
| |
| //A class to ensure that even when exceptions are thrown the pop action for the TTD call stack is executed -- defined after EventLog so we can refer to it in the .h file |
| class TTDExceptionFramePopper |
| { |
| private: |
| ExecutionInfoManager* m_eimanager; |
| Js::JavascriptFunction* m_function; |
| |
| public: |
| TTDExceptionFramePopper() |
| : m_eimanager(nullptr), m_function(nullptr) |
| { |
| ; |
| } |
| |
| ~TTDExceptionFramePopper() |
| { |
| //we didn't clear this so an exception was thrown and we are propagating |
| if(this->m_eimanager != nullptr) |
| { |
| //if it doesn't have an exception frame then this is the frame where the exception was thrown so record our info |
| this->m_eimanager->PopCallEventException(this->m_function); |
| } |
| } |
| |
| void PushInfo(ExecutionInfoManager* eimanager, Js::JavascriptFunction* function) |
| { |
| this->m_eimanager = eimanager; //set the log info so if the pop isn't called the destructor will record propagation |
| this->m_function = function; |
| } |
| |
| void PopInfo() |
| { |
| this->m_eimanager = nullptr; //normal pop (no exception) just clear so destructor nops |
| } |
| }; |
| } |
| |
| #endif |