// Copyright 2012 the V8 project authors. All rights reserved. // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials provided // with the distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived // from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "v8.h" #include "api.h" #include "arguments.h" #include "bootstrapper.h" #include "code-stubs.h" #include "codegen.h" #include "compilation-cache.h" #include "compiler.h" #include "debug.h" #include "deoptimizer.h" #include "execution.h" #include "full-codegen.h" #include "global-handles.h" #include "ic.h" #include "ic-inl.h" #include "isolate-inl.h" #include "list.h" #include "messages.h" #include "natives.h" #include "stub-cache.h" #include "log.h" #include "../include/v8-debug.h" namespace v8 { namespace internal { #ifdef ENABLE_DEBUGGER_SUPPORT Debug::Debug(Isolate* isolate) : has_break_points_(false), script_cache_(NULL), debug_info_list_(NULL), disable_break_(false), break_on_exception_(false), break_on_uncaught_exception_(false), debug_break_return_(NULL), debug_break_slot_(NULL), isolate_(isolate) { memset(registers_, 0, sizeof(JSCallerSavedBuffer)); } Debug::~Debug() { } static void PrintLn(v8::Local value) { v8::Local s = value->ToString(); ScopedVector data(s->Length() + 1); if (data.start() == NULL) { V8::FatalProcessOutOfMemory("PrintLn"); return; } s->WriteAscii(data.start()); PrintF("%s\n", data.start()); } static Handle ComputeCallDebugPrepareStepIn(int argc, Code::Kind kind) { Isolate* isolate = Isolate::Current(); return isolate->stub_cache()->ComputeCallDebugPrepareStepIn(argc, kind); } 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() { ASSERT(reloc_iterator_ != NULL); ASSERT(reloc_iterator_original_ != NULL); delete reloc_iterator_; delete reloc_iterator_original_; } void BreakLocationIterator::Next() { AssertNoAllocation nogc; ASSERT(!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()); ASSERT(position_ >= 0); ASSERT(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_unary_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; } if (type_ == ALL_BREAK_LOCATIONS) { if (Debug::IsBreakStub(code)) { break_point_++; return; } } else { ASSERT(type_ == SOURCE_BREAK_LOCATIONS); if (Debug::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 closest to the supplied 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) { // Run through all break points to locate the one closest to the source // position. int closest_break_point = 0; int distance = kMaxInt; while (!Done()) { // Check if this break point is closer that what was previously found. if (position <= statement_position() && statement_position() - position < distance) { closest_break_point = break_point(); distance = statement_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()); reloc_iterator_original_ = new RelocIterator(debug_info_->original_code()); // 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(); } ASSERT(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(); ASSERT(!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()) { ASSERT(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()) { ASSERT(IsDebugBreak()); return; } // Patch code removing debug break. ClearDebugBreak(); ASSERT(!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(); } ASSERT(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(); } ASSERT(!IsDebugBreak()); } void BreakLocationIterator::PrepareStepIn() { HandleScope scope; // 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)); if (target_code->is_call_stub() || target_code->is_keyed_call_stub()) { // Step in through IC call is handled by the runtime system. Therefore make // sure that the any current IC is cleared and the runtime system is // called. If the executing code has a debug break at the location change // the call in the original code as it is the code there that will be // executed in place of the debug break call. Handle stub = ComputeCallDebugPrepareStepIn( target_code->arguments_count(), target_code->kind()); if (IsDebugBreak()) { original_rinfo()->set_target_address(stub->entry()); } else { rinfo()->set_target_address(stub->entry()); } } else { #ifdef DEBUG // 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 && maybe_call_function_stub->major_key() == 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. ASSERT(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()); } } 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(Debug::FindDebugBreak(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 { ASSERT(reloc_iterator_->done() == reloc_iterator_original_->done()); return reloc_iterator_->done(); } void BreakLocationIterator::RinfoNext() { reloc_iterator_->next(); reloc_iterator_original_->next(); #ifdef DEBUG ASSERT(reloc_iterator_->done() == reloc_iterator_original_->done()); if (!reloc_iterator_->done()) { ASSERT(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; thread_local_.after_break_target_ = 0; // TODO(isolates): frames_are_dropped_? thread_local_.debugger_entry_ = NULL; thread_local_.pending_interrupts_ = 0; thread_local_.restarter_frame_function_pointer_ = NULL; } char* Debug::ArchiveDebug(char* storage) { char* to = storage; memcpy(to, reinterpret_cast(&thread_local_), sizeof(ThreadLocal)); to += sizeof(ThreadLocal); memcpy(to, reinterpret_cast(®isters_), sizeof(registers_)); ThreadInit(); ASSERT(to <= storage + ArchiveSpacePerThread()); return storage + ArchiveSpacePerThread(); } char* Debug::RestoreDebug(char* storage) { char* from = storage; memcpy(reinterpret_cast(&thread_local_), from, sizeof(ThreadLocal)); from += sizeof(ThreadLocal); memcpy(reinterpret_cast(®isters_), from, sizeof(registers_)); ASSERT(from <= storage + ArchiveSpacePerThread()); return storage + ArchiveSpacePerThread(); } int Debug::ArchiveSpacePerThread() { return sizeof(ThreadLocal) + sizeof(JSCallerSavedBuffer); } // Frame structure (conforms InternalFrame structure): // -- code // -- SMI maker // -- function (slot is called "context") // -- frame base Object** Debug::SetUpFrameDropperFrame(StackFrame* bottom_js_frame, Handle code) { ASSERT(bottom_js_frame->is_java_script()); Address fp = bottom_js_frame->fp(); // Move function pointer into "context" slot. Memory::Object_at(fp + StandardFrameConstants::kContextOffset) = Memory::Object_at(fp + JavaScriptFrameConstants::kFunctionOffset); Memory::Object_at(fp + InternalFrameConstants::kCodeOffset) = *code; Memory::Object_at(fp + StandardFrameConstants::kMarkerOffset) = Smi::FromInt(StackFrame::INTERNAL); return reinterpret_cast(&Memory::Object_at( fp + StandardFrameConstants::kContextOffset)); } const int Debug::kFrameDropperFrameSize = 4; void ScriptCache::Add(Handle