LiveEdit: implement frame dropping

Review URL: http://codereview.chromium.org/1118007

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@4351 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
peter.rybin@gmail.com 2010-04-06 17:58:28 +00:00
parent 070ee02057
commit be5bb26e38
18 changed files with 660 additions and 82 deletions

View File

@ -216,8 +216,23 @@ void Debug::GenerateStubNoRegistersDebugBreak(MacroAssembler* masm) {
}
void Debug::GeneratePlainReturnLiveEdit(MacroAssembler* masm) {
masm->Abort("LiveEdit frame dropping is not supported on arm");
}
void Debug::GenerateFrameDropperLiveEdit(MacroAssembler* masm) {
masm->Abort("LiveEdit frame dropping is not supported on arm");
}
#undef __
void Debug::SetUpFrameDropperFrame(StackFrame* bottom_js_frame,
Handle<Code> code) {
UNREACHABLE();
}
const int Debug::kFrameDropperFrameSize = -1;
#endif // ENABLE_DEBUGGER_SUPPORT
} } // namespace v8::internal

View File

@ -1392,6 +1392,14 @@ static void Generate_Return_DebugBreak(MacroAssembler* masm) {
static void Generate_StubNoRegisters_DebugBreak(MacroAssembler* masm) {
Debug::GenerateStubNoRegistersDebugBreak(masm);
}
static void Generate_PlainReturn_LiveEdit(MacroAssembler* masm) {
Debug::GeneratePlainReturnLiveEdit(masm);
}
static void Generate_FrameDropper_LiveEdit(MacroAssembler* masm) {
Debug::GenerateFrameDropperLiveEdit(masm);
}
#endif
Object* Builtins::builtins_[builtin_count] = { NULL, };

View File

@ -126,7 +126,9 @@ enum BuiltinExtraArguments {
V(LoadIC_DebugBreak, LOAD_IC, DEBUG_BREAK) \
V(KeyedLoadIC_DebugBreak, KEYED_LOAD_IC, DEBUG_BREAK) \
V(StoreIC_DebugBreak, STORE_IC, DEBUG_BREAK) \
V(KeyedStoreIC_DebugBreak, KEYED_STORE_IC, DEBUG_BREAK)
V(KeyedStoreIC_DebugBreak, KEYED_STORE_IC, DEBUG_BREAK) \
V(PlainReturn_LiveEdit, BUILTIN, DEBUG_BREAK) \
V(FrameDropper_LiveEdit, BUILTIN, DEBUG_BREAK)
#else
#define BUILTIN_LIST_DEBUG_A(V)
#endif

View File

@ -474,6 +474,11 @@ Debug.disassembleConstructor = function(f) {
return %DebugDisassembleConstructor(f);
};
Debug.ExecuteInDebugContext = function(f, without_debugger) {
if (!IS_FUNCTION(f)) throw new Error('Parameters have wrong types.');
return %ExecuteInDebugContext(f, !!without_debugger);
};
Debug.sourcePosition = function(f) {
if (!IS_FUNCTION(f)) throw new Error('Parameters have wrong types.');
return %FunctionGetScriptSourcePosition(f);
@ -2010,7 +2015,7 @@ DebugCommandProcessor.prototype.changeLiveRequest_ = function(request, response)
if (e instanceof Debug.LiveEditChangeScript.Failure) {
// Let's treat it as a "success" so that body with change_log will be
// sent back. "change_log" will have "failure" field set.
change_log.push( { failure: true } );
change_log.push( { failure: true, message: e.toString() } );
} else {
throw e;
}

View File

@ -814,6 +814,8 @@ Object* Debug::Break(Arguments args) {
HandleScope scope;
ASSERT(args.length() == 0);
thread_local_.frames_are_dropped_ = false;
// Get the top-most JavaScript frame.
JavaScriptFrameIterator it;
JavaScriptFrame* frame = it.frame();
@ -890,8 +892,13 @@ Object* Debug::Break(Arguments args) {
PrepareStep(step_action, step_count);
}
// Install jump to the call address which was overwritten.
SetAfterBreakTarget(frame);
if (thread_local_.frames_are_dropped_) {
// We must have been calling IC stub. Do not return there anymore.
Code* plain_return = Builtins::builtin(Builtins::PlainReturn_LiveEdit);
thread_local_.after_break_target_ = plain_return->entry();
} else {
SetAfterBreakTarget(frame);
}
return Heap::undefined_value();
}
@ -1655,6 +1662,12 @@ void Debug::SetAfterBreakTarget(JavaScriptFrame* frame) {
}
void Debug::FramesHaveBeenDropped(StackFrame::Id new_break_frame_id) {
thread_local_.frames_are_dropped_ = true;
thread_local_.break_frame_id_ = new_break_frame_id;
}
bool Debug::IsDebugGlobal(GlobalObject* global) {
return IsLoaded() && global == Debug::debug_context()->global();
}

View File

@ -377,10 +377,18 @@ class Debug {
static void GenerateConstructCallDebugBreak(MacroAssembler* masm);
static void GenerateReturnDebugBreak(MacroAssembler* masm);
static void GenerateStubNoRegistersDebugBreak(MacroAssembler* masm);
static void GeneratePlainReturnLiveEdit(MacroAssembler* masm);
static void GenerateFrameDropperLiveEdit(MacroAssembler* masm);
// Called from stub-cache.cc.
static void GenerateCallICDebugBreak(MacroAssembler* masm);
static void FramesHaveBeenDropped(StackFrame::Id new_break_frame_id);
static void SetUpFrameDropperFrame(StackFrame* bottom_js_frame,
Handle<Code> code);
static const int kFrameDropperFrameSize;
private:
static bool CompileDebuggerScript(int index);
static void ClearOneShot();
@ -446,6 +454,9 @@ class Debug {
// Storage location for jump when exiting debug break calls.
Address after_break_target_;
// Indicates that LiveEdit has patched the stack.
bool frames_are_dropped_;
// Top debugger entry.
EnterDebugger* debugger_entry_;

View File

@ -382,6 +382,12 @@ void EntryFrame::ComputeCallerState(State* state) const {
}
void EntryFrame::SetCallerFp(Address caller_fp) {
const int offset = EntryFrameConstants::kCallerFPOffset;
Memory::Address_at(this->fp() + offset) = caller_fp;
}
StackFrame::Type EntryFrame::GetCallerState(State* state) const {
const int offset = EntryFrameConstants::kCallerFPOffset;
Address fp = Memory::Address_at(this->fp() + offset);
@ -414,6 +420,11 @@ void ExitFrame::ComputeCallerState(State* state) const {
}
void ExitFrame::SetCallerFp(Address caller_fp) {
Memory::Address_at(fp() + ExitFrameConstants::kCallerFPOffset) = caller_fp;
}
Address ExitFrame::GetCallerStackPointer() const {
return fp() + ExitFrameConstants::kCallerSPDisplacement;
}
@ -443,6 +454,12 @@ void StandardFrame::ComputeCallerState(State* state) const {
}
void StandardFrame::SetCallerFp(Address caller_fp) {
Memory::Address_at(fp() + StandardFrameConstants::kCallerFPOffset) =
caller_fp;
}
bool StandardFrame::IsExpressionInsideHandler(int n) const {
Address address = GetExpressionAddress(n);
for (StackHandlerIterator it(this, top_handler()); !it.done(); it.Advance()) {
@ -767,4 +784,40 @@ int JSCallerSavedCode(int n) {
}
#define DEFINE_WRAPPER(type, field) \
class field##_Wrapper : public ZoneObject { \
public: /* NOLINT */ \
field##_Wrapper(const field& original) : frame_(original) { \
} \
field frame_; \
};
STACK_FRAME_TYPE_LIST(DEFINE_WRAPPER)
#undef DEFINE_WRAPPER
static StackFrame* AllocateFrameCopy(StackFrame* frame) {
#define FRAME_TYPE_CASE(type, field) \
case StackFrame::type: { \
field##_Wrapper* wrapper = \
new field##_Wrapper(*(reinterpret_cast<field*>(frame))); \
return &wrapper->frame_; \
}
switch (frame->type()) {
STACK_FRAME_TYPE_LIST(FRAME_TYPE_CASE)
default: UNREACHABLE();
}
#undef FRAME_TYPE_CASE
return NULL;
}
Vector<StackFrame*> CreateStackMap() {
ZoneList<StackFrame*> list(10);
for (StackFrameIterator it; !it.done(); it.Advance()) {
StackFrame* frame = AllocateFrameCopy(it.frame());
list.Add(frame);
}
return list.ToVector();
}
} } // namespace v8::internal

View File

@ -114,6 +114,12 @@ class StackFrame BASE_EMBEDDED {
// by the debugger.
enum Id { NO_ID = 0 };
// Copy constructor; it breaks the connection to host iterator.
StackFrame(const StackFrame& original) {
this->state_ = original.state_;
this->iterator_ = NULL;
}
// Type testers.
bool is_entry() const { return type() == ENTRY; }
bool is_entry_construct() const { return type() == ENTRY_CONSTRUCT; }
@ -132,6 +138,8 @@ class StackFrame BASE_EMBEDDED {
Address pc() const { return *pc_address(); }
void set_pc(Address pc) { *pc_address() = pc; }
virtual void SetCallerFp(Address caller_fp) = 0;
Address* pc_address() const { return state_.pc_address; }
// Get the id of this stack frame.
@ -200,7 +208,8 @@ class StackFrame BASE_EMBEDDED {
friend class StackHandlerIterator;
friend class SafeStackFrameIterator;
DISALLOW_IMPLICIT_CONSTRUCTORS(StackFrame);
private:
void operator=(const StackFrame& original);
};
@ -218,6 +227,7 @@ class EntryFrame: public StackFrame {
ASSERT(frame->is_entry());
return static_cast<EntryFrame*>(frame);
}
virtual void SetCallerFp(Address caller_fp);
protected:
explicit EntryFrame(StackFrameIterator* iterator) : StackFrame(iterator) { }
@ -268,6 +278,8 @@ class ExitFrame: public StackFrame {
// Garbage collection support.
virtual void Iterate(ObjectVisitor* v) const;
virtual void SetCallerFp(Address caller_fp);
static ExitFrame* cast(StackFrame* frame) {
ASSERT(frame->is_exit());
return static_cast<ExitFrame*>(frame);
@ -303,6 +315,8 @@ class StandardFrame: public StackFrame {
inline void SetExpression(int index, Object* value);
int ComputeExpressionsCount() const;
virtual void SetCallerFp(Address caller_fp);
static StandardFrame* cast(StackFrame* frame) {
ASSERT(frame->is_standard());
return static_cast<StandardFrame*>(frame);
@ -658,6 +672,10 @@ class StackFrameLocator BASE_EMBEDDED {
};
// Reads all frames on the current stack and copies them into the current
// zone memory.
Vector<StackFrame*> CreateStackMap();
} } // namespace v8::internal
#endif // V8_FRAMES_H_

View File

@ -206,8 +206,58 @@ void Debug::GenerateStubNoRegistersDebugBreak(MacroAssembler* masm) {
}
void Debug::GeneratePlainReturnLiveEdit(MacroAssembler* masm) {
masm->ret(0);
}
// FrameDropper is a code replacement for a JavaScript frame with possibly
// several frames above.
// There is no calling conventions here, because it never actually gets called,
// it only gets returned to.
// Frame structure (conforms InternalFrame structure):
// -- JSFunction
// -- code
// -- SMI maker
// -- context
// -- frame base
void Debug::GenerateFrameDropperLiveEdit(MacroAssembler* masm) {
// We do not know our frame height, but set esp based on ebp.
__ lea(esp, Operand(ebp, -4 * kPointerSize));
__ pop(edi); // function
// Skip code self-reference and marker.
__ add(Operand(esp), Immediate(2 * kPointerSize));
__ pop(esi); // Context.
__ pop(ebp);
// Get function code.
__ mov(edx, FieldOperand(edi, JSFunction::kSharedFunctionInfoOffset));
__ mov(edx, FieldOperand(edx, SharedFunctionInfo::kCodeOffset));
__ lea(edx, FieldOperand(edx, Code::kHeaderSize));
// Re-run JSFunction, edi is function, esi is context.
__ jmp(Operand(edx));
}
#undef __
void Debug::SetUpFrameDropperFrame(StackFrame* bottom_js_frame,
Handle<Code> code) {
ASSERT(bottom_js_frame->is_java_script());
Address fp = bottom_js_frame->fp();
Memory::Object_at(fp - 4 * kPointerSize) =
Memory::Object_at(fp - 2 * kPointerSize); // Move edi (function).
Memory::Object_at(fp - 3 * kPointerSize) = *code;
Memory::Object_at(fp - 2 * kPointerSize) = Smi::FromInt(StackFrame::INTERNAL);
}
const int Debug::kFrameDropperFrameSize = 5;
#endif // ENABLE_DEBUGGER_SUPPORT
} } // namespace v8::internal

View File

@ -389,19 +389,32 @@ Debug.LiveEditChangeScript.CheckStackActivations = function(shared_wrapper_list,
for (var i = 0; i < shared_wrapper_list.length; i++) {
shared_list[i] = shared_wrapper_list[i].info;
}
var result = %LiveEditCheckStackActivations(shared_list);
var result = %LiveEditCheckAndDropActivations(shared_list, true);
if (result[shared_list.length]) {
// Extra array element may contain error message.
throw new liveedit.Failure(result[shared_list.length]);
}
var problems = new Array();
var dropped = new Array();
for (var i = 0; i < shared_list.length; i++) {
if (result[i] == liveedit.FunctionPatchabilityStatus.FUNCTION_BLOCKED_ON_STACK) {
var shared = shared_list[i];
var shared = shared_wrapper_list[i];
if (result[i] == liveedit.FunctionPatchabilityStatus.REPLACED_ON_ACTIVE_STACK) {
dropped.push({ name: shared.function_name } );
} else if (result[i] != liveedit.FunctionPatchabilityStatus.AVAILABLE_FOR_PATCH) {
var description = {
name: shared.function_name,
start_pos: shared.start_position,
end_pos: shared.end_position
start_pos: shared.start_position,
end_pos: shared.end_position,
replace_problem:
liveedit.FunctionPatchabilityStatus.SymbolName(result[i])
};
problems.push(description);
}
}
if (dropped.length > 0) {
change_log.push({ dropped_from_stack: dropped });
}
if (problems.length > 0) {
change_log.push( { functions_on_stack: problems } );
throw new liveedit.Failure("Blocked by functions on stack");
@ -410,8 +423,21 @@ Debug.LiveEditChangeScript.CheckStackActivations = function(shared_wrapper_list,
// A copy of the FunctionPatchabilityStatus enum from liveedit.h
Debug.LiveEditChangeScript.FunctionPatchabilityStatus = {
FUNCTION_AVAILABLE_FOR_PATCH: 0,
FUNCTION_BLOCKED_ON_STACK: 1
AVAILABLE_FOR_PATCH: 1,
BLOCKED_ON_ACTIVE_STACK: 2,
BLOCKED_ON_OTHER_STACK: 3,
BLOCKED_UNDER_NATIVE_CODE: 4,
REPLACED_ON_ACTIVE_STACK: 5
}
Debug.LiveEditChangeScript.FunctionPatchabilityStatus.SymbolName =
function(code) {
var enum = Debug.LiveEditChangeScript.FunctionPatchabilityStatus;
for (name in enum) {
if (enum[name] == code) {
return name;
}
}
}

View File

@ -34,6 +34,7 @@
#include "scopes.h"
#include "global-handles.h"
#include "debug.h"
#include "memory.h"
namespace v8 {
namespace internal {
@ -673,6 +674,272 @@ void LiveEdit::PatchFunctionPositions(Handle<JSArray> shared_info_array,
}
// Check an activation against list of functions. If there is a function
// that matches, its status in result array is changed to status argument value.
static bool CheckActivation(Handle<JSArray> shared_info_array,
Handle<JSArray> result, StackFrame* frame,
LiveEdit::FunctionPatchabilityStatus status) {
if (!frame->is_java_script()) {
return false;
}
int len = Smi::cast(shared_info_array->length())->value();
for (int i = 0; i < len; i++) {
JSValue* wrapper = JSValue::cast(shared_info_array->GetElement(i));
Handle<SharedFunctionInfo> shared(
SharedFunctionInfo::cast(wrapper->value()));
if (frame->code() == shared->code()) {
SetElement(result, i, Handle<Smi>(Smi::FromInt(status)));
return true;
}
}
return false;
}
// Iterates over handler chain and removes all elements that are inside
// frames being dropped.
static bool FixTryCatchHandler(StackFrame* top_frame,
StackFrame* bottom_frame) {
Address* pointer_address =
&Memory::Address_at(Top::get_address_from_id(Top::k_handler_address));
while (*pointer_address < top_frame->sp()) {
pointer_address = &Memory::Address_at(*pointer_address);
}
Address* above_frame_address = pointer_address;
while (*pointer_address < bottom_frame->fp()) {
pointer_address = &Memory::Address_at(*pointer_address);
}
bool change = *above_frame_address != *pointer_address;
*above_frame_address = *pointer_address;
return change;
}
// Removes specified range of frames from stack. There may be 1 or more
// frames in range. Anyway the bottom frame is restarted rather than dropped,
// and therefore has to be a JavaScript frame.
// Returns error message or NULL.
static const char* DropFrames(Vector<StackFrame*> frames,
int top_frame_index,
int bottom_js_frame_index) {
StackFrame* pre_top_frame = frames[top_frame_index - 1];
StackFrame* top_frame = frames[top_frame_index];
StackFrame* bottom_js_frame = frames[bottom_js_frame_index];
ASSERT(bottom_js_frame->is_java_script());
// Check the nature of the top frame.
if (pre_top_frame->code()->is_inline_cache_stub() &&
pre_top_frame->code()->ic_state() == DEBUG_BREAK) {
// OK, we can drop inline cache calls.
} else if (pre_top_frame->code() ==
Builtins::builtin(Builtins::FrameDropper_LiveEdit)) {
// OK, we can drop our own code.
} else if (pre_top_frame->code()->kind() == Code::STUB &&
pre_top_frame->code()->major_key()) {
// Unit Test entry, it's fine, we support this case.
} else {
return "Unknown structure of stack above changing function";
}
Address unused_stack_top = top_frame->sp();
Address unused_stack_bottom = bottom_js_frame->fp()
- Debug::kFrameDropperFrameSize * kPointerSize // Size of the new frame.
+ kPointerSize; // Bigger address end is exclusive.
if (unused_stack_top > unused_stack_bottom) {
return "Not enough space for frame dropper frame";
}
// Committing now. After this point we should return only NULL value.
FixTryCatchHandler(pre_top_frame, bottom_js_frame);
// Make sure FixTryCatchHandler is idempotent.
ASSERT(!FixTryCatchHandler(pre_top_frame, bottom_js_frame));
Handle<Code> code(Builtins::builtin(Builtins::FrameDropper_LiveEdit));
top_frame->set_pc(code->entry());
pre_top_frame->SetCallerFp(bottom_js_frame->fp());
Debug::SetUpFrameDropperFrame(bottom_js_frame, code);
for (Address a = unused_stack_top;
a < unused_stack_bottom;
a += kPointerSize) {
Memory::Object_at(a) = Smi::FromInt(0);
}
return NULL;
}
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) {
ZoneScope scope(DELETE_ON_EXIT);
Vector<StackFrame*> frames = CreateStackMap();
int array_len = Smi::cast(shared_info_array->length())->value();
int top_frame_index = -1;
int frame_index = 0;
for (; frame_index < frames.length(); frame_index++) {
StackFrame* frame = frames[frame_index];
if (frame->id() == Debug::break_frame_id()) {
top_frame_index = frame_index;
break;
}
if (CheckActivation(shared_info_array, result, 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";
}
}
if (top_frame_index == -1) {
// We haven't found break frame, but no function is blocking us anyway.
return NULL;
}
bool target_frame_found = false;
int bottom_js_frame_index = top_frame_index;
bool c_code_found = false;
for (; frame_index < frames.length(); frame_index++) {
StackFrame* frame = frames[frame_index];
if (!IsDropableFrame(frame)) {
c_code_found = true;
break;
}
if (CheckActivation(shared_info_array, result, frame,
LiveEdit::FUNCTION_BLOCKED_ON_ACTIVE_STACK)) {
target_frame_found = true;
bottom_js_frame_index = frame_index;
}
}
if (c_code_found) {
// There is a C frames on stack. Check that there are no target frames
// below them.
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)) {
// Cannot drop frame under C frames.
return NULL;
}
}
}
}
if (!do_drop) {
// We are in check-only mode.
return NULL;
}
if (!target_frame_found) {
// Nothing to drop.
return NULL;
}
const char* error_message = DropFrames(frames, top_frame_index,
bottom_js_frame_index);
if (error_message != NULL) {
return error_message;
}
// Adjust break_frame after some frames has been dropped.
StackFrame::Id new_id = StackFrame::NO_ID;
for (int i = bottom_js_frame_index + 1; i < frames.length(); i++) {
if (frames[i]->type() == StackFrame::JAVA_SCRIPT) {
new_id = frames[i]->id();
break;
}
}
Debug::FramesHaveBeenDropped(new_id);
// Replace "blocked on active" with "replaced on active" status.
for (int i = 0; i < array_len; i++) {
if (result->GetElement(i) ==
Smi::FromInt(LiveEdit::FUNCTION_BLOCKED_ON_ACTIVE_STACK)) {
result->SetElement(i, Smi::FromInt(
LiveEdit::FUNCTION_REPLACED_ON_ACTIVE_STACK));
}
}
return NULL;
}
class InactiveThreadActivationsChecker : public ThreadVisitor {
public:
InactiveThreadActivationsChecker(Handle<JSArray> shared_info_array,
Handle<JSArray> result)
: shared_info_array_(shared_info_array), result_(result),
has_blocked_functions_(false) {
}
void VisitThread(ThreadLocalTop* top) {
for (StackFrameIterator it(top); !it.done(); it.Advance()) {
has_blocked_functions_ |= CheckActivation(
shared_info_array_, result_, it.frame(),
LiveEdit::FUNCTION_BLOCKED_ON_OTHER_STACK);
}
}
bool HasBlockedFunctions() {
return has_blocked_functions_;
}
private:
Handle<JSArray> shared_info_array_;
Handle<JSArray> result_;
bool has_blocked_functions_;
};
Handle<JSArray> LiveEdit::CheckAndDropActivations(
Handle<JSArray> shared_info_array, bool do_drop) {
int len = Smi::cast(shared_info_array->length())->value();
Handle<JSArray> result = Factory::NewJSArray(len);
// Fill the default values.
for (int i = 0; i < len; i++) {
SetElement(result, i,
Handle<Smi>(Smi::FromInt(FUNCTION_AVAILABLE_FOR_PATCH)));
}
// First check inactive threads. Fail if some functions are blocked there.
InactiveThreadActivationsChecker inactive_threads_checker(shared_info_array,
result);
ThreadManager::IterateThreads(&inactive_threads_checker);
if (inactive_threads_checker.HasBlockedFunctions()) {
return result;
}
// Try to drop activations from the current stack.
const char* error_message =
DropActivationsInActiveThread(shared_info_array, result, do_drop);
if (error_message != NULL) {
// Add error message as an array extra element.
Vector<const char> vector_message(error_message, strlen(error_message));
Handle<String> str = Factory::NewStringFromAscii(vector_message);
SetElement(result, len, str);
}
return result;
}
LiveEditFunctionTracker::LiveEditFunctionTracker(FunctionLiteral* fun) {
if (active_function_info_listener != NULL) {
active_function_info_listener->FunctionStarted(fun);

View File

@ -91,10 +91,21 @@ class LiveEdit : AllStatic {
static void PatchFunctionPositions(Handle<JSArray> shared_info_array,
Handle<JSArray> position_change_array);
// Checks listed functions on stack and return array with corresponding
// FunctionPatchabilityStatus statuses; extra array element may
// contain general error message. Modifies the current stack and
// has restart the lowest found frames and drops all other frames above
// if possible and if do_drop is true.
static Handle<JSArray> CheckAndDropActivations(
Handle<JSArray> shared_info_array, bool do_drop);
// A copy of this is in liveedit-debugger.js.
enum FunctionPatchabilityStatus {
FUNCTION_AVAILABLE_FOR_PATCH = 0,
FUNCTION_BLOCKED_ON_STACK = 1
FUNCTION_AVAILABLE_FOR_PATCH = 1,
FUNCTION_BLOCKED_ON_ACTIVE_STACK = 2,
FUNCTION_BLOCKED_ON_OTHER_STACK = 3,
FUNCTION_BLOCKED_UNDER_NATIVE_CODE = 4,
FUNCTION_REPLACED_ON_ACTIVE_STACK = 5
};
};

View File

@ -104,8 +104,24 @@ void Debug::GenerateStubNoRegistersDebugBreak(MacroAssembler* masm) {
}
void Debug::GeneratePlainReturnLiveEdit(MacroAssembler* masm) {
masm->Abort("LiveEdit frame dropping is not supported on mips");
}
void Debug::GenerateFrameDropperLiveEdit(MacroAssembler* masm) {
masm->Abort("LiveEdit frame dropping is not supported on mips");
}
#undef __
void Debug::SetUpFrameDropperFrame(StackFrame* bottom_js_frame,
Handle<Code> code) {
UNREACHABLE();
}
const int Debug::kFrameDropperFrameSize = -1;
#endif // ENABLE_DEBUGGER_SUPPORT
} } // namespace v8::internal

View File

@ -9693,41 +9693,18 @@ static Object* Runtime_LiveEditPatchFunctionPositions(Arguments args) {
}
static LiveEdit::FunctionPatchabilityStatus FindFunctionCodeOnStacks(
Handle<SharedFunctionInfo> shared) {
// TODO(635): check all threads, not only the current one.
for (StackFrameIterator it; !it.done(); it.Advance()) {
StackFrame* frame = it.frame();
if (frame->code() == shared->code()) {
return LiveEdit::FUNCTION_BLOCKED_ON_STACK;
}
}
return LiveEdit::FUNCTION_AVAILABLE_FOR_PATCH;
}
// For array of SharedFunctionInfo's (each wrapped in JSValue)
// checks that none of them have activations on stacks (of any thread).
// Returns array of the same length with corresponding results of
// LiveEdit::FunctionPatchabilityStatus type.
static Object* Runtime_LiveEditCheckStackActivations(Arguments args) {
ASSERT(args.length() == 1);
static Object* Runtime_LiveEditCheckAndDropActivations(Arguments args) {
ASSERT(args.length() == 2);
HandleScope scope;
CONVERT_ARG_CHECKED(JSArray, shared_array, 0);
CONVERT_BOOLEAN_CHECKED(do_drop, args[1]);
int len = Smi::cast(shared_array->length())->value();
Handle<JSArray> result = Factory::NewJSArray(len);
for (int i = 0; i < len; i++) {
JSValue* wrapper = JSValue::cast(shared_array->GetElement(i));
Handle<SharedFunctionInfo> shared(
SharedFunctionInfo::cast(wrapper->value()));
LiveEdit::FunctionPatchabilityStatus check_res =
FindFunctionCodeOnStacks(shared);
SetElement(result, i, Handle<Smi>(Smi::FromInt(check_res)));
}
return *result;
return *LiveEdit::CheckAndDropActivations(shared_array, do_drop);
}
@ -9761,6 +9738,35 @@ static Object* Runtime_GetFunctionCodePositionFromSource(Arguments args) {
}
// Calls specified function with or without entering the debugger.
// This is used in unit tests to run code as if debugger is entered or simply
// to have a stack with C++ frame in the middle.
static Object* Runtime_ExecuteInDebugContext(Arguments args) {
ASSERT(args.length() == 2);
HandleScope scope;
CONVERT_ARG_CHECKED(JSFunction, function, 0);
CONVERT_BOOLEAN_CHECKED(without_debugger, args[1]);
Handle<Object> result;
bool pending_exception;
{
if (without_debugger) {
result = Execution::Call(function, Top::global(), 0, NULL,
&pending_exception);
} else {
EnterDebugger enter_debugger;
result = Execution::Call(function, Top::global(), 0, NULL,
&pending_exception);
}
}
if (!pending_exception) {
return *result;
} else {
return Failure::Exception();
}
}
#endif // ENABLE_DEBUGGER_SUPPORT
#ifdef ENABLE_LOGGING_AND_PROFILING

View File

@ -334,8 +334,9 @@ namespace internal {
F(LiveEditReplaceFunctionCode, 2, 1) \
F(LiveEditRelinkFunctionToScript, 2, 1) \
F(LiveEditPatchFunctionPositions, 2, 1) \
F(LiveEditCheckStackActivations, 1, 1) \
F(GetFunctionCodePositionFromSource, 2, 1)
F(LiveEditCheckAndDropActivations, 2, 1) \
F(GetFunctionCodePositionFromSource, 2, 1) \
F(ExecuteInDebugContext, 2, 1)
#else
#define RUNTIME_FUNCTION_LIST_DEBUGGER_SUPPORT(F)
#endif

View File

@ -177,9 +177,24 @@ void Debug::GenerateStubNoRegistersDebugBreak(MacroAssembler* masm) {
}
void Debug::GeneratePlainReturnLiveEdit(MacroAssembler* masm) {
masm->Abort("LiveEdit frame dropping is not supported on x64");
}
void Debug::GenerateFrameDropperLiveEdit(MacroAssembler* masm) {
masm->Abort("LiveEdit frame dropping is not supported on x64");
}
#undef __
void Debug::SetUpFrameDropperFrame(StackFrame* bottom_js_frame,
Handle<Code> code) {
UNREACHABLE();
}
const int Debug::kFrameDropperFrameSize = -1;
void BreakLocationIterator::ClearDebugBreakAtReturn() {
rinfo()->PatchCode(original_rinfo()->pc(),
Assembler::kJSReturnSequenceLength);

View File

@ -30,55 +30,112 @@
Debug = debug.Debug
eval(
"function ChooseAnimal(callback) {\n " +
" callback();\n" +
" return 'Cat';\n" +
"}\n"
);
unique_id = 1;
function Noop() {}
var res = ChooseAnimal(Noop);
function TestBase(name) {
print("TestBase constructor: " + name);
assertEquals("Cat", res);
this.ChooseAnimal = eval(
"/* " + unique_id + "*/\n" +
"(function ChooseAnimal(callback) {\n " +
" callback();\n" +
" return 'Cat';\n" +
"})\n"
);
// Prevents eval script caching.
unique_id++;
var script = Debug.findScript(ChooseAnimal);
var script = Debug.findScript(this.ChooseAnimal);
var orig_animal = "'Cat'";
var patch_pos = script.source.indexOf(orig_animal);
var new_animal_patch = "'Capybara'";
var orig_animal = "'Cat'";
var patch_pos = script.source.indexOf(orig_animal);
var new_animal_patch = "'Capybara'";
var got_exception = false;
var successfully_changed = false;
var got_exception = false;
var successfully_changed = false;
function Changer() {
// Never try the same patch again.
assertEquals(false, successfully_changed);
var change_log = new Array();
try {
Debug.LiveEditChangeScript(script, patch_pos, orig_animal.length, new_animal_patch, change_log);
successfully_changed = true;
} catch (e) {
if (e instanceof Debug.LiveEditChangeScript.Failure) {
got_exception = true;
print(e);
} else {
throw e;
// Should be called from Debug context.
this.ScriptChanger = function() {
assertEquals(false, successfully_changed, "applying patch second time");
// Runs in debugger context.
var change_log = new Array();
try {
Debug.LiveEditChangeScript(script, patch_pos, orig_animal.length, new_animal_patch, change_log);
} finally {
print("Change log: " + JSON.stringify(change_log) + "\n");
}
}
print("Change log: " + JSON.stringify(change_log) + "\n");
successfully_changed = true;
};
}
var new_res = ChooseAnimal(Changer);
// Function must be not pached.
assertEquals("Cat", new_res);
function Noop() {}
assertEquals(true, got_exception);
function WrapInCatcher(f, holder) {
return function() {
delete holder[0];
try {
f();
} catch (e) {
if (e instanceof Debug.LiveEditChangeScript.Failure) {
holder[0] = e;
} else {
throw e;
}
}
};
}
// This time it should succeed.
Changer();
function WrapInNativeCall(f) {
return function() {
return Debug.ExecuteInDebugContext(f, true);
};
}
new_res = ChooseAnimal(Noop);
// Function must be not pached.
assertEquals("Capybara", new_res);
function WrapInDebuggerCall(f) {
return function() {
return Debug.ExecuteInDebugContext(f, false);
};
}
function WrapInRestartProof(f) {
var already_called = false;
return function() {
if (already_called) {
return;
}
already_called = true;
f();
}
}
function WrapInConstructor(f) {
return function() {
return new function() {
f();
};
}
}
// A series of tests. In each test we call ChooseAnimal function that calls
// a callback that attempts to modify the function on the fly.
test = new TestBase("First test ChooseAnimal without edit");
assertEquals("Cat", test.ChooseAnimal(Noop));
test = new TestBase("Test without function on stack");
test.ScriptChanger();
assertEquals("Capybara", test.ChooseAnimal(Noop));
test = new TestBase("Test with function on stack");
assertEquals("Capybara", test.ChooseAnimal(WrapInDebuggerCall(WrapInRestartProof(test.ScriptChanger))));
test = new TestBase("Test with function on stack and with constructor frame");
assertEquals("Capybara", test.ChooseAnimal(WrapInConstructor(WrapInDebuggerCall(WrapInRestartProof(test.ScriptChanger)))));
test = new TestBase("Test with C++ frame above ChooseAnimal frame");
exception_holder = {};
assertEquals("Cat", test.ChooseAnimal(WrapInNativeCall(WrapInDebuggerCall(WrapInCatcher(test.ScriptChanger, exception_holder)))));
assertTrue(!!exception_holder[0]);

View File

@ -48,6 +48,10 @@ unicode-case-overoptimization: PASS, TIMEOUT if ($arch == arm)
# Skip long running test in debug and allow it to timeout in release mode.
regress/regress-524: (PASS || TIMEOUT), SKIP if $mode == debug
# Skip experimental liveedit drop frame on non-ia32 architectures.
# debug-liveedit-check-stack: SKIP if $arch != ia32
debug-liveedit-check-stack: SKIP
[ $arch == arm ]
# Slow tests which times out in debug mode.