// 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 #include "src/api-inl.h" #include "src/arguments.h" #include "src/assembler-inl.h" #include "src/base/platform/mutex.h" #include "src/bootstrapper.h" #include "src/builtins/builtins.h" #include "src/code-stubs.h" #include "src/compilation-cache.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/global-handles.h" #include "src/globals.h" #include "src/interpreter/bytecode-array-accessor.h" #include "src/interpreter/bytecode-array-iterator.h" #include "src/interpreter/interpreter.h" #include "src/isolate-inl.h" #include "src/log.h" #include "src/messages.h" #include "src/objects/api-callbacks-inl.h" #include "src/objects/debug-objects-inl.h" #include "src/objects/js-generator-inl.h" #include "src/objects/js-promise-inl.h" #include "src/snapshot/natives.h" #include "src/snapshot/snapshot.h" #include "src/wasm/wasm-objects-inl.h" namespace v8 { namespace internal { class Debug::TemporaryObjectsTracker : public HeapObjectAllocationTracker { public: TemporaryObjectsTracker() = default; ~TemporaryObjectsTracker() override = default; void AllocationEvent(Address addr, int) override { objects_.insert(addr); } void MoveEvent(Address from, Address to, int) override { if (from == to) return; base::LockGuard guard(&mutex_); auto it = objects_.find(from); if (it == objects_.end()) { // If temporary object was collected we can get MoveEvent which moves // existing non temporary object to the address where we had temporary // object. So we should mark new address as non temporary. objects_.erase(to); return; } objects_.erase(it); objects_.insert(to); } bool HasObject(Handle obj) const { if (obj->IsJSObject() && Handle::cast(obj)->GetEmbedderFieldCount()) { // Embedder may store any pointers using embedder fields and implements // non trivial logic, e.g. create wrappers lazily and store pointer to // native object inside embedder field. We should consider all objects // with embedder fields as non temporary. return false; } return objects_.find(obj->address()) != objects_.end(); } private: std::unordered_set
objects_; base::Mutex mutex_; DISALLOW_COPY_AND_ASSIGN(TemporaryObjectsTracker); }; Debug::Debug(Isolate* isolate) : is_active_(false), hook_on_function_call_(false), is_suppressed_(false), break_disabled_(false), break_points_active_(true), break_on_exception_(false), break_on_uncaught_exception_(false), side_effect_check_failed_(false), debug_info_list_(nullptr), feature_tracker_(isolate), isolate_(isolate) { ThreadInit(); } Debug::~Debug() { DCHECK_NULL(debug_delegate_); } BreakLocation BreakLocation::FromFrame(Handle debug_info, JavaScriptFrame* frame) { if (debug_info->CanBreakAtEntry()) { return BreakLocation(Debug::kBreakAtEntryPosition, DEBUG_BREAK_AT_ENTRY); } auto summary = FrameSummary::GetTop(frame).AsJavaScript(); int offset = summary.code_offset(); Handle abstract_code = summary.abstract_code(); BreakIterator it(debug_info); it.SkipTo(BreakIndexFromCodeOffset(debug_info, abstract_code, offset)); return it.GetBreakLocation(); } void BreakLocation::AllAtCurrentStatement( Handle debug_info, JavaScriptFrame* frame, std::vector* result_out) { DCHECK(!debug_info->CanBreakAtEntry()); 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; { BreakIterator it(debug_info); it.SkipTo(BreakIndexFromCodeOffset(debug_info, abstract_code, offset)); statement_position = it.statement_position(); } for (BreakIterator it(debug_info); !it.Done(); it.Next()) { if (it.statement_position() == statement_position) { result_out->push_back(it.GetBreakLocation()); } } } JSGeneratorObject* BreakLocation::GetGeneratorObjectForSuspendedFrame( JavaScriptFrame* frame) const { DCHECK(IsSuspend()); DCHECK_GE(generator_obj_reg_index_, 0); Object* generator_obj = InterpretedFrame::cast(frame)->ReadInterpreterRegister( generator_obj_reg_index_); return JSGeneratorObject::cast(generator_obj); } 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 (BreakIterator it(debug_info); !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(Isolate* isolate, Handle debug_info) const { // First check whether there is a break point with the same source position. if (!debug_info->HasBreakPoint(isolate, position_)) return false; if (debug_info->CanBreakAtEntry()) { DCHECK_EQ(Debug::kBreakAtEntryPosition, position_); return debug_info->BreakAtEntry(); } else { // 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. DCHECK(abstract_code_->IsBytecodeArray()); BreakIterator it(debug_info); it.SkipToPosition(position_); return it.code_offset() == code_offset_; } } debug::BreakLocationType BreakLocation::type() const { switch (type_) { case DEBUGGER_STATEMENT: return debug::kDebuggerStatementBreakLocation; case DEBUG_BREAK_SLOT_AT_CALL: return debug::kCallBreakLocation; case DEBUG_BREAK_SLOT_AT_RETURN: return debug::kReturnBreakLocation; // Externally, suspend breaks should look like normal breaks. case DEBUG_BREAK_SLOT_AT_SUSPEND: default: return debug::kCommonBreakLocation; } } BreakIterator::BreakIterator(Handle debug_info) : debug_info_(debug_info), break_index_(-1), source_position_iterator_( debug_info->DebugBytecodeArray()->SourcePositionTable()) { position_ = debug_info->shared()->StartPosition(); statement_position_ = position_; // There is at least one break location. DCHECK(!Done()); Next(); } int BreakIterator::BreakIndexFromPosition(int source_position) { int distance = kMaxInt; int closest_break = break_index(); while (!Done()) { int 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; } void BreakIterator::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_LE(0, position_); DCHECK_LE(0, statement_position_); DebugBreakType type = GetDebugBreakType(); if (type != NOT_DEBUG_BREAK) break; } break_index_++; } DebugBreakType BreakIterator::GetDebugBreakType() { BytecodeArray* bytecode_array = debug_info_->OriginalBytecodeArray(); interpreter::Bytecode bytecode = interpreter::Bytecodes::FromByte(bytecode_array->get(code_offset())); // Make sure we read the actual bytecode, not a prefix scaling bytecode. if (interpreter::Bytecodes::IsPrefixScalingBytecode(bytecode)) { bytecode = interpreter::Bytecodes::FromByte( bytecode_array->get(code_offset() + 1)); } 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::kSuspendGenerator) { return DEBUG_BREAK_SLOT_AT_SUSPEND; } 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 BreakIterator::SkipToPosition(int position) { BreakIterator it(debug_info_); SkipTo(it.BreakIndexFromPosition(position)); } void BreakIterator::SetDebugBreak() { DebugBreakType debug_break_type = GetDebugBreakType(); if (debug_break_type == DEBUGGER_STATEMENT) return; HandleScope scope(isolate()); DCHECK(debug_break_type >= DEBUG_BREAK_SLOT); Handle bytecode_array(debug_info_->DebugBytecodeArray(), isolate()); interpreter::BytecodeArrayAccessor(bytecode_array, code_offset()) .ApplyDebugBreak(); } void BreakIterator::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())); } BreakLocation BreakIterator::GetBreakLocation() { Handle code( AbstractCode::cast(debug_info_->DebugBytecodeArray()), isolate()); DebugBreakType type = GetDebugBreakType(); int generator_object_reg_index = -1; if (type == DEBUG_BREAK_SLOT_AT_SUSPEND) { // For suspend break, we'll need the generator object to be able to step // over the suspend as if it didn't return. We get the interpreter register // index that holds the generator object by reading it directly off the // bytecode array, and we'll read the actual generator object off the // interpreter stack frame in GetGeneratorObjectForSuspendedFrame. BytecodeArray* bytecode_array = debug_info_->OriginalBytecodeArray(); interpreter::BytecodeArrayAccessor accessor( handle(bytecode_array, isolate()), code_offset()); DCHECK_EQ(accessor.current_bytecode(), interpreter::Bytecode::kSuspendGenerator); interpreter::Register generator_obj_reg = accessor.GetRegisterOperand(0); generator_object_reg_index = generator_obj_reg.index(); } return BreakLocation(code, type, code_offset(), position_, generator_object_reg_index); } Isolate* BreakIterator::isolate() { return debug_info_->GetIsolate(); } 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_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_.fast_forward_to_return_ = false; thread_local_.ignore_step_into_function_ = Smi::kZero; thread_local_.target_frame_count_ = -1; thread_local_.return_value_ = Smi::kZero; thread_local_.last_breakpoint_id_ = 0; clear_suspended_generator(); thread_local_.restart_fp_ = kNullAddress; base::Relaxed_Store(&thread_local_.current_debug_scope_, static_cast(0)); thread_local_.break_on_next_function_call_ = false; UpdateHookOnFunctionCall(); } char* Debug::ArchiveDebug(char* storage) { MemCopy(storage, reinterpret_cast(&thread_local_), ArchiveSpacePerThread()); return storage + ArchiveSpacePerThread(); } char* Debug::RestoreDebug(char* storage) { MemCopy(reinterpret_cast(&thread_local_), storage, ArchiveSpacePerThread()); // Enter the debugger. DebugScope debug_scope(this); // Clear any one-shot breakpoints that may have been set by the other // thread, and reapply breakpoints for this thread. ClearOneShot(); if (thread_local_.last_step_action_ != StepNone) { // Reset the previous step action for this thread. PrepareStep(thread_local_.last_step_action_); } return storage + ArchiveSpacePerThread(); } int Debug::ArchiveSpacePerThread() { return sizeof(ThreadLocal); } void Debug::Iterate(RootVisitor* v) { v->VisitRootPointer(Root::kDebug, nullptr, &thread_local_.return_value_); v->VisitRootPointer(Root::kDebug, nullptr, &thread_local_.suspended_generator_); v->VisitRootPointer(Root::kDebug, nullptr, &thread_local_.ignore_step_into_function_); } DebugInfoListNode::DebugInfoListNode(Isolate* isolate, DebugInfo* debug_info) : next_(nullptr) { // Globalize the request debug info object and make it weak. GlobalHandles* global_handles = isolate->global_handles(); debug_info_ = global_handles->Create(debug_info).location(); } DebugInfoListNode::~DebugInfoListNode() { if (debug_info_ == nullptr) return; GlobalHandles::Destroy(reinterpret_cast(debug_info_)); debug_info_ = nullptr; } void Debug::Unload() { ClearAllBreakPoints(); ClearStepping(); RemoveAllCoverageInfos(); ClearAllDebuggerHints(); debug_delegate_ = nullptr; } void Debug::Break(JavaScriptFrame* frame, Handle break_target) { // 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); DisableBreak no_recursive_break(this); // Return if we fail to retrieve debug info. Handle shared(break_target->shared(), isolate_); if (!EnsureBreakInfo(shared)) return; PrepareFunctionForDebugExecution(shared); 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() || break_on_next_function_call()) { // Clear all current stepping setup. ClearStepping(); // Notify the debug event listeners. OnDebugBreak(!break_points_hit.is_null() ? break_points_hit.ToHandleChecked() : isolate_->factory()->empty_fixed_array()); return; } // Debug break at function entry, do not worry about stepping. if (location.IsDebugBreakAtEntry()) { DCHECK(debug_info->BreakAtEntry()); return; } DCHECK_NOT_NULL(frame); // 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_; // StepOut at not return position was requested and return break locations // were flooded with one shots. if (thread_local_.fast_forward_to_return_) { DCHECK(location.IsReturnOrSuspend()); // We have to ignore recursive calls to function. if (current_frame_count > target_frame_count) return; ClearStepping(); PrepareStep(StepOut); return; } 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; V8_FALLTHROUGH; case StepIn: { // Special case "next" and "in" for generators that are about to suspend. if (location.IsSuspend()) { DCHECK(!has_suspended_generator()); thread_local_.suspended_generator_ = location.GetGeneratorObjectForSuspendedFrame(frame); ClearStepping(); return; } 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()->empty_fixed_array()); } 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(isolate_, debug_info); if (has_break_points) *has_break_points = has_break_points_to_check; if (!has_break_points_to_check) return {}; return Debug::GetHitBreakPoints(debug_info, location->position()); } 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()->HasBreakInfo()) return false; Handle debug_info(function->shared()->GetDebugInfo(), isolate_); // Enter the debugger. DebugScope debug_scope(this); std::vector break_locations; BreakLocation::AllAtCurrentStatement(debug_info, frame, &break_locations); bool has_break_points_at_all = false; for (size_t i = 0; i < break_locations.size(); 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; } // Check whether a single break point object is triggered. bool Debug::CheckBreakPoint(Handle break_point, bool is_break_at_entry) { HandleScope scope(isolate_); if (!break_point->condition()->length()) return true; Handle condition(break_point->condition(), isolate_); MaybeHandle maybe_result; Handle result; if (is_break_at_entry) { maybe_result = DebugEvaluate::WithTopmostArguments(isolate_, condition); } else { // Since we call CheckBreakpoint only for deoptimized frame on top of stack, // we can use 0 as index of inlined frame. const int inlined_jsframe_index = 0; const bool throw_on_side_effect = false; maybe_result = DebugEvaluate::Local(isolate_, break_frame_id(), inlined_jsframe_index, condition, throw_on_side_effect); } if (!maybe_result.ToHandle(&result)) { if (isolate_->has_pending_exception()) { isolate_->clear_pending_exception(); } return false; } return result->BooleanValue(isolate_); } bool Debug::SetBreakPoint(Handle function, Handle break_point, int* source_position) { HandleScope scope(isolate_); // Make sure the function is compiled and has set up the debug info. Handle shared(function->shared(), isolate_); if (!EnsureBreakInfo(shared)) return false; PrepareFunctionForDebugExecution(shared); Handle debug_info(shared->GetDebugInfo(), isolate_); // Source positions starts with zero. DCHECK_LE(0, *source_position); // Find the break point and change it. *source_position = FindBreakablePosition(debug_info, *source_position); DebugInfo::SetBreakPoint(isolate_, debug_info, *source_position, break_point); // At least one active break point now. DCHECK_LT(0, debug_info->GetBreakPointCount(isolate_)); ClearBreakPoints(debug_info); ApplyBreakPoints(debug_info); feature_tracker()->Track(DebugFeatureTracker::kBreakPoint); return true; } bool Debug::SetBreakPointForScript(Handle