| // 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/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<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* 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<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 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_DYNAMIC_MODULES) | |
| 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<BreakpointLocation*> 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, | |
| AbstractType* static_type = nullptr); | |
| 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<intptr_t> 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<ActivationFrame*> 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<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 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<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<RwLock> 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<const Script>& 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<void()> fun); | |
| void NotifySingleStepping(bool value); | |
| 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); | |
| // 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<const Script>& 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_DYNAMIC_MODULES) | |
| 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<ObjectPtr> 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_ |