// 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 #include "include/dart_tools_api.h" #include "vm/compiler/api/deopt_id.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. #define TD_Print(format, ...) \ if (FLAG_verbose_debug) Log::Current()->Print(format, ##__VA_ARGS__) namespace dart { class CodeBreakpoint; class Isolate; class JSONArray; class JSONStream; class ObjectPointerVisitor; class BreakpointLocation; class StackFrame; // A user-defined breakpoint, which can be set for a particular closure // (if |closure| is not |null|) and can fire one (|is_single_shot| is |true|) // or many times. class Breakpoint { public: Breakpoint(intptr_t id, BreakpointLocation* bpt_location, bool is_single_shot, const Closure& closure) : id_(id), next_(nullptr), closure_(closure.ptr()), bpt_location_(bpt_location), is_single_shot_(is_single_shot) {} 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 is_single_shot() const { return is_single_shot_; } ClosurePtr closure() const { return closure_; } void Enable() { ASSERT(!enabled_); enabled_ = true; } void Disable() { ASSERT(enabled_); enabled_ = false; } bool is_enabled() const { return enabled_; } void PrintJSON(JSONStream* stream); private: void VisitObjectPointers(ObjectPointerVisitor* visitor); intptr_t id_; Breakpoint* next_; ClosurePtr closure_; BreakpointLocation* bpt_location_; bool is_single_shot_; 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, also known as a pending breakpoint, is one where // the underlying code has not been compiled yet. Since the code has not been // compiled, we can't determine the definitive token position to associate with // the breakpoint yet. // // 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& 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* AddBreakpoint(Debugger* dbg, const Closure& closure, bool single_shot); 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 scripts_; StringPtr url_; std::unique_ptr line_number_lock_; intptr_t line_number_; // lazily computed for token_pos_ std::atomic token_pos_; std::atomic 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 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(const Bytecode& code, BreakpointLocation* loc, uword pc); ~CodeBreakpoint(); // Used by GroupDebugger to find CodeBreakpoint associated with // particular function. FunctionPtr function() const { #if defined(DART_BYTECODE_INTERPRETER) if (bytecode_ != Bytecode::null()) { return Bytecode::Handle(bytecode_).function(); } #endif 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; uint32_t OrigOpcode() const { return saved_opcode_; } 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_; BytecodePtr bytecode_; uword pc_; int enabled_count_; // incremented for every enabled breakpoint location // Breakpoint locations from different debuggers/isolates that // point to this code breakpoint. MallocGrowableArray breakpoint_locations_; CodeBreakpoint* next_; UntaggedPcDescriptors::Kind breakpoint_kind_; CodePtr saved_value_; uint32_t saved_opcode_; 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 ZoneObject { public: enum Kind { kRegular, kAsyncSuspensionMarker, kAsyncAwaiter, }; ActivationFrame(uword pc, uword fp, uword sp, const Function& function, const Object& code_or_bytecode, const Array& deopt_frame, intptr_t deopt_frame_offset); // Create a |kAsyncAwaiter| frame representing asynchronous awaiter // waiting for the completion of a |Future|. // // |closure| is the listener which will be invoked when awaited // computation completes. ActivationFrame(uword pc, const Function& function, const Object& code_or_bytecode, const Closure& closure); explicit ActivationFrame(Kind kind); Kind kind() const { return kind_; } uword pc() const { return pc_; } uword fp() const { return fp_; } uword sp() const { return sp_; } // For |kAsyncAwaiter| frames this is the listener which will be invoked // when the frame below (callee) completes. const Closure& closure() const { return closure_; } const Function& function() const { return function_; } const Code& code() const { ASSERT(!code_or_bytecode_.IsNull()); return Code::Cast(code_or_bytecode_); } const Bytecode& bytecode() const { ASSERT(!code_or_bytecode_.IsNull()); return Bytecode::Cast(code_or_bytecode_); } intptr_t PayloadStart() const { ASSERT(!code_or_bytecode_.IsNull()); return IsInterpreted() ? bytecode().PayloadStart() : code().PayloadStart(); } bool IsInterpreted() const { return code_or_bytecode_.IsBytecode(); } enum Relation { kCallee, kSelf, kCaller, }; Relation CompareTo(uword other_fp) const { return CompareTo(IsInterpreted(), fp(), other_fp); } // Should only be called if both frame pointers come from either interpreted // code or compiled code. static Relation CompareTo(bool is_interpreted, uword fp, uword other_fp); 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); ObjectPtr GetParameter(intptr_t index); ClosurePtr GetClosure(); ObjectPtr GetReceiver(); const Context& GetSavedCurrentContext(intptr_t* index = nullptr); ObjectPtr GetSuspendStateVar(); ObjectPtr GetSuspendableFunctionData(); TypeArgumentsPtr BuildParameters( const GrowableObjectArray& param_names, const GrowableObjectArray& param_values, const GrowableObjectArray& type_params_names, const GrowableObjectArray& type_params_bounds, const GrowableObjectArray& type_params_defaults); ObjectPtr EvaluateCompiledExpression(const ExternalTypedData& kernel_data, const Array& arguments, const Array& type_definitions, const TypeArguments& type_arguments); void PrintToJSONObject(JSONObject* jsobj); bool HandlesException(const Instance& exc_obj); private: void PrintToJSONObjectRegular(JSONObject* jsobj); void PrintToJSONObjectAsyncAwaiter(JSONObject* jsobj); void PrintToJSONObjectAsyncSuspensionMarker(JSONObject* jsobj); void PrintContextMismatchError(intptr_t ctx_slot, intptr_t frame_ctx_level, intptr_t var_ctx_level); void PrintContextLevelError(const char* message); intptr_t TryIndex(); intptr_t DeoptId(); void GetPcDescriptors(); void GetVarDescriptors(); void GetDescIndices(); static const char* KindToCString(Kind kind) { switch (kind) { case kRegular: return "Regular"; case kAsyncAwaiter: // Keeping the legacy name in the protocol itself. return "AsyncCausal"; case kAsyncSuspensionMarker: return "AsyncSuspensionMarker"; 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_ = 0; uword fp_ = 0; uword sp_ = 0; // The anchor of the context chain for this function. Context& ctx_ = Context::ZoneHandle(); const Object& code_or_bytecode_; const Function& function_; const Closure& closure_; bool context_initialized_ = false; bool token_pos_initialized_ = false; TokenPosition token_pos_ = TokenPosition::kNoSource; intptr_t try_index_ = -1; intptr_t deopt_id_ = dart::DeoptId::kNone; intptr_t line_number_ = -1; intptr_t column_number_ = -1; intptr_t context_level_ = -1; // 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_ = false; LocalVarDescriptors& var_descriptors_ = LocalVarDescriptors::ZoneHandle(); ZoneGrowableArray desc_indices_; PcDescriptors& pc_desc_ = PcDescriptors::ZoneHandle(); friend class Debugger; friend class DebuggerStackTrace; DISALLOW_COPY_AND_ASSIGN(ActivationFrame); }; // Array of function activations on the call stack. class DebuggerStackTrace : public ZoneObject { public: explicit DebuggerStackTrace(int capacity) : thread_(Thread::Current()), zone_(thread_->zone()), 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* Collect(); static DebuggerStackTrace* CollectAsyncAwaiters(); // 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); bool has_async_catch_error() const { return has_async_catch_error_; } void set_has_async_catch_error(bool has_async_catch_error) { has_async_catch_error_ = has_async_catch_error; } private: void AddActivation(ActivationFrame* frame); void AddAsyncSuspension(); void AddAsyncAwaiterFrame(uword pc, const Function& function, const Object& code_or_bytecode, const Closure& closure); void AppendCodeFrames(StackFrame* frame, const Code& code); void AppendBytecodeFrame(StackFrame* frame, const Function& function, const Bytecode& bytecode); Thread* thread_; Zone* zone_; Code& inlined_code_ = Code::Handle(); Array& deopt_frame_ = Array::Handle(); ZoneGrowableArray trace_; bool has_async_catch_error_ = false; 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(nullptr), 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(key)); } static bool IsKeyEqual(Pair kv, Key key) { return kv.key == key; } }; class DebuggerSet : public MallocDirectChainedHashMap { 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::Insert(pair); } void Remove(const Key& key) { MallocDirectChainedHashMap::Remove(key); } }; class BoolCallable : public ValueObject { public: BoolCallable() {} virtual ~BoolCallable() {} virtual bool Call() = 0; private: DISALLOW_COPY_AND_ASSIGN(BoolCallable); }; template 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 MakeCodeBreakpointAtUnsafe(Thread* thread, const Function& func, BreakpointLocation* bpt); 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); uint32_t GetPatchedOpcode(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 HasCodeBreakpointInFunctionUnsafe(const Function& func); 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(); } RwLock* single_stepping_set_lock() { return single_stepping_set_lock_.get(); } void RegisterSingleSteppingDebugger(Thread* thread, const Debugger* debugger); void UnregisterSingleSteppingDebugger(Thread* thread, const Debugger* debugger); // Returns [true] if there is at least one breakpoint set in function or code. // Checks for both user-defined and internal temporary breakpoints. bool HasBreakpointUnsafe(Thread* thread, const Function& function); bool HasBreakpoint(Thread* thread, const Function& function); bool IsDebugging(Thread* thread, const Function& function); IsolateGroup* isolate_group() { return isolate_group_; } private: IsolateGroup* isolate_group_; std::unique_ptr 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 breakpoint_locations_lock_; MallocGrowableArray breakpoint_locations_; std::unique_ptr 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(); Isolate* isolate() const { return isolate_; } void NotifyIsolateCreated(); void Shutdown(); void NotifyDoneLoading(); // Tries to set a breakpoint at the first debuggable token position within // |target_function|. // // If |Error::null()| is returned, it means that a breakpoint was set // successfully, and that a (non-null) pointer to a |Breakpoint| object was // stored into |*result_breakpoint|. If any other |ErrorPtr| is returned, it // means that a breakpoint was not set successfully, and the return value will // point to an |Error| describing why the breakpoint could not be set. ErrorPtr SetBreakpointAtEntry(const Function& target_function, bool single_shot, Breakpoint** result_breakpoint); // Tries to set a breakpoint at the first debuggable token position within // |closure|. // // If |Error::null()| is returned, it means that a breakpoint was set // successfully, and that a (non-null) pointer to a |Breakpoint| object was // stored into |*result_breakpoint|. If any other |ErrorPtr| is returned, it // means that a breakpoint was not set successfully, and the return value will // point to an |Error| describing why the breakpoint could not be set. ErrorPtr SetBreakpointAtActivation(const Instance& closure, bool single_shot, Breakpoint** result_breakpoint); // If a breakpoint has already been set at the activation of |closure|, // returns a pointer to it. Otherwise, returns |nullptr|. Breakpoint* BreakpointAtActivation(const Instance& closure); // Tries to set a breakpoint at the first debuggable token position within the // token range specified by |script_url|, |line_number|, and |column_number|. // // If |Error::null()| is returned, it means that a breakpoint was set // successfully, and that a (non-null) pointer to a |Breakpoint| object was // stored into |*result_breakpoint|. If any other |ErrorPtr| is returned, it // means that a breakpoint was not set successfully, and the return value will // point to an |Error| describing why the breakpoint could not be set. ErrorPtr SetBreakpointAtLineCol(const String& script_url, intptr_t line_number, intptr_t column_number, Breakpoint** result_breakpoint); // Tries to set |CodeBreakpoint|s at all code mapped to the first debuggable // token position within the range specified by |script_url|, |line_number|, // and |column_number| and then prepare a |BreakpointLocation| containing // those |CodeBreakpoint|s. // // If |Error::null()| is returned, it means that a |BreakpointLocation| was // prepared successfully, and that a (non-null) pointer to a // |BreakpointLocation| object was stored into |*result_breakpoint_location|. // If any other |ErrorPtr| is returned, it means that a |BreakpointLocation| // was not prepared successfully, and the return value will point to an // |Error| describing why the |BreakpointLocation| could not be prepared. ErrorPtr BreakpointLocationAtLineCol( const String& script_url, intptr_t line_number, intptr_t column_number, BreakpointLocation** result_breakpoint_location); // 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 AsyncStepInto(const Closure& awaiter); void Continue(); bool SetResumeAction(ResumeAction action, intptr_t frame_index = 1, const char** error = nullptr); bool IsStepping() const { return resume_action_ != kContinue; } bool IsSingleStepping() const { return resume_action_ == kStepInto; } bool IsPaused() const { return pause_event_ != nullptr; } 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 nullptr. 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* AsyncAwaiterStackTrace(); // 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(); // Sets breakpoint at resumption of a suspendable function // with given function data (such as _Future or _AsyncStarStreamController). void SetBreakpointAtResumption(const Object& function_data); // Check breakpoints at frame resumption. Called from generated code. void ResumptionBreakpoint(); 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); // Finds all |Function|s that span the token range [start_pos, end_pos] in any // of the scripts in |scripts|, compiles these functions, and then adds them // to |code_function_list|. If an error occurs during compilation, the error // is returned. Otherwise, |Error::null()| is returned. ErrorPtr FindAndCompileMatchingFunctions( const GrowableHandlePtrArray& scripts, TokenPosition start_pos, TokenPosition end_pos, GrowableObjectArray& code_function_list) const; bool FindBestFit(const Script& script, TokenPosition token_pos, TokenPosition last_token_pos, Function* best_fit); void DeoptimizeWorld(); void RunWithStoppedDeoptimizedWorld(std::function fun); void NotifySingleStepping(bool value); BreakpointLocation* SetCodeBreakpoints( const GrowableHandlePtrArray& scripts, TokenPosition token_pos, TokenPosition last_token_pos, intptr_t requested_line, intptr_t requested_column, TokenPosition exact_token_pos, const GrowableObjectArray& functions); // Tries to set |CodeBreakpoint|s at all code mapped to the first debuggable // token position within the range specified by |script|, |line_number|, and // |column_number| and then prepare a |BreakpointLocation| containing those // |CodeBreakpoint|s. // // If |Error::null()| is returned, it means that a |BreakpointLocation| was // prepared successfully, and that a (non-null) pointer to a // |BreakpointLocation| object was stored into |*result_breakpoint_location|. // If any other |ErrorPtr| is returned, it means that a |BreakpointLocation| // was not prepared successfully, and the return value will point to an // |Error| describing why the |BreakpointLocation| could not be prepared. ErrorPtr SetBreakpoint(const Script& script, TokenPosition token_pos, TokenPosition last_token_pos, intptr_t requested_line, intptr_t requested_column, const Function& function, BreakpointLocation** result_breakpoint_location); // Tries to set |CodeBreakpoint|s at all code mapped to the first debuggable // token position within the range specified by |scripts|, |line_number|, and // |column_number| and then prepare a |BreakpointLocation| containing those // |CodeBreakpoint|s. All of the scripts in |scripts| must have identical // tokens in all positions. // // If |Error::null()| is returned, it means that a |BreakpointLocation| was // prepared successfully, and that a (non-null) pointer to a // |BreakpointLocation| object was stored into |*result_breakpoint_location|. // If any other |ErrorPtr| is returned, it means that a |BreakpointLocation| // was not prepared successfully, and the return value will point to an // |Error| describing why the |BreakpointLocation| could not be prepared. ErrorPtr SetBreakpoint(const GrowableHandlePtrArray& scripts, TokenPosition token_pos, TokenPosition last_token_pos, intptr_t requested_line, intptr_t requested_column, const Function& function, BreakpointLocation** result_breakpoint_location); 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 RegisterBreakpointLocationUnsafe(BreakpointLocation* loc); 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(); void CacheStackTraces(DebuggerStackTrace* stack_trace, DebuggerStackTrace* async_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 ResetSteppingFramePointer(); void SetSyncSteppingFramePointer(ActivationFrame* frame); void ResetLastSteppingInformation(); void SetLastSteppingInformation(ActivationFrame* frame); void SetLastSteppingInformation(BreakpointLocation* bpt_location); bool MatchesLastSteppingInformation(ActivationFrame* frame); 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 nullptr. 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_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_; #if defined(DART_BYTECODE_INTERPRETER) bool stepping_fp_from_interpreted_frame_ = false; #endif // When stepping through code, do not stop more than once in the same // token position range. uword last_stepping_fp_; TokenPosition last_stepping_pos_; Dart_ExceptionPauseInfo exc_pause_info_; // Holds function data corresponding to suspendable // function which should be stopped when resumed. MallocGrowableArray breakpoints_at_resumption_; 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_ != nullptr); 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_