From 630d6b8b71871384201a519deb399e875931ce70 Mon Sep 17 00:00:00 2001 From: "yangguo@chromium.org" Date: Wed, 5 Nov 2014 08:44:30 +0000 Subject: [PATCH] Introduce new stepping mode to step into another frame. R=aandrey@chromium.org BUG=chromium:267592 LOG=Y Review URL: https://codereview.chromium.org/690263004 Cr-Commit-Position: refs/heads/master@{#25130} git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@25130 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- src/debug-debugger.js | 3 +- src/debug.cc | 101 +++++++++++++++++++++--------------- src/debug.h | 31 +++++------ src/objects.cc | 10 ++-- src/runtime/runtime-debug.cc | 2 +- test/mjsunit/debug-stepframe.js | 111 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 195 insertions(+), 63 deletions(-) create mode 100644 test/mjsunit/debug-stepframe.js diff --git a/src/debug-debugger.js b/src/debug-debugger.js index a1468a0..fc4c5da 100644 --- a/src/debug-debugger.js +++ b/src/debug-debugger.js @@ -33,7 +33,8 @@ Debug.StepAction = { StepOut: 0, StepNext: 1, StepIn: 2, StepMin: 3, - StepInMin: 4 }; + StepInMin: 4, + StepFrame: 5 }; // The different types of scripts matching enum ScriptType in objects.h. Debug.ScriptType = { Native: 0, diff --git a/src/debug.cc b/src/debug.cc index 841b6cf..7fe9064 100644 --- a/src/debug.cc +++ b/src/debug.cc @@ -120,21 +120,37 @@ void BreakLocationIterator::Next() { DCHECK(statement_position_ >= 0); } - if (IsDebugBreakSlot()) { - // There is always a possible break point at a debug break slot. + // Check for break at return. + if (RelocInfo::IsJSReturn(rmode())) { + // Set the positions to the end of the function. + if (debug_info_->shared()->HasSourceCode()) { + position_ = debug_info_->shared()->end_position() - + debug_info_->shared()->start_position() - 1; + } else { + position_ = 0; + } + statement_position_ = position_; break_point_++; return; - } else if (RelocInfo::IsCodeTarget(rmode())) { + } + + if (RelocInfo::IsCodeTarget(rmode())) { // Check for breakable code target. Look in the original code as setting // break points can cause the code targets in the running (debugged) code // to be of a different kind than in the original code. Address target = original_rinfo()->target_address(); Code* code = Code::GetCodeFromTargetAddress(target); - if ((code->is_inline_cache_stub() && - !code->is_binary_op_stub() && - !code->is_compare_ic_stub() && - !code->is_to_boolean_ic_stub()) || - RelocInfo::IsConstructCall(rmode())) { + + if (RelocInfo::IsConstructCall(rmode()) || code->is_call_stub()) { + break_point_++; + return; + } + + // Skip below if we only want locations for calls and returns. + if (type_ == CALLS_AND_RETURNS) continue; + + if ((code->is_inline_cache_stub() && !code->is_binary_op_stub() && + !code->is_compare_ic_stub() && !code->is_to_boolean_ic_stub())) { break_point_++; return; } @@ -157,16 +173,8 @@ void BreakLocationIterator::Next() { } } - // Check for break at return. - if (RelocInfo::IsJSReturn(rmode())) { - // Set the positions to the end of the function. - if (debug_info_->shared()->HasSourceCode()) { - position_ = debug_info_->shared()->end_position() - - debug_info_->shared()->start_position() - 1; - } else { - position_ = 0; - } - statement_position_ = position_; + if (IsDebugBreakSlot() && type_ != CALLS_AND_RETURNS) { + // There is always a possible break point at a debug break slot. break_point_++; return; } @@ -1189,7 +1197,8 @@ void Debug::ClearAllBreakPoints() { } -void Debug::FloodWithOneShot(Handle function) { +void Debug::FloodWithOneShot(Handle function, + BreakLocatorType type) { PrepareForBreakPoints(); // Make sure the function is compiled and has set up the debug info. @@ -1200,7 +1209,7 @@ void Debug::FloodWithOneShot(Handle function) { } // Flood the function with break points. - BreakLocationIterator it(GetDebugInfo(shared), ALL_BREAK_LOCATIONS); + BreakLocationIterator it(GetDebugInfo(shared), type); while (!it.Done()) { it.SetOneShot(); it.Next(); @@ -1216,7 +1225,7 @@ void Debug::FloodBoundFunctionWithOneShot(Handle function) { if (!bindee.is_null() && bindee->IsJSFunction() && !JSFunction::cast(*bindee)->IsFromNativeScript()) { Handle bindee_function(JSFunction::cast(*bindee)); - Debug::FloodWithOneShot(bindee_function); + FloodWithOneShot(bindee_function); } } @@ -1296,7 +1305,7 @@ void Debug::PrepareStep(StepAction step_action, FloodHandlerWithOneShot(); // If the function on the top frame is unresolved perform step out. This will - // be the case when calling unknown functions and having the debugger stopped + // be the case when calling unknown function and having the debugger stopped // in an unhandled exception. if (!frame->function()->IsJSFunction()) { // Step out: Find the calling JavaScript frame and flood it with @@ -1354,7 +1363,7 @@ void Debug::PrepareStep(StepAction step_action, if ((maybe_call_function_stub->kind() == Code::STUB && CodeStub::GetMajorKey(maybe_call_function_stub) == CodeStub::CallFunction) || - maybe_call_function_stub->kind() == Code::CALL_IC) { + maybe_call_function_stub->is_call_stub()) { // Save reference to the code as we may need it to find out arguments // count for 'step in' later. call_function_stub = Handle(maybe_call_function_stub); @@ -1395,7 +1404,9 @@ void Debug::PrepareStep(StepAction step_action, // Step next or step min. // Fill the current function with one-shot break points. - FloodWithOneShot(function); + // If we are stepping into another frame, only fill calls and returns. + FloodWithOneShot(function, step_action == StepFrame ? CALLS_AND_RETURNS + : ALL_BREAK_LOCATIONS); // Remember source position and frame to handle step next. thread_local_.last_statement_position_ = @@ -1454,7 +1465,7 @@ void Debug::PrepareStep(StepAction step_action, if (fun->IsJSFunction()) { Handle js_function(JSFunction::cast(fun)); if (js_function->shared()->bound()) { - Debug::FloodBoundFunctionWithOneShot(js_function); + FloodBoundFunctionWithOneShot(js_function); } else if (!js_function->IsFromNativeScript()) { // Don't step into builtins. // It will also compile target function if it's not compiled yet. @@ -1467,7 +1478,9 @@ void Debug::PrepareStep(StepAction step_action, // a call target as the function called might be a native function for // which step in will not stop. It also prepares for stepping in // getters/setters. - FloodWithOneShot(function); + // If we are stepping into another frame, only fill calls and returns. + FloodWithOneShot(function, step_action == StepFrame ? CALLS_AND_RETURNS + : ALL_BREAK_LOCATIONS); if (is_load_or_store) { // Remember source position and frame to handle step in getter/setter. If @@ -1496,15 +1509,20 @@ bool Debug::StepNextContinue(BreakLocationIterator* break_location_iterator, JavaScriptFrame* frame) { // StepNext and StepOut shouldn't bring us deeper in code, so last frame // shouldn't be a parent of current frame. - if (thread_local_.last_step_action_ == StepNext || - thread_local_.last_step_action_ == StepOut) { + StepAction step_action = thread_local_.last_step_action_; + + if (step_action == StepNext || step_action == StepOut) { if (frame->fp() < thread_local_.last_fp_) return true; } + // We stepped into a new frame if the frame pointer changed. + if (step_action == StepFrame) { + return frame->UnpaddedFP() == thread_local_.last_fp_; + } + // If the step last action was step next or step in make sure that a new // statement is hit. - if (thread_local_.last_step_action_ == StepNext || - thread_local_.last_step_action_ == StepIn) { + if (step_action == StepNext || step_action == StepIn) { // Never continue if returning from function. if (break_location_iterator->IsExit()) return false; @@ -1571,10 +1589,13 @@ Handle Debug::GetSourceBreakLocations( // Handle stepping into a function. -void Debug::HandleStepIn(Handle function, - Handle holder, - Address fp, - bool is_constructor) { +void Debug::HandleStepIn(Handle function_obj, Handle holder, + Address fp, bool is_constructor) { + // Flood getter/setter if we either step in or step to another frame. + bool step_frame = thread_local_.last_step_action_ == StepFrame; + if (!StepInActive() && !step_frame) return; + if (!function_obj->IsJSFunction()) return; + Handle function = Handle::cast(function_obj); Isolate* isolate = function->GetIsolate(); // If the frame pointer is not supplied by the caller find it. if (fp == 0) { @@ -1589,11 +1610,11 @@ void Debug::HandleStepIn(Handle function, } // Flood the function with one-shot break points if it is called from where - // step into was requested. - if (fp == thread_local_.step_into_fp_) { + // step into was requested, or when stepping into a new frame. + if (fp == thread_local_.step_into_fp_ || step_frame) { if (function->shared()->bound()) { // Handle Function.prototype.bind - Debug::FloodBoundFunctionWithOneShot(function); + FloodBoundFunctionWithOneShot(function); } else if (!function->IsFromNativeScript()) { // Don't allow step into functions in the native context. if (function->shared()->code() == @@ -1607,14 +1628,14 @@ void Debug::HandleStepIn(Handle function, if (!holder.is_null() && holder->IsJSFunction()) { Handle js_function = Handle::cast(holder); if (!js_function->IsFromNativeScript()) { - Debug::FloodWithOneShot(js_function); + FloodWithOneShot(js_function); } else if (js_function->shared()->bound()) { // Handle Function.prototype.bind - Debug::FloodBoundFunctionWithOneShot(js_function); + FloodBoundFunctionWithOneShot(js_function); } } } else { - Debug::FloodWithOneShot(function); + FloodWithOneShot(function); } } } diff --git a/src/debug.h b/src/debug.h index 2afe0f6..3bfa02f 100644 --- a/src/debug.h +++ b/src/debug.h @@ -32,13 +32,14 @@ class DebugScope; // Step actions. NOTE: These values are in macros.py as well. enum StepAction { StepNone = -1, // Stepping not prepared. - StepOut = 0, // Step out of the current function. - StepNext = 1, // Step to the next statement in the current function. - StepIn = 2, // Step into new functions invoked or the next statement - // in the current function. - StepMin = 3, // Perform a minimum step in the current function. - StepInMin = 4 // Step into new functions invoked or perform a minimum step - // in the current function. + StepOut = 0, // Step out of the current function. + StepNext = 1, // Step to the next statement in the current function. + StepIn = 2, // Step into new functions invoked or the next statement + // in the current function. + StepMin = 3, // Perform a minimum step in the current function. + StepInMin = 4, // Step into new functions invoked or perform a minimum step + // in the current function. + StepFrame = 5 // Step into a new frame or return to previous frame. }; @@ -49,10 +50,11 @@ enum ExceptionBreakType { }; -// Type of exception break. NOTE: These values are in macros.py as well. +// Type of exception break. enum BreakLocatorType { ALL_BREAK_LOCATIONS = 0, - SOURCE_BREAK_LOCATIONS = 1 + SOURCE_BREAK_LOCATIONS = 1, + CALLS_AND_RETURNS = 2, }; @@ -385,7 +387,8 @@ class Debug { BreakPositionAlignment alignment); void ClearBreakPoint(Handle break_point_object); void ClearAllBreakPoints(); - void FloodWithOneShot(Handle function); + void FloodWithOneShot(Handle function, + BreakLocatorType type = ALL_BREAK_LOCATIONS); void FloodBoundFunctionWithOneShot(Handle function); void FloodHandlerWithOneShot(); void ChangeBreakOnException(ExceptionBreakType type, bool enable); @@ -401,10 +404,8 @@ class Debug { bool StepNextContinue(BreakLocationIterator* break_location_iterator, JavaScriptFrame* frame); bool StepInActive() { return thread_local_.step_into_fp_ != 0; } - void HandleStepIn(Handle function, - Handle holder, - Address fp, - bool is_constructor); + void HandleStepIn(Handle function_obj, Handle holder, + Address fp, bool is_constructor); bool StepOutActive() { return thread_local_.step_out_fp_ != 0; } // Purge all code objects that have no debug break slots. @@ -623,7 +624,7 @@ class Debug { // Number of steps left to perform before debug event. int step_count_; - // Frame pointer from last step next action. + // Frame pointer from last step next or step frame action. Address last_fp_; // Number of queued steps left to perform before debug event. diff --git a/src/objects.cc b/src/objects.cc index 9933e9c..7c2a89f 100644 --- a/src/objects.cc +++ b/src/objects.cc @@ -542,9 +542,8 @@ MaybeHandle Object::GetPropertyWithDefinedGetter( Debug* debug = isolate->debug(); // Handle stepping into a getter if step into is active. // TODO(rossberg): should this apply to getters that are function proxies? - if (debug->StepInActive() && getter->IsJSFunction()) { - debug->HandleStepIn( - Handle::cast(getter), Handle::null(), 0, false); + if (debug->is_active()) { + debug->HandleStepIn(getter, Handle::null(), 0, false); } return Execution::Call(isolate, getter, receiver, 0, NULL, true); @@ -560,9 +559,8 @@ MaybeHandle Object::SetPropertyWithDefinedSetter( Debug* debug = isolate->debug(); // Handle stepping into a setter if step into is active. // TODO(rossberg): should this apply to getters that are function proxies? - if (debug->StepInActive() && setter->IsJSFunction()) { - debug->HandleStepIn( - Handle::cast(setter), Handle::null(), 0, false); + if (debug->is_active()) { + debug->HandleStepIn(setter, Handle::null(), 0, false); } Handle argv[] = { value }; diff --git a/src/runtime/runtime-debug.cc b/src/runtime/runtime-debug.cc index 2de372f..95ac77b 100644 --- a/src/runtime/runtime-debug.cc +++ b/src/runtime/runtime-debug.cc @@ -1984,7 +1984,7 @@ RUNTIME_FUNCTION(Runtime_PrepareStep) { StepAction step_action = static_cast(NumberToInt32(args[1])); if (step_action != StepIn && step_action != StepNext && step_action != StepOut && step_action != StepInMin && - step_action != StepMin) { + step_action != StepMin && step_action != StepFrame) { return isolate->Throw(isolate->heap()->illegal_argument_string()); } diff --git a/test/mjsunit/debug-stepframe.js b/test/mjsunit/debug-stepframe.js new file mode 100644 index 0000000..8f4ee4c --- /dev/null +++ b/test/mjsunit/debug-stepframe.js @@ -0,0 +1,111 @@ +// Copyright 2014 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flags: --expose-debug-as debug + +function f0() { + var v00 = 0; // Break 1 + var v01 = 1; + // Normal function call in a catch scope. + try { + throw 1; + } catch (e) { + try{ + f1(); + } catch (e) { + var v02 = 2; // Break 13 + } + } + var v03 = 3; + var v04 = 4; +} + +function f1() { + var v10 = 0; // Break 2 + var v11 = 1; + // Getter call. + var v12 = o.get; + var v13 = 3 // Break 4 + // Setter call. + o.set = 2; + var v14 = 4; // Break 6 + // Function.prototype.call. + f2.call(); + var v15 = 5; // Break 12 + var v16 = 6; + // Exit function by throw. + throw 1; + var v17 = 7; +} + +function get() { + var g0 = 0; // Break 3 + var g1 = 1; + return 3; +} + +function set() { + var s0 = 0; // Break 5 + return 3; +} + +function f2() { + var v20 = 0; // Break 7 + // Construct call. + var v21 = new c0(); + var v22 = 2; // Break 9 + // Bound function. + b0(); + return 2; // Break 11 +} + +function c0() { + this.v0 = 0; // Break 8 + this.v1 = 1; +} + +function f3() { + var v30 = 0; // Break 10 + var v31 = 1; + return 3; +} + +var b0 = f3.bind(o); + +var o = {}; +Object.defineProperty(o, "get", { get : get }); +Object.defineProperty(o, "set", { set : set }); + +Debug = debug.Debug; +var break_count = 0 +var exception = null; +var step_size; + +function listener(event, exec_state, event_data, data) { + if (event != Debug.DebugEvent.Break) return; + try { + var line = exec_state.frame(0).sourceLineText(); + print(line); + var match = line.match(/\/\/ Break (\d+)$/); + assertEquals(2, match.length); + assertEquals(break_count, parseInt(match[1])); + break_count += step_size; + exec_state.prepareStep(Debug.StepAction.StepFrame, step_size); + } catch (e) { + print(e + e.stack); + exception = e; + } +} + +for (step_size = 1; step_size < 6; step_size++) { + print("step size = " + step_size); + break_count = 0; + Debug.setListener(listener); + debugger; // Break 0 + f0(); + Debug.setListener(null); // Break 14 + assertTrue(break_count > 14); +} + +assertNull(exception); -- 2.7.4