Commit 2957881a authored by sgjesse@chromium.org's avatar sgjesse@chromium.org

Misc debugger enhancements and bug fixes.

1. Added gdb style debugger commands (and their shortcuts) for d8.
These include:
- s[tep] : step into the current statement.
- s[tep]i[n]: step into the current statement with the minimum step.
- n[ext] : step to the next statement.
- fin[ish] : step out of the current function.
- cond : setting conditions on breakpoints.
- d[elete] : deletes breakpoints.
- en[able]|dis[able]: enables/disables breakpoints including
exception breakpoints.
- ignore : ignores a breakpoint for a specified period.
- inf[o] ar[gs] : info on arguments of the current function.
- inf[o] lo[cals] : info on local vars of the current function.
- inf[o] br[eakpoints] : info on breakpoints.
- l[ist] : similar to source, but allows the user to continually
dump subsequent lines of source code either in the
forward or backward direction.
- quit / exit / disconnect : terminates the remote debugger
session.

NOTE: Active breakpoints will automatically be disabled when
the remote debugger detaches. This allows v8 to continue to
run without worrying about a loss of a debugger session.

2. Added support for breaking the debugger by simply typing ENTER.
The break command is now optional.

3. Once the debugger is broken, the user can now just type ENTER
to repeat the last command. This is useful to functionality that
needs to be invoked repeatedly e.g. step, list.

4. Added more verbose descriptions in d8's help.

5. Fixed a line and column number offset bug in the listing of breakpoint
line and column numbers.

6. Added a gc command to allow GCs to be requested from the debugger
interface. The plumbing for requesting different types of GCs is
there, but the underlying implementation currently only triggers a
full mark-compact GC. The command also returns the before and after
sizes of the heap.

7. Added trace json, and flags commands that are not published in help.
trace json is used for tracing the debugger packets send from and
received by d8. flags is for setting v8 flags. These are useful for
people debugging v8 itself, but not necessarily users of v8.

8. Added the ability to enable and disable break on all / uncaught
exceptions in to d8.

9. Added a fix to prevent the Debugger Agent from being re-instantiated
if one already exists.

10. Added the ability to filter results of the script command by matching
text or numbers on the results.

11. Added v8 flags to enable/disable the sending of debugger BeforeCompile,
AfterCompile, and ScriptCollected events.

12. Fixed some undefined value bugs that resulted in v8 or the debugger
failing.

13. Added a few minor WEBOS__ customizations (analogous to ANDROID
customizations).

Patch by Mark Lam from Hewlett-Packard Development Company, LP

Review URL: http://codereview.chromium.org/5980006
Review URL: http://codereview.chromium.org/6086010


