57b27a7b44
- Updated conditional compilation flags throughout the runtime codebase to transition from DART_DYNAMIC_MODULES to DART_BYTECODE_INTERPRETER. - Adjusted logic in various files including object_graph_copy.cc, object_reload.cc, profiler.cc, and others to ensure compatibility with the new interpreter model. - Ensured that all references to dynamic modules are replaced with bytecode interpreter checks, maintaining functionality for interpreted code execution. - Modified stack frame handling and service-related code to align with the new interpreter architecture. - Updated tests and service implementations to reflect the changes in the runtime environment. Signed-off-by: Tony <tonylu@tony-cloud.com>
1059 lines
37 KiB
C++
1059 lines
37 KiB
C++
// 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_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<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);
|
|
|
|
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_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<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_
|