|
|
@ -6,96 +6,14 @@ var spawn = require('child_process').spawn; |
|
|
|
exports.port = 5858; |
|
|
|
|
|
|
|
exports.start = function () { |
|
|
|
startInterface(); |
|
|
|
|
|
|
|
process.on('exit', function () { |
|
|
|
if (child) child.kill(); |
|
|
|
}); |
|
|
|
var interface = new Interface(); |
|
|
|
}; |
|
|
|
|
|
|
|
var child; |
|
|
|
var c; |
|
|
|
var term; |
|
|
|
|
|
|
|
var args = process.argv.slice(2); |
|
|
|
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
|
|
|
@ -185,7 +103,7 @@ var NO_FRAME = -1; |
|
|
|
|
|
|
|
function Client() { |
|
|
|
net.Stream.call(this); |
|
|
|
var protocol = this.protocol = new Protocol(c); |
|
|
|
var protocol = this.protocol = new Protocol(this); |
|
|
|
this._reqCallbacks = []; |
|
|
|
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) { |
|
|
|
var req = { |
|
|
|
command: 'continue', |
|
|
@ -394,93 +312,112 @@ function SourceInfo(body) { |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var restartQuestionPrompt = "The program being debugged has " + |
|
|
|
"been started already.\n" + |
|
|
|
"Start it from the beginning? (y or n): "; |
|
|
|
// 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; |
|
|
|
|
|
|
|
function restartQuestion (cb) { |
|
|
|
term.question(restartQuestionPrompt, 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."); |
|
|
|
restartQuestion(cb); |
|
|
|
} |
|
|
|
process.on('exit', function () { |
|
|
|
self.killChild(); |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
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); |
|
|
|
} |
|
|
|
|
|
|
|
var stdin = process.openStdin(); |
|
|
|
stdin.addListener('data', function(chunk) { |
|
|
|
term.write(chunk); |
|
|
|
}); |
|
|
|
|
|
|
|
function printNotConnected () { |
|
|
|
console.log("Program not running. Try 'run'."); |
|
|
|
term.setPrompt('debug> '); |
|
|
|
term.prompt(); |
|
|
|
} |
|
|
|
|
|
|
|
this.quitTried = false; |
|
|
|
|
|
|
|
function startInterface() { |
|
|
|
|
|
|
|
term = readline.createInterface(process.stdout); |
|
|
|
term.on('SIGINT', function () { |
|
|
|
self.tryQuit(); |
|
|
|
}); |
|
|
|
|
|
|
|
var stdin = process.openStdin(); |
|
|
|
stdin.addListener('data', function(chunk) { |
|
|
|
term.write(chunk); |
|
|
|
|
|
|
|
term.on('close', function () { |
|
|
|
self.tryQuit(); |
|
|
|
}); |
|
|
|
|
|
|
|
var prompt = 'debug> '; |
|
|
|
term.on('line', function(cmd) { |
|
|
|
|
|
|
|
term.setPrompt('debug> '); |
|
|
|
term.prompt(); |
|
|
|
// trim whitespace
|
|
|
|
cmd = cmd.replace(/^\s*/, '').replace(/\s*$/, ''); |
|
|
|
self.handleCommand(cmd); |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
var quitTried = false; |
|
|
|
|
|
|
|
function tryQuit() { |
|
|
|
if (quitTried) return; |
|
|
|
quitTried = true; |
|
|
|
killChild(); |
|
|
|
term.close(); |
|
|
|
Interface.prototype.tryQuit = function() { |
|
|
|
if (this.quitTried) return; |
|
|
|
this.quitTried = true; |
|
|
|
this.killChild(); |
|
|
|
this.term.close(); |
|
|
|
process.exit(0); |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
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); |
|
|
|
|
|
|
|
term.on('SIGINT', tryQuit); |
|
|
|
term.on('close', tryQuit); |
|
|
|
this.client.currentSourceLine = r.sourceLine; |
|
|
|
this.client.currentFrame = 0; |
|
|
|
this.client.currentScript = r.script.name; |
|
|
|
|
|
|
|
term.on('line', function(cmd) { |
|
|
|
// trim whitespace
|
|
|
|
cmd = cmd.replace(/^\s*/, '').replace(/\s*$/, ''); |
|
|
|
console.log(result); |
|
|
|
|
|
|
|
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') { |
|
|
|
tryQuit(); |
|
|
|
self.tryQuit(); |
|
|
|
|
|
|
|
} else if (/^r(un)?/.test(cmd)) { |
|
|
|
if (child) { |
|
|
|
restartQuestion(function (yes) { |
|
|
|
if (self.child) { |
|
|
|
self.restartQuestion(function (yes) { |
|
|
|
if (!yes) { |
|
|
|
term.prompt(); |
|
|
|
} else { |
|
|
|
console.log("restarting..."); |
|
|
|
trySpawn(function () { |
|
|
|
c.reqContinue(); |
|
|
|
}); |
|
|
|
self.trySpawn(); |
|
|
|
} |
|
|
|
}); |
|
|
|
} else { |
|
|
|
trySpawn(function () { |
|
|
|
c.reqContinue(); |
|
|
|
}); |
|
|
|
self.trySpawn(); |
|
|
|
} |
|
|
|
|
|
|
|
} else if (/^help/.test(cmd)) { |
|
|
@ -488,31 +425,31 @@ function startInterface() { |
|
|
|
term.prompt(); |
|
|
|
|
|
|
|
} else if ('version' == cmd) { |
|
|
|
if (!c) { |
|
|
|
printNotConnected(); |
|
|
|
if (!client) { |
|
|
|
self.printNotConnected(); |
|
|
|
return; |
|
|
|
} |
|
|
|
c.reqVersion(function (v) { |
|
|
|
client.reqVersion(function (v) { |
|
|
|
console.log(v); |
|
|
|
term.prompt(); |
|
|
|
}); |
|
|
|
|
|
|
|
} else if (/info +breakpoints/.test(cmd)) { |
|
|
|
if (!c) { |
|
|
|
printNotConnected(); |
|
|
|
if (!client) { |
|
|
|
self.printNotConnected(); |
|
|
|
return; |
|
|
|
} |
|
|
|
c.listbreakpoints(function (res) { |
|
|
|
client.listbreakpoints(function (res) { |
|
|
|
console.log(res); |
|
|
|
term.prompt(); |
|
|
|
}); |
|
|
|
|
|
|
|
} else if (/^backtrace/.test(cmd) || /^bt/.test(cmd)) { |
|
|
|
if (!c) { |
|
|
|
printNotConnected(); |
|
|
|
if (!client) { |
|
|
|
self.printNotConnected(); |
|
|
|
return; |
|
|
|
} |
|
|
|
c.reqBacktrace(function (bt) { |
|
|
|
client.reqBacktrace(function (bt) { |
|
|
|
if (/full/.test(cmd)) { |
|
|
|
console.log(bt); |
|
|
|
} else if (bt.totalFrames == 0) { |
|
|
@ -529,43 +466,43 @@ function startInterface() { |
|
|
|
}); |
|
|
|
|
|
|
|
} else if (cmd == 'scripts' || cmd == 'scripts full') { |
|
|
|
if (!c) { |
|
|
|
printNotConnected(); |
|
|
|
if (!client) { |
|
|
|
self.printNotConnected(); |
|
|
|
return; |
|
|
|
} |
|
|
|
printScripts(); |
|
|
|
self.printScripts(); |
|
|
|
term.prompt(); |
|
|
|
|
|
|
|
} else if (/^continue/.test(cmd) || /^c/.test(cmd)) { |
|
|
|
if (!c) { |
|
|
|
printNotConnected(); |
|
|
|
} else if (/^c(ontinue)?/.test(cmd)) { |
|
|
|
if (!client) { |
|
|
|
self.printNotConnected(); |
|
|
|
return; |
|
|
|
} |
|
|
|
c.reqContinue(function (res) { |
|
|
|
client.reqContinue(function (res) { |
|
|
|
// Wait for break point. (disable raw mode?)
|
|
|
|
}); |
|
|
|
|
|
|
|
} else if (/^next/.test(cmd) || /^n/.test(cmd)) { |
|
|
|
if (!c) { |
|
|
|
printNotConnected(); |
|
|
|
if (!client) { |
|
|
|
self.printNotConnected(); |
|
|
|
return; |
|
|
|
} |
|
|
|
c.step('next', 1, function (res) { |
|
|
|
client.step('next', 1, function (res) { |
|
|
|
// Wait for break point. (disable raw mode?)
|
|
|
|
}); |
|
|
|
|
|
|
|
} else if (/^step/.test(cmd) || /^s/.test(cmd)) { |
|
|
|
if (!c) { |
|
|
|
printNotConnected(); |
|
|
|
if (!client) { |
|
|
|
self.printNotConnected(); |
|
|
|
return; |
|
|
|
} |
|
|
|
c.step('in', 1, function (res) { |
|
|
|
client.step('in', 1, function (res) { |
|
|
|
// Wait for break point. (disable raw mode?)
|
|
|
|
}); |
|
|
|
|
|
|
|
} else if (/^print/.test(cmd) || /^p/.test(cmd)) { |
|
|
|
if (!c) { |
|
|
|
printNotConnected(); |
|
|
|
if (!client) { |
|
|
|
self.printNotConnected(); |
|
|
|
return; |
|
|
|
} |
|
|
|
var i = cmd.indexOf(' '); |
|
|
@ -574,7 +511,7 @@ function startInterface() { |
|
|
|
term.prompt(); |
|
|
|
} else { |
|
|
|
cmd = cmd.slice(i); |
|
|
|
c.reqEval(cmd, function (res) { |
|
|
|
client.reqEval(cmd, function (res) { |
|
|
|
if (res) { |
|
|
|
console.log(res.text); |
|
|
|
} else { |
|
|
@ -591,5 +528,100 @@ function startInterface() { |
|
|
|
} |
|
|
|
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 { |
|
|
|
console.log("Please answer y or n."); |
|
|
|
self.restartQuestion(cb); |
|
|
|
} |
|
|
|
}); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
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); |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|