Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(183)

Unified Diff: runtime/vm/debugger.cc

Issue 2523053002: Implement rewind: drop one or more frames from the debugger. (Closed)
Patch Set: code review Created 4 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « runtime/vm/debugger.h ('k') | runtime/vm/debugger_api_impl.cc » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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;
« no previous file with comments | « runtime/vm/debugger.h ('k') | runtime/vm/debugger_api_impl.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698