Commit 34d6ff76 authored by sgjesse@chromium.org's avatar sgjesse@chromium.org

Implemented missing pieces of the debugger for ARM.

The main piece of this change was to add support for break on return for ARM. On ARM the normal js function return consist of the following code sequence.

  mov sp, fp
  ldmia sp!, {fp, lr}
  add sp, sp, #4
  bx lr

to a call to the debug break return entry code using the following code sequence

  mov lr, pc
  ldr pc, [pc, #-4]
  <debug break return entry code entry point address>
  bktp 0

The values of Assembler::kPatchReturnSequenceLength and Assembler::kPatchReturnSequenceLength are somewhat misleading, but they fit the current use in the debugger. Also Assembler::kPatchReturnSequenceLength is used in the IC code as well (for something which is not related to return sequences at all). I will change that in a separate changelist.

For the debugger to work also added recording of the return sequence in the relocation info and handling of source position recording when a function ends with a return statement.

Used the constant kInstrSize instead of sizeof(Instr).

Passes all debugger tests on both simulator and hardware (only release mode tested on hardware).
Review URL: http://codereview.chromium.org/199075

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@2879 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent 6f88ca63
......@@ -105,40 +105,45 @@ Address* RelocInfo::target_reference_address() {
Address RelocInfo::call_address() {
ASSERT(IsCallInstruction());
UNIMPLEMENTED();
return NULL;
// The 2 instructions offset assumes patched return sequence.
ASSERT(IsJSReturn(rmode()));
return Memory::Address_at(pc_ + 2 * Assembler::kInstrSize);
}
void RelocInfo::set_call_address(Address target) {
ASSERT(IsCallInstruction());
UNIMPLEMENTED();
// The 2 instructions offset assumes patched return sequence.
ASSERT(IsJSReturn(rmode()));
Memory::Address_at(pc_ + 2 * Assembler::kInstrSize) = target;
}
Object* RelocInfo::call_object() {
ASSERT(IsCallInstruction());
UNIMPLEMENTED();
return NULL;
return *call_object_address();
}
Object** RelocInfo::call_object_address() {
ASSERT(IsCallInstruction());
UNIMPLEMENTED();
return NULL;
// The 2 instructions offset assumes patched return sequence.
ASSERT(IsJSReturn(rmode()));
return reinterpret_cast<Object**>(pc_ + 2 * Assembler::kInstrSize);
}
void RelocInfo::set_call_object(Object* target) {
ASSERT(IsCallInstruction());
UNIMPLEMENTED();
*call_object_address() = target;
}
bool RelocInfo::IsCallInstruction() {
UNIMPLEMENTED();
return false;
// On ARM a "call instruction" is actually two instructions.
// mov lr, pc
// ldr pc, [pc, #XXX]
return (Assembler::instr_at(pc_) == kMovLrPc)
&& ((Assembler::instr_at(pc_ + Assembler::kInstrSize) & kLdrPCPattern)
== kLdrPCPattern);
}
......
......@@ -93,7 +93,14 @@ const int RelocInfo::kApplyMask = 0;
void RelocInfo::PatchCode(byte* instructions, int instruction_count) {
// Patch the code at the current address with the supplied instructions.
UNIMPLEMENTED();
Instr* pc = reinterpret_cast<Instr*>(pc_);
Instr* instr = reinterpret_cast<Instr*>(instructions);
for (int i = 0; i < instruction_count; i++) {
*(pc + i) = *(instr + i);
}
// Indicate that code has changed.
CPU::FlushICache(pc_, instruction_count * Assembler::kInstrSize);
}
......@@ -232,6 +239,10 @@ static const Instr kPushRegPattern =
// register r is not encoded.
static const Instr kPopRegPattern =
al | B26 | L | 4 | PostIndex | sp.code() * B16;
// mov lr, pc
const Instr kMovLrPc = al | 13*B21 | pc.code() | lr.code() * B12;
// ldr pc, [pc, #XXX]
const Instr kLdrPCPattern = al | B26 | L | pc.code() * B16;
// spare_buffer_
static const int kMinimalBufferSize = 4*KB;
......@@ -1301,6 +1312,13 @@ void Assembler::lea(Register dst,
// Debugging
void Assembler::RecordJSReturn() {
WriteRecordedPositions();
CheckBuffer();
RecordRelocInfo(RelocInfo::JS_RETURN);
}
void Assembler::RecordComment(const char* msg) {
if (FLAG_debug_code) {
CheckBuffer();
......@@ -1387,16 +1405,20 @@ void Assembler::GrowBuffer() {
RelocInfo& rinfo = prinfo_[i];
ASSERT(rinfo.rmode() != RelocInfo::COMMENT &&
rinfo.rmode() != RelocInfo::POSITION);
rinfo.set_pc(rinfo.pc() + pc_delta);
if (rinfo.rmode() != RelocInfo::JS_RETURN) {
rinfo.set_pc(rinfo.pc() + pc_delta);
}
}
}
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::COMMENT && rmode <= RelocInfo::STATEMENT_POSITION) {
// adjust code for new modes
ASSERT(RelocInfo::IsComment(rmode) || RelocInfo::IsPosition(rmode));
if (rmode >= RelocInfo::JS_RETURN && rmode <= RelocInfo::STATEMENT_POSITION) {
// Adjust code for new modes
ASSERT(RelocInfo::IsJSReturn(rmode)
|| RelocInfo::IsComment(rmode)
|| RelocInfo::IsPosition(rmode));
// these modes do not need an entry in the constant pool
} else {
ASSERT(num_prinfo_ < kMaxNumPRInfo);
......@@ -1490,6 +1512,7 @@ void Assembler::CheckConstPool(bool force_emit, bool require_jump) {
rinfo.rmode() != RelocInfo::POSITION &&
rinfo.rmode() != RelocInfo::STATEMENT_POSITION);
Instr instr = instr_at(rinfo.pc());
// Instruction to patch must be a ldr/str [pc, #offset]
// P and U set, B and W clear, Rn == pc, offset12 still 0
ASSERT((instr & (7*B25 | P | U | B | W | 15*B16 | Off12Mask)) ==
......
......@@ -376,6 +376,10 @@ class MemOperand BASE_EMBEDDED {
typedef int32_t Instr;
extern const Instr kMovLrPc;
extern const Instr kLdrPCPattern;
class Assembler : public Malloced {
public:
// Create an assembler. Instructions and relocation information are emitted
......@@ -433,12 +437,16 @@ class Assembler : public Malloced {
INLINE(static Address target_address_at(Address pc));
INLINE(static void set_target_address_at(Address pc, Address target));
// Size of an instruction.
static const int kInstrSize = sizeof(Instr);
// Distance between the instruction referring to the address of the call
// target (ldr pc, [target addr in const pool]) and the return address
static const int kCallTargetAddressOffset = sizeof(Instr);
static const int kCallTargetAddressOffset = kInstrSize;
// Distance between start of patched return sequence and the emitted address
// to jump to.
static const int kPatchReturnSequenceAddressOffset = 1;
static const int kPatchReturnSequenceAddressOffset = kInstrSize;
// Difference between address of current opcode and value read from pc
// register.
......@@ -652,9 +660,16 @@ class Assembler : public Malloced {
// Jump unconditionally to given label.
void jmp(Label* L) { b(L, al); }
// Check the code size generated from label to here.
int InstructionsGeneratedSince(Label* l) {
return (pc_offset() - l->pos()) / kInstrSize;
}
// Debugging
// Mark address of the ExitJSFrame code.
void RecordJSReturn();
// Record a comment relocation entry that can be used by a disassembler.
// Use --debug_code to enable.
void RecordComment(const char* msg);
......@@ -671,7 +686,7 @@ class Assembler : public Malloced {
int buffer_space() const { return reloc_info_writer.pos() - pc_; }
// Read/patch instructions
Instr instr_at(byte* pc) { return *reinterpret_cast<Instr*>(pc); }
static Instr instr_at(byte* pc) { return *reinterpret_cast<Instr*>(pc); }
void instr_at_put(byte* pc, Instr instr) {
*reinterpret_cast<Instr*>(pc) = instr;
}
......@@ -708,7 +723,6 @@ class Assembler : public Malloced {
int next_buffer_check_; // pc offset of next buffer check
// Code generation
static const int kInstrSize = sizeof(Instr); // signed size
// The relocation writer's position is at least kGap bytes below the end of
// the generated instructions. This is so that multi-instruction sequences do
// not have to check for overflow. The same is true for writes of large
......@@ -795,6 +809,8 @@ class Assembler : public Malloced {
void RecordRelocInfo(RelocInfo::Mode rmode, intptr_t data = 0);
friend class RegExpMacroAssemblerARM;
friend class RelocInfo;
friend class CodePatcher;
};
} } // namespace v8::internal
......
......@@ -299,7 +299,10 @@ void CodeGenerator::GenCode(FunctionLiteral* fun) {
}
// Generate the return sequence if necessary.
if (frame_ != NULL || function_return_.is_linked()) {
if (has_valid_frame() || function_return_.is_linked()) {
if (!function_return_.is_linked()) {
CodeForReturnPosition(fun);
}
// exit
// r0: result
// sp: stack pointer
......@@ -315,12 +318,24 @@ void CodeGenerator::GenCode(FunctionLiteral* fun) {
frame_->CallRuntime(Runtime::kTraceExit, 1);
}
// Add a label for checking the size of the code used for returning.
Label check_exit_codesize;
masm_->bind(&check_exit_codesize);
// Tear down the frame which will restore the caller's frame pointer and
// the link register.
frame_->Exit();
__ add(sp, sp, Operand((scope_->num_parameters() + 1) * kPointerSize));
__ Jump(lr);
// Here we use masm_-> instead of the __ macro to avoid the code coverage
// tool from instrumenting as we rely on the code size here.
masm_->add(sp, sp, Operand((scope_->num_parameters() + 1) * kPointerSize));
masm_->Jump(lr);
// Check that the size of the code used for returning matches what is
// expected by the debugger.
ASSERT_EQ(kJSReturnSequenceLength,
masm_->InstructionsGeneratedSince(&check_exit_codesize));
}
// Code generation state must be reset.
......@@ -1111,10 +1126,10 @@ void CodeGenerator::CheckStack() {
if (FLAG_check_stack) {
Comment cmnt(masm_, "[ check stack");
__ LoadRoot(ip, Heap::kStackLimitRootIndex);
// Put the lr setup instruction in the delay slot. The 'sizeof(Instr)' is
// added to the implicit 8 byte offset that always applies to operations
// with pc and gives a return address 12 bytes down.
masm_->add(lr, pc, Operand(sizeof(Instr)));
// Put the lr setup instruction in the delay slot. kInstrSize is added to
// the implicit 8 byte offset that always applies to operations with pc and
// gives a return address 12 bytes down.
masm_->add(lr, pc, Operand(Assembler::kInstrSize));
masm_->cmp(sp, Operand(ip));
StackCheckStub stub;
// Call the stub if lower.
......@@ -1380,16 +1395,12 @@ void CodeGenerator::VisitReturnStatement(ReturnStatement* node) {
VirtualFrame::SpilledScope spilled_scope;
Comment cmnt(masm_, "[ ReturnStatement");
CodeForStatementPosition(node);
LoadAndSpill(node->expression());
if (function_return_is_shadowed_) {
CodeForStatementPosition(node);
LoadAndSpill(node->expression());
frame_->EmitPop(r0);
function_return_.Jump();
} else {
// Load the returned value.
CodeForStatementPosition(node);
LoadAndSpill(node->expression());
// Pop the result from the frame and prepare the frame for
// returning thus making it easier to merge.
frame_->EmitPop(r0);
......
......@@ -180,6 +180,10 @@ class CodeGenerator: public AstVisitor {
static const int kUnknownIntValue = -1;
// Number of instructions used for the JS return sequence. The constant is
// used by the debugger to patch the JS return sequence.
static const int kJSReturnSequenceLength = 4;
private:
// Construction/Destruction
CodeGenerator(int buffer_size, Handle<Script> script, bool is_eval);
......
......@@ -34,28 +34,41 @@ namespace v8 {
namespace internal {
#ifdef ENABLE_DEBUGGER_SUPPORT
// Currently debug break is not supported in frame exit code on ARM.
bool BreakLocationIterator::IsDebugBreakAtReturn() {
return false;
return Debug::IsDebugBreakAtReturn(rinfo());
}
// Currently debug break is not supported in frame exit code on ARM.
void BreakLocationIterator::SetDebugBreakAtReturn() {
UNIMPLEMENTED();
// Patch the code changing the return from JS function sequence from
// mov sp, fp
// ldmia sp!, {fp, lr}
// add sp, sp, #4
// bx lr
// to a call to the debug break return code.
// mov lr, pc
// ldr pc, [pc, #-4]
// <debug break return code entry point address>
// bktp 0
CodePatcher patcher(rinfo()->pc(), 4);
patcher.masm()->mov(v8::internal::lr, v8::internal::pc);
patcher.masm()->ldr(v8::internal::pc, MemOperand(v8::internal::pc, -4));
patcher.Emit(Debug::debug_break_return()->entry());
patcher.masm()->bkpt(0);
}
// Currently debug break is not supported in frame exit code on ARM.
// Restore the JS frame exit code.
void BreakLocationIterator::ClearDebugBreakAtReturn() {
UNIMPLEMENTED();
rinfo()->PatchCode(original_rinfo()->pc(),
CodeGenerator::kJSReturnSequenceLength);
}
// A debug break in the exit code is identified by a call.
bool Debug::IsDebugBreakAtReturn(RelocInfo* rinfo) {
ASSERT(RelocInfo::IsJSReturn(rinfo->rmode()));
// Currently debug break is not supported in frame exit code on ARM.
return false;
return rinfo->IsCallInstruction();
}
......@@ -95,8 +108,6 @@ static void Generate_DebugBreakCallHelper(MacroAssembler* masm,
__ LeaveInternalFrame();
// Inlined ExitJSFrame ends here.
// Finally restore all registers.
__ RestoreRegistersFromMemory(kJSCallerSaved);
......@@ -138,12 +149,20 @@ void Debug::GenerateStoreICDebugBreak(MacroAssembler* masm) {
void Debug::GenerateKeyedLoadICDebugBreak(MacroAssembler* masm) {
// Keyed load IC not implemented on ARM.
// ---------- S t a t e --------------
// -- lr : return address
// -- sp[0] : key
// -- sp[4] : receiver
Generate_DebugBreakCallHelper(masm, 0);
}
void Debug::GenerateKeyedStoreICDebugBreak(MacroAssembler* masm) {
// Keyed store IC not implemented on ARM.
// ---------- S t a t e --------------
// -- lr : return address
// -- sp[0] : key
// -- sp[4] : receiver
Generate_DebugBreakCallHelper(masm, 0);
}
......@@ -180,7 +199,10 @@ void Debug::GenerateReturnDebugBreak(MacroAssembler* masm) {
void Debug::GenerateStubNoRegistersDebugBreak(MacroAssembler* masm) {
// Generate nothing as CodeStub CallFunction is not used on ARM.
// ----------- S t a t e -------------
// No registers used on entry.
// -----------------------------------
Generate_DebugBreakCallHelper(masm, 0);
}
......
......@@ -133,7 +133,7 @@ void MacroAssembler::Call(intptr_t target, RelocInfo::Mode rmode,
// and the target address of the call would be referenced by the first
// instruction rather than the second one, which would make it harder to patch
// (two instructions before the return address, instead of one).
ASSERT(kCallTargetAddressOffset == sizeof(Instr));
ASSERT(kCallTargetAddressOffset == kInstrSize);
}
......@@ -167,7 +167,7 @@ void MacroAssembler::SmiJumpTable(Register index, Vector<Label*> targets) {
add(pc, pc, Operand(index,
LSL,
assembler::arm::Instr::kInstrSizeLog2 - kSmiTagSize));
BlockConstPoolBefore(pc_offset() + (targets.length() + 1) * sizeof(Instr));
BlockConstPoolBefore(pc_offset() + (targets.length() + 1) * kInstrSize);
nop(); // Jump table alignment.
for (int i = 0; i < targets.length(); i++) {
b(targets[i]);
......@@ -1054,7 +1054,7 @@ void MacroAssembler::InvokeBuiltin(Builtins::JavaScript id,
Bootstrapper::FixupFlagsArgumentsCount::encode(argc) |
Bootstrapper::FixupFlagsIsPCRelative::encode(true) |
Bootstrapper::FixupFlagsUseCodeObject::encode(false);
Unresolved entry = { pc_offset() - sizeof(Instr), flags, name };
Unresolved entry = { pc_offset() - kInstrSize, flags, name };
unresolved_.Add(entry);
}
}
......@@ -1072,7 +1072,7 @@ void MacroAssembler::GetBuiltinEntry(Register target, Builtins::JavaScript id) {
Bootstrapper::FixupFlagsArgumentsCount::encode(argc) |
Bootstrapper::FixupFlagsIsPCRelative::encode(true) |
Bootstrapper::FixupFlagsUseCodeObject::encode(true);
Unresolved entry = { pc_offset() - sizeof(Instr), flags, name };
Unresolved entry = { pc_offset() - kInstrSize, flags, name };
unresolved_.Add(entry);
}
......@@ -1153,4 +1153,38 @@ void MacroAssembler::Abort(const char* msg) {
}
#ifdef ENABLE_DEBUGGER_SUPPORT
CodePatcher::CodePatcher(byte* address, int instructions)
: address_(address),
instructions_(instructions),
size_(instructions * Assembler::kInstrSize),
masm_(address, size_ + Assembler::kGap) {
// Create a new macro assembler pointing to the address of the code to patch.
// The size is adjusted with kGap on order for the assembler to generate size
// bytes of instructions without failing with buffer size constraints.
ASSERT(masm_.reloc_info_writer.pos() == address_ + size_ + Assembler::kGap);
}
CodePatcher::~CodePatcher() {
// Indicate that code has changed.
CPU::FlushICache(address_, size_);
// Check that the code was patched as expected.
ASSERT(masm_.pc_ == address_ + size_);
ASSERT(masm_.reloc_info_writer.pos() == address_ + size_ + Assembler::kGap);
}
void CodePatcher::Emit(Instr x) {
masm()->emit(x);
}
void CodePatcher::Emit(Address addr) {
masm()->emit(reinterpret_cast<Instr>(addr));
}
#endif // ENABLE_DEBUGGER_SUPPORT
} } // namespace v8::internal
......@@ -339,6 +339,35 @@ class MacroAssembler: public Assembler {
};
#ifdef ENABLE_DEBUGGER_SUPPORT
// The code patcher is used to patch (typically) small parts of code e.g. for
// debugging and other types of instrumentation. When using the code patcher
// the exact number of bytes specified must be emitted. It is not legal to emit
// relocation information. If any of these constraints are violated it causes
// an assertion to fail.
class CodePatcher {
public:
CodePatcher(byte* address, int instructions);
virtual ~CodePatcher();
// Macro assembler to emit code.
MacroAssembler* masm() { return &masm_; }
// Emit an instruction directly.
void Emit(Instr x);
// Emit an address directly.
void Emit(Address addr);
private:
byte* address_; // The address of the code being patched.
int instructions_; // Number of instructions of the expected patch size.
int size_; // Number of bytes of the expected patch size.
MacroAssembler masm_; // Macro assembler used to generate the code.
};
#endif // ENABLE_DEBUGGER_SUPPORT
// -----------------------------------------------------------------------------
// Static helper functions.
......
......@@ -127,6 +127,10 @@ void VirtualFrame::Enter() {
void VirtualFrame::Exit() {
Comment cmnt(masm(), "[ Exit JS frame");
// Record the location of the JS exit code for patching when setting
// break point.
__ RecordJSReturn();
// Drop the execution stack down to the frame pointer and restore the caller
// frame pointer and return address.
__ mov(sp, fp);
......@@ -149,10 +153,10 @@ void VirtualFrame::AllocateStackSlots() {
__ push(ip);
}
if (FLAG_check_stack) {
// Put the lr setup instruction in the delay slot. The 'sizeof(Instr)' is
// added to the implicit 8 byte offset that always applies to operations
// with pc and gives a return address 12 bytes down.
masm()->add(lr, pc, Operand(sizeof(Instr)));
// Put the lr setup instruction in the delay slot. The kInstrSize is added
// to the implicit 8 byte offset that always applies to operations with pc
// and gives a return address 12 bytes down.
masm()->add(lr, pc, Operand(Assembler::kInstrSize));
masm()->cmp(sp, Operand(r2));
StackCheckStub stub;
// Call the stub if lower.
......
......@@ -36,8 +36,6 @@ test-api/ApplyInterruption: PASS || TIMEOUT
[ $arch == arm ]
test-debug: SKIP
# BUG(113): Test seems flaky on ARM.
test-spaces/LargeObjectSpace: PASS || FAIL
......
......@@ -2301,13 +2301,8 @@ TEST(DebugStepLinearMixedICs) {
break_point_hit_count = 0;
foo->Call(env->Global(), 0, NULL);
// With stepping all break locations are hit. For ARM the keyed load/store
// is not hit as they are not implemented as ICs.
#if defined (__arm__) || defined(__thumb__)
CHECK_EQ(6, break_point_hit_count);
#else
// With stepping all break locations are hit.
CHECK_EQ(8, break_point_hit_count);
#endif
v8::Debug::SetDebugEventListener(NULL);
CheckDebuggerUnloaded();
......
......@@ -45,31 +45,6 @@ debug-scripts-request: PASS, SKIP if $mode == debug
# Flaky test that can hit compilation-time stack overflow in debug mode.
unicode-test: PASS, (PASS || FAIL) if $mode == debug
# Bug number 1020483: Debug tests fail on ARM.
debug-constructor: CRASH, FAIL
debug-continue: SKIP
debug-evaluate-recursive: CRASH || FAIL
debug-changebreakpoint: CRASH || FAIL
debug-clearbreakpoint: CRASH || FAIL
debug-clearbreakpointgroup: PASS, FAIL if $mode == debug
debug-conditional-breakpoints: CRASH || FAIL
debug-evaluate: CRASH || FAIL
debug-ignore-breakpoints: CRASH || FAIL
debug-multiple-breakpoints: CRASH || FAIL
debug-setbreakpoint: CRASH || FAIL || PASS
debug-step-stub-callfunction: SKIP
debug-stepin-accessor: CRASH || FAIL
debug-stepin-builtin: CRASH || FAIL
debug-stepin-call-function-stub: CRASH || FAIL
debug-stepin-constructor: CRASH, FAIL
debug-stepin-function-call: CRASH || FAIL
debug-stepout-recursive-function: CRASH || FAIL
debug-stepout-to-builtin: CRASH || FAIL
debug-step: SKIP
debug-breakpoints: PASS || FAIL
debug-handle: CRASH || FAIL || PASS
regress/regress-269: SKIP
# Bug number 130 http://code.google.com/p/v8/issues/detail?id=130
# Fails on real ARM hardware but not on the simulator.
string-compare-alignment: PASS || FAIL
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment