// 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 #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-dispatcher/optimizing-compile-dispatcher.h" #include "src/compiler.h" #include "src/debug/liveedit.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/globals.h" #include "src/interpreter/interpreter.h" #include "src/isolate-inl.h" #include "src/list.h" #include "src/log.h" #include "src/messages.h" #include "src/snapshot/natives.h" #include "src/wasm/wasm-module.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(); } BreakLocation BreakLocation::FromFrame(Handle debug_info, JavaScriptFrame* frame) { FrameSummary summary = FrameSummary::GetFirst(frame); int offset = summary.code_offset(); Handle abstract_code = summary.abstract_code(); if (abstract_code->IsCode()) offset = offset - 1; auto it = BreakIterator::GetIterator(debug_info, abstract_code); it->SkipTo(BreakIndexFromCodeOffset(debug_info, abstract_code, offset)); return it->GetBreakLocation(); } void BreakLocation::AllAtCurrentStatement(Handle debug_info, JavaScriptFrame* frame, List* result_out) { FrameSummary summary = FrameSummary::GetFirst(frame); int offset = summary.code_offset(); Handle abstract_code = summary.abstract_code(); if (abstract_code->IsCode()) offset = offset - 1; int statement_position; { auto it = BreakIterator::GetIterator(debug_info, abstract_code); it->SkipTo(BreakIndexFromCodeOffset(debug_info, abstract_code, offset)); statement_position = it->statement_position(); } for (auto it = BreakIterator::GetIterator(debug_info, abstract_code); !it->Done(); it->Next()) { if (it->statement_position() == statement_position) { result_out->Add(it->GetBreakLocation()); } } } int BreakLocation::BreakIndexFromCodeOffset(Handle debug_info, Handle abstract_code, int offset) { // Run through all break points to locate the one closest to the address. int closest_break = 0; int distance = kMaxInt; DCHECK(0 <= offset && offset < abstract_code->Size()); for (auto it = BreakIterator::GetIterator(debug_info, abstract_code); !it->Done(); it->Next()) { // Check if this break point is closer that what was previously found. if (it->code_offset() <= offset && offset - it->code_offset() < distance) { closest_break = it->break_index(); distance = offset - it->code_offset(); // Check whether we can't get any closer. if (distance == 0) break; } } return closest_break; } bool BreakLocation::HasBreakPoint(Handle debug_info) const { // First check whether there is a break point with the same source position. if (!debug_info->HasBreakPoint(position_)) return false; // Then check whether a break point at that source position would have // the same code offset. Otherwise it's just a break location that we can // step to, but not actually a location where we can put a break point. if (abstract_code_->IsCode()) { DCHECK_EQ(debug_info->DebugCode(), abstract_code_->GetCode()); CodeBreakIterator it(debug_info, ALL_BREAK_LOCATIONS); it.SkipToPosition(position_, BREAK_POSITION_ALIGNED); return it.code_offset() == code_offset_; } else { DCHECK(abstract_code_->IsBytecodeArray()); BytecodeArrayBreakIterator it(debug_info, ALL_BREAK_LOCATIONS); it.SkipToPosition(position_, BREAK_POSITION_ALIGNED); return it.code_offset() == code_offset_; } } std::unique_ptr BreakIterator::GetIterator( Handle debug_info, Handle abstract_code, BreakLocatorType type) { if (abstract_code->IsBytecodeArray()) { DCHECK(debug_info->HasDebugBytecodeArray()); return std::unique_ptr( new BytecodeArrayBreakIterator(debug_info, type)); } else { DCHECK(abstract_code->IsCode()); DCHECK(debug_info->HasDebugCode()); return std::unique_ptr( new CodeBreakIterator(debug_info, type)); } } BreakIterator::BreakIterator(Handle debug_info, BreakLocatorType type) : debug_info_(debug_info), break_index_(-1), break_locator_type_(type) { position_ = debug_info->shared()->start_position(); statement_position_ = position_; } int BreakIterator::BreakIndexFromPosition(int source_position, BreakPositionAlignment alignment) { int distance = kMaxInt; int closest_break = break_index(); while (!Done()) { int next_position; if (alignment == STATEMENT_ALIGNED) { next_position = statement_position(); } else { DCHECK(alignment == BREAK_POSITION_ALIGNED); next_position = position(); } if (source_position <= next_position && next_position - source_position < distance) { closest_break = break_index(); distance = next_position - source_position; // Check whether we can't get any closer. if (distance == 0) break; } Next(); } return closest_break; } CodeBreakIterator::CodeBreakIterator(Handle debug_info, BreakLocatorType type) : BreakIterator(debug_info, type), reloc_iterator_(debug_info->DebugCode(), GetModeMask(type)), source_position_iterator_( debug_info->DebugCode()->source_position_table()) { // There is at least one break location. DCHECK(!Done()); Next(); } int CodeBreakIterator::GetModeMask(BreakLocatorType type) { int mask = 0; mask |= RelocInfo::ModeMask(RelocInfo::DEBUG_BREAK_SLOT_AT_RETURN); mask |= RelocInfo::ModeMask(RelocInfo::DEBUG_BREAK_SLOT_AT_CALL); if (isolate()->is_tail_call_elimination_enabled()) { mask |= RelocInfo::ModeMask(RelocInfo::DEBUG_BREAK_SLOT_AT_TAIL_CALL); } if (type == ALL_BREAK_LOCATIONS) { mask |= RelocInfo::ModeMask(RelocInfo::DEBUG_BREAK_SLOT_AT_POSITION); mask |= RelocInfo::ModeMask(RelocInfo::DEBUGGER_STATEMENT); } return mask; } void CodeBreakIterator::Next() { DisallowHeapAllocation no_gc; DCHECK(!Done()); // Iterate through reloc info stopping at each breakable code target. bool first = break_index_ == -1; if (!first) reloc_iterator_.next(); first = false; if (Done()) return; int offset = code_offset(); while (!source_position_iterator_.done() && source_position_iterator_.code_offset() <= offset) { position_ = source_position_iterator_.source_position().ScriptOffset(); if (source_position_iterator_.is_statement()) { statement_position_ = position_; } source_position_iterator_.Advance(); } DCHECK(RelocInfo::IsDebugBreakSlot(rmode()) || RelocInfo::IsDebuggerStatement(rmode())); break_index_++; } DebugBreakType CodeBreakIterator::GetDebugBreakType() { if (RelocInfo::IsDebugBreakSlotAtReturn(rmode())) { return DEBUG_BREAK_SLOT_AT_RETURN; } else if (RelocInfo::IsDebugBreakSlotAtCall(rmode())) { return DEBUG_BREAK_SLOT_AT_CALL; } else if (RelocInfo::IsDebugBreakSlotAtTailCall(rmode())) { return isolate()->is_tail_call_elimination_enabled() ? DEBUG_BREAK_SLOT_AT_TAIL_CALL : DEBUG_BREAK_SLOT_AT_CALL; } else if (RelocInfo::IsDebuggerStatement(rmode())) { return DEBUGGER_STATEMENT; } else if (RelocInfo::IsDebugBreakSlot(rmode())) { return DEBUG_BREAK_SLOT; } else { return NOT_DEBUG_BREAK; } } void CodeBreakIterator::SkipToPosition(int position, BreakPositionAlignment alignment) { CodeBreakIterator it(debug_info_, break_locator_type_); SkipTo(it.BreakIndexFromPosition(position, alignment)); } void CodeBreakIterator::SetDebugBreak() { DebugBreakType debug_break_type = GetDebugBreakType(); if (debug_break_type == DEBUGGER_STATEMENT) return; DCHECK(debug_break_type >= DEBUG_BREAK_SLOT); Builtins* builtins = isolate()->builtins(); Handle target = debug_break_type == DEBUG_BREAK_SLOT_AT_RETURN ? builtins->Return_DebugBreak() : builtins->Slot_DebugBreak(); DebugCodegen::PatchDebugBreakSlot(isolate(), rinfo()->pc(), target); } void CodeBreakIterator::ClearDebugBreak() { DebugBreakType debug_break_type = GetDebugBreakType(); if (debug_break_type == DEBUGGER_STATEMENT) return; DCHECK(debug_break_type >= DEBUG_BREAK_SLOT); DebugCodegen::ClearDebugBreakSlot(isolate(), rinfo()->pc()); } bool CodeBreakIterator::IsDebugBreak() { DebugBreakType debug_break_type = GetDebugBreakType(); if (debug_break_type == DEBUGGER_STATEMENT) return false; DCHECK(debug_break_type >= DEBUG_BREAK_SLOT); return DebugCodegen::DebugBreakSlotIsPatched(rinfo()->pc()); } BreakLocation CodeBreakIterator::GetBreakLocation() { Handle code(AbstractCode::cast(debug_info_->DebugCode())); return BreakLocation(code, GetDebugBreakType(), code_offset(), position_); } BytecodeArrayBreakIterator::BytecodeArrayBreakIterator( Handle debug_info, BreakLocatorType type) : BreakIterator(debug_info, type), source_position_iterator_( debug_info->DebugBytecodeArray()->source_position_table()) { // There is at least one break location. DCHECK(!Done()); Next(); } void BytecodeArrayBreakIterator::Next() { DisallowHeapAllocation no_gc; DCHECK(!Done()); bool first = break_index_ == -1; while (!Done()) { if (!first) source_position_iterator_.Advance(); first = false; if (Done()) return; position_ = source_position_iterator_.source_position().ScriptOffset(); if (source_position_iterator_.is_statement()) { statement_position_ = position_; } DCHECK(position_ >= 0); DCHECK(statement_position_ >= 0); DebugBreakType type = GetDebugBreakType(); if (type == NOT_DEBUG_BREAK) continue; if (break_locator_type_ == ALL_BREAK_LOCATIONS) break; DCHECK_EQ(CALLS_AND_RETURNS, break_locator_type_); if (type == DEBUG_BREAK_SLOT_AT_CALL) break; if (type == DEBUG_BREAK_SLOT_AT_RETURN) break; } break_index_++; } DebugBreakType BytecodeArrayBreakIterator::GetDebugBreakType() { BytecodeArray* bytecode_array = debug_info_->OriginalBytecodeArray(); interpreter::Bytecode bytecode = interpreter::Bytecodes::FromByte(bytecode_array->get(code_offset())); if (bytecode == interpreter::Bytecode::kDebugger) { return DEBUGGER_STATEMENT; } else if (bytecode == interpreter::Bytecode::kReturn) { return DEBUG_BREAK_SLOT_AT_RETURN; } else if (bytecode == interpreter::Bytecode::kTailCall) { return isolate()->is_tail_call_elimination_enabled() ? DEBUG_BREAK_SLOT_AT_TAIL_CALL : DEBUG_BREAK_SLOT_AT_CALL; } else if (interpreter::Bytecodes::IsCallOrNew(bytecode)) { return DEBUG_BREAK_SLOT_AT_CALL; } else if (source_position_iterator_.is_statement()) { return DEBUG_BREAK_SLOT; } else { return NOT_DEBUG_BREAK; } } void BytecodeArrayBreakIterator::SkipToPosition( int position, BreakPositionAlignment alignment) { BytecodeArrayBreakIterator it(debug_info_, break_locator_type_); SkipTo(it.BreakIndexFromPosition(position, alignment)); } void BytecodeArrayBreakIterator::SetDebugBreak() { DebugBreakType debug_break_type = GetDebugBreakType(); if (debug_break_type == DEBUGGER_STATEMENT) return; DCHECK(debug_break_type >= DEBUG_BREAK_SLOT); BytecodeArray* bytecode_array = debug_info_->DebugBytecodeArray(); interpreter::Bytecode bytecode = interpreter::Bytecodes::FromByte(bytecode_array->get(code_offset())); if (interpreter::Bytecodes::IsDebugBreak(bytecode)) return; interpreter::Bytecode debugbreak = interpreter::Bytecodes::GetDebugBreak(bytecode); bytecode_array->set(code_offset(), interpreter::Bytecodes::ToByte(debugbreak)); } void BytecodeArrayBreakIterator::ClearDebugBreak() { DebugBreakType debug_break_type = GetDebugBreakType(); if (debug_break_type == DEBUGGER_STATEMENT) return; DCHECK(debug_break_type >= DEBUG_BREAK_SLOT); BytecodeArray* bytecode_array = debug_info_->DebugBytecodeArray(); BytecodeArray* original = debug_info_->OriginalBytecodeArray(); bytecode_array->set(code_offset(), original->get(code_offset())); } bool BytecodeArrayBreakIterator::IsDebugBreak() { DebugBreakType debug_break_type = GetDebugBreakType(); if (debug_break_type == DEBUGGER_STATEMENT) return false; DCHECK(debug_break_type >= DEBUG_BREAK_SLOT); BytecodeArray* bytecode_array = debug_info_->DebugBytecodeArray(); interpreter::Bytecode bytecode = interpreter::Bytecodes::FromByte(bytecode_array->get(code_offset())); return interpreter::Bytecodes::IsDebugBreak(bytecode); } BreakLocation BytecodeArrayBreakIterator::GetBreakLocation() { Handle code( AbstractCode::cast(debug_info_->DebugBytecodeArray())); return BreakLocation(code, GetDebugBreakType(), code_offset(), position_); } 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_ = kNoSourcePosition; thread_local_.last_fp_ = 0; thread_local_.target_fp_ = 0; thread_local_.return_value_ = Handle(); clear_suspended_generator(); // TODO(isolates): frames_are_dropped_? base::NoBarrier_Store(&thread_local_.current_debug_scope_, static_cast(0)); } char* Debug::ArchiveDebug(char* storage) { // Simply reset state. Don't archive anything. ThreadInit(); return storage + ArchiveSpacePerThread(); } char* Debug::RestoreDebug(char* storage) { // Simply reset state. Don't restore anything. ThreadInit(); return storage + ArchiveSpacePerThread(); } int Debug::ArchiveSpacePerThread() { return 0; } void Debug::Iterate(ObjectVisitor* v) { v->VisitPointer(&thread_local_.suspended_generator_); } 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; // TODO(yangguo): we rely on the fact that first context snapshot is usable // as debug context. This dependency is gone once we remove // debug context completely. static const int kFirstContextSnapshotIndex = 0; Handle context = isolate_->bootstrapper()->CreateEnvironment( MaybeHandle(), v8::Local(), &no_extensions, kFirstContextSnapshotIndex, 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(JavaScriptFrame* frame) { HandleScope scope(isolate_); // 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(), isolate_); // Find the break location where execution has stopped. BreakLocation location = BreakLocation::FromFrame(debug_info, frame); // Find actual break points, if any, and trigger debug break event. Handle break_points_hit = CheckBreakPoints(debug_info, &location); if (!break_points_hit->IsUndefined(isolate_)) { // 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 = false; switch (step_action) { case StepNone: return; case StepOut: // Step out has not reached the target frame yet. if (current_fp < target_fp) return; step_break = true; break; case StepNext: // Step next should not break in a deeper frame. if (current_fp < target_fp) return; // For step-next, a tail call is like a return and should break. step_break = location.IsTailCall(); // Fall through. case StepIn: { FrameSummary summary = FrameSummary::GetFirst(frame); int offset = summary.code_offset(); step_break = step_break || location.IsReturn() || (current_fp != last_fp) || (thread_local_.last_statement_position_ != summary.abstract_code()->SourceStatementPosition(offset)); 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); } } // Find break point objects for this location, if any, and evaluate them. // Return an array of break point objects that evaluated true. Handle Debug::CheckBreakPoints(Handle debug_info, BreakLocation* location, bool* has_break_points) { Factory* factory = isolate_->factory(); bool has_break_points_to_check = break_points_active_ && location->HasBreakPoint(debug_info); if (has_break_points) *has_break_points = has_break_points_to_check; if (!has_break_points_to_check) return factory->undefined_value(); Handle break_point_objects = debug_info->GetBreakPointObjects(location->position()); // 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(isolate_)); 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 break_point_object(array->get(i), isolate_); if (CheckBreakPoint(break_point_object)) { break_points_hit->set(break_points_hit_count++, *break_point_object); } } } else { break_points_hit = factory->NewFixedArray(1); if (CheckBreakPoint(break_point_objects)) { break_points_hit->set(break_points_hit_count++, *break_point_objects); } } if (break_points_hit_count == 0) return factory->undefined_value(); Handle result = factory->NewJSArrayWithElements(break_points_hit); result->set_length(Smi::FromInt(break_points_hit_count)); return result; } bool Debug::IsMutedAtCurrentLocation(JavaScriptFrame* frame) { // A break location is considered muted if break locations on the current // statement have at least one break point, and all of these break points // evaluate to false. Aside from not triggering a debug break event at the // break location, we also do not trigger one for debugger statements, nor // an exception event on exception at this location. Object* fun = frame->function(); if (!fun->IsJSFunction()) return false; JSFunction* function = JSFunction::cast(fun); if (!function->shared()->HasDebugInfo()) return false; HandleScope scope(isolate_); Handle debug_info(function->shared()->GetDebugInfo()); // Enter the debugger. DebugScope debug_scope(this); if (debug_scope.failed()) return false; List break_locations; BreakLocation::AllAtCurrentStatement(debug_info, frame, &break_locations); bool has_break_points_at_all = false; for (int i = 0; i < break_locations.length(); i++) { bool has_break_points; Handle check_result = CheckBreakPoints(debug_info, &break_locations[i], &has_break_points); has_break_points_at_all |= has_break_points; if (has_break_points && !check_result->IsUndefined(isolate_)) return false; } return has_break_points_at_all; } MaybeHandle Debug::CallFunction(const char* name, int argc, Handle args[]) { PostponeInterruptsScope no_interrupts(isolate_); AssertDebugContext(); Handle holder = Handle::cast(isolate_->natives_utils_object()); Handle fun = Handle::cast( JSReceiver::GetProperty(isolate_, holder, name).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(isolate_); } 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. *source_position = FindBreakablePosition(debug_info, *source_position, STATEMENT_ALIGNED); DebugInfo::SetBreakPoint(debug_info, *source_position, break_point_object); // At least one active break point now. DCHECK(debug_info->GetBreakPointCount() > 0); ClearBreakPoints(debug_info); ApplyBreakPoints(debug_info); feature_tracker()->Track(DebugFeatureTracker::kBreakPoint); return true; } bool Debug::SetBreakPointForScript(Handle