| // 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 VM_DEBUGGER_H_ |
| #define VM_DEBUGGER_H_ |
| |
| #include "include/dart_tools_api.h" |
| |
| #include "vm/object.h" |
| #include "vm/port.h" |
| |
| namespace dart { |
| |
| class CodeBreakpoint; |
| class Isolate; |
| class JSONArray; |
| class JSONStream; |
| class ObjectPointerVisitor; |
| class RemoteObjectCache; |
| 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) {} |
| |
| 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; } |
| RawInstance* 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.raw(); |
| } |
| |
| void PrintJSON(JSONStream* stream); |
| |
| private: |
| void VisitObjectPointers(ObjectPointerVisitor* visitor); |
| |
| enum ConditionKind { |
| kNone, |
| kRepeated, |
| kSingleShot, |
| kPerClosure, |
| }; |
| |
| intptr_t id_; |
| ConditionKind kind_; |
| Breakpoint* next_; |
| RawInstance* closure_; |
| BreakpointLocation* bpt_location_; |
| |
| 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(const Script& script, |
| intptr_t token_pos, |
| intptr_t end_token_pos, |
| intptr_t requested_line_number, |
| intptr_t requested_column_number); |
| // Create a new latent breakpoint. |
| BreakpointLocation(const String& url, |
| intptr_t requested_line_number, |
| intptr_t requested_column_number); |
| |
| ~BreakpointLocation(); |
| |
| RawFunction* function() const { return function_; } |
| intptr_t token_pos() const { return token_pos_; } |
| intptr_t end_token_pos() const { return end_token_pos_; } |
| |
| RawScript* script() const { return script_; } |
| RawString* url() const { return url_; } |
| |
| intptr_t requested_line_number() const { return requested_line_number_; } |
| intptr_t requested_column_number() const { return requested_column_number_; } |
| |
| intptr_t LineNumber(); |
| intptr_t ColumnNumber(); |
| |
| void GetCodeLocation(Library* lib, |
| Script* script, |
| intptr_t* token_pos) const; |
| |
| Breakpoint* AddRepeated(Debugger* dbg); |
| Breakpoint* AddSingleShot(Debugger* dbg); |
| Breakpoint* AddPerClosure(Debugger* dbg, const Instance& closure); |
| |
| bool AnyEnabled() const; |
| bool IsResolved() const { return is_resolved_; } |
| bool IsLatent() const { return token_pos_ < 0; } |
| |
| private: |
| void VisitObjectPointers(ObjectPointerVisitor* visitor); |
| |
| void SetResolved(const Function& func, intptr_t 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; } |
| |
| RawScript* script_; |
| RawString* url_; |
| intptr_t token_pos_; |
| intptr_t end_token_pos_; |
| bool is_resolved_; |
| BreakpointLocation* next_; |
| Breakpoint* conditions_; |
| intptr_t requested_line_number_; |
| intptr_t requested_column_number_; |
| |
| // Valid for resolved breakpoints: |
| RawFunction* function_; |
| intptr_t line_number_; |
| intptr_t column_number_; |
| |
| friend class Debugger; |
| 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. |
| class CodeBreakpoint { |
| public: |
| CodeBreakpoint(const Code& code, |
| intptr_t token_pos, |
| uword pc, |
| RawPcDescriptors::Kind kind); |
| ~CodeBreakpoint(); |
| |
| RawFunction* function() const; |
| uword pc() const { return pc_; } |
| intptr_t token_pos() const { return token_pos_; } |
| bool IsInternal() const { return bpt_location_ == NULL; } |
| |
| RawScript* SourceCode(); |
| RawString* SourceUrl(); |
| intptr_t LineNumber(); |
| |
| void Enable(); |
| void Disable(); |
| bool IsEnabled() const { return is_enabled_; } |
| |
| RawCode* OrigStubAddress() const; |
| |
| private: |
| void VisitObjectPointers(ObjectPointerVisitor* visitor); |
| |
| BreakpointLocation* bpt_location() const { return bpt_location_; } |
| void set_bpt_location(BreakpointLocation* value) { bpt_location_ = value; } |
| |
| void set_next(CodeBreakpoint* value) { next_ = value; } |
| CodeBreakpoint* next() const { return this->next_; } |
| |
| void PatchCode(); |
| void RestoreCode(); |
| |
| RawCode* code_; |
| intptr_t token_pos_; |
| uword pc_; |
| intptr_t line_number_; |
| bool is_enabled_; |
| |
| BreakpointLocation* bpt_location_; |
| CodeBreakpoint* next_; |
| |
| RawPcDescriptors::Kind breakpoint_kind_; |
| RawCode* saved_value_; |
| |
| friend class Debugger; |
| DISALLOW_COPY_AND_ASSIGN(CodeBreakpoint); |
| }; |
| |
| |
| // ActivationFrame represents one dart function activation frame |
| // on the call stack. |
| class ActivationFrame : public ZoneAllocated { |
| public: |
| ActivationFrame(uword pc, uword fp, uword sp, const Code& code, |
| const Array& deopt_frame, intptr_t deopt_frame_offset); |
| |
| uword pc() const { return pc_; } |
| uword fp() const { return fp_; } |
| uword sp() const { return sp_; } |
| const Function& function() const { |
| ASSERT(!function_.IsNull()); |
| return function_; |
| } |
| const Code& code() const { |
| ASSERT(!code_.IsNull()); |
| return code_; |
| } |
| |
| RawString* QualifiedFunctionName(); |
| RawString* SourceUrl(); |
| RawScript* SourceScript(); |
| RawLibrary* Library(); |
| intptr_t 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; |
| |
| // 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, |
| intptr_t* token_pos, |
| intptr_t* end_pos, |
| Object* value); |
| |
| RawArray* GetLocalVariables(); |
| RawObject* GetParameter(intptr_t index); |
| RawObject* GetClosure(); |
| RawObject* GetReceiver(); |
| |
| const Context& GetSavedCurrentContext(); |
| RawObject* GetAsyncOperation(); |
| |
| RawObject* Evaluate(const String& expr); |
| |
| // Print the activation frame into |jsobj|. if |full| is false, script |
| // and local variable objects are only references. if |full| is true, |
| // the complete script, function, and, local variable objects are included. |
| void PrintToJSONObject(JSONObject* jsobj, bool full = false); |
| |
| private: |
| void PrintContextMismatchError(intptr_t ctx_slot, |
| intptr_t frame_ctx_level, |
| intptr_t var_ctx_level); |
| |
| intptr_t TryIndex(); |
| void GetPcDescriptors(); |
| void GetVarDescriptors(); |
| void GetDescIndices(); |
| |
| RawObject* GetStackVar(intptr_t slot_index); |
| RawObject* 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_; |
| const Code& code_; |
| const Function& function_; |
| bool token_pos_initialized_; |
| intptr_t token_pos_; |
| intptr_t try_index_; |
| |
| 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_; |
| |
| 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; |
| |
| private: |
| void AddActivation(ActivationFrame* frame); |
| ZoneGrowableArray<ActivationFrame*> trace_; |
| |
| friend class Debugger; |
| DISALLOW_COPY_AND_ASSIGN(DebuggerStackTrace); |
| }; |
| |
| |
| class DebuggerEvent { |
| public: |
| enum EventType { |
| kBreakpointReached = 1, |
| kBreakpointResolved = 2, |
| kExceptionThrown = 3, |
| kIsolateCreated = 4, |
| kIsolateShutdown = 5, |
| kIsolateInterrupted = 6, |
| }; |
| |
| explicit DebuggerEvent(Isolate* isolate, EventType event_type) |
| : isolate_(isolate), |
| type_(event_type), |
| top_frame_(NULL), |
| breakpoint_(NULL), |
| exception_(NULL), |
| async_continuation_(NULL), |
| at_async_jump_(false), |
| timestamp_(-1) {} |
| |
| Isolate* isolate() const { return isolate_; } |
| |
| EventType type() const { return type_; } |
| |
| bool IsPauseEvent() const { |
| return (type_ == kBreakpointReached || |
| type_ == kIsolateInterrupted || |
| type_ == kExceptionThrown); |
| } |
| |
| ActivationFrame* top_frame() const { |
| ASSERT(IsPauseEvent()); |
| return top_frame_; |
| } |
| void set_top_frame(ActivationFrame* frame) { |
| ASSERT(IsPauseEvent()); |
| top_frame_ = frame; |
| } |
| |
| Breakpoint* breakpoint() const { |
| ASSERT(type_ == kBreakpointReached || type_ == kBreakpointResolved); |
| return breakpoint_; |
| } |
| void set_breakpoint(Breakpoint* bpt) { |
| ASSERT(type_ == kBreakpointReached || type_ == kBreakpointResolved); |
| breakpoint_ = bpt; |
| } |
| |
| const Object* exception() const { |
| ASSERT(type_ == kExceptionThrown); |
| return exception_; |
| } |
| void set_exception(const Object* exception) { |
| ASSERT(type_ == kExceptionThrown); |
| exception_ = exception; |
| } |
| |
| const Object* async_continuation() const { |
| ASSERT(type_ == kBreakpointReached); |
| return async_continuation_; |
| } |
| void set_async_continuation(const Object* closure) { |
| ASSERT(type_ == kBreakpointReached); |
| async_continuation_ = closure; |
| } |
| |
| bool at_async_jump() const { |
| return at_async_jump_; |
| } |
| void set_at_async_jump(bool value) { |
| at_async_jump_ = value; |
| } |
| |
| Dart_Port isolate_id() const { |
| return isolate_->main_port(); |
| } |
| |
| void UpdateTimestamp(); |
| |
| int64_t timestamp() const { |
| return timestamp_; |
| } |
| |
| private: |
| Isolate* isolate_; |
| EventType type_; |
| ActivationFrame* top_frame_; |
| Breakpoint* breakpoint_; |
| const Object* exception_; |
| const Object* async_continuation_; |
| bool at_async_jump_; |
| int64_t timestamp_; |
| }; |
| |
| |
| class Debugger { |
| public: |
| typedef void EventHandler(DebuggerEvent* event); |
| |
| Debugger(); |
| ~Debugger(); |
| |
| void Initialize(Isolate* isolate); |
| void NotifyIsolateCreated(); |
| void Shutdown(); |
| |
| void NotifyCompilation(const Function& func); |
| void NotifyDoneLoading(); |
| |
| RawFunction* ResolveFunction(const Library& library, |
| const String& class_name, |
| const String& function_name); |
| |
| // Set breakpoint at closest location to function entry. |
| Breakpoint* SetBreakpointAtEntry(const Function& target_function, |
| bool single_shot); |
| Breakpoint* SetBreakpointAtActivation(const Instance& closure); |
| 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); |
| RawError* OneTimeBreakAtEntry(const Function& target_function); |
| |
| BreakpointLocation* BreakpointLocationAtLineCol(const String& script_url, |
| intptr_t line_number, |
| intptr_t column_number); |
| |
| |
| void RemoveBreakpoint(intptr_t bp_id); |
| Breakpoint* GetBreakpointById(intptr_t id); |
| |
| void SetStepOver(); |
| void SetSingleStep(); |
| void SetStepOut(); |
| bool IsStepping() const { return resume_action_ != kContinue; } |
| |
| bool IsPaused() const { return pause_event_ != NULL; } |
| |
| // 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 DebuggerEvent* PauseEvent() const { return pause_event_; } |
| |
| void SetExceptionPauseInfo(Dart_ExceptionPauseInfo pause_info); |
| Dart_ExceptionPauseInfo GetExceptionPauseInfo() const; |
| |
| void VisitObjectPointers(ObjectPointerVisitor* visitor); |
| |
| // Called from Runtime when a breakpoint in Dart code is reached. |
| void BreakpointCallback(); |
| |
| // Returns true if there is at least one breakpoint set in func or code. |
| // Checks for both user-defined and internal temporary breakpoints. |
| bool HasBreakpoint(const Function& func); |
| bool HasBreakpoint(const Code& code); |
| |
| // Returns true if the call at address pc is patched to point to |
| // a debugger stub. |
| bool HasActiveBreakpoint(uword pc); |
| |
| // 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* CurrentStackTrace(); |
| |
| // 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. |
| DebuggerStackTrace* StackTraceFrom(const Stacktrace& dart_stacktrace); |
| |
| RawArray* GetInstanceFields(const Instance& obj); |
| RawArray* GetStaticFields(const Class& cls); |
| RawArray* GetLibraryFields(const Library& lib); |
| RawArray* GetGlobalFields(const Library& lib); |
| |
| intptr_t CacheObject(const Object& obj); |
| RawObject* GetCachedObject(intptr_t obj_id); |
| bool IsValidObjectId(intptr_t obj_id); |
| |
| Dart_Port GetIsolateId() { return isolate_id_; } |
| |
| static void SetEventHandler(EventHandler* handler); |
| |
| // Utility functions. |
| static const char* QualifiedFunctionName(const Function& func); |
| |
| RawObject* GetInstanceField(const Class& cls, |
| const String& field_name, |
| const Instance& object); |
| RawObject* GetStaticField(const Class& cls, |
| const String& field_name); |
| |
| RawError* SignalBpReached(); |
| RawError* DebuggerStepCallback(); |
| RawError* SignalIsolateInterrupted(); |
| |
| void BreakHere(const String& msg); |
| |
| void SignalExceptionThrown(const Instance& exc); |
| void SignalIsolateEvent(DebuggerEvent::EventType type); |
| |
| RawCode* GetPatchedStubAddress(uword breakpoint_address); |
| |
| void PrintBreakpointsToJSONArray(JSONArray* jsarr) const; |
| void PrintSettingsToJSONObject(JSONObject* jsobj) const; |
| |
| static bool IsDebuggable(const Function& func); |
| |
| intptr_t limitBreakpointId() { return next_id_; } |
| |
| private: |
| enum ResumeAction { |
| kContinue, |
| kStepOver, |
| kStepOut, |
| kSingleStep |
| }; |
| |
| static bool HasEventHandler(); |
| void InvokeEventHandler(DebuggerEvent* event); |
| |
| void FindCompiledFunctions(const Script& script, |
| intptr_t start_pos, |
| intptr_t end_pos, |
| GrowableObjectArray* function_list); |
| RawFunction* FindBestFit(const Script& script, intptr_t token_pos); |
| RawFunction* FindInnermostClosure(const Function& function, |
| intptr_t token_pos); |
| intptr_t ResolveBreakpointPos(const Function& func, |
| intptr_t requested_token_pos, |
| intptr_t last_token_pos, |
| intptr_t requested_column); |
| void DeoptimizeWorld(); |
| BreakpointLocation* SetBreakpoint(const Script& script, |
| intptr_t token_pos, |
| intptr_t last_token_pos, |
| intptr_t requested_line, |
| intptr_t requested_column); |
| void RemoveInternalBreakpoints(); |
| void UnlinkCodeBreakpoints(BreakpointLocation* bpt_location); |
| BreakpointLocation* GetLatentBreakpoint(const String& url, |
| intptr_t line, |
| intptr_t column); |
| void RegisterBreakpointLocation(BreakpointLocation* bpt); |
| void RegisterCodeBreakpoint(CodeBreakpoint* bpt); |
| BreakpointLocation* GetBreakpointLocation(const Script& script, |
| intptr_t token_pos, |
| intptr_t requested_column); |
| void MakeCodeBreakpointAt(const Function& func, |
| BreakpointLocation* bpt); |
| // Returns NULL if no breakpoint exists for the given address. |
| CodeBreakpoint* GetCodeBreakpoint(uword breakpoint_address); |
| |
| void SyncBreakpointLocation(BreakpointLocation* loc); |
| |
| ActivationFrame* TopDartFrame() const; |
| static ActivationFrame* CollectDartFrame(Isolate* isolate, |
| uword pc, |
| StackFrame* frame, |
| const Code& code, |
| const Array& deopt_frame, |
| intptr_t deopt_frame_offset); |
| static RawArray* DeoptimizeToArray(Thread* thread, |
| StackFrame* frame, |
| const Code& code); |
| static DebuggerStackTrace* CollectStackTrace(); |
| void SignalBpResolved(Breakpoint *bpt); |
| void SignalPausedEvent(ActivationFrame* top_frame, |
| Breakpoint* bpt); |
| |
| intptr_t nextId() { return next_id_++; } |
| |
| bool ShouldPauseOnException(DebuggerStackTrace* stack_trace, |
| const Instance& exc); |
| |
| void CollectLibraryFields(const GrowableObjectArray& field_list, |
| const Library& lib, |
| const String& prefix, |
| bool include_private_fields); |
| |
| // Handles any events which pause vm execution. Breakpoints, |
| // interrupts, etc. |
| void Pause(DebuggerEvent* event); |
| |
| void HandleSteppingRequest(DebuggerStackTrace* stack_trace); |
| |
| Zone* zone() const { return isolate_->current_zone(); } |
| |
| Isolate* isolate_; |
| Dart_Port isolate_id_; // A unique ID for the isolate in the debugger. |
| bool initialized_; |
| |
| // ID number generator. |
| intptr_t next_id_; |
| |
| BreakpointLocation* latent_locations_; |
| BreakpointLocation* breakpoint_locations_; |
| CodeBreakpoint* code_breakpoints_; |
| |
| // Tells debugger what to do when resuming execution after a breakpoint. |
| ResumeAction resume_action_; |
| |
| // 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. |
| DebuggerEvent* pause_event_; |
| |
| // An id -> object map. Valid only while IsPaused(). |
| RemoteObjectCache* obj_cache_; |
| |
| // Current stack trace. Valid only while IsPaused(). |
| DebuggerStackTrace* 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_; |
| |
| Dart_ExceptionPauseInfo exc_pause_info_; |
| |
| static EventHandler* event_handler_; |
| |
| friend class Isolate; |
| friend class BreakpointLocation; |
| DISALLOW_COPY_AND_ASSIGN(Debugger); |
| }; |
| |
| |
| } // namespace dart |
| |
| #endif // VM_DEBUGGER_H_ |