Skip to content

Commit 8d82ec2

Browse files
committed
Add beginning of build-in debugger
+ test-debugger-client (which is currently broken)
1 parent 0df804b commit 8d82ec2

File tree

3 files changed

+300
-7
lines changed

3 files changed

+300
-7
lines changed

lib/_debugger.js

Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
var net = require('net');
2+
var readline = require('readline');
3+
var inherits = require('util').inherits;
4+
5+
exports.port = 5858;
6+
7+
exports.start = function (pid) {
8+
if (pid) {
9+
process.kill(pid, "SIGUSR1");
10+
setTimeout(tryConnect, 100);
11+
} else {
12+
tryConnect();
13+
}
14+
};
15+
16+
var c;
17+
18+
function tryConnect() {
19+
c = new Client();
20+
21+
process.stdout.write("connecting...");
22+
c.connect(exports.port, function () {
23+
process.stdout.write("ok\r\n");
24+
startInterface();
25+
});
26+
27+
}
28+
29+
//
30+
// Parser/Serializer for V8 debugger protocol
31+
// http://code.google.com/p/v8/wiki/DebuggerProtocol
32+
//
33+
// Usage:
34+
// p = new Protocol();
35+
//
36+
// p.onResponse = function (res) {
37+
// // do stuff with response from V8
38+
// };
39+
//
40+
// socket.setEncoding('utf8');
41+
// socket.on('data', function (s) {
42+
// // Pass strings into the protocol
43+
// p.execute(s);
44+
// });
45+
//
46+
//
47+
function Protocol() {
48+
this._newRes();
49+
}
50+
exports.Protocol = Protocol;
51+
52+
53+
Protocol.prototype._newRes = function(raw) {
54+
this.res = { raw: raw || '', headers: {} };
55+
this.state = 'headers';
56+
this.reqSeq = 1;
57+
};
58+
59+
60+
Protocol.prototype.execute = function(d) {
61+
var res = this.res;
62+
res.raw += d;
63+
64+
switch (this.state) {
65+
case 'headers':
66+
var endHeaderIndex = res.raw.indexOf('\r\n\r\n');
67+
68+
if (endHeaderIndex < 0) break;
69+
70+
var lines = res.raw.slice(0, endHeaderIndex).split('\r\n');
71+
for (var i = 0; i < lines.length; i++) {
72+
var kv = lines[i].split(/: +/);
73+
res.headers[kv[0]] = kv[1];
74+
}
75+
76+
this.contentLength = +res.headers['Content-Length'];
77+
this.bodyStartIndex = endHeaderIndex + 4;
78+
79+
this.state = 'body';
80+
if (res.raw.length - this.bodyStartIndex < this.contentLength) break;
81+
// pass thru
82+
83+
case 'body':
84+
if (res.raw.length - this.bodyStartIndex >= this.contentLength) {
85+
res.body =
86+
res.raw.slice(this.bodyStartIndex,
87+
this.bodyStartIndex + this.contentLength);
88+
// JSON parse body?
89+
res.body = res.body.length ? JSON.parse(res.body) : {};
90+
91+
// Done!
92+
this.onResponse(res);
93+
94+
this._newRes(res.raw.slice(this.bodyStartIndex + this.contentLength));
95+
}
96+
break;
97+
98+
default:
99+
throw new Error("Unknown state");
100+
break;
101+
}
102+
};
103+
104+
105+
Protocol.prototype.serialize = function(req) {
106+
req.type = 'request';
107+
req.seq = this.reqSeq++;
108+
var json = JSON.stringify(req);
109+
return 'Content-Length: ' + json.length + '\r\n\r\n' + json;
110+
};
111+
112+
113+
114+
115+
function Client() {
116+
net.Stream.call(this);
117+
var protocol = this.protocol = new Protocol(c);
118+
this._reqCallbacks = [];
119+
var socket = this;
120+
121+
// Note that 'Protocol' requires strings instead of Buffers.
122+
socket.setEncoding('utf8');
123+
socket.on('data', function(d) {
124+
protocol.execute(d);
125+
});
126+
127+
protocol.onResponse = this._onResponse.bind(this);
128+
};
129+
inherits(Client, net.Stream);
130+
exports.Client = Client;
131+
132+
133+
Client.prototype._onResponse = function(res) {
134+
console.error(res);
135+
for (var i = 0; i < this._reqCallbacks.length; i++) {
136+
var cb = this._reqCallbacks[i];
137+
if (this._reqCallbacks[i].request_seq == cb.request_seq) break;
138+
}
139+
140+
if (cb) {
141+
this._reqCallbacks.splice(i, 1);
142+
cb(res.body);
143+
} else if (res.headers.Type == 'connect') {
144+
// do nothing
145+
} else {
146+
console.error("unhandled res: %j", res.body);
147+
}
148+
};
149+
150+
151+
Client.prototype.req = function(req, cb) {
152+
this.write(this.protocol.serialize(req));
153+
cb.request_seq = req.seq;
154+
this._reqCallbacks.push(cb);
155+
};
156+
157+
158+
Client.prototype.reqVersion = function(cb) {
159+
this.req({ command: 'version' } , function (res) {
160+
if (cb) cb(res.body.V8Version, res.running);
161+
});
162+
};
163+
164+
165+
var helpMessage = "Commands: version, eval, help, quit";
166+
167+
168+
function startInterface() {
169+
170+
var i = readline.createInterface(process.stdout);
171+
var stdin = process.openStdin();
172+
stdin.addListener('data', function(chunk) {
173+
i.write(chunk);
174+
});
175+
176+
var prompt = '> ';
177+
178+
i.setPrompt(prompt);
179+
i.prompt();
180+
181+
i.on('SIGINT', function() {
182+
i.close();
183+
});
184+
185+
i.on('line', function(cmd) {
186+
if (cmd == 'quit') {
187+
process.exit(0);
188+
} else if (/^help/.test(cmd)) {
189+
console.log(helpMessage);
190+
i.prompt();
191+
192+
} else if ('version' == cmd) {
193+
c.reqVersion(function (v) {
194+
console.log(v);
195+
i.prompt();
196+
});
197+
198+
} else if (/^eval/.test(cmd)) {
199+
var req = {
200+
command: 'evaluate',
201+
arguments: { 'expression': cmd.slice(5) }
202+
};
203+
204+
c.req(req, function (res) {
205+
console.log(res);
206+
i.prompt();
207+
});
208+
209+
} else {
210+
if (!/^\s*$/.test(cmd)) {
211+
// If it's not all white-space print this error message.
212+
console.log('Unknown command "%s". Try "help"', cmd);
213+
}
214+
i.prompt();
215+
}
216+
});
217+
218+
i.on('close', function() {
219+
stdin.destroy();
220+
});
221+
}

