Skip to content

Commit

Permalink
fix: debug-brk not working and failed to resolve value by handle, #993 (
Browse files Browse the repository at this point in the history
  • Loading branch information
hustxiaoc committed Apr 9, 2017
1 parent 6ed8414 commit 3ff79ed
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 143 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -3,6 +3,7 @@ node_modules
plugins/*
v8.log
.DS_Store
.idea/
/test/fixtures/work
npm-debug.log
node-inspector-*.tgz
43 changes: 39 additions & 4 deletions lib/BreakEventHandler.js
Expand Up @@ -19,10 +19,8 @@ function BreakEventHandler(config, session) {
this._scriptManager = session.scriptManager;
this._callFramesProvider = new CallFramesProvider(config, session);

this._injectorClient.once('inject', function() {
this._debuggerClient.on('break', this._onBreak.bind(this));
this._debuggerClient.on('exception', this._onBreak.bind(this));
}.bind(this));
this._debuggerClient.on('break', this._onBreak.bind(this));
this._debuggerClient.on('exception', this._onBreak.bind(this));
}

var callbackForNextBreak;
Expand All @@ -45,8 +43,45 @@ Object.defineProperties(BreakEventHandler.prototype, {
}
});

// If we started with --debug-brk, first onBreak should get the `require` handle, so we should detect
// if it is --debug-brk first

BreakEventHandler.prototype._handleOnFirstBreak = function(cb) {
var self = this;
var debuggerClient = this._debuggerClient;
var injectorClient = this._injectorClient;
var isBreak = debuggerClient.target && debuggerClient.target.break === true;
if (this._firstBreakHandled || !isBreak) {
return cb();
}

if (isBreak) {
debuggerClient.request('evaluate', {
expression: 'process._require = require',
global: false,
}, function(err, response) {
if (err) {
cb(err);
} else {
self._firstBreakHandled = true;
// prevent injecting again
injectorClient._injected = true;
async.waterfall([
injectorClient._inject.bind(injectorClient),
injectorClient._onInjection.bind(injectorClient),
], cb);
}
});
}
};

BreakEventHandler.prototype._onBreak = function(obj) {
// ignore breakpoint when injecting
if(obj.script.name === 'bootstrap_node.js' && !this._injectorClient._injected) {
return;
}
async.waterfall([
this._handleOnFirstBreak.bind(this),
this._handleInjectorClientBreak.bind(this, obj),
this._resolveScriptSource.bind(this, obj),
this._handleIgnoreBreakpoint.bind(this, obj),
Expand Down
2 changes: 0 additions & 2 deletions lib/DebuggerAgent.js
Expand Up @@ -64,8 +64,6 @@ DebuggerAgent.prototype = {
this._removeAllBreakpoints.bind(this),
this._reloadScripts.bind(this),
this._tryConnectInjector.bind(this),
this._restartFrameIfPaused.bind(this),
this._sendBacktraceIfPaused.bind(this)
]);
},

Expand Down
17 changes: 14 additions & 3 deletions lib/DebuggerClient.js
Expand Up @@ -53,12 +53,18 @@ Object.defineProperties(DebuggerClient.prototype, {

DebuggerClient.prototype.connect = function() {
this._conn = DebugConnection.attachDebugger(this._port);

this.pendingEvents = [];
this._conn
.on('connect', this._onConnectionOpen.bind(this))
.on('error', this.emit.bind(this, 'error'))
.on('close', this._onConnectionClose.bind(this))
.on('event', function(obj) { this.emit(obj.event, obj.body); }.bind(this));
.on('event', function(obj) {
if (this.isReady) {
this.emit(obj.event, obj.body);
} else {
this.pendingEvents.push(obj);
}
}.bind(this));
};

DebuggerClient.prototype._onConnectionOpen = function() {
Expand All @@ -69,7 +75,8 @@ DebuggerClient.prototype._onConnectionOpen = function() {

var describeProgram = '(' + function() {
return {
pid: process.pid,
break: process.execArgv.indexOf('--debug-brk') > -1,
pid: console.log('') && process.pid,
cwd: process.cwd(),
filename: process.mainModule ? process.mainModule.filename : process.argv[1],
nodeVersion: process.version
Expand All @@ -79,6 +86,10 @@ DebuggerClient.prototype._onConnectionOpen = function() {
this.evaluateGlobal(describeProgram, function(error, result) {
this.target = result;
this.emit('connect');
this.pendingEvents.forEach(function(obj) {
this.emit(obj.event, obj.body);
}.bind(this));
this.pendingEvents = [];
}.bind(this));
};

Expand Down
177 changes: 43 additions & 134 deletions lib/InjectorClient.js
Expand Up @@ -44,15 +44,15 @@ InjectorClient.prototype.tryHandleDebuggerBreak = function(sourceLine, done) {
/**
*/
InjectorClient.prototype.inject = function(cb) {
if (typeof cb !== 'function')
if (typeof cb !== 'function') {
cb = function(error, result) {};
}

var _water = [];

if (this.needsInject) {
_water.unshift(
this._getFuncWithNMInScope.bind(this),
this._findNMInScope.bind(this),
this._injectRequire.bind(this),
this._inject.bind(this),
this._onInjection.bind(this)
);
Expand Down Expand Up @@ -85,94 +85,38 @@ InjectorClient.prototype._resume = function(cb) {
}.bind(this));
};

InjectorClient.prototype._getFuncWithNMInScope = function(cb) {
this._debuggerClient.request('evaluate', {
global: true,
expression: FN_WITH_SCOPED_NM
}, function(error, result) {
if (error) return cb(error);

cb(null, result.handle);
}.bind(this));
};


