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:
parent
070ee02057
commit
be5bb26e38
@ -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
|
||||
|
@ -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, };
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
17
src/debug.cc
17
src/debug.cc
@ -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();
|
||||
}
|
||||
|
11
src/debug.h
11
src/debug.h
@ -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_;
|
||||
|
||||
|
@ -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
|
||||
|
20
src/frames.h
20
src/frames.h
@ -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_
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
267
src/liveedit.cc
267
src/liveedit.cc
@ -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);
|
||||
|
@ -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
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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]);
|
||||
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user