| // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| #ifndef RUNTIME_VM_DEBUGGER_H_ |
| #define RUNTIME_VM_DEBUGGER_H_ |
| |
| #include <memory> |
| |
| #include "include/dart_tools_api.h" |
| |
| #include "vm/kernel_isolate.h" |
| #include "vm/object.h" |
| #include "vm/port.h" |
| #include "vm/scopes.h" |
| #include "vm/service_event.h" |
| #include "vm/simulator.h" |
| #include "vm/stack_frame.h" |
| #include "vm/stack_trace.h" |
| |
| #if !defined(PRODUCT) |
| |
| DECLARE_FLAG(bool, verbose_debug); |
| |
| // 'Trace Debugger' TD_Print. |
| #if defined(_MSC_VER) |
| #define TD_Print(format, ...) \ |
| if (FLAG_verbose_debug) Log::Current()->Print(format, __VA_ARGS__) |
| #else |
| #define TD_Print(format, ...) \ |
| if (FLAG_verbose_debug) Log::Current()->Print(format, ##__VA_ARGS__) |
| #endif |
| |
| namespace dart { |
| |
| class CodeBreakpoint; |
| class Isolate; |
| class JSONArray; |
| class JSONStream; |
| class ObjectPointerVisitor; |
| class BreakpointLocation; |
| class StackFrame; |
| |
| // A user-defined breakpoint, which either fires once, for a particular closure, |
| // or always. The API's notion of a breakpoint corresponds to this object. |
| class Breakpoint { |
| public: |
| Breakpoint(intptr_t id, BreakpointLocation* bpt_location) |
| : id_(id), |
| kind_(Breakpoint::kNone), |
| next_(NULL), |
| closure_(Instance::null()), |
| bpt_location_(bpt_location), |
| is_synthetic_async_(false) {} |
| |
| intptr_t id() const { return id_; } |
| Breakpoint* next() const { return next_; } |
| void set_next(Breakpoint* n) { next_ = n; } |
| |
| BreakpointLocation* bpt_location() const { return bpt_location_; } |
| void set_bpt_location(BreakpointLocation* new_bpt_location); |
| |
| bool IsRepeated() const { return kind_ == kRepeated; } |
| bool IsSingleShot() const { return kind_ == kSingleShot; } |
| bool IsPerClosure() const { return kind_ == kPerClosure; } |
| InstancePtr closure() const { return closure_; } |
| |
| void SetIsRepeated() { |
| ASSERT(kind_ == kNone); |
| kind_ = kRepeated; |
| } |
| |
| void SetIsSingleShot() { |
| ASSERT(kind_ == kNone); |
| kind_ = kSingleShot; |
| } |
| |
| void SetIsPerClosure(const Instance& closure) { |
| ASSERT(kind_ == kNone); |
| kind_ = kPerClosure; |
| closure_ = closure.ptr(); |
| } |
| |
| void Enable() { |
| ASSERT(!enabled_); |
| enabled_ = true; |
| } |
| |
| void Disable() { |
| ASSERT(enabled_); |
| enabled_ = false; |
| } |
| |
| bool is_enabled() const { return enabled_; } |
| |
| // Mark that this breakpoint is a result of a step OverAwait request. |
| void set_is_synthetic_async(bool is_synthetic_async) { |
| is_synthetic_async_ = is_synthetic_async; |
| } |
| bool is_synthetic_async() const { return is_synthetic_async_; } |
| |
| void PrintJSON(JSONStream* stream); |
| |
| private: |
| void VisitObjectPointers(ObjectPointerVisitor* visitor); |
| |
| enum ConditionKind { |
| kNone, |
| kRepeated, |
| kSingleShot, |
| kPerClosure, |
| }; |
| |
| intptr_t id_; |
| ConditionKind kind_; |
| Breakpoint* next_; |
| InstancePtr closure_; |
| BreakpointLocation* bpt_location_; |
| bool is_synthetic_async_; |
| bool enabled_ = false; |
| |
| friend class BreakpointLocation; |
| DISALLOW_COPY_AND_ASSIGN(Breakpoint); |
| }; |
| |
| // BreakpointLocation represents a collection of breakpoint conditions at the |
| // same token position in Dart source. There may be more than one CodeBreakpoint |
| // object per BreakpointLocation. |
| // An unresolved breakpoint is one where the underlying code has not |
| // been compiled yet. Since the code has not been compiled, we don't know |
| // the definitive source location yet. The requested source location may |
| // change when the underlying code gets compiled. |
| // A latent breakpoint represents a breakpoint location in Dart source |
| // that is not loaded in the VM when the breakpoint is requested. |
| // When a script with matching url is loaded, a latent breakpoint |
| // becomes an unresolved breakpoint. |
| class BreakpointLocation { |
| public: |
| // Create a new unresolved breakpoint. |
| BreakpointLocation(Debugger* debugger, |
| const GrowableHandlePtrArray<const Script>& scripts, |
| TokenPosition token_pos, |
| TokenPosition end_token_pos, |
| intptr_t requested_line_number, |
| intptr_t requested_column_number); |
| // Create a new latent breakpoint. |
| BreakpointLocation(Debugger* debugger, |
| const String& url, |
| intptr_t requested_line_number, |
| intptr_t requested_column_number); |
| |
| ~BreakpointLocation(); |
| |
| TokenPosition token_pos() const { return token_pos_.load(); } |
| intptr_t line_number(); |
| TokenPosition end_token_pos() const { return end_token_pos_.load(); } |
| |
| ScriptPtr script() const { |
| if (scripts_.length() == 0) { |
| return Script::null(); |
| } |
| return scripts_.At(0); |
| } |
| StringPtr url() const { return url_; } |
| |
| intptr_t requested_line_number() const { return requested_line_number_; } |
| intptr_t requested_column_number() const { return requested_column_number_; } |
| |
| void GetCodeLocation(Script* script, TokenPosition* token_pos) const; |
| |
| Breakpoint* AddRepeated(Debugger* dbg); |
| Breakpoint* AddSingleShot(Debugger* dbg); |
| Breakpoint* AddPerClosure(Debugger* dbg, |
| const Instance& closure, |
| bool for_over_await); |
| |
| bool AnyEnabled() const; |
| bool IsResolved() const { return code_token_pos_.IsReal(); } |
| bool IsLatent() const { return !token_pos().IsReal(); } |
| |
| bool EnsureIsResolved(const Function& target_function, |
| TokenPosition exact_token_pos); |
| |
| Debugger* debugger() { return debugger_; } |
| |
| private: |
| void VisitObjectPointers(ObjectPointerVisitor* visitor); |
| |
| void SetResolved(const Function& func, TokenPosition token_pos); |
| |
| BreakpointLocation* next() const { return this->next_; } |
| void set_next(BreakpointLocation* value) { next_ = value; } |
| |
| void AddBreakpoint(Breakpoint* bpt, Debugger* dbg); |
| |
| Breakpoint* breakpoints() const { return this->conditions_; } |
| void set_breakpoints(Breakpoint* head) { this->conditions_ = head; } |
| |
| // Finds the breakpoint we hit at |location|. |
| Breakpoint* FindHitBreakpoint(ActivationFrame* top_frame); |
| |
| SafepointRwLock* line_number_lock() { return line_number_lock_.get(); } |
| |
| Debugger* debugger_; |
| MallocGrowableArray<ScriptPtr> scripts_; |
| StringPtr url_; |
| std::unique_ptr<SafepointRwLock> line_number_lock_; |
| intptr_t line_number_; // lazily computed for token_pos_ |
| std::atomic<TokenPosition> token_pos_; |
| std::atomic<TokenPosition> end_token_pos_; |
| BreakpointLocation* next_; |
| Breakpoint* conditions_; |
| intptr_t requested_line_number_; |
| intptr_t requested_column_number_; |
| |
| // Valid for resolved breakpoints: |
| TokenPosition code_token_pos_; |
| |
| friend class Debugger; |
| friend class GroupDebugger; |
| DISALLOW_COPY_AND_ASSIGN(BreakpointLocation); |
| }; |
| |
| // CodeBreakpoint represents a location in compiled code. |
| // There may be more than one CodeBreakpoint for one BreakpointLocation, |
| // e.g. when a function gets compiled as a regular function and as a closure. |
| // There may be more than one BreakpointLocation associated with CodeBreakpoint, |
| // one for for every isolate in a group that sets a breakpoint at particular |
| // code location represented by the CodeBreakpoint. |
| // Each BreakpointLocation might be enabled/disabled based on whether it has |
| // any actual breakpoints associated with it. |
| // The CodeBreakpoint is enabled if it has any such BreakpointLocations |
| // associated with it. |
| // The class is not thread-safe - users of this class need to ensure the access |
| // is synchronized, guarded by mutexes or run inside of a safepoint scope. |
| class CodeBreakpoint { |
| public: |
| // Unless CodeBreakpoint is unlinked and is no longer used there should be at |
| // least one BreakpointLocation associated with CodeBreakpoint. If there are |
| // more BreakpointLocation added assumption is is that all of them point to |
| // the same source so have the same token pos. |
| CodeBreakpoint(const Code& code, |
| BreakpointLocation* loc, |
| uword pc, |
| UntaggedPcDescriptors::Kind kind); |
| ~CodeBreakpoint(); |
| |
| // Used by GroupDebugger to find CodeBreakpoint associated with |
| // particular function. |
| FunctionPtr function() const { return Code::Handle(code_).function(); } |
| |
| uword pc() const { return pc_; } |
| bool HasBreakpointLocation(BreakpointLocation* breakpoint_location); |
| bool FindAndDeleteBreakpointLocation(BreakpointLocation* breakpoint_location); |
| bool HasNoBreakpointLocations() { |
| return breakpoint_locations_.length() == 0; |
| } |
| |
| void Enable(); |
| void Disable(); |
| bool IsEnabled() const { return enabled_count_ > 0; } |
| |
| CodePtr OrigStubAddress() const; |
| |
| const char* ToCString() const; |
| |
| private: |
| void VisitObjectPointers(ObjectPointerVisitor* visitor); |
| |
| // Finds right BreakpointLocation for a given Isolate's debugger. |
| BreakpointLocation* FindBreakpointForDebugger(Debugger* debugger); |
| // Adds new BreakpointLocation for another isolate that wants to |
| // break at the same function/code location that this CodeBreakpoint |
| // represents. |
| void AddBreakpointLocation(BreakpointLocation* breakpoint_location) { |
| ASSERT(breakpoint_locations_.length() == 0 || |
| (breakpoint_location->token_pos() == |
| breakpoint_locations_.At(0)->token_pos() && |
| breakpoint_location->url() == breakpoint_locations_.At(0)->url())); |
| breakpoint_locations_.Add(breakpoint_location); |
| } |
| |
| void set_next(CodeBreakpoint* value) { next_ = value; } |
| CodeBreakpoint* next() const { return this->next_; } |
| |
| void PatchCode(); |
| void RestoreCode(); |
| |
| CodePtr code_; |
| uword pc_; |
| int enabled_count_; // incremented for every enabled breakpoint location |
| |
| // Breakpoint locations from different debuggers/isolates that |
| // point to this code breakpoint. |
| MallocGrowableArray<BreakpointLocation*> breakpoint_locations_; |
| CodeBreakpoint* next_; |
| |
| UntaggedPcDescriptors::Kind breakpoint_kind_; |
| CodePtr saved_value_; |
| |
| friend class Debugger; |
| friend class GroupDebugger; |
| DISALLOW_COPY_AND_ASSIGN(CodeBreakpoint); |
| }; |
| |
| // ActivationFrame represents one dart function activation frame |
| // on the call stack. |
| class ActivationFrame : public ZoneAllocated { |
| public: |
| enum Kind { |
| kRegular, |
| kAsyncSuspensionMarker, |
| kAsyncCausal, |
| kAsyncActivation, |
| }; |
| |
| ActivationFrame(uword pc, |
| uword fp, |
| uword sp, |
| const Code& code, |
| const Array& deopt_frame, |
| intptr_t deopt_frame_offset, |
| Kind kind = kRegular); |
| |
| ActivationFrame(uword pc, const Code& code); |
| |
| explicit ActivationFrame(Kind kind); |
| |
| explicit ActivationFrame(const Closure& async_activation); |
| |
| uword pc() const { return pc_; } |
| uword fp() const { return fp_; } |
| uword sp() const { return sp_; } |
| |
| uword GetCallerSp() const { return fp() + (kCallerSpSlotFromFp * kWordSize); } |
| |
| const Function& function() const { |
| return function_; |
| } |
| const Code& code() const { |
| ASSERT(!code_.IsNull()); |
| return code_; |
| } |
| |
| enum Relation { |
| kCallee, |
| kSelf, |
| kCaller, |
| }; |
| |
| Relation CompareTo(uword other_fp) const; |
| |
| StringPtr QualifiedFunctionName(); |
| StringPtr SourceUrl(); |
| ScriptPtr SourceScript(); |
| LibraryPtr Library(); |
| TokenPosition TokenPos(); |
| intptr_t LineNumber(); |
| intptr_t ColumnNumber(); |
| |
| // Returns true if this frame is for a function that is visible |
| // to the user and can be debugged. |
| bool IsDebuggable() const; |
| |
| // Returns true if it is possible to rewind the debugger to this frame. |
| bool IsRewindable() const; |
| |
| // The context level of a frame is the context level at the |
| // PC/token index of the frame. It determines the depth of the context |
| // chain that belongs to the function of this activation frame. |
| intptr_t ContextLevel(); |
| |
| const char* ToCString(); |
| |
| intptr_t NumLocalVariables(); |
| |
| void VariableAt(intptr_t i, |
| String* name, |
| TokenPosition* declaration_token_pos, |
| TokenPosition* visible_start_token_pos, |
| TokenPosition* visible_end_token_pos, |
| Object* value); |
| |
| ArrayPtr GetLocalVariables(); |
| ObjectPtr GetParameter(intptr_t index); |
| ClosurePtr GetClosure(); |
| ObjectPtr GetReceiver(); |
| |
| const Context& GetSavedCurrentContext(); |
| ObjectPtr GetAsyncOperation(); |
| |
| TypeArgumentsPtr BuildParameters( |
| const GrowableObjectArray& param_names, |
| const GrowableObjectArray& param_values, |
| const GrowableObjectArray& type_params_names); |
| |
| ObjectPtr EvaluateCompiledExpression(const ExternalTypedData& kernel_data, |
| const Array& arguments, |
| const Array& type_definitions, |
| const TypeArguments& type_arguments); |
| |
| void PrintToJSONObject(JSONObject* jsobj); |
| |
| // Get Closure that await'ed this async frame. |
| ObjectPtr GetAsyncAwaiter(CallerClosureFinder* caller_closure_finder); |
| |
| bool HandlesException(const Instance& exc_obj); |
| |
| private: |
| void PrintToJSONObjectRegular(JSONObject* jsobj); |
| void PrintToJSONObjectAsyncCausal(JSONObject* jsobj); |
| void PrintToJSONObjectAsyncSuspensionMarker(JSONObject* jsobj); |
| void PrintContextMismatchError(intptr_t ctx_slot, |
| intptr_t frame_ctx_level, |
| intptr_t var_ctx_level); |
| void PrintDescriptorsError(const char* message); |
| |
| intptr_t TryIndex(); |
| intptr_t DeoptId(); |
| void GetPcDescriptors(); |
| void GetVarDescriptors(); |
| void GetDescIndices(); |
| |
| ObjectPtr GetAsyncContextVariable(const String& name); |
| |
| // Get the current continuation index in the :await_jump_var pulled from the |
| // context. |
| intptr_t GetAwaitJumpVariable(); |
| |
| void ExtractTokenPositionFromAsyncClosure(); |
| |
| bool IsAsyncMachinery() const; |
| |
| static const char* KindToCString(Kind kind) { |
| switch (kind) { |
| case kRegular: |
| return "Regular"; |
| case kAsyncCausal: |
| return "AsyncCausal"; |
| case kAsyncSuspensionMarker: |
| return "AsyncSuspensionMarker"; |
| case kAsyncActivation: |
| return "AsyncActivation"; |
| default: |
| UNREACHABLE(); |
| return ""; |
| } |
| } |
| |
| ObjectPtr GetStackVar(VariableIndex var_index); |
| ObjectPtr GetRelativeContextVar(intptr_t ctxt_level, |
| intptr_t slot_index, |
| intptr_t frame_ctx_level); |
| ObjectPtr GetContextVar(intptr_t ctxt_level, intptr_t slot_index); |
| |
| uword pc_; |
| uword fp_; |
| uword sp_; |
| |
| // The anchor of the context chain for this function. |
| Context& ctx_; |
| Code& code_; |
| Function& function_; |
| bool live_frame_; // Is this frame a live frame? |
| bool token_pos_initialized_; |
| TokenPosition token_pos_; |
| intptr_t try_index_; |
| intptr_t deopt_id_; |
| |
| intptr_t line_number_; |
| intptr_t column_number_; |
| intptr_t context_level_; |
| |
| // Some frames are deoptimized into a side array in order to inspect them. |
| const Array& deopt_frame_; |
| const intptr_t deopt_frame_offset_; |
| |
| Kind kind_; |
| |
| bool vars_initialized_; |
| LocalVarDescriptors& var_descriptors_; |
| ZoneGrowableArray<intptr_t> desc_indices_; |
| PcDescriptors& pc_desc_; |
| |
| friend class Debugger; |
| friend class DebuggerStackTrace; |
| DISALLOW_COPY_AND_ASSIGN(ActivationFrame); |
| }; |
| |
| // Array of function activations on the call stack. |
| class DebuggerStackTrace : public ZoneAllocated { |
| public: |
| explicit DebuggerStackTrace(int capacity) : trace_(capacity) {} |
| |
| intptr_t Length() const { return trace_.length(); } |
| |
| ActivationFrame* FrameAt(int i) const { return trace_[i]; } |
| |
| ActivationFrame* GetHandlerFrame(const Instance& exc_obj) const; |
| |
| static DebuggerStackTrace* CollectAwaiterReturn(); |
| static DebuggerStackTrace* Collect(); |
| // Returns a debugger stack trace corresponding to a dart.core.StackTrace. |
| // Frames corresponding to invisible functions are omitted. It is not valid |
| // to query local variables in the returned stack. |
| static DebuggerStackTrace* From(const class StackTrace& ex_trace); |
| |
| private: |
| void AddActivation(ActivationFrame* frame); |
| void AddMarker(ActivationFrame::Kind marker); |
| void AddAsyncCausalFrame(uword pc, const Code& code); |
| |
| void AppendCodeFrames(Thread* thread, |
| Isolate* isolate, |
| Zone* zone, |
| StackFrame* frame, |
| Code* code, |
| Code* inlined_code, |
| Array* deopt_frame); |
| |
| static DebuggerStackTrace* CollectAsyncCausal(); |
| static DebuggerStackTrace* CollectAsyncLazy(); |
| |
| ZoneGrowableArray<ActivationFrame*> trace_; |
| |
| friend class Debugger; |
| |
| DISALLOW_COPY_AND_ASSIGN(DebuggerStackTrace); |
| }; |
| |
| // On which exceptions to pause. |
| typedef enum { |
| kNoPauseOnExceptions = 1, |
| kPauseOnUnhandledExceptions, |
| kPauseOnAllExceptions, |
| kInvalidExceptionPauseInfo |
| } Dart_ExceptionPauseInfo; |
| |
| class DebuggerKeyValueTrait : public AllStatic { |
| public: |
| typedef const Debugger* Key; |
| typedef bool Value; |
| |
| struct Pair { |
| Key key; |
| Value value; |
| Pair() : key(NULL), value(false) {} |
| Pair(const Key key, const Value& value) : key(key), value(value) {} |
| Pair(const Pair& other) : key(other.key), value(other.value) {} |
| Pair& operator=(const Pair&) = default; |
| }; |
| |
| static Key KeyOf(Pair kv) { return kv.key; } |
| static Value ValueOf(Pair kv) { return kv.value; } |
| static uword Hash(Key key) { |
| return Utils::WordHash(reinterpret_cast<intptr_t>(key)); |
| } |
| static bool IsKeyEqual(Pair kv, Key key) { return kv.key == key; } |
| }; |
| |
| class DebuggerSet : public MallocDirectChainedHashMap<DebuggerKeyValueTrait> { |
| public: |
| typedef DebuggerKeyValueTrait::Key Key; |
| typedef DebuggerKeyValueTrait::Value Value; |
| typedef DebuggerKeyValueTrait::Pair Pair; |
| |
| virtual ~DebuggerSet() { Clear(); } |
| |
| void Insert(const Key& key) { |
| Pair pair(key, /*value=*/true); |
| MallocDirectChainedHashMap<DebuggerKeyValueTrait>::Insert(pair); |
| } |
| |
| void Remove(const Key& key) { |
| MallocDirectChainedHashMap<DebuggerKeyValueTrait>::Remove(key); |
| } |
| }; |
| |
| class BoolCallable : public ValueObject { |
| public: |
| BoolCallable() {} |
| virtual ~BoolCallable() {} |
| |
| virtual bool Call() = 0; |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(BoolCallable); |
| }; |
| |
| template <typename T> |
| class LambdaBoolCallable : public BoolCallable { |
| public: |
| explicit LambdaBoolCallable(T& lambda) : lambda_(lambda) {} |
| bool Call() { return lambda_(); } |
| |
| private: |
| T& lambda_; |
| DISALLOW_COPY_AND_ASSIGN(LambdaBoolCallable); |
| }; |
| |
| class GroupDebugger { |
| public: |
| explicit GroupDebugger(IsolateGroup* isolate_group); |
| ~GroupDebugger(); |
| |
| void MakeCodeBreakpointAt(const Function& func, BreakpointLocation* bpt); |
| |
| // Returns [nullptr] if no breakpoint exists for the given address. |
| CodeBreakpoint* GetCodeBreakpoint(uword breakpoint_address); |
| BreakpointLocation* GetBreakpointLocationFor(Debugger* debugger, |
| uword breakpoint_address, |
| CodeBreakpoint** pcbpt); |
| CodePtr GetPatchedStubAddress(uword breakpoint_address); |
| |
| void RegisterBreakpointLocation(BreakpointLocation* location); |
| void UnregisterBreakpointLocation(BreakpointLocation* location); |
| |
| void RemoveBreakpointLocation(BreakpointLocation* bpt_location); |
| void UnlinkCodeBreakpoints(BreakpointLocation* bpt_location); |
| |
| // Returns true if the call at address pc is patched to point to |
| // a debugger stub. |
| bool HasActiveBreakpoint(uword pc); |
| bool HasCodeBreakpointInFunction(const Function& func); |
| bool HasCodeBreakpointInCode(const Code& code); |
| |
| bool HasBreakpointInFunction(const Function& func); |
| bool HasBreakpointInCode(const Code& code); |
| |
| void SyncBreakpointLocation(BreakpointLocation* loc); |
| |
| void Pause(); |
| |
| bool EnsureLocationIsInFunction(Zone* zone, |
| const Function& function, |
| BreakpointLocation* location); |
| void NotifyCompilation(const Function& func); |
| |
| void VisitObjectPointers(ObjectPointerVisitor* visitor); |
| |
| SafepointRwLock* code_breakpoints_lock() { |
| return code_breakpoints_lock_.get(); |
| } |
| |
| SafepointRwLock* breakpoint_locations_lock() { |
| return breakpoint_locations_lock_.get(); |
| } |
| |
| SafepointRwLock* single_stepping_set_lock() { |
| return single_stepping_set_lock_.get(); |
| } |
| void RegisterSingleSteppingDebugger(Thread* thread, const Debugger* debugger); |
| void UnregisterSingleSteppingDebugger(Thread* thread, |
| const Debugger* debugger); |
| |
| bool RunUnderReadLockIfNeededCallable(Thread* thread, |
| SafepointRwLock* rw_lock, |
| BoolCallable* callable); |
| |
| template <typename T> |
| bool RunUnderReadLockIfNeeded(Thread* thread, |
| SafepointRwLock* rw_lock, |
| T function) { |
| LambdaBoolCallable<T> callable(function); |
| return RunUnderReadLockIfNeededCallable(thread, rw_lock, &callable); |
| } |
| |
| // Returns [true] if there is at least one breakpoint set in function or code. |
| // Checks for both user-defined and internal temporary breakpoints. |
| bool HasBreakpoint(Thread* thread, const Function& function); |
| bool IsDebugging(Thread* thread, const Function& function); |
| |
| private: |
| IsolateGroup* isolate_group_; |
| |
| std::unique_ptr<SafepointRwLock> code_breakpoints_lock_; |
| CodeBreakpoint* code_breakpoints_; |
| |
| // Secondary list of all breakpoint_locations_(primary is in Debugger class). |
| // This list is kept in sync with all the lists in Isolate Debuggers and is |
| // used to quickly scan BreakpointLocations when new Function is compiled. |
| std::unique_ptr<SafepointRwLock> breakpoint_locations_lock_; |
| MallocGrowableArray<BreakpointLocation*> breakpoint_locations_; |
| |
| std::unique_ptr<SafepointRwLock> single_stepping_set_lock_; |
| DebuggerSet single_stepping_set_; |
| |
| void RemoveUnlinkedCodeBreakpoints(); |
| void RegisterCodeBreakpoint(CodeBreakpoint* bpt); |
| |
| bool needs_breakpoint_cleanup_; |
| }; |
| |
| class Debugger { |
| public: |
| enum ResumeAction { |
| kContinue, |
| kStepInto, |
| kStepOver, |
| kStepOut, |
| kStepRewind, |
| kStepOverAsyncSuspension, |
| }; |
| |
| explicit Debugger(Isolate* isolate); |
| ~Debugger(); |
| |
| void NotifyIsolateCreated(); |
| void Shutdown(); |
| |
| void NotifyDoneLoading(); |
| |
| // Set breakpoint at closest location to function entry. |
| Breakpoint* SetBreakpointAtEntry(const Function& target_function, |
| bool single_shot); |
| Breakpoint* SetBreakpointAtActivation(const Instance& closure, |
| bool for_over_await); |
| Breakpoint* BreakpointAtActivation(const Instance& closure); |
| |
| // TODO(turnidge): script_url may no longer be specific enough. |
| Breakpoint* SetBreakpointAtLine(const String& script_url, |
| intptr_t line_number); |
| Breakpoint* SetBreakpointAtLineCol(const String& script_url, |
| intptr_t line_number, |
| intptr_t column_number); |
| |
| // Sets synthetic breakpoint at async_op to step over the synthetic part of |
| // the stack trace. |
| Breakpoint* SetBreakpointAtAsyncOp(const Function& async_op); |
| |
| BreakpointLocation* BreakpointLocationAtLineCol(const String& script_url, |
| intptr_t line_number, |
| intptr_t column_number); |
| |
| // Returns true if the breakpoint's state changed. |
| bool SetBreakpointState(Breakpoint* bpt, bool enable); |
| |
| void RemoveBreakpoint(intptr_t bp_id); |
| Breakpoint* GetBreakpointById(intptr_t id); |
| |
| void MaybeAsyncStepInto(const Closure& async_op); |
| void AsyncStepInto(const Closure& async_op); |
| |
| void Continue(); |
| |
| bool SetResumeAction(ResumeAction action, |
| intptr_t frame_index = 1, |
| const char** error = NULL); |
| |
| bool IsStepping() const { return resume_action_ != kContinue; } |
| |
| bool IsSingleStepping() const { return resume_action_ == kStepInto; } |
| |
| bool IsPaused() const { return pause_event_ != NULL; } |
| |
| bool ignore_breakpoints() const { return ignore_breakpoints_; } |
| void set_ignore_breakpoints(bool ignore_breakpoints) { |
| ignore_breakpoints_ = ignore_breakpoints; |
| } |
| |
| // Put the isolate into single stepping mode when Dart code next runs. |
| // |
| // This is used by the vm service to allow the user to step while |
| // paused at isolate start. |
| void EnterSingleStepMode(); |
| |
| // Indicates why the debugger is currently paused. If the debugger |
| // is not paused, this returns NULL. Note that the debugger can be |
| // paused for breakpoints, isolate interruption, and (sometimes) |
| // exceptions. |
| const ServiceEvent* PauseEvent() const { return pause_event_; } |
| |
| void SetExceptionPauseInfo(Dart_ExceptionPauseInfo pause_info); |
| Dart_ExceptionPauseInfo GetExceptionPauseInfo() const; |
| |
| void VisitObjectPointers(ObjectPointerVisitor* visitor); |
| |
| // Returns a stack trace with frames corresponding to invisible functions |
| // omitted. CurrentStackTrace always returns a new trace on the current stack. |
| // The trace returned by StackTrace may have been cached; it is suitable for |
| // use when stepping, but otherwise may be out of sync with the current stack. |
| DebuggerStackTrace* StackTrace(); |
| |
| DebuggerStackTrace* AsyncCausalStackTrace(); |
| |
| DebuggerStackTrace* AwaiterStackTrace(); |
| |
| // Pause execution for a breakpoint. Called from generated code. |
| ErrorPtr PauseBreakpoint(); |
| |
| // Pause execution due to stepping. Called from generated code. |
| ErrorPtr PauseStepping(); |
| |
| // Pause execution due to isolate interrupt. |
| ErrorPtr PauseInterrupted(); |
| |
| // Pause after a reload request. |
| ErrorPtr PausePostRequest(); |
| |
| // Pause execution due to an uncaught exception. |
| void PauseException(const Instance& exc); |
| |
| // Pause execution due to a call to the debugger() function from |
| // Dart. |
| void PauseDeveloper(const String& msg); |
| |
| void PrintBreakpointsToJSONArray(JSONArray* jsarr) const; |
| void PrintSettingsToJSONObject(JSONObject* jsobj) const; |
| |
| static bool IsDebuggable(const Function& func); |
| |
| intptr_t limitBreakpointId() { return next_id_; } |
| |
| // Callback to the debugger to continue frame rewind, post-deoptimization. |
| void RewindPostDeopt(); |
| |
| private: |
| ErrorPtr PauseRequest(ServiceEvent::EventKind kind); |
| |
| // Will return false if we are not at an await. |
| bool SetupStepOverAsyncSuspension(const char** error); |
| |
| bool NeedsIsolateEvents(); |
| bool NeedsDebugEvents(); |
| |
| void SendBreakpointEvent(ServiceEvent::EventKind kind, Breakpoint* bpt); |
| |
| void FindCompiledFunctions( |
| const GrowableHandlePtrArray<const Script>& scripts, |
| TokenPosition start_pos, |
| TokenPosition end_pos, |
| GrowableObjectArray* code_function_list); |
| bool FindBestFit(const Script& script, |
| TokenPosition token_pos, |
| TokenPosition last_token_pos, |
| Function* best_fit); |
| void DeoptimizeWorld(); |
| void NotifySingleStepping(bool value) const; |
| BreakpointLocation* SetCodeBreakpoints( |
| const GrowableHandlePtrArray<const Script>& scripts, |
| TokenPosition token_pos, |
| TokenPosition last_token_pos, |
| intptr_t requested_line, |
| intptr_t requested_column, |
| TokenPosition exact_token_pos, |
| const GrowableObjectArray& functions); |
| BreakpointLocation* SetBreakpoint(const Script& script, |
| TokenPosition token_pos, |
| TokenPosition last_token_pos, |
| intptr_t requested_line, |
| intptr_t requested_column, |
| const Function& function); |
| BreakpointLocation* SetBreakpoint( |
| const GrowableHandlePtrArray<const Script>& scripts, |
| TokenPosition token_pos, |
| TokenPosition last_token_pos, |
| intptr_t requested_line, |
| intptr_t requested_column, |
| const Function& function); |
| bool RemoveBreakpointFromTheList(intptr_t bp_id, BreakpointLocation** list); |
| Breakpoint* GetBreakpointByIdInTheList(intptr_t id, BreakpointLocation* list); |
| BreakpointLocation* GetLatentBreakpoint(const String& url, |
| intptr_t line, |
| intptr_t column); |
| void RegisterBreakpointLocation(BreakpointLocation* bpt); |
| BreakpointLocation* GetResolvedBreakpointLocation( |
| const String& script_url, |
| TokenPosition code_token_pos); |
| BreakpointLocation* GetBreakpointLocation( |
| const String& script_url, |
| TokenPosition token_pos, |
| intptr_t requested_line, |
| intptr_t requested_column, |
| TokenPosition code_token_pos = TokenPosition::kNoSource); |
| |
| void PrintBreakpointsListToJSONArray(BreakpointLocation* sbpt, |
| JSONArray* jsarr) const; |
| |
| void SignalPausedEvent(ActivationFrame* top_frame, Breakpoint* bpt); |
| |
| intptr_t nextId() { return next_id_++; } |
| |
| bool ShouldPauseOnException(DebuggerStackTrace* stack_trace, |
| const Instance& exc); |
| |
| // Handles any events which pause vm execution. Breakpoints, |
| // interrupts, etc. |
| void Pause(ServiceEvent* event); |
| |
| void HandleSteppingRequest(DebuggerStackTrace* stack_trace, |
| bool skip_next_step = false); |
| |
| void CacheStackTraces(DebuggerStackTrace* stack_trace, |
| DebuggerStackTrace* async_causal_stack_trace, |
| DebuggerStackTrace* awaiter_stack_trace); |
| void ClearCachedStackTraces(); |
| |
| void RewindToFrame(intptr_t frame_index); |
| void RewindToUnoptimizedFrame(StackFrame* frame, const Code& code); |
| void RewindToOptimizedFrame(StackFrame* frame, |
| const Code& code, |
| intptr_t post_deopt_frame_index); |
| |
| void ResetSteppingFramePointers(); |
| bool SteppedForSyntheticAsyncBreakpoint() const; |
| void CleanupSyntheticAsyncBreakpoint(); |
| void RememberTopFrameAwaiter(); |
| void SetAsyncSteppingFramePointer(DebuggerStackTrace* stack_trace); |
| void SetSyncSteppingFramePointer(DebuggerStackTrace* stack_trace); |
| |
| GroupDebugger* group_debugger() { return isolate_->group()->debugger(); } |
| |
| Isolate* isolate_; |
| |
| // ID number generator. |
| intptr_t next_id_; |
| |
| BreakpointLocation* latent_locations_; |
| BreakpointLocation* breakpoint_locations_; |
| |
| // Tells debugger what to do when resuming execution after a breakpoint. |
| ResumeAction resume_action_; |
| void set_resume_action(ResumeAction action); |
| intptr_t resume_frame_index_; |
| intptr_t post_deopt_frame_index_; |
| |
| // Do not call back to breakpoint handler if this flag is set. |
| // Effectively this means ignoring breakpoints. Set when Dart code may |
| // be run as a side effect of getting values of fields. |
| bool ignore_breakpoints_; |
| |
| // Indicates why the debugger is currently paused. If the debugger |
| // is not paused, this is NULL. Note that the debugger can be |
| // paused for breakpoints, isolate interruption, and (sometimes) |
| // exceptions. |
| ServiceEvent* pause_event_; |
| |
| // Current stack trace. Valid only while IsPaused(). |
| DebuggerStackTrace* stack_trace_; |
| DebuggerStackTrace* async_causal_stack_trace_; |
| DebuggerStackTrace* awaiter_stack_trace_; |
| |
| // When stepping through code, only pause the program if the top |
| // frame corresponds to this fp value, or if the top frame is |
| // lower on the stack. |
| uword stepping_fp_; |
| |
| // When stepping through code, do not stop more than once in the same |
| // token position range. |
| uword last_stepping_fp_; |
| TokenPosition last_stepping_pos_; |
| |
| // Used to track the current async/async* function. |
| uword async_stepping_fp_; |
| ObjectPtr top_frame_awaiter_; |
| |
| // If we step while at a breakpoint, we would hit the same pc twice. |
| // We use this field to let us skip the next single-step after a |
| // breakpoint. |
| bool skip_next_step_; |
| |
| // We keep this breakpoint alive until after the debugger does the step over |
| // async continuation machinery so that we can report that we've stopped |
| // at the breakpoint. |
| Breakpoint* synthetic_async_breakpoint_; |
| |
| Dart_ExceptionPauseInfo exc_pause_info_; |
| |
| friend class Isolate; |
| friend class BreakpointLocation; |
| DISALLOW_COPY_AND_ASSIGN(Debugger); |
| }; |
| |
| class DisableBreakpointsScope : public ValueObject { |
| public: |
| DisableBreakpointsScope(Debugger* debugger, bool disable) |
| : debugger_(debugger) { |
| ASSERT(debugger_ != NULL); |
| initial_state_ = debugger_->ignore_breakpoints(); |
| debugger_->set_ignore_breakpoints(disable); |
| } |
| |
| ~DisableBreakpointsScope() { |
| debugger_->set_ignore_breakpoints(initial_state_); |
| } |
| |
| private: |
| Debugger* debugger_; |
| bool initial_state_; |
| |
| DISALLOW_COPY_AND_ASSIGN(DisableBreakpointsScope); |
| }; |
| |
| } // namespace dart |
| |
| #endif // !defined(PRODUCT) |
| |
| #endif // RUNTIME_VM_DEBUGGER_H_ |