// 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/v8.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/debug.h" #include "src/deoptimizer.h" #include "src/execution.h" #include "src/full-codegen.h" #include "src/global-handles.h" #include "src/isolate-inl.h" #include "src/list.h" #include "src/log.h" #include "src/messages.h" #include "src/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), event_command_queue_(isolate->logger(), kQueueInitialSize), is_active_(false), is_suppressed_(false), live_edit_enabled_(true), // TODO(yangguo): set to false by default. has_break_points_(false), break_disabled_(false), break_on_exception_(false), break_on_uncaught_exception_(false), script_cache_(NULL), debug_info_list_(NULL), isolate_(isolate) { ThreadInit(); } static v8::Handle 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); } BreakLocationIterator::BreakLocationIterator(Handle debug_info, BreakLocatorType type) { debug_info_ = debug_info; type_ = type; reloc_iterator_ = NULL; reloc_iterator_original_ = NULL; Reset(); // Initialize the rest of the member variables. } BreakLocationIterator::~BreakLocationIterator() { DCHECK(reloc_iterator_ != NULL); DCHECK(reloc_iterator_original_ != NULL); delete reloc_iterator_; delete reloc_iterator_original_; } // Check whether a code stub with the specified major key is a possible break // point location when looking for source break locations. static bool IsSourceBreakStub(Code* code) { CodeStub::Major major_key = CodeStub::GetMajorKey(code); return major_key == CodeStub::CallFunction; } // Check whether a code stub with the specified major key is a possible break // location. static bool IsBreakStub(Code* code) { CodeStub::Major major_key = CodeStub::GetMajorKey(code); return major_key == CodeStub::CallFunction; } void BreakLocationIterator::Next() { DisallowHeapAllocation no_gc; DCHECK(!RinfoDone()); // Iterate through reloc info for code and original code stopping at each // breakable code target. bool first = break_point_ == -1; while (!RinfoDone()) { if (!first) RinfoNext(); first = false; if (RinfoDone()) 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); } if (IsDebugBreakSlot()) { // There is always a possible break point at a debug break slot. break_point_++; return; } else if (RelocInfo::IsCodeTarget(rmode())) { // Check for breakable code target. Look in the original code as setting // break points can cause the code targets in the running (debugged) code // to be of a different kind than in the original code. Address target = original_rinfo()->target_address(); Code* code = Code::GetCodeFromTargetAddress(target); if ((code->is_inline_cache_stub() && !code->is_binary_op_stub() && !code->is_compare_ic_stub() && !code->is_to_boolean_ic_stub()) || RelocInfo::IsConstructCall(rmode())) { break_point_++; return; } if (code->kind() == Code::STUB) { if (IsDebuggerStatement()) { break_point_++; return; } else if (type_ == ALL_BREAK_LOCATIONS) { if (IsBreakStub(code)) { break_point_++; return; } } else { DCHECK(type_ == SOURCE_BREAK_LOCATIONS); if (IsSourceBreakStub(code)) { break_point_++; return; } } } } // Check for break at return. if (RelocInfo::IsJSReturn(rmode())) { // Set the positions to the end of the function. if (debug_info_->shared()->HasSourceCode()) { position_ = debug_info_->shared()->end_position() - debug_info_->shared()->start_position() - 1; } else { position_ = 0; } statement_position_ = position_; break_point_++; return; } } } void BreakLocationIterator::Next(int count) { while (count > 0) { Next(); count--; } } // Find the break point at the supplied address, or the closest one before // the address. void BreakLocationIterator::FindBreakLocationFromAddress(Address pc) { // Run through all break points to locate the one closest to the address. int closest_break_point = 0; int distance = kMaxInt; while (!Done()) { // Check if this break point is closer that what was previously found. if (this->pc() <= pc && pc - this->pc() < distance) { closest_break_point = break_point(); distance = static_cast(pc - this->pc()); // Check whether we can't get any closer. if (distance == 0) break; } Next(); } // Move to the break point found. Reset(); Next(closest_break_point); } // Find the break point closest to the supplied source position. void BreakLocationIterator::FindBreakLocationFromPosition(int position, BreakPositionAlignment alignment) { // Run through all break points to locate the one closest to the source // position. int closest_break_point = 0; int distance = kMaxInt; while (!Done()) { int next_position; switch (alignment) { case STATEMENT_ALIGNED: next_position = this->statement_position(); break; case BREAK_POSITION_ALIGNED: next_position = this->position(); break; default: UNREACHABLE(); next_position = this->statement_position(); } // Check if this break point is closer that what was previously found. if (position <= next_position && next_position - position < distance) { closest_break_point = break_point(); distance = next_position - position; // Check whether we can't get any closer. if (distance == 0) break; } Next(); } // Move to the break point found. Reset(); Next(closest_break_point); } void BreakLocationIterator::Reset() { // Create relocation iterators for the two code objects. if (reloc_iterator_ != NULL) delete reloc_iterator_; if (reloc_iterator_original_ != NULL) delete reloc_iterator_original_; reloc_iterator_ = new RelocIterator( debug_info_->code(), ~RelocInfo::ModeMask(RelocInfo::CODE_AGE_SEQUENCE)); reloc_iterator_original_ = new RelocIterator( debug_info_->original_code(), ~RelocInfo::ModeMask(RelocInfo::CODE_AGE_SEQUENCE)); // Position at the first break point. break_point_ = -1; position_ = 1; statement_position_ = 1; Next(); } bool BreakLocationIterator::Done() const { return RinfoDone(); } void BreakLocationIterator::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_position(), position(), statement_position(), break_point_object); } void BreakLocationIterator::ClearBreakPoint(Handle break_point_object) { // Clear the break point information. DebugInfo::ClearBreakPoint(debug_info_, code_position(), break_point_object); // If there are no more break points here remove the debug break. if (!HasBreakPoint()) { ClearDebugBreak(); DCHECK(!IsDebugBreak()); } } void BreakLocationIterator::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 BreakLocationIterator::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 BreakLocationIterator::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; if (RelocInfo::IsJSReturn(rmode())) { // Patch the frame exit code with a break point. SetDebugBreakAtReturn(); } else if (IsDebugBreakSlot()) { // Patch the code in the break slot. SetDebugBreakAtSlot(); } else { // Patch the IC call. SetDebugBreakAtIC(); } DCHECK(IsDebugBreak()); } void BreakLocationIterator::ClearDebugBreak() { // Debugger statement always calls debugger. No need to modify it. if (IsDebuggerStatement()) return; if (RelocInfo::IsJSReturn(rmode())) { // Restore the frame exit code. ClearDebugBreakAtReturn(); } else if (IsDebugBreakSlot()) { // Restore the code in the break slot. ClearDebugBreakAtSlot(); } else { // Patch the IC call. ClearDebugBreakAtIC(); } DCHECK(!IsDebugBreak()); } bool BreakLocationIterator::IsStepInLocation(Isolate* isolate) { if (RelocInfo::IsConstructCall(original_rmode())) { return true; } else if (RelocInfo::IsCodeTarget(rmode())) { HandleScope scope(debug_info_->GetIsolate()); Address target = original_rinfo()->target_address(); Handle target_code(Code::GetCodeFromTargetAddress(target)); if (target_code->kind() == Code::STUB) { return CodeStub::GetMajorKey(*target_code) == CodeStub::CallFunction; } return target_code->is_call_stub(); } return false; } void BreakLocationIterator::PrepareStepIn(Isolate* isolate) { #ifdef DEBUG HandleScope scope(isolate); // Step in can only be prepared if currently positioned on an IC call, // construct call or CallFunction stub call. Address target = rinfo()->target_address(); Handle target_code(Code::GetCodeFromTargetAddress(target)); // All the following stuff is needed only for assertion checks so the code // is wrapped in ifdef. Handle maybe_call_function_stub = target_code; if (IsDebugBreak()) { Address original_target = original_rinfo()->target_address(); maybe_call_function_stub = Handle(Code::GetCodeFromTargetAddress(original_target)); } bool is_call_function_stub = (maybe_call_function_stub->kind() == Code::STUB && CodeStub::GetMajorKey(*maybe_call_function_stub) == CodeStub::CallFunction); // Step in through construct call requires no changes to the running code. // Step in through getters/setters should already be prepared as well // because caller of this function (Debug::PrepareStep) is expected to // flood the top frame's function with one shot breakpoints. // Step in through CallFunction stub should also be prepared by caller of // this function (Debug::PrepareStep) which should flood target function // with breakpoints. DCHECK(RelocInfo::IsConstructCall(rmode()) || target_code->is_inline_cache_stub() || is_call_function_stub); #endif } // Check whether the break point is at a position which will exit the function. bool BreakLocationIterator::IsExit() const { return (RelocInfo::IsJSReturn(rmode())); } bool BreakLocationIterator::HasBreakPoint() { return debug_info_->HasBreakPoint(code_position()); } // Check whether there is a debug break at the current position. bool BreakLocationIterator::IsDebugBreak() { if (RelocInfo::IsJSReturn(rmode())) { return IsDebugBreakAtReturn(); } else if (IsDebugBreakSlot()) { return IsDebugBreakAtSlot(); } else { return Debug::IsDebugBreak(rinfo()->target_address()); } } // Find the builtin to use for invoking the debug break static Handle DebugBreakForIC(Handle code, RelocInfo::Mode mode) { Isolate* isolate = code->GetIsolate(); // Find the builtin debug break function matching the calling convention // used by the call site. if (code->is_inline_cache_stub()) { switch (code->kind()) { case Code::CALL_IC: return isolate->builtins()->CallICStub_DebugBreak(); case Code::LOAD_IC: return isolate->builtins()->LoadIC_DebugBreak(); case Code::STORE_IC: return isolate->builtins()->StoreIC_DebugBreak(); case Code::KEYED_LOAD_IC: return isolate->builtins()->KeyedLoadIC_DebugBreak(); case Code::KEYED_STORE_IC: return isolate->builtins()->KeyedStoreIC_DebugBreak(); case Code::COMPARE_NIL_IC: return isolate->builtins()->CompareNilIC_DebugBreak(); default: UNREACHABLE(); } } if (RelocInfo::IsConstructCall(mode)) { if (code->has_function_cache()) { return isolate->builtins()->CallConstructStub_Recording_DebugBreak(); } else { return isolate->builtins()->CallConstructStub_DebugBreak(); } } if (code->kind() == Code::STUB) { DCHECK(CodeStub::GetMajorKey(*code) == CodeStub::CallFunction); return isolate->builtins()->CallFunctionStub_DebugBreak(); } UNREACHABLE(); return Handle::null(); } void BreakLocationIterator::SetDebugBreakAtIC() { // Patch the original code with the current address as the current address // might have changed by the inline caching since the code was copied. original_rinfo()->set_target_address(rinfo()->target_address()); RelocInfo::Mode mode = rmode(); if (RelocInfo::IsCodeTarget(mode)) { Address target = rinfo()->target_address(); Handle target_code(Code::GetCodeFromTargetAddress(target)); // Patch the code to invoke the builtin debug break function matching the // calling convention used by the call site. Handle dbgbrk_code = DebugBreakForIC(target_code, mode); rinfo()->set_target_address(dbgbrk_code->entry()); } } void BreakLocationIterator::ClearDebugBreakAtIC() { // Patch the code to the original invoke. rinfo()->set_target_address(original_rinfo()->target_address()); } bool BreakLocationIterator::IsDebuggerStatement() { return RelocInfo::DEBUG_BREAK == rmode(); } bool BreakLocationIterator::IsDebugBreakSlot() { return RelocInfo::DEBUG_BREAK_SLOT == rmode(); } Object* BreakLocationIterator::BreakPointObjects() { return debug_info_->GetBreakPointObjects(code_position()); } // 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 BreakLocationIterator::ClearAllDebugBreak() { while (!Done()) { ClearDebugBreak(); Next(); } } bool BreakLocationIterator::RinfoDone() const { DCHECK(reloc_iterator_->done() == reloc_iterator_original_->done()); return reloc_iterator_->done(); } void BreakLocationIterator::RinfoNext() { reloc_iterator_->next(); reloc_iterator_original_->next(); #ifdef DEBUG DCHECK(reloc_iterator_->done() == reloc_iterator_original_->done()); if (!reloc_iterator_->done()) { DCHECK(rmode() == original_rmode()); } #endif } // 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_.step_count_ = 0; thread_local_.last_fp_ = 0; thread_local_.queued_step_count_ = 0; thread_local_.step_into_fp_ = 0; thread_local_.step_out_fp_ = 0; // TODO(isolates): frames_are_dropped_? thread_local_.current_debug_scope_ = NULL; thread_local_.restarter_frame_function_pointer_ = NULL; } 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); } ScriptCache::ScriptCache(Isolate* isolate) : HashMap(HashMap::PointersMatch), isolate_(isolate) { Heap* heap = isolate_->heap(); HandleScope scope(isolate_); // Perform two GCs to get rid of all unreferenced scripts. The first GC gets // rid of all the cached script wrappers and the second gets rid of the // scripts which are no longer referenced. heap->CollectAllGarbage(Heap::kMakeHeapIterableMask, "ScriptCache"); heap->CollectAllGarbage(Heap::kMakeHeapIterableMask, "ScriptCache"); // Scan heap for Script objects. HeapIterator iterator(heap); DisallowHeapAllocation no_allocation; for (HeapObject* obj = iterator.next(); obj != NULL; obj = iterator.next()) { if (obj->IsScript() && Script::cast(obj)->HasValidSource()) { Add(Handle