// Copyright 2012 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "src/debug/debug.h" #include "src/api.h" #include "src/arguments.h" #include "src/bootstrapper.h" #include "src/code-stubs.h" #include "src/codegen.h" #include "src/compilation-cache.h" #include "src/compiler.h" #include "src/deoptimizer.h" #include "src/execution.h" #include "src/frames-inl.h" #include "src/full-codegen/full-codegen.h" #include "src/global-handles.h" #include "src/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 "include/v8-debug.h" namespace v8 { namespace internal { Debug::Debug(Isolate* isolate) : debug_context_(Handle()), event_listener_(Handle()), event_listener_data_(Handle()), message_handler_(NULL), command_received_(0), command_queue_(isolate->logger(), kQueueInitialSize), is_active_(false), is_suppressed_(false), live_edit_enabled_(true), // TODO(yangguo): set to false by default. break_disabled_(false), break_points_active_(true), in_debug_event_listener_(false), break_on_exception_(false), break_on_uncaught_exception_(false), debug_info_list_(NULL), feature_tracker_(isolate), isolate_(isolate) { ThreadInit(); } static v8::Local GetDebugEventContext(Isolate* isolate) { Handle context = isolate->debug()->debugger_entry()->GetContext(); // Isolate::context() may have been NULL when "script collected" event // occured. if (context.is_null()) return v8::Local(); Handle native_context(context->native_context()); return v8::Utils::ToLocal(native_context); } BreakLocation::BreakLocation(Handle debug_info, DebugBreakType type, int code_offset, int position, int statement_position) : debug_info_(debug_info), code_offset_(code_offset), type_(type), position_(position), statement_position_(statement_position) {} BreakLocation::Iterator* BreakLocation::GetIterator( Handle debug_info, BreakLocatorType type) { if (debug_info->abstract_code()->IsBytecodeArray()) { return new BytecodeArrayIterator(debug_info, type); } else { return new CodeIterator(debug_info, type); } } BreakLocation::Iterator::Iterator(Handle debug_info) : debug_info_(debug_info), break_index_(-1), position_(1), statement_position_(1) {} int BreakLocation::Iterator::ReturnPosition() { if (debug_info_->shared()->HasSourceCode()) { return debug_info_->shared()->end_position() - debug_info_->shared()->start_position() - 1; } else { return 0; } } BreakLocation::CodeIterator::CodeIterator(Handle debug_info, BreakLocatorType type) : Iterator(debug_info), reloc_iterator_(debug_info->abstract_code()->GetCode(), GetModeMask(type)) { // There is at least one break location. DCHECK(!Done()); Next(); } int BreakLocation::CodeIterator::GetModeMask(BreakLocatorType type) { int mask = 0; mask |= RelocInfo::ModeMask(RelocInfo::POSITION); mask |= RelocInfo::ModeMask(RelocInfo::STATEMENT_POSITION); mask |= RelocInfo::ModeMask(RelocInfo::DEBUG_BREAK_SLOT_AT_RETURN); mask |= RelocInfo::ModeMask(RelocInfo::DEBUG_BREAK_SLOT_AT_CALL); if (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 BreakLocation::CodeIterator::Next() { DisallowHeapAllocation no_gc; DCHECK(!Done()); // Iterate through reloc info stopping at each breakable code target. bool first = break_index_ == -1; while (!Done()) { if (!first) reloc_iterator_.next(); first = false; if (Done()) return; // Whenever a statement position or (plain) position is passed update the // current value of these. if (RelocInfo::IsPosition(rmode())) { if (RelocInfo::IsStatementPosition(rmode())) { statement_position_ = static_cast( rinfo()->data() - debug_info_->shared()->start_position()); } // Always update the position as we don't want that to be before the // statement position. position_ = static_cast(rinfo()->data() - debug_info_->shared()->start_position()); DCHECK(position_ >= 0); DCHECK(statement_position_ >= 0); continue; } DCHECK(RelocInfo::IsDebugBreakSlot(rmode()) || RelocInfo::IsDebuggerStatement(rmode())); if (RelocInfo::IsDebugBreakSlotAtReturn(rmode())) { // Set the positions to the end of the function. statement_position_ = position_ = ReturnPosition(); } break; } break_index_++; } BreakLocation BreakLocation::CodeIterator::GetBreakLocation() { DebugBreakType type; if (RelocInfo::IsDebugBreakSlotAtReturn(rmode())) { type = DEBUG_BREAK_SLOT_AT_RETURN; } else if (RelocInfo::IsDebugBreakSlotAtCall(rmode())) { type = DEBUG_BREAK_SLOT_AT_CALL; } else if (RelocInfo::IsDebugBreakSlotAtTailCall(rmode())) { type = isolate()->is_tail_call_elimination_enabled() ? DEBUG_BREAK_SLOT_AT_TAIL_CALL : DEBUG_BREAK_SLOT_AT_CALL; } else if (RelocInfo::IsDebuggerStatement(rmode())) { type = DEBUGGER_STATEMENT; } else if (RelocInfo::IsDebugBreakSlot(rmode())) { type = DEBUG_BREAK_SLOT; } else { type = NOT_DEBUG_BREAK; } return BreakLocation(debug_info_, type, code_offset(), position(), statement_position()); } BreakLocation::BytecodeArrayIterator::BytecodeArrayIterator( Handle debug_info, BreakLocatorType type) : Iterator(debug_info), source_position_iterator_(debug_info->abstract_code() ->GetBytecodeArray() ->source_position_table()), break_locator_type_(type), start_position_(debug_info->shared()->start_position()) { // There is at least one break location. DCHECK(!Done()); Next(); } void BreakLocation::BytecodeArrayIterator::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() - start_position_; if (source_position_iterator_.is_statement()) { statement_position_ = position_; } DCHECK(position_ >= 0); DCHECK(statement_position_ >= 0); enum 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) { DCHECK_EQ(ReturnPosition(), position_); DCHECK_EQ(ReturnPosition(), statement_position_); break; } } break_index_++; } BreakLocation::DebugBreakType BreakLocation::BytecodeArrayIterator::GetDebugBreakType() { BytecodeArray* bytecode_array = debug_info_->original_bytecode_array(); 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; } } BreakLocation BreakLocation::BytecodeArrayIterator::GetBreakLocation() { return BreakLocation(debug_info_, GetDebugBreakType(), code_offset(), position(), statement_position()); } // Find the break point at the supplied address, or the closest one before // the address. BreakLocation BreakLocation::FromCodeOffset(Handle debug_info, int offset) { base::SmartPointer it(GetIterator(debug_info)); it->SkipTo(BreakIndexFromCodeOffset(debug_info, offset)); return it->GetBreakLocation(); } FrameSummary GetFirstFrameSummary(JavaScriptFrame* frame) { List frames(FLAG_max_inlining_levels + 1); frame->Summarize(&frames); return frames.first(); } int CallOffsetFromCodeOffset(int code_offset, bool is_interpreted) { // Code offset points to the instruction after the call. Subtract 1 to // exclude that instruction from the search. For bytecode, the code offset // still points to the call. return is_interpreted ? code_offset : code_offset - 1; } BreakLocation BreakLocation::FromFrame(Handle debug_info, JavaScriptFrame* frame) { FrameSummary summary = GetFirstFrameSummary(frame); int call_offset = CallOffsetFromCodeOffset(summary.code_offset(), frame->is_interpreted()); return FromCodeOffset(debug_info, call_offset); } void BreakLocation::AllForStatementPosition(Handle debug_info, int statement_position, List* result_out) { for (base::SmartPointer it(GetIterator(debug_info)); !it->Done(); it->Next()) { if (it->statement_position() == statement_position) { result_out->Add(it->GetBreakLocation()); } } } int BreakLocation::BreakIndexFromCodeOffset(Handle debug_info, 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 < debug_info->abstract_code()->Size()); for (base::SmartPointer it(GetIterator(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; } BreakLocation BreakLocation::FromPosition(Handle debug_info, int position, BreakPositionAlignment alignment) { // Run through all break points to locate the one closest to the source // position. int distance = kMaxInt; base::SmartPointer it(GetIterator(debug_info)); BreakLocation closest_break = it->GetBreakLocation(); while (!it->Done()) { int next_position; if (alignment == STATEMENT_ALIGNED) { next_position = it->statement_position(); } else { DCHECK(alignment == BREAK_POSITION_ALIGNED); next_position = it->position(); } if (position <= next_position && next_position - position < distance) { closest_break = it->GetBreakLocation(); distance = next_position - position; // Check whether we can't get any closer. if (distance == 0) break; } it->Next(); } return closest_break; } void BreakLocation::SetBreakPoint(Handle break_point_object) { // If there is not already a real break point here patch code with debug // break. if (!HasBreakPoint()) SetDebugBreak(); DCHECK(IsDebugBreak() || IsDebuggerStatement()); // Set the break point information. DebugInfo::SetBreakPoint(debug_info_, code_offset_, position_, statement_position_, break_point_object); } void BreakLocation::ClearBreakPoint(Handle break_point_object) { // Clear the break point information. DebugInfo::ClearBreakPoint(debug_info_, code_offset_, break_point_object); // If there are no more break points here remove the debug break. if (!HasBreakPoint()) { ClearDebugBreak(); DCHECK(!IsDebugBreak()); } } void BreakLocation::SetOneShot() { // Debugger statement always calls debugger. No need to modify it. if (IsDebuggerStatement()) return; // If there is a real break point here no more to do. if (HasBreakPoint()) { DCHECK(IsDebugBreak()); return; } // Patch code with debug break. SetDebugBreak(); } void BreakLocation::ClearOneShot() { // Debugger statement always calls debugger. No need to modify it. if (IsDebuggerStatement()) return; // If there is a real break point here no more to do. if (HasBreakPoint()) { DCHECK(IsDebugBreak()); return; } // Patch code removing debug break. ClearDebugBreak(); DCHECK(!IsDebugBreak()); } void BreakLocation::SetDebugBreak() { // Debugger statement always calls debugger. No need to modify it. if (IsDebuggerStatement()) return; // If there is already a break point here just return. This might happen if // the same code is flooded with break points twice. Flooding the same // function twice might happen when stepping in a function with an exception // handler as the handler and the function is the same. if (IsDebugBreak()) return; DCHECK(IsDebugBreakSlot()); if (abstract_code()->IsCode()) { Code* code = abstract_code()->GetCode(); DCHECK(code->kind() == Code::FUNCTION); Builtins* builtins = isolate()->builtins(); Handle target = IsReturn() ? builtins->Return_DebugBreak() : builtins->Slot_DebugBreak(); Address pc = code->instruction_start() + code_offset(); DebugCodegen::PatchDebugBreakSlot(isolate(), pc, target); } else { BytecodeArray* bytecode_array = abstract_code()->GetBytecodeArray(); interpreter::Bytecode bytecode = interpreter::Bytecodes::FromByte(bytecode_array->get(code_offset())); interpreter::Bytecode debugbreak = interpreter::Bytecodes::GetDebugBreak(bytecode); bytecode_array->set(code_offset(), interpreter::Bytecodes::ToByte(debugbreak)); } DCHECK(IsDebugBreak()); } void BreakLocation::ClearDebugBreak() { // Debugger statement always calls debugger. No need to modify it. if (IsDebuggerStatement()) return; DCHECK(IsDebugBreakSlot()); if (abstract_code()->IsCode()) { Code* code = abstract_code()->GetCode(); DCHECK(code->kind() == Code::FUNCTION); Address pc = code->instruction_start() + code_offset(); DebugCodegen::ClearDebugBreakSlot(isolate(), pc); } else { BytecodeArray* bytecode_array = abstract_code()->GetBytecodeArray(); BytecodeArray* original = debug_info_->original_bytecode_array(); bytecode_array->set(code_offset(), original->get(code_offset())); } DCHECK(!IsDebugBreak()); } bool BreakLocation::IsDebugBreak() const { if (IsDebuggerStatement()) return false; DCHECK(IsDebugBreakSlot()); if (abstract_code()->IsCode()) { Code* code = abstract_code()->GetCode(); DCHECK(code->kind() == Code::FUNCTION); Address pc = code->instruction_start() + code_offset(); return DebugCodegen::DebugBreakSlotIsPatched(pc); } else { BytecodeArray* bytecode_array = abstract_code()->GetBytecodeArray(); interpreter::Bytecode bytecode = interpreter::Bytecodes::FromByte(bytecode_array->get(code_offset())); return interpreter::Bytecodes::IsDebugBreak(bytecode); } } Handle BreakLocation::BreakPointObjects() const { return debug_info_->GetBreakPointObjects(code_offset_); } void DebugFeatureTracker::Track(DebugFeatureTracker::Feature feature) { uint32_t mask = 1 << feature; // Only count one sample per feature and isolate. if (bitfield_ & mask) return; isolate_->counters()->debug_feature_usage()->AddSample(feature); bitfield_ |= mask; } // Threading support. void Debug::ThreadInit() { thread_local_.break_count_ = 0; thread_local_.break_id_ = 0; thread_local_.break_frame_id_ = StackFrame::NO_ID; thread_local_.last_step_action_ = StepNone; thread_local_.last_statement_position_ = RelocInfo::kNoPosition; thread_local_.last_fp_ = 0; thread_local_.target_fp_ = 0; thread_local_.step_in_enabled_ = false; thread_local_.return_value_ = Handle(); // TODO(isolates): frames_are_dropped_? base::NoBarrier_Store(&thread_local_.current_debug_scope_, static_cast(0)); } char* Debug::ArchiveDebug(char* storage) { char* to = storage; MemCopy(to, reinterpret_cast(&thread_local_), sizeof(ThreadLocal)); ThreadInit(); return storage + ArchiveSpacePerThread(); } char* Debug::RestoreDebug(char* storage) { char* from = storage; MemCopy(reinterpret_cast(&thread_local_), from, sizeof(ThreadLocal)); return storage + ArchiveSpacePerThread(); } int Debug::ArchiveSpacePerThread() { return sizeof(ThreadLocal); } DebugInfoListNode::DebugInfoListNode(DebugInfo* debug_info): next_(NULL) { // Globalize the request debug info object and make it weak. GlobalHandles* global_handles = debug_info->GetIsolate()->global_handles(); debug_info_ = Handle::cast(global_handles->Create(debug_info)).location(); } DebugInfoListNode::~DebugInfoListNode() { if (debug_info_ == nullptr) return; GlobalHandles::Destroy(reinterpret_cast(debug_info_)); debug_info_ = nullptr; } bool Debug::Load() { // Return if debugger is already loaded. if (is_loaded()) return true; // Bail out if we're already in the process of compiling the native // JavaScript source code for the debugger. if (is_suppressed_) return false; SuppressDebug while_loading(this); // Disable breakpoints and interrupts while compiling and running the // debugger scripts including the context creation code. DisableBreak disable(this, true); PostponeInterruptsScope postpone(isolate_); // Create the debugger context. HandleScope scope(isolate_); ExtensionConfiguration no_extensions; Handle context = isolate_->bootstrapper()->CreateEnvironment( MaybeHandle(), v8::Local(), &no_extensions, DEBUG_CONTEXT); // Fail if no context could be created. if (context.is_null()) return false; debug_context_ = Handle::cast( isolate_->global_handles()->Create(*context)); feature_tracker()->Track(DebugFeatureTracker::kActive); return true; } void Debug::Unload() { ClearAllBreakPoints(); ClearStepping(); // Return debugger is not loaded. if (!is_loaded()) return; // Clear debugger context global handle. GlobalHandles::Destroy(Handle::cast(debug_context_).location()); debug_context_ = Handle(); } void Debug::Break(JavaScriptFrame* frame) { HandleScope scope(isolate_); // Initialize LiveEdit. LiveEdit::InitializeThreadLocal(this); // Just continue if breaks are disabled or debugger cannot be loaded. if (break_disabled()) return; // Enter the debugger. DebugScope debug_scope(this); if (debug_scope.failed()) return; // Postpone interrupt during breakpoint processing. PostponeInterruptsScope postpone(isolate_); // Get the debug info (create it if it does not exist). Handle function(frame->function()); Handle shared(function->shared()); if (!EnsureDebugInfo(shared, function)) { // Return if we failed to retrieve the debug info. return; } Handle debug_info(shared->GetDebugInfo()); // Find the break location where execution has stopped. BreakLocation location = BreakLocation::FromFrame(debug_info, frame); // Find actual break points, if any, and trigger debug break event. Handle break_points_hit = CheckBreakPoints(&location); if (!break_points_hit->IsUndefined()) { // Clear all current stepping setup. ClearStepping(); // Notify the debug event listeners. OnDebugBreak(break_points_hit, false); return; } // No break point. Check for stepping. StepAction step_action = last_step_action(); Address current_fp = frame->UnpaddedFP(); Address target_fp = thread_local_.target_fp_; Address last_fp = thread_local_.last_fp_; bool step_break = 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 = GetFirstFrameSummary(frame); int offset = summary.code_offset(); step_break = step_break || location.IsReturn() || (current_fp != last_fp) || (thread_local_.last_statement_position_ != location.abstract_code()->SourceStatementPosition(offset)); break; } case StepFrame: step_break = current_fp != last_fp; break; } // Clear all current stepping setup. ClearStepping(); if (step_break) { // Notify the debug event listeners. OnDebugBreak(isolate_->factory()->undefined_value(), false); } else { // Re-prepare to continue. PrepareStep(step_action); } } // Find break point objects for this location, if any, and evaluate them. // Return an array of break point objects that evaluated true. Handle Debug::CheckBreakPoints(BreakLocation* location, bool* has_break_points) { Factory* factory = isolate_->factory(); bool has_break_points_to_check = break_points_active_ && location->HasBreakPoint(); if (has_break_points) *has_break_points = has_break_points_to_check; if (!has_break_points_to_check) return factory->undefined_value(); Handle break_point_objects = location->BreakPointObjects(); // Count the number of break points hit. If there are multiple break points // they are in a FixedArray. Handle break_points_hit; int break_points_hit_count = 0; DCHECK(!break_point_objects->IsUndefined()); if (break_point_objects->IsFixedArray()) { Handle array(FixedArray::cast(*break_point_objects)); break_points_hit = factory->NewFixedArray(array->length()); for (int i = 0; i < array->length(); i++) { Handle break_point_object(array->get(i), isolate_); if (CheckBreakPoint(break_point_object)) { break_points_hit->set(break_points_hit_count++, *break_point_object); } } } else { break_points_hit = factory->NewFixedArray(1); if (CheckBreakPoint(break_point_objects)) { break_points_hit->set(break_points_hit_count++, *break_point_objects); } } if (break_points_hit_count == 0) return factory->undefined_value(); Handle result = factory->NewJSArrayWithElements(break_points_hit); result->set_length(Smi::FromInt(break_points_hit_count)); return result; } bool Debug::IsMutedAtCurrentLocation(JavaScriptFrame* frame) { // A break location is considered muted if break locations on the current // statement have at least one break point, and all of these break points // evaluate to false. Aside from not triggering a debug break event at the // break location, we also do not trigger one for debugger statements, nor // an exception event on exception at this location. Object* fun = frame->function(); if (!fun->IsJSFunction()) return false; JSFunction* function = JSFunction::cast(fun); if (!function->shared()->HasDebugInfo()) return false; HandleScope scope(isolate_); Handle debug_info(function->shared()->GetDebugInfo()); // Enter the debugger. DebugScope debug_scope(this); if (debug_scope.failed()) return false; BreakLocation current_position = BreakLocation::FromFrame(debug_info, frame); List break_locations; BreakLocation::AllForStatementPosition( debug_info, current_position.statement_position(), &break_locations); bool has_break_points_at_all = false; for (int i = 0; i < break_locations.length(); i++) { bool has_break_points; Handle check_result = CheckBreakPoints(&break_locations[i], &has_break_points); has_break_points_at_all |= has_break_points; if (has_break_points && !check_result->IsUndefined()) return false; } return has_break_points_at_all; } MaybeHandle Debug::CallFunction(const char* name, int argc, Handle args[]) { PostponeInterruptsScope no_interrupts(isolate_); AssertDebugContext(); Handle holder = Handle::cast(isolate_->natives_utils_object()); Handle fun = Handle::cast( JSReceiver::GetProperty(isolate_, holder, name).ToHandleChecked()); Handle undefined = isolate_->factory()->undefined_value(); return Execution::TryCall(isolate_, fun, undefined, argc, args); } // Check whether a single break point object is triggered. bool Debug::CheckBreakPoint(Handle break_point_object) { Factory* factory = isolate_->factory(); HandleScope scope(isolate_); // Ignore check if break point object is not a JSObject. if (!break_point_object->IsJSObject()) return true; // Get the break id as an object. Handle break_id = factory->NewNumberFromInt(Debug::break_id()); // Call IsBreakPointTriggered. Handle argv[] = { break_id, break_point_object }; Handle result; if (!CallFunction("IsBreakPointTriggered", arraysize(argv), argv) .ToHandle(&result)) { return false; } // Return whether the break point is triggered. return result->IsTrue(); } bool Debug::SetBreakPoint(Handle function, Handle break_point_object, int* source_position) { HandleScope scope(isolate_); // Make sure the function is compiled and has set up the debug info. Handle shared(function->shared()); if (!EnsureDebugInfo(shared, function)) { // Return if retrieving debug info failed. return true; } Handle debug_info(shared->GetDebugInfo()); // Source positions starts with zero. DCHECK(*source_position >= 0); // Find the break point and change it. BreakLocation location = BreakLocation::FromPosition( debug_info, *source_position, STATEMENT_ALIGNED); *source_position = location.statement_position(); location.SetBreakPoint(break_point_object); feature_tracker()->Track(DebugFeatureTracker::kBreakPoint); // At least one active break point now. return debug_info->GetBreakPointCount() > 0; } bool Debug::SetBreakPointForScript(Handle