// Copyright 2006-2008 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 "compiler.h" #include "debug.h" #include "execution.h" #include "global-handles.h" #include "natives.h" #include "stub-cache.h" #include "log.h" namespace v8 { namespace internal { static void PrintLn(v8::Local value) { v8::Local s = value->ToString(); char* data = NewArray(s->Length() + 1); if (data == NULL) { V8::FatalProcessOutOfMemory("PrintLn"); return; } s->WriteAscii(data); PrintF("%s\n", data); DeleteArray(data); } static Handle ComputeCallDebugBreak(int argc) { CALL_HEAP_FUNCTION(StubCache::ComputeCallDebugBreak(argc), Code); } static Handle ComputeCallDebugPrepareStepIn(int argc) { CALL_HEAP_FUNCTION(StubCache::ComputeCallDebugPrepareStepIn(argc), Code); } 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_ = rinfo()->data() - debug_info_->shared()->start_position(); } // Always update the position as we don't want that to be before the // statement position. position_ = rinfo()->data() - debug_info_->shared()->start_position(); ASSERT(position_ >= 0); ASSERT(statement_position_ >= 0); } // 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. if (RelocInfo::IsCodeTarget(rmode())) { Address target = original_rinfo()->target_address(); Code* code = Debug::GetCodeTarget(target); if (code->is_inline_cache_stub() || RelocInfo::IsConstructCall(rmode())) { break_point_++; return; } if (code->kind() == Code::STUB) { 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(); } 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 = 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()); // 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() { // 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() { // 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() { // 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())) { // This path is currently only used on IA32 as JSExitFrame on ARM uses a // stub. // Patch the JS frame exit code with a debug break call. See // VisitReturnStatement and ExitJSFrame in codegen-ia32.cc for the // precise return instructions sequence. ASSERT(Debug::kIa32JSReturnSequenceLength >= Debug::kIa32CallInstructionLength); rinfo()->patch_code_with_call(Debug::debug_break_return_entry()->entry(), Debug::kIa32JSReturnSequenceLength - Debug::kIa32CallInstructionLength); } else { // 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()); // Patch the code to invoke the builtin debug break function matching the // calling convention used by the call site. Handle dbgbrk_code(Debug::FindDebugBreak(rinfo())); rinfo()->set_target_address(dbgbrk_code->entry()); } ASSERT(IsDebugBreak()); } void BreakLocationIterator::ClearDebugBreak() { if (RelocInfo::IsJSReturn(rmode())) { // Restore the JS frame exit code. rinfo()->patch_code(original_rinfo()->pc(), Debug::kIa32JSReturnSequenceLength); } else { // Patch the code to the original invoke. rinfo()->set_target_address(original_rinfo()->target_address()); } ASSERT(!IsDebugBreak()); } void BreakLocationIterator::PrepareStepIn() { // Step in can only be prepared if currently positioned on an IC call or // construct call. Address target = rinfo()->target_address(); Code* code = Debug::GetCodeTarget(target); if (code->is_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(code->arguments_count()); if (IsDebugBreak()) { original_rinfo()->set_target_address(stub->entry()); } else { rinfo()->set_target_address(stub->entry()); } } else { // Step in through constructs call requires no changes to the running code. ASSERT(RelocInfo::IsConstructCall(rmode())); } } // 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())) { // This is IA32 specific but works as long as the ARM version // still uses a stub for JSExitFrame. // // TODO(1240753): Make the test architecture independent or split // parts of the debugger into architecture dependent files. return (*(rinfo()->pc()) == 0xE8); } else { return Debug::IsDebugBreak(rinfo()->target_address()); } } Object* BreakLocationIterator::BreakPointObjects() { return debug_info_->GetBreakPointObjects(code_position()); } 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 } bool Debug::has_break_points_ = false; DebugInfoListNode* Debug::debug_info_list_ = NULL; // Threading support. void Debug::ThreadInit() { 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_.step_into_fp_ = 0; thread_local_.after_break_target_ = 0; } JSCallerSavedBuffer Debug::registers_; Debug::ThreadLocal Debug::thread_local_; 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(registers_); } // Default break enabled. bool Debug::disable_break_ = false; // Default call debugger on uncaught exception. bool Debug::break_on_exception_ = false; bool Debug::break_on_uncaught_exception_ = true; Handle Debug::debug_context_ = Handle(); Code* Debug::debug_break_return_entry_ = NULL; Code* Debug::debug_break_return_ = NULL; void Debug::HandleWeakDebugInfo(v8::Persistent obj, void* data) { DebugInfoListNode* node = reinterpret_cast(data); RemoveDebugInfo(node->debug_info()); #ifdef DEBUG node = Debug::debug_info_list_; while (node != NULL) { ASSERT(node != reinterpret_cast(data)); node = node->next(); } #endif } DebugInfoListNode::DebugInfoListNode(DebugInfo* debug_info): next_(NULL) { // Globalize the request debug info object and make it weak. debug_info_ = Handle::cast((GlobalHandles::Create(debug_info))); GlobalHandles::MakeWeak(reinterpret_cast(debug_info_.location()), this, Debug::HandleWeakDebugInfo); } DebugInfoListNode::~DebugInfoListNode() { GlobalHandles::Destroy(reinterpret_cast(debug_info_.location())); } void Debug::Setup(bool create_heap_objects) { ThreadInit(); if (create_heap_objects) { // Get code to handle entry to debug break on return. debug_break_return_entry_ = Builtins::builtin(Builtins::Return_DebugBreakEntry); ASSERT(debug_break_return_entry_->IsCode()); // Get code to handle debug break on return. debug_break_return_ = Builtins::builtin(Builtins::Return_DebugBreak); ASSERT(debug_break_return_->IsCode()); } } bool Debug::CompileDebuggerScript(int index) { HandleScope scope; // Bail out if the index is invalid. if (index == -1) { return false; } // Find source and name for the requested script. Handle source_code = Bootstrapper::NativesSourceLookup(index); Vector name = Natives::GetScriptName(index); Handle script_name = Factory::NewStringFromAscii(name); // Compile the script. bool allow_natives_syntax = FLAG_allow_natives_syntax; FLAG_allow_natives_syntax = true; Handle boilerplate; boilerplate = Compiler::Compile(source_code, script_name, 0, 0, NULL, NULL); FLAG_allow_natives_syntax = allow_natives_syntax; // Silently ignore stack overflows during compilation. if (boilerplate.is_null()) { ASSERT(Top::has_pending_exception()); Top::clear_pending_exception(); return false; } // Execute the boilerplate function in the debugger context. Handle context = Top::global_context(); bool caught_exception = false; Handle function = Factory::NewFunctionFromBoilerplate(boilerplate, context); Handle result = Execution::TryCall(function, Handle(context->global()), 0, NULL, &caught_exception); // Check for caught exceptions. if (caught_exception) { Handle message = MessageHandler::MakeMessageObject( "error_loading_debugger", NULL, HandleVector(&result, 1), Handle()); MessageHandler::ReportMessage(NULL, message); return false; } // Mark this script as native and return successfully. Handle