[liveedit] reimplement frame restarting.

Previously, when restarting a frame, we would rewrite all frames
between the debugger activation and the frame to restart to squash
them, and replace the return address with that of a builtin to
leave that rewritten frame, and restart the function by calling it.

We now simply remember the frame to drop to, and upon returning
from the debugger, we check whether to drop the frame, load the
new FP, and restart the function.

R=jgruber@chromium.org, mstarzinger@chromium.org
BUG=v8:5587

Review-Url: https://codereview.chromium.org/2636913002
Cr-Commit-Position: refs/heads/master@{#42725}
This commit is contained in:
yangguo 2017-01-26 23:31:03 -08:00 committed by Commit bot
parent 34bee46b0b
commit 3f47c63ded
65 changed files with 594 additions and 732 deletions

View File

@ -437,6 +437,14 @@ void ResumeGeneratorDescriptor::InitializePlatformSpecific(
data->InitializePlatformSpecific(arraysize(registers), registers);
}
void FrameDropperTrampolineDescriptor::InitializePlatformSpecific(
CallInterfaceDescriptorData* data) {
Register registers[] = {
r1, // loaded new FP
};
data->InitializePlatformSpecific(arraysize(registers), registers);
}
} // namespace internal
} // namespace v8

View File

@ -1930,6 +1930,16 @@ void MacroAssembler::DebugBreak() {
Call(ces.GetCode(), RelocInfo::DEBUGGER_STATEMENT);
}
void MacroAssembler::MaybeDropFrames() {
// Check whether we need to drop frames to restart a function on the stack.
ExternalReference restart_fp =
ExternalReference::debug_restart_fp_address(isolate());
mov(r1, Operand(restart_fp));
ldr(r1, MemOperand(r1));
tst(r1, r1);
Jump(isolate()->builtins()->FrameDropperTrampoline(), RelocInfo::CODE_TARGET,
ne);
}
void MacroAssembler::PushStackHandler() {
// Adjust this code if not the case.

View File

@ -718,6 +718,7 @@ class MacroAssembler: public Assembler {
// Debugger Support
void DebugBreak();
void MaybeDropFrames();
// ---------------------------------------------------------------------------
// Exception handling

View File

@ -468,6 +468,14 @@ void ResumeGeneratorDescriptor::InitializePlatformSpecific(
data->InitializePlatformSpecific(arraysize(registers), registers);
}
void FrameDropperTrampolineDescriptor::InitializePlatformSpecific(
CallInterfaceDescriptorData* data) {
Register registers[] = {
x1, // loaded new FP
};
data->InitializePlatformSpecific(arraysize(registers), registers);
}
} // namespace internal
} // namespace v8

View File

