From 0df804b9e7b509797e652a0e8657989023a01911 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Sat, 18 Dec 2010 11:17:29 -0800 Subject: [PATCH 01/46] start debug agent on SIGUSR1 --- src/node.cc | 86 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 58 insertions(+), 28 deletions(-) diff --git a/src/node.cc b/src/node.cc index 9d8405571f..04a96a4187 100644 --- a/src/node.cc +++ b/src/node.cc @@ -87,6 +87,7 @@ static ev_idle tick_spinner; static bool need_tick_cb; static Persistent 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; @@ -1862,6 +1863,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; @@ -1967,37 +2003,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. From 8d82ec21308812132a71afe45c5bc517b4a43354 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Tue, 21 Dec 2010 19:14:29 -0800 Subject: [PATCH 02/46] Add beginning of build-in debugger + test-debugger-client (which is currently broken) --- lib/_debugger.js | 221 ++++++++++++++++++++++++++++ src/node.js | 23 ++- test/simple/test-debugger-client.js | 63 ++++++++ 3 files changed, 300 insertions(+), 7 deletions(-) create mode 100644 lib/_debugger.js create mode 100644 test/simple/test-debugger-client.js diff --git a/lib/_debugger.js b/lib/_debugger.js new file mode 100644 index 0000000000..9f95e896ce --- /dev/null +++ b/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(); + }); +} diff --git a/src/node.js b/src/node.js index 43ccdb0325..4717e65d47 100644 --- a/src/node.js +++ b/src/node.js @@ -535,14 +535,23 @@ } 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'); + var pid = process.argv[2]; + d.start(pid); + + } 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 diff --git a/test/simple/test-debugger-client.js b/test/simple/test-debugger-client.js new file mode 100644 index 0000000000..a070b7aa15 --- /dev/null +++ b/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); +}); + From 797aa97e19bbb7b9536e2a7da03012e2db018d23 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Tue, 21 Dec 2010 22:31:30 -0800 Subject: [PATCH 03/46] Fix test-debugger-client --- lib/_debugger.js | 7 ++- test/simple/test-debugger-client.js | 66 +++++++++++++++-------------- 2 files changed, 37 insertions(+), 36 deletions(-) diff --git a/lib/_debugger.js b/lib/_debugger.js index 9f95e896ce..5c0dd55501 100644 --- a/lib/_debugger.js +++ b/lib/_debugger.js @@ -131,17 +131,16 @@ 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) { + if (res.headers.Type == 'connect') { + // do nothing + } else 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); } diff --git a/test/simple/test-debugger-client.js b/test/simple/test-debugger-client.js index a070b7aa15..5239430d40 100644 --- a/test/simple/test-debugger-client.js +++ b/test/simple/test-debugger-client.js @@ -1,12 +1,12 @@ var common = require('../common'); var assert = require('assert'); -var d = require('_debugger'); +var debug = require('_debugger'); var spawn = require('child_process').spawn; var resCount = 0; -var p = new d.Protocol(); +var p = new debug.Protocol(); p.onResponse = function (res) { resCount++; }; @@ -18,46 +18,48 @@ p.execute("Type: connect\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 connectCount = 0; -var connected = false; +function test(cb) { + var nodeProcess = spawn(process.execPath, + ['-e', 'setInterval(function () { console.log("blah"); }, 1000);']); -n.stdout.once('data', function () { - console.log("new node process: %d", n.pid); - process.kill(n.pid, "SIGUSR1"); - console.log("signaling it with SIGUSR1"); - -}); + 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; -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!"); + 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, function () { + connectCount++; + console.log("connected!"); + cb(c, nodeProcess); + }); + } }); +} - c.reqVersion(function (v) { + +test(function (client, nodeProcess) { + client.reqVersion(function (v) { + console.log("version: %s", v); assert.equal(process.versions.v8, v); - n.kill(); + nodeProcess.kill(); }); -} +}); process.on('exit', function() { - assert.ok(connected); + assert.equal(1, connectCount); }); From 8e96b8ab9bd559914c18b0bdf3b291862c961f94 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 22 Dec 2010 00:48:22 -0800 Subject: [PATCH 04/46] keep track of current frame. eval works for global scope --- lib/_debugger.js | 51 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/lib/_debugger.js b/lib/_debugger.js index 5c0dd55501..0232f312ba 100644 --- a/lib/_debugger.js +++ b/lib/_debugger.js @@ -110,7 +110,7 @@ Protocol.prototype.serialize = function(req) { }; - +var NO_FRAME = -1; function Client() { net.Stream.call(this); @@ -118,6 +118,9 @@ function Client() { this._reqCallbacks = []; var socket = this; + this.currentFrame = NO_FRAME; + this.currentSourceLine = -1; + // Note that 'Protocol' requires strings instead of Buffers. socket.setEncoding('utf8'); socket.on('data', function(d) { @@ -142,7 +145,7 @@ Client.prototype._onResponse = function(res) { this._reqCallbacks.splice(i, 1); cb(res.body); } else { - console.error("unhandled res: %j", res.body); + this.emit('unhandledResponse', res.body); } }; @@ -161,7 +164,30 @@ Client.prototype.reqVersion = function(cb) { }; -var helpMessage = "Commands: version, eval, help, quit"; +Client.prototype.reqEval = function(expression, cb) { + var req = { + command: 'evaluate', + arguments: { expression: expression } + }; + + if (this.currentFrame == NO_FRAME) req.arguments.global = true; + + 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); + }); +}; + + +var helpMessage = "Commands: backtrace, version, eval, help, quit"; function startInterface() { @@ -194,13 +220,14 @@ function startInterface() { i.prompt(); }); - } else if (/^eval/.test(cmd)) { - var req = { - command: 'evaluate', - arguments: { 'expression': cmd.slice(5) } - }; + } else if ('backtrace' == cmd || 'bt' == cmd) { + c.reqBacktrace(function (bt) { + console.log(bt); + i.prompt(); + }); - c.req(req, function (res) { + } else if (/^eval/.test(cmd)) { + c.reqEval(cmd.slice(5), function (res) { console.log(res); i.prompt(); }); @@ -214,6 +241,12 @@ function startInterface() { } }); + c.on('unhandledResponse', function (res) { + console.log("\r\nunhandled res:"); + console.log(res.body); + i.prompt(); + }); + i.on('close', function() { stdin.destroy(); }); From a8417c128e0c871c6af03236fa42209dc1977caa Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 22 Dec 2010 14:31:57 -0800 Subject: [PATCH 05/46] Add more debugger tests --- lib/_debugger.js | 32 ++++++++++++- test/simple/test-debugger-client.js | 73 ++++++++++++++++++++++++----- 2 files changed, 92 insertions(+), 13 deletions(-) diff --git a/lib/_debugger.js b/lib/_debugger.js index 0232f312ba..72af3a5cbc 100644 --- a/lib/_debugger.js +++ b/lib/_debugger.js @@ -187,7 +187,30 @@ Client.prototype.reqBacktrace = function(cb) { }; -var helpMessage = "Commands: backtrace, version, eval, help, quit"; +// 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) { + this.req({ command: 'scripts' } , function (res) { + if (cb) cb(res.body); + }); +}; + + +var helpMessage = "Commands: scripts, backtrace, version, eval, help, quit"; function startInterface() { @@ -226,6 +249,13 @@ function startInterface() { i.prompt(); }); + } else if (/^scripts/.test(cmd)) { + c.reqScripts(function (res) { + var text = res.map(function (x) { return x.text; }); + console.log(text.join('\n')); + i.prompt(); + }); + } else if (/^eval/.test(cmd)) { c.reqEval(cmd.slice(5), function (res) { console.log(res); diff --git a/test/simple/test-debugger-client.js b/test/simple/test-debugger-client.js index 5239430d40..434a05ae66 100644 --- a/test/simple/test-debugger-client.js +++ b/test/simple/test-debugger-client.js @@ -18,17 +18,59 @@ p.execute("Type: connect\r\n" + "Content-Length: 0\r\n\r\n"); assert.equal(1, 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 (s) { + console.error("got %d scripts", s.length); + var foundMainScript = false; + for (var i = 0; i < s.length; i++) { + if (s[i].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 test(cb) { +function doTest(cb, done) { var nodeProcess = spawn(process.execPath, ['-e', 'setInterval(function () { console.log("blah"); }, 1000);']); nodeProcess.stdout.once('data', function () { - console.log("new node process: %d", nodeProcess.pid); + console.log(">>> new node process: %d", nodeProcess.pid); process.kill(nodeProcess.pid, "SIGUSR1"); - console.log("signaling it with SIGUSR1"); + console.log(">>> signaling it with SIGUSR1"); }); var didTryConnect = false; @@ -39,27 +81,34 @@ function test(cb) { // Wait for some data before trying to connect var c = new debug.Client(); - process.stdout.write("connecting..."); + process.stdout.write(">>> connecting..."); c.connect(debug.port, function () { connectCount++; console.log("connected!"); - cb(c, nodeProcess); + cb(c, function () { + console.error(">>> killing node process %d\n\n", nodeProcess.pid); + nodeProcess.kill(); + done(); + }); }); } }); } -test(function (client, nodeProcess) { - client.reqVersion(function (v) { - console.log("version: %s", v); - assert.equal(process.versions.v8, v); - nodeProcess.kill(); +function run () { + var t = tests[0]; + if (!t) return; + + doTest(t, function () { + tests.shift(); + run(); }); -}); +} +run(); process.on('exit', function() { - assert.equal(1, connectCount); + assert.equal(expectedConnections, connectCount); }); From 4e81cf7def7c672b620086323c34010d2718e697 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 22 Dec 2010 14:40:28 -0800 Subject: [PATCH 06/46] Debugger client emits ready after recving initial res --- lib/_debugger.js | 4 +++- test/simple/test-debugger-client.js | 7 ++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/_debugger.js b/lib/_debugger.js index 72af3a5cbc..25f82e72e1 100644 --- a/lib/_debugger.js +++ b/lib/_debugger.js @@ -19,7 +19,8 @@ function tryConnect() { c = new Client(); process.stdout.write("connecting..."); - c.connect(exports.port, function () { + c.connect(exports.port); + c.on('ready', function () { process.stdout.write("ok\r\n"); startInterface(); }); @@ -141,6 +142,7 @@ Client.prototype._onResponse = function(res) { if (res.headers.Type == 'connect') { // do nothing + this.emit('ready'); } else if (cb) { this._reqCallbacks.splice(i, 1); cb(res.body); diff --git a/test/simple/test-debugger-client.js b/test/simple/test-debugger-client.js index 434a05ae66..c542f89c49 100644 --- a/test/simple/test-debugger-client.js +++ b/test/simple/test-debugger-client.js @@ -65,7 +65,7 @@ var connectCount = 0; function doTest(cb, done) { var nodeProcess = spawn(process.execPath, - ['-e', 'setInterval(function () { console.log("blah"); }, 1000);']); + ['-e', 'setInterval(function () { console.log("blah"); }, 100);']); nodeProcess.stdout.once('data', function () { console.log(">>> new node process: %d", nodeProcess.pid); @@ -82,9 +82,10 @@ function doTest(cb, done) { // Wait for some data before trying to connect var c = new debug.Client(); process.stdout.write(">>> connecting..."); - c.connect(debug.port, function () { + c.connect(debug.port) + c.on('ready', function () { connectCount++; - console.log("connected!"); + console.log("ready!"); cb(c, function () { console.error(">>> killing node process %d\n\n", nodeProcess.pid); nodeProcess.kill(); From 0dcbe3f74a0d92a399c508c92110648e76917d9b Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 22 Dec 2010 17:17:34 -0800 Subject: [PATCH 07/46] Fork out to debugger on debugger statements Also implement continue in Client. --- lib/_debugger.js | 36 +++++++++++++++++++++++++---- lib/readline.js | 2 ++ src/node.cc | 59 +++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 92 insertions(+), 5 deletions(-) diff --git a/lib/_debugger.js b/lib/_debugger.js index 25f82e72e1..83bc6c8fba 100644 --- a/lib/_debugger.js +++ b/lib/_debugger.js @@ -212,6 +212,13 @@ Client.prototype.reqScripts = function(cb) { }; +Client.prototype.reqContinue = function(cb) { + this.req({ command: 'continue' } , function (res) { + if (cb) cb(res.body); + }); +}; + + var helpMessage = "Commands: scripts, backtrace, version, eval, help, quit"; @@ -223,18 +230,33 @@ function startInterface() { i.write(chunk); }); - var prompt = '> '; + var prompt = 'debug> '; i.setPrompt(prompt); i.prompt(); - i.on('SIGINT', function() { + var quitTried = false; + + function tryQuit() { + if (quitTried) return; + quitTried = true; i.close(); - }); + console.log("debug done\n"); + if (c.writable) { + c.reqContinue(function (res) { + process.exit(0); + }); + } else { + process.exit(0); + } + } + + i.on('SIGINT', tryQuit); + i.on('close', tryQuit); i.on('line', function(cmd) { if (cmd == 'quit') { - process.exit(0); + tryQuit(); } else if (/^help/.test(cmd)) { console.log(helpMessage); i.prompt(); @@ -251,6 +273,12 @@ function startInterface() { i.prompt(); }); + } else if ('continue' == cmd || 'c' == cmd) { + c.reqContinue(function (res) { + console.log(res); + i.prompt(); + }); + } else if (/^scripts/.test(cmd)) { c.reqScripts(function (res) { var text = res.map(function (x) { return x.text; }); diff --git a/lib/readline.js b/lib/readline.js index 341b20b479..3ae43f4735 100644 --- a/lib/readline.js +++ b/lib/readline.js @@ -160,6 +160,8 @@ Interface.prototype._refreshLine = function() { Interface.prototype.close = function(d) { + if (this._closing) return; + this._closing = true; if (this.enabled) { tty.setRawMode(false); } diff --git a/src/node.cc b/src/node.cc index 04a96a4187..d81aab841a 100644 --- a/src/node.cc +++ b/src/node.cc @@ -17,6 +17,11 @@ #include /* getpwnam() */ #include /* getgrnam() */ +// waitpid +#include +#include + + #include "platform.h" #include @@ -1908,6 +1913,57 @@ static int RegisterSignalHandler(int signal, void (*handler)(int)) { } +static bool debugger_slave_running = false; + +static void HandleDebugEvent(DebugEvent event, + Handle exec_state, + Handle event_data, + Handle data) { + HandleScope scope; + + if (debugger_slave_running) return; + + if (event != Break) { + return; + } + + // Then we take one of two actions + // 1. Inspect the environ variable NODE_DEBUG_PROG; if it is not empty // + // then start it. (TODO) + // 2. Start the built-in debugger. + + + size_t size = 2*PATH_MAX; + char node_path[size]; + OS::GetExecutablePath(node_path, &size); + + int pid = vfork(); + + if (pid == -1) { + perror("vfork()"); + return; + } + + if (pid == 0) { + // Child process + char *argv[] = { node_path, "debug", NULL }; + execvp(node_path, argv); + perror("execvp()"); + _exit(127); + } + + debugger_slave_running = true; + + // We've hit some debugger event. First we will enable the debugger agent. + EnableDebug(true); + + // TODO probably need to waitpid here or something to avoid zombies. + // int status; + // waitpid(pid, &status, 0); + Debug::DebugBreak(); +} + + int Start(int argc, char *argv[]) { // Hack aroung with the argv pointer. Used for process.title = "blah". argv = node::Platform::SetupArgs(argc, argv); @@ -2022,7 +2078,8 @@ int Start(int argc, char *argv[]) { // If the --debug flag was specified then initialize the debug thread. if (node::use_debug_agent) { - EnableDebug(debug_wait_connect); + // XXX: only use if debug flag enabled? + Debug::SetDebugEventListener(HandleDebugEvent); } else { RegisterSignalHandler(SIGUSR1, EnableDebugSignalHandler); ev_async_init(&enable_debug, EnableDebug2); From b5aed43f04b50b2e21bf2c370b859f9b8fbd0270 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 22 Dec 2010 18:30:56 -0800 Subject: [PATCH 08/46] Add better breakpoint text --- lib/_debugger.js | 145 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 119 insertions(+), 26 deletions(-) diff --git a/lib/_debugger.js b/lib/_debugger.js index 83bc6c8fba..d1920943ae 100644 --- a/lib/_debugger.js +++ b/lib/_debugger.js @@ -142,7 +142,10 @@ Client.prototype._onResponse = function(res) { if (res.headers.Type == 'connect') { // do nothing + console.log(res); this.emit('ready'); + } else if (res.body && res.body.event == 'break') { + this.emit('break', res.body); } else if (cb) { this._reqCallbacks.splice(i, 1); cb(res.body); @@ -214,33 +217,76 @@ Client.prototype.reqScripts = function(cb) { Client.prototype.reqContinue = function(cb) { this.req({ command: 'continue' } , function (res) { - if (cb) cb(res.body); + if (cb) cb(res); }); }; var helpMessage = "Commands: scripts, backtrace, version, eval, help, quit"; +function SourceUnderline(source_text, position) { + if (!source_text) { + 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 (source_text[i] == '\t') { + underline += '\t'; + } else { + underline += ' '; + } + } + underline += '^'; + + // Return the source line text with the underline beneath. + return source_text + '\n' + underline; +} + +function SourceInfo(body) { + var result = ''; + + if (body.script) { + if (body.script.name) { + result += body.script.name; + } else { + result += '[unnamed]'; + } + } + result += ' line '; + result += body.sourceLine + 1; + result += ' column '; + result += body.sourceColumn + 1; + + return result; +} + + + + function startInterface() { - var i = readline.createInterface(process.stdout); + var term = readline.createInterface(process.stdout); var stdin = process.openStdin(); stdin.addListener('data', function(chunk) { - i.write(chunk); + term.write(chunk); }); var prompt = 'debug> '; - i.setPrompt(prompt); - i.prompt(); + term.setPrompt('debug> '); + term.prompt(); var quitTried = false; function tryQuit() { if (quitTried) return; quitTried = true; - i.close(); + term.close(); console.log("debug done\n"); if (c.writable) { c.reqContinue(function (res) { @@ -251,45 +297,64 @@ function startInterface() { } } - i.on('SIGINT', tryQuit); - i.on('close', tryQuit); + term.on('SIGINT', tryQuit); + term.on('close', tryQuit); + c.on('close', function () { + process.exit(0); + }); - i.on('line', function(cmd) { + term.on('line', function(cmd) { if (cmd == 'quit') { tryQuit(); } else if (/^help/.test(cmd)) { console.log(helpMessage); - i.prompt(); + term.prompt(); } else if ('version' == cmd) { c.reqVersion(function (v) { console.log(v); - i.prompt(); + term.prompt(); }); - } else if ('backtrace' == cmd || 'bt' == cmd) { + } else if (/^backtrace/.test(cmd) || /^bt/.test(cmd)) { c.reqBacktrace(function (bt) { - console.log(bt); - i.prompt(); + if (/full/.test(cmd)) { + console.log(bt); + } else if (bt.totalFrames == 0) { + console.log('(empty stack)'); + } else { + var result = 'Frames #' + bt.fromFrame + + ' to #' + (bt.toFrame - 1) + + ' of ' + bt.totalFrames + '\n'; + 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 ('continue' == cmd || 'c' == cmd) { + } else if (/^continue/.test(cmd) || /^c/.test(cmd)) { c.reqContinue(function (res) { - console.log(res); - i.prompt(); + // Wait for break point. (disable raw mode?) }); } else if (/^scripts/.test(cmd)) { c.reqScripts(function (res) { - var text = res.map(function (x) { return x.text; }); - console.log(text.join('\n')); - i.prompt(); + if (/full/.test(cmd)) { + console.log(res); + } else { + var text = res.map(function (x) { return x.text; }); + console.log(text.join('\n')); + } + term.prompt(); }); } else if (/^eval/.test(cmd)) { c.reqEval(cmd.slice(5), function (res) { console.log(res); - i.prompt(); + term.prompt(); }); } else { @@ -297,17 +362,45 @@ function startInterface() { // If it's not all white-space print this error message. console.log('Unknown command "%s". Try "help"', cmd); } - i.prompt(); + term.prompt(); } }); c.on('unhandledResponse', function (res) { console.log("\r\nunhandled res:"); - console.log(res.body); - i.prompt(); + console.log(res); + term.prompt(); }); - i.on('close', function() { - stdin.destroy(); + 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; + + console.log(result); + + term.prompt(); }); } From 3be4f097a3dc1228e99b48e9beca9120c9c70a5f Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 22 Dec 2010 18:39:22 -0800 Subject: [PATCH 09/46] Debugger: Fix some output --- lib/_debugger.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/_debugger.js b/lib/_debugger.js index d1920943ae..d3936def82 100644 --- a/lib/_debugger.js +++ b/lib/_debugger.js @@ -142,7 +142,6 @@ Client.prototype._onResponse = function(res) { if (res.headers.Type == 'connect') { // do nothing - console.log(res); this.emit('ready'); } else if (res.body && res.body.event == 'break') { this.emit('break', res.body); @@ -373,7 +372,7 @@ function startInterface() { }); c.on('break', function (res) { - var result = ''; + var result = '\r\n'; if (res.body.breakpoints) { result += 'breakpoint'; if (res.body.breakpoints.length > 1) { From 8874c51d0465d80c9296cdd55b6eb17de49253db Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 22 Dec 2010 23:31:06 -0800 Subject: [PATCH 10/46] Disable OPOST Helps when sharing a tty with non-raw mode processes --- lib/readline.js | 16 ---------------- src/node_stdio.cc | 4 ++-- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/lib/readline.js b/lib/readline.js index 3ae43f4735..4a0565a40a 100644 --- a/lib/readline.js +++ b/lib/readline.js @@ -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 = ''; diff --git a/src/node_stdio.cc b/src/node_stdio.cc index ad098352b9..3c4c3b584c 100644 --- a/src/node_stdio.cc +++ b/src/node_stdio.cc @@ -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, From 0c928b124c25b01e4ff7f151f5c98fdf9cb7d532 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 22 Dec 2010 23:31:54 -0800 Subject: [PATCH 11/46] debugger: Clean up a few commands --- lib/_debugger.js | 61 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 49 insertions(+), 12 deletions(-) diff --git a/lib/_debugger.js b/lib/_debugger.js index d3936def82..802e2da1e6 100644 --- a/lib/_debugger.js +++ b/lib/_debugger.js @@ -174,7 +174,11 @@ Client.prototype.reqEval = function(expression, cb) { arguments: { expression: expression } }; - if (this.currentFrame == NO_FRAME) req.arguments.global = true; + 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); @@ -215,13 +219,27 @@ Client.prototype.reqScripts = function(cb) { Client.prototype.reqContinue = function(cb) { - this.req({ command: 'continue' } , function (res) { + this.req({ command: 'continue' }, function (res) { + if (cb) cb(res); + }); +}; + +// c.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: scripts, backtrace, version, eval, help, quit"; + + +var helpMessage = "Commands: print, step, next, continue, scripts, backtrace, version, quit"; function SourceUnderline(source_text, position) { if (!source_text) { @@ -303,7 +321,7 @@ function startInterface() { }); term.on('line', function(cmd) { - if (cmd == 'quit') { + if (cmd == 'quit' || cmd == 'q' || cmd == 'exit') { tryQuit(); } else if (/^help/.test(cmd)) { console.log(helpMessage); @@ -322,9 +340,7 @@ function startInterface() { } else if (bt.totalFrames == 0) { console.log('(empty stack)'); } else { - var result = 'Frames #' + bt.fromFrame + - ' to #' + (bt.toFrame - 1) + - ' of ' + bt.totalFrames + '\n'; + var result = ''; for (j = 0; j < bt.frames.length; j++) { if (j != 0) result += '\n'; result += bt.frames[j].text; @@ -336,7 +352,17 @@ function startInterface() { } else if (/^continue/.test(cmd) || /^c/.test(cmd)) { c.reqContinue(function (res) { - // Wait for break point. (disable raw mode?) + // Wait for break point. (disable raw mode?) + }); + + } else if (/^next/.test(cmd) || /^n/.test(cmd)) { + c.step('next', 1, function (res) { + // Wait for break point. (disable raw mode?) + }); + + } else if (/^step/.test(cmd) || /^s/.test(cmd)) { + c.step('in', 1, function (res) { + // Wait for break point. (disable raw mode?) }); } else if (/^scripts/.test(cmd)) { @@ -350,11 +376,22 @@ function startInterface() { term.prompt(); }); - } else if (/^eval/.test(cmd)) { - c.reqEval(cmd.slice(5), function (res) { - console.log(res); + } else if (/^print/.test(cmd) || /^p/.test(cmd)) { + var i = cmd.indexOf(' '); + if (i < 0) { + console.log("print [expression]"); term.prompt(); - }); + } else { + cmd = cmd.slice(i); + c.reqEval(cmd, function (res) { + if (res) { + console.log(res.text); + } else { + console.log(res); + } + term.prompt(); + }); + } } else { if (!/^\s*$/.test(cmd)) { From 0adc6b29bd193b27bdbbce0e2e414efbee0453ab Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 23 Dec 2010 10:10:28 -0800 Subject: [PATCH 12/46] Start on frame zero - don't do extra break --- lib/_debugger.js | 2 +- src/node.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/_debugger.js b/lib/_debugger.js index 802e2da1e6..6be3fa55b8 100644 --- a/lib/_debugger.js +++ b/lib/_debugger.js @@ -119,7 +119,7 @@ function Client() { this._reqCallbacks = []; var socket = this; - this.currentFrame = NO_FRAME; + this.currentFrame = 0; this.currentSourceLine = -1; // Note that 'Protocol' requires strings instead of Buffers. diff --git a/src/node.cc b/src/node.cc index d81aab841a..c349afb8dd 100644 --- a/src/node.cc +++ b/src/node.cc @@ -1960,7 +1960,7 @@ static void HandleDebugEvent(DebugEvent event, // TODO probably need to waitpid here or something to avoid zombies. // int status; // waitpid(pid, &status, 0); - Debug::DebugBreak(); + //Debug::DebugBreak(); } From 080daf9ddddc05c4ed9efc3e37fac7d52fb9fb17 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 23 Dec 2010 10:18:06 -0800 Subject: [PATCH 13/46] debugger: 'scripts' command was conflicting with 's' --- lib/_debugger.js | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/lib/_debugger.js b/lib/_debugger.js index 6be3fa55b8..0dd3b93144 100644 --- a/lib/_debugger.js +++ b/lib/_debugger.js @@ -350,6 +350,18 @@ function startInterface() { term.prompt(); }); + } else if (cmd == 'scripts') { + c.reqScripts(function (res) { + if (/full/.test(cmd)) { + console.log(res); + } else { + var text = res.map(function (x) { return x.text; }); + console.log(text.join('\n')); + } + term.prompt(); + }); + + } else if (/^continue/.test(cmd) || /^c/.test(cmd)) { c.reqContinue(function (res) { // Wait for break point. (disable raw mode?) @@ -365,17 +377,6 @@ function startInterface() { // Wait for break point. (disable raw mode?) }); - } else if (/^scripts/.test(cmd)) { - c.reqScripts(function (res) { - if (/full/.test(cmd)) { - console.log(res); - } else { - var text = res.map(function (x) { return x.text; }); - console.log(text.join('\n')); - } - term.prompt(); - }); - } else if (/^print/.test(cmd) || /^p/.test(cmd)) { var i = cmd.indexOf(' '); if (i < 0) { @@ -409,7 +410,7 @@ function startInterface() { }); c.on('break', function (res) { - var result = '\r\n'; + var result = ''; if (res.body.breakpoints) { result += 'breakpoint'; if (res.body.breakpoints.length > 1) { From 74cc021ec289d1f61cc9b6a2b5528d2d0e8650e4 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 23 Dec 2010 10:32:34 -0800 Subject: [PATCH 14/46] highlight current script --- lib/_debugger.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/_debugger.js b/lib/_debugger.js index 0dd3b93144..d8b7c92295 100644 --- a/lib/_debugger.js +++ b/lib/_debugger.js @@ -121,6 +121,7 @@ function Client() { this.currentFrame = 0; this.currentSourceLine = -1; + this.currentSource = null; // Note that 'Protocol' requires strings instead of Buffers. socket.setEncoding('utf8'); @@ -350,13 +351,17 @@ function startInterface() { term.prompt(); }); - } else if (cmd == 'scripts') { + } else if (cmd == 'scripts' || cmd == 'scripts full') { c.reqScripts(function (res) { if (/full/.test(cmd)) { console.log(res); } else { - var text = res.map(function (x) { return x.text; }); - console.log(text.join('\n')); + var text = ''; + for (var i = 0; i < res.length; i++) { + text += res[i].name == c.currentScript ? '* ' : ' '; + text += res[i].name + '\n'; + } + process.stdout.write(text); } term.prompt(); }); @@ -435,6 +440,7 @@ function startInterface() { c.currentSourceLine = res.body.sourceLine; c.currentFrame = 0; + c.currentScript = res.body.script.name; console.log(result); From 50c1c1e12d073621fa372d5ece16d66932922af4 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 23 Dec 2010 10:38:43 -0800 Subject: [PATCH 15/46] add 'info breakpoints' --- lib/_debugger.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lib/_debugger.js b/lib/_debugger.js index d8b7c92295..2ae26ec100 100644 --- a/lib/_debugger.js +++ b/lib/_debugger.js @@ -225,6 +225,12 @@ Client.prototype.reqContinue = function(cb) { }); }; +Client.prototype.listbreakpoints = function(cb) { + this.req({ command: 'listbreakpoints' }, function (res) { + if (cb) cb(res); + }); +}; + // c.next(1, cb); Client.prototype.step = function(action, count, cb) { var req = { @@ -334,6 +340,12 @@ function startInterface() { term.prompt(); }); + } else if (/info +breakpoints/.test(cmd)) { + c.listbreakpoints(function (res) { + console.log(res); + term.prompt(); + }); + } else if (/^backtrace/.test(cmd) || /^bt/.test(cmd)) { c.reqBacktrace(function (bt) { if (/full/.test(cmd)) { From 90e55c3357cc264dfd38626f99a739e8d9927a26 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 29 Dec 2010 22:07:38 -0800 Subject: [PATCH 16/46] Rather have the debugger be parent process --- lib/_debugger.js | 127 +++++++++++++++++++++++------------------ src/node.cc | 143 ++++++++++------------------------------------- src/node.js | 3 +- 3 files changed, 101 insertions(+), 172 deletions(-) diff --git a/lib/_debugger.js b/lib/_debugger.js index 2ae26ec100..382afd18cd 100644 --- a/lib/_debugger.js +++ b/lib/_debugger.js @@ -1,30 +1,84 @@ 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 (pid) { - if (pid) { - process.kill(pid, "SIGUSR1"); - setTimeout(tryConnect, 100); - } else { - tryConnect(); - } +exports.start = function () { + startInterface(); }; var c; +var term; + +function trySpawn(cb) { + var args = process.argv.slice(2); + args.unshift('--debug-brk'); + + console.log(args); -function tryConnect() { + var child = spawn(process.execPath, args, { + customFds: [0, 1, 2] + }); + + setTimeout(function () { + tryConnect(cb); + }, 1000); +} + +function tryConnect(cb) { c = new Client(); process.stdout.write("connecting..."); c.connect(exports.port); - c.on('ready', function () { + c.once('ready', function () { process.stdout.write("ok\r\n"); - startInterface(); + 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(); + }); } // @@ -146,6 +200,8 @@ Client.prototype._onResponse = function(res) { this.emit('ready'); } else if (res.body && res.body.event == 'break') { this.emit('break', res.body); + } else if (res.body && res.body.event == 'afterCompile') { + this.emit('afterCompile', res.body); } else if (cb) { this._reqCallbacks.splice(i, 1); cb(res.body); @@ -290,11 +346,10 @@ function SourceInfo(body) { - - function startInterface() { - var term = readline.createInterface(process.stdout); + term = readline.createInterface(process.stdout); + var stdin = process.openStdin(); stdin.addListener('data', function(chunk) { term.write(chunk); @@ -323,13 +378,14 @@ function startInterface() { term.on('SIGINT', tryQuit); term.on('close', tryQuit); - c.on('close', function () { - process.exit(0); - }); term.on('line', function(cmd) { if (cmd == 'quit' || cmd == 'q' || cmd == 'exit') { tryQuit(); + + } else if (/^r(un)?/.test(cmd)) { + trySpawn(); + } else if (/^help/.test(cmd)) { console.log(helpMessage); term.prompt(); @@ -419,43 +475,4 @@ function startInterface() { term.prompt(); } }); - - 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(); - }); } diff --git a/src/node.cc b/src/node.cc index c349afb8dd..9d8405571f 100644 --- a/src/node.cc +++ b/src/node.cc @@ -17,11 +17,6 @@ #include /* getpwnam() */ #include /* getgrnam() */ -// waitpid -#include -#include - - #include "platform.h" #include @@ -92,7 +87,6 @@ static ev_idle tick_spinner; static bool need_tick_cb; static Persistent 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; @@ -1868,41 +1862,6 @@ 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; @@ -1913,57 +1872,6 @@ static int RegisterSignalHandler(int signal, void (*handler)(int)) { } -static bool debugger_slave_running = false; - -static void HandleDebugEvent(DebugEvent event, - Handle exec_state, - Handle event_data, - Handle data) { - HandleScope scope; - - if (debugger_slave_running) return; - - if (event != Break) { - return; - } - - // Then we take one of two actions - // 1. Inspect the environ variable NODE_DEBUG_PROG; if it is not empty // - // then start it. (TODO) - // 2. Start the built-in debugger. - - - size_t size = 2*PATH_MAX; - char node_path[size]; - OS::GetExecutablePath(node_path, &size); - - int pid = vfork(); - - if (pid == -1) { - perror("vfork()"); - return; - } - - if (pid == 0) { - // Child process - char *argv[] = { node_path, "debug", NULL }; - execvp(node_path, argv); - perror("execvp()"); - _exit(127); - } - - debugger_slave_running = true; - - // We've hit some debugger event. First we will enable the debugger agent. - EnableDebug(true); - - // TODO probably need to waitpid here or something to avoid zombies. - // int status; - // waitpid(pid, &status, 0); - //Debug::DebugBreak(); -} - - int Start(int argc, char *argv[]) { // Hack aroung with the argv pointer. Used for process.title = "blah". argv = node::Platform::SetupArgs(argc, argv); @@ -2059,32 +1967,37 @@ 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) { - // XXX: only use if debug flag enabled? - Debug::SetDebugEventListener(HandleDebugEvent); - } else { - RegisterSignalHandler(SIGUSR1, EnableDebugSignalHandler); - ev_async_init(&enable_debug, EnableDebug2); - ev_async_start(EV_DEFAULT_UC_ &enable_debug); + // 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); + + // 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. diff --git a/src/node.js b/src/node.js index 4717e65d47..b1645eeac7 100644 --- a/src/node.js +++ b/src/node.js @@ -539,8 +539,7 @@ if (process.argv[1] == 'debug') { // Start the debugger agent var d = requireNative('_debugger'); - var pid = process.argv[2]; - d.start(pid); + d.start(); } else { // Load module From e33d0de129533465276158980e53c0979096b8be Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 30 Dec 2010 00:10:30 -0800 Subject: [PATCH 17/46] debugger: Clean up child --- lib/_debugger.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/_debugger.js b/lib/_debugger.js index 382afd18cd..12f67a7518 100644 --- a/lib/_debugger.js +++ b/lib/_debugger.js @@ -7,8 +7,13 @@ exports.port = 5858; exports.start = function () { startInterface(); + + process.on('exit', function () { + if (child) child.kill(); + }); }; +var child; var c; var term; @@ -18,13 +23,11 @@ function trySpawn(cb) { console.log(args); - var child = spawn(process.execPath, args, { - customFds: [0, 1, 2] - }); + child = spawn(process.execPath, args, { customFds: [0, 1, 2] }); setTimeout(function () { tryConnect(cb); - }, 1000); + }, 100); } function tryConnect(cb) { @@ -384,7 +387,11 @@ function startInterface() { tryQuit(); } else if (/^r(un)?/.test(cmd)) { - trySpawn(); + trySpawn(function () { + c.reqContinue(function (res) { + // Wait for break point. (disable raw mode?) + }); + }); } else if (/^help/.test(cmd)) { console.log(helpMessage); From bb400d5697dd41d50bb7307d8b3478fa8ae1092f Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 30 Dec 2010 01:34:54 -0800 Subject: [PATCH 18/46] debugger: Work towards interactive restart --- lib/_debugger.js | 77 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 60 insertions(+), 17 deletions(-) diff --git a/lib/_debugger.js b/lib/_debugger.js index 12f67a7518..c401066efa 100644 --- a/lib/_debugger.js +++ b/lib/_debugger.js @@ -16,19 +16,9 @@ exports.start = function () { var child; var c; var term; +var args = process.argv.slice(2); +args.unshift('--debug-brk'); -function trySpawn(cb) { - var args = process.argv.slice(2); - args.unshift('--debug-brk'); - - console.log(args); - - child = spawn(process.execPath, args, { customFds: [0, 1, 2] }); - - setTimeout(function () { - tryConnect(cb); - }, 100); -} function tryConnect(cb) { c = new Client(); @@ -84,6 +74,27 @@ function tryConnect(cb) { }); } + +function trySpawn(cb) { + if (child) { + child.kill(); + child = null; + } + + if (c) { + c.destroy(); + c = null; + } + + child = spawn(process.execPath, args, { customFds: [0, 1, 2] }); + + console.log("trySpawn"); + setTimeout(function () { + console.log("after timeout"); + tryConnect(cb); + }, 1000); +} + // // Parser/Serializer for V8 debugger protocol // http://code.google.com/p/v8/wiki/DebuggerProtocol @@ -305,7 +316,7 @@ Client.prototype.step = function(action, count, cb) { -var helpMessage = "Commands: print, step, next, continue, scripts, backtrace, version, quit"; +var helpMessage = "Commands: run, print, step, next, continue, scripts, backtrace, version, quit"; function SourceUnderline(source_text, position) { if (!source_text) { @@ -348,6 +359,23 @@ function SourceInfo(body) { } +var restartQuestionPrompt = "The program being debugged has " + + "been started already.\n" + + "Start it from the beginning? (y or n): "; + +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); + } + }); +} + function startInterface() { @@ -387,11 +415,26 @@ function startInterface() { tryQuit(); } else if (/^r(un)?/.test(cmd)) { - trySpawn(function () { - c.reqContinue(function (res) { - // Wait for break point. (disable raw mode?) + if (child) { + restartQuestion(function (yes) { + if (!yes) { + term.prompt(); + } else { + console.log("restarting..."); + trySpawn(function () { + c.reqContinue(function (res) { + // Wait for break point. (disable raw mode?) + }); + }); + } }); - }); + } else { + trySpawn(function () { + c.reqContinue(function (res) { + // Wait for break point. (disable raw mode?) + }); + }); + } } else if (/^help/.test(cmd)) { console.log(helpMessage); From f484cbf4c7bbbc8549de55984c072cf2f1132c7a Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 30 Dec 2010 10:02:42 -0800 Subject: [PATCH 19/46] Debugger: better maintance of script list --- lib/_debugger.js | 75 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 56 insertions(+), 19 deletions(-) diff --git a/lib/_debugger.js b/lib/_debugger.js index c401066efa..a364910597 100644 --- a/lib/_debugger.js +++ b/lib/_debugger.js @@ -190,6 +190,8 @@ function Client() { this.currentFrame = 0; this.currentSourceLine = -1; this.currentSource = null; + this.handles = {}; + this.scripts = {}; // Note that 'Protocol' requires strings instead of Buffers. socket.setEncoding('utf8'); @@ -203,22 +205,53 @@ 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); + } +}; + + +Client.prototype._addScript = function(desc) { + this.scripts[desc.id] = desc; +}; + + +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 == cb.request_seq) break; } + var self = this; + if (res.headers.Type == 'connect') { - // do nothing - this.emit('ready'); + // Request a list of scripts for our own storage. + self.reqScripts(); + self.emit('ready'); + } else if (res.body && res.body.event == 'break') { this.emit('break', res.body); + } else if (res.body && res.body.event == 'afterCompile') { - this.emit('afterCompile', res.body); + this._addHandle(res.body.body.script); + + } else if (res.body && res.body.event == 'scriptCollected') { + // ??? + this._removeScript(res.body.body.script); + } else if (cb) { this._reqCallbacks.splice(i, 1); cb(res.body); + } else { this.emit('unhandledResponse', res.body); } @@ -283,8 +316,12 @@ Client.prototype.reqBacktrace = function(cb) { // text: 'node.js (lines: 562)' } // Client.prototype.reqScripts = function(cb) { + var self = this; this.req({ command: 'scripts' } , function (res) { - if (cb) cb(res.body); + for (var i = 0; i < res.body.length; i++) { + self._addHandle(res.body[i]); + } + if (cb) cb(); }); }; @@ -376,6 +413,18 @@ function 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 startInterface() { @@ -397,7 +446,7 @@ function startInterface() { if (quitTried) return; quitTried = true; term.close(); - console.log("debug done\n"); + console.log("\ndebug done\n"); if (c.writable) { c.reqContinue(function (res) { process.exit(0); @@ -470,20 +519,8 @@ function startInterface() { }); } else if (cmd == 'scripts' || cmd == 'scripts full') { - c.reqScripts(function (res) { - if (/full/.test(cmd)) { - console.log(res); - } else { - var text = ''; - for (var i = 0; i < res.length; i++) { - text += res[i].name == c.currentScript ? '* ' : ' '; - text += res[i].name + '\n'; - } - process.stdout.write(text); - } - term.prompt(); - }); - + printScripts(); + term.prompt(); } else if (/^continue/.test(cmd) || /^c/.test(cmd)) { c.reqContinue(function (res) { From 481329ee7c4ca7f5fdb57ad6f687f229baec461e Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 30 Dec 2010 10:18:25 -0800 Subject: [PATCH 20/46] debugger: clean up --- lib/_debugger.js | 37 ++++++++++++++++--------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/lib/_debugger.js b/lib/_debugger.js index a364910597..c84d2b823e 100644 --- a/lib/_debugger.js +++ b/lib/_debugger.js @@ -75,22 +75,24 @@ function tryConnect(cb) { } -function trySpawn(cb) { +function killChild() { + if (c) { + c.destroy(); + } + if (child) { child.kill(); child = null; } +} - if (c) { - c.destroy(); - c = null; - } + +function trySpawn(cb) { + killChild(); child = spawn(process.execPath, args, { customFds: [0, 1, 2] }); - console.log("trySpawn"); setTimeout(function () { - console.log("after timeout"); tryConnect(cb); }, 1000); } @@ -445,21 +447,18 @@ function startInterface() { function tryQuit() { if (quitTried) return; quitTried = true; + killChild(); term.close(); - console.log("\ndebug done\n"); - if (c.writable) { - c.reqContinue(function (res) { - process.exit(0); - }); - } else { - process.exit(0); - } + process.exit(0); } term.on('SIGINT', tryQuit); term.on('close', tryQuit); term.on('line', function(cmd) { + // trim whitespace + cmd = cmd.replace(/^\s*/, '').replace(/\s*$/, ''); + if (cmd == 'quit' || cmd == 'q' || cmd == 'exit') { tryQuit(); @@ -471,17 +470,13 @@ function startInterface() { } else { console.log("restarting..."); trySpawn(function () { - c.reqContinue(function (res) { - // Wait for break point. (disable raw mode?) - }); + c.reqContinue(); }); } }); } else { trySpawn(function () { - c.reqContinue(function (res) { - // Wait for break point. (disable raw mode?) - }); + c.reqContinue(); }); } From 2a7e7b1c46ab499f65121c6dc74590bab98b73be Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Sat, 18 Dec 2010 11:17:29 -0800 Subject: [PATCH 21/46] start debug agent on SIGUSR1 --- src/node.cc | 86 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 58 insertions(+), 28 deletions(-) diff --git a/src/node.cc b/src/node.cc index 9d8405571f..04a96a4187 100644 --- a/src/node.cc +++ b/src/node.cc @@ -87,6 +87,7 @@ static ev_idle tick_spinner; static bool need_tick_cb; static Persistent 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; @@ -1862,6 +1863,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; @@ -1967,37 +2003,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. From 9244a64b59f200282370192c87c5986103221389 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 30 Dec 2010 10:31:33 -0800 Subject: [PATCH 22/46] debugger: Print error if executing command that requires being connected --- lib/_debugger.js | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/lib/_debugger.js b/lib/_debugger.js index c84d2b823e..f73d13e0f0 100644 --- a/lib/_debugger.js +++ b/lib/_debugger.js @@ -415,6 +415,7 @@ function restartQuestion (cb) { }); } + function printScripts () { var text = ''; for (var id in c.scripts) { @@ -428,6 +429,12 @@ function printScripts () { } +function printNotConnected () { + console.log("Program not running. Try 'run'."); + term.prompt(); +} + + function startInterface() { term = readline.createInterface(process.stdout); @@ -485,18 +492,30 @@ function startInterface() { term.prompt(); } else if ('version' == cmd) { + if (!c) { + printNotConnected(); + return; + } c.reqVersion(function (v) { console.log(v); term.prompt(); }); } else if (/info +breakpoints/.test(cmd)) { + if (!c) { + printNotConnected(); + return; + } c.listbreakpoints(function (res) { console.log(res); term.prompt(); }); } else if (/^backtrace/.test(cmd) || /^bt/.test(cmd)) { + if (!c) { + printNotConnected(); + return; + } c.reqBacktrace(function (bt) { if (/full/.test(cmd)) { console.log(bt); @@ -514,25 +533,45 @@ function startInterface() { }); } else if (cmd == 'scripts' || cmd == 'scripts full') { + if (!c) { + printNotConnected(); + return; + } printScripts(); term.prompt(); } else if (/^continue/.test(cmd) || /^c/.test(cmd)) { + if (!c) { + printNotConnected(); + return; + } c.reqContinue(function (res) { // Wait for break point. (disable raw mode?) }); } else if (/^next/.test(cmd) || /^n/.test(cmd)) { + if (!c) { + printNotConnected(); + return; + } c.step('next', 1, function (res) { // Wait for break point. (disable raw mode?) }); } else if (/^step/.test(cmd) || /^s/.test(cmd)) { + if (!c) { + printNotConnected(); + return; + } c.step('in', 1, function (res) { // Wait for break point. (disable raw mode?) }); } else if (/^print/.test(cmd) || /^p/.test(cmd)) { + if (!c) { + printNotConnected(); + return; + } var i = cmd.indexOf(' '); if (i < 0) { console.log("print [expression]"); From 109f845e4938ac7f213ebfb15f8d55c5135fc2ad Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 30 Dec 2010 10:32:41 -0800 Subject: [PATCH 23/46] debugger: style --- lib/_debugger.js | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/lib/_debugger.js b/lib/_debugger.js index f73d13e0f0..07a02cc8ce 100644 --- a/lib/_debugger.js +++ b/lib/_debugger.js @@ -357,17 +357,15 @@ Client.prototype.step = function(action, count, cb) { var helpMessage = "Commands: run, print, step, next, continue, scripts, backtrace, version, quit"; -function SourceUnderline(source_text, position) { - if (!source_text) { - return; - } +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 (source_text[i] == '\t') { + if (sourceText[i] == '\t') { underline += '\t'; } else { underline += ' '; @@ -376,7 +374,7 @@ function SourceUnderline(source_text, position) { underline += '^'; // Return the source line text with the underline beneath. - return source_text + '\n' + underline; + return sourceText + '\n' + underline; } function SourceInfo(body) { @@ -389,10 +387,8 @@ function SourceInfo(body) { result += '[unnamed]'; } } - result += ' line '; + result += ':'; result += body.sourceLine + 1; - result += ' column '; - result += body.sourceColumn + 1; return result; } From d4859a55bc8f18f1834546af12a8d8f6a8802ebd Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 30 Dec 2010 11:17:11 -0800 Subject: [PATCH 24/46] Wrap up debugger in one class just for better readablity --- lib/_debugger.js | 526 +++++++++++++++++++++++++---------------------- 1 file changed, 279 insertions(+), 247 deletions(-) diff --git a/lib/_debugger.js b/lib/_debugger.js index 07a02cc8ce..e0bd3e1c74 100644 --- a/lib/_debugger.js +++ b/lib/_debugger.js @@ -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,202 +312,316 @@ 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); -} - - -function printNotConnected () { - console.log("Program not running. Try 'run'."); - term.prompt(); -} - - -function startInterface() { - - term = readline.createInterface(process.stdout); var stdin = process.openStdin(); stdin.addListener('data', function(chunk) { term.write(chunk); }); - var prompt = 'debug> '; - term.setPrompt('debug> '); 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', tryQuit); + term.on('close', function () { + self.tryQuit(); + }); term.on('line', function(cmd) { + // trim whitespace 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)) { - console.log(helpMessage); - term.prompt(); +Interface.prototype.tryQuit = function() { + if (this.quitTried) return; + 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)) { - if (!c) { - printNotConnected(); - return; +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 += ', #'; } - c.listbreakpoints(function (res) { - console.log(res); - term.prompt(); - }); + result += r.breakpoints[i]; + } + } 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)) { - if (!c) { - printNotConnected(); - return; - } - c.reqBacktrace(function (bt) { - if (/full/.test(cmd)) { - console.log(bt); - } else if (bt.totalFrames == 0) { - console.log('(empty stack)'); + this.client.currentSourceLine = r.sourceLine; + this.client.currentFrame = 0; + this.client.currentScript = r.script.name; + + 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') { + self.tryQuit(); + + } else if (/^r(un)?/.test(cmd)) { + if (self.child) { + self.restartQuestion(function (yes) { + if (!yes) { + term.prompt(); } else { - var result = ''; - for (j = 0; j < bt.frames.length; j++) { - if (j != 0) result += '\n'; - result += bt.frames[j].text; - } - console.log(result); + console.log("restarting..."); + self.trySpawn(); } - term.prompt(); }); + } else { + self.trySpawn(); + } - } else if (cmd == 'scripts' || cmd == 'scripts full') { - if (!c) { - printNotConnected(); - return; - } - printScripts(); + } 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 (/^continue/.test(cmd) || /^c/.test(cmd)) { - if (!c) { - printNotConnected(); - return; - } - c.reqContinue(function (res) { - // Wait for break point. (disable raw mode?) - }); + } else if (/info +breakpoints/.test(cmd)) { + if (!client) { + self.printNotConnected(); + return; + } + client.listbreakpoints(function (res) { + console.log(res); + term.prompt(); + }); - } else if (/^next/.test(cmd) || /^n/.test(cmd)) { - if (!c) { - printNotConnected(); - return; + } 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); } - c.step('next', 1, function (res) { - // Wait for break point. (disable raw mode?) - }); + term.prompt(); + }); - } else if (/^step/.test(cmd) || /^s/.test(cmd)) { - if (!c) { - printNotConnected(); - return; - } - c.step('in', 1, function (res) { - // Wait for break point. (disable raw mode?) - }); + } else if (cmd == 'scripts' || cmd == 'scripts full') { + if (!client) { + self.printNotConnected(); + return; + } + self.printScripts(); + term.prompt(); - } else if (/^print/.test(cmd) || /^p/.test(cmd)) { - if (!c) { - printNotConnected(); - return; - } - var i = cmd.indexOf(' '); - if (i < 0) { - console.log("print [expression]"); + } else if (/^c(ontinue)?/.test(cmd)) { + if (!client) { + self.printNotConnected(); + return; + } + client.reqContinue(function (res) { + // Wait for break point. (disable raw mode?) + }); + + } 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 { - cmd = cmd.slice(i); - c.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(); + } +}; + + +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 { - 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(); + 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); +}; + + + From 074af67dd36b0e29d801c39cf7bd2b1170880df9 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 30 Dec 2010 11:53:55 -0800 Subject: [PATCH 25/46] debugger: don't display node's internal scripts --- lib/_debugger.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/_debugger.js b/lib/_debugger.js index e0bd3e1c74..fb7b31a59a 100644 --- a/lib/_debugger.js +++ b/lib/_debugger.js @@ -135,8 +135,13 @@ Client.prototype._addHandle = function(desc) { }; +var natives = process.binding('natives'); + + Client.prototype._addScript = function(desc) { this.scripts[desc.id] = desc; + desc.isNative = (desc.name.replace('.js', '') in natives) || + desc.name == 'node.js'; }; @@ -470,7 +475,7 @@ Interface.prototype.handleCommand = function(cmd) { self.printNotConnected(); return; } - self.printScripts(); + self.printScripts(cmd.indexOf('full') > 0); term.prompt(); } else if (/^c(ontinue)?/.test(cmd)) { @@ -610,14 +615,17 @@ Interface.prototype.printNotConnected = function() { }; -Interface.prototype.printScripts = function() { +// 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) { - text += script.name == client.currentScript ? '* ' : ' '; - text += script.name + '\n'; + if (displayNatives || script.name == client.currentScript || !script.isNative) { + text += script.name == client.currentScript ? '* ' : ' '; + text += script.name + '\n'; + } } } process.stdout.write(text); From a3c4e17c2a6dd602e21fcfeccc28bdb2fb79734d Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 30 Dec 2010 11:54:49 -0800 Subject: [PATCH 26/46] debugger: repeat command functionality --- lib/_debugger.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/_debugger.js b/lib/_debugger.js index fb7b31a59a..d544e41f54 100644 --- a/lib/_debugger.js +++ b/lib/_debugger.js @@ -351,10 +351,15 @@ function Interface() { }); term.on('line', function(cmd) { - // trim whitespace cmd = cmd.replace(/^\s*/, '').replace(/\s*$/, ''); - self.handleCommand(cmd); + + if (cmd.length) { + self._lastCommand = cmd; + self.handleCommand(cmd); + } else { + self.handleCommand(self._lastCommand); + } }); } From 866201bd74b8d06d06a19dc5cdfa9ff7c62e5a26 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 30 Dec 2010 12:33:27 -0800 Subject: [PATCH 27/46] debugger: add 'kill' command --- lib/_debugger.js | 58 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 42 insertions(+), 16 deletions(-) diff --git a/lib/_debugger.js b/lib/_debugger.js index d544e41f54..6517431e32 100644 --- a/lib/_debugger.js +++ b/lib/_debugger.js @@ -278,7 +278,8 @@ Client.prototype.step = function(action, count, cb) { -var helpMessage = "Commands: run, print, step, next, continue, scripts, backtrace, version, quit"; +var helpMessage = "Commands: run, kill, print, step, next, " + + "continue, scripts, backtrace, version, quit"; function SourceUnderline(sourceText, position) { if (!sourceText) return; @@ -420,10 +421,15 @@ Interface.prototype.handleCommand = function(cmd) { if (self.child) { self.restartQuestion(function (yes) { if (!yes) { + self._lastCommand = null; term.prompt(); } else { console.log("restarting..."); - self.trySpawn(); + self.killChild(); + // XXX need to wait a little bit for the restart to work? + setTimeout(function () { + self.trySpawn(); + }, 1000); } }); } else { @@ -488,9 +494,22 @@ Interface.prototype.handleCommand = function(cmd) { self.printNotConnected(); return; } - client.reqContinue(function (res) { - // Wait for break point. (disable raw mode?) - }); + client.reqContinue(); + + } else if (/^k(ill)?/.test(cmd)) { + // kill + if (self.child) { + self.killQuestion(function (yes) { + if (yes) { + self.killChild(); + } else { + self._lastCommand = null; + } + self.term.prompt(); + }); + } else { + self.term.prompt(); + } } else if (/^next/.test(cmd) || /^n/.test(cmd)) { if (!client) { @@ -541,13 +560,10 @@ Interface.prototype.handleCommand = function(cmd) { }; -var restartQuestionPrompt = "The program being debugged has " + - "been started already.\n" + - "Start it from the beginning? (y or n): "; -Interface.prototype.restartQuestion = function(cb) { +Interface.prototype.yesNoQuestion = function(prompt, cb) { var self = this; - this.term.question(restartQuestionPrompt, function (answer) { + this.term.question(prompt, function (answer) { if (/^y(es)?$/i.test(answer)) { cb(true); } else if (/^n(o)?$/i.test(answer)) { @@ -560,16 +576,27 @@ Interface.prototype.restartQuestion = function(cb) { }; -Interface.prototype.killChild = function() { - if (this.client) { - this.client.destroy(); - this.client = null; - } +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.killChild = function() { if (this.child) { this.child.kill(); this.child = null; } + + if (this.client) { + this.client.destroy(); + this.client = null; + } }; @@ -598,7 +625,6 @@ Interface.prototype.trySpawn = function(cb) { console.log("\nprogram terminated"); self.client = null; self.killChild(); - self.term.prompt(); }); client.on('unhandledResponse', function (res) { From 0946474b5c03e39653930e0a7097088f5a1f21e3 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 30 Dec 2010 13:25:49 -0800 Subject: [PATCH 28/46] debugger: Clean ups, bug fixes --- lib/_debugger.js | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/lib/_debugger.js b/lib/_debugger.js index 6517431e32..a9818be8bc 100644 --- a/lib/_debugger.js +++ b/lib/_debugger.js @@ -140,8 +140,10 @@ var natives = process.binding('natives'); Client.prototype._addScript = function(desc) { this.scripts[desc.id] = desc; - desc.isNative = (desc.name.replace('.js', '') in natives) || - desc.name == 'node.js'; + if (desc.name) { + desc.isNative = (desc.name.replace('.js', '') in natives) || + desc.name == 'node.js'; + } }; @@ -153,33 +155,40 @@ Client.prototype._removeScript = function(desc) { 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 == cb.request_seq) break; + 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; + + } - } else if (cb) { + if (cb) { this._reqCallbacks.splice(i, 1); + handled = true; cb(res.body); - - } else { - this.emit('unhandledResponse', res.body); } + + if (!handled) this.emit('unhandledResponse', res.body); }; @@ -418,6 +427,7 @@ Interface.prototype.handleCommand = function(cmd) { self.tryQuit(); } else if (/^r(un)?/.test(cmd)) { + self._lastCommand = null; if (self.child) { self.restartQuestion(function (yes) { if (!yes) { @@ -497,6 +507,10 @@ Interface.prototype.handleCommand = function(cmd) { client.reqContinue(); } else if (/^k(ill)?/.test(cmd)) { + if (!client) { + self.printNotConnected(); + return; + } // kill if (self.child) { self.killQuestion(function (yes) { From 62e0ca05a63863dd6628a9140ee6edb442aa53d0 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 30 Dec 2010 15:46:47 -0800 Subject: [PATCH 29/46] debugger: Disable/Enable raw mode for child --- benchmark/http_simple.js | 1 + lib/_debugger.js | 60 +++++++++++++++++++++++++++++++++------- lib/readline.js | 14 ++++++++++ 3 files changed, 65 insertions(+), 10 deletions(-) diff --git a/benchmark/http_simple.js b/benchmark/http_simple.js index 8d66190e0f..6d6b0d39c4 100644 --- a/benchmark/http_simple.js +++ b/benchmark/http_simple.js @@ -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) { diff --git a/lib/_debugger.js b/lib/_debugger.js index a9818be8bc..4f95293a44 100644 --- a/lib/_debugger.js +++ b/lib/_debugger.js @@ -310,6 +310,7 @@ function SourceUnderline(sourceText, position) { return sourceText + '\n' + underline; } + function SourceInfo(body) { var result = ''; @@ -340,8 +341,8 @@ function Interface() { self.killChild(); }); - var stdin = process.openStdin(); - stdin.addListener('data', function(chunk) { + this.stdin = process.openStdin(); + this.stdin.addListener('data', function(chunk) { term.write(chunk); }); @@ -350,12 +351,14 @@ function Interface() { this.quitTried = false; + process.on('SIGINT', function () { + self.handleSIGINT(); + }); term.on('SIGINT', function () { - self.tryQuit(); + self.handleSIGINT(); }); - term.on('close', function () { self.tryQuit(); }); @@ -374,6 +377,15 @@ function Interface() { } +Interface.prototype.handleSIGINT = function() { + if (this.paused) { + this.child.kill('SIGINT'); + } else { + this.tryQuit(); + } +}; + + Interface.prototype.tryQuit = function() { if (this.quitTried) return; this.quitTried = true; @@ -383,6 +395,23 @@ Interface.prototype.tryQuit = function() { }; +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) { @@ -413,7 +442,7 @@ Interface.prototype.handleBreak = function(r) { console.log(result); - this.term.prompt(); + if(!this.resume()) this.term.prompt(); }; @@ -504,7 +533,11 @@ Interface.prototype.handleCommand = function(cmd) { self.printNotConnected(); return; } - client.reqContinue(); + + self.pause(); + client.reqContinue(function () { + self.resume(); + }); } else if (/^k(ill)?/.test(cmd)) { if (!client) { @@ -611,15 +644,21 @@ Interface.prototype.killChild = function() { 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] }); - var self = this; + + this.pause(); + setTimeout(function () { process.stdout.write("connecting..."); var client = self.client = new Client(); @@ -630,15 +669,16 @@ Interface.prototype.trySpawn = function(cb) { // since we did debug-brk, we're hitting a break point immediately // continue before anything else. - client.reqContinue(); - - if (cb) cb(); + client.reqContinue(function () { + if (cb) cb(); + }); }); client.on('close', function () { console.log("\nprogram terminated"); self.client = null; self.killChild(); + self.term.prompt(); }); client.on('unhandledResponse', function (res) { diff --git a/lib/readline.js b/lib/readline.js index 4a0565a40a..474e227e5d 100644 --- a/lib/readline.js +++ b/lib/readline.js @@ -154,6 +154,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); From 294bcb33e6dc2d774deb4d6edbde2c1584e7bc20 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 30 Dec 2010 17:33:06 -0800 Subject: [PATCH 30/46] debugger: Fix some parser issues Wouldn't handle events if it got backed up. --- lib/_debugger.js | 1 + test/simple/test-debugger-client.js | 49 +++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/lib/_debugger.js b/lib/_debugger.js index 4f95293a44..98e19be131 100644 --- a/lib/_debugger.js +++ b/lib/_debugger.js @@ -43,6 +43,7 @@ Protocol.prototype._newRes = function(raw) { this.res = { raw: raw || '', headers: {} }; this.state = 'headers'; this.reqSeq = 1; + this.execute(''); }; diff --git a/test/simple/test-debugger-client.js b/test/simple/test-debugger-client.js index c542f89c49..9708e70da4 100644 --- a/test/simple/test-debugger-client.js +++ b/test/simple/test-debugger-client.js @@ -18,6 +18,55 @@ p.execute("Type: connect\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":"#'); +assert.equal(78, parts[2].length); +bodyLength += parts[2].length; + +parts.push('.[anonymous](req=#, res=#)",' + + '"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":"#"}],"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) { From aea568b04a26cb7d725df57e5c593a1e0d5147b3 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 30 Dec 2010 17:52:14 -0800 Subject: [PATCH 31/46] Fix some errors in debugger tests --- lib/_debugger.js | 2 +- test/simple/test-debugger-client.js | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/_debugger.js b/lib/_debugger.js index 98e19be131..275d33e69e 100644 --- a/lib/_debugger.js +++ b/lib/_debugger.js @@ -108,7 +108,7 @@ function Client() { this._reqCallbacks = []; var socket = this; - this.currentFrame = 0; + this.currentFrame = NO_FRAME; this.currentSourceLine = -1; this.currentSource = null; this.handles = {}; diff --git a/test/simple/test-debugger-client.js b/test/simple/test-debugger-client.js index 9708e70da4..a517f56ba7 100644 --- a/test/simple/test-debugger-client.js +++ b/test/simple/test-debugger-client.js @@ -85,11 +85,13 @@ addTest(function (client, done) { addTest(function (client, done) { console.error("requesting scripts"); - client.reqScripts(function (s) { - console.error("got %d scripts", s.length); + client.reqScripts(function () { + console.error("got %d scripts", Object.keys(client.scripts).length); + var foundMainScript = false; - for (var i = 0; i < s.length; i++) { - if (s[i].name === 'node.js') { + for (var k in client.scripts) { + var script = client.scripts[k]; + if (script && script.name === 'node.js') { foundMainScript = true; break; } From 7b9a2f29b40073ed3c172a2e1bdb95f9fd9e46f6 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 30 Dec 2010 18:07:19 -0800 Subject: [PATCH 32/46] debugger: Don't prompt until child is killed --- lib/_debugger.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/_debugger.js b/lib/_debugger.js index 275d33e69e..8b5a65a244 100644 --- a/lib/_debugger.js +++ b/lib/_debugger.js @@ -553,7 +553,6 @@ Interface.prototype.handleCommand = function(cmd) { } else { self._lastCommand = null; } - self.term.prompt(); }); } else { self.term.prompt(); From af6662d5e5be3659d5d0c9b0721e0ec056dafaec Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 30 Dec 2010 18:28:30 -0800 Subject: [PATCH 33/46] debugger: Prompt before quitting --- lib/_debugger.js | 35 ++++++++++++++++++++++++++++++----- lib/readline.js | 15 +++++++++++---- 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/lib/_debugger.js b/lib/_debugger.js index 8b5a65a244..bc1d810056 100644 --- a/lib/_debugger.js +++ b/lib/_debugger.js @@ -350,7 +350,7 @@ function Interface() { term.setPrompt('debug> '); term.prompt(); - this.quitTried = false; + this.quitting = false; process.on('SIGINT', function () { self.handleSIGINT(); @@ -387,15 +387,32 @@ Interface.prototype.handleSIGINT = function() { }; -Interface.prototype.tryQuit = function() { - if (this.quitTried) return; - this.quitTried = true; +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(); @@ -454,6 +471,7 @@ Interface.prototype.handleCommand = function(cmd) { var term = this.term; if (cmd == 'quit' || cmd == 'q' || cmd == 'exit') { + self._lastCommand = null; self.tryQuit(); } else if (/^r(un)?/.test(cmd)) { @@ -610,6 +628,7 @@ Interface.prototype.handleCommand = function(cmd) { 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); @@ -634,6 +653,12 @@ Interface.prototype.killQuestion = function(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(); @@ -678,7 +703,7 @@ Interface.prototype.trySpawn = function(cb) { console.log("\nprogram terminated"); self.client = null; self.killChild(); - self.term.prompt(); + if (!self.quitting) self.term.prompt(); }); client.on('unhandledResponse', function (res) { diff --git a/lib/readline.js b/lib/readline.js index 474e227e5d..6d588fd5b8 100644 --- a/lib/readline.js +++ b/lib/readline.js @@ -89,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(); + } } }; From 916f567d23c9e665f71c63df05734823b085fff4 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Thu, 30 Dec 2010 18:46:49 -0800 Subject: [PATCH 34/46] debugger: a little bit of doc --- doc/api/_toc.markdown | 1 + doc/api/all.markdown | 1 + doc/api/debugger.markdown | 73 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+) create mode 100644 doc/api/debugger.markdown diff --git a/doc/api/_toc.markdown b/doc/api/_toc.markdown index 1ee6011793..12c8a335c7 100644 --- a/doc/api/_toc.markdown +++ b/doc/api/_toc.markdown @@ -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) diff --git a/doc/api/all.markdown b/doc/api/all.markdown index c966dcf0fc..cfd173afd1 100644 --- a/doc/api/all.markdown +++ b/doc/api/all.markdown @@ -29,6 +29,7 @@ @include assert @include tty @include os +@include debugger # Appendixes @include appendix_1 diff --git a/doc/api/debugger.markdown b/doc/api/debugger.markdown new file mode 100644 index 0000000000..1d37df8d25 --- /dev/null +++ b/doc/api/debugger.markdown @@ -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 #._onTimeout(), myscript.js:4 + debugger; + ^ + debug> next + break in #._onTimeout(), myscript.js:5 + console.log("world"); + ^ + debug> print x + 5 + debug> print 2+2 + 4 + debug> next + world + break in #._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`. + + From feb77eab6563c849a99cf620c354d084faabc245 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Sun, 2 Jan 2011 18:08:08 -0800 Subject: [PATCH 35/46] Fix REPL for named functions add some tests. --- lib/repl.js | 7 ++++++- test/simple/test-repl.js | 21 ++++++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/lib/repl.js b/lib/repl.js index aef0e9e2d3..c425603ef5 100644 --- a/lib/repl.js +++ b/lib/repl.js @@ -115,12 +115,17 @@ function REPLServer(prompt, stream) { // and statements e.g. // 'for (var i = 0; i < 10; i++) console.log(i);' - var ret; + var ret, success = false; try { // First we attempt to eval as expression with parens. // This catches '{a : 1}' properly. ret = vm.runInContext('(' + self.bufferedCommand + ')', context, 'repl'); + if (typeof ret !== 'function') success = true; } catch (e) { + success = false; + } + + if (!success) { // Now as statement without parens. ret = vm.runInContext(self.bufferedCommand, context, 'repl'); } diff --git a/test/simple/test-repl.js b/test/simple/test-repl.js index 22b41add01..de839f5635 100644 --- a/test/simple/test-repl.js +++ b/test/simple/test-repl.js @@ -97,7 +97,26 @@ function error_test() { // invalid input to JSON.parse error is special case of syntax error, // should throw { client: client_unix, send: 'JSON.parse(\'{invalid: \\\'json\\\'}\');', - expect: /^SyntaxError: Unexpected token ILLEGAL/ } + expect: /^SyntaxError: Unexpected token ILLEGAL/ }, + // Named functions can be used: + { client: client_unix, send: 'function blah() { return 1; }', + expect: prompt_unix }, + { client: client_unix, send: 'blah()', + expect: "1\n" + prompt_unix }, + // Multiline object + { client: client_unix, send: '{ a: ', + expect: prompt_multiline }, + { client: client_unix, send: '1 }', + expect: "{ a: 1 }" }, + // Multiline anonymous function with comment + { client: client_unix, send: '(function () {', + expect: prompt_multiline }, + { client: client_unix, send: '// blah', + expect: prompt_multiline }, + { client: client_unix, send: 'return 1;', + expect: prompt_multiline }, + { client: client_unix, send: '})()', + expect: "1" }, ]); } From 57544ba1c54c7d0da890317deeb73076350c5647 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Sun, 2 Jan 2011 18:33:49 -0800 Subject: [PATCH 36/46] Bump version to v0.3.3 --- AUTHORS | 3 +++ ChangeLog | 25 ++++++++++++++++++++++++- doc/index.html | 8 ++++---- src/node_version.h | 2 +- 4 files changed, 32 insertions(+), 6 deletions(-) diff --git a/AUTHORS b/AUTHORS index 625580c405..c5a9261d21 100644 --- a/AUTHORS +++ b/AUTHORS @@ -148,3 +148,6 @@ Michael W Sean Braithwaite Anders Conbere Devin Torres +Theo Schlossnagle +Kai Chen +Daniel C <333222@gmail.com> diff --git a/ChangeLog b/ChangeLog index 7455b57205..d204ce6dea 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,27 @@ -2010.12.16, Version 0.3.2 (unstable) +2011.01.02, Version 0.3.3 (unstable) + +* TLS improvements. + +* url.parse(url, true) defaults query field to {} (Jeremy Martin) + +* Upgrade V8 to 3.0.4 + +* Handle ECONNABORT properly (Theo Schlossnagle) + +* Fix memory leaks (Tom Hughes) + +* Add os.cpus(), os.freemem(), os.totalmem(), os.loadavg() and other + functions for OSX, Linux, and Cygwin. (Brian White) + +* Fix REPL syntax error bug (GH-543), improve how REPL commands are + evaulated. + +* Use process.stdin instead of process.openStdin(). + +* Disable TLS tests when node doesn't have OpenSSL. + + +2010.12.16, Version 0.3.2 (unstable), 4bb914bde9f3c2d6de00853353b6b8fc9c66143a * Rip out the old (broken) TLS implementation introduce new tested implementation and API. See docs. HTTPS not supported in this release. diff --git a/doc/index.html b/doc/index.html index da1c96296d..7efb94bc27 100644 --- a/doc/index.html +++ b/doc/index.html @@ -23,7 +23,7 @@
  • About
  • Links
  • Contributing
  • -
  • v0.3.2 docs
  • +
  • v0.3.3 docs
  • v0.2.6 docs
  • @@ -92,9 +92,9 @@ net.createServer(function (socket) {

    - Unstable: 2010.12.16 - node-v0.3.2.tar.gz - (Documentation) + Unstable: 2011.01.02 + node-v0.3.3.tar.gz + (Documentation)

    Historical: versions, docs

    diff --git a/src/node_version.h b/src/node_version.h index 51798bc6a5..e1a92461eb 100644 --- a/src/node_version.h +++ b/src/node_version.h @@ -7,7 +7,7 @@ #define NODE_MAJOR_VERSION 0 #define NODE_MINOR_VERSION 3 #define NODE_PATCH_VERSION 3 -#define NODE_VERSION_IS_RELEASE 0 +#define NODE_VERSION_IS_RELEASE 1 #ifndef NODE_STRINGIFY #define NODE_STRINGIFY(n) NODE_STRINGIFY_HELPER(n) From 2b5b128cbac3cee8535d3e4f54c94d6a7c962dac Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Sun, 2 Jan 2011 18:57:22 -0800 Subject: [PATCH 37/46] Now working on v0.3.4-pre --- src/node_version.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/node_version.h b/src/node_version.h index e1a92461eb..70bd4be74b 100644 --- a/src/node_version.h +++ b/src/node_version.h @@ -6,8 +6,8 @@ #define NODE_MAJOR_VERSION 0 #define NODE_MINOR_VERSION 3 -#define NODE_PATCH_VERSION 3 -#define NODE_VERSION_IS_RELEASE 1 +#define NODE_PATCH_VERSION 4 +#define NODE_VERSION_IS_RELEASE 0 #ifndef NODE_STRINGIFY #define NODE_STRINGIFY(n) NODE_STRINGIFY_HELPER(n) From 282cce1ea58b6a450c397af22b64fac8fd03d579 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Mon, 3 Jan 2011 12:58:19 -0800 Subject: [PATCH 38/46] debugger: don't display whole path of scripts --- lib/_debugger.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/_debugger.js b/lib/_debugger.js index bc1d810056..8c7c2a7ff0 100644 --- a/lib/_debugger.js +++ b/lib/_debugger.js @@ -734,7 +734,8 @@ Interface.prototype.printScripts = function(displayNatives) { if (typeof script == 'object' && script.name) { if (displayNatives || script.name == client.currentScript || !script.isNative) { text += script.name == client.currentScript ? '* ' : ' '; - text += script.name + '\n'; + var n = require('path').split(script.name); + text += n[n.length - 1] + '\n'; } } } From d040f1d19d17e056223ab3de0e58ed470c8b9f52 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Mon, 3 Jan 2011 13:21:28 -0800 Subject: [PATCH 39/46] debugger: use correct handle handle --- lib/_debugger.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/_debugger.js b/lib/_debugger.js index 8c7c2a7ff0..b0a539e7ff 100644 --- a/lib/_debugger.js +++ b/lib/_debugger.js @@ -128,7 +128,7 @@ exports.Client = Client; Client.prototype._addHandle = function(desc) { if (typeof desc != 'object' || !desc.handle) throw new Error("bad type"); - this.handles[desc.id] = desc; + this.handles[desc.handle] = desc; if (desc.type == 'script') { this._addScript(desc); @@ -208,11 +208,13 @@ Client.prototype.reqVersion = function(cb) { Client.prototype.reqEval = function(expression, cb) { + var self = this; var req = { command: 'evaluate', arguments: { expression: expression } }; + if (this.currentFrame == NO_FRAME) { req.arguments.global = true; } else { @@ -220,6 +222,8 @@ Client.prototype.reqEval = function(expression, cb) { } this.req(req, function (res) { + console.error('reqEval res ', res.body); + self._addHandle(res.body); if (cb) cb(res.body); }); }; From e4dd5cd6fd7b44f4fc21a5cfd39615a7a5833957 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Mon, 3 Jan 2011 15:27:57 -0800 Subject: [PATCH 40/46] NODE_DEBUG uses strings instead of bitflags --- lib/http.js | 3 +-- lib/net.js | 3 +-- lib/tls.js | 3 +-- src/node.js | 3 +-- 4 files changed, 4 insertions(+), 8 deletions(-) diff --git a/lib/http.js b/lib/http.js index 0b4a0e2212..5594a8ff1c 100644 --- a/lib/http.js +++ b/lib/http.js @@ -6,8 +6,7 @@ var HTTPParser = process.binding('http_parser').HTTPParser; var debug; -var debugLevel = parseInt(process.env.NODE_DEBUG, 16); -if (debugLevel & 0x4) { +if (process.env.NODE_DEBUG && /http/.test(process.env.NODE_DEBUG)) { debug = function(x) { console.error('HTTP: %s', x); }; } else { debug = function() { }; diff --git a/lib/net.js b/lib/net.js index d29984668a..f240f8a351 100644 --- a/lib/net.js +++ b/lib/net.js @@ -5,9 +5,8 @@ var stream = require('stream'); var kMinPoolSpace = 128; var kPoolSize = 40 * 1024; -var debugLevel = parseInt(process.env.NODE_DEBUG, 16); var debug; -if (debugLevel & 0x2) { +if (process.env.NODE_DEBUG && /net/.test(process.env.NODE_DEBUG)) { debug = function(x) { util.error.apply(this, arguments); }; } else { debug = function() { }; diff --git a/lib/tls.js b/lib/tls.js index 433d33958c..bcb036edc8 100644 --- a/lib/tls.js +++ b/lib/tls.js @@ -6,9 +6,8 @@ var stream = require('stream'); var assert = process.assert; -var debugLevel = parseInt(process.env.NODE_DEBUG, 16); var debug; -if (debugLevel & 0x2) { +if (process.env.NODE_DEBUG && /tls/.test(process.env.NODE_DEBUG)) { debug = function() { util.error.apply(this, arguments); }; } else { debug = function() { }; diff --git a/src/node.js b/src/node.js index ca4bead83e..1d1ca687d8 100644 --- a/src/node.js +++ b/src/node.js @@ -123,9 +123,8 @@ // Modules - var debugLevel = parseInt(process.env['NODE_DEBUG'], 16); var debug; - if (debugLevel & 1) { + if (process.env.NODE_DEBUG && /module/.test(process.env.NODE_DEBUG)) { debug = function(x) { console.error(x); }; } else { debug = function() { }; From 94f8368cf9e5077050525e32a24188402f077074 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Sun, 2 Jan 2011 01:13:56 -0800 Subject: [PATCH 41/46] First pass at new https server --- lib/http.js | 1 + lib/https.js | 22 ++++++++++++++++ lib/tls.js | 14 ++++++++-- test/simple/test-https-simple.js | 44 ++++++++++++++++++++++++++++++++ 4 files changed, 79 insertions(+), 2 deletions(-) create mode 100644 lib/https.js create mode 100644 test/simple/test-https-simple.js diff --git a/lib/http.js b/lib/http.js index 5594a8ff1c..786f373e74 100644 --- a/lib/http.js +++ b/lib/http.js @@ -871,6 +871,7 @@ function connectionListener(socket) { return false; // Not a HEAD response. (Not even a response!) }; } +exports._connectionListener = connectionListener; function Client() { diff --git a/lib/https.js b/lib/https.js new file mode 100644 index 0000000000..c3e17a3602 --- /dev/null +++ b/lib/https.js @@ -0,0 +1,22 @@ +var tls = require('tls'); +var http = require('http'); +var inherits = require('util').inherits; + + +function Server(opts, requestListener) { + if (!(this instanceof Server)) return new Server(opts, requestListener); + tls.Server.call(this, opts, http._connectionListener); + + if (requestListener) { + this.addListener('request', requestListener); + } +} +inherits(Server, tls.Server); + + +exports.Server = Server; + + +exports.createServer = function(opts, requestListener) { + return new Server(opts, requestListener); +}; diff --git a/lib/tls.js b/lib/tls.js index bcb036edc8..08fa742ac8 100644 --- a/lib/tls.js +++ b/lib/tls.js @@ -77,6 +77,11 @@ CryptoStream.prototype.resume = function() { }; +CryptoStream.prototype.setTimeout = function(n) { + if (this.socket) this.socket.setTimeout(n); +}; + + function parseCertString (s) { // EG '/C=US/ST=CA/L=SF/O=Joyent/OU=Node.js/CN=ca1/emailAddress=ry@tinyclouds.org' var out = {}; @@ -183,8 +188,13 @@ CryptoStream.prototype._blow = function() { } while ((chunkBytes > 0) && (pool.used + bytesRead < pool.length)); if (bytesRead > 0) { - chunk = pool.slice(0, bytesRead); - this.emit('data', chunk); + if (this._events && this._events['data']) { + chunk = pool.slice(0, bytesRead); + this.emit('data', chunk); + } + + // Optimization: emit the original buffer with end points + if (this.ondata) this.ondata(pool, 0, bytesRead); } } while (bytesRead > 0 && this._writeState === true); }; diff --git a/test/simple/test-https-simple.js b/test/simple/test-https-simple.js new file mode 100644 index 0000000000..a90c0269e6 --- /dev/null +++ b/test/simple/test-https-simple.js @@ -0,0 +1,44 @@ +if (!process.versions.openssl) { + console.error("Skipping because node compiled without OpenSSL."); + process.exit(0); +} + +var common = require('../common'); +var assert = require('assert'); + +var fs = require('fs'); +var exec = require('child_process').exec; + +var https = require('https'); + +var options = { + key: fs.readFileSync(common.fixturesDir + '/keys/agent1-key.pem'), + cert: fs.readFileSync(common.fixturesDir + '/keys/agent1-cert.pem') +}; + +var reqCount = 0; +var body = 'hello world\n'; + +var server = https.createServer(options, function (req, res) { + reqCount++; + console.log("got request"); + res.writeHead(200, { 'content-type': 'text/plain' }); + res.end(body); +}) + +function afterCurl (err, stdout, stderr) { + if (err) throw err; + server.close(); + common.error(common.inspect(stdout)); + assert.equal(body, stdout); +}; + +server.listen(common.PORT, function () { + var cmd = 'curl --insecure https://127.0.0.1:' + common.PORT + '/'; + console.error("executing %j", cmd); + exec(cmd, afterCurl); +}); + +process.on('exit', function () { + assert.equal(1, reqCount); +}); From 73f4ec51fde3bfb3e16d6d706aae42656d083c1a Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Tue, 4 Jan 2011 10:36:05 -0800 Subject: [PATCH 42/46] hack for ending https connections --- lib/http.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/http.js b/lib/http.js index 786f373e74..f061effe33 100644 --- a/lib/http.js +++ b/lib/http.js @@ -833,11 +833,17 @@ function connectionListener(socket) { if (message._last) { // No more messages to be pushed out. - // HACK: need way to do this with socket interface - if (socket._writeQueue.length) { - socket.__destroyOnDrain = true; //socket.end(); + if (!socket._writeQueue) { + // Putting this here for https. Really need to add below hack to + // both socket and https interfaces. + socket.end(); } else { - socket.destroy(); + // HACK: need way to do this with socket interface + if (socket._writeQueue.length) { + socket.__destroyOnDrain = true; //socket.end(); + } else { + socket.destroy(); + } } } else if (socket._outgoing.length) { From 2957382991c6559fa397d0f9a790ee82f5029b37 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Tue, 4 Jan 2011 11:22:17 -0800 Subject: [PATCH 43/46] Implement new stream method, destroySoon Still missing on fs.WriteStream --- doc/api/streams.markdown | 5 +++++ lib/fs.js | 3 +++ lib/http.js | 15 +-------------- lib/net.js | 8 ++++++++ lib/tls.js | 14 ++++++++++++++ 5 files changed, 31 insertions(+), 14 deletions(-) diff --git a/doc/api/streams.markdown b/doc/api/streams.markdown index 51540cc1c0..354c2ce32a 100644 --- a/doc/api/streams.markdown +++ b/doc/api/streams.markdown @@ -65,6 +65,11 @@ Resumes the incoming `'data'` events after a `pause()`. Closes the underlying file descriptor. Stream will not emit any more events. + +### stream.destroySoon() + +After the write queue is drained, close the file descriptor. + ### stream.pipe(destination, [options]) This is a `Stream.prototype` method available on all `Stream`s. diff --git a/lib/fs.js b/lib/fs.js index b9e6b2d716..75b7cf98c6 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -981,3 +981,6 @@ WriteStream.prototype.destroy = function(cb) { } }; + +// TODO: WriteStream.prototype.destroySoon() + diff --git a/lib/http.js b/lib/http.js index f061effe33..960d28aaa3 100644 --- a/lib/http.js +++ b/lib/http.js @@ -733,13 +733,11 @@ function httpSocketSetup(socket) { // An array of outgoing messages for the socket. In pipelined connections // we need to keep track of the order they were sent. socket._outgoing = []; - socket.__destroyOnDrain = false; // NOTE: be sure not to use ondrain elsewhere in this file! socket.ondrain = function() { var message = socket._outgoing[0]; if (message) message.emit('drain'); - if (socket.__destroyOnDrain) socket.destroy(); }; } @@ -833,18 +831,7 @@ function connectionListener(socket) { if (message._last) { // No more messages to be pushed out. - if (!socket._writeQueue) { - // Putting this here for https. Really need to add below hack to - // both socket and https interfaces. - socket.end(); - } else { - // HACK: need way to do this with socket interface - if (socket._writeQueue.length) { - socket.__destroyOnDrain = true; //socket.end(); - } else { - socket.destroy(); - } - } + socket.destroySoon(); } else if (socket._outgoing.length) { // Push out the next message. diff --git a/lib/net.js b/lib/net.js index f240f8a351..24f2e4a5bb 100644 --- a/lib/net.js +++ b/lib/net.js @@ -554,6 +554,7 @@ Stream.prototype._onWritable = function() { if (this.flush()) { if (this._events && this._events['drain']) this.emit('drain'); if (this.ondrain) this.ondrain(); // Optimization + if (this.__destroyOnDrain) this.destroy(); } }; @@ -705,6 +706,13 @@ Stream.prototype.resume = function() { this._readWatcher.start(); }; +Stream.prototype.destroySoon = function() { + if (this.flush()) { + this.destroy(); + } else { + this.__destroyOnDrain = true; + } +}; Stream.prototype.destroy = function(exception) { // pool is shared between sockets, so don't need to free it here. diff --git a/lib/tls.js b/lib/tls.js index 08fa742ac8..41197f385c 100644 --- a/lib/tls.js +++ b/lib/tls.js @@ -139,6 +139,19 @@ CryptoStream.prototype.end = function(d) { }; +CryptoStream.prototype.destroySoon = function(err) { + if (this.pair._done) return; + + this.pair._cycle(); + + if (this._pending.length) { + this.__destroyOnDrain = true; + } else { + this.end(); + } +}; + + CryptoStream.prototype.destroy = function(err) { if (this.pair._done) return; this.pair._destroy(); @@ -249,6 +262,7 @@ CryptoStream.prototype._suck = function() { if (havePending && this._pending && this._pending.length === 0) { debug('drain'); this.emit('drain'); + if (this.__destroyOnDrain) this.end(); } }; From bc1d758408ce67db8fbdb212d7677831eac55d9e Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Tue, 4 Jan 2011 11:25:36 -0800 Subject: [PATCH 44/46] net.js: Check that readWatcher exists before pause, resume --- lib/net.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/net.js b/lib/net.js index 24f2e4a5bb..832954aa3e 100644 --- a/lib/net.js +++ b/lib/net.js @@ -695,15 +695,17 @@ Stream.prototype.setTimeout = function(msecs) { Stream.prototype.pause = function() { - this._readWatcher.stop(); + if (this._readWatcher) this._readWatcher.stop(); }; Stream.prototype.resume = function() { if (this.fd === null) throw new Error('Cannot resume() closed Stream.'); - this._readWatcher.stop(); - this._readWatcher.set(this.fd, true, false); - this._readWatcher.start(); + if (this._readWatcher) { + this._readWatcher.stop(); + this._readWatcher.set(this.fd, true, false); + this._readWatcher.start(); + } }; Stream.prototype.destroySoon = function() { From cda4d56bcf235baf15c401276b281de5955edd5d Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Tue, 4 Jan 2011 11:39:12 -0800 Subject: [PATCH 45/46] Implement WriteStream.destroySoon --- lib/fs.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/fs.js b/lib/fs.js index 75b7cf98c6..af18877a8d 100644 --- a/lib/fs.js +++ b/lib/fs.js @@ -981,6 +981,6 @@ WriteStream.prototype.destroy = function(cb) { } }; - -// TODO: WriteStream.prototype.destroySoon() +// There is no shutdown() for files. +WriteStream.prototype.destroySoon = WriteStream.prototype.end; From 131546e7339d6960ea91629915468c6f04a33cf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mihai=20C=C4=83lin=20Bazon?= Date: Tue, 4 Jan 2011 12:24:17 -0800 Subject: [PATCH 46/46] realpath files during module load --- src/node.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/node.js b/src/node.js index 1d1ca687d8..57ff87f062 100644 --- a/src/node.js +++ b/src/node.js @@ -169,7 +169,7 @@ try { var stats = fs.statSync(requestPath); if (stats && !stats.isDirectory()) { - return requestPath; + return fs.realpathSync(requestPath); } } catch (e) {} return false; @@ -277,6 +277,7 @@ if (!filename) { throw new Error("Cannot find module '" + request + "'"); } + id = filename; return [id, filename]; }