UNPKG

asmimproved-dbgmits

Version:

Provides the ability to control GDB and LLDB programmatically via GDB/MI.

319 lines 20.1 kB
// Copyright (c) 2015 Vadim Macagon // MIT License, see LICENSE file for full terms. "use strict"; 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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