v8/src/debug.cc

1946 lines
63 KiB
C++
Raw Normal View History

// 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<v8::Value> value) {
v8::Local<v8::String> s = value->ToString();
char* data = NewArray<char>(s->Length() + 1);
if (data == NULL) {
V8::FatalProcessOutOfMemory("PrintLn");
return;
}
s->WriteAscii(data);
PrintF("%s\n", data);
DeleteArray(data);
}
static Handle<Code> ComputeCallDebugBreak(int argc) {
CALL_HEAP_FUNCTION(StubCache::ComputeCallDebugBreak(argc), Code);
}
static Handle<Code> ComputeCallDebugPrepareStepIn(int argc) {
CALL_HEAP_FUNCTION(StubCache::ComputeCallDebugPrepareStepIn(argc), Code);
}
BreakLocationIterator::BreakLocationIterator(Handle<DebugInfo> 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;
Defer the writing of the source position data to the relocation information until a possible debug break location is reached. Currently this is call sites with calls to code objects and JS return. Source position information in the code therefore no longer refers to the "first" instruction generated for a given source position (which was not the case defered code anyway) but to the first break location after that source position was passed (again defered code always start with source position information). This doesn't make a difference for the debugger as it will always be stopped only at debug break locations. However, this makes the life of the peep-hole optimizer much easier as many oportunities for posh/pop eliminations where previosly blocked by relocation information already written to the code object. Two types of source positions are still collected. Statement positions indicate the position of the start of the statement leading to this code and (plain) positions indicate other places typically call sites to help indicate current position in backtraces. The two different types of positions are also used to distinguish between step next and step in. Runs all the tests (including debugger tests) as before. Moved the checking for the FLAG_debug_info to one place. I will do the same changes to the ARM codegenerator in a seperate changelist. Review URL: http://codereview.chromium.org/2957 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@335 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
2008-09-18 08:51:43 +00:00
// 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();
}
Defer the writing of the source position data to the relocation information until a possible debug break location is reached. Currently this is call sites with calls to code objects and JS return. Source position information in the code therefore no longer refers to the "first" instruction generated for a given source position (which was not the case defered code anyway) but to the first break location after that source position was passed (again defered code always start with source position information). This doesn't make a difference for the debugger as it will always be stopped only at debug break locations. However, this makes the life of the peep-hole optimizer much easier as many oportunities for posh/pop eliminations where previosly blocked by relocation information already written to the code object. Two types of source positions are still collected. Statement positions indicate the position of the start of the statement leading to this code and (plain) positions indicate other places typically call sites to help indicate current position in backtraces. The two different types of positions are also used to distinguish between step next and step in. Runs all the tests (including debugger tests) as before. Moved the checking for the FLAG_debug_info to one place. I will do the same changes to the ARM codegenerator in a seperate changelist. Review URL: http://codereview.chromium.org/2957 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@335 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
2008-09-18 08:51:43 +00:00
// 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<Object> 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<Object> 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<Code> 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<Code> 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<char*>(&thread_local_), sizeof(ThreadLocal));
to += sizeof(ThreadLocal);
memcpy(to, reinterpret_cast<char*>(&registers_), sizeof(registers_));
ThreadInit();
ASSERT(to <= storage + ArchiveSpacePerThread());
return storage + ArchiveSpacePerThread();
}
char* Debug::RestoreDebug(char* storage) {
char* from = storage;
memcpy(reinterpret_cast<char*>(&thread_local_), from, sizeof(ThreadLocal));
from += sizeof(ThreadLocal);
memcpy(reinterpret_cast<char*>(&registers_), 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<Context> Debug::debug_context_ = Handle<Context>();
Code* Debug::debug_break_return_entry_ = NULL;
Code* Debug::debug_break_return_ = NULL;
void Debug::HandleWeakDebugInfo(v8::Persistent<v8::Value> obj, void* data) {
DebugInfoListNode* node = reinterpret_cast<DebugInfoListNode*>(data);
RemoveDebugInfo(node->debug_info());
#ifdef DEBUG
node = Debug::debug_info_list_;
while (node != NULL) {
ASSERT(node != reinterpret_cast<DebugInfoListNode*>(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<DebugInfo>::cast((GlobalHandles::Create(debug_info)));
GlobalHandles::MakeWeak(reinterpret_cast<Object**>(debug_info_.location()),
this, Debug::HandleWeakDebugInfo);
}
DebugInfoListNode::~DebugInfoListNode() {
GlobalHandles::Destroy(reinterpret_cast<Object**>(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<String> source_code = Bootstrapper::NativesSourceLookup(index);
Vector<const char> name = Natives::GetScriptName(index);
Handle<String> script_name = Factory::NewStringFromAscii(name);
// Compile the script.
bool allow_natives_syntax = FLAG_allow_natives_syntax;
FLAG_allow_natives_syntax = true;
Handle<JSFunction> 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> context = Top::global_context();
bool caught_exception = false;
Handle<JSFunction> function =
Factory::NewFunctionFromBoilerplate(boilerplate, context);
Handle<Object> result =
Execution::TryCall(function, Handle<Object>(context->global()),
0, NULL, &caught_exception);
// Check for caught exceptions.
if (caught_exception) {
Handle<Object> message = MessageHandler::MakeMessageObject(
"error_loading_debugger", NULL, HandleVector<Object>(&result, 1),
Handle<String>());
MessageHandler::ReportMessage(NULL, message);
return false;
}
// Mark this script as native and return successfully.
Handle<Script> script(Script::cast(function->shared()->script()));
script->set_type(Smi::FromInt(SCRIPT_TYPE_NATIVE));
return true;
}
bool Debug::Load() {
// Return if debugger is already loaded.
if (IsLoaded()) return true;
// Bail out if we're already in the process of compiling the native
// JavaScript source code for the debugger.
if (Debugger::compiling_natives() || Debugger::is_loading_debugger())
return false;
Debugger::set_loading_debugger(true);
// Disable breakpoints and interrupts while compiling and running the
// debugger scripts including the context creation code.
DisableBreak disable(true);
PostponeInterruptsScope postpone;
// Create the debugger context.
HandleScope scope;
Handle<Context> context =
Bootstrapper::CreateEnvironment(Handle<Object>::null(),
v8::Handle<ObjectTemplate>(),
NULL);
// Use the debugger context.
SaveContext save;
Top::set_context(*context);
// Expose the builtins object in the debugger context.
Handle<String> key = Factory::LookupAsciiSymbol("builtins");
Handle<GlobalObject> global = Handle<GlobalObject>(context->global());
SetProperty(global, key, Handle<Object>(global->builtins()), NONE);
// Compile the JavaScript for the debugger in the debugger context.
Debugger::set_compiling_natives(true);
bool caught_exception =
!CompileDebuggerScript(Natives::GetIndex("mirror")) ||
!CompileDebuggerScript(Natives::GetIndex("debug"));
Debugger::set_compiling_natives(false);
// Make sure we mark the debugger as not loading before we might
// return.
Debugger::set_loading_debugger(false);
// Check for caught exceptions.
if (caught_exception) return false;
// Debugger loaded.
debug_context_ = Handle<Context>::cast(GlobalHandles::Create(*context));
return true;
}
void Debug::Unload() {
// Return debugger is not loaded.
if (!IsLoaded()) {
return;
}
// Clear debugger context global handle.
GlobalHandles::Destroy(reinterpret_cast<Object**>(debug_context_.location()));
debug_context_ = Handle<Context>();
}
void Debug::Iterate(ObjectVisitor* v) {
#define VISIT(field) v->VisitPointer(bit_cast<Object**, Code**>(&(field)));
VISIT(debug_break_return_entry_);
VISIT(debug_break_return_);
#undef VISIT
}
Object* Debug::Break(Arguments args) {
HandleScope scope;
ASSERT(args.length() == 0);
// Get the top-most JavaScript frame.
JavaScriptFrameIterator it;
JavaScriptFrame* frame = it.frame();
// Just continue if breaks are disabled or debugger cannot be loaded.
if (disable_break() || !Load()) {
SetAfterBreakTarget(frame);
return Heap::undefined_value();
}
// Enter the debugger.
EnterDebugger debugger;
if (debugger.FailedToEnter()) {
return Heap::undefined_value();
}
// Postpone interrupt during breakpoint processing.
PostponeInterruptsScope postpone;
// Get the debug info (create it if it does not exist).
Handle<SharedFunctionInfo> shared =
Handle<SharedFunctionInfo>(JSFunction::cast(frame->function())->shared());
Handle<DebugInfo> debug_info = GetDebugInfo(shared);
// Find the break point where execution has stopped.
BreakLocationIterator break_location_iterator(debug_info,
ALL_BREAK_LOCATIONS);
break_location_iterator.FindBreakLocationFromAddress(frame->pc());
// Check whether step next reached a new statement.
if (!StepNextContinue(&break_location_iterator, frame)) {
// Decrease steps left if performing multiple steps.
if (thread_local_.step_count_ > 0) {
thread_local_.step_count_--;
}
}
// If there is one or more real break points check whether any of these are
// triggered.
Handle<Object> break_points_hit(Heap::undefined_value());
if (break_location_iterator.HasBreakPoint()) {
Handle<Object> break_point_objects =
Handle<Object>(break_location_iterator.BreakPointObjects());
break_points_hit = CheckBreakPoints(break_point_objects);
}
// Notify debugger if a real break point is triggered or if performing single
// stepping with no more steps to perform. Otherwise do another step.
if (!break_points_hit->IsUndefined() ||
(thread_local_.last_step_action_ != StepNone &&
thread_local_.step_count_ == 0)) {
// Clear all current stepping setup.
ClearStepping();
// Notify the debug event listeners.
Debugger::OnDebugBreak(break_points_hit);
} else if (thread_local_.last_step_action_ != StepNone) {
// Hold on to last step action as it is cleared by the call to
// ClearStepping.
StepAction step_action = thread_local_.last_step_action_;
int step_count = thread_local_.step_count_;
// Clear all current stepping setup.
ClearStepping();
// Set up for the remaining steps.
PrepareStep(step_action, step_count);
}
// Install jump to the call address which was overwritten.
SetAfterBreakTarget(frame);
return Heap::undefined_value();
}
// Check the break point objects for whether one or more are actually
// triggered. This function returns a JSArray with the break point objects
// which is triggered.
Handle<Object> Debug::CheckBreakPoints(Handle<Object> break_point_objects) {
int break_points_hit_count = 0;
Handle<JSArray> break_points_hit = Factory::NewJSArray(1);
// If there are multiple break points they are in a FixedArray.
ASSERT(!break_point_objects->IsUndefined());
if (break_point_objects->IsFixedArray()) {
Handle<FixedArray> array(FixedArray::cast(*break_point_objects));
for (int i = 0; i < array->length(); i++) {
Handle<Object> o(array->get(i));
if (CheckBreakPoint(o)) {
break_points_hit->SetElement(break_points_hit_count++, *o);
}
}
} else {
if (CheckBreakPoint(break_point_objects)) {
break_points_hit->SetElement(break_points_hit_count++,
*break_point_objects);
}
}
// Return undefined if no break points where triggered.
if (break_points_hit_count == 0) {
return Factory::undefined_value();
}
return break_points_hit;
}
// Check whether a single break point object is triggered.
bool Debug::CheckBreakPoint(Handle<Object> break_point_object) {
// Ignore check if break point object is not a JSObject.
if (!break_point_object->IsJSObject()) return true;
// Get the function CheckBreakPoint (defined in debug.js).
Handle<JSFunction> check_break_point =
Handle<JSFunction>(JSFunction::cast(
debug_context()->global()->GetProperty(
*Factory::LookupAsciiSymbol("IsBreakPointTriggered"))));
// Get the break id as an object.
Handle<Object> break_id = Factory::NewNumberFromInt(Top::break_id());
// Call HandleBreakPointx.
bool caught_exception = false;
const int argc = 2;
Object** argv[argc] = {
break_id.location(),
reinterpret_cast<Object**>(break_point_object.location())
};
Handle<Object> result = Execution::TryCall(check_break_point,
Top::builtins(), argc, argv,
&caught_exception);
// If exception or non boolean result handle as not triggered
if (caught_exception || !result->IsBoolean()) {
return false;
}
// Return whether the break point is triggered.
return *result == Heap::true_value();
}
// Check whether the function has debug information.
bool Debug::HasDebugInfo(Handle<SharedFunctionInfo> shared) {
return !shared->debug_info()->IsUndefined();
}
// Return the debug info for this function. EnsureDebugInfo must be called
// prior to ensure the debug info has been generated for shared.
Handle<DebugInfo> Debug::GetDebugInfo(Handle<SharedFunctionInfo> shared) {
ASSERT(HasDebugInfo(shared));
return Handle<DebugInfo>(DebugInfo::cast(shared->debug_info()));
}
void Debug::SetBreakPoint(Handle<SharedFunctionInfo> shared,
int source_position,
Handle<Object> break_point_object) {
if (!EnsureDebugInfo(shared)) {
// Return if retrieving debug info failed.
return;
}
Handle<DebugInfo> debug_info = GetDebugInfo(shared);
// Source positions starts with zero.
ASSERT(source_position >= 0);
// Find the break point and change it.
BreakLocationIterator it(debug_info, SOURCE_BREAK_LOCATIONS);
it.FindBreakLocationFromPosition(source_position);
it.SetBreakPoint(break_point_object);
// At least one active break point now.
ASSERT(debug_info->GetBreakPointCount() > 0);
}
void Debug::ClearBreakPoint(Handle<Object> break_point_object) {
DebugInfoListNode* node = debug_info_list_;
while (node != NULL) {
Object* result = DebugInfo::FindBreakPointInfo(node->debug_info(),
break_point_object);
if (!result->IsUndefined()) {
// Get information in the break point.
BreakPointInfo* break_point_info = BreakPointInfo::cast(result);
Handle<DebugInfo> debug_info = node->debug_info();
Handle<SharedFunctionInfo> shared(debug_info->shared());
int source_position = break_point_info->statement_position()->value();
// Source positions starts with zero.
ASSERT(source_position >= 0);
// Find the break point and clear it.
BreakLocationIterator it(debug_info, SOURCE_BREAK_LOCATIONS);
it.FindBreakLocationFromPosition(source_position);
it.ClearBreakPoint(break_point_object);
// If there are no more break points left remove the debug info for this
// function.
if (debug_info->GetBreakPointCount() == 0) {
RemoveDebugInfo(debug_info);
}
return;
}
node = node->next();
}
}
void Debug::FloodWithOneShot(Handle<SharedFunctionInfo> shared) {
// Make sure the function has setup the debug info.
if (!EnsureDebugInfo(shared)) {
// Return if we failed to retrieve the debug info.
return;
}
// Flood the function with break points.
BreakLocationIterator it(GetDebugInfo(shared), ALL_BREAK_LOCATIONS);
while (!it.Done()) {
it.SetOneShot();
it.Next();
}
}
void Debug::FloodHandlerWithOneShot() {
StackFrame::Id id = Top::break_frame_id();
for (JavaScriptFrameIterator it(id); !it.done(); it.Advance()) {
JavaScriptFrame* frame = it.frame();
if (frame->HasHandler()) {
Handle<SharedFunctionInfo> shared =
Handle<SharedFunctionInfo>(
JSFunction::cast(frame->function())->shared());
// Flood the function with the catch block with break points
FloodWithOneShot(shared);
return;
}
}
}
void Debug::ChangeBreakOnException(ExceptionBreakType type, bool enable) {
if (type == BreakUncaughtException) {
break_on_uncaught_exception_ = enable;
} else {
break_on_exception_ = enable;
}
}
void Debug::PrepareStep(StepAction step_action, int step_count) {
HandleScope scope;
ASSERT(Debug::InDebugger());
// Remember this step action and count.
thread_local_.last_step_action_ = step_action;
thread_local_.step_count_ = step_count;
// 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 id = Top::break_frame_id();
JavaScriptFrameIterator frames_it(id);
JavaScriptFrame* frame = frames_it.frame();
// First of all ensure there is one-shot break points in the top handler
// if any.
FloodHandlerWithOneShot();
// If the function on the top frame is unresolved perform step out. This will
// be the case when calling unknown functions 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 = JSFunction::cast(frames_it.frame()->function());
FloodWithOneShot(Handle<SharedFunctionInfo>(function->shared()));
return;
}
// Get the debug info (create it if it does not exist).
Handle<SharedFunctionInfo> shared =
Handle<SharedFunctionInfo>(JSFunction::cast(frame->function())->shared());
if (!EnsureDebugInfo(shared)) {
// Return if ensuring debug info failed.
return;
}
Handle<DebugInfo> debug_info = GetDebugInfo(shared);
// Find the break location where execution has stopped.
BreakLocationIterator it(debug_info, ALL_BREAK_LOCATIONS);
it.FindBreakLocationFromAddress(frame->pc());
// Compute whether or not the target is a call target.
bool is_call_target = false;
if (RelocInfo::IsCodeTarget(it.rinfo()->rmode())) {
Address target = it.rinfo()->target_address();
Code* code = Debug::GetCodeTarget(target);
if (code->is_call_stub()) is_call_target = true;
}
// If this is the last break code target step out is the only possibility.
if (it.IsExit() || step_action == StepOut) {
// Step out: If there is a JavaScript caller frame, we need to
// flood it with breakpoints.
frames_it.Advance();
if (!frames_it.done()) {
// Fill the function to return to with one-shot break points.
JSFunction* function = JSFunction::cast(frames_it.frame()->function());
FloodWithOneShot(Handle<SharedFunctionInfo>(function->shared()));
}
} else if (!(is_call_target || RelocInfo::IsConstructCall(it.rmode())) ||
step_action == StepNext || step_action == StepMin) {
// Step next or step min.
// Fill the current function with one-shot break points.
FloodWithOneShot(shared);
// Remember source position and frame to handle step next.
thread_local_.last_statement_position_ =
debug_info->code()->SourceStatementPosition(frame->pc());
thread_local_.last_fp_ = frame->fp();
} else {
// Fill the current function with one-shot break points even for step in on
// a call target as the function called might be a native function for
// which step in will not stop.
FloodWithOneShot(shared);
// Step in or Step in min
it.PrepareStepIn();
ActivateStepIn(frame);
}
}
// Check whether the current debug break should be reported to the debugger. It
// is used to have step next and step in only report break back to the debugger
// if on a different frame or in a different statement. In some situations
// there will be several break points in the same statement when the code is
// flooded with one-shot break points. This function helps to perform several
// steps before reporting break back to the debugger.
bool Debug::StepNextContinue(BreakLocationIterator* break_location_iterator,
JavaScriptFrame* frame) {
// If the step last action was step next or step in make sure that a new
// statement is hit.
if (thread_local_.last_step_action_ == StepNext ||
thread_local_.last_step_action_ == StepIn) {
// Never continue if returning from function.
if (break_location_iterator->IsExit()) return false;
// Continue if we are still on the same frame and in the same statement.
int current_statement_position =
break_location_iterator->code()->SourceStatementPosition(frame->pc());
return thread_local_.last_fp_ == frame->fp() &&
Defer the writing of the source position data to the relocation information until a possible debug break location is reached. Currently this is call sites with calls to code objects and JS return. Source position information in the code therefore no longer refers to the "first" instruction generated for a given source position (which was not the case defered code anyway) but to the first break location after that source position was passed (again defered code always start with source position information). This doesn't make a difference for the debugger as it will always be stopped only at debug break locations. However, this makes the life of the peep-hole optimizer much easier as many oportunities for posh/pop eliminations where previosly blocked by relocation information already written to the code object. Two types of source positions are still collected. Statement positions indicate the position of the start of the statement leading to this code and (plain) positions indicate other places typically call sites to help indicate current position in backtraces. The two different types of positions are also used to distinguish between step next and step in. Runs all the tests (including debugger tests) as before. Moved the checking for the FLAG_debug_info to one place. I will do the same changes to the ARM codegenerator in a seperate changelist. Review URL: http://codereview.chromium.org/2957 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@335 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
2008-09-18 08:51:43 +00:00
thread_local_.last_statement_position_ == current_statement_position;
}
// No step next action - don't continue.
return false;
}
// Check whether the code object at the specified address is a debug break code
// object.
bool Debug::IsDebugBreak(Address addr) {
Code* code = GetCodeTarget(addr);
return code->ic_state() == DEBUG_BREAK;
}
// Check whether a code stub with the specified major key is a possible break
// point location when looking for source break locations.
bool Debug::IsSourceBreakStub(Code* code) {
CodeStub::Major major_key = code->major_key();
return major_key == CodeStub::CallFunction;
}
// Check whether a code stub with the specified major key is a possible break
// location.
bool Debug::IsBreakStub(Code* code) {
CodeStub::Major major_key = code->major_key();
return major_key == CodeStub::CallFunction ||
major_key == CodeStub::StackCheck;
}
// Find the builtin to use for invoking the debug break
Handle<Code> Debug::FindDebugBreak(RelocInfo* rinfo) {
// Find the builtin debug break function matching the calling convention
// used by the call site.
RelocInfo::Mode mode = rinfo->rmode();
if (RelocInfo::IsCodeTarget(mode)) {
Address target = rinfo->target_address();
Code* code = Debug::GetCodeTarget(target);
if (code->is_inline_cache_stub()) {
if (code->is_call_stub()) {
return ComputeCallDebugBreak(code->arguments_count());
}
if (code->is_load_stub()) {
return Handle<Code>(Builtins::builtin(Builtins::LoadIC_DebugBreak));
}
if (code->is_store_stub()) {
return Handle<Code>(Builtins::builtin(Builtins::StoreIC_DebugBreak));
}
if (code->is_keyed_load_stub()) {
Handle<Code> result =
Handle<Code>(Builtins::builtin(Builtins::KeyedLoadIC_DebugBreak));
return result;
}
if (code->is_keyed_store_stub()) {
Handle<Code> result =
Handle<Code>(Builtins::builtin(Builtins::KeyedStoreIC_DebugBreak));
return result;
}
}
if (RelocInfo::IsConstructCall(mode)) {
Handle<Code> result =
Handle<Code>(Builtins::builtin(Builtins::ConstructCall_DebugBreak));
return result;
}
if (code->kind() == Code::STUB) {
ASSERT(code->major_key() == CodeStub::CallFunction ||
code->major_key() == CodeStub::StackCheck);
Handle<Code> result =
Handle<Code>(Builtins::builtin(Builtins::StubNoRegisters_DebugBreak));
return result;
}
}
UNREACHABLE();
return Handle<Code>::null();
}
// Simple function for returning the source positions for active break points.
Handle<Object> Debug::GetSourceBreakLocations(
Handle<SharedFunctionInfo> shared) {
if (!HasDebugInfo(shared)) return Handle<Object>(Heap::undefined_value());
Handle<DebugInfo> debug_info = GetDebugInfo(shared);
if (debug_info->GetBreakPointCount() == 0) {
return Handle<Object>(Heap::undefined_value());
}
Handle<FixedArray> locations =
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()) {
BreakPointInfo* break_point_info =
BreakPointInfo::cast(debug_info->break_points()->get(i));
if (break_point_info->GetBreakPointCount() > 0) {
locations->set(count++, break_point_info->statement_position());
}
}
}
return locations;
}
void Debug::ClearStepping() {
// Clear the various stepping setup.
ClearOneShot();
ClearStepIn();
ClearStepNext();
// Clear multiple step counter.
thread_local_.step_count_ = 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.
DebugInfoListNode* node = debug_info_list_;
while (node != NULL) {
BreakLocationIterator it(node->debug_info(), ALL_BREAK_LOCATIONS);
while (!it.Done()) {
it.ClearOneShot();
it.Next();
}
node = node->next();
}
}
void Debug::ActivateStepIn(StackFrame* frame) {
thread_local_.step_into_fp_ = frame->fp();
}
void Debug::ClearStepIn() {
thread_local_.step_into_fp_ = 0;
}
void Debug::ClearStepNext() {
thread_local_.last_step_action_ = StepNone;
thread_local_.last_statement_position_ = RelocInfo::kNoPosition;
thread_local_.last_fp_ = 0;
}
bool Debug::EnsureCompiled(Handle<SharedFunctionInfo> shared) {
if (shared->is_compiled()) return true;
return CompileLazyShared(shared, CLEAR_EXCEPTION, 0);
}
// Ensures the debug information is present for shared.
bool Debug::EnsureDebugInfo(Handle<SharedFunctionInfo> shared) {
// Return if we already have the debug info for shared.
if (HasDebugInfo(shared)) return true;
// Ensure shared in compiled. Return false if this failed.
if (!EnsureCompiled(shared)) return false;
// Create the debug info object.
Handle<DebugInfo> debug_info = 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;
// Now there is at least one break point.
has_break_points_ = true;
return true;
}
void Debug::RemoveDebugInfo(Handle<DebugInfo> debug_info) {
ASSERT(debug_info_list_ != NULL);
// 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() == *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());
}
current->debug_info()->shared()->set_debug_info(Heap::undefined_value());
delete current;
// If there are no more debug info objects there are not more break
// points.
has_break_points_ = debug_info_list_ != NULL;
return;
}
// Move to next in list.
prev = current;
current = current->next();
}
UNREACHABLE();
}
void Debug::SetAfterBreakTarget(JavaScriptFrame* frame) {
// Get the executing function in which the debug break occurred.
Handle<SharedFunctionInfo> shared =
Handle<SharedFunctionInfo>(JSFunction::cast(frame->function())->shared());
if (!EnsureDebugInfo(shared)) {
// Return if we failed to retrieve the debug info.
return;
}
Handle<DebugInfo> debug_info = GetDebugInfo(shared);
Handle<Code> code(debug_info->code());
Handle<Code> original_code(debug_info->original_code());
#ifdef DEBUG
// Get the code which is actually executing.
Handle<Code> frame_code(frame->FindCode());
ASSERT(frame_code.is_identical_to(code));
#endif
// Find the call address in the running code. This address holds the call to
// either a DebugBreakXXX or to the debug break return entry code if the
// break point is still active after processing the break point.
Address addr = frame->pc() - Assembler::kTargetAddrToReturnAddrDist;
// Check if the location is at JS exit.
bool at_js_exit = false;
RelocIterator it(debug_info->code());
while (!it.done()) {
if (RelocInfo::IsJSReturn(it.rinfo()->rmode())) {
at_js_exit = it.rinfo()->pc() == addr - 1;
}
it.next();
}
// Handle the jump to continue execution after break point depending on the
// break location.
if (at_js_exit) {
// First check if the call in the code is still the debug break return
// entry code. If it is the break point is still active. If not the break
// point was removed during break point processing.
if (Assembler::target_address_at(addr) ==
debug_break_return_entry()->entry()) {
// Break point still active. Jump to the corresponding place in the
// original code.
addr += original_code->instruction_start() - code->instruction_start();
}
// Move one byte back to where the call instruction was placed.
thread_local_.after_break_target_ = addr - 1;
} else {
// Check if there still is a debug break call at the target address. If the
// break point has been removed it will have disappeared. If it have
// disappeared don't try to look in the original code as the running code
// will have the right address. This takes care of the case where the last
// break point is removed from the function and therefore no "original code"
// is available. If the debug break call is still there find the address in
// the original code.
if (IsDebugBreak(Assembler::target_address_at(addr))) {
// If the break point is still there find the call address which was
// overwritten in the original code by the call to DebugBreakXXX.
// Find the corresponding address in the original code.
addr += original_code->instruction_start() - code->instruction_start();
}
// Install jump to the call address in the original code. This will be the
// call which was overwritten by the call to DebugBreakXXX.
thread_local_.after_break_target_ = Assembler::target_address_at(addr);
}
}
Code* Debug::GetCodeTarget(Address target) {
// Maybe this can be refactored with the stuff in ic-inl.h?
Code* result =
Code::cast(HeapObject::FromAddress(target - Code::kHeaderSize));
return result;
}
bool Debug::IsDebugGlobal(GlobalObject* global) {
return IsLoaded() && global == Debug::debug_context()->global();
}
bool Debugger::debugger_active_ = false;
bool Debugger::compiling_natives_ = false;
bool Debugger::is_loading_debugger_ = false;
DebugMessageThread* Debugger::message_thread_ = NULL;
v8::DebugMessageHandler Debugger::debug_message_handler_ = NULL;
void* Debugger::debug_message_handler_data_ = NULL;
Handle<Object> Debugger::MakeJSObject(Vector<const char> constructor_name,
int argc, Object*** argv,
bool* caught_exception) {
ASSERT(Top::context() == *Debug::debug_context());
// Create the execution state object.
Handle<String> constructor_str = Factory::LookupSymbol(constructor_name);
Handle<Object> constructor(Top::global()->GetProperty(*constructor_str));
ASSERT(constructor->IsJSFunction());
if (!constructor->IsJSFunction()) {
*caught_exception = true;
return Factory::undefined_value();
}
Handle<Object> js_object = Execution::TryCall(
Handle<JSFunction>::cast(constructor),
Handle<JSObject>(Debug::debug_context()->global()), argc, argv,
caught_exception);
return js_object;
}
Handle<Object> Debugger::MakeExecutionState(bool* caught_exception) {
// Create the execution state object.
Handle<Object> break_id = Factory::NewNumberFromInt(Top::break_id());
const int argc = 1;
Object** argv[argc] = { break_id.location() };
return MakeJSObject(CStrVector("MakeExecutionState"),
argc, argv, caught_exception);
}
Handle<Object> Debugger::MakeBreakEvent(Handle<Object> exec_state,
Handle<Object> break_points_hit,
bool* caught_exception) {
// Create the new break event object.
const int argc = 2;
Object** argv[argc] = { exec_state.location(),
break_points_hit.location() };
return MakeJSObject(CStrVector("MakeBreakEvent"),
argc,
argv,
caught_exception);
}
Handle<Object> Debugger::MakeExceptionEvent(Handle<Object> exec_state,
Handle<Object> exception,
bool uncaught,
bool* caught_exception) {
// Create the new exception event object.
const int argc = 3;
Object** argv[argc] = { exec_state.location(),
exception.location(),
uncaught ? Factory::true_value().location() :
Factory::false_value().location()};
return MakeJSObject(CStrVector("MakeExceptionEvent"),
argc, argv, caught_exception);
}
Handle<Object> Debugger::MakeNewFunctionEvent(Handle<Object> function,
bool* caught_exception) {
// Create the new function event object.
const int argc = 1;
Object** argv[argc] = { function.location() };
return MakeJSObject(CStrVector("MakeNewFunctionEvent"),
argc, argv, caught_exception);
}
Handle<Object> Debugger::MakeCompileEvent(Handle<Script> script,
Handle<Object> script_function,
bool* caught_exception) {
// Create the compile event object.
Handle<Object> exec_state = MakeExecutionState(caught_exception);
Handle<Object> script_source(script->source());
Handle<Object> script_name(script->name());
const int argc = 3;
Object** argv[argc] = { script_source.location(),
script_name.location(),
script_function.location() };
return MakeJSObject(CStrVector("MakeCompileEvent"),
argc,
argv,
caught_exception);
}
void Debugger::OnException(Handle<Object> exception, bool uncaught) {
HandleScope scope;
// Bail out based on state or if there is no listener for this event
if (Debug::InDebugger()) return;
if (!Debugger::EventActive(v8::Exception)) return;
// Bail out if exception breaks are not active
if (uncaught) {
// Uncaught exceptions are reported by either flags.
if (!(Debug::break_on_uncaught_exception() ||
Debug::break_on_exception())) return;
} else {
// Caught exceptions are reported is activated.
if (!Debug::break_on_exception()) return;
}
// Enter the debugger.
EnterDebugger debugger;
if (debugger.FailedToEnter()) return;
// Clear all current stepping setup.
Debug::ClearStepping();
// Create the event data object.
bool caught_exception = false;
Handle<Object> exec_state = MakeExecutionState(&caught_exception);
Handle<Object> event_data;
if (!caught_exception) {
event_data = MakeExceptionEvent(exec_state, exception, uncaught,
&caught_exception);
}
// Bail out and don't call debugger if exception.
if (caught_exception) {
return;
}
// Process debug event
ProcessDebugEvent(v8::Exception, event_data);
// Return to continue execution from where the exception was thrown.
}
void Debugger::OnDebugBreak(Handle<Object> break_points_hit) {
HandleScope scope;
// Debugger has already been entered by caller.
ASSERT(Top::context() == *Debug::debug_context());
// Bail out if there is no listener for this event
if (!Debugger::EventActive(v8::Break)) return;
// Debugger must be entered in advance.
ASSERT(Top::context() == *Debug::debug_context());
// Create the event data object.
bool caught_exception = false;
Handle<Object> exec_state = MakeExecutionState(&caught_exception);
Handle<Object> event_data;
if (!caught_exception) {
event_data = MakeBreakEvent(exec_state, break_points_hit,
&caught_exception);
}
// Bail out and don't call debugger if exception.
if (caught_exception) {
return;
}
// Process debug event
ProcessDebugEvent(v8::Break, event_data);
}
void Debugger::OnBeforeCompile(Handle<Script> script) {
HandleScope scope;
// Bail out based on state or if there is no listener for this event
if (Debug::InDebugger()) return;
if (compiling_natives()) return;
if (!EventActive(v8::BeforeCompile)) return;
// Enter the debugger.
EnterDebugger debugger;
if (debugger.FailedToEnter()) return;
// Create the event data object.
bool caught_exception = false;
Handle<Object> event_data = MakeCompileEvent(script,
Factory::undefined_value(),
&caught_exception);
// Bail out and don't call debugger if exception.
if (caught_exception) {
return;
}
// Process debug event
ProcessDebugEvent(v8::BeforeCompile, event_data);
}
// Handle debugger actions when a new script is compiled.
void Debugger::OnAfterCompile(Handle<Script> script, Handle<JSFunction> fun) {
HandleScope scope;
// No compile events while compiling natives.
if (compiling_natives()) return;
// No more to do if not debugging.
if (!debugger_active()) return;
// Enter the debugger.
EnterDebugger debugger;
if (debugger.FailedToEnter()) return;
// If debugging there might be script break points registered for this
// script. Make sure that these break points are set.
// Get the function UpdateScriptBreakPoints (defined in debug-delay.js).
Handle<Object> update_script_break_points =
Handle<Object>(Debug::debug_context()->global()->GetProperty(
*Factory::LookupAsciiSymbol("UpdateScriptBreakPoints")));
if (!update_script_break_points->IsJSFunction()) {
return;
}
ASSERT(update_script_break_points->IsJSFunction());
// Wrap the script object in a proper JS object before passing it
// to JavaScript.
Handle<JSValue> wrapper = GetScriptWrapper(script);
// Call UpdateScriptBreakPoints expect no exceptions.
bool caught_exception = false;
const int argc = 1;
Object** argv[argc] = { reinterpret_cast<Object**>(wrapper.location()) };
Handle<Object> result = Execution::TryCall(
Handle<JSFunction>::cast(update_script_break_points),
Top::builtins(), argc, argv,
&caught_exception);
if (caught_exception) {
return;
}
// Bail out based on state or if there is no listener for this event
if (Debug::InDebugger()) return;
if (!Debugger::EventActive(v8::AfterCompile)) return;
// Create the compile state object.
Handle<Object> event_data = MakeCompileEvent(script,
Factory::undefined_value(),
&caught_exception);
// Bail out and don't call debugger if exception.
if (caught_exception) {
return;
}
// Process debug event
ProcessDebugEvent(v8::AfterCompile, event_data);
}
void Debugger::OnNewFunction(Handle<JSFunction> function) {
return;
HandleScope scope;
// Bail out based on state or if there is no listener for this event
if (Debug::InDebugger()) return;
if (compiling_natives()) return;
if (!Debugger::EventActive(v8::NewFunction)) return;
// Enter the debugger.
EnterDebugger debugger;
if (debugger.FailedToEnter()) return;
// Create the event object.
bool caught_exception = false;
Handle<Object> event_data = MakeNewFunctionEvent(function, &caught_exception);
// Bail out and don't call debugger if exception.
if (caught_exception) {
return;
}
// Process debug event.
ProcessDebugEvent(v8::NewFunction, event_data);
}
void Debugger::ProcessDebugEvent(v8::DebugEvent event,
Handle<Object> event_data) {
// Create the execution state.
bool caught_exception = false;
Handle<Object> exec_state = MakeExecutionState(&caught_exception);
if (caught_exception) {
return;
}
// First notify the builtin debugger.
if (message_thread_ != NULL) {
message_thread_->DebugEvent(event, exec_state, event_data);
}
// Notify registered debug event listeners. The list can contain both C and
// JavaScript functions.
v8::NeanderArray listeners(Factory::debug_event_listeners());
int length = listeners.length();
for (int i = 0; i < length; i++) {
if (listeners.get(i)->IsUndefined()) continue; // Skip deleted ones.
v8::NeanderObject listener(JSObject::cast(listeners.get(i)));
Handle<Object> callback_data(listener.get(1));
if (listener.get(0)->IsProxy()) {
// C debug event listener.
Handle<Proxy> callback_obj(Proxy::cast(listener.get(0)));
v8::DebugEventCallback callback =
FUNCTION_CAST<v8::DebugEventCallback>(callback_obj->proxy());
callback(event,
v8::Utils::ToLocal(Handle<JSObject>::cast(exec_state)),
v8::Utils::ToLocal(Handle<JSObject>::cast(event_data)),
v8::Utils::ToLocal(callback_data));
} else {
// JavaScript debug event listener.
ASSERT(listener.get(0)->IsJSFunction());
Handle<JSFunction> fun(JSFunction::cast(listener.get(0)));
// Invoke the JavaScript debug event listener.
const int argc = 4;
Object** argv[argc] = { Handle<Object>(Smi::FromInt(event)).location(),
exec_state.location(),
event_data.location(),
callback_data.location() };
Handle<Object> result = Execution::TryCall(fun, Top::global(),
argc, argv, &caught_exception);
if (caught_exception) {
// Silently ignore exceptions from debug event listeners.
}
}
}
}
void Debugger::SetMessageHandler(v8::DebugMessageHandler handler, void* data) {
debug_message_handler_ = handler;
debug_message_handler_data_ = data;
if (!message_thread_) {
message_thread_ = new DebugMessageThread();
message_thread_->Start();
}
UpdateActiveDebugger();
}
// Posts an output message from the debugger to the debug_message_handler
// callback. This callback is part of the public API. Messages are
// kept internally as Vector<uint16_t> strings, which are allocated in various
// places and deallocated by the calling function sometime after this call.
void Debugger::SendMessage(Vector< uint16_t> message) {
if (debug_message_handler_ != NULL) {
debug_message_handler_(message.start(), message.length(),
debug_message_handler_data_);
}
}
void Debugger::ProcessCommand(Vector<const uint16_t> command) {
if (message_thread_ != NULL) {
message_thread_->ProcessCommand(
Vector<uint16_t>(const_cast<uint16_t *>(command.start()),
command.length()));
}
}
void Debugger::UpdateActiveDebugger() {
v8::NeanderArray listeners(Factory::debug_event_listeners());
int length = listeners.length();
bool active_listener = false;
for (int i = 0; i < length && !active_listener; i++) {
active_listener = !listeners.get(i)->IsUndefined();
}
set_debugger_active((Debugger::message_thread_ != NULL &&
Debugger::debug_message_handler_ != NULL) ||
active_listener);
if (!debugger_active() && message_thread_)
message_thread_->OnDebuggerInactive();
}
DebugMessageThread::DebugMessageThread()
: host_running_(true),
command_queue_(kQueueInitialSize),
message_queue_(kQueueInitialSize) {
command_received_ = OS::CreateSemaphore(0);
message_received_ = OS::CreateSemaphore(0);
}
// Does not free resources held by DebugMessageThread
// because this cannot be done thread-safely.
DebugMessageThread::~DebugMessageThread() {
}
// Puts an event coming from V8 on the queue. Creates
// a copy of the JSON formatted event string managed by the V8.
// Called by the V8 thread.
// The new copy of the event string is destroyed in Run().
void DebugMessageThread::SendMessage(Vector<uint16_t> message) {
Vector<uint16_t> message_copy = message.Clone();
Logger::DebugTag("Put message on event message_queue.");
message_queue_.Put(message_copy);
message_received_->Signal();
}
void DebugMessageThread::SetEventJSONFromEvent(Handle<Object> event_data) {
v8::HandleScope scope;
// Call toJSONProtocol on the debug event object.
v8::Local<v8::Object> api_event_data =
v8::Utils::ToLocal(Handle<JSObject>::cast(event_data));
v8::Local<v8::String> fun_name = v8::String::New("toJSONProtocol");
v8::Local<v8::Function> fun =
v8::Function::Cast(*api_event_data->Get(fun_name));
v8::TryCatch try_catch;
v8::Local<v8::Value> json_event = *fun->Call(api_event_data, 0, NULL);
v8::Local<v8::String> json_event_string;
if (!try_catch.HasCaught()) {
if (!json_event->IsUndefined()) {
json_event_string = json_event->ToString();
if (FLAG_trace_debug_json) {
PrintLn(json_event_string);
}
v8::String::Value val(json_event_string);
Vector<uint16_t> str(reinterpret_cast<uint16_t*>(*val),
json_event_string->Length());
SendMessage(str);
} else {
SendMessage(Vector<uint16_t>::empty());
}
} else {
PrintLn(try_catch.Exception());
SendMessage(Vector<uint16_t>::empty());
}
}
void DebugMessageThread::Run() {
// Sends debug events to an installed debugger message callback.
while (true) {
// Wait and Get are paired so that semaphore count equals queue length.
message_received_->Wait();
Logger::DebugTag("Get message from event message_queue.");
Vector<uint16_t> message = message_queue_.Get();
if (message.length() > 0) {
Debugger::SendMessage(message);
}
}
}
// This method is called by the V8 thread whenever a debug event occurs in
// the VM.
void DebugMessageThread::DebugEvent(v8::DebugEvent event,
Handle<Object> exec_state,
Handle<Object> event_data) {
if (!Debug::Load()) return;
// Process the individual events.
bool interactive = false;
switch (event) {
case v8::Break:
interactive = true; // Break event is always interactive
break;
case v8::Exception:
interactive = true; // Exception event is always interactive
break;
case v8::BeforeCompile:
break;
case v8::AfterCompile:
break;
case v8::NewFunction:
break;
default:
UNREACHABLE();
}
// Done if not interactive.
if (!interactive) return;
// Get the DebugCommandProcessor.
v8::Local<v8::Object> api_exec_state =
v8::Utils::ToLocal(Handle<JSObject>::cast(exec_state));
v8::Local<v8::String> fun_name =
v8::String::New("debugCommandProcessor");
v8::Local<v8::Function> fun =
v8::Function::Cast(*api_exec_state->Get(fun_name));
v8::TryCatch try_catch;
v8::Local<v8::Object> cmd_processor =
v8::Object::Cast(*fun->Call(api_exec_state, 0, NULL));
if (try_catch.HasCaught()) {
PrintLn(try_catch.Exception());
return;
}
// Notify the debugger that a debug event has occurred.
host_running_ = false;
SetEventJSONFromEvent(event_data);
// Wait for requests from the debugger.
while (true) {
command_received_->Wait();
Logger::DebugTag("Got request from command queue, in interactive loop.");
Vector<uint16_t> command = command_queue_.Get();
ASSERT(!host_running_);
if (!Debugger::debugger_active()) {
host_running_ = true;
return;
}
// Invoke the JavaScript to process the debug request.
v8::Local<v8::String> fun_name;
v8::Local<v8::Function> fun;
v8::Local<v8::Value> request;
v8::TryCatch try_catch;
fun_name = v8::String::New("processDebugRequest");
fun = v8::Function::Cast(*cmd_processor->Get(fun_name));
request = v8::String::New(reinterpret_cast<uint16_t*>(command.start()),
command.length());
static const int kArgc = 1;
v8::Handle<Value> argv[kArgc] = { request };
v8::Local<v8::Value> response_val = fun->Call(cmd_processor, kArgc, argv);
// Get the response.
v8::Local<v8::String> response;
bool running = false;
if (!try_catch.HasCaught()) {
// Get response string.
if (!response_val->IsUndefined()) {
response = v8::String::Cast(*response_val);
} else {
response = v8::String::New("");
}
// Log the JSON request/response.
if (FLAG_trace_debug_json) {
PrintLn(request);
PrintLn(response);
}
// Get the running state.
fun_name = v8::String::New("isRunning");
fun = v8::Function::Cast(*cmd_processor->Get(fun_name));
static const int kArgc = 1;
v8::Handle<Value> argv[kArgc] = { response };
v8::Local<v8::Value> running_val = fun->Call(cmd_processor, kArgc, argv);
if (!try_catch.HasCaught()) {
running = running_val->ToBoolean()->Value();
}
} else {
// In case of failure the result text is the exception text.
response = try_catch.Exception()->ToString();
}
// Convert text result to C string.
v8::String::Value val(response);
Vector<uint16_t> str(reinterpret_cast<uint16_t*>(*val),
response->Length());
// Set host_running_ correctly for nested debugger evaluations.
host_running_ = running;
// Return the result.
SendMessage(str);
// Return from debug event processing is VM should be running.
if (running) {
return;
}
}
}
// 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. This is where the API client hands off
// processing of the command to the DebugMessageThread thread.
// The new copy of the command is destroyed in HandleCommand().
void DebugMessageThread::ProcessCommand(Vector<uint16_t> command) {
Vector<uint16_t> command_copy = command.Clone();
Logger::DebugTag("Put command on command_queue.");
command_queue_.Put(command_copy);
command_received_->Signal();
}
void DebugMessageThread::OnDebuggerInactive() {
// Send an empty command to the debugger if in a break to make JavaScript run
// again if the debugger is closed.
if (!host_running_) {
ProcessCommand(Vector<uint16_t>::empty());
}
}
MessageQueue::MessageQueue(int size) : start_(0), end_(0), size_(size) {
messages_ = NewArray<Vector<uint16_t> >(size);
}
MessageQueue::~MessageQueue() {
DeleteArray(messages_);
}
Vector<uint16_t> MessageQueue::Get() {
ASSERT(!IsEmpty());
int result = start_;
start_ = (start_ + 1) % size_;
return messages_[result];
}
void MessageQueue::Put(const Vector<uint16_t>& message) {
if ((end_ + 1) % size_ == start_) {
Expand();
}
messages_[end_] = message;
end_ = (end_ + 1) % size_;
}
void MessageQueue::Expand() {
MessageQueue new_queue(size_ * 2);
while (!IsEmpty()) {
new_queue.Put(Get());
}
Vector<uint16_t>* array_to_free = messages_;
*this = new_queue;
new_queue.messages_ = array_to_free;
// Automatic destructor called on new_queue, freeing array_to_free.
}
LockingMessageQueue::LockingMessageQueue(int size) : queue_(size) {
lock_ = OS::CreateMutex();
}
LockingMessageQueue::~LockingMessageQueue() {
delete lock_;
}
bool LockingMessageQueue::IsEmpty() const {
ScopedLock sl(lock_);
return queue_.IsEmpty();
}
Vector<uint16_t> LockingMessageQueue::Get() {
ScopedLock sl(lock_);
Vector<uint16_t> result = queue_.Get();
Logger::DebugEvent("Get", result);
return result;
}
void LockingMessageQueue::Put(const Vector<uint16_t>& message) {
ScopedLock sl(lock_);
queue_.Put(message);
Logger::DebugEvent("Put", message);
}
void LockingMessageQueue::Clear() {
ScopedLock sl(lock_);
queue_.Clear();
}
} } // namespace v8::internal