InjectorClient.prototype._findNMInBackTrace = function(cb) {
// inject process._require before injecting others.
InjectorClient.prototype._injectRequire = function(cb) {
var self = this;
this._debuggerClient.request('backtrace', { inlineRefs: true } , function(err, trace) {
if (err) {
err.message = 'request backtrace, ' + err.message;
return cb(err);
}
var breakpoint;
var debuggerClient = this._debuggerClient;

if (trace.totalFrames <= 0) return cb(Error('No frames'));
var refs = [];
for (var i = 0; i < trace.frames.length; i++) {
var frame = trace.frames[i];
refs.push(frame.script.ref);
refs.push(frame.func.ref);
refs.push(frame.receiver.ref);
function handleBreak(obj) {
if (obj.script.id !== breakpoint.script_id || obj.breakpoints.indexOf(breakpoint.breakpoint) == -1) {
return;
}

var handles = [];

function findNMInScope() {
var handle = handles.pop();
if (handle) {
self._debuggerClient.request('scope', {
functionHandle: handle
}, function(error, result, refs) {
var NM = refs && refs[result.object.ref].properties.filter(function(prop) {
return prop.name == 'NativeModule';
debuggerClient.removeListener('break', handleBreak);
debuggerClient.request('evaluate', {
expression: 'process._require = NativeModule.require',
}, function(err, res) {
if (err) {
cb(err);
} else {
debuggerClient.request('clearbreakpoint', {
breakpoint: breakpoint.breakpoint,
}, function(err) {
self._resume(function() {
cb(err);
});

if (!(NM && NM.length)) {
findNMInScope();
} else {
cb(null , NM[0].ref);
}
});
} else {
cb(new Error('No NativeModule in target scope') , null);
}
}

this._debuggerClient.request('lookup', {handles: refs}, function(err, res) {
if (err) return cb(err);
for (var ref in res) {
var desc = res[ref];
if (util.isObject(desc) &&
desc.type === 'function' &&
desc.source.indexOf('NativeModule.') > -1) {
handles.push(desc.handle);
}
}
findNMInScope();
});
}.bind(this));
};
}

// >= node6.4.0 fix https://github.com/node-inspector/node-inspector/issues/905
InjectorClient.prototype._tryFindNM = function(cb) {
var self = this;
var breakpoint;
this._debuggerClient.once('break', function() {
self._debuggerClient.request('clearbreakpoint', {
breakpoint: breakpoint.breakpoint,
}, function(err) {
if (err) {
err.message = 'request clearbreakpoint, ' + err.message;
return cb(err);
}
self._findNMInBackTrace(function(err, NM) {
self._resume(function() {
cb(err, NM);
});
});
});
}.bind(this));
debuggerClient.on('break', handleBreak);

this._debuggerClient.request('scripts', {
debuggerClient.request('scripts', {
includeSource: true,
}, function(err, res) {
if (err) {
Expand All @@ -198,26 +142,8 @@ InjectorClient.prototype._tryFindNM = function(cb) {
}
}

if (!self._debuggerClient.isRunning) {
self._resume(function() {
self._debuggerClient.request('setbreakpoint', {
type: 'scriptId',
target: desc.id,
line: line + 1
}, function(err, res) {
if (err) {
err.message = 'request setbreakpoint, ' + err.message;
return cb(err);
}
breakpoint = res;
self._debuggerClient.request('evaluate', {
global: true,
expression: 'try{console.assert();}catch(e){}',
});
});
});
} else {
self._debuggerClient.request('setbreakpoint', {
self._resume(function() {
debuggerClient.request('setbreakpoint', {
type: 'scriptId',
target: desc.id,
line: line + 1
Expand All @@ -227,56 +153,39 @@ InjectorClient.prototype._tryFindNM = function(cb) {
return cb(err);
}
breakpoint = res;
self._debuggerClient.request('evaluate', {
// we need to call NativeModule.require
// https://github.com/nodejs/node/blob/v7.8.0/lib/console.js#L95
debuggerClient.request('evaluate', {
global: true,
expression: 'try{console.assert();}catch(e){}',
});
});
}
});
};

InjectorClient.prototype._findNMInScope = function(funcHandle, cb) {
this._debuggerClient.request('scope', {
functionHandle: funcHandle
}, function(error, result, refs) {
if (error) return cb(error);

var NM = refs[result.object.ref].properties.filter(function(prop) {
return prop.name == 'NativeModule';
});
});

if (!NM.length) {
this._tryFindNM(cb);
} else {
cb(error, error ? null : NM[0].ref);
}
}.bind(this));
});
};

/**
* @param {Number} NM - handle of NativeModule object
*/
InjectorClient.prototype._inject = function(NM, cb) {
InjectorClient.prototype._inject = function(cb) {
var injectorServerPath = JSON.stringify(require.resolve('./InjectorServer'));
var options = {
'v8-debug': require.resolve('v8-debug'),
'convert': require.resolve('./convert')
};
var injection = '(function (NM) {' +
'NM.require("module")._load(' + injectorServerPath + ')' +
'(' + JSON.stringify(options) + ')' +
'})(NM)';


var args = {
global: true,
expression: '(function (require) {' +
'require("module")._load(' + injectorServerPath + ')' +
'(' + JSON.stringify(options) + ')' +
'})(process._require)'
};

this._debuggerClient.request(
'evaluate',
{
expression: injection,
global: true,
additional_context: [
{ name: 'NM', handle: NM }
]
},
args,
function(error) {
cb(error);
}
Expand Down

0 comments on commit 3ff79ed

Please sign in to comment.