git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@6200 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent 90445b33
This diff is collapsed.
......@@ -27,9 +27,11 @@
#include "v8.h"
#include "debug.h"
#include "debug-agent.h"
#ifdef ENABLE_DEBUGGER_SUPPORT
namespace v8 {
namespace internal {
......@@ -167,22 +169,33 @@ void DebuggerAgentSession::Run() {
while (true) {
// Read data from the debugger front end.
SmartPointer<char> message = DebuggerAgentUtil::ReceiveMessage(client_);
if (*message == NULL) {
// Session is closed.
agent_->OnSessionClosed(this);
return;
const char* msg = *message;
bool is_closing_session = (msg == NULL);
if (msg == NULL) {
// If we lost the connection, then simulate a disconnect msg:
msg = "{\"seq\":1,\"type\":\"request\",\"command\":\"disconnect\"}";
} else {
// Check if we're getting a disconnect request:
const char* disconnectRequestStr =
"\"type\":\"request\",\"command\":\"disconnect\"}";
const char* result = strstr(msg, disconnectRequestStr);
if (result != NULL) {
is_closing_session = true;
}
}
// Convert UTF-8 to UTF-16.
unibrow::Utf8InputBuffer<> buf(*message,
StrLength(*message));
unibrow::Utf8InputBuffer<> buf(msg, StrLength(msg));
int len = 0;
while (buf.has_more()) {
buf.GetNext();
len++;
}
ScopedVector<int16_t> temp(len + 1);
buf.Reset(*message, StrLength(*message));
buf.Reset(msg, StrLength(msg));
for (int i = 0; i < len; i++) {
temp[i] = buf.GetNext();
}
......@@ -190,6 +203,12 @@ void DebuggerAgentSession::Run() {
// Send the request received to the debugger.
v8::Debug::SendCommand(reinterpret_cast<const uint16_t *>(temp.start()),
len);
if (is_closing_session) {
// Session is closed.
agent_->OnSessionClosed(this);
return;
}
}
}
......
......@@ -654,13 +654,19 @@ Debug.setBreakPoint = function(func, opt_line, opt_column, opt_condition) {
Debug.enableBreakPoint = function(break_point_number) {
var break_point = this.findBreakPoint(break_point_number, false);
break_point.enable();
// Only enable if the breakpoint hasn't been deleted:
if (break_point) {
break_point.enable();
}
};
Debug.disableBreakPoint = function(break_point_number) {
var break_point = this.findBreakPoint(break_point_number, false);
break_point.disable();
// Only enable if the breakpoint hasn't been deleted:
if (break_point) {
break_point.disable();
}
};
......@@ -701,6 +707,17 @@ Debug.clearAllBreakPoints = function() {
};
Debug.disableAllBreakPoints = function() {
// Disable all user defined breakpoints:
for (var i = 1; i < next_break_point_number; i++) {
Debug.disableBreakPoint(i);
}
// Disable all exception breakpoints:
%ChangeBreakOnException(Debug.ExceptionBreak.Caught, false);
%ChangeBreakOnException(Debug.ExceptionBreak.Uncaught, false);
};
Debug.findScriptBreakPoint = function(break_point_number, remove) {
var script_break_point;
for (var i = 0; i < script_break_points.length; i++) {
......@@ -1341,6 +1358,10 @@ DebugCommandProcessor.prototype.processDebugJSONRequest = function(json_request)
this.clearBreakPointRequest_(request, response);
} else if (request.command == 'clearbreakpointgroup') {
this.clearBreakPointGroupRequest_(request, response);
} else if (request.command == 'disconnect') {
this.disconnectRequest_(request, response);
} else if (request.command == 'setexceptionbreak') {
this.setExceptionBreakRequest_(request, response);
} else if (request.command == 'listbreakpoints') {
this.listBreakpointsRequest_(request, response);
} else if (request.command == 'backtrace') {
......@@ -1373,6 +1394,13 @@ DebugCommandProcessor.prototype.processDebugJSONRequest = function(json_request)
this.changeLiveRequest_(request, response);
} else if (request.command == 'flags') {
this.debuggerFlagsRequest_(request, response);
} else if (request.command == 'v8flags') {
this.v8FlagsRequest_(request, response);
// GC tools:
} else if (request.command == 'gc') {
this.gcRequest_(request, response);
} else {
throw new Error('Unknown command "' + request.command + '" in request');
}
......@@ -1690,7 +1718,63 @@ DebugCommandProcessor.prototype.listBreakpointsRequest_ = function(request, resp
array.push(description);
}
response.body = { breakpoints: array }
response.body = {
breakpoints: array,
breakOnExceptions: Debug.isBreakOnException(),
breakOnUncaughtExceptions: Debug.isBreakOnUncaughtException()
}
}
DebugCommandProcessor.prototype.disconnectRequest_ =
function(request, response) {
Debug.disableAllBreakPoints();
this.continueRequest_(request, response);
}
DebugCommandProcessor.prototype.setExceptionBreakRequest_ =
function(request, response) {
// Check for legal request.
if (!request.arguments) {
response.failed('Missing arguments');
return;
}
// Pull out and check the 'type' argument:
var type = request.arguments.type;
if (!type) {
response.failed('Missing argument "type"');
return;
}
// Initialize the default value of enable:
var enabled;
if (type == 'all') {
enabled = !Debug.isBreakOnException();
} else if (type == 'uncaught') {
enabled = !Debug.isBreakOnUncaughtException();
}
// Pull out and check the 'enabled' argument if present:
if (!IS_UNDEFINED(request.arguments.enabled)) {
enabled = request.arguments.enabled;
if ((enabled != true) && (enabled != false)) {
response.failed('Illegal value for "enabled":"' + enabled + '"');
}
}
// Now set the exception break state:
if (type == 'all') {
%ChangeBreakOnException(Debug.ExceptionBreak.Caught, enabled);
} else if (type == 'uncaught') {
%ChangeBreakOnException(Debug.ExceptionBreak.Uncaught, enabled);
} else {
response.failed('Unknown "type":"' + type + '"');
}
// Add the cleared break point number to the response.
response.body = { 'type': type, 'enabled': enabled };
}
......@@ -2047,6 +2131,16 @@ DebugCommandProcessor.prototype.scriptsRequest_ = function(request, response) {
idsToInclude[ids[i]] = true;
}
}
var filterStr = null;
var filterNum = null;
if (!IS_UNDEFINED(request.arguments.filter)) {
var num = %ToNumber(request.arguments.filter);
if (!isNaN(num)) {
filterNum = num;
}
filterStr = request.arguments.filter;
}
}
// Collect all scripts in the heap.
......@@ -2058,6 +2152,21 @@ DebugCommandProcessor.prototype.scriptsRequest_ = function(request, response) {
if (idsToInclude && !idsToInclude[scripts[i].id]) {
continue;
}
if (filterStr || filterNum) {
var script = scripts[i];
var found = false;
if (filterNum && !found) {
if (script.id && script.id === filterNum) {
found = true;
}
}
if (filterStr && !found) {
if (script.name && script.name.indexOf(filterStr) >= 0) {
found = true;
}
}
if (!found) continue;
}
if (types & ScriptTypeFlag(scripts[i].type)) {
response.body.push(MakeMirror(scripts[i]));
}
......@@ -2196,6 +2305,27 @@ DebugCommandProcessor.prototype.debuggerFlagsRequest_ = function(request,
}
DebugCommandProcessor.prototype.v8FlagsRequest_ = function(request, response) {
var flags = request.arguments.flags;
if (!flags) flags = '';
%SetFlags(flags);
};
DebugCommandProcessor.prototype.gcRequest_ = function(request, response) {
var type = request.arguments.type;
if (!type) type = 'all';
var before = %GetHeapUsage();
%CollectGarbage(type);
var after = %GetHeapUsage();
response.body = { "before": before, "after": after };
};
// Check whether the previously processed command caused the VM to become
// running.
DebugCommandProcessor.prototype.isRunning = function() {
......
......@@ -622,7 +622,7 @@ bool Debug::disable_break_ = false;
// Default call debugger on uncaught exception.
bool Debug::break_on_exception_ = false;
bool Debug::break_on_uncaught_exception_ = true;
bool Debug::break_on_uncaught_exception_ = false;
Handle<Context> Debug::debug_context_ = Handle<Context>();
Code* Debug::debug_break_return_ = NULL;
......@@ -2740,8 +2740,10 @@ bool Debugger::StartAgent(const char* name, int port,
}
if (Socket::Setup()) {
agent_ = new DebuggerAgent(name, port);
agent_->Start();
if (agent_ == NULL) {
agent_ = new DebuggerAgent(name, port);
agent_->Start();
}
return true;
}
......
......@@ -32,6 +32,7 @@
#include "debug-agent.h"
#include "execution.h"
#include "factory.h"
#include "flags.h"
#include "hashmap.h"
#include "platform.h"
#include "string-stream.h"
......@@ -772,6 +773,15 @@ class Debugger {
}
}
if (((event == v8::BeforeCompile) || (event == v8::AfterCompile)) &&
!FLAG_debug_compile_events) {
return false;
} else if ((event == v8::ScriptCollected) &&
!FLAG_debug_script_collected_events) {
return false;
}
// Currently argument event is not used.
return !compiling_natives_ && Debugger::IsDebuggerActive();
}
......
......@@ -355,6 +355,16 @@ DEFINE_string(map_counters, NULL, "Map counters to a file")
DEFINE_args(js_arguments, JSArguments(),
"Pass all remaining arguments to the script. Alias for \"--\".")
#if defined(WEBOS__)
DEFINE_bool(debug_compile_events, false, "Enable debugger compile events")
DEFINE_bool(debug_script_collected_events, false,
"Enable debugger script collected events")
#else
DEFINE_bool(debug_compile_events, true, "Enable debugger compile events")
DEFINE_bool(debug_script_collected_events, true,
"Enable debugger script collected events")
#endif
//
// Debug only flags
//
......
......@@ -10406,10 +10406,36 @@ static MaybeObject* Runtime_ExecuteInDebugContext(Arguments args) {
}
// Sets a v8 flag.
static MaybeObject* Runtime_SetFlags(Arguments args) {
CONVERT_CHECKED(String, arg, args[0]);
SmartPointer<char> flags =
arg->ToCString(DISALLOW_NULLS, ROBUST_STRING_TRAVERSAL);
FlagList::SetFlagsFromString(*flags, strlen(*flags));
return Heap::undefined_value();
}
// Performs a GC.
// Presently, it only does a full GC.
static MaybeObject* Runtime_CollectGarbage(Arguments args) {
Heap::CollectAllGarbage(true);
return Heap::undefined_value();
}
// Gets the current heap usage.
static MaybeObject* Runtime_GetHeapUsage(Arguments args) {
int usage = Heap::SizeOfObjects();
if (!Smi::IsValid(usage)) {
return *Factory::NewNumberFromInt(usage);
}
return Smi::FromInt(usage);
}
#endif // ENABLE_DEBUGGER_SUPPORT
#ifdef ENABLE_LOGGING_AND_PROFILING
#ifdef ENABLE_LOGGING_AND_PROFILING
static MaybeObject* Runtime_ProfilerResume(Arguments args) {
NoHandleAllocation ha;
ASSERT(args.length() == 2);
......
......@@ -363,7 +363,12 @@ namespace internal {
F(LiveEditCheckAndDropActivations, 2, 1) \
F(LiveEditCompareStringsLinewise, 2, 1) \
F(GetFunctionCodePositionFromSource, 2, 1) \
F(ExecuteInDebugContext, 2, 1)
F(ExecuteInDebugContext, 2, 1) \
\
F(SetFlags, 1, 1) \
F(CollectGarbage, 1, 1) \
F(GetHeapUsage, 0, 1)
#else
#define RUNTIME_FUNCTION_LIST_DEBUGGER_SUPPORT(F)
#endif
......
......@@ -3712,7 +3712,7 @@ TEST(BreakOnException) {
v8::V8::AddMessageListener(MessageCallbackCount);
v8::Debug::SetDebugEventListener(DebugEventCounter);
// Initial state should be break on uncaught exception.
// Initial state should be no break on exceptions.
DebugEventCounterClear();
MessageCallbackCountClear();
caught->Call(env->Global(), 0, NULL);
......@@ -3720,8 +3720,8 @@ TEST(BreakOnException) {
CHECK_EQ(0, uncaught_exception_hit_count);
CHECK_EQ(0, message_callback_count);
notCaught->Call(env->Global(), 0, NULL);
CHECK_EQ(1, exception_hit_count);
CHECK_EQ(1, uncaught_exception_hit_count);
CHECK_EQ(0, exception_hit_count);
CHECK_EQ(0, uncaught_exception_hit_count);
CHECK_EQ(1, message_callback_count);
// No break on exception
......@@ -3841,6 +3841,9 @@ TEST(BreakOnCompileException) {
v8::HandleScope scope;
DebugLocalContext env;
// For this test, we want to break on uncaught exceptions:
ChangeBreakOnException(false, true);
v8::internal::Top::TraceException(false);
// Create a function for checking the function when hitting a break point.
......@@ -3892,6 +3895,9 @@ TEST(StepWithException) {
v8::HandleScope scope;
DebugLocalContext env;
// For this test, we want to break on uncaught exceptions:
ChangeBreakOnException(false, true);
// Create a function for checking the function when hitting a break point.
frame_function_name = CompileFunction(&env,
frame_function_name_source,
......@@ -6523,6 +6529,10 @@ static void ExceptionMessageHandler(const v8::Debug::Message& message) {
TEST(ExceptionMessageWhenMessageHandlerIsReset) {
v8::HandleScope scope;
DebugLocalContext env;
// For this test, we want to break on uncaught exceptions:
ChangeBreakOnException(false, true);
exception_event_count = 0;
const char* script = "function f() {throw new Error()};";
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment