Merge branch 'debugger'
This commit is contained in:
commit
6593a96373
@ -43,6 +43,7 @@ var server = http.createServer(function (req, res) {
|
||||
|
||||
if (command == "bytes") {
|
||||
var n = parseInt(arg, 10)
|
||||
debugger;
|
||||
if (n <= 0)
|
||||
throw "bytes called with n <= 0"
|
||||
if (stored[n] === undefined) {
|
||||
|
@ -28,6 +28,7 @@
|
||||
* [Assertion Testing](assert.html)
|
||||
* [TTY](tty.html)
|
||||
* [OS](os.html)
|
||||
* [Debugger](debugger.html)
|
||||
* Appendixes
|
||||
* [Appendix 1: Recommended Third-party Modules](appendix_1.html)
|
||||
* [Appendix 2: Deprecated API's](appendix_2.html)
|
||||
|
@ -29,6 +29,7 @@
|
||||
@include assert
|
||||
@include tty
|
||||
@include os
|
||||
@include debugger
|
||||
|
||||
# Appendixes
|
||||
@include appendix_1
|
||||
|
73
doc/api/debugger.markdown
Normal file
73
doc/api/debugger.markdown
Normal file
@ -0,0 +1,73 @@
|
||||
## Debugger
|
||||
|
||||
V8 comes with an extensive debugger which is accessable out-of-process via a
|
||||
simple [TCP protocol](http://code.google.com/p/v8/wiki/DebuggerProtocol).
|
||||
Node has a built-in client for this debugger. To use this, start Node with the
|
||||
`debug` argument; a prompt will appear:
|
||||
|
||||
% node debug myscript.js
|
||||
debug>
|
||||
|
||||
At this point `myscript.js` is not yet running. To start the script, enter
|
||||
the command `run`. If everything works okay, the output should look like
|
||||
this:
|
||||
|
||||
% node debug myscript.js
|
||||
debug> run
|
||||
debugger listening on port 5858
|
||||
connecting...ok
|
||||
|
||||
Node's debugger client doesn't support the full range of commands, but
|
||||
simple step and inspection is possible. By putting the statement `debugger;`
|
||||
into the source code of your script, you will enable a breakpoint.
|
||||
|
||||
For example, suppose `myscript.js` looked like this:
|
||||
|
||||
// myscript.js
|
||||
x = 5;
|
||||
setTimeout(function () {
|
||||
debugger;
|
||||
console.log("world");
|
||||
}, 1000);
|
||||
console.log("hello");
|
||||
|
||||
Then once the debugger is run, it will break on line 4.
|
||||
|
||||
% ./node debug myscript.js
|
||||
debug> run
|
||||
debugger listening on port 5858
|
||||
connecting...ok
|
||||
hello
|
||||
break in #<an Object>._onTimeout(), myscript.js:4
|
||||
debugger;
|
||||
^
|
||||
debug> next
|
||||
break in #<an Object>._onTimeout(), myscript.js:5
|
||||
console.log("world");
|
||||
^
|
||||
debug> print x
|
||||
5
|
||||
debug> print 2+2
|
||||
4
|
||||
debug> next
|
||||
world
|
||||
break in #<an Object>._onTimeout() returning undefined, myscript.js:6
|
||||
}, 1000);
|
||||
^
|
||||
debug> quit
|
||||
A debugging session is active. Quit anyway? (y or n) y
|
||||
%
|
||||
|
||||
|
||||
The `print` command allows you to evaluate variables. The `next` command steps
|
||||
over to the next line. There are a few other commands available and more to
|
||||
come type `help` to see others.
|
||||
|
||||
|
||||
### Advanced Usage
|
||||
|
||||
The V8 debugger can be enabled and accessed either by starting Node with
|
||||
the `--debug` command-line flag or by signaling an existing Node process
|
||||
with `SIGUSR1`.
|
||||
|
||||
|
745
lib/_debugger.js
Normal file
745
lib/_debugger.js
Normal file
@ -0,0 +1,745 @@
|
||||
var net = require('net');
|
||||
var readline = require('readline');
|
||||
var inherits = require('util').inherits;
|
||||
var spawn = require('child_process').spawn;
|
||||
|
||||
exports.port = 5858;
|
||||
|
||||
exports.start = function () {
|
||||
var interface = new Interface();
|
||||
};
|
||||
|
||||
|
||||
var args = process.argv.slice(2);
|
||||
args.unshift('--debug-brk');
|
||||
|
||||
|
||||
|
||||
//
|
||||
// Parser/Serializer for V8 debugger protocol
|
||||
// http://code.google.com/p/v8/wiki/DebuggerProtocol
|
||||
//
|
||||
// Usage:
|
||||
// p = new Protocol();
|
||||
//
|
||||
// p.onResponse = function (res) {
|
||||
// // do stuff with response from V8
|
||||
// };
|
||||
//
|
||||
// socket.setEncoding('utf8');
|
||||
// socket.on('data', function (s) {
|
||||
// // Pass strings into the protocol
|
||||
// p.execute(s);
|
||||
// });
|
||||
//
|
||||
//
|
||||
function Protocol() {
|
||||
this._newRes();
|
||||
}
|
||||
exports.Protocol = Protocol;
|
||||
|
||||
|
||||
Protocol.prototype._newRes = function(raw) {
|
||||
this.res = { raw: raw || '', headers: {} };
|
||||
this.state = 'headers';
|
||||
this.reqSeq = 1;
|
||||
this.execute('');
|
||||
};
|
||||
|
||||
|
||||
Protocol.prototype.execute = function(d) {
|
||||
var res = this.res;
|
||||
res.raw += d;
|
||||
|
||||
switch (this.state) {
|
||||
case 'headers':
|
||||
var endHeaderIndex = res.raw.indexOf('\r\n\r\n');
|
||||
|
||||
if (endHeaderIndex < 0) break;
|
||||
|
||||
var lines = res.raw.slice(0, endHeaderIndex).split('\r\n');
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
var kv = lines[i].split(/: +/);
|
||||
res.headers[kv[0]] = kv[1];
|
||||
}
|
||||
|
||||
this.contentLength = +res.headers['Content-Length'];
|
||||
this.bodyStartIndex = endHeaderIndex + 4;
|
||||
|
||||
this.state = 'body';
|
||||
if (res.raw.length - this.bodyStartIndex < this.contentLength) break;
|
||||
// pass thru
|
||||
|
||||
case 'body':
|
||||
if (res.raw.length - this.bodyStartIndex >= this.contentLength) {
|
||||
res.body =
|
||||
res.raw.slice(this.bodyStartIndex,
|
||||
this.bodyStartIndex + this.contentLength);
|
||||
// JSON parse body?
|
||||
res.body = res.body.length ? JSON.parse(res.body) : {};
|
||||
|
||||
// Done!
|
||||
this.onResponse(res);
|
||||
|
||||
this._newRes(res.raw.slice(this.bodyStartIndex + this.contentLength));
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error("Unknown state");
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Protocol.prototype.serialize = function(req) {
|
||||
req.type = 'request';
|
||||
req.seq = this.reqSeq++;
|
||||
var json = JSON.stringify(req);
|
||||
return 'Content-Length: ' + json.length + '\r\n\r\n' + json;
|
||||
};
|
||||
|
||||
|
||||
var NO_FRAME = -1;
|
||||
|
||||
function Client() {
|
||||
net.Stream.call(this);
|
||||
var protocol = this.protocol = new Protocol(this);
|
||||
this._reqCallbacks = [];
|
||||
var socket = this;
|
||||
|
||||
this.currentFrame = NO_FRAME;
|
||||
this.currentSourceLine = -1;
|
||||
this.currentSource = null;
|
||||
this.handles = {};
|
||||
this.scripts = {};
|
||||
|
||||
// Note that 'Protocol' requires strings instead of Buffers.
|
||||
socket.setEncoding('utf8');
|
||||
socket.on('data', function(d) {
|
||||
protocol.execute(d);
|
||||
});
|
||||
|
||||
protocol.onResponse = this._onResponse.bind(this);
|
||||
};
|
||||
inherits(Client, net.Stream);
|
||||
exports.Client = Client;
|
||||
|
||||
|
||||
Client.prototype._addHandle = function(desc) {
|
||||
if (typeof desc != 'object' || !desc.handle) throw new Error("bad type");
|
||||
this.handles[desc.id] = desc;
|
||||
|
||||
if (desc.type == 'script') {
|
||||
this._addScript(desc);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
var natives = process.binding('natives');
|
||||
|
||||
|
||||
Client.prototype._addScript = function(desc) {
|
||||
this.scripts[desc.id] = desc;
|
||||
if (desc.name) {
|
||||
desc.isNative = (desc.name.replace('.js', '') in natives) ||
|
||||
desc.name == 'node.js';
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Client.prototype._removeScript = function(desc) {
|
||||
this.scripts[desc.id] = undefined;
|
||||
};
|
||||
|
||||
|
||||
Client.prototype._onResponse = function(res) {
|
||||
for (var i = 0; i < this._reqCallbacks.length; i++) {
|
||||
var cb = this._reqCallbacks[i];
|
||||
if (this._reqCallbacks[i].request_seq == res.body.request_seq) break;
|
||||
}
|
||||
|
||||
var self = this;
|
||||
var handled = false;
|
||||
|
||||
if (res.headers.Type == 'connect') {
|
||||
// Request a list of scripts for our own storage.
|
||||
self.reqScripts();
|
||||
self.emit('ready');
|
||||
handled = true;
|
||||
|
||||
} else if (res.body && res.body.event == 'break') {
|
||||
this.emit('break', res.body);
|
||||
handled = true;
|
||||
|
||||
} else if (res.body && res.body.event == 'afterCompile') {
|
||||
this._addHandle(res.body.body.script);
|
||||
handled = true;
|
||||
|
||||
} else if (res.body && res.body.event == 'scriptCollected') {
|
||||
// ???
|
||||
this._removeScript(res.body.body.script);
|
||||
handled = true;
|
||||
|
||||
}
|
||||
|
||||
if (cb) {
|
||||
this._reqCallbacks.splice(i, 1);
|
||||
handled = true;
|
||||
cb(res.body);
|
||||
}
|
||||
|
||||
if (!handled) this.emit('unhandledResponse', res.body);
|
||||
};
|
||||
|
||||
|
||||
Client.prototype.req = function(req, cb) {
|
||||
this.write(this.protocol.serialize(req));
|
||||
cb.request_seq = req.seq;
|
||||
this._reqCallbacks.push(cb);
|
||||
};
|
||||
|
||||
|
||||
Client.prototype.reqVersion = function(cb) {
|
||||
this.req({ command: 'version' } , function (res) {
|
||||
if (cb) cb(res.body.V8Version, res.running);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Client.prototype.reqEval = function(expression, cb) {
|
||||
var req = {
|
||||
command: 'evaluate',
|
||||
arguments: { expression: expression }
|
||||
};
|
||||
|
||||
if (this.currentFrame == NO_FRAME) {
|
||||
req.arguments.global = true;
|
||||
} else {
|
||||
req.arguments.frame = this.currentFrame;
|
||||
}
|
||||
|
||||
this.req(req, function (res) {
|
||||
if (cb) cb(res.body);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// reqBacktrace(cb)
|
||||
// TODO: from, to, bottom
|
||||
Client.prototype.reqBacktrace = function(cb) {
|
||||
this.req({ command: 'backtrace' } , function (res) {
|
||||
if (cb) cb(res.body);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// Returns an array of objects like this:
|
||||
//
|
||||
// { handle: 11,
|
||||
// type: 'script',
|
||||
// name: 'node.js',
|
||||
// id: 14,
|
||||
// lineOffset: 0,
|
||||
// columnOffset: 0,
|
||||
// lineCount: 562,
|
||||
// sourceStart: '(function(process) {\n\n ',
|
||||
// sourceLength: 15939,
|
||||
// scriptType: 2,
|
||||
// compilationType: 0,
|
||||
// context: { ref: 10 },
|
||||
// text: 'node.js (lines: 562)' }
|
||||
//
|
||||
Client.prototype.reqScripts = function(cb) {
|
||||
var self = this;
|
||||
this.req({ command: 'scripts' } , function (res) {
|
||||
for (var i = 0; i < res.body.length; i++) {
|
||||
self._addHandle(res.body[i]);
|
||||
}
|
||||
if (cb) cb();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Client.prototype.reqContinue = function(cb) {
|
||||
this.req({ command: 'continue' }, function (res) {
|
||||
if (cb) cb(res);
|
||||
});
|
||||
};
|
||||
|
||||
Client.prototype.listbreakpoints = function(cb) {
|
||||
this.req({ command: 'listbreakpoints' }, function (res) {
|
||||
if (cb) cb(res);
|
||||
});
|
||||
};
|
||||
|
||||
// client.next(1, cb);
|
||||
Client.prototype.step = function(action, count, cb) {
|
||||
var req = {
|
||||
command: 'continue',
|
||||
arguments: { stepaction: action, stepcount: count }
|
||||
};
|
||||
|
||||
this.req(req, function (res) {
|
||||
if (cb) cb(res);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
var helpMessage = "Commands: run, kill, print, step, next, " +
|
||||
"continue, scripts, backtrace, version, quit";
|
||||
|
||||
function SourceUnderline(sourceText, position) {
|
||||
if (!sourceText) return;
|
||||
|
||||
// Create an underline with a caret pointing to the source position. If the
|
||||
// source contains a tab character the underline will have a tab character in
|
||||
// the same place otherwise the underline will have a space character.
|
||||
var underline = '';
|
||||
for (var i = 0; i < position; i++) {
|
||||
if (sourceText[i] == '\t') {
|
||||
underline += '\t';
|
||||
} else {
|
||||
underline += ' ';
|
||||
}
|
||||
}
|
||||
underline += '^';
|
||||
|
||||
// Return the source line text with the underline beneath.
|
||||
return sourceText + '\n' + underline;
|
||||
}
|
||||
|
||||
|
||||
function SourceInfo(body) {
|
||||
var result = '';
|
||||
|
||||
if (body.script) {
|
||||
if (body.script.name) {
|
||||
result += body.script.name;
|
||||
} else {
|
||||
result += '[unnamed]';
|
||||
}
|
||||
}
|
||||
result += ':';
|
||||
result += body.sourceLine + 1;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// This class is the readline-enabled debugger interface which is invoked on
|
||||
// "node debug"
|
||||
function Interface() {
|
||||
var self = this;
|
||||
var term = this.term = readline.createInterface(process.stdout);
|
||||
var child;
|
||||
var client;
|
||||
var term;
|
||||
|
||||
process.on('exit', function () {
|
||||
self.killChild();
|
||||
});
|
||||
|
||||
this.stdin = process.openStdin();
|
||||
this.stdin.addListener('data', function(chunk) {
|
||||
term.write(chunk);
|
||||
});
|
||||
|
||||
term.setPrompt('debug> ');
|
||||
term.prompt();
|
||||
|
||||
this.quitting = false;
|
||||
|
||||
process.on('SIGINT', function () {
|
||||
self.handleSIGINT();
|
||||
});
|
||||
|
||||
term.on('SIGINT', function () {
|
||||
self.handleSIGINT();
|
||||
});
|
||||
|
||||
term.on('close', function () {
|
||||
self.tryQuit();
|
||||
});
|
||||
|
||||
term.on('line', function(cmd) {
|
||||
// trim whitespace
|
||||
cmd = cmd.replace(/^\s*/, '').replace(/\s*$/, '');
|
||||
|
||||
if (cmd.length) {
|
||||
self._lastCommand = cmd;
|
||||
self.handleCommand(cmd);
|
||||
} else {
|
||||
self.handleCommand(self._lastCommand);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Interface.prototype.handleSIGINT = function() {
|
||||
if (this.paused) {
|
||||
this.child.kill('SIGINT');
|
||||
} else {
|
||||
this.tryQuit();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Interface.prototype.quit = function() {
|
||||
if (this.quitting) return;
|
||||
this.quitting = true;
|
||||
this.killChild();
|
||||
this.term.close();
|
||||
process.exit(0);
|
||||
};
|
||||
|
||||
|
||||
Interface.prototype.tryQuit = function() {
|
||||
var self = this;
|
||||
|
||||
if (self.child) {
|
||||
self.quitQuestion(function (yes) {
|
||||
if (yes) {
|
||||
self.quit();
|
||||
} else {
|
||||
self.term.prompt();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
self.quit();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Interface.prototype.pause = function() {
|
||||
this.paused = true;
|
||||
this.stdin.pause();
|
||||
this.term.pause();
|
||||
};
|
||||
|
||||
|
||||
Interface.prototype.resume = function() {
|
||||
if (!this.paused) return false
|
||||
this.paused = false;
|
||||
this.stdin.resume();
|
||||
this.term.resume();
|
||||
this.term.prompt();
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
Interface.prototype.handleBreak = function(r) {
|
||||
var result = '';
|
||||
if (r.breakpoints) {
|
||||
result += 'breakpoint';
|
||||
if (r.breakpoints.length > 1) {
|
||||
result += 's';
|
||||
}
|
||||
result += ' #';
|
||||
for (var i = 0; i < r.breakpoints.length; i++) {
|
||||
if (i > 0) {
|
||||
result += ', #';
|
||||
}
|
||||
result += r.breakpoints[i];
|
||||
}
|
||||
} else {
|
||||
result += 'break';
|
||||
}
|
||||
result += ' in ';
|
||||
result += r.invocationText;
|
||||
result += ', ';
|
||||
result += SourceInfo(r);
|
||||
result += '\n';
|
||||
result += SourceUnderline(r.sourceLineText, r.sourceColumn);
|
||||
|
||||
this.client.currentSourceLine = r.sourceLine;
|
||||
this.client.currentFrame = 0;
|
||||
this.client.currentScript = r.script.name;
|
||||
|
||||
console.log(result);
|
||||
|
||||
if(!this.resume()) this.term.prompt();
|
||||
};
|
||||
|
||||
|
||||
Interface.prototype.handleCommand = function(cmd) {
|
||||
var self = this;
|
||||
|
||||
var client = this.client;
|
||||
var term = this.term;
|
||||
|
||||
if (cmd == 'quit' || cmd == 'q' || cmd == 'exit') {
|
||||
self._lastCommand = null;
|
||||
self.tryQuit();
|
||||
|
||||
} else if (/^r(un)?/.test(cmd)) {
|
||||
self._lastCommand = null;
|
||||
if (self.child) {
|
||||
self.restartQuestion(function (yes) {
|
||||
if (!yes) {
|
||||
self._lastCommand = null;
|
||||
term.prompt();
|
||||
} else {
|
||||
console.log("restarting...");
|
||||
self.killChild();
|
||||
// XXX need to wait a little bit for the restart to work?
|
||||
setTimeout(function () {
|
||||
self.trySpawn();
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
self.trySpawn();
|
||||
}
|
||||
|
||||
} else if (/^help/.test(cmd)) {
|
||||
console.log(helpMessage);
|
||||
term.prompt();
|
||||
|
||||
} else if ('version' == cmd) {
|
||||
if (!client) {
|
||||
self.printNotConnected();
|
||||
return;
|
||||
}
|
||||
client.reqVersion(function (v) {
|
||||
console.log(v);
|
||||
term.prompt();
|
||||
});
|
||||
|
||||
} else if (/info +breakpoints/.test(cmd)) {
|
||||
if (!client) {
|
||||
self.printNotConnected();
|
||||
return;
|
||||
}
|
||||
client.listbreakpoints(function (res) {
|
||||
console.log(res);
|
||||
term.prompt();
|
||||
});
|
||||
|
||||
} else if (/^backtrace/.test(cmd) || /^bt/.test(cmd)) {
|
||||
if (!client) {
|
||||
self.printNotConnected();
|
||||
return;
|
||||
}
|
||||
client.reqBacktrace(function (bt) {
|
||||
if (/full/.test(cmd)) {
|
||||
console.log(bt);
|
||||
} else if (bt.totalFrames == 0) {
|
||||
console.log('(empty stack)');
|
||||
} else {
|
||||
var result = '';
|
||||
for (j = 0; j < bt.frames.length; j++) {
|
||||
if (j != 0) result += '\n';
|
||||
result += bt.frames[j].text;
|
||||
}
|
||||
console.log(result);
|
||||
}
|
||||
term.prompt();
|
||||
});
|
||||
|
||||
} else if (cmd == 'scripts' || cmd == 'scripts full') {
|
||||
if (!client) {
|
||||
self.printNotConnected();
|
||||
return;
|
||||
}
|
||||
self.printScripts(cmd.indexOf('full') > 0);
|
||||
term.prompt();
|
||||
|
||||
} else if (/^c(ontinue)?/.test(cmd)) {
|
||||
if (!client) {
|
||||
self.printNotConnected();
|
||||
return;
|
||||
}
|
||||
|
||||
self.pause();
|
||||
client.reqContinue(function () {
|
||||
self.resume();
|
||||
});
|
||||
|
||||
} else if (/^k(ill)?/.test(cmd)) {
|
||||
if (!client) {
|
||||
self.printNotConnected();
|
||||
return;
|
||||
}
|
||||
// kill
|
||||
if (self.child) {
|
||||
self.killQuestion(function (yes) {
|
||||
if (yes) {
|
||||
self.killChild();
|
||||
} else {
|
||||
self._lastCommand = null;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
self.term.prompt();
|
||||
}
|
||||
|
||||
} else if (/^next/.test(cmd) || /^n/.test(cmd)) {
|
||||
if (!client) {
|
||||
self.printNotConnected();
|
||||
return;
|
||||
}
|
||||
client.step('next', 1, function (res) {
|
||||
// Wait for break point. (disable raw mode?)
|
||||
});
|
||||
|
||||
} else if (/^step/.test(cmd) || /^s/.test(cmd)) {
|
||||
if (!client) {
|
||||
self.printNotConnected();
|
||||
return;
|
||||
}
|
||||
client.step('in', 1, function (res) {
|
||||
// Wait for break point. (disable raw mode?)
|
||||
});
|
||||
|
||||
} else if (/^print/.test(cmd) || /^p/.test(cmd)) {
|
||||
if (!client) {
|
||||
self.printNotConnected();
|
||||
return;
|
||||
}
|
||||
var i = cmd.indexOf(' ');
|
||||
if (i < 0) {
|
||||
console.log("print [expression]");
|
||||
term.prompt();
|
||||
} else {
|
||||
cmd = cmd.slice(i);
|
||||
client.reqEval(cmd, function (res) {
|
||||
if (res) {
|
||||
console.log(res.text);
|
||||
} else {
|
||||
console.log(res);
|
||||
}
|
||||
term.prompt();
|
||||
});
|
||||
}
|
||||
|
||||
} else {
|
||||
if (!/^\s*$/.test(cmd)) {
|
||||
// If it's not all white-space print this error message.
|
||||
console.log('Unknown command "%s". Try "help"', cmd);
|
||||
}
|
||||
term.prompt();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
Interface.prototype.yesNoQuestion = function(prompt, cb) {
|
||||
var self = this;
|
||||
self.resume();
|
||||
this.term.question(prompt, function (answer) {
|
||||
if (/^y(es)?$/i.test(answer)) {
|
||||
cb(true);
|
||||
} else if (/^n(o)?$/i.test(answer)) {
|
||||
cb(false);
|
||||
} else {
|
||||
console.log("Please answer y or n.");
|
||||
self.restartQuestion(cb);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Interface.prototype.restartQuestion = function(cb) {
|
||||
this.yesNoQuestion("The program being debugged has been started already.\n" +
|
||||
"Start it from the beginning? (y or n) ", cb);
|
||||
};
|
||||
|
||||
|
||||
Interface.prototype.killQuestion = function(cb) {
|
||||
this.yesNoQuestion("Kill the program being debugged? (y or n) ", cb);
|
||||
};
|
||||
|
||||
|
||||
Interface.prototype.quitQuestion = function(cb) {
|
||||
this.yesNoQuestion("A debugging session is active. Quit anyway? (y or n) ",
|
||||
cb);
|
||||
};
|
||||
|
||||
|
||||
Interface.prototype.killChild = function() {
|
||||
if (this.child) {
|
||||
this.child.kill();
|
||||
this.child = null;
|
||||
}
|
||||
|
||||
if (this.client) {
|
||||
this.client.destroy();
|
||||
this.client = null;
|
||||
}
|
||||
|
||||
this.resume();
|
||||
};
|
||||
|
||||
|
||||
Interface.prototype.trySpawn = function(cb) {
|
||||
var self = this;
|
||||
|
||||
this.killChild();
|
||||
|
||||
this.child = spawn(process.execPath, args, { customFds: [0, 1, 2] });
|
||||
|
||||
|
||||
this.pause();
|
||||
|
||||
setTimeout(function () {
|
||||
process.stdout.write("connecting...");
|
||||
var client = self.client = new Client();
|
||||
client.connect(exports.port);
|
||||
|
||||
client.once('ready', function () {
|
||||
process.stdout.write("ok\r\n");
|
||||
|
||||
// since we did debug-brk, we're hitting a break point immediately
|
||||
// continue before anything else.
|
||||
client.reqContinue(function () {
|
||||
if (cb) cb();
|
||||
});
|
||||
});
|
||||
|
||||
client.on('close', function () {
|
||||
console.log("\nprogram terminated");
|
||||
self.client = null;
|
||||
self.killChild();
|
||||
if (!self.quitting) self.term.prompt();
|
||||
});
|
||||
|
||||
client.on('unhandledResponse', function (res) {
|
||||
console.log("\r\nunhandled res:");
|
||||
console.log(res);
|
||||
self.term.prompt();
|
||||
});
|
||||
|
||||
client.on('break', function (res) {
|
||||
self.handleBreak(res.body);
|
||||
});
|
||||
}, 100);
|
||||
};
|
||||
|
||||
|
||||
Interface.prototype.printNotConnected = function() {
|
||||
console.log("Program not running. Try 'run'.");
|
||||
this.term.prompt();
|
||||
};
|
||||
|
||||
|
||||
// argument full tells if it should display internal node scripts or not
|
||||
Interface.prototype.printScripts = function(displayNatives) {
|
||||
var client = this.client;
|
||||
var text = '';
|
||||
for (var id in client.scripts) {
|
||||
var script = client.scripts[id];
|
||||
if (typeof script == 'object' && script.name) {
|
||||
if (displayNatives || script.name == client.currentScript || !script.isNative) {
|
||||
text += script.name == client.currentScript ? '* ' : ' ';
|
||||
text += script.name + '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
process.stdout.write(text);
|
||||
};
|
||||
|
||||
|
||||
|
@ -17,19 +17,6 @@ exports.createInterface = function(output, completer) {
|
||||
return new Interface(output, completer);
|
||||
};
|
||||
|
||||
function writeFilter(stream) {
|
||||
if (stream._writeFiltered) return;
|
||||
stream._writeFiltered = true;
|
||||
stream._normalWrite = stream.write;
|
||||
stream.write = function(d) {
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
if (typeof d == 'string') {
|
||||
args[0] = d.replace(/([^\r])\n|^\n/g, '$1\r\n');
|
||||
}
|
||||
// TODO what about buffers?
|
||||
return stream._normalWrite.apply(stream, args);
|
||||
}
|
||||
}
|
||||
|
||||
function Interface(output, completer) {
|
||||
if (!(this instanceof Interface)) return new Interface(output, completer);
|
||||
@ -49,9 +36,6 @@ function Interface(output, completer) {
|
||||
if (this.enabled) {
|
||||
// input refers to stdin
|
||||
|
||||
writeFilter(this.output);
|
||||
writeFilter(process.stdout);
|
||||
|
||||
// Current line
|
||||
this.line = '';
|
||||
|
||||
@ -105,10 +89,17 @@ Interface.prototype.prompt = function() {
|
||||
|
||||
Interface.prototype.question = function(query, cb) {
|
||||
if (cb) {
|
||||
this._oldPrompt = this._prompt;
|
||||
this.setPrompt(query);
|
||||
this._questionCallback = cb;
|
||||
this.prompt();
|
||||
this.resume();
|
||||
if (this._questionCallback) {
|
||||
this.output.write('\n');
|
||||
this.prompt();
|
||||
} else {
|
||||
this._oldPrompt = this._prompt;
|
||||
this.setPrompt(query);
|
||||
this._questionCallback = cb;
|
||||
this.output.write('\n');
|
||||
this.prompt();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -160,6 +151,8 @@ Interface.prototype._refreshLine = function() {
|
||||
|
||||
|
||||
Interface.prototype.close = function(d) {
|
||||
if (this._closing) return;
|
||||
this._closing = true;
|
||||
if (this.enabled) {
|
||||
tty.setRawMode(false);
|
||||
}
|
||||
@ -168,6 +161,20 @@ Interface.prototype.close = function(d) {
|
||||
};
|
||||
|
||||
|
||||
Interface.prototype.pause = function() {
|
||||
if (this.enabled) {
|
||||
tty.setRawMode(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Interface.prototype.resume = function() {
|
||||
if (this.enabled) {
|
||||
tty.setRawMode(true);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Interface.prototype.write = function(d) {
|
||||
if (this._closed) return;
|
||||
return this.enabled ? this._ttyWrite(d) : this._normalWrite(d);
|
||||
|
86
src/node.cc
86
src/node.cc
@ -87,6 +87,7 @@ static ev_idle tick_spinner;
|
||||
static bool need_tick_cb;
|
||||
static Persistent<String> tick_callback_sym;
|
||||
|
||||
static ev_async enable_debug;
|
||||
static ev_async eio_want_poll_notifier;
|
||||
static ev_async eio_done_poll_notifier;
|
||||
static ev_idle eio_poller;
|
||||
@ -1879,6 +1880,41 @@ static void SignalExit(int signal) {
|
||||
}
|
||||
|
||||
|
||||
static void EnableDebugSignalHandler(int signal) {
|
||||
// can't do much here, marshal this back into the main thread where we'll
|
||||
// enable the debugger.
|
||||
ev_async_send(EV_DEFAULT_UC_ &enable_debug);
|
||||
}
|
||||
|
||||
|
||||
static void EnableDebug(bool wait_connect) {
|
||||
// Start the debug thread and it's associated TCP server on port 5858.
|
||||
bool r = Debug::EnableAgent("node " NODE_VERSION, debug_port);
|
||||
|
||||
if (wait_connect) {
|
||||
// Set up an empty handler so v8 will not continue until a debugger
|
||||
// attaches. This is the same behavior as Debug::EnableAgent(_,_,true)
|
||||
// except we don't break at the beginning of the script.
|
||||
// see Debugger::StartAgent in debug.cc of v8/src
|
||||
Debug::SetMessageHandler2(node::DebugBreakMessageHandler);
|
||||
}
|
||||
|
||||
// Crappy check that everything went well. FIXME
|
||||
assert(r);
|
||||
|
||||
// Print out some information.
|
||||
fprintf(stderr, "debugger listening on port %d\r\n", debug_port);
|
||||
}
|
||||
|
||||
|
||||
static void EnableDebug2(EV_P_ ev_async *watcher, int revents) {
|
||||
assert(watcher == &enable_debug);
|
||||
assert(revents == EV_ASYNC);
|
||||
EnableDebug(false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static int RegisterSignalHandler(int signal, void (*handler)(int)) {
|
||||
struct sigaction sa;
|
||||
|
||||
@ -1984,37 +2020,31 @@ int Start(int argc, char *argv[]) {
|
||||
|
||||
V8::SetFatalErrorHandler(node::OnFatalError);
|
||||
|
||||
|
||||
// Initialize the async watcher for receiving messages from the debug
|
||||
// thread and marshal it into the main thread. DebugMessageCallback()
|
||||
// is called from the main thread to execute a random bit of javascript
|
||||
// - which will give V8 control so it can handle whatever new message
|
||||
// had been received on the debug thread.
|
||||
ev_async_init(&node::debug_watcher, node::DebugMessageCallback);
|
||||
ev_set_priority(&node::debug_watcher, EV_MAXPRI);
|
||||
// Set the callback DebugMessageDispatch which is called from the debug
|
||||
// thread.
|
||||
Debug::SetDebugMessageDispatchHandler(node::DebugMessageDispatch);
|
||||
// Start the async watcher.
|
||||
ev_async_start(EV_DEFAULT_UC_ &node::debug_watcher);
|
||||
// unref it so that we exit the event loop despite it being active.
|
||||
ev_unref(EV_DEFAULT_UC);
|
||||
|
||||
|
||||
// If the --debug flag was specified then initialize the debug thread.
|
||||
if (node::use_debug_agent) {
|
||||
// Initialize the async watcher for receiving messages from the debug
|
||||
// thread and marshal it into the main thread. DebugMessageCallback()
|
||||
// is called from the main thread to execute a random bit of javascript
|
||||
// - which will give V8 control so it can handle whatever new message
|
||||
// had been received on the debug thread.
|
||||
ev_async_init(&node::debug_watcher, node::DebugMessageCallback);
|
||||
ev_set_priority(&node::debug_watcher, EV_MAXPRI);
|
||||
// Set the callback DebugMessageDispatch which is called from the debug
|
||||
// thread.
|
||||
Debug::SetDebugMessageDispatchHandler(node::DebugMessageDispatch);
|
||||
// Start the async watcher.
|
||||
ev_async_start(EV_DEFAULT_UC_ &node::debug_watcher);
|
||||
// unref it so that we exit the event loop despite it being active.
|
||||
EnableDebug(debug_wait_connect);
|
||||
} else {
|
||||
RegisterSignalHandler(SIGUSR1, EnableDebugSignalHandler);
|
||||
ev_async_init(&enable_debug, EnableDebug2);
|
||||
ev_async_start(EV_DEFAULT_UC_ &enable_debug);
|
||||
ev_unref(EV_DEFAULT_UC);
|
||||
|
||||
// Start the debug thread and it's associated TCP server on port 5858.
|
||||
bool r = Debug::EnableAgent("node " NODE_VERSION, node::debug_port);
|
||||
if (node::debug_wait_connect) {
|
||||
// Set up an empty handler so v8 will not continue until a debugger
|
||||
// attaches. This is the same behavior as Debug::EnableAgent(_,_,true)
|
||||
// except we don't break at the beginning of the script.
|
||||
// see Debugger::StartAgent in debug.cc of v8/src
|
||||
Debug::SetMessageHandler2(node::DebugBreakMessageHandler);
|
||||
}
|
||||
|
||||
// Crappy check that everything went well. FIXME
|
||||
assert(r);
|
||||
// Print out some information.
|
||||
printf("debugger listening on port %d\n", node::debug_port);
|
||||
}
|
||||
|
||||
// Create the one and only Context.
|
||||
|
22
src/node.js
22
src/node.js
@ -544,14 +544,22 @@
|
||||
}
|
||||
|
||||
if (process.argv[1]) {
|
||||
// Load module
|
||||
if (process.argv[1].charAt(0) != '/' &&
|
||||
!(/^http:\/\//).exec(process.argv[1])) {
|
||||
process.argv[1] = path.join(cwd, process.argv[1]);
|
||||
|
||||
if (process.argv[1] == 'debug') {
|
||||
// Start the debugger agent
|
||||
var d = requireNative('_debugger');
|
||||
d.start();
|
||||
|
||||
} else {
|
||||
// Load module
|
||||
if (process.argv[1].charAt(0) != '/' &&
|
||||
!(/^http:\/\//).exec(process.argv[1])) {
|
||||
process.argv[1] = path.join(cwd, process.argv[1]);
|
||||
}
|
||||
// REMOVEME: nextTick should not be necessary. This hack to get
|
||||
// test/simple/test-exception-handler2.js working.
|
||||
process.nextTick(module.runMain);
|
||||
}
|
||||
// REMOVEME: nextTick should not be necessary. This hack to get
|
||||
// test/simple/test-exception-handler2.js working.
|
||||
process.nextTick(module.runMain);
|
||||
|
||||
} else if (process._eval) {
|
||||
// -e, --eval
|
||||
|
@ -43,8 +43,8 @@ static int EnableRawMode(int fd) {
|
||||
/* input modes: no break, no CR to NL, no parity check, no strip char,
|
||||
* no start/stop output control. */
|
||||
raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
|
||||
/* output modes - disable post processing */
|
||||
raw.c_oflag &= ~(OPOST);
|
||||
/* output modes */
|
||||
raw.c_oflag |= (ONLCR);
|
||||
/* control modes - set 8 bit chars */
|
||||
raw.c_cflag |= (CS8);
|
||||
/* local modes - choing off, canonical off, no extended functions,
|
||||
|
166
test/simple/test-debugger-client.js
Normal file
166
test/simple/test-debugger-client.js
Normal file
@ -0,0 +1,166 @@
|
||||
var common = require('../common');
|
||||
var assert = require('assert');
|
||||
var debug = require('_debugger');
|
||||
|
||||
var spawn = require('child_process').spawn;
|
||||
|
||||
|
||||
var resCount = 0;
|
||||
var p = new debug.Protocol();
|
||||
p.onResponse = function (res) {
|
||||
resCount++;
|
||||
};
|
||||
|
||||
p.execute("Type: connect\r\n" +
|
||||
"V8-Version: 3.0.4.1\r\n" +
|
||||
"Protocol-Version: 1\r\n" +
|
||||
"Embedding-Host: node v0.3.3-pre\r\n" +
|
||||
"Content-Length: 0\r\n\r\n");
|
||||
assert.equal(1, resCount);
|
||||
|
||||
// Make sure split messages go in.
|
||||
|
||||
var parts = [];
|
||||
parts.push('Content-Length: 336\r\n');
|
||||
assert.equal(21, parts[0].length);
|
||||
parts.push('\r\n');
|
||||
assert.equal(2, parts[1].length);
|
||||
var bodyLength = 0;
|
||||
|
||||
parts.push('{"seq":12,"type":"event","event":"break","body":' +
|
||||
'{"invocationText":"#<a Server>');
|
||||
assert.equal(78, parts[2].length);
|
||||
bodyLength += parts[2].length;
|
||||
|
||||
parts.push('.[anonymous](req=#<an IncomingMessage>, res=#<a ServerResponse>)",' +
|
||||
'"sourceLine"');
|
||||
assert.equal(78, parts[3].length);
|
||||
bodyLength += parts[3].length;
|
||||
|
||||
parts.push(':45,"sourceColumn":4,"sourceLineText":" debugger;","script":' +
|
||||
'{"id":24,"name":"/home/ryan/projects/node/benchmark/http_simple.js",' +
|
||||
'"lineOffset":0,"columnOffset":0,"lineCount":98}}}');
|
||||
assert.equal(180, parts[4].length);
|
||||
bodyLength += parts[4].length;
|
||||
|
||||
assert.equal(336, bodyLength);
|
||||
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
p.execute(parts[i]);
|
||||
}
|
||||
assert.equal(2, resCount);
|
||||
|
||||
|
||||
// Make sure that if we get backed up, we still manage to get all the
|
||||
// messages
|
||||
var d = 'Content-Length: 466\r\n\r\n' +
|
||||
'{"seq":10,"type":"event","event":"afterCompile","success":true,' +
|
||||
'"body":{"script":{"handle":1,"type":"script","name":"dns.js",' +
|
||||
'"id":34,"lineOffset":0,"columnOffset":0,"lineCount":241,"sourceStart":' +
|
||||
'"(function (module, exports, require) {var dns = process.binding(\'cares\')' +
|
||||
';\\nvar ne","sourceLength":6137,"scriptType":2,"compilationType":0,' +
|
||||
'"context":{"ref":0},"text":"dns.js (lines: 241)"}},"refs":[{"handle":0' +
|
||||
',"type":"context","text":"#<a ContextMirror>"}],"running":true}' +
|
||||
'Content-Length: 119\r\n\r\n' +
|
||||
'{"seq":11,"type":"event","event":"scriptCollected","success":true,' +
|
||||
'"body":{"script":{"id":26}},"refs":[],"running":true}';
|
||||
p.execute(d);
|
||||
assert.equal(4, resCount);
|
||||
|
||||
var expectedConnections = 0;
|
||||
var tests = [];
|
||||
function addTest (cb) {
|
||||
expectedConnections++;
|
||||
tests.push(cb);
|
||||
}
|
||||
|
||||
addTest(function (client, done) {
|
||||
console.error("requesting version");
|
||||
client.reqVersion(function (v) {
|
||||
console.log("version: %s", v);
|
||||
assert.equal(process.versions.v8, v);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
addTest(function (client, done) {
|
||||
console.error("requesting scripts");
|
||||
client.reqScripts(function () {
|
||||
console.error("got %d scripts", Object.keys(client.scripts).length);
|
||||
|
||||
var foundMainScript = false;
|
||||
for (var k in client.scripts) {
|
||||
var script = client.scripts[k];
|
||||
if (script && script.name === 'node.js') {
|
||||
foundMainScript = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert.ok(foundMainScript);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
addTest(function (client, done) {
|
||||
console.error("eval 2+2");
|
||||
client.reqEval("2+2", function (res) {
|
||||
console.error(res);
|
||||
assert.equal('4', res.text);
|
||||
assert.equal(4, res.value);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
var connectCount = 0;
|
||||
|
||||
function doTest(cb, done) {
|
||||
var nodeProcess = spawn(process.execPath,
|
||||
['-e', 'setInterval(function () { console.log("blah"); }, 100);']);
|
||||
|
||||
nodeProcess.stdout.once('data', function () {
|
||||
console.log(">>> new node process: %d", nodeProcess.pid);
|
||||
process.kill(nodeProcess.pid, "SIGUSR1");
|
||||
console.log(">>> signaling it with SIGUSR1");
|
||||
});
|
||||
|
||||
var didTryConnect = false;
|
||||
nodeProcess.stderr.setEncoding('utf8');
|
||||
nodeProcess.stderr.on('data', function (data) {
|
||||
if (didTryConnect == false && /debugger/.test(data)) {
|
||||
didTryConnect = true;
|
||||
|
||||
// Wait for some data before trying to connect
|
||||
var c = new debug.Client();
|
||||
process.stdout.write(">>> connecting...");
|
||||
c.connect(debug.port)
|
||||
c.on('ready', function () {
|
||||
connectCount++;
|
||||
console.log("ready!");
|
||||
cb(c, function () {
|
||||
console.error(">>> killing node process %d\n\n", nodeProcess.pid);
|
||||
nodeProcess.kill();
|
||||
done();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function run () {
|
||||
var t = tests[0];
|
||||
if (!t) return;
|
||||
|
||||
doTest(t, function () {
|
||||
tests.shift();
|
||||
run();
|
||||
});
|
||||
}
|
||||
|
||||
run();
|
||||
|
||||
process.on('exit', function() {
|
||||
assert.equal(expectedConnections, connectCount);
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user