// 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/assembler-inl.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/debug-evaluate.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 "src/wasm/wasm-objects.h" #include "include/v8-debug.h" namespace v8 { namespace internal { Debug::Debug(Isolate* isolate) : debug_context_(Handle()), is_active_(false), hook_on_function_call_(false), is_suppressed_(false), live_edit_enabled_(true), // TODO(yangguo): set to false by default. break_disabled_(false), break_points_active_(true), break_on_exception_(false), break_on_uncaught_exception_(false), side_effect_check_failed_(false), debug_info_list_(NULL), feature_tracker_(isolate), isolate_(isolate) { ThreadInit(); } BreakLocation BreakLocation::FromFrame(Handle debug_info, JavaScriptFrame* frame) { auto summary = FrameSummary::GetTop(frame).AsJavaScript(); 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) { auto summary = FrameSummary::GetTop(frame).AsJavaScript(); 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); it.SkipToPosition(position_, BREAK_POSITION_ALIGNED); return it.code_offset() == code_offset_; } else { DCHECK(abstract_code_->IsBytecodeArray()); BytecodeArrayBreakIterator it(debug_info); it.SkipToPosition(position_, BREAK_POSITION_ALIGNED); return it.code_offset() == code_offset_; } } std::unique_ptr BreakIterator::GetIterator( Handle debug_info, Handle abstract_code) { if (abstract_code->IsBytecodeArray()) { DCHECK(debug_info->HasDebugBytecodeArray()); return std::unique_ptr( new BytecodeArrayBreakIterator(debug_info)); } else { DCHECK(abstract_code->IsCode()); DCHECK(debug_info->HasDebugCode()); return std::unique_ptr(new CodeBreakIterator(debug_info)); } } BreakIterator::BreakIterator(Handle debug_info) : debug_info_(debug_info), break_index_(-1) { 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) : BreakIterator(debug_info), reloc_iterator_(debug_info->DebugCode(), GetModeMask()), source_position_iterator_( debug_info->DebugCode()->source_position_table()) { // There is at least one break location. DCHECK(!Done()); Next(); } int CodeBreakIterator::GetModeMask() { int mask = 0; mask |= RelocInfo::ModeMask(RelocInfo::DEBUG_BREAK_SLOT_AT_RETURN); mask |= RelocInfo::ModeMask(RelocInfo::DEBUG_BREAK_SLOT_AT_CALL); mask |= RelocInfo::ModeMask(RelocInfo::DEBUG_BREAK_SLOT_AT_TAIL_CALL); mask |= RelocInfo::ModeMask(RelocInfo::DEBUG_BREAK_SLOT_AT_POSITION); 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())); 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::IsDebugBreakSlot(rmode())) { return DEBUG_BREAK_SLOT; } else { return NOT_DEBUG_BREAK; } } void CodeBreakIterator::SkipToPosition(int position, BreakPositionAlignment alignment) { CodeBreakIterator it(debug_info_); SkipTo(it.BreakIndexFromPosition(position, alignment)); } void CodeBreakIterator::SetDebugBreak() { DebugBreakType debug_break_type = GetDebugBreakType(); 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() { DCHECK(GetDebugBreakType() >= DEBUG_BREAK_SLOT); DebugCodegen::ClearDebugBreakSlot(isolate(), rinfo()->pc()); } bool CodeBreakIterator::IsDebugBreak() { DCHECK(GetDebugBreakType() >= 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) : BreakIterator(debug_info), 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) 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::IsCallOrConstruct(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_); 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_frame_count_ = -1; thread_local_.target_frame_count_ = -1; thread_local_.return_value_ = Smi::kZero; thread_local_.async_task_count_ = 0; clear_suspended_generator(); thread_local_.restart_fp_ = nullptr; base::NoBarrier_Store(&thread_local_.current_debug_scope_, static_cast(0)); UpdateHookOnFunctionCall(); } 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_.return_value_); 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); 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, v8::DeserializeInternalFieldsCallback(), 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(); RemoveDebugDelegate(); // 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) { // 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_); DisableBreak no_recursive_break(this); // Return if we fail to retrieve debug info. Handle function(frame->function()); Handle shared(function->shared()); if (!EnsureDebugInfo(shared)) 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. MaybeHandle break_points_hit = CheckBreakPoints(debug_info, &location); if (!break_points_hit.is_null()) { // Clear all current stepping setup. ClearStepping(); // Notify the debug event listeners. Handle jsarr = isolate_->factory()->NewJSArrayWithElements( break_points_hit.ToHandleChecked()); OnDebugBreak(jsarr); return; } // No break point. Check for stepping. StepAction step_action = last_step_action(); int current_frame_count = CurrentFrameCount(); int target_frame_count = thread_local_.target_frame_count_; int last_frame_count = thread_local_.last_frame_count_; bool step_break = false; switch (step_action) { case StepNone: return; case StepOut: // Step out should not break in a deeper frame than target frame. if (current_frame_count > target_frame_count) return; step_break = true; break; case StepNext: // Step next should not break in a deeper frame than target frame. if (current_frame_count > target_frame_count) 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::GetTop(frame); step_break = step_break || location.IsReturn() || current_frame_count != last_frame_count || thread_local_.last_statement_position_ != summary.SourceStatementPosition(); break; } } // Clear all current stepping setup. ClearStepping(); if (step_break) { // Notify the debug event listeners. OnDebugBreak(isolate_->factory()->undefined_value()); } 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, or an empty // handle if none evaluated true. MaybeHandle Debug::CheckBreakPoints(Handle debug_info, BreakLocation* location, bool* has_break_points) { 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 {}; Handle break_point_objects = debug_info->GetBreakPointObjects(location->position()); return Debug::GetHitBreakPointObjects(break_point_objects); } bool Debug::IsMutedAtCurrentLocation(JavaScriptFrame* frame) { HandleScope scope(isolate_); // 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. FrameSummary summary = FrameSummary::GetTop(frame); DCHECK(!summary.IsWasm()); Handle function = summary.AsJavaScript().function(); if (!function->shared()->HasDebugInfo()) return false; 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; MaybeHandle 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.is_null()) 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(); MaybeHandle maybe_exception; return Execution::TryCall(isolate_, fun, undefined, argc, args, Execution::MessageHandling::kReport, &maybe_exception); } // 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)) 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