Browse Source

Wrap up debugger in one class

just for better readablity
v0.7.4-release
Ryan Dahl 14 years ago
parent
commit
d4859a55bc
  1. 526
      lib/_debugger.js

526
lib/_debugger.js

@ -6,96 +6,14 @@ var spawn = require('child_process').spawn;
exports.port = 5858; exports.port = 5858;
exports.start = function () { exports.start = function () {
startInterface(); var interface = new Interface();
process.on('exit', function () {
if (child) child.kill();
});
}; };
var child;
var c;
var term;
var args = process.argv.slice(2); var args = process.argv.slice(2);
args.unshift('--debug-brk'); args.unshift('--debug-brk');
function tryConnect(cb) {
c = new Client();
process.stdout.write("connecting...");
c.connect(exports.port);
c.once('ready', function () {
process.stdout.write("ok\r\n");
if (cb) cb();
});
c.on('close', function () {
process.exit(0);
});
c.on('unhandledResponse', function (res) {
console.log("\r\nunhandled res:");
console.log(res);
term.prompt();
});
c.on('break', function (res) {
var result = '';
if (res.body.breakpoints) {
result += 'breakpoint';
if (res.body.breakpoints.length > 1) {
result += 's';
}
result += ' #';
for (var i = 0; i < res.body.breakpoints.length; i++) {
if (i > 0) {
result += ', #';
}
result += res.body.breakpoints[i];
}
} else {
result += 'break';
}
result += ' in ';
result += res.body.invocationText;
result += ', ';
result += SourceInfo(res.body);
result += '\n';
result += SourceUnderline(res.body.sourceLineText, res.body.sourceColumn);
c.currentSourceLine = res.body.sourceLine;
c.currentFrame = 0;
c.currentScript = res.body.script.name;
console.log(result);
term.prompt();
});
}
function killChild() {
if (c) {
c.destroy();
}
if (child) {
child.kill();
child = null;
}
}
function trySpawn(cb) {
killChild();
child = spawn(process.execPath, args, { customFds: [0, 1, 2] });
setTimeout(function () {
tryConnect(cb);
}, 1000);
}
// //
// Parser/Serializer for V8 debugger protocol // Parser/Serializer for V8 debugger protocol
@ -185,7 +103,7 @@ var NO_FRAME = -1;
function Client() { function Client() {
net.Stream.call(this); net.Stream.call(this);
var protocol = this.protocol = new Protocol(c); var protocol = this.protocol = new Protocol(this);
this._reqCallbacks = []; this._reqCallbacks = [];
var socket = this; var socket = this;
@ -340,7 +258,7 @@ Client.prototype.listbreakpoints = function(cb) {
}); });
}; };
// c.next(1, cb); // client.next(1, cb);
Client.prototype.step = function(action, count, cb) { Client.prototype.step = function(action, count, cb) {
var req = { var req = {
command: 'continue', command: 'continue',
@ -394,202 +312,316 @@ function SourceInfo(body) {
} }
var restartQuestionPrompt = "The program being debugged has " + // This class is the readline-enabled debugger interface which is invoked on
"been started already.\n" + // "node debug"
"Start it from the beginning? (y or n): "; function Interface() {
var self = this;
var term = this.term = readline.createInterface(process.stdout);
var child;
var client;
var term;
function restartQuestion (cb) { process.on('exit', function () {
term.question(restartQuestionPrompt, function (answer) { self.killChild();
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.");
restartQuestion(cb);
}
}); });
}
function printScripts () {
var text = '';
for (var id in c.scripts) {
var script = c.scripts[id];
if (typeof script == 'object' && script.name) {
text += script.name == c.currentScript ? '* ' : ' ';
text += script.name + '\n';
}
}
process.stdout.write(text);
}
function printNotConnected () {
console.log("Program not running. Try 'run'.");
term.prompt();
}
function startInterface() {
term = readline.createInterface(process.stdout);
var stdin = process.openStdin(); var stdin = process.openStdin();
stdin.addListener('data', function(chunk) { stdin.addListener('data', function(chunk) {
term.write(chunk); term.write(chunk);
}); });
var prompt = 'debug> ';
term.setPrompt('debug> '); term.setPrompt('debug> ');
term.prompt(); term.prompt();
var quitTried = false; this.quitTried = false;
term.on('SIGINT', function () {
self.tryQuit();
});
function tryQuit() {
if (quitTried) return;
quitTried = true;
killChild();
term.close();
process.exit(0);
}
term.on('SIGINT', tryQuit); term.on('close', function () {
term.on('close', tryQuit); self.tryQuit();
});
term.on('line', function(cmd) { term.on('line', function(cmd) {
// trim whitespace // trim whitespace
cmd = cmd.replace(/^\s*/, '').replace(/\s*$/, ''); cmd = cmd.replace(/^\s*/, '').replace(/\s*$/, '');
self.handleCommand(cmd);
});
}
if (cmd == 'quit' || cmd == 'q' || cmd == 'exit') {
tryQuit();
} else if (/^r(un)?/.test(cmd)) {
if (child) {
restartQuestion(function (yes) {
if (!yes) {
term.prompt();
} else {
console.log("restarting...");
trySpawn(function () {
c.reqContinue();
});
}
});
} else {
trySpawn(function () {
c.reqContinue();
});
}
} else if (/^help/.test(cmd)) { Interface.prototype.tryQuit = function() {
console.log(helpMessage); if (this.quitTried) return;
term.prompt(); this.quitTried = true;
this.killChild();
this.term.close();
process.exit(0);
};
} else if ('version' == cmd) {
if (!c) {
printNotConnected();
return;
}
c.reqVersion(function (v) {
console.log(v);
term.prompt();
});
} else if (/info +breakpoints/.test(cmd)) { Interface.prototype.handleBreak = function(r) {
if (!c) { var result = '';
printNotConnected(); if (r.breakpoints) {
return; result += 'breakpoint';
if (r.breakpoints.length > 1) {
result += 's';
}
result += ' #';
for (var i = 0; i < r.breakpoints.length; i++) {
if (i > 0) {
result += ', #';
} }
c.listbreakpoints(function (res) { result += r.breakpoints[i];
console.log(res); }
term.prompt(); } else {
}); result += 'break';
}
result += ' in ';
result += r.invocationText;
result += ', ';
result += SourceInfo(r);
result += '\n';
result += SourceUnderline(r.sourceLineText, r.sourceColumn);
} else if (/^backtrace/.test(cmd) || /^bt/.test(cmd)) { this.client.currentSourceLine = r.sourceLine;
if (!c) { this.client.currentFrame = 0;
printNotConnected(); this.client.currentScript = r.script.name;
return;
} console.log(result);
c.reqBacktrace(function (bt) {
if (/full/.test(cmd)) { this.term.prompt();
console.log(bt); };
} else if (bt.totalFrames == 0) {
console.log('(empty stack)');
Interface.prototype.handleCommand = function(cmd) {
var self = this;
var client = this.client;
var term = this.term;
if (cmd == 'quit' || cmd == 'q' || cmd == 'exit') {
self.tryQuit();
} else if (/^r(un)?/.test(cmd)) {
if (self.child) {
self.restartQuestion(function (yes) {
if (!yes) {
term.prompt();
} else { } else {
var result = ''; console.log("restarting...");
for (j = 0; j < bt.frames.length; j++) { self.trySpawn();
if (j != 0) result += '\n';
result += bt.frames[j].text;
}
console.log(result);
} }
term.prompt();
}); });
} else {
self.trySpawn();
}
} else if (cmd == 'scripts' || cmd == 'scripts full') { } else if (/^help/.test(cmd)) {
if (!c) { console.log(helpMessage);
printNotConnected(); term.prompt();
return;
} } else if ('version' == cmd) {
printScripts(); if (!client) {
self.printNotConnected();
return;
}
client.reqVersion(function (v) {
console.log(v);
term.prompt(); term.prompt();
});
} else if (/^continue/.test(cmd) || /^c/.test(cmd)) { } else if (/info +breakpoints/.test(cmd)) {
if (!c) { if (!client) {
printNotConnected(); self.printNotConnected();
return; return;
} }
c.reqContinue(function (res) { client.listbreakpoints(function (res) {
// Wait for break point. (disable raw mode?) console.log(res);
}); term.prompt();
});
} else if (/^next/.test(cmd) || /^n/.test(cmd)) { } else if (/^backtrace/.test(cmd) || /^bt/.test(cmd)) {
if (!c) { if (!client) {
printNotConnected(); self.printNotConnected();
return; 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);
} }
c.step('next', 1, function (res) { term.prompt();
// Wait for break point. (disable raw mode?) });
});
} else if (/^step/.test(cmd) || /^s/.test(cmd)) { } else if (cmd == 'scripts' || cmd == 'scripts full') {
if (!c) { if (!client) {
printNotConnected(); self.printNotConnected();
return; return;
} }
c.step('in', 1, function (res) { self.printScripts();
// Wait for break point. (disable raw mode?) term.prompt();
});
} else if (/^print/.test(cmd) || /^p/.test(cmd)) { } else if (/^c(ontinue)?/.test(cmd)) {
if (!c) { if (!client) {
printNotConnected(); self.printNotConnected();
return; return;
} }
var i = cmd.indexOf(' '); client.reqContinue(function (res) {
if (i < 0) { // Wait for break point. (disable raw mode?)
console.log("print [expression]"); });
} 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(); term.prompt();
} else { });
cmd = cmd.slice(i); }
c.reqEval(cmd, function (res) {
if (res) { } else {
console.log(res.text); if (!/^\s*$/.test(cmd)) {
} else { // If it's not all white-space print this error message.
console.log(res); console.log('Unknown command "%s". Try "help"', cmd);
} }
term.prompt(); term.prompt();
}); }
} };
var restartQuestionPrompt = "The program being debugged has " +
"been started already.\n" +
"Start it from the beginning? (y or n): ";
Interface.prototype.restartQuestion = function(cb) {
var self = this;
this.term.question(restartQuestionPrompt, function (answer) {
if (/^y(es)?$/i.test(answer)) {
cb(true);
} else if (/^n(o)?$/i.test(answer)) {
cb(false);
} else { } else {
if (!/^\s*$/.test(cmd)) { console.log("Please answer y or n.");
// If it's not all white-space print this error message. self.restartQuestion(cb);
console.log('Unknown command "%s". Try "help"', cmd);
}
term.prompt();
} }
}); });
} };
Interface.prototype.killChild = function() {
if (this.client) {
this.client.destroy();
this.client = null;
}
if (this.child) {
this.child.kill();
this.child = null;
}
};
Interface.prototype.trySpawn = function(cb) {
this.killChild();
this.child = spawn(process.execPath, args, { customFds: [0, 1, 2] });
var self = this;
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();
if (cb) cb();
});
client.on('close', function () {
console.log("\nprogram terminated");
self.client = null;
self.killChild();
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();
};
Interface.prototype.printScripts = function() {
var client = this.client;
var text = '';
for (var id in client.scripts) {
var script = client.scripts[id];
if (typeof script == 'object' && script.name) {
text += script.name == client.currentScript ? '* ' : ' ';
text += script.name + '\n';
}
}
process.stdout.write(text);
};

Loading…
Cancel
Save