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


git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@6180 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
sgjesse@chromium.org 2011-01-05 13:47:53 +00:00
parent 344e534bde
commit 8669f630c0
8 changed files with 775 additions and 76 deletions

619
src/d8.js
View File

@ -110,17 +110,32 @@ Debug.ScopeType = { Global: 0,
const kNoFrame = -1; const kNoFrame = -1;
Debug.State = { Debug.State = {
currentFrame: kNoFrame, currentFrame: kNoFrame,
displaySourceStartLine: -1,
displaySourceEndLine: -1,
currentSourceLine: -1 currentSourceLine: -1
} }
var trace_compile = false; // Tracing all compile events? var trace_compile = false; // Tracing all compile events?
var trace_debug_json = false; // Tracing all debug json packets?
var last_cmd_line = '';
var repeat_cmd_line = '';
var is_running = true;
// Copied from debug-delay.js. This is needed below:
function ScriptTypeFlag(type) {
return (1 << type);
}
// Process a debugger JSON message into a display text and a running status. // Process a debugger JSON message into a display text and a running status.
// This function returns an object with properties "text" and "running" holding // This function returns an object with properties "text" and "running" holding
// this information. // this information.
function DebugMessageDetails(message) { function DebugMessageDetails(message) {
if (trace_debug_json) {
print("received: '" + message + "'");
}
// Convert the JSON string to an object. // Convert the JSON string to an object.
var response = new ProtocolPackage(message); var response = new ProtocolPackage(message);
is_running = response.running();
if (response.type() == 'event') { if (response.type() == 'event') {
return DebugEventDetails(response); return DebugEventDetails(response);
@ -161,6 +176,8 @@ function DebugEventDetails(response) {
result += '\n'; result += '\n';
result += SourceUnderline(body.sourceLineText, body.sourceColumn); result += SourceUnderline(body.sourceLineText, body.sourceColumn);
Debug.State.currentSourceLine = body.sourceLine; Debug.State.currentSourceLine = body.sourceLine;
Debug.State.displaySourceStartLine = -1;
Debug.State.displaySourceEndLine = -1;
Debug.State.currentFrame = 0; Debug.State.currentFrame = 0;
details.text = result; details.text = result;
break; break;
@ -180,10 +197,14 @@ function DebugEventDetails(response) {
result += '\n'; result += '\n';
result += SourceUnderline(body.sourceLineText, body.sourceColumn); result += SourceUnderline(body.sourceLineText, body.sourceColumn);
Debug.State.currentSourceLine = body.sourceLine; Debug.State.currentSourceLine = body.sourceLine;
Debug.State.displaySourceStartLine = -1;
Debug.State.displaySourceEndLine = -1;
Debug.State.currentFrame = 0; Debug.State.currentFrame = 0;
} else { } else {
result += ' (empty stack)'; result += ' (empty stack)';
Debug.State.currentSourceLine = -1; Debug.State.currentSourceLine = -1;
Debug.State.displaySourceStartLine = -1;
Debug.State.displaySourceEndLine = -1;
Debug.State.currentFrame = kNoFrame; Debug.State.currentFrame = kNoFrame;
} }
details.text = result; details.text = result;
@ -202,6 +223,10 @@ function DebugEventDetails(response) {
details.text = result; details.text = result;
break; break;
case 'scriptCollected':
details.text = result;
break;
default: default:
details.text = 'Unknown debug event ' + response.event(); details.text = 'Unknown debug event ' + response.event();
} }
@ -254,7 +279,11 @@ function SourceUnderline(source_text, position) {
// Converts a text command to a JSON request. // Converts a text command to a JSON request.
function DebugCommandToJSONRequest(cmd_line) { function DebugCommandToJSONRequest(cmd_line) {
return new DebugRequest(cmd_line).JSONRequest(); var result = new DebugRequest(cmd_line).JSONRequest();
if (trace_debug_json && result) {
print("sending: '" + result + "'");
}
return result;
}; };
@ -266,6 +295,20 @@ function DebugRequest(cmd_line) {
return; return;
} }
// Check for a simple carriage return to repeat the last command:
var is_repeating = false;
if (cmd_line == '\n') {
if (is_running) {
cmd_line = 'break'; // Not in debugger mode, break with a frame request.
} else {
cmd_line = repeat_cmd_line; // use command to repeat.
is_repeating = true;
}
}
if (!is_running) { // Only save the command if in debugger mode.
repeat_cmd_line = cmd_line; // save last command.
}
// Trim string for leading and trailing whitespace. // Trim string for leading and trailing whitespace.
cmd_line = cmd_line.replace(/^\s+|\s+$/g, ''); cmd_line = cmd_line.replace(/^\s+|\s+$/g, '');
@ -281,6 +324,13 @@ function DebugRequest(cmd_line) {
args = cmd_line.slice(pos).replace(/^\s+|\s+$/g, ''); args = cmd_line.slice(pos).replace(/^\s+|\s+$/g, '');
} }
if ((cmd === undefined) || !cmd) {
this.request_ = void 0;
return;
}
last_cmd = cmd;
// Switch on command. // Switch on command.
switch (cmd) { switch (cmd) {
case 'continue': case 'continue':
@ -290,7 +340,22 @@ function DebugRequest(cmd_line) {
case 'step': case 'step':
case 's': case 's':
this.request_ = this.stepCommandToJSONRequest_(args); this.request_ = this.stepCommandToJSONRequest_(args, 'in');
break;
case 'stepi':
case 'si':
this.request_ = this.stepCommandToJSONRequest_(args, 'min');
break;
case 'next':
case 'n':
this.request_ = this.stepCommandToJSONRequest_(args, 'next');
break;
case 'finish':
case 'fin':
this.request_ = this.stepCommandToJSONRequest_(args, 'out');
break; break;
case 'backtrace': case 'backtrace':
@ -311,6 +376,26 @@ function DebugRequest(cmd_line) {
this.request_ = this.scopeCommandToJSONRequest_(args); this.request_ = this.scopeCommandToJSONRequest_(args);
break; break;
case 'disconnect':
case 'exit':
case 'quit':
this.request_ = this.disconnectCommandToJSONRequest_(args);
break;
case 'up':
this.request_ =
this.frameCommandToJSONRequest_('' +
(Debug.State.currentFrame + 1));
break;
case 'down':
case 'do':
this.request_ =
this.frameCommandToJSONRequest_('' +
(Debug.State.currentFrame - 1));
break;
case 'set':
case 'print': case 'print':
case 'p': case 'p':
this.request_ = this.printCommandToJSONRequest_(args); this.request_ = this.printCommandToJSONRequest_(args);
@ -328,11 +413,17 @@ function DebugRequest(cmd_line) {
this.request_ = this.instancesCommandToJSONRequest_(args); this.request_ = this.instancesCommandToJSONRequest_(args);
break; break;
case 'list':
case 'l':
this.request_ = this.listCommandToJSONRequest_(args);
break;
case 'source': case 'source':
this.request_ = this.sourceCommandToJSONRequest_(args); this.request_ = this.sourceCommandToJSONRequest_(args);
break; break;
case 'scripts': case 'scripts':
case 'script':
case 'scr':
this.request_ = this.scriptsCommandToJSONRequest_(args); this.request_ = this.scriptsCommandToJSONRequest_(args);
break; break;
@ -347,6 +438,8 @@ function DebugRequest(cmd_line) {
break; break;
case 'clear': case 'clear':
case 'delete':
case 'd':
this.request_ = this.clearCommandToJSONRequest_(args); this.request_ = this.clearCommandToJSONRequest_(args);
break; break;
@ -354,7 +447,42 @@ function DebugRequest(cmd_line) {
this.request_ = this.threadsCommandToJSONRequest_(args); this.request_ = this.threadsCommandToJSONRequest_(args);
break; break;
case 'cond':
this.request_ = this.changeBreakpointCommandToJSONRequest_(args, 'cond');
break;
case 'enable':
case 'en':
this.request_ =
this.changeBreakpointCommandToJSONRequest_(args, 'enable');
break;
case 'disable':
case 'dis':
this.request_ =
this.changeBreakpointCommandToJSONRequest_(args, 'disable');
break;
case 'ignore':
this.request_ =
this.changeBreakpointCommandToJSONRequest_(args, 'ignore');
break;
case 'info':
case 'inf':
this.request_ = this.infoCommandToJSONRequest_(args);
break;
case 'flags':
this.request_ = this.v8FlagsToJSONRequest_(args);
break;
case 'gc':
this.request_ = this.gcToJSONRequest_(args);
break;
case 'trace': case 'trace':
case 'tr':
// Return undefined to indicate command handled internally (no JSON). // Return undefined to indicate command handled internally (no JSON).
this.request_ = void 0; this.request_ = void 0;
this.traceCommand_(args); this.traceCommand_(args);
@ -370,8 +498,6 @@ function DebugRequest(cmd_line) {
default: default:
throw new Error('Unknown command "' + cmd + '"'); throw new Error('Unknown command "' + cmd + '"');
} }
last_cmd = cmd;
} }
DebugRequest.prototype.JSONRequest = function() { DebugRequest.prototype.JSONRequest = function() {
@ -465,59 +591,73 @@ DebugRequest.prototype.continueCommandToJSONRequest_ = function(args) {
// Create a JSON request for the step command. // Create a JSON request for the step command.
DebugRequest.prototype.stepCommandToJSONRequest_ = function(args) { DebugRequest.prototype.stepCommandToJSONRequest_ = function(args, type) {
// Requesting a step is through the continue command with additional // Requesting a step is through the continue command with additional
// arguments. // arguments.
var request = this.createRequest('continue'); var request = this.createRequest('continue');
request.arguments = {}; request.arguments = {};
// Process arguments if any. // Process arguments if any.
// Only process args if the command is 'step' which is indicated by type being
// set to 'in'. For all other commands, ignore the args.
if (args && args.length > 0) { if (args && args.length > 0) {
args = args.split(/\s*[ ]+\s*/g); args = args.split(/\s+/g);
if (args.length > 2) { if (args.length > 2) {
throw new Error('Invalid step arguments.'); throw new Error('Invalid step arguments.');
} }
if (args.length > 0) { if (args.length > 0) {
// Get step count argument if any. // Check if we have a gdb stype step command. If so, the 1st arg would
if (args.length == 2) { // be the step count. If it's not a number, then assume that we're
var stepcount = parseInt(args[1]); // parsing for the legacy v8 step command.
if (isNaN(stepcount) || stepcount <= 0) { var stepcount = Number(args[0]);
throw new Error('Invalid step count argument "' + args[0] + '".'); if (stepcount == Number.NaN) {
// No step count at arg 1. Process as legacy d8 step command:
if (args.length == 2) {
var stepcount = parseInt(args[1]);
if (isNaN(stepcount) || stepcount <= 0) {
throw new Error('Invalid step count argument "' + args[0] + '".');
}
request.arguments.stepcount = stepcount;
} }
// Get the step action.
switch (args[0]) {
case 'in':
case 'i':
request.arguments.stepaction = 'in';
break;
case 'min':
case 'm':
request.arguments.stepaction = 'min';
break;
case 'next':
case 'n':
request.arguments.stepaction = 'next';
break;
case 'out':
case 'o':
request.arguments.stepaction = 'out';
break;
default:
throw new Error('Invalid step argument "' + args[0] + '".');
}
} else {
// gdb style step commands:
request.arguments.stepaction = type;
request.arguments.stepcount = stepcount; request.arguments.stepcount = stepcount;
} }
// Get the step action.
switch (args[0]) {
case 'in':
case 'i':
request.arguments.stepaction = 'in';
break;
case 'min':
case 'm':
request.arguments.stepaction = 'min';
break;
case 'next':
case 'n':
request.arguments.stepaction = 'next';
break;
case 'out':
case 'o':
request.arguments.stepaction = 'out';
break;
default:
throw new Error('Invalid step argument "' + args[0] + '".');
}
} }
} else { } else {
// Default is step next. // Default is step of the specified type.
request.arguments.stepaction = 'next'; request.arguments.stepaction = type;
} }
return request.toJSONProtocol(); return request.toJSONProtocol();
@ -648,6 +788,41 @@ DebugRequest.prototype.instancesCommandToJSONRequest_ = function(args) {
}; };
// Create a JSON request for the list command.
DebugRequest.prototype.listCommandToJSONRequest_ = function(args) {
// Default is ten lines starting five lines before the current location.
if (Debug.State.displaySourceEndLine == -1) {
// If we list forwards, we will start listing after the last source end
// line. Set it to start from 5 lines before the current location.
Debug.State.displaySourceEndLine = Debug.State.currentSourceLine - 5;
// If we list backwards, we will start listing backwards from the last
// source start line. Set it to start from 1 lines before the current
// location.
Debug.State.displaySourceStartLine = Debug.State.currentSourceLine + 1;
}
var from = Debug.State.displaySourceEndLine + 1;
var lines = 10;
// Parse the arguments.
args = args.split(/\s*,\s*/g);
if (args == '') {
} else if ((args.length == 1) && (args[0] == '-')) {
from = Debug.State.displaySourceStartLine - lines;
} else if (args.length == 2) {
from = parseInt(args[0]);
lines = parseInt(args[1]) - from + 1; // inclusive of the ending line.
} else {
throw new Error('Invalid list arguments.');
}
Debug.State.displaySourceStartLine = from;
Debug.State.displaySourceEndLine = from + lines - 1;
var sourceArgs = '' + from + ' ' + lines;
return this.sourceCommandToJSONRequest_(sourceArgs);
};
// Create a JSON request for the source command. // Create a JSON request for the source command.
DebugRequest.prototype.sourceCommandToJSONRequest_ = function(args) { DebugRequest.prototype.sourceCommandToJSONRequest_ = function(args) {
// Build a evaluate request from the text command. // Build a evaluate request from the text command.
@ -709,7 +884,10 @@ DebugRequest.prototype.scriptsCommandToJSONRequest_ = function(args) {
break; break;
default: default:
throw new Error('Invalid argument "' + args[0] + '".'); // If the arg is not one of the know one aboves, then it must be a
// filter used for filtering the results:
request.arguments.filter = args[0];
break;
} }
} }
@ -731,6 +909,8 @@ DebugRequest.prototype.breakCommandToJSONRequest_ = function(args) {
var request = this.createRequest('setbreakpoint'); var request = this.createRequest('setbreakpoint');
// Break the args into target spec and condition if appropriate.
// Check for breakpoint condition. // Check for breakpoint condition.
pos = args.indexOf(' '); pos = args.indexOf(' ');
if (pos > 0) { if (pos > 0) {
@ -801,6 +981,178 @@ DebugRequest.prototype.clearCommandToJSONRequest_ = function(args) {
}; };
// Create a JSON request for the change breakpoint command.
DebugRequest.prototype.changeBreakpointCommandToJSONRequest_ =
function(args, command) {
var request;
// Check for exception breaks first:
// en[able] exc[eptions] [all|unc[aught]]
// en[able] [all|unc[aught]] exc[eptions]
// dis[able] exc[eptions] [all|unc[aught]]
// dis[able] [all|unc[aught]] exc[eptions]
if ((command == 'enable' || command == 'disable') &&
args && args.length > 1) {
var nextPos = args.indexOf(' ');
var arg1 = (nextPos > 0) ? args.substring(0, nextPos) : args;
var excType = null;
// Check for:
// en[able] exc[eptions] [all|unc[aught]]
// dis[able] exc[eptions] [all|unc[aught]]
if (arg1 == 'exc' || arg1 == 'exception' || arg1 == 'exceptions') {
var arg2 = (nextPos > 0) ?
args.substring(nextPos + 1, args.length) : 'all';
if (!arg2) {
arg2 = 'all'; // if unspecified, set for all.
} if (arg2 == 'unc') { // check for short cut.
arg2 = 'uncaught';
}
excType = arg2;
// Check for:
// en[able] [all|unc[aught]] exc[eptions]
// dis[able] [all|unc[aught]] exc[eptions]
} else if (arg1 == 'all' || arg1 == 'unc' || arg1 == 'uncaught') {
var arg2 = (nextPos > 0) ?
args.substring(nextPos + 1, args.length) : null;
if (arg2 == 'exc' || arg1 == 'exception' || arg1 == 'exceptions') {
excType = arg1;
if (excType == 'unc') {
excType = 'uncaught';
}
}
}
// If we matched one of the command formats, then excType will be non-null:
if (excType) {
// Build a evaluate request from the text command.
request = this.createRequest('setexceptionbreak');
request.arguments = {};
request.arguments.type = excType;
request.arguments.enabled = (command == 'enable');
return request.toJSONProtocol();
}
}
// Build a evaluate request from the text command.
request = this.createRequest('changebreakpoint');
// Process arguments if any.
if (args && args.length > 0) {
request.arguments = {};
var pos = args.indexOf(' ');
var breakpointArg = args;
var otherArgs;
if (pos > 0) {
breakpointArg = args.substring(0, pos);
otherArgs = args.substring(pos + 1, args.length);
}
request.arguments.breakpoint = parseInt(breakpointArg);
switch(command) {
case 'cond':
request.arguments.condition = otherArgs ? otherArgs : null;
break;
case 'enable':
request.arguments.enabled = true;
break;
case 'disable':
request.arguments.enabled = false;
break;
case 'ignore':
request.arguments.ignoreCount = parseInt(otherArgs);
break;
default:
throw new Error('Invalid arguments.');
}
} else {
throw new Error('Invalid arguments.');
}
return request.toJSONProtocol();
};
// Create a JSON request for the disconnect command.
DebugRequest.prototype.disconnectCommandToJSONRequest_ = function(args) {
var request;
request = this.createRequest('disconnect');
return request.toJSONProtocol();
};
// Create a JSON request for the info command.
DebugRequest.prototype.infoCommandToJSONRequest_ = function(args) {
var request;
if (args && (args == 'break' || args == 'br')) {
// Build a evaluate request from the text command.
request = this.createRequest('listbreakpoints');
last_cmd = 'info break';
} else if (args && (args == 'locals' || args == 'lo')) {
// Build a evaluate request from the text command.
request = this.createRequest('frame');
last_cmd = 'info locals';
} else if (args && (args == 'args' || args == 'ar')) {
// Build a evaluate request from the text command.
request = this.createRequest('frame');
last_cmd = 'info args';
} else {
throw new Error('Invalid info arguments.');
}
return request.toJSONProtocol();
};
DebugRequest.prototype.v8FlagsToJSONRequest_ = function(args) {
var request;
request = this.createRequest('v8flags');
request.arguments = {};
request.arguments.flags = args;
return request.toJSONProtocol();
};
DebugRequest.prototype.gcToJSONRequest_ = function(args) {
var request;
if (!args) {
args = 'all';
}
var args = args.split(/\s+/g);
var cmd = args[0];
switch(cmd) {
case 'all':
case 'quick':
case 'full':
case 'young':
case 'old':
case 'compact':
case 'sweep':
case 'scavenge': {
if (cmd == 'young') { cmd = 'quick'; }
else if (cmd == 'old') { cmd = 'full'; }
request = this.createRequest('gc');
request.arguments = {};
request.arguments.type = cmd;
break;
}
// Else fall thru to the default case below to report the error.
default:
throw new Error('Missing arguments after ' + cmd + '.');
}
return request.toJSONProtocol();
};
// Create a JSON request for the threads command. // Create a JSON request for the threads command.
DebugRequest.prototype.threadsCommandToJSONRequest_ = function(args) { DebugRequest.prototype.threadsCommandToJSONRequest_ = function(args) {
// Build a threads request from the text command. // Build a threads request from the text command.
@ -816,6 +1168,10 @@ DebugRequest.prototype.traceCommand_ = function(args) {
if (args == 'compile') { if (args == 'compile') {
trace_compile = !trace_compile; trace_compile = !trace_compile;
print('Tracing of compiled scripts ' + (trace_compile ? 'on' : 'off')); print('Tracing of compiled scripts ' + (trace_compile ? 'on' : 'off'));
} else if (args === 'debug json' || args === 'json' || args === 'packets') {
trace_debug_json = !trace_debug_json;
print('Tracing of debug json packets ' +
(trace_debug_json ? 'on' : 'off'));
} else { } else {
throw new Error('Invalid trace arguments.'); throw new Error('Invalid trace arguments.');
} }
@ -831,24 +1187,63 @@ DebugRequest.prototype.helpCommand_ = function(args) {
print('warning: arguments to \'help\' are ignored'); print('warning: arguments to \'help\' are ignored');
} }
print('break'); print('Note: <> denotes symbollic values to be replaced with real values.');
print('break location [condition]'); print('Note: [] denotes optional parts of commands, or optional options / arguments.');
print(' break on named function: location is a function name'); print(' e.g. d[elete] - you get the same command if you type d or delete.');
print(' break on function: location is #<id>#'); print('');
print(' break on script position: location is name:line[:column]'); print('[break] - break as soon as possible');
print('clear <breakpoint #>'); print('b[reak] location [condition]');
print('backtrace [n] | [-n] | [from to]'); print(' - break on named function: location is a function name');
print('frame <frame #>'); print(' - break on function: location is #<id>#');
print(' - break on script position: location is name:line[:column]');
print('');
print('clear <breakpoint #> - deletes the specified user defined breakpoint');
print('d[elete] <breakpoint #> - deletes the specified user defined breakpoint');
print('dis[able] <breakpoint #> - disables the specified user defined breakpoint');
print('dis[able] exc[eptions] [[all] | unc[aught]]');
print(' - disables breaking on exceptions');
print('en[able] <breakpoint #> - enables the specified user defined breakpoint');
print('en[able] exc[eptions] [[all] | unc[aught]]');
print(' - enables breaking on exceptions');
print('');
print('b[ack]t[race] [n] | [-n] | [from to]');
print(' - prints the stack back trace');
print('f[rame] - prints info about the current frame context');
print('f[rame] <frame #> - set context to specified frame #');
print('scopes'); print('scopes');
print('scope <scope #>'); print('scope <scope #>');
print('');
print('up - set context to caller of current frame');
print('do[wn] - set context to callee of current frame');
print('inf[o] br[eak] - prints info about breakpoints in use');
print('inf[o] ar[gs] - prints info about arguments of the current function');
print('inf[o] lo[cals] - prints info about locals in the current function');
print('inf[o] liveobjectlist|lol - same as \'lol info\'');
print('');
print('step [in | next | out| min [step count]]'); print('step [in | next | out| min [step count]]');
print('print <expression>'); print('c[ontinue] - continue executing after a breakpoint');
print('dir <expression>'); print('s[tep] [<N>] - step into the next N callees (default N is 1)');
print('s[tep]i [<N>] - step into the next N callees (default N is 1)');
print('n[ext] [<N>] - step over the next N callees (default N is 1)');
print('fin[ish] [<N>] - step out of N frames (default N is 1)');
print('');
print('p[rint] <expression> - prints the result of the specified expression');
print('dir <expression> - prints the object structure of the result');
print('set <var> = <expression> - executes the specified statement');
print('');
print('l[ist] - list the source code around for the current pc');
print('l[ist] [- | <start>,<end>] - list the specified range of source code');
print('source [from line [num lines]]'); print('source [from line [num lines]]');
print('scripts'); print('scr[ipts] [native|extensions|all]');
print('continue'); print('scr[ipts] [<filter text>] - list scripts with the specified text in its description');
print('');
print('gc - runs the garbage collector');
print('');
print('trace compile'); print('trace compile');
print('help'); // hidden command: trace debug json - toggles tracing of debug json packets
print('');
print('disconnect|exit|quit - disconnects and quits the debugger');
print('help - prints this help information');
} }
@ -930,6 +1325,27 @@ function formatScope_(scope) {
} }
function refObjectToString_(protocolPackage, handle) {
var value = protocolPackage.lookup(handle);
var result = '';
if (value.isString()) {
result = '"' + value.value() + '"';
} else if (value.isPrimitive()) {
result = value.valueString();
} else if (value.isObject()) {
result += formatObject_(value, true);
}
return result;
}
// Rounds number 'num' to 'length' decimal places.
function roundNumber(num, length) {
var factor = Math.pow(10, length);
return Math.round(num * factor) / factor;
}
// Convert a JSON response to text for display in a text based debugger. // Convert a JSON response to text for display in a text based debugger.
function DebugResponseDetails(response) { function DebugResponseDetails(response) {
details = {text:'', running:false} details = {text:'', running:false}
@ -962,6 +1378,11 @@ function DebugResponseDetails(response) {
details.text = result; details.text = result;
break; break;
case 'changebreakpoint':
result = 'successfully changed breakpoint';
details.text = result;
break;
case 'listbreakpoints': case 'listbreakpoints':
result = 'breakpoints: (' + body.breakpoints.length + ')'; result = 'breakpoints: (' + body.breakpoints.length + ')';
for (var i = 0; i < body.breakpoints.length; i++) { for (var i = 0; i < body.breakpoints.length; i++) {
@ -974,9 +1395,9 @@ function DebugResponseDetails(response) {
if (breakpoint.script_name) { if (breakpoint.script_name) {
result += ' script_name=' + breakpoint.script_name; result += ' script_name=' + breakpoint.script_name;
} }
result += ' line=' + breakpoint.line; result += ' line=' + (breakpoint.line + 1);
if (breakpoint.column != null) { if (breakpoint.column != null) {
result += ' column=' + breakpoint.column; result += ' column=' + (breakpoint.column + 1);
} }
if (breakpoint.groupId) { if (breakpoint.groupId) {
result += ' groupId=' + breakpoint.groupId; result += ' groupId=' + breakpoint.groupId;
@ -992,6 +1413,24 @@ function DebugResponseDetails(response) {
} }
result += ' hit_count=' + breakpoint.hit_count; result += ' hit_count=' + breakpoint.hit_count;
} }
if (body.breakpoints.length === 0) {
result = "No user defined breakpoints\n";
} else {
result += '\n';
}
if (body.breakOnExceptions) {
result += '* breaking on ALL exceptions is enabled\n';
} else if (body.breakOnUncaughtExceptions) {
result += '* breaking on UNCAUGHT exceptions is enabled\n';
} else {
result += '* all exception breakpoints are disabled\n';
}
details.text = result;
break;
case 'setexceptionbreak':
result = 'Break on ' + body.type + ' exceptions: ';
result += body.enabled ? 'enabled' : 'disabled';
details.text = result; details.text = result;
break; break;
@ -1010,10 +1449,39 @@ function DebugResponseDetails(response) {
break; break;
case 'frame': case 'frame':
details.text = SourceUnderline(body.sourceLineText, if (last_cmd === 'info locals') {
body.column); var locals = body.locals;
Debug.State.currentSourceLine = body.line; if (locals.length === 0) {
Debug.State.currentFrame = body.index; result = 'No locals';
} else {
for (var i = 0; i < locals.length; i++) {
var local = locals[i];
result += local.name + ' = ';
result += refObjectToString_(response, local.value.ref);
result += '\n';
}
}
} else if (last_cmd === 'info args') {
var args = body.arguments;
if (args.length === 0) {
result = 'No arguments';
} else {
for (var i = 0; i < args.length; i++) {
var arg = args[i];
result += arg.name + ' = ';
result += refObjectToString_(response, arg.value.ref);
result += '\n';
}
}
} else {
result = SourceUnderline(body.sourceLineText,
body.column);
Debug.State.currentSourceLine = body.line;
Debug.State.currentFrame = body.index;
Debug.State.displaySourceStartLine = -1;
Debug.State.displaySourceEndLine = -1;
}
details.text = result;
break; break;
case 'scopes': case 'scopes':
@ -1132,7 +1600,9 @@ function DebugResponseDetails(response) {
if (body[i].name) { if (body[i].name) {
result += body[i].name; result += body[i].name;
} else { } else {
if (body[i].compilationType == Debug.ScriptCompilationType.Eval) { if (body[i].compilationType == Debug.ScriptCompilationType.Eval
&& body[i].evalFromScript
) {
result += 'eval from '; result += 'eval from ';
var script_value = response.lookup(body[i].evalFromScript.ref); var script_value = response.lookup(body[i].evalFromScript.ref);
result += ' ' + script_value.field('name'); result += ' ' + script_value.field('name');
@ -1162,6 +1632,9 @@ function DebugResponseDetails(response) {
result += sourceStart; result += sourceStart;
result += ']'; result += ']';
} }
if (body.length == 0) {
result = "no matching scripts found";
}
details.text = result; details.text = result;
break; break;
@ -1181,6 +1654,23 @@ function DebugResponseDetails(response) {
details.text = "(running)"; details.text = "(running)";
break; break;
case 'v8flags':
details.text = "flags set";
break;
case 'gc':
details.text = "GC " + body.before + " => " + body.after;
if (body.after > (1024*1024)) {
details.text +=
" (" + roundNumber(body.before/(1024*1024), 1) + "M => " +
roundNumber(body.after/(1024*1024), 1) + "M)";
} else if (body.after > 1024) {
details.text +=
" (" + roundNumber(body.before/1024, 1) + "K => " +
roundNumber(body.after/1024, 1) + "K)";
}
break;
default: default:
details.text = details.text =
'Response for unknown command \'' + response.command() + '\'' + 'Response for unknown command \'' + response.command() + '\'' +
@ -1467,6 +1957,11 @@ ProtocolValue.prototype.value = function() {
} }
ProtocolValue.prototype.valueString = function() {
return this.value_.text;
}
function ProtocolReference(handle) { function ProtocolReference(handle) {
this.handle_ = handle; this.handle_ = handle;
} }
@ -1613,7 +2108,9 @@ function SimpleObjectToJSON_(object) {
var property_value_json; var property_value_json;
switch (typeof property_value) { switch (typeof property_value) {
case 'object': case 'object':
if (typeof property_value.toJSONProtocol == 'function') { if (property_value === null) {
property_value_json = 'null';
} else if (typeof property_value.toJSONProtocol == 'function') {
property_value_json = property_value.toJSONProtocol(true) property_value_json = property_value.toJSONProtocol(true)
} else if (property_value.constructor.name == 'Array'){ } else if (property_value.constructor.name == 'Array'){
property_value_json = SimpleArrayToJSON_(property_value); property_value_json = SimpleArrayToJSON_(property_value);

View File

@ -27,9 +27,11 @@
#include "v8.h" #include "v8.h"
#include "debug.h"
#include "debug-agent.h" #include "debug-agent.h"
#ifdef ENABLE_DEBUGGER_SUPPORT #ifdef ENABLE_DEBUGGER_SUPPORT
namespace v8 { namespace v8 {
namespace internal { namespace internal {
@ -167,22 +169,33 @@ void DebuggerAgentSession::Run() {
while (true) { while (true) {
// Read data from the debugger front end. // Read data from the debugger front end.
SmartPointer<char> message = DebuggerAgentUtil::ReceiveMessage(client_); SmartPointer<char> message = DebuggerAgentUtil::ReceiveMessage(client_);
if (*message == NULL) {
// Session is closed. const char* msg = *message;
agent_->OnSessionClosed(this); bool is_closing_session = (msg == NULL);
return;
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. // Convert UTF-8 to UTF-16.
unibrow::Utf8InputBuffer<> buf(*message, unibrow::Utf8InputBuffer<> buf(msg, StrLength(msg));
StrLength(*message));
int len = 0; int len = 0;
while (buf.has_more()) { while (buf.has_more()) {
buf.GetNext(); buf.GetNext();
len++; len++;
} }
ScopedVector<int16_t> temp(len + 1); ScopedVector<int16_t> temp(len + 1);
buf.Reset(*message, StrLength(*message)); buf.Reset(msg, StrLength(msg));
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
temp[i] = buf.GetNext(); temp[i] = buf.GetNext();
} }
@ -190,6 +203,12 @@ void DebuggerAgentSession::Run() {
// Send the request received to the debugger. // Send the request received to the debugger.
v8::Debug::SendCommand(reinterpret_cast<const uint16_t *>(temp.start()), v8::Debug::SendCommand(reinterpret_cast<const uint16_t *>(temp.start()),
len); len);
if (is_closing_session) {
// Session is closed.
agent_->OnSessionClosed(this);
return;
}
} }
} }

View File

@ -654,13 +654,19 @@ Debug.setBreakPoint = function(func, opt_line, opt_column, opt_condition) {
Debug.enableBreakPoint = function(break_point_number) { Debug.enableBreakPoint = function(break_point_number) {
var break_point = this.findBreakPoint(break_point_number, false); 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) { Debug.disableBreakPoint = function(break_point_number) {
var break_point = this.findBreakPoint(break_point_number, false); 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) { Debug.findScriptBreakPoint = function(break_point_number, remove) {
var script_break_point; var script_break_point;
for (var i = 0; i < script_break_points.length; i++) { for (var i = 0; i < script_break_points.length; i++) {
@ -1341,6 +1358,10 @@ DebugCommandProcessor.prototype.processDebugJSONRequest = function(json_request)
this.clearBreakPointRequest_(request, response); this.clearBreakPointRequest_(request, response);
} else if (request.command == 'clearbreakpointgroup') { } else if (request.command == 'clearbreakpointgroup') {
this.clearBreakPointGroupRequest_(request, response); 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') { } else if (request.command == 'listbreakpoints') {
this.listBreakpointsRequest_(request, response); this.listBreakpointsRequest_(request, response);
} else if (request.command == 'backtrace') { } else if (request.command == 'backtrace') {
@ -1373,6 +1394,13 @@ DebugCommandProcessor.prototype.processDebugJSONRequest = function(json_request)
this.changeLiveRequest_(request, response); this.changeLiveRequest_(request, response);
} else if (request.command == 'flags') { } else if (request.command == 'flags') {
this.debuggerFlagsRequest_(request, response); 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 { } else {
throw new Error('Unknown command "' + request.command + '" in request'); throw new Error('Unknown command "' + request.command + '" in request');
} }
@ -1690,7 +1718,63 @@ DebugCommandProcessor.prototype.listBreakpointsRequest_ = function(request, resp
array.push(description); 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; 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. // Collect all scripts in the heap.
@ -2058,6 +2152,21 @@ DebugCommandProcessor.prototype.scriptsRequest_ = function(request, response) {
if (idsToInclude && !idsToInclude[scripts[i].id]) { if (idsToInclude && !idsToInclude[scripts[i].id]) {
continue; 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)) { if (types & ScriptTypeFlag(scripts[i].type)) {
response.body.push(MakeMirror(scripts[i])); 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 // Check whether the previously processed command caused the VM to become
// running. // running.
DebugCommandProcessor.prototype.isRunning = function() { DebugCommandProcessor.prototype.isRunning = function() {

View File

@ -622,7 +622,7 @@ bool Debug::disable_break_ = false;
// Default call debugger on uncaught exception. // Default call debugger on uncaught exception.
bool Debug::break_on_exception_ = false; 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>(); Handle<Context> Debug::debug_context_ = Handle<Context>();
Code* Debug::debug_break_return_ = NULL; Code* Debug::debug_break_return_ = NULL;
@ -2740,8 +2740,10 @@ bool Debugger::StartAgent(const char* name, int port,
} }
if (Socket::Setup()) { if (Socket::Setup()) {
agent_ = new DebuggerAgent(name, port); if (agent_ == NULL) {
agent_->Start(); agent_ = new DebuggerAgent(name, port);
agent_->Start();
}
return true; return true;
} }

View File

@ -32,6 +32,7 @@
#include "debug-agent.h" #include "debug-agent.h"
#include "execution.h" #include "execution.h"
#include "factory.h" #include "factory.h"
#include "flags.h"
#include "hashmap.h" #include "hashmap.h"
#include "platform.h" #include "platform.h"
#include "string-stream.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. // Currently argument event is not used.
return !compiling_natives_ && Debugger::IsDebuggerActive(); return !compiling_natives_ && Debugger::IsDebuggerActive();
} }

View File

@ -355,6 +355,16 @@ DEFINE_string(map_counters, NULL, "Map counters to a file")
DEFINE_args(js_arguments, JSArguments(), DEFINE_args(js_arguments, JSArguments(),
"Pass all remaining arguments to the script. Alias for \"--\".") "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 // Debug only flags
// //

View File

@ -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 #endif // ENABLE_DEBUGGER_SUPPORT
#ifdef ENABLE_LOGGING_AND_PROFILING
#ifdef ENABLE_LOGGING_AND_PROFILING
static MaybeObject* Runtime_ProfilerResume(Arguments args) { static MaybeObject* Runtime_ProfilerResume(Arguments args) {
NoHandleAllocation ha; NoHandleAllocation ha;
ASSERT(args.length() == 2); ASSERT(args.length() == 2);

View File

@ -363,7 +363,12 @@ namespace internal {
F(LiveEditCheckAndDropActivations, 2, 1) \ F(LiveEditCheckAndDropActivations, 2, 1) \
F(LiveEditCompareStringsLinewise, 2, 1) \ F(LiveEditCompareStringsLinewise, 2, 1) \
F(GetFunctionCodePositionFromSource, 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 #else
#define RUNTIME_FUNCTION_LIST_DEBUGGER_SUPPORT(F) #define RUNTIME_FUNCTION_LIST_DEBUGGER_SUPPORT(F)
#endif #endif