More precise break points and stepping when debugging

Added support for more precise break points when debugging and stepping. To achieve that additional nop instructions are inserted where breaking would otherwise be impossible. The number of nop instructions inserted are sufficient to make place for patching with a call to a debug break code stub. On Intel that is 5 nop's for 32-bit and 13 for 64-bit. Om ARM 3 nop instructions (12 bytes) are required.

In order to avoid inserting nop's in to many places a simple ast checker have been added to check whether there are breakable code in a statement or expression. If it is possible to break in an expression no additional break enabeling code is inserted.

Added break locations to the true and false part of a conditional expression.

Added stepping tests to cover more constructs.

These changes are only in the full compiler.

Changed the default value for the option --debugger in teh d8 shell from true to false. The reason for this is that with --debugger turned on the full compiler will be used for all code in when running d8, which can be unexpeceted.

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

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@4820 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
sgjesse@chromium.org 2010-06-08 12:04:49 +00:00
parent a217c50ab2
commit 634fb9152c
39 changed files with 1187 additions and 121 deletions

View File

@ -168,6 +168,12 @@ bool RelocInfo::IsPatchedReturnSequence() {
}
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) {
@ -178,8 +184,10 @@ void RelocInfo::Visit(ObjectVisitor* visitor) {
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) {

View File

@ -2040,6 +2040,13 @@ void Assembler::RecordJSReturn() {
}
void Assembler::RecordDebugBreakSlot() {
WriteRecordedPositions();
CheckBuffer();
RecordRelocInfo(RelocInfo::DEBUG_BREAK_SLOT);
}
void Assembler::RecordComment(const char* msg) {
if (FLAG_debug_code) {
CheckBuffer();
@ -2062,13 +2069,16 @@ void Assembler::RecordStatementPosition(int pos) {
}
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
@ -2078,7 +2088,11 @@ void Assembler::WriteRecordedPositions() {
CheckBuffer();
RecordRelocInfo(RelocInfo::POSITION, current_position_);
written_position_ = current_position_;
written = true;
}
// Return whether something was written.
return written;
}
@ -2135,9 +2149,10 @@ void Assembler::GrowBuffer() {
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.

View File

@ -629,22 +629,39 @@ class Assembler : public Malloced {
// 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
@ -981,13 +998,16 @@ class Assembler : public Malloced {
// 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_; }

View File

@ -386,8 +386,10 @@ void CodeGenerator::Generate(CompilationInfo* info) {
// 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
}
}

View File

@ -226,7 +226,9 @@ class CodeGenerator: public AstVisitor {
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_; }

View File

@ -57,7 +57,7 @@ void BreakLocationIterator::SetDebugBreakAtReturn() {
// #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);
@ -73,17 +73,59 @@ void BreakLocationIterator::SetDebugBreakAtReturn() {
// 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)
@ -220,10 +262,31 @@ void Debug::GenerateStubNoRegistersDebugBreak(MacroAssembler* 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");
}

View File

@ -238,8 +238,10 @@ void FullCodeGenerator::EmitReturnSequence(int position) {
// 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
}
}

View File

@ -449,6 +449,11 @@ const char* RelocInfo::RelocModeName(RelocInfo::Mode rmode) {
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";
@ -513,6 +518,7 @@ void RelocInfo::Verify() {
case STATEMENT_POSITION:
case EXTERNAL_REFERENCE:
case INTERNAL_REFERENCE:
case DEBUG_BREAK_SLOT:
case NONE:
break;
case NUMBER_OF_MODES:

View File

@ -118,9 +118,9 @@ class RelocInfo BASE_EMBEDDED {
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.
@ -129,6 +129,7 @@ class RelocInfo BASE_EMBEDDED {
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.
@ -174,6 +175,9 @@ class RelocInfo BASE_EMBEDDED {
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
@ -243,6 +247,10 @@ class RelocInfo BASE_EMBEDDED {
// 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);

View File

@ -1469,10 +1469,14 @@ class Conditional: public Expression {
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);
@ -1482,10 +1486,15 @@ class Conditional: public Expression {
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_;
};

View File

@ -1360,10 +1360,17 @@ static void Generate_StubNoRegisters_DebugBreak(MacroAssembler* masm) {
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);
}

View File

@ -127,6 +127,7 @@ enum BuiltinExtraArguments {
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

View File

@ -285,14 +285,16 @@ template <int> class StaticAssertionHelper { };
#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

View File

@ -415,32 +415,41 @@ CodeGenerator::ConditionAnalysis CodeGenerator::AnalyzeCondition(
}
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);

View File

@ -576,6 +576,9 @@ Handle<String> Shell::ReadFile(const char* name) {
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;

View File

@ -129,10 +129,14 @@ void BreakLocationIterator::Next() {
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() &&
@ -329,6 +333,9 @@ void BreakLocationIterator::SetDebugBreak() {
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();
@ -346,6 +353,9 @@ void BreakLocationIterator::ClearDebugBreak() {
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();
@ -417,6 +427,8 @@ bool BreakLocationIterator::HasBreakPoint() {
bool BreakLocationIterator::IsDebugBreak() {
if (RelocInfo::IsJSReturn(rmode())) {
return IsDebugBreakAtReturn();
} else if (IsDebugBreakSlot()) {
return IsDebugBreakAtSlot();
} else {
return Debug::IsDebugBreak(rinfo()->target_address());
}
@ -478,6 +490,11 @@ bool BreakLocationIterator::IsDebuggerStatement() {
}
bool BreakLocationIterator::IsDebugBreakSlot() {
return RelocInfo::DEBUG_BREAK_SLOT == rmode();
}
Object* BreakLocationIterator::BreakPointObjects() {
return debug_info_->GetBreakPointObjects(code_position());
}
@ -573,6 +590,7 @@ bool Debug::break_on_uncaught_exception_ = true;
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) {
@ -656,6 +674,10 @@ void Debug::Setup(bool create_heap_objects) {
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());
}
}
@ -824,6 +846,7 @@ void Debug::PreemptionWhileInDebugger() {
void Debug::Iterate(ObjectVisitor* v) {
v->VisitPointer(BitCast<Object**, Code**>(&(debug_break_return_)));
v->VisitPointer(BitCast<Object**, Code**>(&(debug_break_slot_)));
}
@ -1631,16 +1654,21 @@ void Debug::SetAfterBreakTarget(JavaScriptFrame* frame) {
// 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();
}
@ -1657,25 +1685,30 @@ void Debug::SetAfterBreakTarget(JavaScriptFrame* frame) {
// 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.
} else if (at_debug_break_slot) {
// Address of where the debug break slot starts.
addr = addr - Assembler::kPatchDebugBreakSlotAddressOffset;
// Find the corresponding address in the original code.
addr += original_code->instruction_start() - code->instruction_start();
}
// 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);
}
}

View File

@ -146,6 +146,11 @@ class BreakLocationIterator {
void SetDebugBreakAtReturn();
void ClearDebugBreakAtReturn();
bool IsDebugBreakSlot();
bool IsDebugBreakAtSlot();
void SetDebugBreakAtSlot();
void ClearDebugBreakAtSlot();
DISALLOW_COPY_AND_ASSIGN(BreakLocationIterator);
};
@ -323,6 +328,7 @@ class Debug {
enum AddressId {
k_after_break_target_address,
k_debug_break_return_address,
k_debug_break_slot_address,
k_register_address
};
@ -342,6 +348,12 @@ class Debug {
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;
@ -370,6 +382,7 @@ class Debug {
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);
@ -377,6 +390,7 @@ class Debug {
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);
@ -472,6 +486,9 @@ class Debug {
// 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);
};
@ -895,6 +912,8 @@ class Debug_Address {
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:

View File

@ -277,7 +277,7 @@ DEFINE_string(testing_serialization_file, "/tmp/serdes",
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")

View File

@ -439,6 +439,231 @@ void FullCodeGenSyntaxChecker::VisitThisFunction(ThisFunction* expr) {
#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) {
@ -552,7 +777,60 @@ void FullCodeGenerator::SetReturnPosition(FunctionLiteral* fun) {
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
}
}
@ -848,7 +1126,11 @@ void FullCodeGenerator::VisitDoWhileStatement(DoWhileStatement* stmt) {
__ 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);
@ -864,7 +1146,6 @@ void FullCodeGenerator::VisitDoWhileStatement(DoWhileStatement* stmt) {
void FullCodeGenerator::VisitWhileStatement(WhileStatement* stmt) {
Comment cmnt(masm_, "[ WhileStatement");
SetStatementPosition(stmt);
Label body, stack_limit_hit, stack_check_success;
Iteration loop_statement(this, stmt);
@ -877,6 +1158,9 @@ void FullCodeGenerator::VisitWhileStatement(WhileStatement* 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);
@ -896,7 +1180,6 @@ void FullCodeGenerator::VisitWhileStatement(WhileStatement* stmt) {
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);
@ -919,6 +1202,9 @@ void FullCodeGenerator::VisitForStatement(ForStatement* 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);
@ -1064,6 +1350,8 @@ void FullCodeGenerator::VisitConditional(Conditional* expr) {
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) {
@ -1071,6 +1359,8 @@ void FullCodeGenerator::VisitConditional(Conditional* expr) {
}
__ 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) {

View File

@ -59,6 +59,31 @@ class FullCodeGenSyntaxChecker: public AstVisitor {
};
// 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.
@ -458,6 +483,7 @@ class FullCodeGenerator: public AstVisitor {
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);

View File

@ -52,16 +52,21 @@ Condition NegateCondition(Condition cc) {
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.
}
}
@ -154,6 +159,11 @@ bool RelocInfo::IsPatchedReturnSequence() {
}
bool RelocInfo::IsPatchedDebugBreakSlotSequence() {
return !Assembler::IsNop(pc());
}
void RelocInfo::Visit(ObjectVisitor* visitor) {
RelocInfo::Mode mode = rmode();
if (mode == RelocInfo::EMBEDDED_OBJECT) {
@ -164,8 +174,10 @@ void RelocInfo::Visit(ObjectVisitor* visitor) {
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) {

View File

@ -206,6 +206,7 @@ void RelocInfo::PatchCodeWithCall(Address target, int guard_bytes) {
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();
}
@ -2371,6 +2372,13 @@ void Assembler::RecordJSReturn() {
}
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);
@ -2393,13 +2401,16 @@ void Assembler::RecordStatementPosition(int pos) {
}
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
@ -2409,7 +2420,11 @@ void Assembler::WriteRecordedPositions() {
EnsureSpace ensure_space(this);
RecordRelocInfo(RelocInfo::POSITION, current_position_);
written_position_ = current_position_;
written = true;
}
// Return whether something was written.
return written;
}

View File

@ -468,9 +468,16 @@ class Assembler : public Malloced {
// 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
//
@ -809,13 +816,16 @@ class Assembler : public Malloced {
// 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.
@ -833,6 +843,8 @@ class Assembler : public Malloced {
// 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;

View File

@ -316,7 +316,9 @@ class CodeGenerator: public AstVisitor {
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_; }

View File

@ -69,6 +69,27 @@ bool Debug::IsDebugBreakAtReturn(RelocInfo* rinfo) {
}
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)
@ -208,10 +229,31 @@ void Debug::GenerateStubNoRegistersDebugBreak(MacroAssembler* 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,

View File

@ -273,8 +273,10 @@ class MarkingVisitor : public ObjectVisitor {
}
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);
}
@ -1106,8 +1108,10 @@ class PointersToNewGenUpdatingVisitor: public ObjectVisitor {
}
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());
@ -1856,8 +1860,10 @@ class UpdatingVisitor: public ObjectVisitor {
}
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(

View File

@ -1046,13 +1046,16 @@ void Assembler::RecordStatementPosition(int pos) {
}
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
@ -1062,7 +1065,11 @@ void Assembler::WriteRecordedPositions() {
CheckBuffer();
RecordRelocInfo(RelocInfo::POSITION, current_position_);
written_position_ = current_position_;
written = true;
}
// Return whether something was written.
return written;
}

View File

@ -355,6 +355,9 @@ class Assembler : public Malloced {
// 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.
@ -518,7 +521,7 @@ class Assembler : public Malloced {
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_; }

View File

@ -5264,8 +5264,10 @@ void ObjectVisitor::VisitCodeTarget(RelocInfo* rinfo) {
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);
@ -5278,6 +5280,7 @@ void Code::CodeIterateBody(ObjectVisitor* v) {
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()) {

View File

@ -2867,10 +2867,13 @@ Expression* Parser::ParseConditionalExpression(bool accept_IN, bool* ok) {
// 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));
}

View File

@ -229,6 +229,10 @@ void ExternalReferenceTable::PopulateTable() {
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,

View File

@ -210,6 +210,10 @@ void RelocInfo::apply(intptr_t delta) {
// 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
}
}
@ -298,6 +302,11 @@ bool RelocInfo::IsPatchedReturnSequence() {
}
bool RelocInfo::IsPatchedDebugBreakSlotSequence() {
return !Assembler::IsNop(pc());
}
Address RelocInfo::call_address() {
ASSERT(IsPatchedReturnSequence());
return Memory::Address_at(
@ -341,8 +350,10 @@ void RelocInfo::Visit(ObjectVisitor* visitor) {
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) {

View File

@ -2800,6 +2800,13 @@ void Assembler::RecordJSReturn() {
}
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);
@ -2822,13 +2829,16 @@ void Assembler::RecordStatementPosition(int pos) {
}
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
@ -2838,7 +2848,11 @@ void Assembler::WriteRecordedPositions() {
EnsureSpace ensure_space(this);
RecordRelocInfo(RelocInfo::POSITION, current_position_);
written_position_ = current_position_;
written = true;
}
// Return whether something was written.
return written;
}

View File

@ -455,6 +455,11 @@ class Assembler : public Malloced {
// 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;
@ -463,6 +468,10 @@ class Assembler : public Malloced {
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
//
@ -1135,13 +1144,16 @@ class Assembler : public Malloced {
// 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_; }
@ -1159,6 +1171,8 @@ class Assembler : public Malloced {
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;

View File

@ -314,7 +314,9 @@ class CodeGenerator: public AstVisitor {
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_; }

View File

@ -181,10 +181,31 @@ void Debug::GenerateStubNoRegistersDebugBreak(MacroAssembler* 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");
}
@ -217,6 +238,28 @@ void BreakLocationIterator::SetDebugBreakAtReturn() {
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

View File

@ -1231,6 +1231,11 @@ TEST(GCDuringBreakPointProcessing) {
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();
}
@ -1660,7 +1665,7 @@ TEST(ConditionalScriptBreakPoint) {
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);
@ -2144,17 +2149,19 @@ TEST(DebugEvaluate) {
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);
@ -2164,7 +2171,7 @@ TEST(DebugEvaluate) {
// 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);
@ -2426,6 +2433,9 @@ TEST(DebugStepKeyedLoadLoop) {
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(
@ -2451,9 +2461,6 @@ TEST(DebugStepKeyedLoadLoop) {
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;
@ -2461,7 +2468,7 @@ TEST(DebugStepKeyedLoadLoop) {
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();
@ -2473,6 +2480,9 @@ TEST(DebugStepKeyedStoreLoop) {
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(
@ -2497,9 +2507,6 @@ TEST(DebugStepKeyedStoreLoop) {
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;
@ -2507,7 +2514,7 @@ TEST(DebugStepKeyedStoreLoop) {
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();
@ -2519,6 +2526,9 @@ TEST(DebugStepNamedLoadLoop) {
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,
@ -2541,9 +2551,6 @@ TEST(DebugStepNamedLoadLoop) {
// 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;
@ -2551,7 +2558,7 @@ TEST(DebugStepNamedLoadLoop) {
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();
@ -2563,6 +2570,9 @@ TEST(DebugStepLinearMixedICs) {
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() {};"
@ -2573,15 +2583,12 @@ TEST(DebugStepLinearMixedICs) {
" 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();
@ -2601,6 +2608,66 @@ TEST(DebugStepLinearMixedICs) {
}
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;
@ -2627,14 +2694,14 @@ TEST(DebugStepIf) {
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);
@ -2662,6 +2729,7 @@ TEST(DebugStepSwitch) {
" case 3:"
" d = 1;"
" e = 1;"
" f = 1;"
" break;"
" }"
"}";
@ -2673,21 +2741,97 @@ TEST(DebugStepSwitch) {
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);
@ -2733,6 +2877,210 @@ TEST(DebugStepFor) {
}
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;
@ -2854,7 +3202,7 @@ TEST(StepInOutBranch) {
// 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);
@ -2923,7 +3271,7 @@ TEST(DebugStepFunctionApply) {
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();
@ -2967,14 +3315,14 @@ TEST(DebugStepFunctionCall) {
// 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();
@ -3267,14 +3615,13 @@ TEST(StepWithException) {
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);
@ -3283,7 +3630,7 @@ TEST(StepWithException) {
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);
@ -3294,7 +3641,7 @@ TEST(StepWithException) {
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);
@ -3303,7 +3650,7 @@ TEST(StepWithException) {
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);

View File

@ -45,7 +45,7 @@ Debug.setListener(listener);
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.
@ -136,7 +136,7 @@ Debug.clearBreakPoint(bp);
// 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();
}
@ -146,8 +146,8 @@ Debug.clearBreakPoint(bp);
// 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();
}
@ -159,8 +159,8 @@ Debug.clearBreakPoint(bp2);
// 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();
}

View File

@ -55,8 +55,9 @@ Debug.setListener(listener);
// 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.
}
};
@ -74,7 +75,7 @@ assertEquals(499, result);
// multiple steps have been requested.
state = 0;
result = -1;
bp2 = Debug.setBreakPoint(f, 2);
bp2 = Debug.setBreakPoint(f, 3);
f();
assertEquals(0, result);