// 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/debug/debug.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/deoptimizer.h" #include "src/execution.h" #include "src/frames-inl.h" #include "src/full-codegen/full-codegen.h" #include "src/global-handles.h" #include "src/isolate-inl.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. break_disabled_(false), break_points_active_(true), in_debug_event_listener_(false), break_on_exception_(false), break_on_uncaught_exception_(false), debug_info_list_(NULL), feature_tracker_(isolate), 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, int position, int statement_position) : debug_info_(debug_info), pc_offset_(static_cast(rinfo->pc() - debug_info->code()->entry())), rmode_(rinfo->rmode()), data_(rinfo->data()), position_(position), statement_position_(statement_position) {} BreakLocation::Iterator::Iterator(Handle debug_info, BreakLocatorType type) : debug_info_(debug_info), reloc_iterator_(debug_info->code(), GetModeMask(type)), break_index_(-1), position_(1), statement_position_(1) { if (!Done()) Next(); } int BreakLocation::Iterator::GetModeMask(BreakLocatorType type) { int mask = 0; mask |= RelocInfo::ModeMask(RelocInfo::POSITION); mask |= RelocInfo::ModeMask(RelocInfo::STATEMENT_POSITION); mask |= RelocInfo::ModeMask(RelocInfo::DEBUG_BREAK_SLOT_AT_RETURN); mask |= RelocInfo::ModeMask(RelocInfo::DEBUG_BREAK_SLOT_AT_CALL); if (type == ALL_BREAK_LOCATIONS) { mask |= RelocInfo::ModeMask(RelocInfo::DEBUG_BREAK_SLOT_AT_POSITION); mask |= RelocInfo::ModeMask(RelocInfo::DEBUGGER_STATEMENT); } return mask; } void BreakLocation::Iterator::Next() { DisallowHeapAllocation no_gc; DCHECK(!Done()); // Iterate through reloc info for code and original code stopping at each // breakable code target. bool first = break_index_ == -1; while (!Done()) { if (!first) reloc_iterator_.next(); first = false; if (Done()) 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; } DCHECK(RelocInfo::IsDebugBreakSlot(rmode()) || RelocInfo::IsDebuggerStatement(rmode())); if (RelocInfo::IsDebugBreakSlotAtReturn(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; } break_index_++; } // Find the break point at the supplied address, or the closest one before // the address. BreakLocation BreakLocation::FromAddress(Handle debug_info, Address pc) { Iterator it(debug_info, ALL_BREAK_LOCATIONS); it.SkipTo(BreakIndexFromAddress(debug_info, 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, Address pc, List* result_out) { int break_index = BreakIndexFromAddress(debug_info, pc); Iterator it(debug_info, ALL_BREAK_LOCATIONS); 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, 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, ALL_BREAK_LOCATIONS); !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, 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, ALL_BREAK_LOCATIONS); !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, ALL_BREAK_LOCATIONS); 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. 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; DCHECK(IsDebugBreakSlot()); Isolate* isolate = debug_info_->GetIsolate(); Builtins* builtins = isolate->builtins(); Handle target = IsReturn() ? builtins->Return_DebugBreak() : builtins->Slot_DebugBreak(); DebugCodegen::PatchDebugBreakSlot(isolate, pc(), target); DCHECK(IsDebugBreak()); } void BreakLocation::ClearDebugBreak() { // Debugger statement always calls debugger. No need to modify it. if (IsDebuggerStatement()) return; DCHECK(IsDebugBreakSlot()); DebugCodegen::ClearDebugBreakSlot(debug_info_->GetIsolate(), pc()); DCHECK(!IsDebugBreak()); } bool BreakLocation::IsDebugBreak() const { if (IsDebuggerStatement()) return false; DCHECK(IsDebugBreakSlot()); return rinfo().IsPatchedDebugBreakSlotSequence(); } Handle BreakLocation::BreakPointObjects() const { return debug_info_->GetBreakPointObjects(pc_offset_); } void DebugFeatureTracker::Track(DebugFeatureTracker::Feature feature) { uint32_t mask = 1 << feature; // Only count one sample per feature and isolate. if (bitfield_ & mask) return; isolate_->counters()->debug_feature_usage()->AddSample(feature); bitfield_ |= mask; } // 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_.last_fp_ = 0; thread_local_.target_fp_ = 0; thread_local_.step_in_enabled_ = false; // TODO(isolates): frames_are_dropped_? base::NoBarrier_Store(&thread_local_.current_debug_scope_, static_cast(0)); } 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); } DebugInfoListNode::DebugInfoListNode(DebugInfo* debug_info): next_(NULL) { // Globalize the request debug info object and make it weak. GlobalHandles* global_handles = debug_info->GetIsolate()->global_handles(); debug_info_ = Handle::cast(global_handles->Create(debug_info)).location(); } DebugInfoListNode::~DebugInfoListNode() { if (debug_info_ == nullptr) return; GlobalHandles::Destroy(reinterpret_cast(debug_info_)); debug_info_ = nullptr; } bool Debug::Load() { // Return if debugger is already loaded. if (is_loaded()) return true; // Bail out if we're already in the process of compiling the native // JavaScript source code for the debugger. if (is_suppressed_) return false; SuppressDebug while_loading(this); // Disable breakpoints and interrupts while compiling and running the // debugger scripts including the context creation code. DisableBreak disable(this, true); PostponeInterruptsScope postpone(isolate_); // Create the debugger context. HandleScope scope(isolate_); ExtensionConfiguration no_extensions; Handle context = isolate_->bootstrapper()->CreateEnvironment( MaybeHandle(), v8::Local(), &no_extensions, DEBUG_CONTEXT); // Fail if no context could be created. if (context.is_null()) return false; debug_context_ = Handle::cast( isolate_->global_handles()->Create(*context)); feature_tracker()->Track(DebugFeatureTracker::kActive); return true; } void Debug::Unload() { ClearAllBreakPoints(); ClearStepping(); // Return debugger is not loaded. if (!is_loaded()) return; // Clear debugger context global handle. GlobalHandles::Destroy(Handle::cast(debug_context_).location()); debug_context_ = Handle(); } void Debug::Break(Arguments args, JavaScriptFrame* frame) { HandleScope scope(isolate_); DCHECK(args.length() == 0); // Initialize LiveEdit. LiveEdit::InitializeThreadLocal(this); // Just continue if breaks are disabled or debugger cannot be loaded. if (break_disabled()) return; // Enter the debugger. DebugScope debug_scope(this); if (debug_scope.failed()) return; // Postpone interrupt during breakpoint processing. PostponeInterruptsScope postpone(isolate_); // Get the debug info (create it if it does not exist). Handle function(frame->function()); Handle shared(function->shared()); if (!EnsureDebugInfo(shared, function)) { // Return if we failed to retrieve the debug info. return; } Handle debug_info(shared->GetDebugInfo()); // Find the break location where execution has stopped. // PC points to the instruction after the current one, possibly a break // location as well. So the "- 1" to exclude it from the search. Address call_pc = frame->pc() - 1; BreakLocation location = BreakLocation::FromAddress(debug_info, call_pc); // Find actual break points, if any, and trigger debug break event. if (break_points_active_ && location.HasBreakPoint()) { Handle break_point_objects = location.BreakPointObjects(); Handle break_points_hit = CheckBreakPoints(break_point_objects); if (!break_points_hit->IsUndefined()) { // Clear all current stepping setup. ClearStepping(); // Notify the debug event listeners. OnDebugBreak(break_points_hit, false); return; } } // No break point. Check for stepping. StepAction step_action = last_step_action(); Address current_fp = frame->UnpaddedFP(); Address target_fp = thread_local_.target_fp_; Address last_fp = thread_local_.last_fp_; bool step_break = true; switch (step_action) { case StepNone: return; case StepOut: // Step out has not reached the target frame yet. if (current_fp < target_fp) return; break; case StepNext: // Step next should not break in a deeper frame. if (current_fp < target_fp) return; // Fall through. case StepIn: step_break = location.IsReturn() || (current_fp != last_fp) || (thread_local_.last_statement_position_ != location.code()->SourceStatementPosition(frame->pc())); break; case StepFrame: step_break = current_fp != last_fp; break; } // Clear all current stepping setup. ClearStepping(); if (step_break) { // Notify the debug event listeners. OnDebugBreak(isolate_->factory()->undefined_value(), false); } else { // Re-prepare to continue. PrepareStep(step_action); } } // Check the break point objects for whether one or more are actually // triggered. This function returns a JSArray with the break point objects // which is triggered. Handle Debug::CheckBreakPoints(Handle break_point_objects) { Factory* factory = isolate_->factory(); // Count the number of break points hit. If there are multiple break points // they are in a FixedArray. Handle break_points_hit; int break_points_hit_count = 0; DCHECK(!break_point_objects->IsUndefined()); if (break_point_objects->IsFixedArray()) { Handle array(FixedArray::cast(*break_point_objects)); break_points_hit = factory->NewFixedArray(array->length()); for (int i = 0; i < array->length(); i++) { Handle o(array->get(i), isolate_); if (CheckBreakPoint(o)) { break_points_hit->set(break_points_hit_count++, *o); } } } else { break_points_hit = factory->NewFixedArray(1); if (CheckBreakPoint(break_point_objects)) { break_points_hit->set(break_points_hit_count++, *break_point_objects); } } // Return undefined if no break points were triggered. if (break_points_hit_count == 0) { return factory->undefined_value(); } // Return break points hit as a JSArray. Handle result = factory->NewJSArrayWithElements(break_points_hit); result->set_length(Smi::FromInt(break_points_hit_count)); return result; } MaybeHandle Debug::CallFunction(const char* name, int argc, Handle args[]) { PostponeInterruptsScope no_interrupts(isolate_); AssertDebugContext(); Handle holder = isolate_->natives_utils_object(); Handle fun = Handle::cast( Object::GetProperty(isolate_, holder, name, STRICT).ToHandleChecked()); Handle undefined = isolate_->factory()->undefined_value(); return Execution::TryCall(isolate_, fun, undefined, argc, args); } // Check whether a single break point object is triggered. bool Debug::CheckBreakPoint(Handle break_point_object) { Factory* factory = isolate_->factory(); HandleScope scope(isolate_); // Ignore check if break point object is not a JSObject. if (!break_point_object->IsJSObject()) return true; // Get the break id as an object. Handle break_id = factory->NewNumberFromInt(Debug::break_id()); // Call IsBreakPointTriggered. Handle argv[] = { break_id, break_point_object }; Handle result; if (!CallFunction("IsBreakPointTriggered", arraysize(argv), argv) .ToHandle(&result)) { return false; } // Return whether the break point is triggered. return result->IsTrue(); } bool Debug::SetBreakPoint(Handle function, Handle break_point_object, int* source_position) { HandleScope scope(isolate_); // Make sure the function is compiled and has set up the debug info. Handle shared(function->shared()); if (!EnsureDebugInfo(shared, function)) { // Return if retrieving debug info failed. return true; } Handle debug_info(shared->GetDebugInfo()); // Source positions starts with zero. DCHECK(*source_position >= 0); // Find the break point and change it. BreakLocation location = BreakLocation::FromPosition( debug_info, *source_position, STATEMENT_ALIGNED); *source_position = location.statement_position(); location.SetBreakPoint(break_point_object); feature_tracker()->Track(DebugFeatureTracker::kBreakPoint); // At least one active break point now. return debug_info->GetBreakPointCount() > 0; } bool Debug::SetBreakPointForScript(Handle