asmimproved-dbgmits
Version:
Provides the ability to control GDB and LLDB programmatically via GDB/MI.
319 lines • 20.1 kB
JavaScript
// Copyright (c) 2015 Vadim Macagon
// MIT License, see LICENSE file for full terms.
;
require('source-map-support').install();
var dbgmits = require('../lib/index');
var bunyan = require('bunyan');
var fs = require('fs');
var path = require('path');
var PrettyStream = require('bunyan-prettystream');
function startDebugSession(logger) {
var debuggerType = ('lldb' === process.env['DBGMITS_DEBUGGER']) ? dbgmits.DebuggerType.LLDB : dbgmits.DebuggerType.GDB;
var debugSession = dbgmits.startDebugSession(debuggerType);
if (logger) {
debugSession.logger = logger;
// log event data emitted by DebugSession
var eventsToLog = [
dbgmits.EVENT_TARGET_RUNNING,
dbgmits.EVENT_TARGET_STOPPED,
dbgmits.EVENT_BREAKPOINT_HIT,
dbgmits.EVENT_STEP_FINISHED,
dbgmits.EVENT_FUNCTION_FINISHED,
dbgmits.EVENT_SIGNAL_RECEIVED,
dbgmits.EVENT_EXCEPTION_RECEIVED,
dbgmits.EVENT_THREAD_GROUP_ADDED,
dbgmits.EVENT_THREAD_GROUP_REMOVED,
dbgmits.EVENT_THREAD_GROUP_STARTED,
dbgmits.EVENT_THREAD_GROUP_EXITED,
dbgmits.EVENT_THREAD_CREATED,
dbgmits.EVENT_THREAD_EXITED,
dbgmits.EVENT_THREAD_SELECTED,
dbgmits.EVENT_LIB_LOADED,
dbgmits.EVENT_LIB_UNLOADED,
];
eventsToLog.forEach(function (eventName) {
debugSession.on(eventName, function (data) {
if (debugSession.logger) {
debugSession.logger.debug({ event: eventName, data: data });
}
});
});
// monkey-patch DebugSession methods that return non-void promises and log the values
// the promises are resolved with
var functionsToLog = [
'addBreakpoint',
'ignoreBreakpoint',
'getStackFrame',
'getStackDepth',
'getStackFrames',
'getStackFrameArgs',
'getStackFrameVariables',
'addWatch',
'updateWatch',
'getWatchChildren',
'setWatchValueFormat',
'getWatchValue',
'setWatchValue',
'getWatchAttributes',
'getWatchExpression',
'evaluateExpression',
'readMemory',
'getRegisterNames',
'getRegisterValues',
'disassembleAddressRange',
'disassembleAddressRangeByLine',
'disassembleFile',
'disassembleFileByLine',
'getThread',
'getThreads'
];
functionsToLog.forEach(function (funcName) {
var func = debugSession[funcName];
debugSession[funcName] = function () {
return func.apply(this, arguments)
.then(function (result) {
if (debugSession.logger) {
debugSession.logger.debug({ func: funcName, result: result });
}
return result;
});
};
});
}
return debugSession;
}
exports.startDebugSession = startDebugSession;
/**
* This function performs the following tasks asynchronously (but sequentially):
* 1. Adds a breakpoint on the given function.
* 2. Runs the target until the breakpoint is hit.
* 3. Invokes a callback.
*
* @param onBreakHit Callback to invoke after the specified function is reached.
*/
function runToFunc(debugSession, funcName, onBreakHit) {
var onBreakpointHit = new Promise(function (resolve, reject) {
debugSession.once(dbgmits.EVENT_BREAKPOINT_HIT, function (breakNotify) {
onBreakHit()
.then(resolve)
.catch(reject);
});
});
// add breakpoint to get to the starting point
return debugSession.addBreakpoint(funcName)
.then(function () {
return Promise.all([
onBreakpointHit,
debugSession.startInferior()
]);
});
}
exports.runToFunc = runToFunc;
/**
* This function performs the following tasks asynchronously (but sequentially):
* 1. Adds a breakpoint on the given function.
* 2. Runs the target until the breakpoint is hit.
* 3. Steps out of the function within which the breakpoint was hit.
* 4. Invokes a callback.
*
* @param afterStepOut Callback to invoke after the debugger finishes stepping out of the specified
* function.
*/
function runToFuncAndStepOut(debugSession, funcName, afterStepOut) {
var onStepOutRunTest = function () {
return new Promise(function (resolve, reject) {
if (debugSession.canEmitFunctionFinishedNotification()) {
debugSession.once(dbgmits.EVENT_FUNCTION_FINISHED, function (stepNotify) {
afterStepOut()
.then(resolve)
.catch(reject);
});
}
else {
// FIXME: LLDB-MI currently doesn't emit a distinct notification for step-out so we have
// to listen to the generic step-finished one.
debugSession.once(dbgmits.EVENT_STEP_FINISHED, function (stepNotify) {
afterStepOut()
.then(resolve)
.catch(reject);
});
}
});
};
var onBreakpointStepOut = new Promise(function (resolve, reject) {
debugSession.once(dbgmits.EVENT_BREAKPOINT_HIT, function (breakNotify) {
Promise.all([
onStepOutRunTest(),
debugSession.stepOut()
])
.then(function () { resolve(); })
.catch(reject);
});
});
// add breakpoint to get to the starting point
return debugSession.addBreakpoint(funcName)
.then(function () {
return Promise.all([
onBreakpointStepOut,
debugSession.startInferior()
]);
});
}
exports.runToFuncAndStepOut = runToFuncAndStepOut;
;
/**
* Wraps mocha's beforeEach() function so that a logger can be passed through to the callback
* function `fn`.
*
* The logger instance passed to the `fn` function is created by a custom mocha reporter,
* but only for tests that are in suites wrapped with [[logSuite]], or for tests wrapped with
* [[logTest]].
*
* @param fn A function to execute before each test in the current suite.
*/
function beforeEachTestWithLogger(fn) {
// unfortunately mocha doesn't return the hook instance from beforeEach(), so it's not possible
// to add additional fields to the hook itself, instead we have to add the additional fields
// to the callback passed to beforeEach()
var cb = function () {
// the custom reporter should've already called cb.setLogger() by this stage
return fn(cb.logger);
};
cb.setLogger = function (logger) {
cb.logger = logger;
};
beforeEach(cb);
}
exports.beforeEachTestWithLogger = beforeEachTestWithLogger;
/**
* Creates a new logger for a test.
*
* The logger will create a log file in the `logs/tests` directory.
*
* @param testIndex Test identifier, should be unique for each test.
* @param title Test title.
* @return A new logger instance.
*/
function createLogger(testIndex, title) {
try {
fs.mkdirSync('logs/tests');
}
catch (err) {
if (err.code !== 'EEXIST') {
throw err;
}
}
// pad the test number with zeroes (e.g. 1 -> 001, 10 -> 010, 100 -> 100)
var pad = '000';
var testFilename = (pad + testIndex).slice(-pad.length) + '.log';
var logPath = path.join('logs/tests', testFilename);
var fileStream = fs.createWriteStream(logPath, { flags: 'w' });
var prettyStream = new PrettyStream({ useColor: false });
prettyStream.pipe(fileStream);
var logger = bunyan.createLogger({
name: 'Test ' + testIndex,
streams: [{ level: 'debug', type: 'raw', stream: prettyStream }]
});
logger.info('====== TEST #%d: %s ======', testIndex, title);
return logger;
}
/**
* Attaches a createLogger() function to a mocha.Test.
*
* For example:
* ```
* describe("Thingie", () => {
* beforeEachTestWithLogger((logger: bunyan.Logger) => {
* ...
* });
*
* logTest(it("Does something", () => {
* ...
* }));
* });
* ```
*/
function logTest(test) {
test.createLogger = createLogger;
return test;
}
exports.logTest = logTest;
/**
* Attaches a createLogger() function to a mocha.Suite.
*
* For example:
* ```
* logSuite(describe("Thingie", () => {
* beforeEachTestWithLogger((logger: bunyan.Logger) => {
* ...
* });
*
* it("Does something", () => {
* ...
* });
* }));
* ```
*/
function logSuite(suite) {
suite.createLogger = createLogger;
return suite;
}
exports.logSuite = logSuite;
/**
* Escapes a string so that it can be safely embedded in a regular expression.
*
* @param original The string to escape.
* @return The escaped string.
*/
function escapeStringForRegExp(original) {
return original.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
}
/**
* Helper class for obtaining the line numbers of relevant source lines in the C++ source files
* of the target executables used in tests.
*/
var SourceLineResolver = (function () {
function SourceLineResolver() {
this._sourceLines = [];
}
SourceLineResolver.loadSourceFileSync = function (filename) {
var sourceLineResolver = new SourceLineResolver();
sourceLineResolver.loadFileSync(filename);
return sourceLineResolver;
};
SourceLineResolver.prototype.loadFileSync = function (filename) {
this._sourceLines = fs.readFileSync(filename, 'utf8').split('\n');
};
/**
* Finds the line number of the first source line that matches the given regular expression.
*
* @param sourceLineRegExp The regular expressions to match source lines against, note that
* the expression is matched line by line so it shouldn't be created
* with the multi-line flag.
* @return The line number of the first matching line in the source file,
* or -1 if no matching lines were found.
*/
SourceLineResolver.prototype.getMatchingLineNumber = function (sourceLineRegExp) {
for (var i = 0; i < this._sourceLines.length; ++i) {
if (sourceLineRegExp.test(this._sourceLines[i])) {
return i + 1;
}
}
return -1;
};
/**
* Finds the line number at which a given single-line comment is located.
*
* @param singleLineComment The test of the single-line (prefixed by `//`) comment.
* @return The line number of a single line comment in the source file,
* or -1 if no such comment was found in the source file.
*/
SourceLineResolver.prototype.getCommentLineNumber = function (singleLineComment) {
var escapedComment = escapeStringForRegExp(singleLineComment);
var commentRegExp = new RegExp("^[\\s\\S]*//\\s*" + escapedComment);
return this.getMatchingLineNumber(commentRegExp);
};
return SourceLineResolver;
}());
exports.SourceLineResolver = SourceLineResolver;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGVzdF91dGlscy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3Rlc3QvdGVzdF91dGlscy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxtQ0FBbUM7QUFDbkMsZ0RBQWdEOztBQUVoRCxPQUFPLENBQUMsb0JBQW9CLENBQUMsQ0FBQyxPQUFPLEVBQUUsQ0FBQztBQUV4QyxJQUFZLE9BQU8sV0FBTSxjQUFjLENBQUMsQ0FBQTtBQUN4QyxJQUFZLE1BQU0sV0FBTSxRQUFRLENBQUMsQ0FBQTtBQUNqQyxJQUFZLEVBQUUsV0FBTSxJQUFJLENBQUMsQ0FBQTtBQUN6QixJQUFZLElBQUksV0FBTSxNQUFNLENBQUMsQ0FBQTtBQUM3QixJQUFPLFlBQVksV0FBVyxxQkFBcUIsQ0FBQyxDQUFDO0FBS3JELDJCQUFrQyxNQUFzQjtJQUN0RCxJQUFNLFlBQVksR0FBRyxDQUFDLE1BQU0sS0FBSyxPQUFPLENBQUMsR0FBRyxDQUFDLGtCQUFrQixDQUFDLENBQUMsR0FBRyxPQUFPLENBQUMsWUFBWSxDQUFDLElBQUksR0FBRyxPQUFPLENBQUMsWUFBWSxDQUFDLEdBQUcsQ0FBQztJQUN6SCxJQUFJLFlBQVksR0FBaUIsT0FBTyxDQUFDLGlCQUFpQixDQUFDLFlBQVksQ0FBQyxDQUFDO0lBQ3pFLEVBQUUsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUM7UUFDWCxZQUFZLENBQUMsTUFBTSxHQUFHLE1BQU0sQ0FBQztRQUU3Qix5Q0FBeUM7UUFDekMsSUFBSSxXQUFXLEdBQUc7WUFDaEIsT0FBTyxDQUFDLG9CQUFvQjtZQUM1QixPQUFPLENBQUMsb0JBQW9CO1lBQzVCLE9BQU8sQ0FBQyxvQkFBb0I7WUFDNUIsT0FBTyxDQUFDLG1CQUFtQjtZQUMzQixPQUFPLENBQUMsdUJBQXVCO1lBQy9CLE9BQU8sQ0FBQyxxQkFBcUI7WUFDN0IsT0FBTyxDQUFDLHdCQUF3QjtZQUNoQyxPQUFPLENBQUMsd0JBQXdCO1lBQ2hDLE9BQU8sQ0FBQywwQkFBMEI7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