src/node.js

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -535,14 +535,23 @@
535535
}
536536

537537
if (process.argv[1]) {
538-
// Load module
539-
if (process.argv[1].charAt(0) != '/' &&
540-
!(/^http:\/\//).exec(process.argv[1])) {
541-
process.argv[1] = path.join(cwd, process.argv[1]);
538+
539+
if (process.argv[1] == 'debug') {
540+
// Start the debugger agent
541+
var d = requireNative('_debugger');
542+
var pid = process.argv[2];
543+
d.start(pid);
544+
545+
} else {
546+
// Load module
547+
if (process.argv[1].charAt(0) != '/' &&
548+
!(/^http:\/\//).exec(process.argv[1])) {
549+
process.argv[1] = path.join(cwd, process.argv[1]);
550+
}
551+
// REMOVEME: nextTick should not be necessary. This hack to get
552+
// test/simple/test-exception-handler2.js working.
553+
process.nextTick(module.runMain);
542554
}
543-
// REMOVEME: nextTick should not be necessary. This hack to get
544-
// test/simple/test-exception-handler2.js working.
545-
process.nextTick(module.runMain);
546555

547556
} else if (process._eval) {
548557
// -e, --eval

test/simple/test-debugger-client.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
var common = require('../common');
2+
var assert = require('assert');
3+
var d = require('_debugger');
4+
5+
var spawn = require('child_process').spawn;
6+
7+
8+
var resCount = 0;
9+
var p = new d.Protocol();
10+
p.onResponse = function (res) {
11+
resCount++;
12+
};
13+
14+
p.execute("Type: connect\r\n" +
15+
"V8-Version: 3.0.4.1\r\n" +
16+
"Protocol-Version: 1\r\n" +
17+
"Embedding-Host: node v0.3.3-pre\r\n" +
18+
"Content-Length: 0\r\n\r\n");
19+
assert.equal(1, resCount);
20+
21+
var n = spawn(process.execPath,
22+
['-e', 'setInterval(function () { console.log("blah"); }, 1000);']);
23+
24+
25+
var connected = false;
26+
27+
n.stdout.once('data', function () {
28+
console.log("new node process: %d", n.pid);
29+
process.kill(n.pid, "SIGUSR1");
30+
console.log("signaling it with SIGUSR1");
31+
32+
});
33+
34+
var didTryConnect = false;
35+
n.stderr.setEncoding('utf8');
36+
n.stderr.on('data', function (d) {
37+
if (didTryConnect == false && /debugger/.test(d)) {
38+
didTryConnect = true;
39+
tryConnect();
40+
}
41+
})
42+
43+
44+
function tryConnect() {
45+
// Wait for some data before trying to connect
46+
var c = new d.Client();
47+
process.stdout.write("connecting...");
48+
c.connect(d.port, function () {
49+
connected = true;
50+
console.log("connected!");
51+
});
52+
53+
c.reqVersion(function (v) {
54+
assert.equal(process.versions.v8, v);
55+
n.kill();
56+
});
57+
}
58+
59+
60+
process.on('exit', function() {
61+
assert.ok(connected);
62+
});
63+

0 commit comments

Comments
 (0)