3 const util = require('util');
4 const path = require('path');
5 const net = require('net');
6 const vm = require('vm');
7 const Module = require('module');
8 const repl = Module.requireRepl();
9 const inherits = util.inherits;
10 const assert = require('assert');
11 const spawn = require('child_process').spawn;
12 const Buffer = require('buffer').Buffer;
14 exports.start = function(argv, stdin, stdout) {
15 argv || (argv = process.argv.slice(2));
17 if (argv.length < 1) {
18 console.error('Usage: node debug script.js');
19 console.error(' node debug <host>:<port>');
20 console.error(' node debug -p <pid>');
24 // Setup input/output streams
25 stdin = stdin || process.stdin;
26 stdout = stdout || process.stdout;
28 var args = ['--debug-brk'].concat(argv),
29 interface_ = new Interface(stdin, stdout, args);
33 process.on('uncaughtException', function(e) {
34 console.error("There was an internal error in Node's debugger. " +
35 'Please report this bug.');
36 console.error(e.message);
37 console.error(e.stack);
38 if (interface_.child) interface_.child.kill();
47 // Parser/Serializer for V8 debugger protocol
48 // https://github.com/v8/v8/wiki/Debugging-Protocol
51 // p = new Protocol();
53 // p.onResponse = function(res) {
54 // // do stuff with response from V8
57 // socket.setEncoding('utf8');
58 // socket.on('data', function(s) {
59 // // Pass strings into the protocol
67 exports.Protocol = Protocol;
70 Protocol.prototype._newRes = function(raw) {
71 this.res = { raw: raw || '', headers: {} };
72 this.state = 'headers';
78 Protocol.prototype.execute = function(d) {
84 var endHeaderIndex = res.raw.indexOf('\r\n\r\n');
86 if (endHeaderIndex < 0) break;
88 var rawHeader = res.raw.slice(0, endHeaderIndex);
89 var endHeaderByteIndex = Buffer.byteLength(rawHeader, 'utf8');
90 var lines = rawHeader.split('\r\n');
91 for (var i = 0; i < lines.length; i++) {
92 var kv = lines[i].split(/: +/);
93 res.headers[kv[0]] = kv[1];
96 this.contentLength = +res.headers['Content-Length'];
97 this.bodyStartByteIndex = endHeaderByteIndex + 4;
101 var len = Buffer.byteLength(res.raw, 'utf8');
102 if (len - this.bodyStartByteIndex < this.contentLength) {
107 var resRawByteLength = Buffer.byteLength(res.raw, 'utf8');
109 if (resRawByteLength - this.bodyStartByteIndex >= this.contentLength) {
110 var buf = new Buffer(resRawByteLength);
111 buf.write(res.raw, 0, resRawByteLength, 'utf8');
113 buf.slice(this.bodyStartByteIndex,
114 this.bodyStartByteIndex +
115 this.contentLength).toString('utf8');
117 res.body = res.body.length ? JSON.parse(res.body) : {};
120 this.onResponse(res);
122 this._newRes(buf.slice(this.bodyStartByteIndex +
123 this.contentLength).toString('utf8'));
128 throw new Error('Unknown state');
133 Protocol.prototype.serialize = function(req) {
134 req.type = 'request';
135 req.seq = this.reqSeq++;
136 var json = JSON.stringify(req);
137 return 'Content-Length: ' + Buffer.byteLength(json, 'utf8') +
145 net.Stream.call(this);
146 var protocol = this.protocol = new Protocol(this);
147 this._reqCallbacks = [];
150 this.currentFrame = NO_FRAME;
151 this.currentSourceLine = -1;
154 this.breakpoints = [];
156 // Note that 'Protocol' requires strings instead of Buffers.
157 socket.setEncoding('utf8');
158 socket.on('data', function(d) {
162 protocol.onResponse = this._onResponse.bind(this);
164 inherits(Client, net.Stream);
165 exports.Client = Client;
168 Client.prototype._addHandle = function(desc) {
169 if (desc === null || typeof desc !== 'object' ||
170 typeof desc.handle !== 'number') {
174 this.handles[desc.handle] = desc;
176 if (desc.type === 'script') {
177 this._addScript(desc);
182 const natives = process.binding('natives');
185 Client.prototype._addScript = function(desc) {
186 this.scripts[desc.id] = desc;
188 desc.isNative = (desc.name.replace('.js', '') in natives) ||
189 desc.name == 'node.js';
194 Client.prototype._removeScript = function(desc) {
195 this.scripts[desc.id] = undefined;
199 Client.prototype._onResponse = function(res) {
203 this._reqCallbacks.some(function(fn, i) {
204 if (fn.request_seq == res.body.request_seq) {
214 if (res.headers.Type == 'connect') {
215 // Request a list of scripts for our own storage.
220 } else if (res.body && res.body.event == 'break') {
221 this.emit('break', res.body);
224 } else if (res.body && res.body.event == 'exception') {
225 this.emit('exception', res.body);
228 } else if (res.body && res.body.event == 'afterCompile') {
229 this._addHandle(res.body.body.script);
232 } else if (res.body && res.body.event == 'scriptCollected') {
234 this._removeScript(res.body.body.script);
237 } else if (res.body && res.body.event === 'compileError') {
238 // This event is not used anywhere right now, perhaps somewhere in the
244 this._reqCallbacks.splice(index, 1);
247 var err = res.success === false && (res.message || true) ||
248 res.body.success === false && (res.body.message || true);
249 cb(err, res.body && res.body.body || res.body, res);
252 if (!handled) this.emit('unhandledResponse', res.body);
256 Client.prototype.req = function(req, cb) {
257 this.write(this.protocol.serialize(req));
258 cb.request_seq = req.seq;
259 this._reqCallbacks.push(cb);
263 Client.prototype.reqVersion = function(cb) {
264 cb = cb || function() {};
265 this.req({ command: 'version' }, function(err, body, res) {
266 if (err) return cb(err);
267 cb(null, res.body.body.V8Version, res.body.running);
272 Client.prototype.reqLookup = function(refs, cb) {
275 // TODO: We have a cache of handle's we've already seen in this.handles
276 // This can be used if we're careful.
284 cb = cb || function() {};
285 this.req(req, function(err, res) {
286 if (err) return cb(err);
287 for (var ref in res) {
288 if (res[ref] !== null && typeof res[ref] === 'object') {
289 self._addHandle(res[ref]);
297 Client.prototype.reqScopes = function(cb) {
304 cb = cb || function() {};
305 this.req(req, function(err, res) {
306 if (err) return cb(err);
307 var refs = res.scopes.map(function(scope) {
308 return scope.object.ref;
311 self.reqLookup(refs, function(err, res) {
312 if (err) return cb(err);
314 var globals = Object.keys(res).map(function(key) {
315 return res[key].properties.map(function(prop) {
320 cb(null, globals.reverse());
325 // This is like reqEval, except it will look up the expression in each of the
326 // scopes associated with the current frame.
327 Client.prototype.reqEval = function(expression, cb) {
330 if (this.currentFrame == NO_FRAME) {
331 // Only need to eval in global scope.
332 this.reqFrameEval(expression, NO_FRAME, cb);
336 cb = cb || function() {};
337 // Otherwise we need to get the current frame to see which scopes it has.
338 this.reqBacktrace(function(err, bt) {
339 if (err || !bt.frames) {
344 var frame = bt.frames[self.currentFrame];
346 var evalFrames = frame.scopes.map(function(s) {
348 var x = bt.frames[s.index];
353 self._reqFramesEval(expression, evalFrames, cb);
358 // Finds the first scope in the array in which the expression evals.
359 Client.prototype._reqFramesEval = function(expression, evalFrames, cb) {
360 if (evalFrames.length == 0) {
361 // Just eval in global scope.
362 this.reqFrameEval(expression, NO_FRAME, cb);
367 var i = evalFrames.shift();
369 cb = cb || function() {};
370 this.reqFrameEval(expression, i, function(err, res) {
371 if (!err) return cb(null, res);
372 self._reqFramesEval(expression, evalFrames, cb);
377 Client.prototype.reqFrameEval = function(expression, frame, cb) {
381 arguments: { expression: expression }
384 if (frame == NO_FRAME) {
385 req.arguments.global = true;
387 req.arguments.frame = frame;
390 cb = cb || function() {};
391 this.req(req, function(err, res) {
392 if (!err) self._addHandle(res);
399 // TODO: from, to, bottom
400 Client.prototype.reqBacktrace = function(cb) {
401 this.req({ command: 'backtrace', arguments: { inlineRefs: true } }, cb);
405 // reqSetExceptionBreak(type, cb)
406 // TODO: from, to, bottom
407 Client.prototype.reqSetExceptionBreak = function(type, cb) {
409 command: 'setexceptionbreak',
410 arguments: { type: type, enabled: true }
415 // Returns an array of objects like this:
424 // sourceStart: '(function(process) {\n\n ',
425 // sourceLength: 15939,
427 // compilationType: 0,
428 // context: { ref: 10 },
429 // text: 'node.js (lines: 562)' }
431 Client.prototype.reqScripts = function(cb) {
433 cb = cb || function() {};
435 this.req({ command: 'scripts' }, function(err, res) {
436 if (err) return cb(err);
438 for (var i = 0; i < res.length; i++) {
439 self._addHandle(res[i]);
446 Client.prototype.reqContinue = function(cb) {
447 this.currentFrame = NO_FRAME;
448 this.req({ command: 'continue' }, cb);
451 Client.prototype.listbreakpoints = function(cb) {
452 this.req({ command: 'listbreakpoints' }, cb);
455 Client.prototype.setBreakpoint = function(req, cb) {
457 command: 'setbreakpoint',
464 Client.prototype.clearBreakpoint = function(req, cb) {
466 command: 'clearbreakpoint',
473 Client.prototype.reqSource = function(from, to, cb) {
484 // client.next(1, cb);
485 Client.prototype.step = function(action, count, cb) {
488 arguments: { stepaction: action, stepcount: count }
491 this.currentFrame = NO_FRAME;
496 Client.prototype.mirrorObject = function(handle, depth, cb) {
501 if (handle.type === 'object') {
502 // The handle looks something like this:
505 // className: 'Object',
506 // constructorFunction: { ref: 9 },
507 // protoObject: { ref: 4 },
508 // prototypeObject: { ref: 2 },
509 // properties: [ { name: 'hello', propertyType: 1, ref: 10 } ],
510 // text: '#<an Object>' }
512 // For now ignore the className and constructor and prototype.
513 // TJ's method of object inspection would probably be good for this:
514 // https://groups.google.com/forum/?pli=1#!topic/nodejs-dev/4gkWBOimiOg
516 var propertyRefs = handle.properties.map(function(p) {
520 cb = cb || function() {};
521 this.reqLookup(propertyRefs, function(err, res) {
523 console.error('problem with reqLookup');
531 if (handle.className == 'Array') {
533 } else if (handle.className == 'Date') {
534 mirror = new Date(handle.value);
541 handle.properties.forEach(function(prop, i) {
542 var value = res[prop.ref];
545 mirrorValue = value.value ? value.value : value.text;
550 if (Array.isArray(mirror) && typeof prop.name !== 'number') {
551 // Skip the 'length' property.
559 if (value && value.handle && depth > 0) {
561 self.mirrorObject(value, depth - 1, function(err, result) {
562 if (!err) keyValues[i].value = result;
569 function waitForOthers() {
570 if (--waiting === 0 && cb) {
571 keyValues.forEach(function(kv) {
572 mirror[kv.name] = kv.value;
579 } else if (handle.type === 'function') {
581 } else if (handle.type === 'null') {
583 } else if (handle.value !== undefined) {
585 } else if (handle.type === 'undefined') {
590 process.nextTick(cb, null, val);
594 Client.prototype.fullTrace = function(cb) {
597 cb = cb || function() {};
598 this.reqBacktrace(function(err, trace) {
599 if (err) return cb(err);
600 if (trace.totalFrames <= 0) return cb(Error('No frames'));
604 for (var i = 0; i < trace.frames.length; i++) {
605 var frame = trace.frames[i];
609 // receiver: { ref: 1 },
611 // script: { ref: 7 },
612 // constructCall: false,
614 // debuggerFrame: false,
620 // sourceLineText: ' debugger;',
621 // scopes: [ { type: 1, index: 0 }, { type: 0, index: 1 } ],
622 // text: '#00 blah() /home/ryan/projects/node/test-debug.js l...' }
623 refs.push(frame.script.ref);
624 refs.push(frame.func.ref);
625 refs.push(frame.receiver.ref);
628 self.reqLookup(refs, function(err, res) {
629 if (err) return cb(err);
631 for (var i = 0; i < trace.frames.length; i++) {
632 var frame = trace.frames[i];
633 frame.script = res[frame.script.ref];
634 frame.func = res[frame.func.ref];
635 frame.receiver = res[frame.receiver.ref];
652 'setBreakpoint (sb)',
653 'clearBreakpoint (cb)'
671 var helpMessage = 'Commands: ' + commands.map(function(group) {
672 return group.join(', ');
676 function SourceUnderline(sourceText, position, repl) {
677 if (!sourceText) return '';
679 var head = sourceText.slice(0, position),
680 tail = sourceText.slice(position);
682 // Colourize char if stdout supports colours
683 if (repl.useColors) {
684 tail = tail.replace(/(.+?)([^\w]|$)/, '\u001b[32m$1\u001b[39m$2');
687 // Return source line with coloured char at `position`
695 function SourceInfo(body) {
696 var result = body.exception ? 'exception in ' : 'break in ';
699 if (body.script.name) {
700 var name = body.script.name,
701 dir = path.resolve() + '/';
703 // Change path to relative, if possible
704 if (name.indexOf(dir) === 0) {
705 name = name.slice(dir.length);
710 result += '[unnamed]';
714 result += body.sourceLine + 1;
716 if (body.exception) result += '\n' + body.exception.text;
721 // This class is the repl-enabled debugger interface which is invoked on
723 function Interface(stdin, stdout, args) {
727 this.stdout = stdout;
730 // Two eval modes are available: controlEval and debugEval
731 // But controlEval is used by default
736 eval: this.controlEval.bind(this),
738 ignoreUndefined: true
740 if (parseInt(process.env['NODE_NO_READLINE'], 10)) {
741 opts.terminal = false;
742 } else if (parseInt(process.env['NODE_FORCE_READLINE'], 10)) {
743 opts.terminal = true;
745 // Emulate Ctrl+C if we're emulating terminal
746 if (!this.stdout.isTTY) {
747 process.on('SIGINT', function() {
748 self.repl.rli.emit('SIGINT');
752 if (parseInt(process.env['NODE_DISABLE_COLORS'], 10)) {
753 opts.useColors = false;
756 this.repl = repl.start(opts);
758 // Do not print useless warning
759 repl._builtinLibs.splice(repl._builtinLibs.indexOf('repl'), 1);
761 // Kill child process when main process dies
762 this.repl.on('exit', function() {
766 // Handle all possible exits
767 process.on('exit', this.killChild.bind(this));
768 process.once('SIGTERM', process.exit.bind(process, 0));
769 process.once('SIGHUP', process.exit.bind(process, 0));
771 var proto = Interface.prototype;
772 const ignored = ['pause', 'resume', 'exitRepl', 'handleBreak',
773 'requireConnection', 'killChild', 'trySpawn',
774 'controlEval', 'debugEval', 'print', 'childPrint',
783 'setBreakpoint': 'sb',
784 'clearBreakpoint': 'cb',
788 function defineProperty(key, protoKey) {
790 var fn = proto[protoKey].bind(self);
792 if (proto[protoKey].length === 0) {
793 Object.defineProperty(self.repl.context, key, {
799 self.repl.context[key] = fn;
803 // Copy all prototype methods in repl context
804 // Setup them as getters if possible
805 for (var i in proto) {
806 if (Object.prototype.hasOwnProperty.call(proto, i) &&
807 ignored.indexOf(i) === -1) {
808 defineProperty(i, i);
809 if (shortcut[i]) defineProperty(shortcut[i], i);
816 this.context = this.repl.context;
821 this.breakpoints = [];
824 // Run script automatically
827 // XXX Need to figure out why we need this delay
828 setTimeout(function() {
830 self.run(function() {
840 Interface.prototype.pause = function() {
841 if (this.killed || this.paused++ > 0) return this;
842 this.repl.rli.pause();
847 Interface.prototype.resume = function(silent) {
848 if (this.killed || this.paused === 0 || --this.paused !== 0) return this;
849 this.repl.rli.resume();
850 if (silent !== true) {
851 this.repl.displayPrompt();
863 // Clear current line
864 Interface.prototype.clearline = function() {
865 if (this.stdout.isTTY) {
866 this.stdout.cursorTo(0);
867 this.stdout.clearLine(1);
869 this.stdout.write('\b');
873 // Print text to output stream
874 Interface.prototype.print = function(text, oneline) {
875 if (this.killed) return;
878 this.stdout.write(typeof text === 'string' ? text : util.inspect(text));
880 if (oneline !== true) {
881 this.stdout.write('\n');
885 // Format and print text from child process
886 Interface.prototype.childPrint = function(text) {
887 this.print(text.toString().split(/\r\n|\r|\n/g).filter(function(chunk) {
889 }).map(function(chunk) {
892 this.repl.displayPrompt(true);
896 Interface.prototype.error = function(text) {
902 // Debugger's `break` event handler
903 Interface.prototype.handleBreak = function(r) {
908 // Save execution context's data
909 this.client.currentSourceLine = r.sourceLine;
910 this.client.currentSourceLineText = r.sourceLineText;
911 this.client.currentSourceColumn = r.sourceColumn;
912 this.client.currentFrame = 0;
913 this.client.currentScript = r.script && r.script.name;
916 this.print(SourceInfo(r));
918 // Show watchers' values
919 this.watchers(true, function(err) {
920 if (err) return self.error(err);
930 // Internal method for checking connection state
931 Interface.prototype.requireConnection = function() {
933 this.error('App isn\'t running... Try `run` instead');
942 // Used for debugger's commands evaluation and execution
943 Interface.prototype.controlEval = function(code, context, filename, callback) {
945 // Repeat last command if empty line are going to be evaluated
946 if (this.repl.rli.history && this.repl.rli.history.length > 0) {
948 code = this.repl.rli.history[0] + '\n';
952 var result = vm.runInContext(code, context, filename);
954 // Repl should not ask for next command
955 // if current one was asynchronous.
956 if (this.paused === 0) return callback(null, result);
958 // Add a callback for asynchronous command
959 // (it will be automatically invoked by .resume() method
960 this.waiting = function() {
961 callback(null, result);
968 // Used for debugger's remote evaluation (`repl`) commands
969 Interface.prototype.debugEval = function(code, context, filename, callback) {
970 if (!this.requireConnection()) return;
973 client = this.client;
975 // Repl asked for scope variables
976 if (code === '.scope') {
977 client.reqScopes(callback);
981 var frame = client.currentFrame === NO_FRAME ? frame : undefined;
985 // Request remote evaluation globally or in current frame
986 client.reqFrameEval(code, frame, function(err, res) {
993 // Request object by handles (and it's sub-properties)
994 client.mirrorObject(res, 3, function(err, mirror) {
995 callback(null, mirror);
1004 // Adds spaces and prefix to number
1005 // maxN is a maximum number we should have space for
1006 function leftPad(n, prefix, maxN) {
1007 var s = n.toString(),
1008 nchars = Math.max(2, String(maxN).length) + 1,
1009 nspaces = nchars - s.length - 1;
1011 for (var i = 0; i < nspaces; i++) {
1022 // Print help message
1023 Interface.prototype.help = function() {
1024 this.print(helpMessage);
1029 Interface.prototype.run = function() {
1030 var callback = arguments[0];
1033 this.error('App is already running... Try `restart` instead');
1034 callback && callback(true);
1036 this.trySpawn(callback);
1042 Interface.prototype.restart = function() {
1043 if (!this.requireConnection()) return;
1050 // XXX need to wait a little bit for the restart to work?
1051 setTimeout(function() {
1059 Interface.prototype.version = function() {
1060 if (!this.requireConnection()) return;
1065 this.client.reqVersion(function(err, v) {
1076 Interface.prototype.list = function(delta) {
1077 if (!this.requireConnection()) return;
1079 delta || (delta = 5);
1082 client = this.client,
1083 from = client.currentSourceLine - delta + 1,
1084 to = client.currentSourceLine + delta + 1;
1087 client.reqSource(from, to, function(err, res) {
1089 self.error('You can\'t list source code right now');
1094 var lines = res.source.split('\n');
1095 for (var i = 0; i < lines.length; i++) {
1096 var lineno = res.fromLine + i + 1;
1097 if (lineno < from || lineno > to) continue;
1099 var current = lineno == 1 + client.currentSourceLine,
1100 breakpoint = client.breakpoints.some(function(bp) {
1101 return (bp.scriptReq === client.currentScript ||
1102 bp.script === client.currentScript) &&
1107 // The first line needs to have the module wrapper filtered out of
1109 var wrapper = Module.wrapper[0];
1110 lines[i] = lines[i].slice(wrapper.length);
1112 client.currentSourceColumn -= wrapper.length;
1115 // Highlight executing statement
1118 line = SourceUnderline(lines[i],
1119 client.currentSourceColumn,
1125 var prefixChar = ' ';
1128 } else if (breakpoint) {
1132 self.print(leftPad(lineno, prefixChar, to) + ' ' + line);
1139 Interface.prototype.backtrace = function() {
1140 if (!this.requireConnection()) return;
1143 client = this.client;
1146 client.fullTrace(function(err, bt) {
1148 self.error('Can\'t request backtrace now');
1153 if (bt.totalFrames == 0) {
1154 self.print('(empty stack)');
1157 firstFrameNative = bt.frames[0].script.isNative;
1159 for (var i = 0; i < bt.frames.length; i++) {
1160 var frame = bt.frames[i];
1161 if (!firstFrameNative && frame.script.isNative) break;
1163 var text = '#' + i + ' ';
1164 if (frame.func.inferredName && frame.func.inferredName.length > 0) {
1165 text += frame.func.inferredName + ' ';
1167 text += path.basename(frame.script.name) + ':';
1168 text += (frame.line + 1) + ':' + (frame.column + 1);
1173 self.print(trace.join('\n'));
1181 // First argument tells if it should display internal node scripts or not
1182 // (available only for internal debugger's functions)
1183 Interface.prototype.scripts = function() {
1184 if (!this.requireConnection()) return;
1186 var client = this.client,
1187 displayNatives = arguments[0] || false,
1191 for (var id in client.scripts) {
1192 var script = client.scripts[id];
1193 if (script !== null && typeof script === 'object' && script.name) {
1194 if (displayNatives ||
1195 script.name == client.currentScript ||
1198 (script.name == client.currentScript ? '* ' : ' ') +
1200 path.basename(script.name)
1205 this.print(scripts.join('\n'));
1210 // Continue execution of script
1211 Interface.prototype.cont = function() {
1212 if (!this.requireConnection()) return;
1216 this.client.reqContinue(function(err) {
1217 if (err) self.error(err);
1223 // Step commands generator
1224 Interface.stepGenerator = function(type, count) {
1226 if (!this.requireConnection()) return;
1231 self.client.step(type, count, function(err, res) {
1232 if (err) self.error(err);
1239 // Jump to next command
1240 Interface.prototype.next = Interface.stepGenerator('next', 1);
1244 Interface.prototype.step = Interface.stepGenerator('in', 1);
1248 Interface.prototype.out = Interface.stepGenerator('out', 1);
1252 Interface.prototype.watch = function(expr) {
1253 this._watchers.push(expr);
1257 Interface.prototype.unwatch = function(expr) {
1258 var index = this._watchers.indexOf(expr);
1260 // Unwatch by expression
1262 // Unwatch by watcher number
1263 this._watchers.splice(index !== -1 ? index : +expr, 1);
1267 Interface.prototype.watchers = function() {
1269 var verbose = arguments[0] || false;
1270 var callback = arguments[1] || function() {};
1271 var waiting = this._watchers.length;
1282 this._watchers.forEach(function(watcher, i) {
1283 self.debugEval(watcher, null, null, function(err, value) {
1284 values[i] = err ? '<error>' : value;
1290 if (--waiting === 0) {
1291 if (verbose) self.print('Watchers:');
1293 self._watchers.forEach(function(watcher, i) {
1294 self.print(leftPad(i, ' ', self._watchers.length - 1) +
1295 ': ' + watcher + ' = ' +
1296 JSON.stringify(values[i]));
1299 if (verbose) self.print('');
1308 // Break on exception
1309 Interface.prototype.breakOnException = function breakOnException() {
1310 if (!this.requireConnection()) return;
1314 // Break on exceptions
1316 this.client.reqSetExceptionBreak('all', function(err, res) {
1322 Interface.prototype.setBreakpoint = function(script, line,
1323 condition, silent) {
1324 if (!this.requireConnection()) return;
1330 // setBreakpoint() should insert breakpoint on current line
1331 if (script === undefined) {
1332 script = this.client.currentScript;
1333 line = this.client.currentSourceLine + 1;
1336 // setBreakpoint(line-number) should insert breakpoint in current script
1337 if (line === undefined && typeof script === 'number') {
1339 script = this.client.currentScript;
1342 if (script === undefined) {
1343 this.print('Cannot determine the current script, ' +
1344 'make sure the debugged process is paused.');
1348 if (/\(\)$/.test(script)) {
1349 // setBreakpoint('functionname()');
1352 target: script.replace(/\(\)$/, ''),
1353 condition: condition
1356 // setBreakpoint('scriptname')
1357 if (script != +script && !this.client.scripts[script]) {
1358 var scripts = this.client.scripts;
1359 for (var id in scripts) {
1362 scripts[id].name.indexOf(script) !== -1) {
1373 if (ambiguous) return this.error('Script name is ambiguous');
1374 if (line <= 0) return this.error('Line should be a positive value');
1382 condition: condition
1385 this.print('Warning: script \'' + script + '\' was not loaded yet.');
1386 var escapedPath = script.replace(/([/\\.?*()^${}|[\]])/g, '\\$1');
1387 var scriptPathRegex = '^(.*[\\/\\\\])?' + escapedPath + '$';
1389 type: 'scriptRegExp',
1390 target: scriptPathRegex,
1392 condition: condition
1398 self.client.setBreakpoint(req, function(err, res) {
1408 // Try load scriptId and line from response
1410 scriptId = res.script_id;
1411 line = res.line + 1;
1414 // Remember this breakpoint even if scriptId is not resolved yet
1415 self.client.breakpoints.push({
1418 script: (self.client.scripts[scriptId] || {}).name,
1420 condition: condition,
1429 Interface.prototype.clearBreakpoint = function(script, line) {
1430 if (!this.requireConnection()) return;
1437 this.client.breakpoints.some(function(bp, i) {
1438 if (bp.scriptId === script ||
1439 bp.scriptReq === script ||
1440 (bp.script && bp.script.indexOf(script) !== -1)) {
1441 if (index !== undefined) {
1445 if (bp.line === line) {
1453 if (!scriptId && !this.client.scripts[script]) {
1454 var scripts = this.client.scripts;
1455 for (var id in scripts) {
1458 scripts[id].name.indexOf(script) !== -1) {
1467 if (ambiguous) return this.error('Script name is ambiguous');
1469 if (scriptId === undefined) {
1470 return this.error('Script ' + script + ' not found');
1473 if (breakpoint === undefined) {
1474 return this.error('Breakpoint not found on line ' + line);
1479 breakpoint: breakpoint
1483 self.client.clearBreakpoint(req, function(err, res) {
1487 self.client.breakpoints.splice(index, 1);
1496 Interface.prototype.breakpoints = function() {
1497 if (!this.requireConnection()) return;
1501 this.client.listbreakpoints(function(err, res) {
1512 // Pause child process
1513 Interface.prototype.pause_ = function() {
1514 if (!this.requireConnection()) return;
1517 cmd = 'process._debugPause();';
1520 this.client.reqFrameEval(cmd, NO_FRAME, function(err, res) {
1530 // Kill child process
1531 Interface.prototype.kill = function() {
1532 if (!this.child) return;
1537 // Activate debug repl
1538 Interface.prototype.repl = function() {
1539 if (!this.requireConnection()) return;
1543 self.print('Press Ctrl + C to leave debug repl');
1545 // Don't display any default messages
1546 var listeners = this.repl.rli.listeners('SIGINT').slice(0);
1547 this.repl.rli.removeAllListeners('SIGINT');
1549 function exitDebugRepl() {
1550 // Restore all listeners
1551 process.nextTick(function() {
1552 listeners.forEach(function(listener) {
1553 self.repl.rli.on('SIGINT', listener);
1560 self.repl.rli.removeListener('SIGINT', exitDebugRepl);
1561 self.repl.removeListener('exit', exitDebugRepl);
1564 // Exit debug repl on SIGINT
1565 this.repl.rli.on('SIGINT', exitDebugRepl);
1567 // Exit debug repl on repl exit
1568 this.repl.on('exit', exitDebugRepl);
1571 this.repl.eval = this.debugEval.bind(this);
1572 this.repl.context = {};
1575 this.history.control = this.repl.rli.history;
1576 this.repl.rli.history = this.history.debug;
1578 this.repl.rli.setPrompt('> ');
1579 this.repl.displayPrompt();
1584 Interface.prototype.exitRepl = function() {
1586 this.repl.eval = this.controlEval.bind(this);
1589 this.history.debug = this.repl.rli.history;
1590 this.repl.rli.history = this.history.control;
1592 this.repl.context = this.context;
1593 this.repl.rli.setPrompt('debug> ');
1594 this.repl.displayPrompt();
1599 Interface.prototype.quit = function() {
1605 // Kills child process
1606 Interface.prototype.killChild = function() {
1614 this.breakpoints = this.client.breakpoints;
1616 this.client.destroy();
1622 // Spawns child process (and restores breakpoints)
1623 Interface.prototype.trySpawn = function(cb) {
1625 breakpoints = this.breakpoints || [],
1626 port = exports.port,
1628 childArgs = this.args;
1631 assert(!this.child);
1633 var isRemote = false;
1634 if (this.args.length === 2) {
1635 var match = this.args[1].match(/^([^:]+):(\d+)$/);
1638 // Connecting to remote debugger
1639 // `node debug localhost:5858`
1641 port = parseInt(match[2], 10);
1644 } else if (this.args.length === 3) {
1645 // `node debug -p pid`
1646 if (this.args[1] === '-p' && /^\d+$/.test(this.args[2])) {
1647 const pid = parseInt(this.args[2], 10);
1649 process._debugProcess(pid);
1651 if (e.code === 'ESRCH') {
1652 console.error(`Target process: ${pid} doesn't exist.`);
1659 var match = this.args[1].match(/^--port=(\d+)$/);
1661 // Start debugger on custom port
1662 // `node debug --port=5858 app.js`
1663 port = parseInt(match[1], 10);
1664 childArgs = ['--debug-brk=' + port].concat(this.args.slice(2));
1670 // pipe stream into debugger
1671 this.child = spawn(process.execPath, childArgs);
1673 this.child.stdout.on('data', this.childPrint.bind(this));
1674 this.child.stderr.on('data', this.childPrint.bind(this));
1679 var client = self.client = new Client(),
1680 connectionAttempts = 0;
1682 client.once('ready', function() {
1683 self.stdout.write(' ok\n');
1685 // Restore breakpoints
1686 breakpoints.forEach(function(bp) {
1687 self.print('Restoring breakpoint ' + bp.scriptReq + ':' + bp.line);
1688 self.setBreakpoint(bp.scriptReq, bp.line, bp.condition, true);
1691 client.on('close', function() {
1693 self.print('program terminated');
1703 client.on('unhandledResponse', function(res) {
1705 self.print('\nunhandled res:' + JSON.stringify(res));
1709 client.on('break', function(res) {
1710 self.handleBreak(res.body);
1713 client.on('exception', function(res) {
1714 self.handleBreak(res.body);
1717 client.on('error', connectError);
1718 function connectError() {
1719 // If it's failed to connect 10 times then print failed message
1720 if (connectionAttempts >= 10) {
1721 console.error(' failed, please retry');
1724 setTimeout(attemptConnect, 500);
1727 function attemptConnect() {
1728 ++connectionAttempts;
1729 self.stdout.write('.');
1730 client.connect(port, host);
1733 self.print('connecting to ' + host + ':' + port + ' ..', true);
1737 this.child.stderr.once('data', function() {
1738 setImmediate(attemptConnect);