Browse Source

Add beginning of build-in debugger

+ test-debugger-client (which is currently broken)
v0.7.4-release
Ryan Dahl 14 years ago
parent
commit
8d82ec2130
  1. 221
      lib/_debugger.js
  2. 9
      src/node.js
  3. 63
      test/simple/test-debugger-client.js

221
lib/_debugger.js

@ -0,0 +1,221 @@
var net = require('net');
var readline = require('readline');
var inherits = require('util').inherits;
exports.port = 5858;
exports.start = function (pid) {
if (pid) {
process.kill(pid, "SIGUSR1");
setTimeout(tryConnect, 100);
} else {
tryConnect();
}
};
var c;
function tryConnect() {
c = new Client();
process.stdout.write("connecting...");
c.connect(exports.port, function () {
process.stdout.write("ok\r\n");
startInterface();
});
}
//
// 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;
};
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;
};
function Client() {
net.Stream.call(this);
var protocol = this.protocol = new Protocol(c);
this._reqCallbacks = [];
var socket = this;
// 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._onResponse = function(res) {
console.error(res);
for (var i = 0; i < this._reqCallbacks.length; i++) {
var cb = this._reqCallbacks[i];
if (this._reqCallbacks[i].request_seq == cb.request_seq) break;
}
if (cb) {
this._reqCallbacks.splice(i, 1);
cb(res.body);
} else if (res.headers.Type == 'connect') {
// do nothing
} else {
console.error("unhandled res: %j", 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);
});
};
var helpMessage = "Commands: version, eval, help, quit";
function startInterface() {
var i = readline.createInterface(process.stdout);
var stdin = process.openStdin();
stdin.addListener('data', function(chunk) {
i.write(chunk);
});
var prompt = '> ';
i.setPrompt(prompt);
i.prompt();
i.on('SIGINT', function() {
i.close();
});
i.on('line', function(cmd) {
if (cmd == 'quit') {
process.exit(0);
} else if (/^help/.test(cmd)) {
console.log(helpMessage);
i.prompt();
} else if ('version' == cmd) {
c.reqVersion(function (v) {
console.log(v);
i.prompt();
});
} else if (/^eval/.test(cmd)) {
var req = {
command: 'evaluate',
arguments: { 'expression': cmd.slice(5) }
};
c.req(req, function (res) {
console.log(res);
i.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);
}
i.prompt();
}
});
i.on('close', function() {
stdin.destroy();
});
}

9
src/node.js

@ -535,6 +535,14 @@
} }
if (process.argv[1]) { if (process.argv[1]) {
if (process.argv[1] == 'debug') {
// Start the debugger agent
var d = requireNative('_debugger');
var pid = process.argv[2];
d.start(pid);
} else {
// Load module // Load module
if (process.argv[1].charAt(0) != '/' && if (process.argv[1].charAt(0) != '/' &&
!(/^http:\/\//).exec(process.argv[1])) { !(/^http:\/\//).exec(process.argv[1])) {
@ -543,6 +551,7 @@
// REMOVEME: nextTick should not be necessary. This hack to get // REMOVEME: nextTick should not be necessary. This hack to get
// test/simple/test-exception-handler2.js working. // test/simple/test-exception-handler2.js working.
process.nextTick(module.runMain); process.nextTick(module.runMain);
}
} else if (process._eval) { } else if (process._eval) {
// -e, --eval // -e, --eval

63
test/simple/test-debugger-client.js

@ -0,0 +1,63 @@
var common = require('../common');
var assert = require('assert');
var d = require('_debugger');
var spawn = require('child_process').spawn;
var resCount = 0;
var p = new d.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);
var n = spawn(process.execPath,
['-e', 'setInterval(function () { console.log("blah"); }, 1000);']);
var connected = false;
n.stdout.once('data', function () {
console.log("new node process: %d", n.pid);
process.kill(n.pid, "SIGUSR1");
console.log("signaling it with SIGUSR1");
});
var didTryConnect = false;
n.stderr.setEncoding('utf8');
n.stderr.on('data', function (d) {
if (didTryConnect == false && /debugger/.test(d)) {
didTryConnect = true;
tryConnect();
}
})
function tryConnect() {
// Wait for some data before trying to connect
var c = new d.Client();
process.stdout.write("connecting...");
c.connect(d.port, function () {
connected = true;
console.log("connected!");
});
c.reqVersion(function (v) {
assert.equal(process.versions.v8, v);
n.kill();
});
}
process.on('exit', function() {
assert.ok(connected);
});
Loading…
Cancel
Save