}
+bool RelocInfo::IsPatchedDebugBreakSlotSequence() {
+ Instr current_instr = Assembler::instr_at(pc_);
+ return !Assembler::IsNop(current_instr, 2);
+}
+
+
void RelocInfo::Visit(ObjectVisitor* visitor) {
RelocInfo::Mode mode = rmode();
if (mode == RelocInfo::EMBEDDED_OBJECT) {
visitor->VisitExternalReference(target_reference_address());
#ifdef ENABLE_DEBUGGER_SUPPORT
} else if (Debug::has_break_points() &&
- RelocInfo::IsJSReturn(mode) &&
- IsPatchedReturnSequence()) {
+ (RelocInfo::IsJSReturn(mode) &&
+ IsPatchedReturnSequence()) ||
+ (RelocInfo::IsDebugBreakSlot(mode) &&
+ IsPatchedDebugBreakSlotSequence())) {
visitor->VisitDebugTarget(this);
#endif
} else if (mode == RelocInfo::RUNTIME_ENTRY) {
}
+void Assembler::RecordDebugBreakSlot() {
+ WriteRecordedPositions();
+ CheckBuffer();
+ RecordRelocInfo(RelocInfo::DEBUG_BREAK_SLOT);
+}
+
+
void Assembler::RecordComment(const char* msg) {
if (FLAG_debug_code) {
CheckBuffer();
}
-void Assembler::WriteRecordedPositions() {
+bool Assembler::WriteRecordedPositions() {
+ bool written = false;
+
// Write the statement position if it is different from what was written last
// time.
if (current_statement_position_ != written_statement_position_) {
CheckBuffer();
RecordRelocInfo(RelocInfo::STATEMENT_POSITION, current_statement_position_);
written_statement_position_ = current_statement_position_;
+ written = true;
}
// Write the position if it is different from what was written last time and
CheckBuffer();
RecordRelocInfo(RelocInfo::POSITION, current_position_);
written_position_ = current_position_;
+ written = true;
}
+
+ // Return whether something was written.
+ return written;
}
void Assembler::RecordRelocInfo(RelocInfo::Mode rmode, intptr_t data) {
RelocInfo rinfo(pc_, rmode, data); // we do not try to reuse pool constants
- if (rmode >= RelocInfo::JS_RETURN && rmode <= RelocInfo::STATEMENT_POSITION) {
+ if (rmode >= RelocInfo::JS_RETURN && rmode <= RelocInfo::DEBUG_BREAK_SLOT) {
// Adjust code for new modes.
- ASSERT(RelocInfo::IsJSReturn(rmode)
+ ASSERT(RelocInfo::IsDebugBreakSlot(rmode)
+ || RelocInfo::IsJSReturn(rmode)
|| RelocInfo::IsComment(rmode)
|| RelocInfo::IsPosition(rmode));
// These modes do not need an entry in the constant pool.
// Distance between start of patched return sequence and the emitted address
// to jump to.
#ifdef USE_BLX
- // Return sequence is:
+ // Patched return sequence is:
// ldr ip, [pc, #0] @ emited address and start
// blx ip
static const int kPatchReturnSequenceAddressOffset = 0 * kInstrSize;
#else
- // Return sequence is:
+ // Patched return sequence is:
// mov lr, pc @ start of sequence
// ldr pc, [pc, #-4] @ emited address
static const int kPatchReturnSequenceAddressOffset = kInstrSize;
#endif
+ // Distance between start of patched debug break slot and the emitted address
+ // to jump to.
+#ifdef USE_BLX
+ // Patched debug break slot code is:
+ // ldr ip, [pc, #0] @ emited address and start
+ // blx ip
+ static const int kPatchDebugBreakSlotAddressOffset = 0 * kInstrSize;
+#else
+ // Patched debug break slot code is:
+ // mov lr, pc @ start of sequence
+ // ldr pc, [pc, #-4] @ emited address
+ static const int kPatchDebugBreakSlotAddressOffset = kInstrSize;
+#endif
+
// Difference between address of current opcode and value read from pc
// register.
static const int kPcLoadDelta = 8;
- static const int kJSReturnSequenceLength = 4;
+ static const int kJSReturnSequenceInstructions = 4;
+ static const int kDebugBreakSlotInstructions = 3;
+ static const int kDebugBreakSlotLength =
+ kDebugBreakSlotInstructions * kInstrSize;
// ---------------------------------------------------------------------------
// Code generation
// Mark address of the ExitJSFrame code.
void RecordJSReturn();
+ // Mark address of a debug break slot.
+ void RecordDebugBreakSlot();
+
// Record a comment relocation entry that can be used by a disassembler.
// Use --debug_code to enable.
void RecordComment(const char* msg);
void RecordPosition(int pos);
void RecordStatementPosition(int pos);
- void WriteRecordedPositions();
+ bool WriteRecordedPositions();
int pc_offset() const { return pc_ - buffer_; }
int current_position() const { return current_position_; }
// the add instruction the add will generate two instructions.
int return_sequence_length =
masm_->InstructionsGeneratedSince(&check_exit_codesize);
- CHECK(return_sequence_length == Assembler::kJSReturnSequenceLength ||
- return_sequence_length == Assembler::kJSReturnSequenceLength + 1);
+ CHECK(return_sequence_length ==
+ Assembler::kJSReturnSequenceInstructions ||
+ return_sequence_length ==
+ Assembler::kJSReturnSequenceInstructions + 1);
#endif
}
}
bool is_toplevel,
Handle<Script> script);
- static void RecordPositions(MacroAssembler* masm, int pos);
+ static bool RecordPositions(MacroAssembler* masm,
+ int pos,
+ bool right_here = false);
// Accessors
MacroAssembler* masm() { return masm_; }
// #endif
// <debug break return code entry point address>
// bktp 0
- CodePatcher patcher(rinfo()->pc(), 4);
+ CodePatcher patcher(rinfo()->pc(), Assembler::kJSReturnSequenceInstructions);
#ifdef USE_BLX
patcher.masm()->ldr(v8::internal::ip, MemOperand(v8::internal::pc, 0));
patcher.masm()->blx(v8::internal::ip);
// Restore the JS frame exit code.
void BreakLocationIterator::ClearDebugBreakAtReturn() {
rinfo()->PatchCode(original_rinfo()->pc(),
- Assembler::kJSReturnSequenceLength);
+ Assembler::kJSReturnSequenceInstructions);
}
-// A debug break in the exit code is identified by a call.
+// A debug break in the frame exit code is identified by the JS frame exit code
+// having been patched with a call instruction.
bool Debug::IsDebugBreakAtReturn(RelocInfo* rinfo) {
ASSERT(RelocInfo::IsJSReturn(rinfo->rmode()));
return rinfo->IsPatchedReturnSequence();
}
+bool BreakLocationIterator::IsDebugBreakAtSlot() {
+ ASSERT(IsDebugBreakSlot());
+ // Check whether the debug break slot instructions have been patched.
+ return rinfo()->IsPatchedDebugBreakSlotSequence();
+}
+
+
+void BreakLocationIterator::SetDebugBreakAtSlot() {
+ ASSERT(IsDebugBreakSlot());
+ // Patch the code changing the debug break slot code from
+ // mov r2, r2
+ // mov r2, r2
+ // mov r2, r2
+ // to a call to the debug break slot code.
+ // #if USE_BLX
+ // ldr ip, [pc, #0]
+ // blx ip
+ // #else
+ // mov lr, pc
+ // ldr pc, [pc, #-4]
+ // #endif
+ // <debug break slot code entry point address>
+ CodePatcher patcher(rinfo()->pc(), Assembler::kDebugBreakSlotInstructions);
+#ifdef USE_BLX
+ patcher.masm()->ldr(v8::internal::ip, MemOperand(v8::internal::pc, 0));
+ patcher.masm()->blx(v8::internal::ip);
+#else
+ patcher.masm()->mov(v8::internal::lr, v8::internal::pc);
+ patcher.masm()->ldr(v8::internal::pc, MemOperand(v8::internal::pc, -4));
+#endif
+ patcher.Emit(Debug::debug_break_return()->entry());
+}
+
+
+void BreakLocationIterator::ClearDebugBreakAtSlot() {
+ ASSERT(IsDebugBreakSlot());
+ rinfo()->PatchCode(original_rinfo()->pc(),
+ Assembler::kDebugBreakSlotInstructions);
+}
+
+
#define __ ACCESS_MASM(masm)
}
+void Debug::GenerateSlot(MacroAssembler* masm) {
+ // Generate enough nop's to make space for a call instruction.
+ Label check_codesize;
+ __ bind(&check_codesize);
+ __ RecordDebugBreakSlot();
+ for (int i = 0; i < Assembler::kDebugBreakSlotInstructions; i++) {
+ __ nop(2);
+ }
+ ASSERT_EQ(Assembler::kDebugBreakSlotInstructions,
+ masm->InstructionsGeneratedSince(&check_codesize));
+}
+
+
+void Debug::GenerateSlotDebugBreak(MacroAssembler* masm) {
+ // In the places where a debug break slot is inserted no registers can contain
+ // object pointers.
+ Generate_DebugBreakCallHelper(masm, 0);
+}
+
+
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");
}
// add instruction the add will generate two instructions.
int return_sequence_length =
masm_->InstructionsGeneratedSince(&check_exit_codesize);
- CHECK(return_sequence_length == Assembler::kJSReturnSequenceLength ||
- return_sequence_length == Assembler::kJSReturnSequenceLength + 1);
+ CHECK(return_sequence_length ==
+ Assembler::kJSReturnSequenceInstructions ||
+ return_sequence_length ==
+ Assembler::kJSReturnSequenceInstructions + 1);
#endif
}
}
return "external reference";
case RelocInfo::INTERNAL_REFERENCE:
return "internal reference";
+ case RelocInfo::DEBUG_BREAK_SLOT:
+#ifndef ENABLE_DEBUGGER_SUPPORT
+ UNREACHABLE();
+#endif
+ return "debug break slot";
case RelocInfo::NUMBER_OF_MODES:
UNREACHABLE();
return "number_of_modes";
case STATEMENT_POSITION:
case EXTERNAL_REFERENCE:
case INTERNAL_REFERENCE:
+ case DEBUG_BREAK_SLOT:
case NONE:
break;
case NUMBER_OF_MODES:
enum Mode {
// Please note the order is important (see IsCodeTarget, IsGCRelocMode).
CONSTRUCT_CALL, // code target that is a call to a JavaScript constructor.
- CODE_TARGET_CONTEXT, // code target used for contextual loads.
- DEBUG_BREAK,
- CODE_TARGET, // code target which is not any of the above.
+ CODE_TARGET_CONTEXT, // Code target used for contextual loads.
+ DEBUG_BREAK, // Code target for the debugger statement.
+ CODE_TARGET, // Code target which is not any of the above.
EMBEDDED_OBJECT,
// Everything after runtime_entry (inclusive) is not GC'ed.
COMMENT,
POSITION, // See comment for kNoPosition above.
STATEMENT_POSITION, // See comment for kNoPosition above.
+ DEBUG_BREAK_SLOT, // Additional code inserted for debug break slot.
EXTERNAL_REFERENCE, // The address of an external C++ function.
INTERNAL_REFERENCE, // An address inside the same function.
static inline bool IsInternalReference(Mode mode) {
return mode == INTERNAL_REFERENCE;
}
+ static inline bool IsDebugBreakSlot(Mode mode) {
+ return mode == DEBUG_BREAK_SLOT;
+ }
static inline int ModeMask(Mode mode) { return 1 << mode; }
// Accessors
// with a call to the debugger.
INLINE(bool IsPatchedReturnSequence());
+ // Check whether this debug break slot has been patched with a call to the
+ // debugger.
+ INLINE(bool IsPatchedDebugBreakSlotSequence());
+
#ifdef ENABLE_DISASSEMBLER
// Printing
static const char* RelocModeName(Mode rmode);
public:
Conditional(Expression* condition,
Expression* then_expression,
- Expression* else_expression)
+ Expression* else_expression,
+ int then_expression_position,
+ int else_expression_position)
: condition_(condition),
then_expression_(then_expression),
- else_expression_(else_expression) { }
+ else_expression_(else_expression),
+ then_expression_position_(then_expression_position),
+ else_expression_position_(else_expression_position) { }
virtual void Accept(AstVisitor* v);
Expression* then_expression() const { return then_expression_; }
Expression* else_expression() const { return else_expression_; }
+ int then_expression_position() { return then_expression_position_; }
+ int else_expression_position() { return else_expression_position_; }
+
private:
Expression* condition_;
Expression* then_expression_;
Expression* else_expression_;
+ int then_expression_position_;
+ int else_expression_position_;
};
Debug::GenerateStubNoRegistersDebugBreak(masm);
}
+
+static void Generate_Slot_DebugBreak(MacroAssembler* masm) {
+ Debug::GenerateSlotDebugBreak(masm);
+}
+
+
static void Generate_PlainReturn_LiveEdit(MacroAssembler* masm) {
Debug::GeneratePlainReturnLiveEdit(masm);
}
+
static void Generate_FrameDropper_LiveEdit(MacroAssembler* masm) {
Debug::GenerateFrameDropperLiveEdit(masm);
}
V(KeyedLoadIC_DebugBreak, KEYED_LOAD_IC, DEBUG_BREAK) \
V(StoreIC_DebugBreak, STORE_IC, DEBUG_BREAK) \
V(KeyedStoreIC_DebugBreak, KEYED_STORE_IC, DEBUG_BREAK) \
+ V(Slot_DebugBreak, BUILTIN, DEBUG_BREAK) \
V(PlainReturn_LiveEdit, BUILTIN, DEBUG_BREAK) \
V(FrameDropper_LiveEdit, BUILTIN, DEBUG_BREAK)
#else
#define ASSERT_RESULT(expr) CHECK(expr)
#define ASSERT(condition) CHECK(condition)
#define ASSERT_EQ(v1, v2) CHECK_EQ(v1, v2)
-#define ASSERT_NE(v1, v2) CHECK_NE(v1, v2)
+#define ASSERT_NE(v1, v2) CHECK_NE(v1, v2)
+#define ASSERT_GE(v1, v2) CHECK_GE(v1, v2)
#define STATIC_ASSERT(test) STATIC_CHECK(test)
#define SLOW_ASSERT(condition) if (FLAG_enable_slow_asserts) CHECK(condition)
#else
#define ASSERT_RESULT(expr) (expr)
#define ASSERT(condition) ((void) 0)
#define ASSERT_EQ(v1, v2) ((void) 0)
-#define ASSERT_NE(v1, v2) ((void) 0)
+#define ASSERT_NE(v1, v2) ((void) 0)
+#define ASSERT_GE(v1, v2) ((void) 0)
#define STATIC_ASSERT(test) ((void) 0)
#define SLOW_ASSERT(condition) ((void) 0)
#endif
}
-void CodeGenerator::RecordPositions(MacroAssembler* masm, int pos) {
+bool CodeGenerator::RecordPositions(MacroAssembler* masm,
+ int pos,
+ bool right_here) {
if (pos != RelocInfo::kNoPosition) {
masm->RecordStatementPosition(pos);
masm->RecordPosition(pos);
+ if (right_here) {
+ return masm->WriteRecordedPositions();
+ }
}
+ return false;
}
void CodeGenerator::CodeForFunctionPosition(FunctionLiteral* fun) {
- if (FLAG_debug_info) RecordPositions(masm(), fun->start_position());
+ if (FLAG_debug_info) RecordPositions(masm(), fun->start_position(), false);
}
void CodeGenerator::CodeForReturnPosition(FunctionLiteral* fun) {
- if (FLAG_debug_info) RecordPositions(masm(), fun->end_position());
+ if (FLAG_debug_info) RecordPositions(masm(), fun->end_position(), false);
}
void CodeGenerator::CodeForStatementPosition(Statement* stmt) {
- if (FLAG_debug_info) RecordPositions(masm(), stmt->statement_pos());
+ if (FLAG_debug_info) RecordPositions(masm(), stmt->statement_pos(), false);
}
+
void CodeGenerator::CodeForDoWhileConditionPosition(DoWhileStatement* stmt) {
- if (FLAG_debug_info) RecordPositions(masm(), stmt->condition_position());
+ if (FLAG_debug_info)
+ RecordPositions(masm(), stmt->condition_position(), false);
}
+
void CodeGenerator::CodeForSourcePosition(int pos) {
if (FLAG_debug_info && pos != RelocInfo::kNoPosition) {
masm()->RecordPosition(pos);
void Shell::RunShell() {
LineEditor* editor = LineEditor::Get();
printf("V8 version %s [console: %s]\n", V8::GetVersion(), editor->name());
+ if (i::FLAG_debugger) {
+ printf("JavaScript debugger enabled\n");
+ }
editor->Open();
while (true) {
Locker locker;
ASSERT(statement_position_ >= 0);
}
- // Check for breakable code target. Look in the original code as setting
- // break points can cause the code targets in the running (debugged) code to
- // be of a different kind than in the original code.
- if (RelocInfo::IsCodeTarget(rmode())) {
+ if (IsDebugBreakSlot()) {
+ // There is always a possible break point at a debug break slot.
+ break_point_++;
+ return;
+ } else if (RelocInfo::IsCodeTarget(rmode())) {
+ // Check for breakable code target. Look in the original code as setting
+ // break points can cause the code targets in the running (debugged) code
+ // to be of a different kind than in the original code.
Address target = original_rinfo()->target_address();
Code* code = Code::GetCodeFromTargetAddress(target);
if ((code->is_inline_cache_stub() &&
if (RelocInfo::IsJSReturn(rmode())) {
// Patch the frame exit code with a break point.
SetDebugBreakAtReturn();
+ } else if (IsDebugBreakSlot()) {
+ // Patch the code in the break slot.
+ SetDebugBreakAtSlot();
} else {
// Patch the IC call.
SetDebugBreakAtIC();
if (RelocInfo::IsJSReturn(rmode())) {
// Restore the frame exit code.
ClearDebugBreakAtReturn();
+ } else if (IsDebugBreakSlot()) {
+ // Restore the code in the break slot.
+ ClearDebugBreakAtSlot();
} else {
// Patch the IC call.
ClearDebugBreakAtIC();
bool BreakLocationIterator::IsDebugBreak() {
if (RelocInfo::IsJSReturn(rmode())) {
return IsDebugBreakAtReturn();
+ } else if (IsDebugBreakSlot()) {
+ return IsDebugBreakAtSlot();
} else {
return Debug::IsDebugBreak(rinfo()->target_address());
}
}
+bool BreakLocationIterator::IsDebugBreakSlot() {
+ return RelocInfo::DEBUG_BREAK_SLOT == rmode();
+}
+
+
Object* BreakLocationIterator::BreakPointObjects() {
return debug_info_->GetBreakPointObjects(code_position());
}
Handle<Context> Debug::debug_context_ = Handle<Context>();
Code* Debug::debug_break_return_ = NULL;
+Code* Debug::debug_break_slot_ = NULL;
void ScriptCache::Add(Handle<Script> script) {
debug_break_return_ =
Builtins::builtin(Builtins::Return_DebugBreak);
ASSERT(debug_break_return_->IsCode());
+ // Get code to handle debug break in debug break slots.
+ debug_break_slot_ =
+ Builtins::builtin(Builtins::Slot_DebugBreak);
+ ASSERT(debug_break_slot_->IsCode());
}
}
void Debug::Iterate(ObjectVisitor* v) {
v->VisitPointer(BitCast<Object**, Code**>(&(debug_break_return_)));
+ v->VisitPointer(BitCast<Object**, Code**>(&(debug_break_slot_)));
}
// break point is still active after processing the break point.
Address addr = frame->pc() - Assembler::kCallTargetAddressOffset;
- // Check if the location is at JS exit.
+ // Check if the location is at JS exit or debug break slot.
bool at_js_return = false;
bool break_at_js_return_active = false;
+ bool at_debug_break_slot = false;
RelocIterator it(debug_info->code());
- while (!it.done()) {
+ while (!it.done() && !at_js_return && !at_debug_break_slot) {
if (RelocInfo::IsJSReturn(it.rinfo()->rmode())) {
at_js_return = (it.rinfo()->pc() ==
addr - Assembler::kPatchReturnSequenceAddressOffset);
break_at_js_return_active = it.rinfo()->IsPatchedReturnSequence();
}
+ if (RelocInfo::IsDebugBreakSlot(it.rinfo()->rmode())) {
+ at_debug_break_slot = (it.rinfo()->pc() ==
+ addr - Assembler::kPatchDebugBreakSlotAddressOffset);
+ }
it.next();
}
// Move back to where the call instruction sequence started.
thread_local_.after_break_target_ =
addr - Assembler::kPatchReturnSequenceAddressOffset;
- } else {
- // Check if there still is a debug break call at the target address. If the
- // break point has been removed it will have disappeared. If it have
- // disappeared don't try to look in the original code as the running code
- // will have the right address. This takes care of the case where the last
- // break point is removed from the function and therefore no "original code"
- // is available. If the debug break call is still there find the address in
- // the original code.
- if (IsDebugBreak(Assembler::target_address_at(addr))) {
- // If the break point is still there find the call address which was
- // overwritten in the original code by the call to DebugBreakXXX.
-
- // Find the corresponding address in the original code.
- addr += original_code->instruction_start() - code->instruction_start();
- }
+ } else if (at_debug_break_slot) {
+ // Address of where the debug break slot starts.
+ addr = addr - Assembler::kPatchDebugBreakSlotAddressOffset;
+
+ // Continue just after the slot.
+ thread_local_.after_break_target_ = addr + Assembler::kDebugBreakSlotLength;
+ } else if (IsDebugBreak(Assembler::target_address_at(addr))) {
+ // We now know that there is still a debug break call at the target address,
+ // so the break point is still there and the original code will hold the
+ // address to jump to in order to complete the call which is replaced by a
+ // call to DebugBreakXXX.
+
+ // Find the corresponding address in the original code.
+ addr += original_code->instruction_start() - code->instruction_start();
// Install jump to the call address in the original code. This will be the
// call which was overwritten by the call to DebugBreakXXX.
thread_local_.after_break_target_ = Assembler::target_address_at(addr);
+ } else {
+ // There is no longer a break point present. Don't try to look in the
+ // original code as the running code will have the right address. This takes
+ // care of the case where the last break point is removed from the function
+ // and therefore no "original code" is available.
+ thread_local_.after_break_target_ = Assembler::target_address_at(addr);
}
}
void SetDebugBreakAtReturn();
void ClearDebugBreakAtReturn();
+ bool IsDebugBreakSlot();
+ bool IsDebugBreakAtSlot();
+ void SetDebugBreakAtSlot();
+ void ClearDebugBreakAtSlot();
+
DISALLOW_COPY_AND_ASSIGN(BreakLocationIterator);
};
enum AddressId {
k_after_break_target_address,
k_debug_break_return_address,
+ k_debug_break_slot_address,
k_register_address
};
return &debug_break_return_;
}
+ // Access to the debug break in debug break slot code.
+ static Code* debug_break_slot() { return debug_break_slot_; }
+ static Code** debug_break_slot_address() {
+ return &debug_break_slot_;
+ }
+
static const int kEstimatedNofDebugInfoEntries = 16;
static const int kEstimatedNofBreakPointsInFunction = 16;
static void AfterGarbageCollection();
// Code generator routines.
+ static void GenerateSlot(MacroAssembler* masm);
static void GenerateLoadICDebugBreak(MacroAssembler* masm);
static void GenerateStoreICDebugBreak(MacroAssembler* masm);
static void GenerateKeyedLoadICDebugBreak(MacroAssembler* masm);
static void GenerateConstructCallDebugBreak(MacroAssembler* masm);
static void GenerateReturnDebugBreak(MacroAssembler* masm);
static void GenerateStubNoRegistersDebugBreak(MacroAssembler* masm);
+ static void GenerateSlotDebugBreak(MacroAssembler* masm);
static void GeneratePlainReturnLiveEdit(MacroAssembler* masm);
static void GenerateFrameDropperLiveEdit(MacroAssembler* masm);
// Code to call for handling debug break on return.
static Code* debug_break_return_;
+ // Code to call for handling debug break in debug break slots.
+ static Code* debug_break_slot_;
+
DISALLOW_COPY_AND_ASSIGN(Debug);
};
return reinterpret_cast<Address>(Debug::after_break_target_address());
case Debug::k_debug_break_return_address:
return reinterpret_cast<Address>(Debug::debug_break_return_address());
+ case Debug::k_debug_break_slot_address:
+ return reinterpret_cast<Address>(Debug::debug_break_slot_address());
case Debug::k_register_address:
return reinterpret_cast<Address>(Debug::register_address(reg_));
default:
DEFINE_bool(help, false, "Print usage message, including flags, on console")
DEFINE_bool(dump_counters, false, "Dump counters on exit")
-DEFINE_bool(debugger, true, "Enable JavaScript debugger")
+DEFINE_bool(debugger, false, "Enable JavaScript debugger")
DEFINE_bool(remote_debugger, false, "Connect JavaScript debugger to the "
"debugger agent in another process")
DEFINE_bool(debugger_agent, false, "Enable debugger agent")
#undef CHECK_BAILOUT
+void BreakableStatementChecker::Check(Statement* stmt) {
+ Visit(stmt);
+}
+
+
+void BreakableStatementChecker::Check(Expression* expr) {
+ Visit(expr);
+}
+
+
+void BreakableStatementChecker::VisitDeclaration(Declaration* decl) {
+}
+
+
+void BreakableStatementChecker::VisitBlock(Block* stmt) {
+}
+
+
+void BreakableStatementChecker::VisitExpressionStatement(
+ ExpressionStatement* stmt) {
+ // Check if expression is breakable.
+ Visit(stmt->expression());
+}
+
+
+void BreakableStatementChecker::VisitEmptyStatement(EmptyStatement* stmt) {
+}
+
+
+void BreakableStatementChecker::VisitIfStatement(IfStatement* stmt) {
+ // If the condition is breakable the if statement is breakable.
+ Visit(stmt->condition());
+}
+
+
+void BreakableStatementChecker::VisitContinueStatement(
+ ContinueStatement* stmt) {
+}
+
+
+void BreakableStatementChecker::VisitBreakStatement(BreakStatement* stmt) {
+}
+
+
+void BreakableStatementChecker::VisitReturnStatement(ReturnStatement* stmt) {
+ // Return is breakable if the expression is.
+ Visit(stmt->expression());
+}
+
+
+void BreakableStatementChecker::VisitWithEnterStatement(
+ WithEnterStatement* stmt) {
+ Visit(stmt->expression());
+}
+
+
+void BreakableStatementChecker::VisitWithExitStatement(
+ WithExitStatement* stmt) {
+}
+
+
+void BreakableStatementChecker::VisitSwitchStatement(SwitchStatement* stmt) {
+ // Switch statements breakable if the tag expression is.
+ Visit(stmt->tag());
+}
+
+
+void BreakableStatementChecker::VisitDoWhileStatement(DoWhileStatement* stmt) {
+ // Mark do while as breakable to avoid adding a break slot in front of it.
+ is_breakable_ = true;
+}
+
+
+void BreakableStatementChecker::VisitWhileStatement(WhileStatement* stmt) {
+ // Mark while statements breakable if the condition expression is.
+ Visit(stmt->cond());
+}
+
+
+void BreakableStatementChecker::VisitForStatement(ForStatement* stmt) {
+ // Mark for statements breakable if the condition expression is.
+ if (stmt->cond() != NULL) {
+ Visit(stmt->cond());
+ }
+}
+
+
+void BreakableStatementChecker::VisitForInStatement(ForInStatement* stmt) {
+ // Mark for in statements breakable if the enumerable expression is.
+ Visit(stmt->enumerable());
+}
+
+
+void BreakableStatementChecker::VisitTryCatchStatement(
+ TryCatchStatement* stmt) {
+ // Mark try catch as breakable to avoid adding a break slot in front of it.
+ is_breakable_ = true;
+}
+
+
+void BreakableStatementChecker::VisitTryFinallyStatement(
+ TryFinallyStatement* stmt) {
+ // Mark try finally as breakable to avoid adding a break slot in front of it.
+ is_breakable_ = true;
+}
+
+
+void BreakableStatementChecker::VisitDebuggerStatement(
+ DebuggerStatement* stmt) {
+ // The debugger statement is breakable.
+ is_breakable_ = true;
+}
+
+
+void BreakableStatementChecker::VisitFunctionLiteral(FunctionLiteral* expr) {
+}
+
+
+void BreakableStatementChecker::VisitSharedFunctionInfoLiteral(
+ SharedFunctionInfoLiteral* expr) {
+}
+
+
+void BreakableStatementChecker::VisitConditional(Conditional* expr) {
+}
+
+
+void BreakableStatementChecker::VisitSlot(Slot* expr) {
+}
+
+
+void BreakableStatementChecker::VisitVariableProxy(VariableProxy* expr) {
+}
+
+
+void BreakableStatementChecker::VisitLiteral(Literal* expr) {
+}
+
+
+void BreakableStatementChecker::VisitRegExpLiteral(RegExpLiteral* expr) {
+}
+
+
+void BreakableStatementChecker::VisitObjectLiteral(ObjectLiteral* expr) {
+}
+
+
+void BreakableStatementChecker::VisitArrayLiteral(ArrayLiteral* expr) {
+}
+
+
+void BreakableStatementChecker::VisitCatchExtensionObject(
+ CatchExtensionObject* expr) {
+}
+
+
+void BreakableStatementChecker::VisitAssignment(Assignment* expr) {
+ // If assigning to a property (including a global property) the assignment is
+ // breakable.
+ Variable* var = expr->target()->AsVariableProxy()->AsVariable();
+ Property* prop = expr->target()->AsProperty();
+ if (prop != NULL || (var != NULL && var->is_global())) {
+ is_breakable_ = true;
+ return;
+ }
+
+ // Otherwise the assignment is breakable if the assigned value is.
+ Visit(expr->value());
+}
+
+
+void BreakableStatementChecker::VisitThrow(Throw* expr) {
+ // Throw is breakable if the expression is.
+ Visit(expr->exception());
+}
+
+
+void BreakableStatementChecker::VisitProperty(Property* expr) {
+ // Property load is breakable.
+ is_breakable_ = true;
+}
+
+
+void BreakableStatementChecker::VisitCall(Call* expr) {
+ // Function calls both through IC and call stub are breakable.
+ is_breakable_ = true;
+}
+
+
+void BreakableStatementChecker::VisitCallNew(CallNew* expr) {
+ // Function calls through new are breakable.
+ is_breakable_ = true;
+}
+
+
+void BreakableStatementChecker::VisitCallRuntime(CallRuntime* expr) {
+}
+
+
+void BreakableStatementChecker::VisitUnaryOperation(UnaryOperation* expr) {
+ Visit(expr->expression());
+}
+
+
+void BreakableStatementChecker::VisitCountOperation(CountOperation* expr) {
+ Visit(expr->expression());
+}
+
+
+void BreakableStatementChecker::VisitBinaryOperation(BinaryOperation* expr) {
+ Visit(expr->left());
+ Visit(expr->right());
+}
+
+
+void BreakableStatementChecker::VisitCompareOperation(CompareOperation* expr) {
+ Visit(expr->left());
+ Visit(expr->right());
+}
+
+
+void BreakableStatementChecker::VisitThisFunction(ThisFunction* expr) {
+}
+
+
#define __ ACCESS_MASM(masm())
Handle<Code> FullCodeGenerator::MakeCode(CompilationInfo* info) {
void FullCodeGenerator::SetStatementPosition(Statement* stmt) {
if (FLAG_debug_info) {
+#ifdef ENABLE_DEBUGGER_SUPPORT
+ if (!Debugger::IsDebuggerActive()) {
+ CodeGenerator::RecordPositions(masm_, stmt->statement_pos());
+ } else {
+ // Check if the statement will be breakable without adding a debug break
+ // slot.
+ BreakableStatementChecker checker;
+ checker.Check(stmt);
+ // Record the statement position right here if the statement is not
+ // breakable. For breakable statements the actual recording of the
+ // position will be postponed to the breakable code (typically an IC).
+ bool position_recorded = CodeGenerator::RecordPositions(
+ masm_, stmt->statement_pos(), !checker.is_breakable());
+ // If the position recording did record a new position generate a debug
+ // break slot to make the statement breakable.
+ if (position_recorded) {
+ Debug::GenerateSlot(masm_);
+ }
+ }
+#else
CodeGenerator::RecordPositions(masm_, stmt->statement_pos());
+#endif
+ }
+}
+
+
+void FullCodeGenerator::SetExpressionPosition(Expression* expr, int pos) {
+ if (FLAG_debug_info) {
+#ifdef ENABLE_DEBUGGER_SUPPORT
+ if (!Debugger::IsDebuggerActive()) {
+ CodeGenerator::RecordPositions(masm_, pos);
+ } else {
+ // Check if the expression will be breakable without adding a debug break
+ // slot.
+ BreakableStatementChecker checker;
+ checker.Check(expr);
+ // Record a statement position right here if the expression is not
+ // breakable. For breakable expressions the actual recording of the
+ // position will be postponed to the breakable code (typically an IC).
+ // NOTE this will record a statement position for something which might
+ // not be a statement. As stepping in the debugger will only stop at
+ // statement positions this is used for e.g. the condition expression of
+ // a do while loop.
+ bool position_recorded = CodeGenerator::RecordPositions(
+ masm_, pos, !checker.is_breakable());
+ // If the position recording did record a new position generate a debug
+ // break slot to make the statement breakable.
+ if (position_recorded) {
+ Debug::GenerateSlot(masm_);
+ }
+ }
+#else
+ CodeGenerator::RecordPositions(masm_, pos);
+#endif
}
}
__ bind(&stack_check_success);
__ bind(loop_statement.continue_target());
- SetStatementPosition(stmt->condition_position());
+
+ // Record the position of the do while condition and make sure it is possible
+ // to break on the condition.
+ SetExpressionPosition(stmt->cond(), stmt->condition_position());
+
VisitForControl(stmt->cond(), &body, loop_statement.break_target());
__ bind(&stack_limit_hit);
void FullCodeGenerator::VisitWhileStatement(WhileStatement* stmt) {
Comment cmnt(masm_, "[ WhileStatement");
- SetStatementPosition(stmt);
Label body, stack_limit_hit, stack_check_success;
Iteration loop_statement(this, stmt);
Visit(stmt->body());
__ bind(loop_statement.continue_target());
+ // Emit the statement position here as this is where the while statement code
+ // starts.
+ SetStatementPosition(stmt);
// Check stack before looping.
__ StackLimitCheck(&stack_limit_hit);
void FullCodeGenerator::VisitForStatement(ForStatement* stmt) {
Comment cmnt(masm_, "[ ForStatement");
- SetStatementPosition(stmt);
Label test, body, stack_limit_hit, stack_check_success;
Iteration loop_statement(this, stmt);
}
__ bind(&test);
+ // Emit the statement position here as this is where the for statement code
+ // starts.
+ SetStatementPosition(stmt);
// Check stack before looping.
__ StackLimitCheck(&stack_limit_hit);
VisitForControl(expr->condition(), &true_case, &false_case);
__ bind(&true_case);
+ SetExpressionPosition(expr->then_expression(),
+ expr->then_expression_position());
Visit(expr->then_expression());
// If control flow falls through Visit, jump to done.
if (context_ == Expression::kEffect || context_ == Expression::kValue) {
}
__ bind(&false_case);
+ SetExpressionPosition(expr->else_expression(),
+ expr->else_expression_position());
Visit(expr->else_expression());
// If control flow falls through Visit, merge it with true case here.
if (context_ == Expression::kEffect || context_ == Expression::kValue) {
};
+// AST node visitor which can tell whether a given statement will be breakable
+// when the code is compiled by the full compiler in the debugger. This means
+// that there will be an IC (load/store/call) in the code generated for the
+// debugger to piggybag on.
+class BreakableStatementChecker: public AstVisitor {
+ public:
+ BreakableStatementChecker() : is_breakable_(false) {}
+
+ void Check(Statement* stmt);
+ void Check(Expression* stmt);
+
+ bool is_breakable() { return is_breakable_; }
+
+ private:
+ // AST node visit functions.
+#define DECLARE_VISIT(type) virtual void Visit##type(type* node);
+ AST_NODE_LIST(DECLARE_VISIT)
+#undef DECLARE_VISIT
+
+ bool is_breakable_;
+
+ DISALLOW_COPY_AND_ASSIGN(BreakableStatementChecker);
+};
+
+
// -----------------------------------------------------------------------------
// Full code generator.
void SetFunctionPosition(FunctionLiteral* fun);
void SetReturnPosition(FunctionLiteral* fun);
void SetStatementPosition(Statement* stmt);
+ void SetExpressionPosition(Expression* expr, int pos);
void SetStatementPosition(int pos);
void SetSourcePosition(int pos);
void RelocInfo::apply(intptr_t delta) {
if (rmode_ == RUNTIME_ENTRY || IsCodeTarget(rmode_)) {
int32_t* p = reinterpret_cast<int32_t*>(pc_);
- *p -= delta; // relocate entry
+ *p -= delta; // Relocate entry.
} else if (rmode_ == JS_RETURN && IsPatchedReturnSequence()) {
// Special handling of js_return when a break point is set (call
// instruction has been inserted).
int32_t* p = reinterpret_cast<int32_t*>(pc_ + 1);
- *p -= delta; // relocate entry
+ *p -= delta; // Relocate entry.
+ } else if (rmode_ == DEBUG_BREAK_SLOT && IsPatchedDebugBreakSlotSequence()) {
+ // Special handling of a debug break slot when a break point is set (call
+ // instruction has been inserted).
+ int32_t* p = reinterpret_cast<int32_t*>(pc_ + 1);
+ *p -= delta; // Relocate entry.
} else if (IsInternalReference(rmode_)) {
// absolute code pointer inside code object moves with the code object.
int32_t* p = reinterpret_cast<int32_t*>(pc_);
- *p += delta; // relocate entry
+ *p += delta; // Relocate entry.
}
}
}
+bool RelocInfo::IsPatchedDebugBreakSlotSequence() {
+ return !Assembler::IsNop(pc());
+}
+
+
void RelocInfo::Visit(ObjectVisitor* visitor) {
RelocInfo::Mode mode = rmode();
if (mode == RelocInfo::EMBEDDED_OBJECT) {
visitor->VisitExternalReference(target_reference_address());
#ifdef ENABLE_DEBUGGER_SUPPORT
} else if (Debug::has_break_points() &&
- RelocInfo::IsJSReturn(mode) &&
- IsPatchedReturnSequence()) {
+ (RelocInfo::IsJSReturn(mode) &&
+ IsPatchedReturnSequence()) ||
+ (RelocInfo::IsDebugBreakSlot(mode) &&
+ IsPatchedDebugBreakSlotSequence())) {
visitor->VisitDebugTarget(this);
#endif
} else if (mode == RelocInfo::RUNTIME_ENTRY) {
patcher.masm()->SizeOfCodeGeneratedSince(&check_codesize));
// Add the requested number of int3 instructions after the call.
+ ASSERT_GE(guard_bytes, 0);
for (int i = 0; i < guard_bytes; i++) {
patcher.masm()->int3();
}
}
+void Assembler::RecordDebugBreakSlot() {
+ WriteRecordedPositions();
+ EnsureSpace ensure_space(this);
+ RecordRelocInfo(RelocInfo::DEBUG_BREAK_SLOT);
+}
+
+
void Assembler::RecordComment(const char* msg) {
if (FLAG_debug_code) {
EnsureSpace ensure_space(this);
}
-void Assembler::WriteRecordedPositions() {
+bool Assembler::WriteRecordedPositions() {
+ bool written = false;
+
// Write the statement position if it is different from what was written last
// time.
if (current_statement_position_ != written_statement_position_) {
EnsureSpace ensure_space(this);
RecordRelocInfo(RelocInfo::STATEMENT_POSITION, current_statement_position_);
written_statement_position_ = current_statement_position_;
+ written = true;
}
// Write the position if it is different from what was written last time and
EnsureSpace ensure_space(this);
RecordRelocInfo(RelocInfo::POSITION, current_position_);
written_position_ = current_position_;
+ written = true;
}
+
+ // Return whether something was written.
+ return written;
}
// to jump to.
static const int kPatchReturnSequenceAddressOffset = 1; // JMP imm32.
+ // Distance between start of patched debug break slot and the emitted address
+ // to jump to.
+ static const int kPatchDebugBreakSlotAddressOffset = 1; // JMP imm32.
+
static const int kCallInstructionLength = 5;
static const int kJSReturnSequenceLength = 6;
+ // The debug break slot must be able to contain a call instruction.
+ static const int kDebugBreakSlotLength = kCallInstructionLength;
+
// ---------------------------------------------------------------------------
// Code generation
//
// Mark address of the ExitJSFrame code.
void RecordJSReturn();
+ // Mark address of a debug break slot.
+ void RecordDebugBreakSlot();
+
// Record a comment relocation entry that can be used by a disassembler.
// Use --debug_code to enable.
void RecordComment(const char* msg);
void RecordPosition(int pos);
void RecordStatementPosition(int pos);
- void WriteRecordedPositions();
+ bool WriteRecordedPositions();
// Writes a single word of data in the code stream.
// Used for inline tables, e.g., jump-tables.
// Get the number of bytes available in the buffer.
inline int available_space() const { return reloc_info_writer.pos() - pc_; }
+ static bool IsNop(Address addr) { return *addr == 0x90; }
+
// Avoid overflows for displacements etc.
static const int kMaximalBufferSize = 512*MB;
static const int kMinimalBufferSize = 4*KB;
static bool ShouldGenerateLog(Expression* type);
#endif
- static void RecordPositions(MacroAssembler* masm, int pos);
+ static bool RecordPositions(MacroAssembler* masm,
+ int pos,
+ bool right_here = false);
// Accessors
MacroAssembler* masm() { return masm_; }
}
+bool BreakLocationIterator::IsDebugBreakAtSlot() {
+ ASSERT(IsDebugBreakSlot());
+ // Check whether the debug break slot instructions have been patched.
+ return rinfo()->IsPatchedDebugBreakSlotSequence();
+}
+
+
+void BreakLocationIterator::SetDebugBreakAtSlot() {
+ ASSERT(IsDebugBreakSlot());
+ rinfo()->PatchCodeWithCall(
+ Debug::debug_break_slot()->entry(),
+ Assembler::kDebugBreakSlotLength - Assembler::kCallInstructionLength);
+}
+
+
+void BreakLocationIterator::ClearDebugBreakAtSlot() {
+ ASSERT(IsDebugBreakSlot());
+ rinfo()->PatchCode(original_rinfo()->pc(), Assembler::kDebugBreakSlotLength);
+}
+
+
#define __ ACCESS_MASM(masm)
}
+void Debug::GenerateSlot(MacroAssembler* masm) {
+ // Generate enough nop's to make space for a call instruction.
+ Label check_codesize;
+ __ bind(&check_codesize);
+ __ RecordDebugBreakSlot();
+ for (int i = 0; i < Assembler::kDebugBreakSlotLength; i++) {
+ __ nop();
+ }
+ ASSERT_EQ(Assembler::kDebugBreakSlotLength,
+ masm->SizeOfCodeGeneratedSince(&check_codesize));
+}
+
+
+void Debug::GenerateSlotDebugBreak(MacroAssembler* masm) {
+ // In the places where a debug break slot is inserted no registers can contain
+ // object pointers.
+ Generate_DebugBreakCallHelper(masm, 0, true);
+}
+
+
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,
}
void VisitDebugTarget(RelocInfo* rinfo) {
- ASSERT(RelocInfo::IsJSReturn(rinfo->rmode()) &&
- rinfo->IsPatchedReturnSequence());
+ ASSERT((RelocInfo::IsJSReturn(rinfo->rmode()) &&
+ rinfo->IsPatchedReturnSequence()) ||
+ (RelocInfo::IsDebugBreakSlot(rinfo->rmode()) &&
+ rinfo->IsPatchedDebugBreakSlotSequence()));
HeapObject* code = Code::GetCodeFromTargetAddress(rinfo->call_address());
MarkCompactCollector::MarkObject(code);
}
}
void VisitDebugTarget(RelocInfo* rinfo) {
- ASSERT(RelocInfo::IsJSReturn(rinfo->rmode()) &&
- rinfo->IsPatchedReturnSequence());
+ ASSERT((RelocInfo::IsJSReturn(rinfo->rmode()) &&
+ rinfo->IsPatchedReturnSequence()) ||
+ (RelocInfo::IsDebugBreakSlot(rinfo->rmode()) &&
+ rinfo->IsPatchedDebugBreakSlotSequence()));
Object* target = Code::GetCodeFromTargetAddress(rinfo->call_address());
VisitPointer(&target);
rinfo->set_call_address(Code::cast(target)->instruction_start());
}
void VisitDebugTarget(RelocInfo* rinfo) {
- ASSERT(RelocInfo::IsJSReturn(rinfo->rmode()) &&
- rinfo->IsPatchedReturnSequence());
+ ASSERT((RelocInfo::IsJSReturn(rinfo->rmode()) &&
+ rinfo->IsPatchedReturnSequence()) ||
+ (RelocInfo::IsDebugBreakSlot(rinfo->rmode()) &&
+ rinfo->IsPatchedDebugBreakSlotSequence()));
Object* target = Code::GetCodeFromTargetAddress(rinfo->call_address());
VisitPointer(&target);
rinfo->set_call_address(
}
-void Assembler::WriteRecordedPositions() {
+bool Assembler::WriteRecordedPositions() {
+ bool written = false;
+
// Write the statement position if it is different from what was written last
// time.
if (current_statement_position_ != written_statement_position_) {
CheckBuffer();
RecordRelocInfo(RelocInfo::STATEMENT_POSITION, current_statement_position_);
written_statement_position_ = current_statement_position_;
+ written = true;
}
// Write the position if it is different from what was written last time and
CheckBuffer();
RecordRelocInfo(RelocInfo::POSITION, current_position_);
written_position_ = current_position_;
+ written = true;
}
+
+ // Return whether something was written.
+ return written;
}
// to jump to.
static const int kPatchReturnSequenceAddressOffset = kInstrSize;
+ // Distance between start of patched debug break slot and the emitted address
+ // to jump to.
+ static const int kPatchDebugBreakSlotAddressOffset = kInstrSize;
// ---------------------------------------------------------------------------
// Code generation.
void RecordPosition(int pos);
void RecordStatementPosition(int pos);
- void WriteRecordedPositions();
+ bool WriteRecordedPositions();
int32_t pc_offset() const { return pc_ - buffer_; }
int32_t current_position() const { return current_position_; }
void ObjectVisitor::VisitDebugTarget(RelocInfo* rinfo) {
- ASSERT(RelocInfo::IsJSReturn(rinfo->rmode()) &&
- rinfo->IsPatchedReturnSequence());
+ ASSERT((RelocInfo::IsJSReturn(rinfo->rmode()) &&
+ rinfo->IsPatchedReturnSequence()) ||
+ (RelocInfo::IsDebugBreakSlot(rinfo->rmode()) &&
+ rinfo->IsPatchedDebugBreakSlotSequence()));
Object* target = Code::GetCodeFromTargetAddress(rinfo->call_address());
Object* old_target = target;
VisitPointer(&target);
RelocInfo::ModeMask(RelocInfo::EMBEDDED_OBJECT) |
RelocInfo::ModeMask(RelocInfo::EXTERNAL_REFERENCE) |
RelocInfo::ModeMask(RelocInfo::JS_RETURN) |
+ RelocInfo::ModeMask(RelocInfo::DEBUG_BREAK_SLOT) |
RelocInfo::ModeMask(RelocInfo::RUNTIME_ENTRY);
for (RelocIterator it(this, mode_mask); !it.done(); it.next()) {
// In parsing the first assignment expression in conditional
// expressions we always accept the 'in' keyword; see ECMA-262,
// section 11.12, page 58.
+ int left_position = scanner().peek_location().beg_pos;
Expression* left = ParseAssignmentExpression(true, CHECK_OK);
Expect(Token::COLON, CHECK_OK);
+ int right_position = scanner().peek_location().beg_pos;
Expression* right = ParseAssignmentExpression(accept_IN, CHECK_OK);
- return NEW(Conditional(expression, left, right));
+ return NEW(Conditional(expression, left, right,
+ left_position, right_position));
}
DEBUG_ADDRESS,
Debug::k_after_break_target_address << kDebugIdShift,
"Debug::after_break_target_address()");
+ Add(Debug_Address(Debug::k_debug_break_slot_address).address(),
+ DEBUG_ADDRESS,
+ Debug::k_debug_break_slot_address << kDebugIdShift,
+ "Debug::debug_break_slot_address()");
Add(Debug_Address(Debug::k_debug_break_return_address).address(),
DEBUG_ADDRESS,
Debug::k_debug_break_return_address << kDebugIdShift,
// Special handling of js_return when a break point is set (call
// instruction has been inserted).
Memory::int32_at(pc_ + 1) -= static_cast<int32_t>(delta); // relocate entry
+ } else if (rmode_ == DEBUG_BREAK_SLOT && IsPatchedDebugBreakSlotSequence()) {
+ // Special handling of debug break slot when a break point is set (call
+ // instruction has been inserted).
+ Memory::int32_at(pc_ + 1) -= static_cast<int32_t>(delta); // relocate entry
}
}
}
+bool RelocInfo::IsPatchedDebugBreakSlotSequence() {
+ return !Assembler::IsNop(pc());
+}
+
+
Address RelocInfo::call_address() {
ASSERT(IsPatchedReturnSequence());
return Memory::Address_at(
visitor->VisitExternalReference(target_reference_address());
#ifdef ENABLE_DEBUGGER_SUPPORT
} else if (Debug::has_break_points() &&
- RelocInfo::IsJSReturn(mode) &&
- IsPatchedReturnSequence()) {
+ (RelocInfo::IsJSReturn(mode) &&
+ IsPatchedReturnSequence()) ||
+ (RelocInfo::IsDebugBreakSlot(mode) &&
+ IsPatchedDebugBreakSlotSequence())) {
visitor->VisitDebugTarget(this);
#endif
} else if (mode == RelocInfo::RUNTIME_ENTRY) {
}
+void Assembler::RecordDebugBreakSlot() {
+ WriteRecordedPositions();
+ EnsureSpace ensure_space(this);
+ RecordRelocInfo(RelocInfo::DEBUG_BREAK_SLOT);
+}
+
+
void Assembler::RecordComment(const char* msg) {
if (FLAG_debug_code) {
EnsureSpace ensure_space(this);
}
-void Assembler::WriteRecordedPositions() {
+bool Assembler::WriteRecordedPositions() {
+ bool written = false;
+
// Write the statement position if it is different from what was written last
// time.
if (current_statement_position_ != written_statement_position_) {
EnsureSpace ensure_space(this);
RecordRelocInfo(RelocInfo::STATEMENT_POSITION, current_statement_position_);
written_statement_position_ = current_statement_position_;
+ written = true;
}
// Write the position if it is different from what was written last time and
EnsureSpace ensure_space(this);
RecordRelocInfo(RelocInfo::POSITION, current_position_);
written_position_ = current_position_;
+ written = true;
}
+
+ // Return whether something was written.
+ return written;
}
// return address. TODO: Use return sequence length instead.
// Should equal Debug::kX64JSReturnSequenceLength - kCallTargetAddressOffset;
static const int kPatchReturnSequenceAddressOffset = 13 - 4;
+ // Distance between start of patched debug break slot and where the
+ // 32-bit displacement of a near call would be, relative to the pushed
+ // return address. TODO: Use return sequence length instead.
+ // Should equal Debug::kX64JSReturnSequenceLength - kCallTargetAddressOffset;
+ static const int kPatchDebugBreakSlotAddressOffset = 13 - 4;
// TODO(X64): Rename this, removing the "Real", after changing the above.
static const int kRealPatchReturnSequenceAddressOffset = 2;
static const int kCallInstructionLength = 13;
static const int kJSReturnSequenceLength = 13;
+ // The debug break slot must be able to contain a call instruction.
+ static const int kDebugBreakSlotLength = kCallInstructionLength;
+
+
// ---------------------------------------------------------------------------
// Code generation
//
// Mark address of the ExitJSFrame code.
void RecordJSReturn();
+ // Mark address of a debug break slot.
+ void RecordDebugBreakSlot();
+
// Record a comment relocation entry that can be used by a disassembler.
// Use --debug_code to enable.
void RecordComment(const char* msg);
void RecordPosition(int pos);
void RecordStatementPosition(int pos);
- void WriteRecordedPositions();
+ bool WriteRecordedPositions();
int pc_offset() const { return static_cast<int>(pc_ - buffer_); }
int current_statement_position() const { return current_statement_position_; }
return static_cast<int>(reloc_info_writer.pos() - pc_);
}
+ static bool IsNop(Address addr) { return *addr == 0x90; }
+
// Avoid overflows for displacements etc.
static const int kMaximalBufferSize = 512*MB;
static const int kMinimalBufferSize = 4*KB;
static bool ShouldGenerateLog(Expression* type);
#endif
- static void RecordPositions(MacroAssembler* masm, int pos);
+ static bool RecordPositions(MacroAssembler* masm,
+ int pos,
+ bool right_here = false);
// Accessors
MacroAssembler* masm() { return masm_; }
}
+void Debug::GenerateSlot(MacroAssembler* masm) {
+ // Generate enough nop's to make space for a call instruction.
+ Label check_codesize;
+ __ bind(&check_codesize);
+ __ RecordDebugBreakSlot();
+ for (int i = 0; i < Assembler::kDebugBreakSlotLength; i++) {
+ __ nop();
+ }
+ ASSERT_EQ(Assembler::kDebugBreakSlotLength,
+ masm->SizeOfCodeGeneratedSince(&check_codesize));
+}
+
+
+void Debug::GenerateSlotDebugBreak(MacroAssembler* masm) {
+ // In the places where a debug break slot is inserted no registers can contain
+ // object pointers.
+ Generate_DebugBreakCallHelper(masm, 0, false);
+}
+
+
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");
}
Assembler::kJSReturnSequenceLength - Assembler::kCallInstructionLength);
}
+
+bool BreakLocationIterator::IsDebugBreakAtSlot() {
+ ASSERT(IsDebugBreakSlot());
+ // Check whether the debug break slot instructions have been patched.
+ return !Assembler::IsNop(rinfo()->pc());
+}
+
+
+void BreakLocationIterator::SetDebugBreakAtSlot() {
+ ASSERT(IsDebugBreakSlot());
+ rinfo()->PatchCodeWithCall(
+ Debug::debug_break_slot()->entry(),
+ Assembler::kDebugBreakSlotLength - Assembler::kCallInstructionLength);
+}
+
+
+void BreakLocationIterator::ClearDebugBreakAtSlot() {
+ ASSERT(IsDebugBreakSlot());
+ rinfo()->PatchCode(original_rinfo()->pc(), Assembler::kDebugBreakSlotLength);
+}
+
+
#endif // ENABLE_DEBUGGER_SUPPORT
} } // namespace v8::internal
SetBreakPoint(foo, 0);
CallWithBreakPoints(env->Global(), foo, 1, 25);
+ // Test debug break slot break point with garbage collection.
+ foo = CompileFunction(&env, "function foo(){var a;}", "foo");
+ SetBreakPoint(foo, 0);
+ CallWithBreakPoints(env->Global(), foo, 1, 25);
+
v8::Debug::SetDebugEventListener(NULL);
CheckDebuggerUnloaded();
}
f->Call(env->Global(), 0, NULL);
CHECK_EQ(1, break_point_hit_count);
- ChangeScriptBreakPointConditionFromJS(sbp1, "a % 2 == 0");
+ ChangeScriptBreakPointConditionFromJS(sbp1, "x % 2 == 0");
break_point_hit_count = 0;
for (int i = 0; i < 10; i++) {
f->Call(env->Global(), 0, NULL);
v8::Local<v8::Function> foo = CompileFunction(&env,
"function foo(x) {"
" var a;"
- " y=0; /* To ensure break location.*/"
+ " y=0;" // To ensure break location 1.
" a=x;"
+ " y=0;" // To ensure break location 2.
"}",
"foo");
- const int foo_break_position = 15;
+ const int foo_break_position_1 = 15;
+ const int foo_break_position_2 = 29;
// Arguments with one parameter "Hello, world!"
v8::Handle<v8::Value> argv_foo[1] = { v8::String::New("Hello, world!") };
// Call foo with breakpoint set before a=x and undefined as parameter.
- int bp = SetBreakPoint(foo, foo_break_position);
+ int bp = SetBreakPoint(foo, foo_break_position_1);
checks = checks_uu;
foo->Call(env->Global(), 0, NULL);
// Call foo with breakpoint set after a=x and parameter "Hello, world!".
ClearBreakPoint(bp);
- SetBreakPoint(foo, foo_break_position + 1);
+ SetBreakPoint(foo, foo_break_position_2);
checks = checks_hh;
foo->Call(env->Global(), 1, argv_foo);
v8::HandleScope scope;
DebugLocalContext env;
+ // Register a debug event listener which steps and counts.
+ v8::Debug::SetDebugEventListener(DebugEventStep);
+
// Create a function for testing stepping of keyed load. The statement 'y=1'
// is there to have more than one breakable statement in the loop, TODO(315).
v8::Local<v8::Function> foo = CompileFunction(
v8::Handle<v8::Value> args[kArgc] = { a };
foo->Call(env->Global(), kArgc, args);
- // Register a debug event listener which steps and counts.
- v8::Debug::SetDebugEventListener(DebugEventStep);
-
// Setup break point and step through the function.
SetBreakPoint(foo, 3);
step_action = StepNext;
foo->Call(env->Global(), kArgc, args);
// With stepping all break locations are hit.
- CHECK_EQ(22, break_point_hit_count);
+ CHECK_EQ(33, break_point_hit_count);
v8::Debug::SetDebugEventListener(NULL);
CheckDebuggerUnloaded();
v8::HandleScope scope;
DebugLocalContext env;
+ // Register a debug event listener which steps and counts.
+ v8::Debug::SetDebugEventListener(DebugEventStep);
+
// Create a function for testing stepping of keyed store. The statement 'y=1'
// is there to have more than one breakable statement in the loop, TODO(315).
v8::Local<v8::Function> foo = CompileFunction(
v8::Handle<v8::Value> args[kArgc] = { a };
foo->Call(env->Global(), kArgc, args);
- // Register a debug event listener which steps and counts.
- v8::Debug::SetDebugEventListener(DebugEventStep);
-
// Setup break point and step through the function.
SetBreakPoint(foo, 3);
step_action = StepNext;
foo->Call(env->Global(), kArgc, args);
// With stepping all break locations are hit.
- CHECK_EQ(22, break_point_hit_count);
+ CHECK_EQ(32, break_point_hit_count);
v8::Debug::SetDebugEventListener(NULL);
CheckDebuggerUnloaded();
v8::HandleScope scope;
DebugLocalContext env;
+ // Register a debug event listener which steps and counts.
+ v8::Debug::SetDebugEventListener(DebugEventStep);
+
// Create a function for testing stepping of named load.
v8::Local<v8::Function> foo = CompileFunction(
&env,
// Call function without any break points to ensure inlining is in place.
foo->Call(env->Global(), 0, NULL);
- // Register a debug event listener which steps and counts.
- v8::Debug::SetDebugEventListener(DebugEventStep);
-
// Setup break point and step through the function.
SetBreakPoint(foo, 4);
step_action = StepNext;
foo->Call(env->Global(), 0, NULL);
// With stepping all break locations are hit.
- CHECK_EQ(41, break_point_hit_count);
+ CHECK_EQ(53, break_point_hit_count);
v8::Debug::SetDebugEventListener(NULL);
CheckDebuggerUnloaded();
v8::HandleScope scope;
DebugLocalContext env;
+ // Register a debug event listener which steps and counts.
+ v8::Debug::SetDebugEventListener(DebugEventStep);
+
// Create a function for testing stepping.
v8::Local<v8::Function> foo = CompileFunction(&env,
"function bar() {};"
" a=1;b=2;x=a;y[index]=3;x=y[index];bar();}", "foo");
SetBreakPoint(foo, 0);
- // Register a debug event listener which steps and counts.
- v8::Debug::SetDebugEventListener(DebugEventStep);
-
step_action = StepIn;
break_point_hit_count = 0;
foo->Call(env->Global(), 0, NULL);
// With stepping all break locations are hit.
- CHECK_EQ(8, break_point_hit_count);
+ CHECK_EQ(11, break_point_hit_count);
v8::Debug::SetDebugEventListener(NULL);
CheckDebuggerUnloaded();
}
+TEST(DebugStepDeclarations) {
+ v8::HandleScope scope;
+ DebugLocalContext env;
+
+ // Register a debug event listener which steps and counts.
+ v8::Debug::SetDebugEventListener(DebugEventStep);
+
+ // Create a function for testing stepping.
+ const char* src = "function foo() { "
+ " var a;"
+ " var b = 1;"
+ " var c = foo;"
+ " var d = Math.floor;"
+ " var e = b + d(1.2);"
+ "}";
+ v8::Local<v8::Function> foo = CompileFunction(&env, src, "foo");
+ SetBreakPoint(foo, 0);
+
+ // Stepping through the declarations.
+ step_action = StepIn;
+ break_point_hit_count = 0;
+ foo->Call(env->Global(), 0, NULL);
+ CHECK_EQ(6, break_point_hit_count);
+
+ // Get rid of the debug event listener.
+ v8::Debug::SetDebugEventListener(NULL);
+ CheckDebuggerUnloaded();
+}
+
+
+TEST(DebugStepLocals) {
+ v8::HandleScope scope;
+ DebugLocalContext env;
+
+ // Register a debug event listener which steps and counts.
+ v8::Debug::SetDebugEventListener(DebugEventStep);
+
+ // Create a function for testing stepping.
+ const char* src = "function foo() { "
+ " var a,b;"
+ " a = 1;"
+ " b = a + 2;"
+ " b = 1 + 2 + 3;"
+ " a = Math.floor(b);"
+ "}";
+ v8::Local<v8::Function> foo = CompileFunction(&env, src, "foo");
+ SetBreakPoint(foo, 0);
+
+ // Stepping through the declarations.
+ step_action = StepIn;
+ break_point_hit_count = 0;
+ foo->Call(env->Global(), 0, NULL);
+ CHECK_EQ(6, break_point_hit_count);
+
+ // Get rid of the debug event listener.
+ v8::Debug::SetDebugEventListener(NULL);
+ CheckDebuggerUnloaded();
+}
+
+
TEST(DebugStepIf) {
v8::HandleScope scope;
DebugLocalContext env;
break_point_hit_count = 0;
v8::Handle<v8::Value> argv_true[argc] = { v8::True() };
foo->Call(env->Global(), argc, argv_true);
- CHECK_EQ(3, break_point_hit_count);
+ CHECK_EQ(4, break_point_hit_count);
// Stepping through the false part.
step_action = StepIn;
break_point_hit_count = 0;
v8::Handle<v8::Value> argv_false[argc] = { v8::False() };
foo->Call(env->Global(), argc, argv_false);
- CHECK_EQ(4, break_point_hit_count);
+ CHECK_EQ(5, break_point_hit_count);
// Get rid of the debug event listener.
v8::Debug::SetDebugEventListener(NULL);
" case 3:"
" d = 1;"
" e = 1;"
+ " f = 1;"
" break;"
" }"
"}";
break_point_hit_count = 0;
v8::Handle<v8::Value> argv_1[argc] = { v8::Number::New(1) };
foo->Call(env->Global(), argc, argv_1);
- CHECK_EQ(4, break_point_hit_count);
+ CHECK_EQ(6, break_point_hit_count);
// Another case.
step_action = StepIn;
break_point_hit_count = 0;
v8::Handle<v8::Value> argv_2[argc] = { v8::Number::New(2) };
foo->Call(env->Global(), argc, argv_2);
- CHECK_EQ(3, break_point_hit_count);
+ CHECK_EQ(5, break_point_hit_count);
// Last case.
step_action = StepIn;
break_point_hit_count = 0;
v8::Handle<v8::Value> argv_3[argc] = { v8::Number::New(3) };
foo->Call(env->Global(), argc, argv_3);
- CHECK_EQ(4, break_point_hit_count);
+ CHECK_EQ(7, break_point_hit_count);
+
+ // Get rid of the debug event listener.
+ v8::Debug::SetDebugEventListener(NULL);
+ CheckDebuggerUnloaded();
+}
+
+
+TEST(DebugStepWhile) {
+ v8::HandleScope scope;
+ DebugLocalContext env;
+
+ // Register a debug event listener which steps and counts.
+ v8::Debug::SetDebugEventListener(DebugEventStep);
+
+ // Create a function for testing stepping.
+ const int argc = 1;
+ const char* src = "function foo(x) { "
+ " var a = 0;"
+ " while (a < x) {"
+ " a++;"
+ " }"
+ "}";
+ v8::Local<v8::Function> foo = CompileFunction(&env, src, "foo");
+ SetBreakPoint(foo, 8); // "var a = 0;"
+
+ // Looping 10 times.
+ step_action = StepIn;
+ break_point_hit_count = 0;
+ v8::Handle<v8::Value> argv_10[argc] = { v8::Number::New(10) };
+ foo->Call(env->Global(), argc, argv_10);
+ CHECK_EQ(23, break_point_hit_count);
+
+ // Looping 100 times.
+ step_action = StepIn;
+ break_point_hit_count = 0;
+ v8::Handle<v8::Value> argv_100[argc] = { v8::Number::New(100) };
+ foo->Call(env->Global(), argc, argv_100);
+ CHECK_EQ(203, break_point_hit_count);
+
+ // Get rid of the debug event listener.
+ v8::Debug::SetDebugEventListener(NULL);
+ CheckDebuggerUnloaded();
+}
+
+
+TEST(DebugStepDoWhile) {
+ v8::HandleScope scope;
+ DebugLocalContext env;
+
+ // Register a debug event listener which steps and counts.
+ v8::Debug::SetDebugEventListener(DebugEventStep);
+
+ // Create a function for testing stepping.
+ const int argc = 1;
+ const char* src = "function foo(x) { "
+ " var a = 0;"
+ " do {"
+ " a++;"
+ " } while (a < x)"
+ "}";
+ v8::Local<v8::Function> foo = CompileFunction(&env, src, "foo");
+ SetBreakPoint(foo, 8); // "var a = 0;"
+
+ // Looping 10 times.
+ step_action = StepIn;
+ break_point_hit_count = 0;
+ v8::Handle<v8::Value> argv_10[argc] = { v8::Number::New(10) };
+ foo->Call(env->Global(), argc, argv_10);
+ CHECK_EQ(22, break_point_hit_count);
+
+ // Looping 100 times.
+ step_action = StepIn;
+ break_point_hit_count = 0;
+ v8::Handle<v8::Value> argv_100[argc] = { v8::Number::New(100) };
+ foo->Call(env->Global(), argc, argv_100);
+ CHECK_EQ(202, break_point_hit_count);
// Get rid of the debug event listener.
v8::Debug::SetDebugEventListener(NULL);
}
+TEST(DebugStepForContinue) {
+ v8::HandleScope scope;
+ DebugLocalContext env;
+
+ // Register a debug event listener which steps and counts.
+ v8::Debug::SetDebugEventListener(DebugEventStep);
+
+ // Create a function for testing stepping.
+ const int argc = 1;
+ const char* src = "function foo(x) { "
+ " var a = 0;"
+ " var b = 0;"
+ " var c = 0;"
+ " for (var i = 0; i < x; i++) {"
+ " a++;"
+ " if (a % 2 == 0) continue;"
+ " b++;"
+ " c++;"
+ " }"
+ " return b;"
+ "}";
+ v8::Local<v8::Function> foo = CompileFunction(&env, src, "foo");
+ v8::Handle<v8::Value> result;
+ SetBreakPoint(foo, 8); // "var a = 0;"
+
+ // Each loop generates 4 or 5 steps depending on whether a is equal.
+
+ // Looping 10 times.
+ step_action = StepIn;
+ break_point_hit_count = 0;
+ v8::Handle<v8::Value> argv_10[argc] = { v8::Number::New(10) };
+ result = foo->Call(env->Global(), argc, argv_10);
+ CHECK_EQ(5, result->Int32Value());
+ CHECK_EQ(50, break_point_hit_count);
+
+ // Looping 100 times.
+ step_action = StepIn;
+ break_point_hit_count = 0;
+ v8::Handle<v8::Value> argv_100[argc] = { v8::Number::New(100) };
+ result = foo->Call(env->Global(), argc, argv_100);
+ CHECK_EQ(50, result->Int32Value());
+ CHECK_EQ(455, break_point_hit_count);
+
+ // Get rid of the debug event listener.
+ v8::Debug::SetDebugEventListener(NULL);
+ CheckDebuggerUnloaded();
+}
+
+
+TEST(DebugStepForBreak) {
+ v8::HandleScope scope;
+ DebugLocalContext env;
+
+ // Register a debug event listener which steps and counts.
+ v8::Debug::SetDebugEventListener(DebugEventStep);
+
+ // Create a function for testing stepping.
+ const int argc = 1;
+ const char* src = "function foo(x) { "
+ " var a = 0;"
+ " var b = 0;"
+ " var c = 0;"
+ " for (var i = 0; i < 1000; i++) {"
+ " a++;"
+ " if (a == x) break;"
+ " b++;"
+ " c++;"
+ " }"
+ " return b;"
+ "}";
+ v8::Local<v8::Function> foo = CompileFunction(&env, src, "foo");
+ v8::Handle<v8::Value> result;
+ SetBreakPoint(foo, 8); // "var a = 0;"
+
+ // Each loop generates 5 steps except for the last (when break is executed)
+ // which only generates 4.
+
+ // Looping 10 times.
+ step_action = StepIn;
+ break_point_hit_count = 0;
+ v8::Handle<v8::Value> argv_10[argc] = { v8::Number::New(10) };
+ result = foo->Call(env->Global(), argc, argv_10);
+ CHECK_EQ(9, result->Int32Value());
+ CHECK_EQ(53, break_point_hit_count);
+
+ // Looping 100 times.
+ step_action = StepIn;
+ break_point_hit_count = 0;
+ v8::Handle<v8::Value> argv_100[argc] = { v8::Number::New(100) };
+ result = foo->Call(env->Global(), argc, argv_100);
+ CHECK_EQ(99, result->Int32Value());
+ CHECK_EQ(503, break_point_hit_count);
+
+ // Get rid of the debug event listener.
+ v8::Debug::SetDebugEventListener(NULL);
+ CheckDebuggerUnloaded();
+}
+
+
+TEST(DebugStepForIn) {
+ v8::HandleScope scope;
+ DebugLocalContext env;
+
+ // Register a debug event listener which steps and counts.
+ v8::Debug::SetDebugEventListener(DebugEventStep);
+
+ v8::Local<v8::Function> foo;
+ const char* src_1 = "function foo() { "
+ " var a = [1, 2];"
+ " for (x in a) {"
+ " b = 0;"
+ " }"
+ "}";
+ foo = CompileFunction(&env, src_1, "foo");
+ SetBreakPoint(foo, 0); // "var a = ..."
+
+ step_action = StepIn;
+ break_point_hit_count = 0;
+ foo->Call(env->Global(), 0, NULL);
+ CHECK_EQ(6, break_point_hit_count);
+
+ const char* src_2 = "function foo() { "
+ " var a = {a:[1, 2, 3]};"
+ " for (x in a.a) {"
+ " b = 0;"
+ " }"
+ "}";
+ foo = CompileFunction(&env, src_2, "foo");
+ SetBreakPoint(foo, 0); // "var a = ..."
+
+ step_action = StepIn;
+ break_point_hit_count = 0;
+ foo->Call(env->Global(), 0, NULL);
+ CHECK_EQ(8, break_point_hit_count);
+
+ // Get rid of the debug event listener.
+ v8::Debug::SetDebugEventListener(NULL);
+ CheckDebuggerUnloaded();
+}
+
+
+TEST(DebugStepWith) {
+ v8::HandleScope scope;
+ DebugLocalContext env;
+
+ // Register a debug event listener which steps and counts.
+ v8::Debug::SetDebugEventListener(DebugEventStep);
+
+ // Create a function for testing stepping.
+ const char* src = "function foo(x) { "
+ " var a = {};"
+ " with (a) {}"
+ " with (b) {}"
+ "}";
+ env->Global()->Set(v8::String::New("b"), v8::Object::New());
+ v8::Local<v8::Function> foo = CompileFunction(&env, src, "foo");
+ v8::Handle<v8::Value> result;
+ SetBreakPoint(foo, 8); // "var a = {};"
+
+ step_action = StepIn;
+ break_point_hit_count = 0;
+ foo->Call(env->Global(), 0, NULL);
+ CHECK_EQ(4, break_point_hit_count);
+
+ // Get rid of the debug event listener.
+ v8::Debug::SetDebugEventListener(NULL);
+ CheckDebuggerUnloaded();
+}
+
+
+TEST(DebugConditional) {
+ v8::HandleScope scope;
+ DebugLocalContext env;
+
+ // Register a debug event listener which steps and counts.
+ v8::Debug::SetDebugEventListener(DebugEventStep);
+
+ // Create a function for testing stepping.
+ const char* src = "function foo(x) { "
+ " var a;"
+ " a = x ? 1 : 2;"
+ " return a;"
+ "}";
+ v8::Local<v8::Function> foo = CompileFunction(&env, src, "foo");
+ SetBreakPoint(foo, 0); // "var a;"
+
+ step_action = StepIn;
+ break_point_hit_count = 0;
+ foo->Call(env->Global(), 0, NULL);
+ CHECK_EQ(5, break_point_hit_count);
+
+ step_action = StepIn;
+ break_point_hit_count = 0;
+ const int argc = 1;
+ v8::Handle<v8::Value> argv_true[argc] = { v8::True() };
+ foo->Call(env->Global(), argc, argv_true);
+ CHECK_EQ(5, break_point_hit_count);
+
+ // Get rid of the debug event listener.
+ v8::Debug::SetDebugEventListener(NULL);
+ CheckDebuggerUnloaded();
+}
+
+
TEST(StepInOutSimple) {
v8::HandleScope scope;
DebugLocalContext env;
// Step through invocation of a.
step_action = StepIn;
break_point_hit_count = 0;
- expected_step_sequence = "abaca";
+ expected_step_sequence = "abbaca";
a->Call(env->Global(), 0, NULL);
CHECK_EQ(StrLength(expected_step_sequence),
break_point_hit_count);
foo->Call(env->Global(), 0, NULL);
// With stepping all break locations are hit.
- CHECK_EQ(6, break_point_hit_count);
+ CHECK_EQ(7, break_point_hit_count);
v8::Debug::SetDebugEventListener(NULL);
CheckDebuggerUnloaded();
// Check stepping where the if condition in bar is false.
break_point_hit_count = 0;
foo->Call(env->Global(), 0, NULL);
- CHECK_EQ(4, break_point_hit_count);
+ CHECK_EQ(6, break_point_hit_count);
// Check stepping where the if condition in bar is true.
break_point_hit_count = 0;
const int argc = 1;
v8::Handle<v8::Value> argv[argc] = { v8::True() };
foo->Call(env->Global(), argc, argv);
- CHECK_EQ(6, break_point_hit_count);
+ CHECK_EQ(8, break_point_hit_count);
v8::Debug::SetDebugEventListener(NULL);
CheckDebuggerUnloaded();
b->Call(env->Global(), 0, NULL);
CHECK_EQ(StrLength(expected_step_sequence),
break_point_hit_count);
-
// Step through invocation of d + e.
v8::Local<v8::Function> d = CompileFunction(&env, src, "d");
SetBreakPoint(d, 0);
ChangeBreakOnException(false, true);
step_action = StepIn;
break_point_hit_count = 0;
- expected_step_sequence = "dded";
+ expected_step_sequence = "ddedd";
d->Call(env->Global(), 0, NULL);
CHECK_EQ(StrLength(expected_step_sequence),
break_point_hit_count);
ChangeBreakOnException(true, true);
step_action = StepIn;
break_point_hit_count = 0;
- expected_step_sequence = "ddeed";
+ expected_step_sequence = "ddeedd";
d->Call(env->Global(), 0, NULL);
CHECK_EQ(StrLength(expected_step_sequence),
break_point_hit_count);
ChangeBreakOnException(false, true);
step_action = StepIn;
break_point_hit_count = 0;
- expected_step_sequence = "ffghf";
+ expected_step_sequence = "ffghhff";
f->Call(env->Global(), 0, NULL);
CHECK_EQ(StrLength(expected_step_sequence),
break_point_hit_count);
ChangeBreakOnException(true, true);
step_action = StepIn;
break_point_hit_count = 0;
- expected_step_sequence = "ffghhf";
+ expected_step_sequence = "ffghhhff";
f->Call(env->Global(), 0, NULL);
CHECK_EQ(StrLength(expected_step_sequence),
break_point_hit_count);
count = 0;
function f() {};
function g() {h(count++)};
-function h(x) {var a=x;};
+function h(x) {var a=x; return a};
// Conditional breakpoint which syntax error.
// Conditional breakpoint which checks a local variable.
break_point_hit_count = 0;
-bp = Debug.setBreakPoint(h, 0, 0, 'a % 2 == 0');
+bp = Debug.setBreakPoint(h, 0, 23, 'a % 2 == 0');
for (var i = 0; i < 10; i++) {
g();
}
// Multiple conditional breakpoint which the same condition.
break_point_hit_count = 0;
-bp1 = Debug.setBreakPoint(h, 0, 0, 'a % 2 == 0');
-bp2 = Debug.setBreakPoint(h, 0, 0, 'a % 2 == 0');
+bp1 = Debug.setBreakPoint(h, 0, 23, 'a % 2 == 0');
+bp2 = Debug.setBreakPoint(h, 0, 23, 'a % 2 == 0');
for (var i = 0; i < 10; i++) {
g();
}
// Multiple conditional breakpoint which different conditions.
break_point_hit_count = 0;
-bp1 = Debug.setBreakPoint(h, 0, 0, 'a % 2 == 0');
-bp2 = Debug.setBreakPoint(h, 0, 0, '(a + 1) % 2 == 0');
+bp1 = Debug.setBreakPoint(h, 0, 23, 'a % 2 == 0');
+bp2 = Debug.setBreakPoint(h, 0, 23, '(a + 1) % 2 == 0');
for (var i = 0; i < 10; i++) {
g();
}
// Test debug event for break point.
function f() {
- for (i = 0; i < 1000; i++) { // Line 1.
- x = 1; // Line 2.
+ var i; // Line 1.
+ for (i = 0; i < 1000; i++) { // Line 2.
+ x = 1; // Line 3.
}
};
// multiple steps have been requested.
state = 0;
result = -1;
-bp2 = Debug.setBreakPoint(f, 2);
+bp2 = Debug.setBreakPoint(f, 3);
f();
assertEquals(0, result);