Support 'restart call frame' debug command

Review URL: https://chromiumcodereview.appspot.com/10544151

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@11836 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
peter.rybin@gmail.com 2012-06-15 16:52:03 +00:00
parent b072e6156b
commit bdfc48a3fd
9 changed files with 343 additions and 21 deletions

View File

@ -1449,6 +1449,8 @@ DebugCommandProcessor.prototype.processDebugJSONRequest = function(
this.profileRequest_(request, response);
} else if (request.command == 'changelive') {
this.changeLiveRequest_(request, response);
} else if (request.command == 'restartframe') {
this.restartFrameRequest_(request, response);
} else if (request.command == 'flags') {
this.debuggerFlagsRequest_(request, response);
} else if (request.command == 'v8flags') {
@ -2358,9 +2360,6 @@ DebugCommandProcessor.prototype.profileRequest_ = function(request, response) {
DebugCommandProcessor.prototype.changeLiveRequest_ = function(
request, response) {
if (!Debug.LiveEdit) {
return response.failed('LiveEdit feature is not supported');
}
if (!request.arguments) {
return response.failed('Missing arguments');
}
@ -2398,6 +2397,37 @@ DebugCommandProcessor.prototype.changeLiveRequest_ = function(
};
DebugCommandProcessor.prototype.restartFrameRequest_ = function(
request, response) {
if (!request.arguments) {
return response.failed('Missing arguments');
}
var frame = request.arguments.frame;
// No frames to evaluate in frame.
if (this.exec_state_.frameCount() == 0) {
return response.failed('No frames');
}
var frame_mirror;
// Check whether a frame was specified.
if (!IS_UNDEFINED(frame)) {
var frame_number = %ToNumber(frame);
if (frame_number < 0 || frame_number >= this.exec_state_.frameCount()) {
return response.failed('Invalid frame "' + frame + '"');
}
// Restart specified frame.
frame_mirror = this.exec_state_.frame(frame_number);
} else {
// Restart selected frame.
frame_mirror = this.exec_state_.frame();
}
var result_description = Debug.LiveEdit.RestartFrame(frame_mirror);
response.body = {result: result_description};
};
DebugCommandProcessor.prototype.debuggerFlagsRequest_ = function(request,
response) {
// Check for legal request.

View File

@ -1,4 +1,4 @@
// Copyright 2010 the V8 project authors. All rights reserved.
// Copyright 2012 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
@ -47,6 +47,8 @@ Debug.LiveEdit = new function() {
// Forward declaration for minifier.
var FunctionStatus;
var NEEDS_STEP_IN_PROPERTY_NAME = "stack_update_needs_step_in";
// Applies the change to the script.
// The change is in form of list of chunks encoded in a single array as
// a series of triplets (pos1_start, pos1_end, pos2_end)
@ -161,7 +163,7 @@ Debug.LiveEdit = new function() {
// Our current implementation requires client to manually issue "step in"
// command for correct stack state.
preview_description.stack_update_needs_step_in =
preview_description[NEEDS_STEP_IN_PROPERTY_NAME] =
preview_description.stack_modified;
// Start with breakpoints. Convert their line/column positions and
@ -1078,6 +1080,18 @@ Debug.LiveEdit = new function() {
return ProcessOldNode(old_code_tree);
}
// Restarts call frame and returns value similar to what LiveEdit returns.
function RestartFrame(frame_mirror) {
var result = frame_mirror.restart();
if (IS_STRING(result)) {
throw new Failure("Failed to restart frame: " + result);
}
var result = {};
result[NEEDS_STEP_IN_PROPERTY_NAME] = true;
return result;
}
// Function is public.
this.RestartFrame = RestartFrame;
// Functions are public for tests.
this.TestApi = {

View File

@ -1595,17 +1595,36 @@ static bool IsDropableFrame(StackFrame* frame) {
return !frame->is_exit();
}
// Fills result array with statuses of functions. Modifies the stack
// removing all listed function if possible and if do_drop is true.
static const char* DropActivationsInActiveThread(
Handle<JSArray> shared_info_array, Handle<JSArray> result, bool do_drop,
Zone* zone) {
// Describes a set of call frames that execute any of listed functions.
// Finding no such frames does not mean error.
class MultipleFunctionTarget {
public:
MultipleFunctionTarget(Handle<JSArray> shared_info_array,
Handle<JSArray> result)
: m_shared_info_array(shared_info_array),
m_result(result) {}
bool MatchActivation(StackFrame* frame,
LiveEdit::FunctionPatchabilityStatus status) {
return CheckActivation(m_shared_info_array, m_result, frame, status);
}
const char* GetNotFoundMessage() {
return NULL;
}
private:
Handle<JSArray> m_shared_info_array;
Handle<JSArray> m_result;
};
// Drops all call frame matched by target and all frames above them.
template<typename TARGET>
static const char* DropActivationsInActiveThreadImpl(
TARGET& target, bool do_drop, Zone* zone) {
Isolate* isolate = Isolate::Current();
Debug* debug = isolate->debug();
ZoneScope scope(isolate, DELETE_ON_EXIT);
Vector<StackFrame*> frames = CreateStackMap(zone);
int array_len = Smi::cast(shared_info_array->length())->value();
int top_frame_index = -1;
int frame_index = 0;
@ -1615,8 +1634,8 @@ static const char* DropActivationsInActiveThread(
top_frame_index = frame_index;
break;
}
if (CheckActivation(shared_info_array, result, frame,
LiveEdit::FUNCTION_BLOCKED_UNDER_NATIVE_CODE)) {
if (target.MatchActivation(
frame, LiveEdit::FUNCTION_BLOCKED_UNDER_NATIVE_CODE)) {
// We are still above break_frame. It is not a target frame,
// it is a problem.
return "Debugger mark-up on stack is not found";
@ -1625,7 +1644,7 @@ static const char* DropActivationsInActiveThread(
if (top_frame_index == -1) {
// We haven't found break frame, but no function is blocking us anyway.
return NULL;
return target.GetNotFoundMessage();
}
bool target_frame_found = false;
@ -1638,8 +1657,8 @@ static const char* DropActivationsInActiveThread(
c_code_found = true;
break;
}
if (CheckActivation(shared_info_array, result, frame,
LiveEdit::FUNCTION_BLOCKED_ON_ACTIVE_STACK)) {
if (target.MatchActivation(
frame, LiveEdit::FUNCTION_BLOCKED_ON_ACTIVE_STACK)) {
target_frame_found = true;
bottom_js_frame_index = frame_index;
}
@ -1651,8 +1670,8 @@ static const char* DropActivationsInActiveThread(
for (; frame_index < frames.length(); frame_index++) {
StackFrame* frame = frames[frame_index];
if (frame->is_java_script()) {
if (CheckActivation(shared_info_array, result, frame,
LiveEdit::FUNCTION_BLOCKED_UNDER_NATIVE_CODE)) {
if (target.MatchActivation(
frame, LiveEdit::FUNCTION_BLOCKED_UNDER_NATIVE_CODE)) {
// Cannot drop frame under C frames.
return NULL;
}
@ -1667,7 +1686,7 @@ static const char* DropActivationsInActiveThread(
if (!target_frame_found) {
// Nothing to drop.
return NULL;
return target.GetNotFoundMessage();
}
Debug::FrameDropMode drop_mode = Debug::FRAMES_UNTOUCHED;
@ -1690,6 +1709,23 @@ static const char* DropActivationsInActiveThread(
}
debug->FramesHaveBeenDropped(new_id, drop_mode,
restarter_frame_function_pointer);
return NULL;
}
// Fills result array with statuses of functions. Modifies the stack
// removing all listed function if possible and if do_drop is true.
static const char* DropActivationsInActiveThread(
Handle<JSArray> shared_info_array, Handle<JSArray> result, bool do_drop,
Zone* zone) {
MultipleFunctionTarget target(shared_info_array, result);
const char* message =
DropActivationsInActiveThreadImpl(target, do_drop, zone);
if (message) {
return message;
}
int array_len = Smi::cast(shared_info_array->length())->value();
// Replace "blocked on active" with "replaced on active" status.
for (int i = 0; i < array_len; i++) {
@ -1766,6 +1802,41 @@ Handle<JSArray> LiveEdit::CheckAndDropActivations(
}
// Describes a single callframe a target. Not finding this frame
// means an error.
class SingleFrameTarget {
public:
explicit SingleFrameTarget(JavaScriptFrame* frame) : m_frame(frame) {}
bool MatchActivation(StackFrame* frame,
LiveEdit::FunctionPatchabilityStatus status) {
if (frame->fp() == m_frame->fp()) {
m_saved_status = status;
return true;
}
return false;
}
const char* GetNotFoundMessage() {
return "Failed to found requested frame";
}
LiveEdit::FunctionPatchabilityStatus saved_status() {
return m_saved_status;
}
private:
JavaScriptFrame* m_frame;
LiveEdit::FunctionPatchabilityStatus m_saved_status;
};
// Finds a drops required frame and all frames above.
// Returns error message or NULL.
const char* LiveEdit::RestartFrame(JavaScriptFrame* frame, Zone* zone) {
SingleFrameTarget target(frame);
return DropActivationsInActiveThreadImpl(target, true, zone);
}
LiveEditFunctionTracker::LiveEditFunctionTracker(Isolate* isolate,
FunctionLiteral* fun)
: isolate_(isolate) {

View File

@ -1,4 +1,4 @@
// Copyright 2010 the V8 project authors. All rights reserved.
// Copyright 2012 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
@ -123,6 +123,10 @@ class LiveEdit : AllStatic {
static Handle<JSArray> CheckAndDropActivations(
Handle<JSArray> shared_info_array, bool do_drop, Zone* zone);
// Restarts the call frame and completely drops all frames above it.
// Return error message or NULL.
static const char* RestartFrame(JavaScriptFrame* frame, Zone* zone);
// A copy of this is in liveedit-debugger.js.
enum FunctionPatchabilityStatus {
FUNCTION_AVAILABLE_FOR_PATCH = 1,

View File

@ -1,4 +1,4 @@
// Copyright 2006-2008 the V8 project authors. All rights reserved.
// Copyright 2006-2012 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
@ -1750,6 +1750,15 @@ FrameMirror.prototype.localsText = function() {
};
FrameMirror.prototype.restart = function() {
var result = %LiveEditRestartFrame(this.break_id_, this.index_);
if (IS_UNDEFINED(result)) {
result = "Failed to find requested frame";
}
return result;
};
FrameMirror.prototype.toText = function(opt_locals) {
var result = '';
result += '#' + (this.index() <= 9 ? '0' : '') + this.index();

View File

@ -12789,6 +12789,45 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_LiveEditCompareStrings) {
}
// Restarts a call frame and completely drops all frames above.
// Returns true if successful. Otherwise returns undefined or an error message.
RUNTIME_FUNCTION(MaybeObject*, Runtime_LiveEditRestartFrame) {
HandleScope scope(isolate);
ASSERT(args.length() == 2);
// Check arguments.
Object* check;
{ MaybeObject* maybe_check = Runtime_CheckExecutionState(
RUNTIME_ARGUMENTS(isolate, args));
if (!maybe_check->ToObject(&check)) return maybe_check;
}
CONVERT_NUMBER_CHECKED(int, index, Int32, args[1]);
Heap* heap = isolate->heap();
// Find the relevant frame with the requested index.
StackFrame::Id id = isolate->debug()->break_frame_id();
if (id == StackFrame::NO_ID) {
// If there are no JavaScript stack frames return undefined.
return heap->undefined_value();
}
int count = 0;
JavaScriptFrameIterator it(isolate, id);
for (; !it.done(); it.Advance()) {
if (index < count + it.frame()->GetInlineCount()) break;
count += it.frame()->GetInlineCount();
}
if (it.done()) return heap->undefined_value();
const char* error_message =
LiveEdit::RestartFrame(it.frame(), isolate->zone());
if (error_message) {
return *(isolate->factory()->LookupAsciiSymbol(error_message));
}
return heap->true_value();
}
// A testing entry. Returns statement position which is the closest to
// source_position.
RUNTIME_FUNCTION(MaybeObject*, Runtime_GetFunctionCodePositionFromSource) {

View File

@ -442,6 +442,7 @@ namespace internal {
F(LiveEditPatchFunctionPositions, 2, 1) \
F(LiveEditCheckAndDropActivations, 2, 1) \
F(LiveEditCompareStrings, 2, 1) \
F(LiveEditRestartFrame, 2, 1) \
F(GetFunctionCodePositionFromSource, 2, 1) \
F(ExecuteInDebugContext, 2, 1) \
\

View File

@ -0,0 +1,153 @@
// Copyright 2012 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Flags: --expose-debug-as debug
// Get the Debug object exposed from the debug context global object.
Debug = debug.Debug
function FindCallFrame(exec_state, frame_code) {
var number = Number(frame_code);
if (number >= 0) {
return exec_state.frame(number);
} else {
for (var i = 0; i < exec_state.frameCount(); i++) {
var frame = exec_state.frame(i);
var func_mirror = frame.func();
if (frame_code == func_mirror.name()) {
return frame;
}
}
}
throw new Error("Failed to find function name " + function_name);
}
function TestCase(test_scenario, expected_output) {
// Global variable, accessed from eval'd script.
test_output = "";
function TestCode() {
function A() {
// Extra stack variable. To make function not slim.
// Restarter doesn't work on slim function when stopped on 'debugger'
// statement. (There is no padding for 'debugger' statement).
var o = {};
test_output += 'A';
test_output += '=';
debugger;
return 'Capybara';
}
function B(p1, p2) {
test_output += 'B';
return A();
}
function C() {
test_output += 'C';
// Function call with argument adaptor is intentional.
return B();
}
function D() {
test_output += 'D';
// Function call with argument adaptor is intentional.
return C(1, 2);
}
function E() {
test_output += 'E';
return D();
}
function F() {
test_output += 'F';
return E();
}
return F();
}
var scenario_pos = 0;
function DebuggerStatementHandler(exec_state) {
while (true) {
assertTrue(scenario_pos < test_scenario.length);
var change_code = test_scenario[scenario_pos++];
if (change_code == '=') {
// Continue.
return;
}
var frame = FindCallFrame(exec_state, change_code);
// Throws if fails.
Debug.LiveEdit.RestartFrame(frame);
}
}
var saved_exception = null;
function listener(event, exec_state, event_data, data) {
if (saved_exception != null) {
return;
}
if (event == Debug.DebugEvent.Break) {
try {
DebuggerStatementHandler(exec_state);
} catch (e) {
saved_exception = e;
}
} else {
print("Other: " + event);
}
}
Debug.setListener(listener);
assertEquals("Capybara", TestCode());
Debug.setListener(null);
if (saved_exception) {
print("Exception: " + saved_exception);
print("Stack: " + saved_exception.stack);
assertUnreachable();
}
print(test_output);
assertEquals(expected_output, test_output);
}
TestCase('0==', "FEDCBA=A=");
TestCase('1==', "FEDCBA=BA=");
TestCase('2==', "FEDCBA=CBA=");
TestCase('3==', "FEDCBA=DCBA=");
TestCase('4==', "FEDCBA=EDCBA=");
TestCase('5==', "FEDCBA=FEDCBA=");
TestCase('=', "FEDCBA=");
TestCase('C==', "FEDCBA=CBA=");
TestCase('B=C=A=D==', "FEDCBA=BA=CBA=A=DCBA=");
// Successive restarts don't work now and require additional fix.
//TestCase('BCDE==', "FEDCBA=EDCBA=");
//TestCase('BC=BCDE==', "FEDCBA=CBA=EDCBA=");
//TestCase('EF==', "FEDCBA=FEDCBA=");

View File

@ -63,6 +63,7 @@ unicode-case-overoptimization: PASS, TIMEOUT if ($arch == arm || $arch == mips)
debug-liveedit-check-stack: SKIP
debug-liveedit-patch-positions-replace: SKIP
debug-liveedit-stack-padding: SKIP
debug-liveedit-restart-frame: SKIP
# Test Crankshaft compilation time. Expected to take too long in debug mode.
regress/regress-1969: PASS, SKIP if $mode == debug