From 8669f630c053c7d0918a287337958bd3ffc9a36b Mon Sep 17 00:00:00 2001 From: "sgjesse@chromium.org" Date: Wed, 5 Jan 2011 13:47:53 +0000 Subject: [PATCH] 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 --- src/d8.js | 611 ++++++++++++++++++++++++++++++++++++++++++++----- src/debug-agent.cc | 33 ++- src/debug-debugger.js | 136 ++++++++++- src/debug.cc | 8 +- src/debug.h | 10 + src/flag-definitions.h | 10 + src/runtime.cc | 28 ++- src/runtime.h | 7 +- 8 files changed, 771 insertions(+), 72 deletions(-) diff --git a/src/d8.js b/src/d8.js index a758e09..8b8f7b2 100644 --- a/src/d8.js +++ b/src/d8.js @@ -110,17 +110,32 @@ Debug.ScopeType = { Global: 0, const kNoFrame = -1; Debug.State = { currentFrame: kNoFrame, + displaySourceStartLine: -1, + displaySourceEndLine: -1, currentSourceLine: -1 } 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. // This function returns an object with properties "text" and "running" holding // this information. function DebugMessageDetails(message) { + if (trace_debug_json) { + print("received: '" + message + "'"); + } // Convert the JSON string to an object. var response = new ProtocolPackage(message); + is_running = response.running(); if (response.type() == 'event') { return DebugEventDetails(response); @@ -161,6 +176,8 @@ function DebugEventDetails(response) { result += '\n'; result += SourceUnderline(body.sourceLineText, body.sourceColumn); Debug.State.currentSourceLine = body.sourceLine; + Debug.State.displaySourceStartLine = -1; + Debug.State.displaySourceEndLine = -1; Debug.State.currentFrame = 0; details.text = result; break; @@ -180,10 +197,14 @@ function DebugEventDetails(response) { result += '\n'; result += SourceUnderline(body.sourceLineText, body.sourceColumn); Debug.State.currentSourceLine = body.sourceLine; + Debug.State.displaySourceStartLine = -1; + Debug.State.displaySourceEndLine = -1; Debug.State.currentFrame = 0; } else { result += ' (empty stack)'; Debug.State.currentSourceLine = -1; + Debug.State.displaySourceStartLine = -1; + Debug.State.displaySourceEndLine = -1; Debug.State.currentFrame = kNoFrame; } details.text = result; @@ -202,6 +223,10 @@ function DebugEventDetails(response) { details.text = result; break; + case 'scriptCollected': + details.text = result; + break; + default: details.text = 'Unknown debug event ' + response.event(); } @@ -254,7 +279,11 @@ function SourceUnderline(source_text, position) { // Converts a text command to a JSON request. 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; } + // 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. 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, ''); } + if ((cmd === undefined) || !cmd) { + this.request_ = void 0; + return; + } + + last_cmd = cmd; + // Switch on command. switch (cmd) { case 'continue': @@ -290,7 +340,22 @@ function DebugRequest(cmd_line) { case 'step': 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; case 'backtrace': @@ -311,6 +376,26 @@ function DebugRequest(cmd_line) { this.request_ = this.scopeCommandToJSONRequest_(args); 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 'p': this.request_ = this.printCommandToJSONRequest_(args); @@ -328,11 +413,17 @@ function DebugRequest(cmd_line) { this.request_ = this.instancesCommandToJSONRequest_(args); break; + case 'list': + case 'l': + this.request_ = this.listCommandToJSONRequest_(args); + break; case 'source': this.request_ = this.sourceCommandToJSONRequest_(args); break; case 'scripts': + case 'script': + case 'scr': this.request_ = this.scriptsCommandToJSONRequest_(args); break; @@ -347,6 +438,8 @@ function DebugRequest(cmd_line) { break; case 'clear': + case 'delete': + case 'd': this.request_ = this.clearCommandToJSONRequest_(args); break; @@ -354,7 +447,42 @@ function DebugRequest(cmd_line) { this.request_ = this.threadsCommandToJSONRequest_(args); 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 'tr': // Return undefined to indicate command handled internally (no JSON). this.request_ = void 0; this.traceCommand_(args); @@ -370,8 +498,6 @@ function DebugRequest(cmd_line) { default: throw new Error('Unknown command "' + cmd + '"'); } - - last_cmd = cmd; } DebugRequest.prototype.JSONRequest = function() { @@ -465,59 +591,73 @@ DebugRequest.prototype.continueCommandToJSONRequest_ = function(args) { // 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 // arguments. var request = this.createRequest('continue'); request.arguments = {}; // 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) { - args = args.split(/\s*[ ]+\s*/g); + args = args.split(/\s+/g); if (args.length > 2) { throw new Error('Invalid step arguments.'); } if (args.length > 0) { - // Get step count argument if any. - if (args.length == 2) { - var stepcount = parseInt(args[1]); - if (isNaN(stepcount) || stepcount <= 0) { - throw new Error('Invalid step count argument "' + args[0] + '".'); + // Check if we have a gdb stype step command. If so, the 1st arg would + // be the step count. If it's not a number, then assume that we're + // parsing for the legacy v8 step command. + var stepcount = Number(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; } - request.arguments.stepcount = stepcount; - } - // Get the step action. - switch (args[0]) { - case 'in': - case 'i': - request.arguments.stepaction = 'in'; - break; + // 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 'min': + case 'm': + request.arguments.stepaction = 'min'; + break; - case 'next': - case 'n': - request.arguments.stepaction = 'next'; - break; + case 'next': + case 'n': + request.arguments.stepaction = 'next'; + break; - case 'out': - case 'o': - request.arguments.stepaction = 'out'; - break; + case 'out': + case 'o': + request.arguments.stepaction = 'out'; + break; - default: - throw new Error('Invalid step argument "' + args[0] + '".'); + default: + throw new Error('Invalid step argument "' + args[0] + '".'); + } + + } else { + // gdb style step commands: + request.arguments.stepaction = type; + request.arguments.stepcount = stepcount; } } } else { - // Default is step next. - request.arguments.stepaction = 'next'; + // Default is step of the specified type. + request.arguments.stepaction = type; } 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. DebugRequest.prototype.sourceCommandToJSONRequest_ = function(args) { // Build a evaluate request from the text command. @@ -709,7 +884,10 @@ DebugRequest.prototype.scriptsCommandToJSONRequest_ = function(args) { break; 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'); + // Break the args into target spec and condition if appropriate. + // Check for breakpoint condition. pos = args.indexOf(' '); 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. DebugRequest.prototype.threadsCommandToJSONRequest_ = function(args) { // Build a threads request from the text command. @@ -816,6 +1168,10 @@ DebugRequest.prototype.traceCommand_ = function(args) { if (args == 'compile') { trace_compile = !trace_compile; 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 { throw new Error('Invalid trace arguments.'); } @@ -831,24 +1187,63 @@ DebugRequest.prototype.helpCommand_ = function(args) { print('warning: arguments to \'help\' are ignored'); } - print('break'); - print('break location [condition]'); - print(' break on named function: location is a function name'); - print(' break on function: location is ##'); - print(' break on script position: location is name:line[:column]'); - print('clear '); - print('backtrace [n] | [-n] | [from to]'); - print('frame '); + print('Note: <> denotes symbollic values to be replaced with real values.'); + print('Note: [] denotes optional parts of commands, or optional options / arguments.'); + print(' e.g. d[elete] - you get the same command if you type d or delete.'); + print(''); + print('[break] - break as soon as possible'); + print('b[reak] location [condition]'); + print(' - break on named function: location is a function name'); + print(' - break on function: location is ##'); + print(' - break on script position: location is name:line[:column]'); + print(''); + print('clear - deletes the specified user defined breakpoint'); + print('d[elete] - deletes the specified user defined breakpoint'); + print('dis[able] - disables the specified user defined breakpoint'); + print('dis[able] exc[eptions] [[all] | unc[aught]]'); + print(' - disables breaking on exceptions'); + print('en[able] - 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] - set context to specified frame #'); print('scopes'); print('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('print '); - print('dir '); + print('c[ontinue] - continue executing after a breakpoint'); + print('s[tep] [] - step into the next N callees (default N is 1)'); + print('s[tep]i [] - step into the next N callees (default N is 1)'); + print('n[ext] [] - step over the next N callees (default N is 1)'); + print('fin[ish] [] - step out of N frames (default N is 1)'); + print(''); + print('p[rint] - prints the result of the specified expression'); + print('dir - prints the object structure of the result'); + print('set = - executes the specified statement'); + print(''); + print('l[ist] - list the source code around for the current pc'); + print('l[ist] [- | ,] - list the specified range of source code'); print('source [from line [num lines]]'); - print('scripts'); - print('continue'); + print('scr[ipts] [native|extensions|all]'); + print('scr[ipts] [] - list scripts with the specified text in its description'); + print(''); + print('gc - runs the garbage collector'); + print(''); 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. function DebugResponseDetails(response) { details = {text:'', running:false} @@ -962,6 +1378,11 @@ function DebugResponseDetails(response) { details.text = result; break; + case 'changebreakpoint': + result = 'successfully changed breakpoint'; + details.text = result; + break; + case 'listbreakpoints': result = 'breakpoints: (' + body.breakpoints.length + ')'; for (var i = 0; i < body.breakpoints.length; i++) { @@ -974,9 +1395,9 @@ function DebugResponseDetails(response) { if (breakpoint.script_name) { result += ' script_name=' + breakpoint.script_name; } - result += ' line=' + breakpoint.line; + result += ' line=' + (breakpoint.line + 1); if (breakpoint.column != null) { - result += ' column=' + breakpoint.column; + result += ' column=' + (breakpoint.column + 1); } if (breakpoint.groupId) { result += ' groupId=' + breakpoint.groupId; @@ -992,6 +1413,24 @@ function DebugResponseDetails(response) { } 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; break; @@ -1010,10 +1449,39 @@ function DebugResponseDetails(response) { break; case 'frame': - details.text = SourceUnderline(body.sourceLineText, - body.column); - Debug.State.currentSourceLine = body.line; - Debug.State.currentFrame = body.index; + if (last_cmd === 'info locals') { + var locals = body.locals; + if (locals.length === 0) { + 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; case 'scopes': @@ -1132,7 +1600,9 @@ function DebugResponseDetails(response) { if (body[i].name) { result += body[i].name; } else { - if (body[i].compilationType == Debug.ScriptCompilationType.Eval) { + if (body[i].compilationType == Debug.ScriptCompilationType.Eval + && body[i].evalFromScript + ) { result += 'eval from '; var script_value = response.lookup(body[i].evalFromScript.ref); result += ' ' + script_value.field('name'); @@ -1162,6 +1632,9 @@ function DebugResponseDetails(response) { result += sourceStart; result += ']'; } + if (body.length == 0) { + result = "no matching scripts found"; + } details.text = result; break; @@ -1181,6 +1654,23 @@ function DebugResponseDetails(response) { details.text = "(running)"; 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: details.text = '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) { this.handle_ = handle; } @@ -1613,7 +2108,9 @@ function SimpleObjectToJSON_(object) { var property_value_json; switch (typeof property_value) { 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) } else if (property_value.constructor.name == 'Array'){ property_value_json = SimpleArrayToJSON_(property_value); diff --git a/src/debug-agent.cc b/src/debug-agent.cc index e2d9304..6901079 100644 --- a/src/debug-agent.cc +++ b/src/debug-agent.cc @@ -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 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 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(temp.start()), len); + + if (is_closing_session) { + // Session is closed. + agent_->OnSessionClosed(this); + return; + } } } diff --git a/src/debug-debugger.js b/src/debug-debugger.js index 090c661..dcff07c 100644 --- a/src/debug-debugger.js +++ b/src/debug-debugger.js @@ -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() { diff --git a/src/debug.cc b/src/debug.cc index c41e545..8ec77e7 100644 --- a/src/debug.cc +++ b/src/debug.cc @@ -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 Debug::debug_context_ = Handle(); 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; } diff --git a/src/debug.h b/src/debug.h index 0d63085..85c4d53 100644 --- a/src/debug.h +++ b/src/debug.h @@ -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(); } diff --git a/src/flag-definitions.h b/src/flag-definitions.h index 2b24d13..6e73258 100644 --- a/src/flag-definitions.h +++ b/src/flag-definitions.h @@ -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 // diff --git a/src/runtime.cc b/src/runtime.cc index 724a436..d64695b 100644 --- a/src/runtime.cc +++ b/src/runtime.cc @@ -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 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); diff --git a/src/runtime.h b/src/runtime.h index 5ecae7e..2fa7438 100644 --- a/src/runtime.h +++ b/src/runtime.h @@ -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 -- 2.7.4