// 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_debugger_api.h" #include "vm/object.h" #include "vm/port.h" namespace dart { class ActiveVariables; class CodeBreakpoint; class Isolate; class JSONArray; class JSONStream; class ObjectPointerVisitor; class RemoteObjectCache; class SourceBreakpoint; class StackFrame; // SourceBreakpoint represents a user-specified breakpoint location in // Dart source. There may be more than one CodeBreakpoint object per // SourceBreakpoint. class SourceBreakpoint { public: SourceBreakpoint(intptr_t id, const Script& script, intptr_t token_pos, intptr_t end_token_pos); RawFunction* function() const { return function_; } intptr_t token_pos() const { return token_pos_; } intptr_t end_token_pos() const { return end_token_pos_; } intptr_t id() const { return id_; } RawScript* script() const { return script_; } RawString* SourceUrl(); intptr_t LineNumber(); void GetCodeLocation(Library* lib, Script* script, intptr_t* token_pos); void Enable(); void Disable(); bool IsEnabled() const { return is_enabled_; } bool IsResolved() const { return is_resolved_; } void PrintJSON(JSONStream* stream); private: void VisitObjectPointers(ObjectPointerVisitor* visitor); void SetResolved(const Function& func, intptr_t token_pos); void set_next(SourceBreakpoint* value) { next_ = value; } SourceBreakpoint* next() const { return this->next_; } const intptr_t id_; RawScript* script_; intptr_t token_pos_; intptr_t end_token_pos_; bool is_resolved_; bool is_enabled_; SourceBreakpoint* next_; // Valid for resolved breakpoints: RawFunction* function_; intptr_t line_number_; friend class Debugger; DISALLOW_COPY_AND_ASSIGN(SourceBreakpoint); }; // CodeBreakpoint represents a location in compiled code. There may be // more than one CodeBreakpoint for one SourceBreakpoint, e.g. when a // function gets compiled as a regular function and as a closure. class CodeBreakpoint { public: CodeBreakpoint(const Code& code, intptr_t pc_desc_index); ~CodeBreakpoint(); RawFunction* function() const; uword pc() const { return pc_; } intptr_t token_pos() const { return token_pos_; } bool IsInternal() const { return src_bpt_ == NULL; } RawScript* SourceCode(); RawString* SourceUrl(); intptr_t LineNumber(); void Enable(); void Disable(); bool IsEnabled() const { return is_enabled_; } uword OrigStubAddress() const; private: void VisitObjectPointers(ObjectPointerVisitor* visitor); SourceBreakpoint* src_bpt() const { return src_bpt_; } void set_src_bpt(SourceBreakpoint* value) { src_bpt_ = value; } void set_next(CodeBreakpoint* value) { next_ = value; } CodeBreakpoint* next() const { return this->next_; } intptr_t pc_desc_index() const { return pc_desc_index_; } void PatchCode(); void RestoreCode(); RawCode* code_; intptr_t pc_desc_index_; intptr_t token_pos_; uword pc_; intptr_t line_number_; bool is_enabled_; SourceBreakpoint* src_bpt_; CodeBreakpoint* next_; PcDescriptors::Kind breakpoint_kind_; uword 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(); void SetContext(const Context& ctx) { ctx_ = ctx.raw(); } // 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, Instance* value); RawArray* GetLocalVariables(); RawObject* GetReceiver(); RawContext* GetSavedEntryContext(); RawContext* GetSavedCurrentContext(); RawObject* Evaluate(const String& expr); void PrintToJSONObject(JSONObject* jsobj); private: void PrintContextMismatchError(const String& var_name, intptr_t ctx_slot, intptr_t frame_ctx_level, intptr_t var_ctx_level); intptr_t PcDescIndex(); intptr_t TryIndex(); void GetPcDescriptors(); void GetVarDescriptors(); void GetDescIndices(); RawObject* GetLocalVar(intptr_t slot_index); RawInstance* GetLocalInstanceVar(intptr_t slot_index); RawContext* GetLocalContextVar(intptr_t slot_index); RawInstance* GetLocalVarValue(intptr_t slot_index); RawInstance* GetInstanceCallReceiver(intptr_t num_actual_args); RawObject* GetClosureObject(intptr_t num_acatual_args); uword pc_; uword fp_; uword sp_; // The anchor of the context chain for this function. Context& ctx_; const Code& code_; const Function& function_; intptr_t token_pos_; intptr_t pc_desc_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 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 trace_; friend class Debugger; DISALLOW_COPY_AND_ASSIGN(DebuggerStackTrace); }; typedef void BreakpointHandler(Dart_Port isolate_id, SourceBreakpoint* bpt, DebuggerStackTrace* stack); // TODO(turnidge): At some point we may want to turn this into a class // hierarchy. class DebuggerEvent { public: enum EventType { kBreakpointReached = 1, kBreakpointResolved = 2, kExceptionThrown = 3, kIsolateCreated = 4, kIsolateShutdown = 5, kIsolateInterrupted = 6, }; explicit DebuggerEvent(EventType event_type) : type_(event_type), top_frame_(NULL), breakpoint_(NULL), exception_(NULL), isolate_id_(0) {} EventType type() const { return type_; } ActivationFrame* top_frame() const { ASSERT(type_ == kBreakpointReached); return top_frame_; } void set_top_frame(ActivationFrame* frame) { ASSERT(type_ == kBreakpointReached); top_frame_ = frame; } SourceBreakpoint* breakpoint() const { ASSERT(type_ == kBreakpointReached || type_ == kBreakpointResolved); return breakpoint_; } void set_breakpoint(SourceBreakpoint* 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; } Dart_Port isolate_id() const { ASSERT(type_ == kIsolateCreated || type_ == kIsolateShutdown || type_ == kIsolateInterrupted); return isolate_id_; } void set_isolate_id(Dart_Port isolate_id) { ASSERT(type_ == kIsolateCreated || type_ == kIsolateShutdown || type_ == kIsolateInterrupted); isolate_id_ = isolate_id; } void PrintJSON(JSONStream* js) const; static const char* EventTypeToCString(EventType type); private: EventType type_; ActivationFrame* top_frame_; SourceBreakpoint* breakpoint_; const Object* exception_; Dart_Port isolate_id_; }; class Debugger { public: typedef void EventHandler(DebuggerEvent *event); Debugger(); ~Debugger(); void Initialize(Isolate* isolate); void Shutdown(); void NotifyCompilation(const Function& func); RawFunction* ResolveFunction(const Library& library, const String& class_name, const String& function_name); // Set breakpoint at closest location to function entry. SourceBreakpoint* SetBreakpointAtEntry(const Function& target_function); SourceBreakpoint* SetBreakpointAtLine(const String& script_url, intptr_t line_number); void OneTimeBreakAtEntry(const Function& target_function); void RemoveBreakpoint(intptr_t bp_id); SourceBreakpoint* GetBreakpointById(intptr_t id); void SetStepOver(); void SetSingleStep(); void SetStepOut(); bool IsStepping() const { return resume_action_ != kContinue; } bool IsPaused() const { return pause_event_ != NULL; } // 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(); 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); void SignalBpReached(); void DebuggerStepCallback(); void SignalExceptionThrown(const Instance& exc); void SignalIsolateEvent(DebuggerEvent::EventType type); static void SignalIsolateInterrupted(); uword GetPatchedStubAddress(uword breakpoint_address); void PrintBreakpointsToJSONArray(JSONArray* jsarr) const; static bool IsDebuggable(const Function& func); private: enum ResumeAction { kContinue, kStepOver, kStepOut, kSingleStep }; 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); void DeoptimizeWorld(); void SetInternalBreakpoints(const Function& target_function); SourceBreakpoint* SetBreakpoint(const Script& script, intptr_t token_pos, intptr_t last_token_pos); void RemoveInternalBreakpoints(); void UnlinkCodeBreakpoints(SourceBreakpoint* src_bpt); void RegisterSourceBreakpoint(SourceBreakpoint* bpt); void RegisterCodeBreakpoint(CodeBreakpoint* bpt); SourceBreakpoint* GetSourceBreakpoint(const Script& script, intptr_t token_pos); void MakeCodeBreakpointAt(const Function& func, SourceBreakpoint* bpt); // Returns NULL if no breakpoint exists for the given address. CodeBreakpoint* GetCodeBreakpoint(uword breakpoint_address); void SyncBreakpoint(SourceBreakpoint* bpt); ActivationFrame* TopDartFrame() const; static ActivationFrame* CollectDartFrame(Isolate* isolate, uword pc, StackFrame* frame, const Code& code, const Array& deopt_frame, intptr_t deopt_frame_offset, ActivationFrame* callee_activation, const Context& entry_ctx); static RawArray* DeoptimizeToArray(Isolate* isolate, StackFrame* frame, const Code& code); static DebuggerStackTrace* CollectStackTrace(); void SignalBpResolved(SourceBreakpoint *bpt); void SignalPausedEvent(ActivationFrame* top_frame, SourceBreakpoint* 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); Isolate* isolate_; Dart_Port isolate_id_; // A unique ID for the isolate in the debugger. bool initialized_; // ID number generator. intptr_t next_id_; SourceBreakpoint* src_breakpoints_; 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 SourceBreakpoint; DISALLOW_COPY_AND_ASSIGN(Debugger); }; } // namespace dart #endif // VM_DEBUGGER_H_