Index: runtime/vm/debugger.cc |
diff --git a/runtime/vm/debugger.cc b/runtime/vm/debugger.cc |
index 3cdf810c8d678527f68328de0fa065be6d01b0e1..5c54271bc56aefaeca52be2ef11e299e17a169e9 100644 |
--- a/runtime/vm/debugger.cc |
+++ b/runtime/vm/debugger.cc |
@@ -6,6 +6,8 @@ |
#include "include/dart_api.h" |
+#include "platform/address_sanitizer.h" |
+ |
#include "vm/code_generator.h" |
#include "vm/code_patcher.h" |
#include "vm/compiler.h" |
@@ -42,6 +44,7 @@ DEFINE_FLAG(bool, |
trace_debugger_stacktrace, |
false, |
"Trace debugger stacktrace collection"); |
+DEFINE_FLAG(bool, trace_rewind, false, "Trace frame rewind"); |
DEFINE_FLAG(bool, verbose_debug, false, "Verbose debugger messages"); |
DEFINE_FLAG(bool, |
steal_breakpoints, |
@@ -850,6 +853,24 @@ RawObject* ActivationFrame::GetStackVar(intptr_t slot_index) { |
} |
+bool ActivationFrame::IsRewindable() const { |
+ if (deopt_frame_.IsNull()) { |
+ return true; |
+ } |
+ // TODO(turnidge): This is conservative. It looks at all values in |
+ // the deopt_frame_ even though some of them may correspond to other |
+ // inlined frames. |
+ Object& obj = Object::Handle(); |
+ for (int i = 0; i < deopt_frame_.Length(); i++) { |
+ obj = deopt_frame_.At(i); |
+ if (obj.raw() == Symbols::OptimizedOut().raw()) { |
+ return false; |
+ } |
+ } |
+ return true; |
+} |
+ |
+ |
void ActivationFrame::PrintContextMismatchError(intptr_t ctx_slot, |
intptr_t frame_ctx_level, |
intptr_t var_ctx_level) { |
@@ -1108,9 +1129,13 @@ void ActivationFrame::PrintToJSONObject(JSONObject* jsobj, bool full) { |
} |
} |
+static bool IsFunctionVisible(const Function& function) { |
+ return FLAG_show_invisible_frames || function.is_visible(); |
+} |
+ |
void DebuggerStackTrace::AddActivation(ActivationFrame* frame) { |
- if (FLAG_show_invisible_frames || frame->function().is_visible()) { |
+ if (IsFunctionVisible(frame->function())) { |
trace_.Add(frame); |
} |
} |
@@ -1237,6 +1262,8 @@ Debugger::Debugger() |
breakpoint_locations_(NULL), |
code_breakpoints_(NULL), |
resume_action_(kContinue), |
+ resume_frame_index_(-1), |
+ post_deopt_frame_index_(-1), |
ignore_breakpoints_(false), |
pause_event_(NULL), |
obj_cache_(NULL), |
@@ -1300,10 +1327,13 @@ static RawFunction* ResolveLibraryFunction(const Library& library, |
} |
-bool Debugger::SetupStepOverAsyncSuspension() { |
+bool Debugger::SetupStepOverAsyncSuspension(const char** error) { |
ActivationFrame* top_frame = TopDartFrame(); |
if (!IsAtAsyncJump(top_frame)) { |
// Not at an async operation. |
+ if (error) { |
+ *error = "Isolate must be paused at an async suspension point"; |
+ } |
return false; |
} |
Object& closure = Object::Handle(top_frame->GetAsyncOperation()); |
@@ -1313,24 +1343,42 @@ bool Debugger::SetupStepOverAsyncSuspension() { |
Breakpoint* bpt = SetBreakpointAtActivation(Instance::Cast(closure), true); |
if (bpt == NULL) { |
// Unable to set the breakpoint. |
+ if (error) { |
+ *error = "Unable to set breakpoint at async suspension point"; |
+ } |
return false; |
} |
return true; |
} |
-void Debugger::SetSingleStep() { |
- resume_action_ = kSingleStep; |
-} |
- |
- |
-void Debugger::SetStepOver() { |
- resume_action_ = kStepOver; |
-} |
- |
- |
-void Debugger::SetStepOut() { |
- resume_action_ = kStepOut; |
+bool Debugger::SetResumeAction(ResumeAction action, |
+ intptr_t frame_index, |
+ const char** error) { |
+ if (error) { |
+ *error = NULL; |
+ } |
+ resume_frame_index_ = -1; |
+ switch (action) { |
+ case kStepInto: |
+ case kStepOver: |
+ case kStepOut: |
+ case kContinue: |
+ resume_action_ = action; |
+ return true; |
+ case kStepRewind: |
+ if (!CanRewindFrame(frame_index, error)) { |
+ return false; |
+ } |
+ resume_action_ = kStepRewind; |
+ resume_frame_index_ = frame_index; |
+ return true; |
+ case kStepOverAsyncSuspension: |
+ return SetupStepOverAsyncSuspension(error); |
+ default: |
+ UNREACHABLE(); |
+ return false; |
+ } |
} |
@@ -1622,6 +1670,7 @@ void Debugger::PauseException(const Instance& exc) { |
ASSERT(stack_trace_ == NULL); |
stack_trace_ = stack_trace; |
Pause(&event); |
+ HandleSteppingRequest(stack_trace_); // we may get a rewind request |
stack_trace_ = NULL; |
} |
@@ -2547,7 +2596,7 @@ void Debugger::EnterSingleStepMode() { |
void Debugger::HandleSteppingRequest(DebuggerStackTrace* stack_trace, |
bool skip_next_step) { |
stepping_fp_ = 0; |
- if (resume_action_ == kSingleStep) { |
+ if (resume_action_ == kStepInto) { |
// When single stepping, we need to deoptimize because we might be |
// stepping into optimized code. This happens in particular if |
// the isolate has been interrupted, but can happen in other cases |
@@ -2557,7 +2606,7 @@ void Debugger::HandleSteppingRequest(DebuggerStackTrace* stack_trace, |
isolate_->set_single_step(true); |
skip_next_step_ = skip_next_step; |
if (FLAG_verbose_debug) { |
- OS::Print("HandleSteppingRequest- kSingleStep\n"); |
+ OS::Print("HandleSteppingRequest- kStepInto\n"); |
} |
} else if (resume_action_ == kStepOver) { |
DeoptimizeWorld(); |
@@ -2582,6 +2631,244 @@ void Debugger::HandleSteppingRequest(DebuggerStackTrace* stack_trace, |
if (FLAG_verbose_debug) { |
OS::Print("HandleSteppingRequest- kStepOut %" Px "\n", stepping_fp_); |
} |
+ } else if (resume_action_ == kStepRewind) { |
+ if (FLAG_trace_rewind) { |
+ OS::PrintErr("Rewinding to frame %" Pd "\n", resume_frame_index_); |
+ OS::PrintErr( |
+ "-------------------------\n" |
+ "All frames...\n\n"); |
+ StackFrameIterator iterator(false); |
+ StackFrame* frame = iterator.NextFrame(); |
+ intptr_t num = 0; |
+ while ((frame != NULL)) { |
+ OS::PrintErr("#%04" Pd " %s\n", num++, frame->ToCString()); |
+ frame = iterator.NextFrame(); |
+ } |
+ } |
+ RewindToFrame(resume_frame_index_); |
+ UNREACHABLE(); |
+ } |
+} |
+ |
+ |
+static intptr_t FindNextRewindFrameIndex(DebuggerStackTrace* stack, |
+ intptr_t frame_index) { |
+ for (intptr_t i = frame_index + 1; i < stack->Length(); i++) { |
+ ActivationFrame* frame = stack->FrameAt(i); |
+ if (frame->IsRewindable()) { |
+ return i; |
+ } |
+ } |
+ return -1; |
+} |
+ |
+ |
+// Can the top frame be rewound? |
+bool Debugger::CanRewindFrame(intptr_t frame_index, const char** error) const { |
+ // check rewind pc is found |
+ DebuggerStackTrace* stack = Isolate::Current()->debugger()->StackTrace(); |
+ intptr_t num_frames = stack->Length(); |
+ if (frame_index < 1 || frame_index >= num_frames) { |
+ if (error) { |
+ *error = Thread::Current()->zone()->PrintToString( |
+ "Frame must be in bounds [1..%" Pd |
+ "]: " |
+ "saw %" Pd "", |
+ num_frames - 1, frame_index); |
+ } |
+ return false; |
+ } |
+ ActivationFrame* frame = stack->FrameAt(frame_index); |
+ if (!frame->IsRewindable()) { |
+ intptr_t next_index = FindNextRewindFrameIndex(stack, frame_index); |
+ if (next_index > 0) { |
+ *error = Thread::Current()->zone()->PrintToString( |
+ "Cannot rewind to frame %" Pd |
+ " due to conflicting compiler " |
+ "optimizations. " |
+ "Run the vm with --no-prune-dead-locals to disallow these " |
+ "optimizations. " |
+ "Next valid rewind frame is %" Pd ".", |
+ frame_index, next_index); |
+ } else { |
+ *error = Thread::Current()->zone()->PrintToString( |
+ "Cannot rewind to frame %" Pd |
+ " due to conflicting compiler " |
+ "optimizations. " |
+ "Run the vm with --no-prune-dead-locals to disallow these " |
+ "optimizations.", |
+ frame_index); |
+ } |
+ return false; |
+ } |
+ return true; |
+} |
+ |
+ |
+// Given a return address pc, find the "rewind" pc, which is the pc |
+// before the corresponding call. |
+static uword LookupRewindPc(const Code& code, uword pc) { |
+ ASSERT(!code.is_optimized()); |
+ ASSERT(code.ContainsInstructionAt(pc)); |
+ |
+ uword pc_offset = pc - code.PayloadStart(); |
+ const PcDescriptors& descriptors = |
+ PcDescriptors::Handle(code.pc_descriptors()); |
+ PcDescriptors::Iterator iter( |
+ descriptors, RawPcDescriptors::kRewind | RawPcDescriptors::kIcCall | |
+ RawPcDescriptors::kUnoptStaticCall); |
+ intptr_t rewind_deopt_id = -1; |
+ uword rewind_pc = 0; |
+ while (iter.MoveNext()) { |
+ if (iter.Kind() == RawPcDescriptors::kRewind) { |
+ // Remember the last rewind so we don't need to iterator twice. |
+ rewind_pc = code.PayloadStart() + iter.PcOffset(); |
+ rewind_deopt_id = iter.DeoptId(); |
+ } |
+ if ((pc_offset == iter.PcOffset()) && (iter.DeoptId() == rewind_deopt_id)) { |
+ return rewind_pc; |
+ } |
+ } |
+ return 0; |
+} |
+ |
+ |
+void Debugger::RewindToFrame(intptr_t frame_index) { |
+ Thread* thread = Thread::Current(); |
+ Zone* zone = thread->zone(); |
+ Code& code = Code::Handle(zone); |
+ Function& function = Function::Handle(zone); |
+ |
+ // Find the requested frame. |
+ StackFrameIterator iterator(false); |
+ intptr_t current_frame = 0; |
+ for (StackFrame* frame = iterator.NextFrame(); frame != NULL; |
+ frame = iterator.NextFrame()) { |
+ ASSERT(frame->IsValid()); |
+ if (frame->IsDartFrame()) { |
+ code = frame->LookupDartCode(); |
+ function = code.function(); |
+ if (!IsFunctionVisible(function)) { |
+ continue; |
+ } |
+ if (code.is_optimized()) { |
+ intptr_t sub_index = 0; |
+ for (InlinedFunctionsIterator it(code, frame->pc()); !it.Done(); |
+ it.Advance()) { |
+ if (current_frame == frame_index) { |
+ RewindToOptimizedFrame(frame, code, sub_index); |
+ UNREACHABLE(); |
+ } |
+ current_frame++; |
+ sub_index++; |
+ } |
+ } else { |
+ if (current_frame == frame_index) { |
+ // We are rewinding to an unoptimized frame. |
+ RewindToUnoptimizedFrame(frame, code); |
+ UNREACHABLE(); |
+ } |
+ current_frame++; |
+ } |
+ } |
+ } |
+ UNIMPLEMENTED(); |
+} |
+ |
+ |
+void Debugger::RewindToUnoptimizedFrame(StackFrame* frame, const Code& code) { |
+ // We will be jumping out of the debugger rather than exiting this |
+ // function, so prepare the debugger state. |
+ stack_trace_ = NULL; |
+ resume_action_ = kContinue; |
+ resume_frame_index_ = -1; |
+ EnterSingleStepMode(); |
+ |
+ uword rewind_pc = LookupRewindPc(code, frame->pc()); |
+ if (FLAG_trace_rewind && rewind_pc == 0) { |
+ OS::PrintErr("Unable to find rewind pc for pc(%" Px ")\n", frame->pc()); |
+ } |
+ ASSERT(rewind_pc != 0); |
+ if (FLAG_trace_rewind) { |
+ OS::PrintErr( |
+ "===============================\n" |
+ "Rewinding to unoptimized frame:\n" |
+ " rewind_pc(0x%" Px ") sp(0x%" Px ") fp(0x%" Px |
+ ")\n" |
+ "===============================\n", |
+ rewind_pc, frame->sp(), frame->fp()); |
+ } |
+ Exceptions::JumpToFrame(Thread::Current(), rewind_pc, frame->sp(), |
+ frame->fp(), true /* clear lazy deopt at target */); |
+ UNREACHABLE(); |
+} |
+ |
+ |
+void Debugger::RewindToOptimizedFrame(StackFrame* frame, |
+ const Code& optimized_code, |
+ intptr_t sub_index) { |
+ post_deopt_frame_index_ = sub_index; |
+ |
+ // We will be jumping out of the debugger rather than exiting this |
+ // function, so prepare the debugger state. |
+ stack_trace_ = NULL; |
+ resume_action_ = kContinue; |
+ resume_frame_index_ = -1; |
+ EnterSingleStepMode(); |
+ |
+ if (FLAG_trace_rewind) { |
+ OS::PrintErr( |
+ "===============================\n" |
+ "Deoptimizing frame for rewind:\n" |
+ " deopt_pc(0x%" Px ") sp(0x%" Px ") fp(0x%" Px |
+ ")\n" |
+ "===============================\n", |
+ frame->pc(), frame->sp(), frame->fp()); |
+ } |
+ Thread* thread = Thread::Current(); |
+ thread->set_resume_pc(frame->pc()); |
+ uword deopt_stub_pc = StubCode::DeoptForRewind_entry()->EntryPoint(); |
+ Exceptions::JumpToFrame(thread, deopt_stub_pc, frame->sp(), frame->fp(), |
+ true /* clear lazy deopt at target */); |
+ UNREACHABLE(); |
+} |
+ |
+ |
+void Debugger::RewindPostDeopt() { |
+ intptr_t rewind_frame = post_deopt_frame_index_; |
+ post_deopt_frame_index_ = -1; |
+ if (FLAG_trace_rewind) { |
+ OS::PrintErr("Post deopt, jumping to frame %" Pd "\n", rewind_frame); |
+ OS::PrintErr( |
+ "-------------------------\n" |
+ "All frames...\n\n"); |
+ StackFrameIterator iterator(false); |
+ StackFrame* frame = iterator.NextFrame(); |
+ intptr_t num = 0; |
+ while ((frame != NULL)) { |
+ OS::PrintErr("#%04" Pd " %s\n", num++, frame->ToCString()); |
+ frame = iterator.NextFrame(); |
+ } |
+ } |
+ |
+ Thread* thread = Thread::Current(); |
+ Zone* zone = thread->zone(); |
+ Code& code = Code::Handle(zone); |
+ |
+ StackFrameIterator iterator(false); |
+ intptr_t current_frame = 0; |
+ for (StackFrame* frame = iterator.NextFrame(); frame != NULL; |
+ frame = iterator.NextFrame()) { |
+ ASSERT(frame->IsValid()); |
+ if (frame->IsDartFrame()) { |
+ code = frame->LookupDartCode(); |
+ ASSERT(!code.is_optimized()); |
+ if (current_frame == rewind_frame) { |
+ RewindToUnoptimizedFrame(frame, code); |
+ UNREACHABLE(); |
+ } |
+ current_frame++; |
+ } |
} |
} |
@@ -2793,7 +3080,7 @@ RawError* Debugger::PauseBreakpoint() { |
// We are at the entry of an async function. |
// We issue a step over to resume at the point after the await statement. |
- SetStepOver(); |
+ SetResumeAction(kStepOver); |
// When we single step from a user breakpoint, our next stepping |
// point will be at the exact same pc. Skip it. |
HandleSteppingRequest(stack_trace_, true /* skip next step */); |
@@ -2846,7 +3133,7 @@ void Debugger::PauseDeveloper(const String& msg) { |
// We are in the native call to Developer_debugger. the developer |
// gets a better experience by not seeing this call. To accomplish |
// this, we continue execution until the call exits (step out). |
- SetStepOut(); |
+ SetResumeAction(kStepOut); |
HandleSteppingRequest(stack_trace_); |
stack_trace_ = NULL; |