@ -2898,6 +2898,16 @@ void MacroAssembler::DebugBreak() {
Call(ces.GetCode(), RelocInfo::DEBUGGER_STATEMENT);
}
void MacroAssembler::MaybeDropFrames() {
// Check whether we need to drop frames to restart a function on the stack.
ExternalReference restart_fp =
ExternalReference::debug_restart_fp_address(isolate());
Mov(x1, Operand(restart_fp));
Ldr(x1, MemOperand(x1));
Tst(x1, x1);
Jump(isolate()->builtins()->FrameDropperTrampoline(), RelocInfo::CODE_TARGET,
ne);
}
void MacroAssembler::PushStackHandler() {
DCHECK(jssp.Is(StackPointer()));

View File

@ -1304,6 +1304,7 @@ class MacroAssembler : public Assembler {
// Debugger Support
void DebugBreak();
void MaybeDropFrames();
// ---------------------------------------------------------------------------
// Exception handling

View File

@ -1579,12 +1579,6 @@ ExternalReference ExternalReference::debug_hook_on_function_call_address(
return ExternalReference(isolate->debug()->hook_on_function_call_address());
}
ExternalReference ExternalReference::debug_after_break_target_address(
Isolate* isolate) {
return ExternalReference(isolate->debug()->after_break_target_address());
}
ExternalReference ExternalReference::runtime_function_table_address(
Isolate* isolate) {
return ExternalReference(
@ -1665,6 +1659,11 @@ ExternalReference ExternalReference::debug_suspended_generator_address(
return ExternalReference(isolate->debug()->suspended_generator_address());
}
ExternalReference ExternalReference::debug_restart_fp_address(
Isolate* isolate) {
return ExternalReference(isolate->debug()->restart_fp_address());
}
ExternalReference ExternalReference::fixed_typed_array_base_data_offset() {
return ExternalReference(reinterpret_cast<void*>(
FixedTypedArrayBase::kDataOffset - kHeapObjectTag));

View File

@ -1011,6 +1011,9 @@ class ExternalReference BASE_EMBEDDED {
// Used to check for suspended generator, used for stepping across await call.
static ExternalReference debug_suspended_generator_address(Isolate* isolate);
// Used to store the frame pointer to drop to when restarting a frame.
static ExternalReference debug_restart_fp_address(Isolate* isolate);
#ifndef V8_INTERPRETED_REGEXP
// C functions called from RegExp generated code.

View File

@ -253,6 +253,8 @@ namespace internal {
V(kUnsupportedPhiUseOfArguments, "Unsupported phi use of arguments") \
V(kUnsupportedPhiUseOfConstVariable, \
"Unsupported phi use of const or let variable") \
V(kUnexpectedReturnFromFrameDropper, \
"Unexpectedly returned from dropping frames") \
V(kUnexpectedReturnFromThrow, "Unexpectedly returned from a throw") \
V(kUnsupportedSwitchStatement, "Unsupported switch statement") \
V(kUnsupportedTaggedImmediate, "Unsupported tagged immediate") \

View File

@ -19,8 +19,12 @@ void Builtins::Generate_Slot_DebugBreak(MacroAssembler* masm) {
DebugCodegen::IGNORE_RESULT_REGISTER);
}
void Builtins::Generate_FrameDropper_LiveEdit(MacroAssembler* masm) {
DebugCodegen::GenerateFrameDropperLiveEdit(masm);
void Builtins::Generate_FrameDropperTrampoline(MacroAssembler* masm) {
DebugCodegen::GenerateFrameDropperTrampoline(masm);
}
void Builtins::Generate_HandleDebuggerStatement(MacroAssembler* masm) {
DebugCodegen::GenerateHandleDebuggerStatement(masm);
}
} // namespace internal

View File

@ -192,7 +192,8 @@ namespace internal {
NewArgumentsElements) \
\
/* Debugger */ \
DBG(FrameDropper_LiveEdit) \
DBG(FrameDropperTrampoline) \
DBG(HandleDebuggerStatement) \
DBG(Return_DebugBreak) \
DBG(Slot_DebugBreak) \
\

View File

@ -311,6 +311,18 @@ Callable CodeFactory::ResumeGenerator(Isolate* isolate) {
ResumeGeneratorDescriptor(isolate));
}
// static
Callable CodeFactory::FrameDropperTrampoline(Isolate* isolate) {
return Callable(isolate->builtins()->FrameDropperTrampoline(),
FrameDropperTrampolineDescriptor(isolate));
}
// static
Callable CodeFactory::HandleDebuggerStatement(Isolate* isolate) {
return Callable(isolate->builtins()->HandleDebuggerStatement(),
ContextOnlyDescriptor(isolate));
}
// static
Callable CodeFactory::FastCloneShallowArray(
Isolate* isolate, AllocationSiteMode allocation_mode) {

View File

@ -61,6 +61,9 @@ class V8_EXPORT_PRIVATE CodeFactory final {
static Callable ResumeGenerator(Isolate* isolate);
static Callable FrameDropperTrampoline(Isolate* isolate);
static Callable HandleDebuggerStatement(Isolate* isolate);
static Callable CompareIC(Isolate* isolate, Token::Value op);
static Callable CompareNilIC(Isolate* isolate, NilValue nil_value);

View File

@ -1285,8 +1285,7 @@ void AstGraphBuilder::VisitTryFinallyStatement(TryFinallyStatement* stmt) {
void AstGraphBuilder::VisitDebuggerStatement(DebuggerStatement* stmt) {
Node* node =
NewNode(javascript()->CallRuntime(Runtime::kHandleDebuggerStatement));
Node* node = NewNode(javascript()->Debugger());
PrepareFrameState(node, stmt->DebugBreakId());
environment()->MarkAllLocalsLive();
}

View File

@ -1819,9 +1819,8 @@ void BytecodeGraphBuilder::VisitReturn() {
void BytecodeGraphBuilder::VisitDebugger() {
PrepareEagerCheckpoint();
Node* call =
NewNode(javascript()->CallRuntime(Runtime::kHandleDebuggerStatement));
environment()->BindAccumulator(call, Environment::kAttachFrameState);
Node* call = NewNode(javascript()->Debugger());
environment()->RecordAfterState(call, Environment::kAttachFrameState);
}
// We cannot create a graph from the debugger copy of the bytecode array.

View File

@ -668,6 +668,11 @@ void JSGenericLowering::LowerJSStackCheck(Node* node) {
ReplaceWithRuntimeCall(node, Runtime::kStackGuard);
}
void JSGenericLowering::LowerJSDebugger(Node* node) {
CallDescriptor::Flags flags = FrameStateFlagForCall(node);
Callable callable = CodeFactory::HandleDebuggerStatement(isolate());
ReplaceWithStubCall(node, callable, flags);
}
Zone* JSGenericLowering::zone() const { return graph()->zone(); }

View File

@ -573,6 +573,7 @@ CompareOperationHint CompareOperationHintOf(const Operator* op) {
V(StoreMessage, Operator::kNoRead | Operator::kNoThrow, 1, 0) \
V(GeneratorRestoreContinuation, Operator::kNoThrow, 1, 1) \
V(StackCheck, Operator::kNoWrite, 0, 0) \
V(Debugger, Operator::kNoProperties, 0, 0) \
V(GetSuperConstructor, Operator::kNoWrite, 1, 1)
#define BINARY_OP_LIST(V) \

View File

@ -674,6 +674,7 @@ class V8_EXPORT_PRIVATE JSOperatorBuilder final
const Operator* GeneratorRestoreRegister(int index);
const Operator* StackCheck();
const Operator* Debugger();
const Operator* CreateFunctionContext(int slot_count, ScopeType scope_type);
const Operator* CreateCatchContext(const Handle<String>& name,

View File

@ -175,7 +175,8 @@
V(JSGeneratorStore) \
V(JSGeneratorRestoreContinuation) \
V(JSGeneratorRestoreRegister) \
V(JSStackCheck)
V(JSStackCheck) \
V(JSDebugger)
#define JS_OP_LIST(V) \
JS_SIMPLE_BINOP_LIST(V) \
@ -775,7 +776,7 @@ class V8_EXPORT_PRIVATE IrOpcode {
// Returns true if opcode for JavaScript operator.
static bool IsJsOpcode(Value value) {
return kJSEqual <= value && value <= kJSStackCheck;
return kJSEqual <= value && value <= kJSDebugger;
}
// Returns true if opcode for constant operator.

View File

@ -104,6 +104,7 @@ bool OperatorProperties::HasFrameStateInput(const Operator* op) {
case IrOpcode::kJSForInNext:
case IrOpcode::kJSForInPrepare:
case IrOpcode::kJSStackCheck:
case IrOpcode::kJSDebugger:
case IrOpcode::kJSGetSuperConstructor:
return true;

View File

@ -1685,6 +1685,8 @@ Type* Typer::Visitor::TypeJSGeneratorRestoreRegister(Node* node) {
Type* Typer::Visitor::TypeJSStackCheck(Node* node) { return Type::Any(); }
Type* Typer::Visitor::TypeJSDebugger(Node* node) { return Type::Any(); }
// Simplified operators.
Type* Typer::Visitor::TypeBooleanNot(Node* node) { return Type::Boolean(); }

View File

@ -723,6 +723,7 @@ void Verifier::Visitor::Check(Node* node) {
break;
case IrOpcode::kJSStackCheck:
case IrOpcode::kJSDebugger:
// Type is empty.
CheckNotTyped(node);
break;

View File

@ -75,14 +75,6 @@ void DebugCodegen::GenerateDebugBreakStub(MacroAssembler* masm,
{
FrameAndConstantPoolScope scope(masm, StackFrame::INTERNAL);
// Load padding words on stack.
__ mov(ip, Operand(Smi::FromInt(LiveEdit::kFramePaddingValue)));
for (int i = 0; i < LiveEdit::kFramePaddingInitialSize; i++) {
__ push(ip);
}
__ mov(ip, Operand(Smi::FromInt(LiveEdit::kFramePaddingInitialSize)));
__ push(ip);
// Push arguments for DebugBreak call.
if (mode == SAVE_RESULT_REGISTER) {
// Break on return.
@ -109,50 +101,45 @@ void DebugCodegen::GenerateDebugBreakStub(MacroAssembler* masm,
}
}
}
// Don't bother removing padding bytes pushed on the stack
// as the frame is going to be restored right away.
// Leave the internal frame.
}
// Now that the break point has been handled, resume normal execution by
// jumping to the target address intended by the caller and that was
// overwritten by the address of DebugBreakXXX.
ExternalReference after_break_target =
ExternalReference::debug_after_break_target_address(masm->isolate());
__ mov(ip, Operand(after_break_target));
__ ldr(ip, MemOperand(ip));
__ Jump(ip);
__ MaybeDropFrames();
// Return to caller.
__ Ret();
}
void DebugCodegen::GenerateHandleDebuggerStatement(MacroAssembler* masm) {
{
FrameScope scope(masm, StackFrame::INTERNAL);
__ CallRuntime(Runtime::kHandleDebuggerStatement, 0);
}
__ MaybeDropFrames();
void DebugCodegen::GenerateFrameDropperLiveEdit(MacroAssembler* masm) {
// Load the function pointer off of our current stack frame.
__ ldr(r1, MemOperand(fp, FrameDropperFrameConstants::kFunctionOffset));
// Return to caller.
__ Ret();
}
// Pop return address, frame and constant pool pointer (if
// FLAG_enable_embedded_constant_pool).
void DebugCodegen::GenerateFrameDropperTrampoline(MacroAssembler* masm) {
// Frame is being dropped:
// - Drop to the target frame specified by r1.
// - Look up current function on the frame.
// - Leave the frame.
// - Restart the frame by calling the function.
__ mov(fp, r1);
__ ldr(r1, MemOperand(fp, JavaScriptFrameConstants::kFunctionOffset));
__ LeaveFrame(StackFrame::INTERNAL);
ParameterCount dummy(0);
__ CheckDebugHook(r1, no_reg, dummy, dummy);
__ ldr(r0, FieldMemOperand(r1, JSFunction::kSharedFunctionInfoOffset));
__ ldr(r0,
FieldMemOperand(r0, SharedFunctionInfo::kFormalParameterCountOffset));
__ mov(r2, r0);
{ ConstantPoolUnavailableScope constant_pool_unavailable(masm);
// Load context from the function.
__ ldr(cp, FieldMemOperand(r1, JSFunction::kContextOffset));
// Clear new.target as a safety measure.
__ LoadRoot(r3, Heap::kUndefinedValueRootIndex);
// Get function code.
__ ldr(ip, FieldMemOperand(r1, JSFunction::kSharedFunctionInfoOffset));
__ ldr(ip, FieldMemOperand(ip, SharedFunctionInfo::kCodeOffset));
__ add(ip, ip, Operand(Code::kHeaderSize - kHeapObjectTag));
// Re-run JSFunction, r1 is function, cp is context.
__ Jump(ip);
}
ParameterCount dummy1(r2);
ParameterCount dummy2(r0);
__ InvokeFunction(r1, dummy1, dummy2, JUMP_FUNCTION,
CheckDebugStepCallWrapper());
}

View File

@ -88,12 +88,6 @@ void DebugCodegen::GenerateDebugBreakStub(MacroAssembler* masm,
{
FrameScope scope(masm, StackFrame::INTERNAL);
// Load padding words on stack.
__ Mov(scratch, Smi::FromInt(LiveEdit::kFramePaddingValue));
__ PushMultipleTimes(scratch, LiveEdit::kFramePaddingInitialSize);
__ Mov(scratch, Smi::FromInt(LiveEdit::kFramePaddingInitialSize));
__ Push(scratch);
// Push arguments for DebugBreak call.
if (mode == SAVE_RESULT_REGISTER) {
// Break on return.
@ -119,52 +113,48 @@ void DebugCodegen::GenerateDebugBreakStub(MacroAssembler* masm,
}
}
}
// Don't bother removing padding bytes pushed on the stack
// as the frame is going to be restored right away.
// Leave the internal frame.
}
// Now that the break point has been handled, resume normal execution by
// jumping to the target address intended by the caller and that was
// overwritten by the address of DebugBreakXXX.
ExternalReference after_break_target =
ExternalReference::debug_after_break_target_address(masm->isolate());
__ Mov(scratch, after_break_target);
__ Ldr(scratch, MemOperand(scratch));
__ Br(scratch);
__ MaybeDropFrames();
// Return to caller.
__ Ret();
}
void DebugCodegen::GenerateHandleDebuggerStatement(MacroAssembler* masm) {
{
FrameScope scope(masm, StackFrame::INTERNAL);
__ CallRuntime(Runtime::kHandleDebuggerStatement, 0);
}
__ MaybeDropFrames();
void DebugCodegen::GenerateFrameDropperLiveEdit(MacroAssembler* masm) {
// We do not know our frame height, but set sp based on fp.
__ Add(masm->StackPointer(), fp, FrameDropperFrameConstants::kFunctionOffset);
// Return to caller.
__ Ret();
}
void DebugCodegen::GenerateFrameDropperTrampoline(MacroAssembler* masm) {
// Frame is being dropped:
// - Drop to the target frame specified by x1.
// - Look up current function on the frame.
// - Leave the frame.
// - Restart the frame by calling the function.
__ Mov(fp, x1);
__ AssertStackConsistency();
__ Ldr(x1, MemOperand(fp, JavaScriptFrameConstants::kFunctionOffset));
__ Pop(x1); // Function
__ Mov(masm->StackPointer(), Operand(fp));
__ Pop(fp, lr); // Frame, Return address.
ParameterCount dummy(0);
__ CheckDebugHook(x1, no_reg, dummy, dummy);
__ Ldr(x0, FieldMemOperand(x1, JSFunction::kSharedFunctionInfoOffset));
__ Ldr(x0,
FieldMemOperand(x0, SharedFunctionInfo::kFormalParameterCountOffset));
__ mov(x2, x0);
UseScratchRegisterScope temps(masm);
Register scratch = temps.AcquireX();
// Load context from the function.
__ Ldr(cp, FieldMemOperand(x1, JSFunction::kContextOffset));
// Clear new.target as a safety measure.
__ LoadRoot(x3, Heap::kUndefinedValueRootIndex);
// Get function code.
__ Ldr(scratch, FieldMemOperand(x1, JSFunction::kSharedFunctionInfoOffset));
__ Ldr(scratch, FieldMemOperand(scratch, SharedFunctionInfo::kCodeOffset));
__ Add(scratch, scratch, Code::kHeaderSize - kHeapObjectTag);
// Re-run JSFunction, x1 is function, cp is context.
__ Br(scratch);
ParameterCount dummy1(x2);
ParameterCount dummy2(x0);
__ InvokeFunction(x1, dummy1, dummy2, JUMP_FUNCTION,
CheckDebugStepCallWrapper());
}

View File

@ -401,10 +401,10 @@ void Debug::ThreadInit() {
thread_local_.last_statement_position_ = kNoSourcePosition;
thread_local_.last_fp_ = 0;
thread_local_.target_fp_ = 0;
thread_local_.return_value_ = Handle<Object>();
thread_local_.return_value_ = Smi::kZero;
thread_local_.async_task_count_ = 0;
clear_suspended_generator();
// TODO(isolates): frames_are_dropped_?
thread_local_.restart_fp_ = nullptr;
base::NoBarrier_Store(&thread_local_.current_debug_scope_,
static_cast<base::AtomicWord>(0));
UpdateHookOnFunctionCall();
@ -427,6 +427,7 @@ char* Debug::RestoreDebug(char* storage) {
int Debug::ArchiveSpacePerThread() { return 0; }
void Debug::Iterate(ObjectVisitor* v) {
v->VisitPointer(&thread_local_.return_value_);
v->VisitPointer(&thread_local_.suspended_generator_);
}
@ -1585,14 +1586,6 @@ void Debug::RemoveDebugInfoAndClearFromShared(Handle<DebugInfo> debug_info) {
UNREACHABLE();
}
void Debug::SetAfterBreakTarget(JavaScriptFrame* frame) {
after_break_target_ = NULL;
if (!LiveEdit::SetAfterBreakTarget(this)) {
// Continue just after the slot.
after_break_target_ = frame->pc();
}
}
bool Debug::IsBreakAtReturn(JavaScriptFrame* frame) {
HandleScope scope(isolate_);
@ -1608,12 +1601,25 @@ bool Debug::IsBreakAtReturn(JavaScriptFrame* frame) {
return location.IsReturn() || location.IsTailCall();
}
void Debug::FramesHaveBeenDropped(StackFrame::Id new_break_frame_id,
LiveEditFrameDropMode mode) {
if (mode != LIVE_EDIT_CURRENTLY_SET_MODE) {
thread_local_.frame_drop_mode_ = mode;
void Debug::ScheduleFrameRestart(StackFrame* frame) {
// Set a target FP for the FrameDropperTrampoline builtin to drop to once
// we return from the debugger.
DCHECK(frame->is_java_script());
// Only reschedule to a frame further below a frame we already scheduled for.
if (frame->fp() <= thread_local_.restart_fp_) return;
// If the frame is optimized, trigger a deopt and jump into the
// FrameDropperTrampoline in the deoptimizer.
thread_local_.restart_fp_ = frame->fp();
// Reset break frame ID to the frame below the restarted frame.
StackTraceFrameIterator it(isolate_);
thread_local_.break_frame_id_ = StackFrame::NO_ID;
for (StackTraceFrameIterator it(isolate_); !it.done(); it.Advance()) {
if (it.frame()->fp() > thread_local_.restart_fp_) {
thread_local_.break_frame_id_ = it.frame()->id();
return;
}
}
thread_local_.break_frame_id_ = new_break_frame_id;
}
@ -2202,6 +2208,8 @@ MaybeHandle<Object> Debug::Call(Handle<Object> fun, Handle<Object> data) {
void Debug::HandleDebugBreak() {
// Initialize LiveEdit.
LiveEdit::InitializeThreadLocal(this);
// Ignore debug break during bootstrapping.
if (isolate_->bootstrapper()->IsActive()) return;
// Just continue if breaks are disabled.
@ -2303,7 +2311,7 @@ DebugScope::DebugScope(Debug* debug)
// Store the previous break id, frame id and return value.
break_id_ = debug_->break_id();
break_frame_id_ = debug_->break_frame_id();
return_value_ = debug_->return_value();
return_value_ = handle(debug_->return_value(), isolate());
// Create the new break info. If there is no proper frames there is no break
// frame id.
@ -2337,7 +2345,7 @@ DebugScope::~DebugScope() {
// Restore to the previous break state.
debug_->thread_local_.break_frame_id_ = break_frame_id_;
debug_->thread_local_.break_id_ = break_id_;
debug_->thread_local_.return_value_ = return_value_;
debug_->thread_local_.return_value_ = *return_value_;
debug_->UpdateState();
}

View File

@ -311,7 +311,6 @@ class Debug {
// Internal logic
bool Load();
void Break(JavaScriptFrame* frame);
void SetAfterBreakTarget(JavaScriptFrame* frame);
// Scripts handling.
Handle<FixedArray> GetLoadedScripts();
@ -384,8 +383,7 @@ class Debug {
bool IsBreakAtReturn(JavaScriptFrame* frame);
// Support for LiveEdit
void FramesHaveBeenDropped(StackFrame::Id new_break_frame_id,
LiveEditFrameDropMode mode);
void ScheduleFrameRestart(StackFrame* frame);
bool IsFrameBlackboxed(JavaScriptFrame* frame);
@ -430,10 +428,8 @@ class Debug {
StackFrame::Id break_frame_id() { return thread_local_.break_frame_id_; }
int break_id() { return thread_local_.break_id_; }
Handle<Object> return_value() { return thread_local_.return_value_; }
void set_return_value(Handle<Object> value) {
thread_local_.return_value_ = value;
}
Object* return_value() { return thread_local_.return_value_; }
void set_return_value(Object* value) { thread_local_.return_value_ = value; }
// Support for embedding into generated code.
Address is_active_address() {
@ -444,10 +440,6 @@ class Debug {
return reinterpret_cast<Address>(&hook_on_function_call_);
}
Address after_break_target_address() {
return reinterpret_cast<Address>(&after_break_target_);
}
Address last_step_action_address() {
return reinterpret_cast<Address>(&thread_local_.last_step_action_);
}
@ -456,6 +448,10 @@ class Debug {
return reinterpret_cast<Address>(&thread_local_.suspended_generator_);
}
Address restart_fp_address() {
return reinterpret_cast<Address>(&thread_local_.restart_fp_);
}
StepAction last_step_action() { return thread_local_.last_step_action_; }
DebugFeatureTracker* feature_tracker() { return &feature_tracker_; }
@ -586,11 +582,6 @@ class Debug {
// List of active debug info objects.
DebugInfoListNode* debug_info_list_;
// Storage location for jump when exiting debug break calls.
// Note that this address is not GC safe. It should be computed immediately
// before returning to the DebugBreakCallHelper.
Address after_break_target_;
// Used to collect histogram data on debugger feature usage.
DebugFeatureTracker feature_tracker_;
@ -621,16 +612,15 @@ class Debug {
// Frame pointer of the target frame we want to arrive at.
Address target_fp_;
// Stores the way how LiveEdit has patched the stack. It is used when
// debugger returns control back to user script.
LiveEditFrameDropMode frame_drop_mode_;
// Value of accumulator in interpreter frames. In non-interpreter frames
// this value will be the hole.
Handle<Object> return_value_;
// Value of the accumulator at the point of entering the debugger.
Object* return_value_;
// The suspended generator object to track when stepping.
Object* suspended_generator_;
// The new frame pointer to drop to when restarting a frame.
Address restart_fp_;
int async_task_count_;
};
@ -747,15 +737,15 @@ class DebugCodegen : public AllStatic {
static void GenerateDebugBreakStub(MacroAssembler* masm,
DebugBreakCallHelperMode mode);
// 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.
static void GenerateFrameDropperLiveEdit(MacroAssembler* masm);
static void GenerateSlot(MacroAssembler* masm, RelocInfo::Mode mode);
// Builtin to drop frames to restart function.
static void GenerateFrameDropperTrampoline(MacroAssembler* masm);
// Builtin to atomically (wrt deopts) handle debugger statement and
// drop frames to restart function if necessary.
static void GenerateHandleDebuggerStatement(MacroAssembler* masm);
static void PatchDebugBreakSlot(Isolate* isolate, Address pc,
Handle<Code> code);
static bool DebugBreakSlotIsPatched(Address pc);

View File

@ -64,12 +64,6 @@ void DebugCodegen::GenerateDebugBreakStub(MacroAssembler* masm,
{
FrameScope scope(masm, StackFrame::INTERNAL);
// Load padding words on stack.
for (int i = 0; i < LiveEdit::kFramePaddingInitialSize; i++) {
__ push(Immediate(Smi::FromInt(LiveEdit::kFramePaddingValue)));
}
__ push(Immediate(Smi::FromInt(LiveEdit::kFramePaddingInitialSize)));
// Push arguments for DebugBreak call.
if (mode == SAVE_RESULT_REGISTER) {
// Break on return.
@ -96,54 +90,43 @@ void DebugCodegen::GenerateDebugBreakStub(MacroAssembler* masm,
}
}
}
__ pop(ebx);
// We divide stored value by 2 (untagging) and multiply it by word's size.
STATIC_ASSERT(kSmiTagSize == 1 && kSmiShiftSize == 0);
__ lea(esp, Operand(esp, ebx, times_half_pointer_size, 0));
// Get rid of the internal frame.
}
// This call did not replace a call , so there will be an unwanted
// return address left on the stack. Here we get rid of that.
__ add(esp, Immediate(kPointerSize));
__ MaybeDropFrames();
// Now that the break point has been handled, resume normal execution by
// jumping to the target address intended by the caller and that was
// overwritten by the address of DebugBreakXXX.
ExternalReference after_break_target =
ExternalReference::debug_after_break_target_address(masm->isolate());
__ jmp(Operand::StaticVariable(after_break_target));
// Return to caller.
__ ret(0);
}
void DebugCodegen::GenerateHandleDebuggerStatement(MacroAssembler* masm) {
{
FrameScope scope(masm, StackFrame::INTERNAL);
__ CallRuntime(Runtime::kHandleDebuggerStatement, 0);
}
__ MaybeDropFrames();
void DebugCodegen::GenerateFrameDropperLiveEdit(MacroAssembler* masm) {
// We do not know our frame height, but set esp based on ebp.
__ lea(esp, Operand(ebp, FrameDropperFrameConstants::kFunctionOffset));
__ pop(edi); // Function.
__ add(esp, Immediate(-FrameDropperFrameConstants::kCodeOffset)); // INTERNAL
// frame
// marker
// and code
__ pop(ebp);
// Return to caller.
__ ret(0);
}
ParameterCount dummy(0);
__ CheckDebugHook(edi, no_reg, dummy, dummy);
void DebugCodegen::GenerateFrameDropperTrampoline(MacroAssembler* masm) {
// Frame is being dropped:
// - Drop to the target frame specified by ebx.
// - Look up current function on the frame.
// - Leave the frame.
// - Restart the frame by calling the function.
__ mov(ebp, ebx);
__ mov(edi, Operand(ebp, JavaScriptFrameConstants::kFunctionOffset));
__ leave();
// Load context from the function.
__ mov(esi, FieldOperand(edi, JSFunction::kContextOffset));
// Clear new.target register as a safety measure.
__ mov(edx, masm->isolate()->factory()->undefined_value());
// Get function code.
__ mov(ebx, FieldOperand(edi, JSFunction::kSharedFunctionInfoOffset));
__ mov(ebx, FieldOperand(ebx, SharedFunctionInfo::kCodeOffset));
__ lea(ebx, FieldOperand(ebx, Code::kHeaderSize));
__ mov(ebx,
FieldOperand(ebx, SharedFunctionInfo::kFormalParameterCountOffset));
// Re-run JSFunction, edi is function, esi is context.
__ jmp(ebx);
ParameterCount dummy(ebx);
__ InvokeFunction(edi, dummy, dummy, JUMP_FUNCTION,
CheckDebugStepCallWrapper());
}

View File

@ -652,33 +652,7 @@ Handle<SharedFunctionInfo> SharedInfoWrapper::GetInfo() {
void LiveEdit::InitializeThreadLocal(Debug* debug) {
debug->thread_local_.frame_drop_mode_ = LIVE_EDIT_FRAMES_UNTOUCHED;
}
bool LiveEdit::SetAfterBreakTarget(Debug* debug) {
Code* code = NULL;
Isolate* isolate = debug->isolate_;
switch (debug->thread_local_.frame_drop_mode_) {
case LIVE_EDIT_FRAMES_UNTOUCHED:
return false;
case LIVE_EDIT_FRAME_DROPPED_IN_DEBUG_SLOT_CALL:
// Debug break slot stub does not return normally, instead it manually
// cleans the stack and jumps. We should patch the jump address.
code = isolate->builtins()->builtin(Builtins::kFrameDropper_LiveEdit);
break;
case LIVE_EDIT_FRAME_DROPPED_IN_DIRECT_CALL:
// Nothing to do, after_break_target is not used here.
return true;
case LIVE_EDIT_FRAME_DROPPED_IN_RETURN_CALL:
code = isolate->builtins()->builtin(Builtins::kFrameDropper_LiveEdit);
break;
case LIVE_EDIT_CURRENTLY_SET_MODE:
UNREACHABLE();
break;
}
debug->after_break_target_ = code->entry();
return true;
debug->thread_local_.restart_fp_ = 0;
}
@ -745,47 +719,6 @@ MaybeHandle<JSArray> LiveEdit::GatherCompileInfo(Handle<Script> script,
}
}
// Visitor that finds all references to a particular code object,
// including "CODE_TARGET" references in other code objects and replaces
// them on the fly.
class ReplacingVisitor : public ObjectVisitor {
public:
explicit ReplacingVisitor(Code* original, Code* substitution)
: original_(original), substitution_(substitution) {
}
void VisitPointers(Object** start, Object** end) override {
for (Object** p = start; p < end; p++) {
if (*p == original_) {
*p = substitution_;
}
}
}
void VisitCodeEntry(Address entry) override {
if (Code::GetObjectFromEntryAddress(entry) == original_) {
Address substitution_entry = substitution_->instruction_start();
Memory::Address_at(entry) = substitution_entry;
}
}
void VisitCodeTarget(RelocInfo* rinfo) override {
if (RelocInfo::IsCodeTarget(rinfo->rmode()) &&
Code::GetCodeFromTargetAddress(rinfo->target_address()) == original_) {
Address substitution_entry = substitution_->instruction_start();
rinfo->set_target_address(substitution_entry);
}
}
void VisitDebugTarget(RelocInfo* rinfo) override { VisitCodeTarget(rinfo); }
private:
Code* original_;
Code* substitution_;
};
// Finds all references to original and replaces them with substitution.
static void ReplaceCodeObject(Handle<Code> original,
Handle<Code> substitution) {
@ -796,20 +729,16 @@ static void ReplaceCodeObject(Handle<Code> original,
// write barriers.
Heap* heap = original->GetHeap();
HeapIterator iterator(heap);
DCHECK(!heap->InNewSpace(*substitution));
ReplacingVisitor visitor(*original, *substitution);
// Iterate over all roots. Stack frames may have pointer into original code,
// so temporary replace the pointers with offset numbers
// in prologue/epilogue.
heap->IterateRoots(&visitor, VISIT_ALL);
// Now iterate over all pointers of all objects, including code_target
// implicit pointers.
for (HeapObject* obj = iterator.next(); obj != NULL; obj = iterator.next()) {
obj->Iterate(&visitor);
if (obj->IsJSFunction()) {
JSFunction* fun = JSFunction::cast(obj);
if (fun->code() == *original) fun->ReplaceCode(*substitution);
} else if (obj->IsSharedFunctionInfo()) {
SharedFunctionInfo* info = SharedFunctionInfo::cast(obj);
if (info->code() == *original) info->set_code(*substitution);
}
}
}
@ -1277,185 +1206,6 @@ static bool CheckActivation(Handle<JSArray> shared_info_array,
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_frame->isolate()->get_address_from_id(
Isolate::kHandlerAddress));
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;
}
// Initializes an artificial stack frame. The data it contains is used for:
// a. successful work of frame dropper code which eventually gets control,
// b. being compatible with a typed frame structure for various stack
// iterators.
// Frame structure (conforms to InternalFrame structure):
// -- function
// -- code
// -- SMI marker
// -- frame base
static void SetUpFrameDropperFrame(StackFrame* bottom_js_frame,
Handle<Code> code) {
DCHECK(bottom_js_frame->is_java_script());
Address fp = bottom_js_frame->fp();
Memory::Object_at(fp + FrameDropperFrameConstants::kFunctionOffset) =
Memory::Object_at(fp + StandardFrameConstants::kFunctionOffset);
Memory::Object_at(fp + FrameDropperFrameConstants::kFrameTypeOffset) =
Smi::FromInt(StackFrame::INTERNAL);
Memory::Object_at(fp + FrameDropperFrameConstants::kCodeOffset) = *code;
}
// 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,
LiveEditFrameDropMode* mode) {
if (!LiveEdit::kFrameDropperSupported) {
return "Stack manipulations are not supported in this architecture.";
}
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];
DCHECK(bottom_js_frame->is_java_script());
// Check the nature of the top frame.
Isolate* isolate = bottom_js_frame->isolate();
Code* pre_top_frame_code = pre_top_frame->LookupCode();
bool frame_has_padding = true;
if (pre_top_frame_code ==
isolate->builtins()->builtin(Builtins::kSlot_DebugBreak)) {
// OK, we can drop debug break slot.
*mode = LIVE_EDIT_FRAME_DROPPED_IN_DEBUG_SLOT_CALL;
} else if (pre_top_frame_code ==
isolate->builtins()->builtin(Builtins::kFrameDropper_LiveEdit)) {
// OK, we can drop our own code.
pre_top_frame = frames[top_frame_index - 2];
top_frame = frames[top_frame_index - 1];
*mode = LIVE_EDIT_CURRENTLY_SET_MODE;
frame_has_padding = false;
} else if (pre_top_frame_code ==
isolate->builtins()->builtin(Builtins::kReturn_DebugBreak)) {
*mode = LIVE_EDIT_FRAME_DROPPED_IN_RETURN_CALL;
} else if (pre_top_frame_code->kind() == Code::STUB &&
CodeStub::GetMajorKey(pre_top_frame_code) == CodeStub::CEntry) {
// Entry from our unit tests on 'debugger' statement.
// It's fine, we support this case.
*mode = LIVE_EDIT_FRAME_DROPPED_IN_DIRECT_CALL;
// We don't have a padding from 'debugger' statement call.
// Here the stub is CEntry, it's not debug-only and can't be padded.
// If anyone would complain, a proxy padded stub could be added.
frame_has_padding = false;
} else if (pre_top_frame->type() == StackFrame::ARGUMENTS_ADAPTOR) {
// This must be adaptor that remain from the frame dropping that
// is still on stack. A frame dropper frame must be above it.
DCHECK(frames[top_frame_index - 2]->LookupCode() ==
isolate->builtins()->builtin(Builtins::kFrameDropper_LiveEdit));
pre_top_frame = frames[top_frame_index - 3];
top_frame = frames[top_frame_index - 2];
*mode = LIVE_EDIT_CURRENTLY_SET_MODE;
frame_has_padding = false;
} else if (pre_top_frame_code->kind() == Code::BYTECODE_HANDLER) {
// Interpreted bytecode takes up two stack frames, one for the bytecode
// handler and one for the interpreter entry trampoline. Therefore we shift
// up by one frame.
*mode = LIVE_EDIT_FRAME_DROPPED_IN_DIRECT_CALL;
pre_top_frame = frames[top_frame_index - 2];
top_frame = frames[top_frame_index - 1];
} else {
return "Unknown structure of stack above changing function";
}
Address unused_stack_top = top_frame->sp();
Address unused_stack_bottom =
bottom_js_frame->fp() - FrameDropperFrameConstants::kFixedFrameSize +
2 * kPointerSize; // Bigger address end is exclusive.
Address* top_frame_pc_address = top_frame->pc_address();
// top_frame may be damaged below this point. Do not used it.
DCHECK(!(top_frame = NULL));
if (unused_stack_top > unused_stack_bottom) {
if (frame_has_padding) {
int shortage_bytes =
static_cast<int>(unused_stack_top - unused_stack_bottom);
Address padding_start =
pre_top_frame->fp() -
(FrameDropperFrameConstants::kFixedFrameSize - kPointerSize);
Address padding_pointer = padding_start;
Smi* padding_object = Smi::FromInt(LiveEdit::kFramePaddingValue);
while (Memory::Object_at(padding_pointer) == padding_object) {
padding_pointer -= kPointerSize;
}
int padding_counter =
Smi::cast(Memory::Object_at(padding_pointer))->value();
if (padding_counter * kPointerSize < shortage_bytes) {
return "Not enough space for frame dropper frame "
"(even with padding frame)";
}
Memory::Object_at(padding_pointer) =
Smi::FromInt(padding_counter - shortage_bytes / kPointerSize);
StackFrame* pre_pre_frame = frames[top_frame_index - 2];
MemMove(padding_start + kPointerSize - shortage_bytes,
padding_start + kPointerSize,
FrameDropperFrameConstants::kFixedFrameSize - kPointerSize);
pre_top_frame->UpdateFp(pre_top_frame->fp() - shortage_bytes);
pre_pre_frame->SetCallerFp(pre_top_frame->fp());
unused_stack_top -= shortage_bytes;
STATIC_ASSERT(sizeof(Address) == kPointerSize);
top_frame_pc_address -= shortage_bytes / kPointerSize;
} else {
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.
DCHECK(!FixTryCatchHandler(pre_top_frame, bottom_js_frame));
Handle<Code> code = isolate->builtins()->FrameDropper_LiveEdit();
*top_frame_pc_address = code->entry();
pre_top_frame->SetCallerFp(bottom_js_frame->fp());
SetUpFrameDropperFrame(bottom_js_frame, code);
for (Address a = unused_stack_top;
a < unused_stack_bottom;
a += kPointerSize) {
Memory::Object_at(a) = Smi::kZero;
}
return NULL;
}
// Describes a set of call frames that execute any of listed functions.
// Finding no such frames does not mean error.
class MultipleFunctionTarget {
@ -1543,7 +1293,6 @@ static const char* DropActivationsInActiveThreadImpl(Isolate* isolate,
Zone zone(isolate->allocator(), ZONE_NAME);
Vector<StackFrame*> frames = CreateStackMap(isolate, &zone);
int top_frame_index = -1;
int frame_index = 0;
for (; frame_index < frames.length(); frame_index++) {
@ -1628,24 +1377,11 @@ static const char* DropActivationsInActiveThreadImpl(Isolate* isolate,
return target.GetNotFoundMessage();
}
LiveEditFrameDropMode drop_mode = LIVE_EDIT_FRAMES_UNTOUCHED;
const char* error_message =
DropFrames(frames, top_frame_index, bottom_js_frame_index, &drop_mode);
if (error_message != NULL) {
return error_message;
if (!LiveEdit::kFrameDropperSupported) {
return "Stack manipulations are not supported in this architecture.";
}
// 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 ||
frames[i]->type() == StackFrame::INTERPRETED) {
new_id = frames[i]->id();
break;
}
}
debug->FramesHaveBeenDropped(new_id, drop_mode);
debug->ScheduleFrameRestart(frames[bottom_js_frame_index]);
return NULL;
}

View File

@ -74,8 +74,6 @@ class LiveEdit : AllStatic {
public:
static void InitializeThreadLocal(Debug* debug);
static bool SetAfterBreakTarget(Debug* debug);
MUST_USE_RESULT static MaybeHandle<JSArray> GatherCompileInfo(
Handle<Script> script,
Handle<String> source);
@ -146,40 +144,6 @@ class LiveEdit : AllStatic {
// Architecture-specific constant.
static const bool kFrameDropperSupported;
/**
* Defines layout of a stack frame that supports padding. This is a regular
* internal frame that has a flexible stack structure. LiveEdit can shift
* its lower part up the stack, taking up the 'padding' space when additional
* stack memory is required.
* Such frame is expected immediately above the topmost JavaScript frame.
*
* Stack Layout:
* --- Top
* LiveEdit routine frames
* ---
* C frames of debug handler
* ---
* ...
* ---
* An internal frame that has n padding words:
* - any number of words as needed by code -- upper part of frame
* - padding size: a Smi storing n -- current size of padding
* - padding: n words filled with kPaddingValue in form of Smi
* - 3 context/type words of a regular InternalFrame
* - fp
* ---
* Topmost JavaScript frame
* ---
* ...
* --- Bottom
*/
// A number of words that should be reserved on stack for the LiveEdit use.
// Stored on stack in form of Smi.
static const int kFramePaddingInitialSize = 1;
// A value that padding words are filled with (in form of Smi). Going
// bottom-top, the first word not having this value is a counter word.
static const int kFramePaddingValue = kFramePaddingInitialSize + 1;
};

View File

@ -69,16 +69,6 @@ void DebugCodegen::GenerateDebugBreakStub(MacroAssembler* masm,
{
FrameScope scope(masm, StackFrame::INTERNAL);
// Load padding words on stack.
__ li(at, Operand(Smi::FromInt(LiveEdit::kFramePaddingValue)));
__ Subu(sp, sp,
Operand(kPointerSize * LiveEdit::kFramePaddingInitialSize));
for (int i = LiveEdit::kFramePaddingInitialSize - 1; i >= 0; i--) {
__ sw(at, MemOperand(sp, kPointerSize * i));
}
__ li(at, Operand(Smi::FromInt(LiveEdit::kFramePaddingInitialSize)));
__ push(at);
// Push arguments for DebugBreak call.
if (mode == SAVE_RESULT_REGISTER) {
// Break on return.
@ -104,47 +94,47 @@ void DebugCodegen::GenerateDebugBreakStub(MacroAssembler* masm,
}
}
}
// Don't bother removing padding bytes pushed on the stack
// as the frame is going to be restored right away.
// Leave the internal frame.
}
// Now that the break point has been handled, resume normal execution by
// jumping to the target address intended by the caller and that was
// overwritten by the address of DebugBreakXXX.
ExternalReference after_break_target =
ExternalReference::debug_after_break_target_address(masm->isolate());
__ li(t9, Operand(after_break_target));
__ lw(t9, MemOperand(t9));
__ Jump(t9);
__ MaybeDropFrames();
// Return to caller.
__ Ret();
}
void DebugCodegen::GenerateHandleDebuggerStatement(MacroAssembler* masm) {
{
FrameScope scope(masm, StackFrame::INTERNAL);
__ CallRuntime(Runtime::kHandleDebuggerStatement, 0);
}
__ MaybeDropFrames();
void DebugCodegen::GenerateFrameDropperLiveEdit(MacroAssembler* masm) {
// We do not know our frame height, but set sp based on fp.
__ lw(a1, MemOperand(fp, FrameDropperFrameConstants::kFunctionOffset));
// Return to caller.
__ Ret();
}
void DebugCodegen::GenerateFrameDropperTrampoline(MacroAssembler* masm) {
// Frame is being dropped:
// - Drop to the target frame specified by a1.
// - Look up current function on the frame.
// - Leave the frame.
// - Restart the frame by calling the function.
__ mov(fp, a1);
__ lw(a1, MemOperand(fp, JavaScriptFrameConstants::kFunctionOffset));
// Pop return address and frame.
__ LeaveFrame(StackFrame::INTERNAL);
ParameterCount dummy(0);
__ CheckDebugHook(a1, no_reg, dummy, dummy);
__ lw(a0, FieldMemOperand(a1, JSFunction::kSharedFunctionInfoOffset));
__ lw(a0,
FieldMemOperand(a0, SharedFunctionInfo::kFormalParameterCountOffset));
__ mov(a2, a0);
// Load context from the function.
__ lw(cp, FieldMemOperand(a1, JSFunction::kContextOffset));
// Clear new.target as a safety measure.
__ LoadRoot(a3, Heap::kUndefinedValueRootIndex);
// Get function code.
__ lw(at, FieldMemOperand(a1, JSFunction::kSharedFunctionInfoOffset));
__ lw(at, FieldMemOperand(at, SharedFunctionInfo::kCodeOffset));
__ Addu(t9, at, Operand(Code::kHeaderSize - kHeapObjectTag));
// Re-run JSFunction, a1 is function, cp is context.
__ Jump(t9);
ParameterCount dummy1(a2);
ParameterCount dummy2(a0);
__ InvokeFunction(a1, dummy1, dummy2, JUMP_FUNCTION,
CheckDebugStepCallWrapper());
}

View File

@ -65,22 +65,23 @@ bool DebugCodegen::DebugBreakSlotIsPatched(Address pc) {
return !Assembler::IsNop(current_instr, Assembler::DEBUG_BREAK_NOP);
}
void DebugCodegen::GenerateHandleDebuggerStatement(MacroAssembler* masm) {
{
FrameScope scope(masm, StackFrame::INTERNAL);
__ CallRuntime(Runtime::kHandleDebuggerStatement, 0);
}
__ MaybeDropFrames();
// Return to caller.
__ Ret();
}
void DebugCodegen::GenerateDebugBreakStub(MacroAssembler* masm,
DebugBreakCallHelperMode mode) {
__ RecordComment("Debug break");
{
FrameScope scope(masm, StackFrame::INTERNAL);
// Load padding words on stack.
__ li(at, Operand(Smi::FromInt(LiveEdit::kFramePaddingValue)));
__ Dsubu(sp, sp,
Operand(kPointerSize * LiveEdit::kFramePaddingInitialSize));
for (int i = LiveEdit::kFramePaddingInitialSize - 1; i >= 0; i--) {
__ sd(at, MemOperand(sp, kPointerSize * i));
}
__ li(at, Operand(Smi::FromInt(LiveEdit::kFramePaddingInitialSize)));
__ push(at);
// Push arguments for DebugBreak call.
if (mode == SAVE_RESULT_REGISTER) {
// Break on return.
@ -107,46 +108,36 @@ void DebugCodegen::GenerateDebugBreakStub(MacroAssembler* masm,
}
}
// Don't bother removing padding bytes pushed on the stack
// as the frame is going to be restored right away.
// Leave the internal frame.
}
// Now that the break point has been handled, resume normal execution by
// jumping to the target address intended by the caller and that was
// overwritten by the address of DebugBreakXXX.
ExternalReference after_break_target =
ExternalReference::debug_after_break_target_address(masm->isolate());
__ li(t9, Operand(after_break_target));
__ ld(t9, MemOperand(t9));
__ Jump(t9);
__ MaybeDropFrames();
// Return to caller.
__ Ret();
}
void DebugCodegen::GenerateFrameDropperLiveEdit(MacroAssembler* masm) {
// We do not know our frame height, but set sp based on fp.
__ ld(a1, MemOperand(fp, FrameDropperFrameConstants::kFunctionOffset));
void DebugCodegen::GenerateFrameDropperTrampoline(MacroAssembler* masm) {
// Frame is being dropped:
// - Drop to the target frame specified by a1.
// - Look up current function on the frame.
// - Leave the frame.
// - Restart the frame by calling the function.
__ mov(fp, a1);
__ ld(a1, MemOperand(fp, JavaScriptFrameConstants::kFunctionOffset));
// Pop return address and frame.
__ LeaveFrame(StackFrame::INTERNAL);
ParameterCount dummy(0);
__ CheckDebugHook(a1, no_reg, dummy, dummy);
__ ld(a0, FieldMemOperand(a1, JSFunction::kSharedFunctionInfoOffset));
__ ld(a0,
FieldMemOperand(a0, SharedFunctionInfo::kFormalParameterCountOffset));
__ mov(a2, a0);
// Load context from the function.
__ ld(cp, FieldMemOperand(a1, JSFunction::kContextOffset));
// Clear new.target as a safety measure.
__ LoadRoot(a3, Heap::kUndefinedValueRootIndex);
// Get function code.
__ ld(at, FieldMemOperand(a1, JSFunction::kSharedFunctionInfoOffset));
__ ld(at, FieldMemOperand(at, SharedFunctionInfo::kCodeOffset));
__ Daddu(t9, at, Operand(Code::kHeaderSize - kHeapObjectTag));
// Re-run JSFunction, a1 is function, cp is context.
__ Jump(t9);
ParameterCount dummy1(a2);
ParameterCount dummy2(a0);
__ InvokeFunction(a1, dummy1, dummy2, JUMP_FUNCTION,
CheckDebugStepCallWrapper());
}

View File

@ -64,12 +64,6 @@ void DebugCodegen::GenerateDebugBreakStub(MacroAssembler* masm,
{
FrameScope scope(masm, StackFrame::INTERNAL);
// Load padding words on stack.
for (int i = 0; i < LiveEdit::kFramePaddingInitialSize; i++) {
__ Push(Smi::FromInt(LiveEdit::kFramePaddingValue));
}
__ Push(Smi::FromInt(LiveEdit::kFramePaddingInitialSize));
// Push arguments for DebugBreak call.
if (mode == SAVE_RESULT_REGISTER) {
// Break on return.
@ -78,12 +72,8 @@ void DebugCodegen::GenerateDebugBreakStub(MacroAssembler* masm,
// Non-return breaks.
__ Push(masm->isolate()->factory()->the_hole_value());
}
__ Set(rax, 1);
__ Move(rbx, ExternalReference(Runtime::FunctionForId(Runtime::kDebugBreak),
masm->isolate()));
CEntryStub ceb(masm->isolate(), 1);
__ CallStub(&ceb);
__ CallRuntime(Runtime::kDebugBreak, 1, kDontSaveFPRegs);
if (FLAG_debug_code) {
for (int i = 0; i < kNumJSCallerSaved; ++i) {
@ -95,55 +85,43 @@ void DebugCodegen::GenerateDebugBreakStub(MacroAssembler* masm,
}
}
}
// Read current padding counter and skip corresponding number of words.
__ Pop(kScratchRegister);
__ SmiToInteger32(kScratchRegister, kScratchRegister);
__ leap(rsp, Operand(rsp, kScratchRegister, times_pointer_size, 0));
// Get rid of the internal frame.
}
// This call did not replace a call , so there will be an unwanted
// return address left on the stack. Here we get rid of that.
__ addp(rsp, Immediate(kPCOnStackSize));
__ MaybeDropFrames();
// Now that the break point has been handled, resume normal execution by
// jumping to the target address intended by the caller and that was
// overwritten by the address of DebugBreakXXX.
ExternalReference after_break_target =
ExternalReference::debug_after_break_target_address(masm->isolate());
__ Move(kScratchRegister, after_break_target);
__ Jump(Operand(kScratchRegister, 0));
// Return to caller.
__ ret(0);
}
void DebugCodegen::GenerateHandleDebuggerStatement(MacroAssembler* masm) {
{
FrameScope scope(masm, StackFrame::INTERNAL);
__ CallRuntime(Runtime::kHandleDebuggerStatement, 0);
}
__ MaybeDropFrames();
void DebugCodegen::GenerateFrameDropperLiveEdit(MacroAssembler* masm) {
// We do not know our frame height, but set rsp based on rbp.
__ leap(rsp, Operand(rbp, FrameDropperFrameConstants::kFunctionOffset));
__ Pop(rdi); // Function.
__ addp(rsp,
Immediate(-FrameDropperFrameConstants::kCodeOffset)); // INTERNAL
// frame marker
// and code
__ popq(rbp);
// Return to caller.
__ ret(0);
}
ParameterCount dummy(0);
__ CheckDebugHook(rdi, no_reg, dummy, dummy);
void DebugCodegen::GenerateFrameDropperTrampoline(MacroAssembler* masm) {
// Frame is being dropped:
// - Drop to the target frame specified by rbx.
// - Look up current function on the frame.
// - Leave the frame.
// - Restart the frame by calling the function.
__ movp(rbp, rbx);
__ movp(rdi, Operand(rbp, JavaScriptFrameConstants::kFunctionOffset));
__ leave();
// Load context from the function.
__ movp(rsi, FieldOperand(rdi, JSFunction::kContextOffset));
// Clear new.target as a safety measure.
__ LoadRoot(rdx, Heap::kUndefinedValueRootIndex);
// Get function code.
__ movp(rbx, FieldOperand(rdi, JSFunction::kSharedFunctionInfoOffset));
__ movp(rbx, FieldOperand(rbx, SharedFunctionInfo::kCodeOffset));
__ leap(rbx, FieldOperand(rbx, Code::kHeaderSize));
__ LoadSharedFunctionInfoSpecialField(
rbx, rbx, SharedFunctionInfo::kFormalParameterCountOffset);
// Re-run JSFunction, rdi is function, rsi is context.
__ jmp(rbx);
ParameterCount dummy(rbx);
__ InvokeFunction(rdi, no_reg, dummy, dummy, JUMP_FUNCTION,
CheckDebugStepCallWrapper());
}
const bool LiveEdit::kFrameDropperSupported = true;

View File

@ -258,8 +258,6 @@ void ExternalReferenceTable::AddReferences(Isolate* isolate) {
"Isolate::promise_hook_or_debug_is_active_address()");
// Debug addresses
Add(ExternalReference::debug_after_break_target_address(isolate).address(),
"Debug::after_break_target_address()");
Add(ExternalReference::debug_is_active_address(isolate).address(),
"Debug::is_active_address()");
Add(ExternalReference::debug_hook_on_function_call_address(isolate).address(),
@ -268,6 +266,8 @@ void ExternalReferenceTable::AddReferences(Isolate* isolate) {
"Debug::step_in_enabled_address()");
Add(ExternalReference::debug_suspended_generator_address(isolate).address(),
"Debug::step_suspended_generator_address()");
Add(ExternalReference::debug_restart_fp_address(isolate).address(),
"Debug::restart_fp_address()");
#ifndef V8_INTERPRETED_REGEXP
Add(ExternalReference::re_case_insensitive_compare_uc16(isolate).address(),

View File

@ -1232,7 +1232,7 @@ void FullCodeGenerator::VisitDebuggerStatement(DebuggerStatement* stmt) {
SetStatementPosition(stmt);
__ DebugBreak();
// Ignore the return value.
__ MaybeDropFrames();
PrepareForBailoutForId(stmt->DebugBreakId(), BailoutState::NO_REGISTERS);
}

View File

@ -1274,20 +1274,6 @@ class CompareOperationFeedback {
};
};
// Describes how exactly a frame has been dropped from stack.
enum LiveEditFrameDropMode {
// No frame has been dropped.
LIVE_EDIT_FRAMES_UNTOUCHED,
// The top JS frame had been calling debug break slot stub. Patch the
// address this stub jumps to in the end.
LIVE_EDIT_FRAME_DROPPED_IN_DEBUG_SLOT_CALL,
// The top JS frame had been calling some C++ function. The return address
// gets patched automatically.
LIVE_EDIT_FRAME_DROPPED_IN_DIRECT_CALL,
LIVE_EDIT_FRAME_DROPPED_IN_RETURN_CALL,
LIVE_EDIT_CURRENTLY_SET_MODE
};
enum class UnicodeEncoding : uint8_t {
// Different unicode encodings in a |word32|:
UTF16, // hi 16bits -> trailing surrogate or 0, low 16bits -> lead surrogate

View File

@ -419,6 +419,14 @@ void ResumeGeneratorDescriptor::InitializePlatformSpecific(
data->InitializePlatformSpecific(arraysize(registers), registers);
}
void FrameDropperTrampolineDescriptor::InitializePlatformSpecific(
CallInterfaceDescriptorData* data) {
Register registers[] = {
ebx, // loaded new FP
};
data->InitializePlatformSpecific(arraysize(registers), registers);
}
} // namespace internal
} // namespace v8

View File

@ -681,6 +681,16 @@ void MacroAssembler::DebugBreak() {
call(ces.GetCode(), RelocInfo::DEBUGGER_STATEMENT);
}
void MacroAssembler::MaybeDropFrames() {
// Check whether we need to drop frames to restart a function on the stack.
ExternalReference restart_fp =
ExternalReference::debug_restart_fp_address(isolate());
mov(ebx, Operand::StaticVariable(restart_fp));
test(ebx, ebx);
j(not_zero, isolate()->builtins()->FrameDropperTrampoline(),
RelocInfo::CODE_TARGET);
}
void MacroAssembler::Cvtsi2sd(XMMRegister dst, const Operand& src) {
xorps(dst, dst);
cvtsi2sd(dst, src);
@ -1916,6 +1926,7 @@ void MacroAssembler::InvokePrologue(const ParameterCount& expected,
DCHECK(actual.reg().is(eax));
DCHECK(expected.reg().is(ebx));
} else {
definitely_matches = true;
Move(eax, actual.reg());
}
}

View File

@ -232,6 +232,7 @@ class MacroAssembler: public Assembler {
// Debugger Support
void DebugBreak();
void MaybeDropFrames();
// Generates function and stub prologue code.
void StubPrologue(StackFrame::Type type);

View File

@ -607,5 +607,13 @@ void InterpreterCEntryDescriptor::InitializePlatformIndependent(
machine_types);
}
void FrameDropperTrampolineDescriptor::InitializePlatformIndependent(
CallInterfaceDescriptorData* data) {
// New FP value.
MachineType machine_types[] = {MachineType::Pointer()};
data->InitializePlatformIndependent(arraysize(machine_types), 0,
machine_types);
}
} // namespace internal
} // namespace v8

View File

@ -100,6 +100,7 @@ class PlatformInterfaceDescriptor;
V(InterpreterPushArgsAndConstructArray) \
V(InterpreterCEntry) \
V(ResumeGenerator) \
V(FrameDropperTrampoline) \
V(PromiseHandleReject)
class V8_EXPORT_PRIVATE CallInterfaceDescriptorData {
@ -891,6 +892,11 @@ class ResumeGeneratorDescriptor final : public CallInterfaceDescriptor {
DECLARE_DESCRIPTOR(ResumeGeneratorDescriptor, CallInterfaceDescriptor)
};
class FrameDropperTrampolineDescriptor final : public CallInterfaceDescriptor {
DECLARE_DESCRIPTOR_WITH_CUSTOM_FUNCTION_TYPE(FrameDropperTrampolineDescriptor,
CallInterfaceDescriptor)
};
class PromiseHandleRejectDescriptor final : public CallInterfaceDescriptor {
public:
DEFINE_PARAMETERS(kPromise, kOnReject, kException)

View File

@ -1223,6 +1223,26 @@ void InterpreterAssembler::AbortIfWordNotEqual(Node* lhs, Node* rhs,
Bind(&ok);
}
void InterpreterAssembler::MaybeDropFrames(Node* context) {
Node* restart_fp_address =
ExternalConstant(ExternalReference::debug_restart_fp_address(isolate()));
Node* restart_fp = Load(MachineType::Pointer(), restart_fp_address);
Node* null = IntPtrConstant(0);
Label ok(this), drop_frames(this);
Branch(IntPtrEqual(restart_fp, null), &ok, &drop_frames);
Bind(&drop_frames);
// We don't expect this call to return since the frame dropper tears down
// the stack and jumps into the function on the target frame to restart it.
CallStub(CodeFactory::FrameDropperTrampoline(isolate()), context, restart_fp);
Abort(kUnexpectedReturnFromFrameDropper);
Goto(&ok);
Bind(&ok);
}
void InterpreterAssembler::TraceBytecode(Runtime::FunctionId function_id) {
CallRuntime(function_id, GetContext(), BytecodeArrayTaggedPointer(),
SmiTag(BytecodeOffset()), GetAccumulatorUnchecked());

View File

@ -217,6 +217,9 @@ class V8_EXPORT_PRIVATE InterpreterAssembler : public CodeStubAssembler {
void AbortIfWordNotEqual(compiler::Node* lhs, compiler::Node* rhs,
BailoutReason bailout_reason);
// Dispatch to frame dropper trampoline if necessary.
void MaybeDropFrames(compiler::Node* context);
// Returns the offset from the BytecodeArrayPointer of the current bytecode.
compiler::Node* BytecodeOffset();

View File

@ -3023,7 +3023,7 @@ void Interpreter::DoReturn(InterpreterAssembler* assembler) {
// Call runtime to handle debugger statement.
void Interpreter::DoDebugger(InterpreterAssembler* assembler) {
Node* context = __ GetContext();
__ CallRuntime(Runtime::kHandleDebuggerStatement, context);
__ CallStub(CodeFactory::HandleDebuggerStatement(isolate_), context);
__ Dispatch();
}
@ -3036,6 +3036,7 @@ void Interpreter::DoDebugger(InterpreterAssembler* assembler) {
Node* accumulator = __ GetAccumulator(); \
Node* original_handler = \
__ CallRuntime(Runtime::kDebugBreakOnBytecode, context, accumulator); \
__ MaybeDropFrames(context); \
__ DispatchToBytecodeHandler(original_handler); \
}
DEBUG_BREAK_BYTECODE_LIST(DEBUG_BREAK);

View File

@ -415,6 +415,14 @@ void ResumeGeneratorDescriptor::InitializePlatformSpecific(
data->InitializePlatformSpecific(arraysize(registers), registers);
}
void FrameDropperTrampolineDescriptor::InitializePlatformSpecific(
CallInterfaceDescriptorData* data) {
Register registers[] = {
a1, // loaded new FP
};
data->InitializePlatformSpecific(arraysize(registers), registers);
}
} // namespace internal
} // namespace v8

View File

@ -3895,6 +3895,15 @@ void MacroAssembler::DebugBreak() {
Call(ces.GetCode(), RelocInfo::DEBUGGER_STATEMENT);
}
void MacroAssembler::MaybeDropFrames() {
// Check whether we need to drop frames to restart a function on the stack.
ExternalReference restart_fp =
ExternalReference::debug_restart_fp_address(isolate());
li(a1, Operand(restart_fp));
lw(a1, MemOperand(a1));
Jump(isolate()->builtins()->FrameDropperTrampoline(), RelocInfo::CODE_TARGET,
ne, a1, Operand(zero_reg));
}
// ---------------------------------------------------------------------------
// Exception handling.

View File

@ -1084,6 +1084,7 @@ class MacroAssembler: public Assembler {
// Debugger Support.
void DebugBreak();
void MaybeDropFrames();
// -------------------------------------------------------------------------
// Exception handling.

View File

@ -414,6 +414,14 @@ void ResumeGeneratorDescriptor::InitializePlatformSpecific(
data->InitializePlatformSpecific(arraysize(registers), registers);
}
void FrameDropperTrampolineDescriptor::InitializePlatformSpecific(
CallInterfaceDescriptorData* data) {
Register registers[] = {
a1, // loaded new FP
};
data->InitializePlatformSpecific(arraysize(registers), registers);
}
} // namespace internal
} // namespace v8

View File

@ -4050,6 +4050,15 @@ void MacroAssembler::DebugBreak() {
Call(ces.GetCode(), RelocInfo::DEBUGGER_STATEMENT);
}
void MacroAssembler::MaybeDropFrames() {
// Check whether we need to drop frames to restart a function on the stack.
ExternalReference restart_fp =
ExternalReference::debug_restart_fp_address(isolate());
li(a1, Operand(restart_fp));
ld(a1, MemOperand(a1));
Jump(isolate()->builtins()->FrameDropperTrampoline(), RelocInfo::CODE_TARGET,
ne, a1, Operand(zero_reg));
}
// ---------------------------------------------------------------------------
// Exception handling.

View File

@ -1138,6 +1138,7 @@ class MacroAssembler: public Assembler {
// Debugger Support.
void DebugBreak();
void MaybeDropFrames();
// -------------------------------------------------------------------------
// Exception handling.

View File

@ -27,29 +27,24 @@ RUNTIME_FUNCTION(Runtime_DebugBreak) {
SealHandleScope shs(isolate);
DCHECK_EQ(1, args.length());
CONVERT_ARG_HANDLE_CHECKED(Object, value, 0);
isolate->debug()->set_return_value(value);
isolate->debug()->set_return_value(*value);
// Get the top-most JavaScript frame.
JavaScriptFrameIterator it(isolate);
isolate->debug()->Break(it.frame());
isolate->debug()->SetAfterBreakTarget(it.frame());
return *isolate->debug()->return_value();
return isolate->debug()->return_value();
}
RUNTIME_FUNCTION(Runtime_DebugBreakOnBytecode) {
SealHandleScope shs(isolate);
DCHECK_EQ(1, args.length());
CONVERT_ARG_HANDLE_CHECKED(Object, value, 0);
isolate->debug()->set_return_value(value);
isolate->debug()->set_return_value(*value);
// Get the top-most JavaScript frame.
JavaScriptFrameIterator it(isolate);
isolate->debug()->Break(it.frame());
// If live-edit has dropped frames, we are not going back to dispatch.
if (LiveEdit::SetAfterBreakTarget(isolate->debug())) return Smi::kZero;
// Return the handler from the original bytecode array.
DCHECK(it.frame()->is_interpreted());
InterpretedFrame* interpreted_frame =
@ -632,7 +627,7 @@ RUNTIME_FUNCTION(Runtime_GetFrameDetails) {
// to the frame information.
Handle<Object> return_value = isolate->factory()->undefined_value();
if (at_return) {
return_value = isolate->debug()->return_value();
return_value = handle(isolate->debug()->return_value(), isolate);
}
// Now advance to the arguments adapter frame (if any). It contains all
@ -1443,26 +1438,6 @@ RUNTIME_FUNCTION(Runtime_FunctionGetDebugName) {
}
// 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.
RUNTIME_FUNCTION(Runtime_ExecuteInDebugContext) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
CONVERT_ARG_HANDLE_CHECKED(JSFunction, function, 0);
DebugScope debug_scope(isolate->debug());
if (debug_scope.failed()) {
DCHECK(isolate->has_pending_exception());
return isolate->heap()->exception();
}
RETURN_RESULT_OR_FAILURE(
isolate, Execution::Call(isolate, function,
handle(function->global_proxy()), 0, NULL));
}
RUNTIME_FUNCTION(Runtime_GetDebugContext) {
HandleScope scope(isolate);
DCHECK_EQ(0, args.length());

View File

@ -181,7 +181,6 @@ namespace internal {
F(DebugSetScriptSource, 2, 1) \
F(FunctionGetInferredName, 1, 1) \
F(FunctionGetDebugName, 1, 1) \
F(ExecuteInDebugContext, 1, 1) \
F(GetDebugContext, 0, 1) \
F(CollectGarbage, 1, 1) \
F(GetHeapUsage, 0, 1) \

View File

@ -415,6 +415,14 @@ void ResumeGeneratorDescriptor::InitializePlatformSpecific(
data->InitializePlatformSpecific(arraysize(registers), registers);
}
void FrameDropperTrampolineDescriptor::InitializePlatformSpecific(
CallInterfaceDescriptorData* data) {
Register registers[] = {
rbx, // loaded new FP
};
data->InitializePlatformSpecific(arraysize(registers), registers);
}
} // namespace internal
} // namespace v8

View File

@ -4088,14 +4088,19 @@ void MacroAssembler::DecrementCounter(StatsCounter* counter, int value) {
}
}
void MacroAssembler::DebugBreak() {
Set(rax, 0); // No arguments.
LoadAddress(rbx,
ExternalReference(Runtime::kHandleDebuggerStatement, isolate()));
CEntryStub ces(isolate(), 1);
DCHECK(AllowThisStubCall(&ces));
Call(ces.GetCode(), RelocInfo::DEBUGGER_STATEMENT);
Call(isolate()->builtins()->HandleDebuggerStatement(),
RelocInfo::DEBUGGER_STATEMENT);
}
void MacroAssembler::MaybeDropFrames() {
// Check whether we need to drop frames to restart a function on the stack.
ExternalReference restart_fp =
ExternalReference::debug_restart_fp_address(isolate());
Load(rbx, restart_fp);
testp(rbx, rbx);
j(not_zero, isolate()->builtins()->FrameDropperTrampoline(),
RelocInfo::CODE_TARGET);
}
void MacroAssembler::PrepareForTailCall(const ParameterCount& callee_args_count,
@ -4297,6 +4302,7 @@ void MacroAssembler::InvokePrologue(const ParameterCount& expected,
DCHECK(actual.reg().is(rax));
DCHECK(expected.reg().is(rbx));
} else {
definitely_matches = true;
Move(rax, actual.reg());
}
}

View File

@ -327,6 +327,7 @@ class MacroAssembler: public Assembler {
// Debugger Support
void DebugBreak();
void MaybeDropFrames();
// Generates function and stub prologue code.
void StubPrologue(StackFrame::Type type);

View File

@ -298,9 +298,6 @@ TEST(DisasmIa320) {
__ jmp(&L1);
__ jmp(Operand(ebx, ecx, times_4, 10000));
ExternalReference after_break_target =
ExternalReference::debug_after_break_target_address(isolate);
__ jmp(Operand::StaticVariable(after_break_target));
__ jmp(ic, RelocInfo::CODE_TARGET);
__ nop();

View File

@ -290,9 +290,6 @@ TEST(DisasmX64) {
__ jmp(&L1);
// TODO(mstarzinger): The following is protected.
// __ jmp(Operand(rbx, rcx, times_4, 10000));
ExternalReference after_break_target =
ExternalReference::debug_after_break_target_address(isolate);
USE(after_break_target);
__ jmp(ic, RelocInfo::CODE_TARGET);
__ nop();

View File

@ -89,9 +89,28 @@ function WrapInNativeCall(f) {
};
}
function ExecuteInDebugContext(f) {
var result;
var exception = null;
Debug.setListener(function(event) {
if (event == Debug.DebugEvent.Break) {
try {
result = f();
} catch (e) {
// Rethrow this exception later.
exception = e;
}
}
});
debugger;
Debug.setListener(null);
if (exception !== null) throw exception;
return result;
}
function WrapInDebuggerCall(f) {
return function() {
return %ExecuteInDebugContext(f);
return ExecuteInDebugContext(f);
};
}

View File

@ -0,0 +1,33 @@
// Copyright 2017 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.
Debug = debug.Debug
var counter = 0;
var exception = null;
function f() {
if (++counter > 5) return;
debugger;
return counter;
}
function listener(event, exec_state, event_data, data) {
if (event != Debug.DebugEvent.Break) return;
try {
var script = Debug.findScript(f);
var original = 'debugger;';
var patch = 'debugger;\n';
var position = script.source.indexOf(original);
Debug.LiveEdit.TestApi.ApplySingleChunkPatch(
script, position, original.length, patch, []);
} catch (e) {
exception = e;
}
}
Debug.setListener(listener);
f();
Debug.setListener(null);
assertNull(exception);
assertEquals(6, counter);

View File

@ -22,8 +22,27 @@ eval(`
function Dummy() {}
function ExecuteInDebugContext(f) {
var result;
var exception = null;
Debug.setListener(function(event) {
if (event == Debug.DebugEvent.Break) {
try {
result = f();
} catch (e) {
// Rethrow this exception later.
exception = e;
}
}
});
debugger;
Debug.setListener(null);
if (exception !== null) throw exception;
return result;
}
function Replace(fun, original, patch) {
%ExecuteInDebugContext(function() {
ExecuteInDebugContext(function() {
var change_log = [];
try {
var script = Debug.findScript(fun);

View File

@ -19,8 +19,27 @@ eval(`
}
`);
function ExecuteInDebugContext(f) {
var result;
var exception = null;
Debug.setListener(function(event) {
if (event == Debug.DebugEvent.Break) {
try {
result = f();
} catch (e) {
// Rethrow this exception later.
exception = e;
}
}
});
debugger;
Debug.setListener(null);
if (exception !== null) throw exception;
return result;
}
function Replace(fun, original, patch) {
%ExecuteInDebugContext(function() {
ExecuteInDebugContext(function() {
var change_log = [];
try {
var script = Debug.findScript(fun);

View File

@ -24,8 +24,27 @@ eval(`
}
`);
function ExecuteInDebugContext(f) {
var result;
var exception = null;
Debug.setListener(function(event) {
if (event == Debug.DebugEvent.Break) {
try {
result = f();
} catch (e) {
// Rethrow this exception later.
exception = e;
}
}
});
debugger;
Debug.setListener(null);
if (exception !== null) throw exception;
return result;
}
function Replace(fun, original, patch) {
%ExecuteInDebugContext(function() {
ExecuteInDebugContext(function() {
var change_log = [];
try {
var script = Debug.findScript(fun);

View File

@ -41,6 +41,25 @@ function MakeFunction() {
assertIteratorResult("Cat", true, iter.next());
})();
function ExecuteInDebugContext(f) {
var result;
var exception = null;
Debug.setListener(function(event) {
if (event == Debug.DebugEvent.Break) {
try {
result = f();
} catch (e) {
// Rethrow this exception later.
exception = e;
}
}
});
debugger;
Debug.setListener(null);
if (exception !== null) throw exception;
return result;
}
function patch(fun, from, to) {
function debug() {
var log = new Array();
@ -53,7 +72,7 @@ function patch(fun, from, to) {
print("Change log: " + JSON.stringify(log) + "\n");
}
}
%ExecuteInDebugContext(debug);
ExecuteInDebugContext(debug);
}
// Try to edit a MakeGenerator while it's running, then again while it's

View File

@ -48,6 +48,25 @@ function MakeFunction() {
assertPromiseValue('Cat', promise);
})();
function ExecuteInDebugContext(f) {
var result;
var exception = null;
Debug.setListener(function(event) {
if (event == Debug.DebugEvent.Break) {
try {
result = f();
} catch (e) {
// Rethrow this exception later.
exception = e;
}
}
});
debugger;
Debug.setListener(null);
if (exception !== null) throw exception;
return result;
}
function patch(fun, from, to) {
function debug() {
var log = new Array();
@ -61,7 +80,7 @@ function patch(fun, from, to) {
print("Change log: " + JSON.stringify(log) + "\n");
}
}
%ExecuteInDebugContext(debug);
ExecuteInDebugContext(debug);
}
// Try to edit a MakeAsyncFunction while it's running, then again while it's

View File

@ -11,10 +11,6 @@
# not work, but we expect it to not crash.
'debug/debug-step-turbofan': [PASS, FAIL],
# Issue 5587: The eval'ed code is piped through Ignition and fails when being
# live edited. This needs investigation.
'debug/debug-liveedit-double-call': [SKIP],
# Issue 3641: The new 'then' semantics suppress some exceptions.
# These tests may be changed or removed when 'chain' is deprecated.
'debug/es6/debug-promises/reject-with-throw-in-reject': [FAIL],
@ -72,23 +68,10 @@
# TODO(jarin/mstarzinger): Investigate debugger issues with TurboFan.
'debug/debug-evaluate-closure': [FAIL],
'debug/debug-evaluate-locals': [FAIL],
'debug/debug-liveedit-double-call': [FAIL],
'debug/debug-set-variable-value': [FAIL],
'debug/es6/debug-evaluate-blockscopes': [FAIL],
}], # variant == turbofan_opt
##############################################################################
['variant == ignition or variant == ignition_staging', {
# TODO(5587): fails to liveedit evaled code.
'debug/debug-liveedit-double-call': [FAIL],
}], # variant == ignition
##############################################################################
['variant == ignition_turbofan', {
# TODO(5587): fails to liveedit evaled code.
'debug/debug-liveedit-double-call': [FAIL],
}], # variant == ignition_turbofan
##############################################################################
['variant == asm_wasm', {
'*': [SKIP],

View File

@ -1,20 +0,0 @@
// Copyright 2015 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: --stack-size=100 --allow-natives-syntax
function g() {}
var count = 0;
function f() {
try {
f();
} catch(e) {
if (count < 100) {
count++;
%ExecuteInDebugContext(g);
}
}
}
f();