// 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 <memory> #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<Context>()), event_listener_(Handle<Object>()), event_listener_data_(Handle<Object>()), 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<DebugInfo> debug_info, JavaScriptFrame* frame) { FrameSummary summary = FrameSummary::GetFirst(frame); int offset = summary.code_offset(); Handle<AbstractCode> 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<DebugInfo> debug_info, JavaScriptFrame* frame, List<BreakLocation>* result_out) { FrameSummary summary = FrameSummary::GetFirst(frame); int offset = summary.code_offset(); Handle<AbstractCode> 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<DebugInfo> debug_info, Handle<AbstractCode> 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<DebugInfo> 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> BreakIterator::GetIterator( Handle<DebugInfo> debug_info, Handle<AbstractCode> abstract_code, BreakLocatorType type) { if (abstract_code->IsBytecodeArray()) { DCHECK(debug_info->HasDebugBytecodeArray()); return std::unique_ptr<BreakIterator>( new BytecodeArrayBreakIterator(debug_info, type)); } else { DCHECK(abstract_code->IsCode()); DCHECK(debug_info->HasDebugCode()); return std::unique_ptr<BreakIterator>( new CodeBreakIterator(debug_info, type)); } } BreakIterator::BreakIterator(Handle<DebugInfo> 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<DebugInfo> 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<Code> 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<AbstractCode> code(AbstractCode::cast(debug_info_->DebugCode())); return BreakLocation(code, GetDebugBreakType(), code_offset(), position_); } BytecodeArrayBreakIterator::BytecodeArrayBreakIterator( Handle<DebugInfo> 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<AbstractCode> 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<Object>(); clear_suspended_generator(); // TODO(isolates): frames_are_dropped_? base::NoBarrier_Store(&thread_local_.current_debug_scope_, static_cast<base::AtomicWord>(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<DebugInfo>::cast(global_handles->Create(debug_info)).location(); } DebugInfoListNode::~DebugInfoListNode() { if (debug_info_ == nullptr) return; GlobalHandles::Destroy(reinterpret_cast<Object**>(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> context = isolate_->bootstrapper()->CreateEnvironment( MaybeHandle<JSGlobalProxy>(), v8::Local<ObjectTemplate>(), &no_extensions, kFirstContextSnapshotIndex, DEBUG_CONTEXT); // Fail if no context could be created. if (context.is_null()) return false; debug_context_ = Handle<Context>::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<Object>::cast(debug_context_).location()); debug_context_ = Handle<Context>(); } 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<JSFunction> function(frame->function()); Handle<SharedFunctionInfo> shared(function->shared()); if (!EnsureDebugInfo(shared, function)) { // Return if we failed to retrieve the debug info. return; } Handle<DebugInfo> 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<Object> 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<Object> Debug::CheckBreakPoints(Handle<DebugInfo> 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<Object> 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<FixedArray> break_points_hit; int break_points_hit_count = 0; DCHECK(!break_point_objects->IsUndefined(isolate_)); if (break_point_objects->IsFixedArray()) { Handle<FixedArray> array(FixedArray::cast(*break_point_objects)); break_points_hit = factory->NewFixedArray(array->length()); for (int i = 0; i < array->length(); i++) { Handle<Object> 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<JSArray> 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<DebugInfo> debug_info(function->shared()->GetDebugInfo()); // Enter the debugger. DebugScope debug_scope(this); if (debug_scope.failed()) return false; List<BreakLocation> 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<Object> 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<Object> Debug::CallFunction(const char* name, int argc, Handle<Object> args[]) { PostponeInterruptsScope no_interrupts(isolate_); AssertDebugContext(); Handle<JSReceiver> holder = Handle<JSReceiver>::cast(isolate_->natives_utils_object()); Handle<JSFunction> fun = Handle<JSFunction>::cast( JSReceiver::GetProperty(isolate_, holder, name).ToHandleChecked()); Handle<Object> 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<Object> 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<Object> break_id = factory->NewNumberFromInt(Debug::break_id()); // Call IsBreakPointTriggered. Handle<Object> argv[] = { break_id, break_point_object }; Handle<Object> 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<JSFunction> function, Handle<Object> break_point_object, int* source_position) { HandleScope scope(isolate_); // Make sure the function is compiled and has set up the debug info. Handle<SharedFunctionInfo> shared(function->shared()); if (!EnsureDebugInfo(shared, function)) { // Return if retrieving debug info failed. return true; } Handle<DebugInfo> 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<Script> script, Handle<Object> break_point_object, int* source_position, BreakPositionAlignment alignment) { if (script->type() == Script::TYPE_WASM) { // TODO(clemensh): set breakpoint for wasm. return false; } HandleScope scope(isolate_); // Obtain shared function info for the function. Handle<Object> result = FindSharedFunctionInfoInScript(script, *source_position); if (result->IsUndefined(isolate_)) return false; // Make sure the function has set up the debug info. Handle<SharedFunctionInfo> shared = Handle<SharedFunctionInfo>::cast(result); if (!EnsureDebugInfo(shared, Handle<JSFunction>::null())) { // Return if retrieving debug info failed. return false; } // Find position within function. The script position might be before the // source position of the first function. if (shared->start_position() > *source_position) { *source_position = shared->start_position(); } Handle<DebugInfo> debug_info(shared->GetDebugInfo()); // Find the break point and change it. *source_position = FindBreakablePosition(debug_info, *source_position, alignment); 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; } int Debug::FindBreakablePosition(Handle<DebugInfo> debug_info, int source_position, BreakPositionAlignment alignment) { int statement_position; int position; if (debug_info->HasDebugCode()) { CodeBreakIterator it(debug_info, ALL_BREAK_LOCATIONS); it.SkipToPosition(source_position, alignment); statement_position = it.statement_position(); position = it.position(); } else { DCHECK(debug_info->HasDebugBytecodeArray()); BytecodeArrayBreakIterator it(debug_info, ALL_BREAK_LOCATIONS); it.SkipToPosition(source_position, alignment); statement_position = it.statement_position(); position = it.position(); } return alignment == STATEMENT_ALIGNED ? statement_position : position; } void Debug::ApplyBreakPoints(Handle<DebugInfo> debug_info) { DisallowHeapAllocation no_gc; if (debug_info->break_points()->IsUndefined(isolate_)) return; FixedArray* break_points = debug_info->break_points(); for (int i = 0; i < break_points->length(); i++) { if (break_points->get(i)->IsUndefined(isolate_)) continue; BreakPointInfo* info = BreakPointInfo::cast(break_points->get(i)); if (info->GetBreakPointCount() == 0) continue; if (debug_info->HasDebugCode()) { CodeBreakIterator it(debug_info, ALL_BREAK_LOCATIONS); it.SkipToPosition(info->source_position(), BREAK_POSITION_ALIGNED); it.SetDebugBreak(); } if (debug_info->HasDebugBytecodeArray()) { BytecodeArrayBreakIterator it(debug_info, ALL_BREAK_LOCATIONS); it.SkipToPosition(info->source_position(), BREAK_POSITION_ALIGNED); it.SetDebugBreak(); } } } void Debug::ClearBreakPoints(Handle<DebugInfo> debug_info) { DisallowHeapAllocation no_gc; if (debug_info->HasDebugCode()) { for (CodeBreakIterator it(debug_info, ALL_BREAK_LOCATIONS); !it.Done(); it.Next()) { it.ClearDebugBreak(); } } if (debug_info->HasDebugBytecodeArray()) { for (BytecodeArrayBreakIterator it(debug_info, ALL_BREAK_LOCATIONS); !it.Done(); it.Next()) { it.ClearDebugBreak(); } } } void Debug::ClearBreakPoint(Handle<Object> break_point_object) { HandleScope scope(isolate_); for (DebugInfoListNode* node = debug_info_list_; node != NULL; node = node->next()) { Handle<Object> result = DebugInfo::FindBreakPointInfo(node->debug_info(), break_point_object); if (result->IsUndefined(isolate_)) continue; Handle<DebugInfo> debug_info = node->debug_info(); if (DebugInfo::ClearBreakPoint(debug_info, break_point_object)) { ClearBreakPoints(debug_info); if (debug_info->GetBreakPointCount() == 0) { RemoveDebugInfoAndClearFromShared(debug_info); } else { ApplyBreakPoints(debug_info); } return; } } } // Clear out all the debug break code. This is ONLY supposed to be used when // shutting down the debugger as it will leave the break point information in // DebugInfo even though the code is patched back to the non break point state. void Debug::ClearAllBreakPoints() { for (DebugInfoListNode* node = debug_info_list_; node != NULL; node = node->next()) { ClearBreakPoints(node->debug_info()); } // Remove all debug info. while (debug_info_list_ != NULL) { RemoveDebugInfoAndClearFromShared(debug_info_list_->debug_info()); } } void Debug::FloodWithOneShot(Handle<JSFunction> function, BreakLocatorType type) { // Debug utility functions are not subject to debugging. if (function->native_context() == *debug_context()) return; if (!function->shared()->IsSubjectToDebugging()) { // Builtin functions are not subject to stepping, but need to be // deoptimized, because optimized code does not check for debug // step in at call sites. Deoptimizer::DeoptimizeFunction(*function); return; } // Make sure the function is compiled and has set up the debug info. Handle<SharedFunctionInfo> shared(function->shared()); if (!EnsureDebugInfo(shared, function)) { // Return if we failed to retrieve the debug info. return; } // Flood the function with break points. Handle<DebugInfo> debug_info(shared->GetDebugInfo()); if (debug_info->HasDebugCode()) { for (CodeBreakIterator it(debug_info, type); !it.Done(); it.Next()) { it.SetDebugBreak(); } } if (debug_info->HasDebugBytecodeArray()) { for (BytecodeArrayBreakIterator it(debug_info, type); !it.Done(); it.Next()) { it.SetDebugBreak(); } } } void Debug::ChangeBreakOnException(ExceptionBreakType type, bool enable) { if (type == BreakUncaughtException) { break_on_uncaught_exception_ = enable; } else { break_on_exception_ = enable; } } bool Debug::IsBreakOnException(ExceptionBreakType type) { if (type == BreakUncaughtException) { return break_on_uncaught_exception_; } else { return break_on_exception_; } } void Debug::PrepareStepIn(Handle<JSFunction> function) { CHECK(last_step_action() >= StepIn); if (!is_active()) return; if (in_debug_scope()) return; FloodWithOneShot(function); } void Debug::PrepareStepInSuspendedGenerator() { CHECK(has_suspended_generator()); if (!is_active()) return; if (in_debug_scope()) return; thread_local_.last_step_action_ = StepIn; Handle<JSFunction> function( JSGeneratorObject::cast(thread_local_.suspended_generator_)->function()); FloodWithOneShot(function); clear_suspended_generator(); } void Debug::PrepareStepOnThrow() { if (!is_active()) return; if (last_step_action() == StepNone) return; if (in_debug_scope()) return; ClearOneShot(); // Iterate through the JavaScript stack looking for handlers. JavaScriptFrameIterator it(isolate_); while (!it.done()) { JavaScriptFrame* frame = it.frame(); if (frame->LookupExceptionHandlerInTable(nullptr, nullptr) > 0) break; it.Advance(); } if (last_step_action() == StepNext || last_step_action() == StepOut) { while (!it.done()) { Address current_fp = it.frame()->UnpaddedFP(); if (current_fp >= thread_local_.target_fp_) break; it.Advance(); } } // Find the closest Javascript frame we can flood with one-shots. while (!it.done() && !it.frame()->function()->shared()->IsSubjectToDebugging()) { it.Advance(); } if (it.done()) return; // No suitable Javascript catch handler. FloodWithOneShot(Handle<JSFunction>(it.frame()->function())); } void Debug::PrepareStep(StepAction step_action) { HandleScope scope(isolate_); DCHECK(in_debug_scope()); // Get the frame where the execution has stopped and skip the debug frame if // any. The debug frame will only be present if execution was stopped due to // hitting a break point. In other situations (e.g. unhandled exception) the // debug frame is not present. StackFrame::Id frame_id = break_frame_id(); // If there is no JavaScript stack don't do anything. if (frame_id == StackFrame::NO_ID) return; JavaScriptFrameIterator frames_it(isolate_, frame_id); JavaScriptFrame* frame = frames_it.frame(); feature_tracker()->Track(DebugFeatureTracker::kStepping); thread_local_.last_step_action_ = step_action; // If the function on the top frame is unresolved perform step out. This will // be the case when calling unknown function and having the debugger stopped // in an unhandled exception. if (!frame->function()->IsJSFunction()) { // Step out: Find the calling JavaScript frame and flood it with // breakpoints. frames_it.Advance(); // Fill the function to return to with one-shot break points. JSFunction* function = frames_it.frame()->function(); FloodWithOneShot(Handle<JSFunction>(function)); return; } // Get the debug info (create it if it does not exist). FrameSummary summary = FrameSummary::GetFirst(frame); Handle<JSFunction> function(summary.function()); Handle<SharedFunctionInfo> shared(function->shared()); if (!EnsureDebugInfo(shared, function)) { // Return if ensuring debug info failed. return; } Handle<DebugInfo> debug_info(shared->GetDebugInfo()); BreakLocation location = BreakLocation::FromFrame(debug_info, frame); // Any step at a return is a step-out. if (location.IsReturn()) step_action = StepOut; // A step-next at a tail call is a step-out. if (location.IsTailCall() && step_action == StepNext) step_action = StepOut; thread_local_.last_statement_position_ = summary.abstract_code()->SourceStatementPosition(summary.code_offset()); thread_local_.last_fp_ = frame->UnpaddedFP(); // No longer perform the current async step. clear_suspended_generator(); switch (step_action) { case StepNone: UNREACHABLE(); break; case StepOut: // Advance to caller frame. frames_it.Advance(); // Skip native and extension functions on the stack. while (!frames_it.done() && !frames_it.frame()->function()->shared()->IsSubjectToDebugging()) { // Builtin functions are not subject to stepping, but need to be // deoptimized to include checks for step-in at call sites. Deoptimizer::DeoptimizeFunction(frames_it.frame()->function()); frames_it.Advance(); } if (!frames_it.done()) { // Fill the caller function to return to with one-shot break points. Handle<JSFunction> caller_function(frames_it.frame()->function()); FloodWithOneShot(caller_function); thread_local_.target_fp_ = frames_it.frame()->UnpaddedFP(); } // Clear last position info. For stepping out it does not matter. thread_local_.last_statement_position_ = kNoSourcePosition; thread_local_.last_fp_ = 0; break; case StepNext: thread_local_.target_fp_ = frame->UnpaddedFP(); FloodWithOneShot(function); break; case StepIn: FloodWithOneShot(function); break; case StepFrame: // No point in setting one-shot breaks at places where we are not about // to leave the current frame. FloodWithOneShot(function, CALLS_AND_RETURNS); break; } } // Simple function for returning the source positions for active break points. Handle<Object> Debug::GetSourceBreakLocations( Handle<SharedFunctionInfo> shared, BreakPositionAlignment position_alignment) { Isolate* isolate = shared->GetIsolate(); if (!shared->HasDebugInfo()) { return isolate->factory()->undefined_value(); } Handle<DebugInfo> debug_info(shared->GetDebugInfo()); if (debug_info->GetBreakPointCount() == 0) { return isolate->factory()->undefined_value(); } Handle<FixedArray> locations = isolate->factory()->NewFixedArray(debug_info->GetBreakPointCount()); int count = 0; for (int i = 0; i < debug_info->break_points()->length(); ++i) { if (!debug_info->break_points()->get(i)->IsUndefined(isolate)) { BreakPointInfo* break_point_info = BreakPointInfo::cast(debug_info->break_points()->get(i)); int break_points = break_point_info->GetBreakPointCount(); if (break_points == 0) continue; Smi* position = NULL; if (position_alignment == STATEMENT_ALIGNED) { if (debug_info->HasDebugCode()) { CodeBreakIterator it(debug_info, ALL_BREAK_LOCATIONS); it.SkipToPosition(break_point_info->source_position(), BREAK_POSITION_ALIGNED); position = Smi::FromInt(it.statement_position()); } else { DCHECK(debug_info->HasDebugBytecodeArray()); BytecodeArrayBreakIterator it(debug_info, ALL_BREAK_LOCATIONS); it.SkipToPosition(break_point_info->source_position(), BREAK_POSITION_ALIGNED); position = Smi::FromInt(it.statement_position()); } } else { DCHECK_EQ(BREAK_POSITION_ALIGNED, position_alignment); position = Smi::FromInt(break_point_info->source_position()); } for (int j = 0; j < break_points; ++j) locations->set(count++, position); } } return locations; } void Debug::ClearStepping() { // Clear the various stepping setup. ClearOneShot(); thread_local_.last_step_action_ = StepNone; thread_local_.last_statement_position_ = kNoSourcePosition; thread_local_.last_fp_ = 0; thread_local_.target_fp_ = 0; } // Clears all the one-shot break points that are currently set. Normally this // function is called each time a break point is hit as one shot break points // are used to support stepping. void Debug::ClearOneShot() { // The current implementation just runs through all the breakpoints. When the // last break point for a function is removed that function is automatically // removed from the list. for (DebugInfoListNode* node = debug_info_list_; node != NULL; node = node->next()) { Handle<DebugInfo> debug_info = node->debug_info(); ClearBreakPoints(debug_info); ApplyBreakPoints(debug_info); } } bool MatchingCodeTargets(Code* target1, Code* target2) { if (target1 == target2) return true; if (target1->kind() != target2->kind()) return false; return target1->is_handler() || target1->is_inline_cache_stub(); } // Count the number of calls before the current frame PC to find the // corresponding PC in the newly recompiled code. static Address ComputeNewPcForRedirect(Code* new_code, Code* old_code, Address old_pc) { DCHECK_EQ(old_code->kind(), Code::FUNCTION); DCHECK_EQ(new_code->kind(), Code::FUNCTION); DCHECK(new_code->has_debug_break_slots()); static const int mask = RelocInfo::kCodeTargetMask; // Find the target of the current call. Code* target = NULL; intptr_t delta = 0; for (RelocIterator it(old_code, mask); !it.done(); it.next()) { RelocInfo* rinfo = it.rinfo(); Address current_pc = rinfo->pc(); // The frame PC is behind the call instruction by the call instruction size. if (current_pc > old_pc) break; delta = old_pc - current_pc; target = Code::GetCodeFromTargetAddress(rinfo->target_address()); } // Count the number of calls to the same target before the current call. int index = 0; for (RelocIterator it(old_code, mask); !it.done(); it.next()) { RelocInfo* rinfo = it.rinfo(); Address current_pc = rinfo->pc(); if (current_pc > old_pc) break; Code* current = Code::GetCodeFromTargetAddress(rinfo->target_address()); if (MatchingCodeTargets(target, current)) index++; } DCHECK(index > 0); // Repeat the count on the new code to find corresponding call. for (RelocIterator it(new_code, mask); !it.done(); it.next()) { RelocInfo* rinfo = it.rinfo(); Code* current = Code::GetCodeFromTargetAddress(rinfo->target_address()); if (MatchingCodeTargets(target, current)) index--; if (index == 0) return rinfo->pc() + delta; } UNREACHABLE(); return NULL; } // Count the number of continuations at which the current pc offset is at. static int ComputeContinuationIndexFromPcOffset(Code* code, int pc_offset) { DCHECK_EQ(code->kind(), Code::FUNCTION); Address pc = code->instruction_start() + pc_offset; int mask = RelocInfo::ModeMask(RelocInfo::GENERATOR_CONTINUATION); int index = 0; for (RelocIterator it(code, mask); !it.done(); it.next()) { index++; RelocInfo* rinfo = it.rinfo(); Address current_pc = rinfo->pc(); if (current_pc == pc) break; DCHECK(current_pc < pc); } return index; } // Find the pc offset for the given continuation index. static int ComputePcOffsetFromContinuationIndex(Code* code, int index) { DCHECK_EQ(code->kind(), Code::FUNCTION); DCHECK(code->has_debug_break_slots()); int mask = RelocInfo::ModeMask(RelocInfo::GENERATOR_CONTINUATION); RelocIterator it(code, mask); for (int i = 1; i < index; i++) it.next(); return static_cast<int>(it.rinfo()->pc() - code->instruction_start()); } class RedirectActiveFunctions : public ThreadVisitor { public: explicit RedirectActiveFunctions(SharedFunctionInfo* shared) : shared_(shared) { DCHECK(shared->HasDebugCode()); } void VisitThread(Isolate* isolate, ThreadLocalTop* top) { for (JavaScriptFrameIterator it(isolate, top); !it.done(); it.Advance()) { JavaScriptFrame* frame = it.frame(); JSFunction* function = frame->function(); if (frame->is_optimized()) continue; if (!function->Inlines(shared_)) continue; if (frame->is_interpreted()) { InterpretedFrame* interpreted_frame = reinterpret_cast<InterpretedFrame*>(frame); BytecodeArray* debug_copy = shared_->GetDebugInfo()->DebugBytecodeArray(); interpreted_frame->PatchBytecodeArray(debug_copy); continue; } Code* frame_code = frame->LookupCode(); DCHECK(frame_code->kind() == Code::FUNCTION); if (frame_code->has_debug_break_slots()) continue; Code* new_code = function->shared()->code(); Address old_pc = frame->pc(); Address new_pc = ComputeNewPcForRedirect(new_code, frame_code, old_pc); if (FLAG_trace_deopt) { PrintF("Replacing pc for debugging: %08" V8PRIxPTR " => %08" V8PRIxPTR "\n", reinterpret_cast<intptr_t>(old_pc), reinterpret_cast<intptr_t>(new_pc)); } if (FLAG_enable_embedded_constant_pool) { // Update constant pool pointer for new code. frame->set_constant_pool(new_code->constant_pool()); } // Patch the return address to return into the code with // debug break slots. frame->set_pc(new_pc); } } private: SharedFunctionInfo* shared_; DisallowHeapAllocation no_gc_; }; bool Debug::PrepareFunctionForBreakPoints(Handle<SharedFunctionInfo> shared) { DCHECK(shared->is_compiled()); if (isolate_->concurrent_recompilation_enabled()) { isolate_->optimizing_compile_dispatcher()->Flush( OptimizingCompileDispatcher::BlockingBehavior::kBlock); } List<Handle<JSFunction> > functions; List<Handle<JSGeneratorObject> > suspended_generators; // Flush all optimized code maps. Note that the below heap iteration does not // cover this, because the given function might have been inlined into code // for which no JSFunction exists. { SharedFunctionInfo::Iterator iterator(isolate_); while (SharedFunctionInfo* shared = iterator.Next()) { shared->ClearCodeFromOptimizedCodeMap(); } } // Make sure we abort incremental marking. isolate_->heap()->CollectAllGarbage(Heap::kMakeHeapIterableMask, GarbageCollectionReason::kDebugger); DCHECK(shared->is_compiled()); bool baseline_exists = shared->HasBaselineCode(); { // TODO(yangguo): with bytecode, we still walk the heap to find all // optimized code for the function to deoptimize. We can probably be // smarter here and avoid the heap walk. HeapIterator iterator(isolate_->heap()); HeapObject* obj; // Continuation from old-style generators need to be recomputed. bool find_resumables = baseline_exists && IsResumableFunction(shared->kind()); while ((obj = iterator.next())) { if (obj->IsJSFunction()) { JSFunction* function = JSFunction::cast(obj); if (!function->Inlines(*shared)) continue; if (function->code()->kind() == Code::OPTIMIZED_FUNCTION) { Deoptimizer::DeoptimizeFunction(function); } if (baseline_exists && function->shared() == *shared) { functions.Add(handle(function)); } } else if (find_resumables && obj->IsJSGeneratorObject()) { // This case handles async functions as well, as they use generator // objects for in-progress async function execution. JSGeneratorObject* generator_obj = JSGeneratorObject::cast(obj); if (!generator_obj->is_suspended()) continue; JSFunction* function = generator_obj->function(); if (!function->Inlines(*shared)) continue; int pc_offset = generator_obj->continuation(); int index = ComputeContinuationIndexFromPcOffset(function->code(), pc_offset); generator_obj->set_continuation(index); suspended_generators.Add(handle(generator_obj)); } } } // We do not need to replace code to debug bytecode. DCHECK(baseline_exists || functions.is_empty()); DCHECK(baseline_exists || suspended_generators.is_empty()); // We do not need to recompile to debug bytecode. if (baseline_exists && !shared->code()->has_debug_break_slots()) { if (!Compiler::CompileDebugCode(shared)) return false; } for (Handle<JSFunction> const function : functions) { function->ReplaceCode(shared->code()); JSFunction::EnsureLiterals(function); } for (Handle<JSGeneratorObject> const generator_obj : suspended_generators) { int index = generator_obj->continuation(); int pc_offset = ComputePcOffsetFromContinuationIndex(shared->code(), index); generator_obj->set_continuation(pc_offset); } // Update PCs on the stack to point to recompiled code. RedirectActiveFunctions redirect_visitor(*shared); redirect_visitor.VisitThread(isolate_, isolate_->thread_local_top()); isolate_->thread_manager()->IterateArchivedThreads(&redirect_visitor); return true; } namespace { template <typename Iterator> void GetBreakablePositions(Iterator* it, int start_position, int end_position, BreakPositionAlignment alignment, std::set<int>* positions) { it->SkipToPosition(start_position, alignment); while (!it->Done() && it->position() < end_position && it->position() >= start_position) { positions->insert(alignment == STATEMENT_ALIGNED ? it->statement_position() : it->position()); it->Next(); } } void FindBreakablePositions(Handle<DebugInfo> debug_info, int start_position, int end_position, BreakPositionAlignment alignment, std::set<int>* positions) { if (debug_info->HasDebugCode()) { CodeBreakIterator it(debug_info, ALL_BREAK_LOCATIONS); GetBreakablePositions(&it, start_position, end_position, alignment, positions); } else { DCHECK(debug_info->HasDebugBytecodeArray()); BytecodeArrayBreakIterator it(debug_info, ALL_BREAK_LOCATIONS); GetBreakablePositions(&it, start_position, end_position, alignment, positions); } } } // namespace bool Debug::GetPossibleBreakpoints(Handle<Script> script, int start_position, int end_position, std::set<int>* positions) { while (true) { if (!script->shared_function_infos()->IsWeakFixedArray()) return false; WeakFixedArray* infos = WeakFixedArray::cast(script->shared_function_infos()); HandleScope scope(isolate_); List<Handle<SharedFunctionInfo>> candidates; { WeakFixedArray::Iterator iterator(infos); SharedFunctionInfo* info; while ((info = iterator.Next<SharedFunctionInfo>())) { if (info->end_position() < start_position || info->start_position() >= end_position) { continue; } if (!info->IsSubjectToDebugging()) continue; if (!info->HasDebugCode() && !info->allows_lazy_compilation()) continue; candidates.Add(i::handle(info)); } } bool was_compiled = false; for (int i = 0; i < candidates.length(); ++i) { // Code that cannot be compiled lazily are internal and not debuggable. DCHECK(candidates[i]->allows_lazy_compilation()); if (!candidates[i]->HasDebugCode()) { if (!Compiler::CompileDebugCode(candidates[i])) { return false; } else { was_compiled = true; } } if (!EnsureDebugInfo(candidates[i], Handle<JSFunction>::null())) return false; } if (was_compiled) continue; for (int i = 0; i < candidates.length(); ++i) { CHECK(candidates[i]->HasDebugInfo()); Handle<DebugInfo> debug_info(candidates[i]->GetDebugInfo()); FindBreakablePositions(debug_info, start_position, end_position, STATEMENT_ALIGNED, positions); } return true; } UNREACHABLE(); return false; } void Debug::RecordAsyncFunction(Handle<JSGeneratorObject> generator_object) { if (last_step_action() <= StepOut) return; if (!IsAsyncFunction(generator_object->function()->shared()->kind())) return; DCHECK(!has_suspended_generator()); thread_local_.suspended_generator_ = *generator_object; ClearStepping(); } class SharedFunctionInfoFinder { public: explicit SharedFunctionInfoFinder(int target_position) : current_candidate_(NULL), current_candidate_closure_(NULL), current_start_position_(kNoSourcePosition), target_position_(target_position) {} void NewCandidate(SharedFunctionInfo* shared, JSFunction* closure = NULL) { if (!shared->IsSubjectToDebugging()) return; int start_position = shared->function_token_position(); if (start_position == kNoSourcePosition) { start_position = shared->start_position(); } if (start_position > target_position_) return; if (target_position_ > shared->end_position()) return; if (current_candidate_ != NULL) { if (current_start_position_ == start_position && shared->end_position() == current_candidate_->end_position()) { // If we already have a matching closure, do not throw it away. if (current_candidate_closure_ != NULL && closure == NULL) return; // If a top-level function contains only one function // declaration the source for the top-level and the function // is the same. In that case prefer the non top-level function. if (!current_candidate_->is_toplevel() && shared->is_toplevel()) return; } else if (start_position < current_start_position_ || current_candidate_->end_position() < shared->end_position()) { return; } } current_start_position_ = start_position; current_candidate_ = shared; current_candidate_closure_ = closure; } SharedFunctionInfo* Result() { return current_candidate_; } JSFunction* ResultClosure() { return current_candidate_closure_; } private: SharedFunctionInfo* current_candidate_; JSFunction* current_candidate_closure_; int current_start_position_; int target_position_; DisallowHeapAllocation no_gc_; }; // We need to find a SFI for a literal that may not yet have been compiled yet, // and there may not be a JSFunction referencing it. Find the SFI closest to // the given position, compile it to reveal possible inner SFIs and repeat. // While we are at this, also ensure code with debug break slots so that we do // not have to compile a SFI without JSFunction, which is paifu for those that // cannot be compiled without context (need to find outer compilable SFI etc.) Handle<Object> Debug::FindSharedFunctionInfoInScript(Handle<Script> script, int position) { for (int iteration = 0;; iteration++) { // Go through all shared function infos associated with this script to // find the inner most function containing this position. // If there is no shared function info for this script at all, there is // no point in looking for it by walking the heap. if (!script->shared_function_infos()->IsWeakFixedArray()) break; SharedFunctionInfo* shared; { SharedFunctionInfoFinder finder(position); WeakFixedArray::Iterator iterator(script->shared_function_infos()); SharedFunctionInfo* candidate; while ((candidate = iterator.Next<SharedFunctionInfo>())) { finder.NewCandidate(candidate); } shared = finder.Result(); if (shared == NULL) break; // We found it if it's already compiled and has debug code. if (shared->HasDebugCode()) { Handle<SharedFunctionInfo> shared_handle(shared); // If the iteration count is larger than 1, we had to compile the outer // function in order to create this shared function info. So there can // be no JSFunction referencing it. We can anticipate creating a debug // info while bypassing PrepareFunctionForBreakpoints. if (iteration > 1) { AllowHeapAllocation allow_before_return; CreateDebugInfo(shared_handle); } return shared_handle; } } // If not, compile to reveal inner functions. HandleScope scope(isolate_); // Code that cannot be compiled lazily are internal and not debuggable. DCHECK(shared->allows_lazy_compilation()); if (!Compiler::CompileDebugCode(handle(shared))) break; } return isolate_->factory()->undefined_value(); } // Ensures the debug information is present for shared. bool Debug::EnsureDebugInfo(Handle<SharedFunctionInfo> shared, Handle<JSFunction> function) { if (!shared->IsSubjectToDebugging()) return false; // Return if we already have the debug info for shared. if (shared->HasDebugInfo()) return true; if (function.is_null()) { DCHECK(shared->HasDebugCode()); } else if (!Compiler::Compile(function, Compiler::CLEAR_EXCEPTION)) { return false; } // To prepare bytecode for debugging, we already need to have the debug // info (containing the debug copy) upfront, but since we do not recompile, // preparing for break points cannot fail. CreateDebugInfo(shared); CHECK(PrepareFunctionForBreakPoints(shared)); return true; } void Debug::CreateDebugInfo(Handle<SharedFunctionInfo> shared) { // Create the debug info object. Handle<DebugInfo> debug_info = isolate_->factory()->NewDebugInfo(shared); // Add debug info to the list. DebugInfoListNode* node = new DebugInfoListNode(*debug_info); node->set_next(debug_info_list_); debug_info_list_ = node; } void Debug::RemoveDebugInfoAndClearFromShared(Handle<DebugInfo> debug_info) { HandleScope scope(isolate_); Handle<SharedFunctionInfo> shared(debug_info->shared()); DCHECK_NOT_NULL(debug_info_list_); // Run through the debug info objects to find this one and remove it. DebugInfoListNode* prev = NULL; DebugInfoListNode* current = debug_info_list_; while (current != NULL) { if (current->debug_info().is_identical_to(debug_info)) { // Unlink from list. If prev is NULL we are looking at the first element. if (prev == NULL) { debug_info_list_ = current->next(); } else { prev->set_next(current->next()); } delete current; shared->set_debug_info(DebugInfo::uninitialized()); return; } // Move to next in list. prev = current; current = current->next(); } UNREACHABLE(); } void Debug::SetAfterBreakTarget(JavaScriptFrame* frame) { after_break_target_ = NULL; if (!LiveEdit::SetAfterBreakTarget(this)) { // Continue just after the slot. after_break_target_ = frame->pc(); } } bool Debug::IsBreakAtReturn(JavaScriptFrame* frame) { HandleScope scope(isolate_); // Get the executing function in which the debug break occurred. Handle<SharedFunctionInfo> shared(frame->function()->shared()); // With no debug info there are no break points, so we can't be at a return. if (!shared->HasDebugInfo()) return false; DCHECK(!frame->is_optimized()); Handle<DebugInfo> debug_info(shared->GetDebugInfo()); BreakLocation location = BreakLocation::FromFrame(debug_info, frame); return location.IsReturn() || location.IsTailCall(); } void Debug::FramesHaveBeenDropped(StackFrame::Id new_break_frame_id, LiveEditFrameDropMode mode) { if (mode != LIVE_EDIT_CURRENTLY_SET_MODE) { thread_local_.frame_drop_mode_ = mode; } thread_local_.break_frame_id_ = new_break_frame_id; } bool Debug::IsDebugGlobal(JSGlobalObject* global) { return is_loaded() && global == debug_context()->global_object(); } void Debug::ClearMirrorCache() { PostponeInterruptsScope postpone(isolate_); HandleScope scope(isolate_); CallFunction("ClearMirrorCache", 0, NULL); } Handle<FixedArray> Debug::GetLoadedScripts() { isolate_->heap()->CollectAllGarbage(Heap::kFinalizeIncrementalMarkingMask, GarbageCollectionReason::kDebugger); Factory* factory = isolate_->factory(); if (!factory->script_list()->IsWeakFixedArray()) { return factory->empty_fixed_array(); } Handle<WeakFixedArray> array = Handle<WeakFixedArray>::cast(factory->script_list()); Handle<FixedArray> results = factory->NewFixedArray(array->Length()); int length = 0; { Script::Iterator iterator(isolate_); Script* script; while ((script = iterator.Next())) { if (script->HasValidSource()) results->set(length++, script); } } results->Shrink(length); return results; } MaybeHandle<Object> Debug::MakeExecutionState() { // Create the execution state object. Handle<Object> argv[] = { isolate_->factory()->NewNumberFromInt(break_id()) }; return CallFunction("MakeExecutionState", arraysize(argv), argv); } MaybeHandle<Object> Debug::MakeBreakEvent(Handle<Object> break_points_hit) { // Create the new break event object. Handle<Object> argv[] = { isolate_->factory()->NewNumberFromInt(break_id()), break_points_hit }; return CallFunction("MakeBreakEvent", arraysize(argv), argv); } MaybeHandle<Object> Debug::MakeExceptionEvent(Handle<Object> exception, bool uncaught, Handle<Object> promise) { // Create the new exception event object. Handle<Object> argv[] = { isolate_->factory()->NewNumberFromInt(break_id()), exception, isolate_->factory()->ToBoolean(uncaught), promise }; return CallFunction("MakeExceptionEvent", arraysize(argv), argv); } MaybeHandle<Object> Debug::MakeCompileEvent(Handle<Script> script, v8::DebugEvent type) { // Create the compile event object. Handle<Object> script_wrapper = Script::GetWrapper(script); Handle<Object> argv[] = { script_wrapper, isolate_->factory()->NewNumberFromInt(type) }; return CallFunction("MakeCompileEvent", arraysize(argv), argv); } MaybeHandle<Object> Debug::MakeAsyncTaskEvent(Handle<String> type, Handle<Object> id, Handle<String> name) { DCHECK(id->IsNumber()); // Create the async task event object. Handle<Object> argv[] = {type, id, name}; return CallFunction("MakeAsyncTaskEvent", arraysize(argv), argv); } void Debug::OnThrow(Handle<Object> exception) { if (in_debug_scope() || ignore_events()) return; PrepareStepOnThrow(); // Temporarily clear any scheduled_exception to allow evaluating // JavaScript from the debug event handler. HandleScope scope(isolate_); Handle<Object> scheduled_exception; if (isolate_->has_scheduled_exception()) { scheduled_exception = handle(isolate_->scheduled_exception(), isolate_); isolate_->clear_scheduled_exception(); } OnException(exception, isolate_->GetPromiseOnStackOnThrow()); if (!scheduled_exception.is_null()) { isolate_->thread_local_top()->scheduled_exception_ = *scheduled_exception; } } void Debug::OnPromiseReject(Handle<Object> promise, Handle<Object> value) { if (in_debug_scope() || ignore_events()) return; HandleScope scope(isolate_); // Check whether the promise has been marked as having triggered a message. Handle<Symbol> key = isolate_->factory()->promise_debug_marker_symbol(); if (!promise->IsJSObject() || JSReceiver::GetDataProperty(Handle<JSObject>::cast(promise), key) ->IsUndefined(isolate_)) { OnException(value, promise); } } void Debug::OnException(Handle<Object> exception, Handle<Object> promise) { // We cannot generate debug events when JS execution is disallowed. // TODO(5530): Reenable debug events within DisallowJSScopes once relevant // code (MakeExceptionEvent and ProcessDebugEvent) have been moved to C++. if (!AllowJavascriptExecution::IsAllowed(isolate_)) return; Isolate::CatchType catch_type = isolate_->PredictExceptionCatcher(); // Don't notify listener of exceptions that are internal to a desugaring. if (catch_type == Isolate::CAUGHT_BY_DESUGARING) return; bool uncaught = catch_type == Isolate::NOT_CAUGHT; if (promise->IsJSObject()) { Handle<JSObject> jspromise = Handle<JSObject>::cast(promise); // Mark the promise as already having triggered a message. Handle<Symbol> key = isolate_->factory()->promise_debug_marker_symbol(); JSObject::SetProperty(jspromise, key, key, STRICT).Assert(); // Check whether the promise reject is considered an uncaught exception. uncaught = !isolate_->PromiseHasUserDefinedRejectHandler(jspromise); } // Bail out if exception breaks are not active if (uncaught) { // Uncaught exceptions are reported by either flags. if (!(break_on_uncaught_exception_ || break_on_exception_)) return; } else { // Caught exceptions are reported is activated. if (!break_on_exception_) return; } { // Check whether the break location is muted. JavaScriptFrameIterator it(isolate_); if (!it.done() && IsMutedAtCurrentLocation(it.frame())) return; } DebugScope debug_scope(this); if (debug_scope.failed()) return; // Create the event data object. Handle<Object> event_data; // Bail out and don't call debugger if exception. if (!MakeExceptionEvent( exception, uncaught, promise).ToHandle(&event_data)) { return; } // Process debug event. ProcessDebugEvent(v8::Exception, Handle<JSObject>::cast(event_data), false); // Return to continue execution from where the exception was thrown. } void Debug::OnDebugBreak(Handle<Object> break_points_hit, bool auto_continue) { // The caller provided for DebugScope. AssertDebugContext(); // Bail out if there is no listener for this event if (ignore_events()) return; #ifdef DEBUG PrintBreakLocation(); #endif // DEBUG HandleScope scope(isolate_); // Create the event data object. Handle<Object> event_data; // Bail out and don't call debugger if exception. if (!MakeBreakEvent(break_points_hit).ToHandle(&event_data)) return; // Process debug event. ProcessDebugEvent(v8::Break, Handle<JSObject>::cast(event_data), auto_continue); } void Debug::OnCompileError(Handle<Script> script) { ProcessCompileEvent(v8::CompileError, script); } void Debug::OnBeforeCompile(Handle<Script> script) { ProcessCompileEvent(v8::BeforeCompile, script); } // Handle debugger actions when a new script is compiled. void Debug::OnAfterCompile(Handle<Script> script) { ProcessCompileEvent(v8::AfterCompile, script); } void Debug::OnAsyncTaskEvent(Handle<String> type, Handle<Object> id, Handle<String> name) { DCHECK(id->IsNumber()); if (in_debug_scope() || ignore_events()) return; HandleScope scope(isolate_); DebugScope debug_scope(this); if (debug_scope.failed()) return; // Create the script collected state object. Handle<Object> event_data; // Bail out and don't call debugger if exception. if (!MakeAsyncTaskEvent(type, id, name).ToHandle(&event_data)) return; // Process debug event. ProcessDebugEvent(v8::AsyncTaskEvent, Handle<JSObject>::cast(event_data), true); } void Debug::ProcessDebugEvent(v8::DebugEvent event, Handle<JSObject> event_data, bool auto_continue) { HandleScope scope(isolate_); // Create the execution state. Handle<Object> exec_state; // Bail out and don't call debugger if exception. if (!MakeExecutionState().ToHandle(&exec_state)) return; // First notify the message handler if any. if (message_handler_ != NULL) { NotifyMessageHandler(event, Handle<JSObject>::cast(exec_state), event_data, auto_continue); } // Notify registered debug event listener. This can be either a C or // a JavaScript function. Don't call event listener for v8::Break // here, if it's only a debug command -- they will be processed later. if ((event != v8::Break || !auto_continue) && !event_listener_.is_null()) { CallEventCallback(event, exec_state, event_data, NULL); } } void Debug::CallEventCallback(v8::DebugEvent event, Handle<Object> exec_state, Handle<Object> event_data, v8::Debug::ClientData* client_data) { // Prevent other interrupts from triggering, for example API callbacks, // while dispatching event listners. PostponeInterruptsScope postpone(isolate_); bool previous = in_debug_event_listener_; in_debug_event_listener_ = true; if (event_listener_->IsForeign()) { // Invoke the C debug event listener. v8::DebugInterface::EventCallback callback = FUNCTION_CAST<v8::DebugInterface::EventCallback>( Handle<Foreign>::cast(event_listener_)->foreign_address()); EventDetailsImpl event_details(event, Handle<JSObject>::cast(exec_state), Handle<JSObject>::cast(event_data), event_listener_data_, client_data); callback(event_details); CHECK(!isolate_->has_scheduled_exception()); } else { // Invoke the JavaScript debug event listener. DCHECK(event_listener_->IsJSFunction()); Handle<Object> argv[] = { Handle<Object>(Smi::FromInt(event), isolate_), exec_state, event_data, event_listener_data_ }; Handle<JSReceiver> global = isolate_->global_proxy(); MaybeHandle<Object> result = Execution::Call(isolate_, Handle<JSFunction>::cast(event_listener_), global, arraysize(argv), argv); CHECK(!result.is_null()); // Listeners must not throw. } in_debug_event_listener_ = previous; } void Debug::ProcessCompileEvent(v8::DebugEvent event, Handle<Script> script) { if (ignore_events()) return; SuppressDebug while_processing(this); bool in_nested_debug_scope = in_debug_scope(); HandleScope scope(isolate_); DebugScope debug_scope(this); if (debug_scope.failed()) return; if (event == v8::AfterCompile) { // If debugging there might be script break points registered for this // script. Make sure that these break points are set. Handle<Object> argv[] = {Script::GetWrapper(script)}; if (CallFunction("UpdateScriptBreakPoints", arraysize(argv), argv) .is_null()) { return; } } // Create the compile state object. Handle<Object> event_data; // Bail out and don't call debugger if exception. if (!MakeCompileEvent(script, event).ToHandle(&event_data)) return; // Don't call NotifyMessageHandler if already in debug scope to avoid running // nested command loop. if (in_nested_debug_scope) { if (event_listener_.is_null()) return; // Create the execution state. Handle<Object> exec_state; // Bail out and don't call debugger if exception. if (!MakeExecutionState().ToHandle(&exec_state)) return; CallEventCallback(event, exec_state, event_data, NULL); } else { // Process debug event. ProcessDebugEvent(event, Handle<JSObject>::cast(event_data), true); } } Handle<Context> Debug::GetDebugContext() { if (!is_loaded()) return Handle<Context>(); DebugScope debug_scope(this); if (debug_scope.failed()) return Handle<Context>(); // The global handle may be destroyed soon after. Return it reboxed. return handle(*debug_context(), isolate_); } void Debug::NotifyMessageHandler(v8::DebugEvent event, Handle<JSObject> exec_state, Handle<JSObject> event_data, bool auto_continue) { // Prevent other interrupts from triggering, for example API callbacks, // while dispatching message handler callbacks. PostponeInterruptsScope no_interrupts(isolate_); DCHECK(is_active_); HandleScope scope(isolate_); // Process the individual events. bool sendEventMessage = false; switch (event) { case v8::Break: sendEventMessage = !auto_continue; break; case v8::NewFunction: case v8::BeforeCompile: case v8::CompileError: case v8::AsyncTaskEvent: break; case v8::Exception: case v8::AfterCompile: sendEventMessage = true; break; } // The debug command interrupt flag might have been set when the command was // added. It should be enough to clear the flag only once while we are in the // debugger. DCHECK(in_debug_scope()); isolate_->stack_guard()->ClearDebugCommand(); // Notify the debugger that a debug event has occurred unless auto continue is // active in which case no event is send. if (sendEventMessage) { MessageImpl message = MessageImpl::NewEvent( event, auto_continue, Handle<JSObject>::cast(exec_state), Handle<JSObject>::cast(event_data)); InvokeMessageHandler(message); } // If auto continue don't make the event cause a break, but process messages // in the queue if any. For script collected events don't even process // messages in the queue as the execution state might not be what is expected // by the client. if (auto_continue && !has_commands()) return; // DebugCommandProcessor goes here. bool running = auto_continue; Handle<Object> cmd_processor_ctor = JSReceiver::GetProperty(isolate_, exec_state, "debugCommandProcessor") .ToHandleChecked(); Handle<Object> ctor_args[] = { isolate_->factory()->ToBoolean(running) }; Handle<JSReceiver> cmd_processor = Handle<JSReceiver>::cast( Execution::Call(isolate_, cmd_processor_ctor, exec_state, 1, ctor_args) .ToHandleChecked()); Handle<JSFunction> process_debug_request = Handle<JSFunction>::cast( JSReceiver::GetProperty(isolate_, cmd_processor, "processDebugRequest") .ToHandleChecked()); Handle<Object> is_running = JSReceiver::GetProperty(isolate_, cmd_processor, "isRunning") .ToHandleChecked(); // Process requests from the debugger. do { // Wait for new command in the queue. command_received_.Wait(); // Get the command from the queue. CommandMessage command = command_queue_.Get(); isolate_->logger()->DebugTag( "Got request from command queue, in interactive loop."); if (!is_active()) { // Delete command text and user data. command.Dispose(); return; } Vector<const uc16> command_text( const_cast<const uc16*>(command.text().start()), command.text().length()); Handle<String> request_text = isolate_->factory()->NewStringFromTwoByte( command_text).ToHandleChecked(); Handle<Object> request_args[] = { request_text }; Handle<Object> answer_value; Handle<String> answer; MaybeHandle<Object> maybe_exception; MaybeHandle<Object> maybe_result = Execution::TryCall(isolate_, process_debug_request, cmd_processor, 1, request_args, &maybe_exception); if (maybe_result.ToHandle(&answer_value)) { if (answer_value->IsUndefined(isolate_)) { answer = isolate_->factory()->empty_string(); } else { answer = Handle<String>::cast(answer_value); } // Log the JSON request/response. if (FLAG_trace_debug_json) { PrintF("%s\n", request_text->ToCString().get()); PrintF("%s\n", answer->ToCString().get()); } Handle<Object> is_running_args[] = { answer }; maybe_result = Execution::Call( isolate_, is_running, cmd_processor, 1, is_running_args); Handle<Object> result; if (!maybe_result.ToHandle(&result)) break; running = result->IsTrue(isolate_); } else { Handle<Object> exception; if (!maybe_exception.ToHandle(&exception)) break; Handle<Object> result; if (!Object::ToString(isolate_, exception).ToHandle(&result)) break; answer = Handle<String>::cast(result); } // Return the result. MessageImpl message = MessageImpl::NewResponse( event, running, exec_state, event_data, answer, command.client_data()); InvokeMessageHandler(message); command.Dispose(); // Return from debug event processing if either the VM is put into the // running state (through a continue command) or auto continue is active // and there are no more commands queued. } while (!running || has_commands()); command_queue_.Clear(); } void Debug::SetEventListener(Handle<Object> callback, Handle<Object> data) { GlobalHandles* global_handles = isolate_->global_handles(); // Remove existing entry. GlobalHandles::Destroy(event_listener_.location()); event_listener_ = Handle<Object>(); GlobalHandles::Destroy(event_listener_data_.location()); event_listener_data_ = Handle<Object>(); // Set new entry. if (!callback->IsUndefined(isolate_) && !callback->IsNull(isolate_)) { event_listener_ = global_handles->Create(*callback); if (data.is_null()) data = isolate_->factory()->undefined_value(); event_listener_data_ = global_handles->Create(*data); } UpdateState(); } void Debug::SetMessageHandler(v8::Debug::MessageHandler handler) { message_handler_ = handler; UpdateState(); if (handler == NULL && in_debug_scope()) { // Send an empty command to the debugger if in a break to make JavaScript // run again if the debugger is closed. EnqueueCommandMessage(Vector<const uint16_t>::empty()); } } void Debug::UpdateState() { bool is_active = message_handler_ != NULL || !event_listener_.is_null(); if (is_active || in_debug_scope()) { // Note that the debug context could have already been loaded to // bootstrap test cases. isolate_->compilation_cache()->Disable(); is_active = Load(); } else if (is_loaded()) { isolate_->compilation_cache()->Enable(); Unload(); } is_active_ = is_active; } // Calls the registered debug message handler. This callback is part of the // public API. void Debug::InvokeMessageHandler(MessageImpl message) { if (message_handler_ != NULL) message_handler_(message); } // Puts a command coming from the public API on the queue. Creates // a copy of the command string managed by the debugger. Up to this // point, the command data was managed by the API client. Called // by the API client thread. void Debug::EnqueueCommandMessage(Vector<const uint16_t> command, v8::Debug::ClientData* client_data) { // Need to cast away const. CommandMessage message = CommandMessage::New( Vector<uint16_t>(const_cast<uint16_t*>(command.start()), command.length()), client_data); isolate_->logger()->DebugTag("Put command on command_queue."); command_queue_.Put(message); command_received_.Signal(); // Set the debug command break flag to have the command processed. if (!in_debug_scope()) isolate_->stack_guard()->RequestDebugCommand(); } MaybeHandle<Object> Debug::Call(Handle<Object> fun, Handle<Object> data) { DebugScope debug_scope(this); if (debug_scope.failed()) return isolate_->factory()->undefined_value(); // Create the execution state. Handle<Object> exec_state; if (!MakeExecutionState().ToHandle(&exec_state)) { return isolate_->factory()->undefined_value(); } Handle<Object> argv[] = { exec_state, data }; return Execution::Call( isolate_, fun, Handle<Object>(debug_context()->global_proxy(), isolate_), arraysize(argv), argv); } void Debug::HandleDebugBreak() { // Ignore debug break during bootstrapping. if (isolate_->bootstrapper()->IsActive()) return; // Just continue if breaks are disabled. if (break_disabled()) return; // Ignore debug break if debugger is not active. if (!is_active()) return; StackLimitCheck check(isolate_); if (check.HasOverflowed()) return; { JavaScriptFrameIterator it(isolate_); DCHECK(!it.done()); Object* fun = it.frame()->function(); if (fun && fun->IsJSFunction()) { // Don't stop in builtin functions. if (!JSFunction::cast(fun)->shared()->IsSubjectToDebugging()) return; JSGlobalObject* global = JSFunction::cast(fun)->context()->global_object(); // Don't stop in debugger functions. if (IsDebugGlobal(global)) return; // Don't stop if the break location is muted. if (IsMutedAtCurrentLocation(it.frame())) return; } } // Collect the break state before clearing the flags. bool debug_command_only = isolate_->stack_guard()->CheckDebugCommand() && !isolate_->stack_guard()->CheckDebugBreak(); isolate_->stack_guard()->ClearDebugBreak(); // Clear stepping to avoid duplicate breaks. ClearStepping(); ProcessDebugMessages(debug_command_only); } void Debug::ProcessDebugMessages(bool debug_command_only) { isolate_->stack_guard()->ClearDebugCommand(); StackLimitCheck check(isolate_); if (check.HasOverflowed()) return; HandleScope scope(isolate_); DebugScope debug_scope(this); if (debug_scope.failed()) return; // Notify the debug event listeners. Indicate auto continue if the break was // a debug command break. OnDebugBreak(isolate_->factory()->undefined_value(), debug_command_only); } #ifdef DEBUG void Debug::PrintBreakLocation() { if (!FLAG_print_break_location) return; HandleScope scope(isolate_); JavaScriptFrameIterator iterator(isolate_); if (iterator.done()) return; JavaScriptFrame* frame = iterator.frame(); FrameSummary summary = FrameSummary::GetFirst(frame); int source_position = summary.abstract_code()->SourcePosition(summary.code_offset()); Handle<Object> script_obj(summary.function()->shared()->script(), isolate_); PrintF("[debug] break in function '"); summary.function()->PrintName(); PrintF("'.\n"); if (script_obj->IsScript()) { Handle<Script> script = Handle<Script>::cast(script_obj); Handle<String> source(String::cast(script->source())); Script::InitLineEnds(script); int line = Script::GetLineNumber(script, source_position) - script->line_offset(); int column = Script::GetColumnNumber(script, source_position) - (line == 0 ? script->column_offset() : 0); Handle<FixedArray> line_ends(FixedArray::cast(script->line_ends())); int line_start = line == 0 ? 0 : Smi::cast(line_ends->get(line - 1))->value() + 1; int line_end = Smi::cast(line_ends->get(line))->value(); DisallowHeapAllocation no_gc; String::FlatContent content = source->GetFlatContent(); if (content.IsOneByte()) { PrintF("[debug] %.*s\n", line_end - line_start, content.ToOneByteVector().start() + line_start); PrintF("[debug] "); for (int i = 0; i < column; i++) PrintF(" "); PrintF("^\n"); } else { PrintF("[debug] at line %d column %d\n", line, column); } } } #endif // DEBUG DebugScope::DebugScope(Debug* debug) : debug_(debug), prev_(debug->debugger_entry()), save_(debug_->isolate_), no_termination_exceptons_(debug_->isolate_, StackGuard::TERMINATE_EXECUTION) { // Link recursive debugger entry. base::NoBarrier_Store(&debug_->thread_local_.current_debug_scope_, reinterpret_cast<base::AtomicWord>(this)); // Store the previous break id, frame id and return value. break_id_ = debug_->break_id(); break_frame_id_ = debug_->break_frame_id(); return_value_ = debug_->return_value(); // Create the new break info. If there is no proper frames there is no break // frame id. StackTraceFrameIterator it(isolate()); bool has_frames = !it.done(); // We don't currently support breaking inside wasm framess. DCHECK(!has_frames || !it.is_wasm()); debug_->thread_local_.break_frame_id_ = has_frames ? it.frame()->id() : StackFrame::NO_ID; debug_->SetNextBreakId(); debug_->UpdateState(); // Make sure that debugger is loaded and enter the debugger context. // The previous context is kept in save_. failed_ = !debug_->is_loaded(); if (!failed_) isolate()->set_context(*debug->debug_context()); } DebugScope::~DebugScope() { if (!failed_ && prev_ == NULL) { // Clear mirror cache when leaving the debugger. Skip this if there is a // pending exception as clearing the mirror cache calls back into // JavaScript. This can happen if the v8::Debug::Call is used in which // case the exception should end up in the calling code. if (!isolate()->has_pending_exception()) debug_->ClearMirrorCache(); // If there are commands in the queue when leaving the debugger request // that these commands are processed. if (debug_->has_commands()) isolate()->stack_guard()->RequestDebugCommand(); } // Leaving this debugger entry. base::NoBarrier_Store(&debug_->thread_local_.current_debug_scope_, reinterpret_cast<base::AtomicWord>(prev_)); // Restore to the previous break state. debug_->thread_local_.break_frame_id_ = break_frame_id_; debug_->thread_local_.break_id_ = break_id_; debug_->thread_local_.return_value_ = return_value_; debug_->UpdateState(); } MessageImpl MessageImpl::NewEvent(DebugEvent event, bool running, Handle<JSObject> exec_state, Handle<JSObject> event_data) { MessageImpl message(true, event, running, exec_state, event_data, Handle<String>(), NULL); return message; } MessageImpl MessageImpl::NewResponse(DebugEvent event, bool running, Handle<JSObject> exec_state, Handle<JSObject> event_data, Handle<String> response_json, v8::Debug::ClientData* client_data) { MessageImpl message(false, event, running, exec_state, event_data, response_json, client_data); return message; } MessageImpl::MessageImpl(bool is_event, DebugEvent event, bool running, Handle<JSObject> exec_state, Handle<JSObject> event_data, Handle<String> response_json, v8::Debug::ClientData* client_data) : is_event_(is_event), event_(event), running_(running), exec_state_(exec_state), event_data_(event_data), response_json_(response_json), client_data_(client_data) {} bool MessageImpl::IsEvent() const { return is_event_; } bool MessageImpl::IsResponse() const { return !is_event_; } DebugEvent MessageImpl::GetEvent() const { return event_; } bool MessageImpl::WillStartRunning() const { return running_; } v8::Local<v8::Object> MessageImpl::GetExecutionState() const { return v8::Utils::ToLocal(exec_state_); } v8::Isolate* MessageImpl::GetIsolate() const { return reinterpret_cast<v8::Isolate*>(exec_state_->GetIsolate()); } v8::Local<v8::Object> MessageImpl::GetEventData() const { return v8::Utils::ToLocal(event_data_); } v8::Local<v8::String> MessageImpl::GetJSON() const { Isolate* isolate = event_data_->GetIsolate(); v8::EscapableHandleScope scope(reinterpret_cast<v8::Isolate*>(isolate)); if (IsEvent()) { // Call toJSONProtocol on the debug event object. Handle<Object> fun = JSReceiver::GetProperty(isolate, event_data_, "toJSONProtocol") .ToHandleChecked(); if (!fun->IsJSFunction()) { return v8::Local<v8::String>(); } MaybeHandle<Object> maybe_json = Execution::TryCall(isolate, fun, event_data_, 0, NULL); Handle<Object> json; if (!maybe_json.ToHandle(&json) || !json->IsString()) { return v8::Local<v8::String>(); } return scope.Escape(v8::Utils::ToLocal(Handle<String>::cast(json))); } else { return v8::Utils::ToLocal(response_json_); } } namespace { v8::Local<v8::Context> GetDebugEventContext(Isolate* isolate) { Handle<Context> context = isolate->debug()->debugger_entry()->GetContext(); // Isolate::context() may have been NULL when "script collected" event // occured. if (context.is_null()) return v8::Local<v8::Context>(); Handle<Context> native_context(context->native_context()); return v8::Utils::ToLocal(native_context); } } // anonymous namespace v8::Local<v8::Context> MessageImpl::GetEventContext() const { Isolate* isolate = event_data_->GetIsolate(); v8::Local<v8::Context> context = GetDebugEventContext(isolate); // Isolate::context() may be NULL when "script collected" event occurs. DCHECK(!context.IsEmpty()); return context; } v8::Debug::ClientData* MessageImpl::GetClientData() const { return client_data_; } EventDetailsImpl::EventDetailsImpl(DebugEvent event, Handle<JSObject> exec_state, Handle<JSObject> event_data, Handle<Object> callback_data, v8::Debug::ClientData* client_data) : event_(event), exec_state_(exec_state), event_data_(event_data), callback_data_(callback_data), client_data_(client_data) {} DebugEvent EventDetailsImpl::GetEvent() const { return event_; } v8::Local<v8::Object> EventDetailsImpl::GetExecutionState() const { return v8::Utils::ToLocal(exec_state_); } v8::Local<v8::Object> EventDetailsImpl::GetEventData() const { return v8::Utils::ToLocal(event_data_); } v8::Local<v8::Context> EventDetailsImpl::GetEventContext() const { return GetDebugEventContext(exec_state_->GetIsolate()); } v8::Local<v8::Value> EventDetailsImpl::GetCallbackData() const { return v8::Utils::ToLocal(callback_data_); } v8::Debug::ClientData* EventDetailsImpl::GetClientData() const { return client_data_; } v8::Isolate* EventDetailsImpl::GetIsolate() const { return reinterpret_cast<v8::Isolate*>(exec_state_->GetIsolate()); } CommandMessage::CommandMessage() : text_(Vector<uint16_t>::empty()), client_data_(NULL) { } CommandMessage::CommandMessage(const Vector<uint16_t>& text, v8::Debug::ClientData* data) : text_(text), client_data_(data) { } void CommandMessage::Dispose() { text_.Dispose(); delete client_data_; client_data_ = NULL; } CommandMessage CommandMessage::New(const Vector<uint16_t>& command, v8::Debug::ClientData* data) { return CommandMessage(command.Clone(), data); } CommandMessageQueue::CommandMessageQueue(int size) : start_(0), end_(0), size_(size) { messages_ = NewArray<CommandMessage>(size); } CommandMessageQueue::~CommandMessageQueue() { while (!IsEmpty()) Get().Dispose(); DeleteArray(messages_); } CommandMessage CommandMessageQueue::Get() { DCHECK(!IsEmpty()); int result = start_; start_ = (start_ + 1) % size_; return messages_[result]; } void CommandMessageQueue::Put(const CommandMessage& message) { if ((end_ + 1) % size_ == start_) { Expand(); } messages_[end_] = message; end_ = (end_ + 1) % size_; } void CommandMessageQueue::Expand() { CommandMessageQueue new_queue(size_ * 2); while (!IsEmpty()) { new_queue.Put(Get()); } CommandMessage* array_to_free = messages_; *this = new_queue; new_queue.messages_ = array_to_free; // Make the new_queue empty so that it doesn't call Dispose on any messages. new_queue.start_ = new_queue.end_; // Automatic destructor called on new_queue, freeing array_to_free. } LockingCommandMessageQueue::LockingCommandMessageQueue(Logger* logger, int size) : logger_(logger), queue_(size) {} bool LockingCommandMessageQueue::IsEmpty() const { base::LockGuard<base::Mutex> lock_guard(&mutex_); return queue_.IsEmpty(); } CommandMessage LockingCommandMessageQueue::Get() { base::LockGuard<base::Mutex> lock_guard(&mutex_); CommandMessage result = queue_.Get(); logger_->DebugEvent("Get", result.text()); return result; } void LockingCommandMessageQueue::Put(const CommandMessage& message) { base::LockGuard<base::Mutex> lock_guard(&mutex_); queue_.Put(message); logger_->DebugEvent("Put", message.text()); } void LockingCommandMessageQueue::Clear() { base::LockGuard<base::Mutex> lock_guard(&mutex_); queue_.Clear(); } } // namespace internal } // namespace v8