// Copyright 2012 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "src/v8.h" #include "src/api.h" #include "src/arguments.h" #include "src/bootstrapper.h" #include "src/code-stubs.h" #include "src/codegen.h" #include "src/compilation-cache.h" #include "src/compiler.h" #include "src/debug.h" #include "src/deoptimizer.h" #include "src/execution.h" #include "src/full-codegen.h" #include "src/global-handles.h" #include "src/list.h" #include "src/log.h" #include "src/messages.h" #include "src/snapshot/natives.h" #include "include/v8-debug.h" namespace v8 { namespace internal { Debug::Debug(Isolate* isolate) : debug_context_(Handle()), event_listener_(Handle()), event_listener_data_(Handle()), message_handler_(NULL), command_received_(0), command_queue_(isolate->logger(), kQueueInitialSize), is_active_(false), is_suppressed_(false), live_edit_enabled_(true), // TODO(yangguo): set to false by default. has_break_points_(false), break_disabled_(false), in_debug_event_listener_(false), break_on_exception_(false), break_on_uncaught_exception_(false), script_cache_(NULL), debug_info_list_(NULL), isolate_(isolate) { ThreadInit(); } static v8::Local GetDebugEventContext(Isolate* isolate) { Handle context = isolate->debug()->debugger_entry()->GetContext(); // Isolate::context() may have been NULL when "script collected" event // occured. if (context.is_null()) return v8::Local(); Handle native_context(context->native_context()); return v8::Utils::ToLocal(native_context); } BreakLocation::BreakLocation(Handle debug_info, RelocInfo* rinfo, RelocInfo* original_rinfo, int position, int statement_position) : debug_info_(debug_info), pc_offset_(static_cast(rinfo->pc() - debug_info->code()->entry())), original_pc_offset_(static_cast( original_rinfo->pc() - debug_info->original_code()->entry())), rmode_(rinfo->rmode()), original_rmode_(original_rinfo->rmode()), data_(rinfo->data()), original_data_(original_rinfo->data()), position_(position), statement_position_(statement_position) {} BreakLocation::Iterator::Iterator(Handle debug_info, BreakLocatorType type) : debug_info_(debug_info), type_(type), reloc_iterator_(debug_info->code(), ~RelocInfo::ModeMask(RelocInfo::CODE_AGE_SEQUENCE)), reloc_iterator_original_( debug_info->original_code(), ~RelocInfo::ModeMask(RelocInfo::CODE_AGE_SEQUENCE)), break_index_(-1), position_(1), statement_position_(1) { Next(); } void BreakLocation::Iterator::Next() { DisallowHeapAllocation no_gc; DCHECK(!RinfoDone()); // Iterate through reloc info for code and original code stopping at each // breakable code target. bool first = break_index_ == -1; while (!RinfoDone()) { if (!first) RinfoNext(); first = false; if (RinfoDone()) return; // Whenever a statement position or (plain) position is passed update the // current value of these. if (RelocInfo::IsPosition(rmode())) { if (RelocInfo::IsStatementPosition(rmode())) { statement_position_ = static_cast( rinfo()->data() - debug_info_->shared()->start_position()); } // Always update the position as we don't want that to be before the // statement position. position_ = static_cast(rinfo()->data() - debug_info_->shared()->start_position()); DCHECK(position_ >= 0); DCHECK(statement_position_ >= 0); continue; } // Check for break at return. if (RelocInfo::IsJSReturn(rmode())) { // Set the positions to the end of the function. if (debug_info_->shared()->HasSourceCode()) { position_ = debug_info_->shared()->end_position() - debug_info_->shared()->start_position() - 1; } else { position_ = 0; } statement_position_ = position_; break_index_++; break; } 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 (RelocInfo::IsConstructCall(rmode()) || code->is_call_stub()) { break_index_++; break; } if (code->kind() == Code::STUB && CodeStub::GetMajorKey(code) == CodeStub::CallFunction) { break_index_++; break; } } // Skip below if we only want locations for calls and returns. if (type_ == CALLS_AND_RETURNS) continue; if (RelocInfo::IsDebuggerStatement(rmode())) { break_index_++; break; } if (RelocInfo::IsDebugBreakSlot(rmode()) && type_ != CALLS_AND_RETURNS) { // There is always a possible break point at a debug break slot. break_index_++; break; } } } // Find the break point at the supplied address, or the closest one before // the address. BreakLocation BreakLocation::FromAddress(Handle debug_info, BreakLocatorType type, Address pc) { Iterator it(debug_info, type); it.SkipTo(BreakIndexFromAddress(debug_info, type, pc)); return it.GetBreakLocation(); } // Find the break point at the supplied address, or the closest one before // the address. void BreakLocation::FromAddressSameStatement(Handle debug_info, BreakLocatorType type, Address pc, List* result_out) { int break_index = BreakIndexFromAddress(debug_info, type, pc); Iterator it(debug_info, type); it.SkipTo(break_index); int statement_position = it.statement_position(); while (!it.Done() && it.statement_position() == statement_position) { result_out->Add(it.GetBreakLocation()); it.Next(); } } int BreakLocation::BreakIndexFromAddress(Handle debug_info, BreakLocatorType type, Address pc) { // Run through all break points to locate the one closest to the address. int closest_break = 0; int distance = kMaxInt; for (Iterator it(debug_info, type); !it.Done(); it.Next()) { // Check if this break point is closer that what was previously found. if (it.pc() <= pc && pc - it.pc() < distance) { closest_break = it.break_index(); distance = static_cast(pc - it.pc()); // Check whether we can't get any closer. if (distance == 0) break; } } return closest_break; } BreakLocation BreakLocation::FromPosition(Handle debug_info, BreakLocatorType type, int position, BreakPositionAlignment alignment) { // Run through all break points to locate the one closest to the source // position. int closest_break = 0; int distance = kMaxInt; for (Iterator it(debug_info, type); !it.Done(); it.Next()) { int next_position; if (alignment == STATEMENT_ALIGNED) { next_position = it.statement_position(); } else { DCHECK(alignment == BREAK_POSITION_ALIGNED); next_position = it.position(); } if (position <= next_position && next_position - position < distance) { closest_break = it.break_index(); distance = next_position - position; // Check whether we can't get any closer. if (distance == 0) break; } } Iterator it(debug_info, type); it.SkipTo(closest_break); return it.GetBreakLocation(); } void BreakLocation::SetBreakPoint(Handle break_point_object) { // If there is not already a real break point here patch code with debug // break. DCHECK(code()->has_debug_break_slots()); if (!HasBreakPoint()) SetDebugBreak(); DCHECK(IsDebugBreak() || IsDebuggerStatement()); // Set the break point information. DebugInfo::SetBreakPoint(debug_info_, pc_offset_, position_, statement_position_, break_point_object); } void BreakLocation::ClearBreakPoint(Handle break_point_object) { // Clear the break point information. DebugInfo::ClearBreakPoint(debug_info_, pc_offset_, break_point_object); // If there are no more break points here remove the debug break. if (!HasBreakPoint()) { ClearDebugBreak(); DCHECK(!IsDebugBreak()); } } void BreakLocation::SetOneShot() { // Debugger statement always calls debugger. No need to modify it. if (IsDebuggerStatement()) return; // If there is a real break point here no more to do. if (HasBreakPoint()) { DCHECK(IsDebugBreak()); return; } // Patch code with debug break. SetDebugBreak(); } void BreakLocation::ClearOneShot() { // Debugger statement always calls debugger. No need to modify it. if (IsDebuggerStatement()) return; // If there is a real break point here no more to do. if (HasBreakPoint()) { DCHECK(IsDebugBreak()); return; } // Patch code removing debug break. ClearDebugBreak(); DCHECK(!IsDebugBreak()); } void BreakLocation::SetDebugBreak() { // Debugger statement always calls debugger. No need to modify it. if (IsDebuggerStatement()) return; // If there is already a break point here just return. This might happen if // the same code is flooded with break points twice. Flooding the same // function twice might happen when stepping in a function with an exception // handler as the handler and the function is the same. if (IsDebugBreak()) return; if (IsExit()) { // 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(); } DCHECK(IsDebugBreak()); } void BreakLocation::ClearDebugBreak() { // Debugger statement always calls debugger. No need to modify it. if (IsDebuggerStatement()) return; if (IsExit()) { // Restore the frame exit code with a break point. RestoreFromOriginal(Assembler::kJSReturnSequenceLength); } else if (IsDebugBreakSlot()) { // Restore the code in the break slot. RestoreFromOriginal(Assembler::kDebugBreakSlotLength); } else { // Restore the IC call. rinfo().set_target_address(original_rinfo().target_address()); // Some ICs store data in the feedback vector. Clear this to ensure we // won't miss future stepping requirements. SharedFunctionInfo* shared = debug_info_->shared(); shared->feedback_vector()->ClearICSlots(shared); } DCHECK(!IsDebugBreak()); } void BreakLocation::RestoreFromOriginal(int length_in_bytes) { memcpy(pc(), original_pc(), length_in_bytes); CpuFeatures::FlushICache(pc(), length_in_bytes); } bool BreakLocation::IsStepInLocation() const { if (IsConstructCall()) return true; if (RelocInfo::IsCodeTarget(rmode())) { HandleScope scope(debug_info_->GetIsolate()); Handle target_code = CodeTarget(); return target_code->is_call_stub(); } return false; } bool BreakLocation::IsDebugBreak() const { if (IsExit()) { return rinfo().IsPatchedReturnSequence(); } else if (IsDebugBreakSlot()) { return rinfo().IsPatchedDebugBreakSlotSequence(); } else { return Debug::IsDebugBreak(rinfo().target_address()); } } // Find the builtin to use for invoking the debug break static Handle DebugBreakForIC(Handle code, RelocInfo::Mode mode) { Isolate* isolate = code->GetIsolate(); // Find the builtin debug break function matching the calling convention // used by the call site. if (code->is_inline_cache_stub()) { DCHECK(code->kind() == Code::CALL_IC); return isolate->builtins()->CallICStub_DebugBreak(); } if (RelocInfo::IsConstructCall(mode)) { if (code->has_function_cache()) { return isolate->builtins()->CallConstructStub_Recording_DebugBreak(); } else { return isolate->builtins()->CallConstructStub_DebugBreak(); } } if (code->kind() == Code::STUB) { DCHECK(CodeStub::GetMajorKey(*code) == CodeStub::CallFunction); return isolate->builtins()->CallFunctionStub_DebugBreak(); } UNREACHABLE(); return Handle::null(); } void BreakLocation::SetDebugBreakAtIC() { // Patch the original code with the current address as the current address // might have changed by the inline caching since the code was copied. original_rinfo().set_target_address(rinfo().target_address()); if (RelocInfo::IsCodeTarget(rmode_)) { Handle target_code = CodeTarget(); // Patch the code to invoke the builtin debug break function matching the // calling convention used by the call site. Handle debug_break_code = DebugBreakForIC(target_code, rmode_); rinfo().set_target_address(debug_break_code->entry()); } } Handle BreakLocation::BreakPointObjects() const { return debug_info_->GetBreakPointObjects(pc_offset_); } Handle BreakLocation::CodeTarget() const { DCHECK(IsCodeTarget()); Address target = rinfo().target_address(); return Handle(Code::GetCodeFromTargetAddress(target)); } Handle BreakLocation::OriginalCodeTarget() const { DCHECK(IsCodeTarget()); Address target = original_rinfo().target_address(); return Handle(Code::GetCodeFromTargetAddress(target)); } bool BreakLocation::Iterator::RinfoDone() const { DCHECK(reloc_iterator_.done() == reloc_iterator_original_.done()); return reloc_iterator_.done(); } void BreakLocation::Iterator::RinfoNext() { reloc_iterator_.next(); reloc_iterator_original_.next(); #ifdef DEBUG DCHECK(reloc_iterator_.done() == reloc_iterator_original_.done()); DCHECK(reloc_iterator_.done() || rmode() == original_rmode()); #endif } // Threading support. void Debug::ThreadInit() { thread_local_.break_count_ = 0; thread_local_.break_id_ = 0; thread_local_.break_frame_id_ = StackFrame::NO_ID; thread_local_.last_step_action_ = StepNone; thread_local_.last_statement_position_ = RelocInfo::kNoPosition; thread_local_.step_count_ = 0; thread_local_.last_fp_ = 0; thread_local_.queued_step_count_ = 0; thread_local_.step_into_fp_ = 0; thread_local_.step_out_fp_ = 0; // TODO(isolates): frames_are_dropped_? base::NoBarrier_Store(&thread_local_.current_debug_scope_, static_cast(0)); thread_local_.restarter_frame_function_pointer_ = NULL; } char* Debug::ArchiveDebug(char* storage) { char* to = storage; MemCopy(to, reinterpret_cast(&thread_local_), sizeof(ThreadLocal)); ThreadInit(); return storage + ArchiveSpacePerThread(); } char* Debug::RestoreDebug(char* storage) { char* from = storage; MemCopy(reinterpret_cast(&thread_local_), from, sizeof(ThreadLocal)); return storage + ArchiveSpacePerThread(); } int Debug::ArchiveSpacePerThread() { return sizeof(ThreadLocal); } ScriptCache::ScriptCache(Isolate* isolate) : isolate_(isolate) { Heap* heap = isolate_->heap(); HandleScope scope(isolate_); DCHECK(isolate_->debug()->is_active()); // Perform a GC to get rid of all unreferenced scripts. heap->CollectAllGarbage(Heap::kMakeHeapIterableMask, "ScriptCache"); // Scan heap for Script objects. List > scripts; { HeapIterator iterator(heap, HeapIterator::kFilterUnreachable); DisallowHeapAllocation no_allocation; for (HeapObject* obj = iterator.next(); obj != NULL; obj = iterator.next()) { if (obj->IsScript() && Script::cast(obj)->HasValidSource()) { scripts.Add(Handle