dbgmits
Version:
Provides the ability to control GDB and LLDB programmatically via GDB/MI.
1,072 lines (1,071 loc) • 88.1 kB
JavaScript
// Copyright (c) 2015 Vadim Macagon
// MIT License, see LICENSE file for full terms.
var __extends = this.__extends || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
__.prototype = b.prototype;
d.prototype = new __();
};
/// <reference path="../typings/lib/tsd.d.ts" />
var child_process_1 = require('child_process');
var readline = require('readline');
var os = require('os');
var path = require('path');
var events = require('events');
var parser = require('./mi_output_parser');
var mi_output_1 = require('./mi_output');
var pty = require('pty.js');
var DebugCommand = (function () {
/**
* @param cmd MI command string (minus the token and dash prefix).
* @param token Token that can be used to match up the command with a response.
* @param done Callback to invoke once a response is received for the command.
*/
function DebugCommand(cmd, token, done) {
this.token = token;
this.text = cmd;
this.done = done;
}
return DebugCommand;
})();
/**
* Used to indicate failure of a MI command sent to the debugger.
*/
var CommandFailedError = (function () {
function CommandFailedError(message, command, code, token) {
this.name = "CommandFailedError";
this.message = message;
this.code = code;
this.command = command;
this.token = token;
}
return CommandFailedError;
})();
exports.CommandFailedError = CommandFailedError;
/**
* Used to indicate the response to an MI command didn't match the expected format.
*/
var MalformedResponseError = (function () {
/**
* @param message The description of the error.
* @param response The malformed response text (usually just the relevant part).
* @param command The command text that was sent to the debugger (minus token and dash prefix).
* @param token Token of the command (if the command had one).
*/
function MalformedResponseError(message, response, command, token) {
this.message = message;
this.response = response;
this.command = command;
this.token = token;
this.name = "MalformedResponseError";
}
return MalformedResponseError;
})();
exports.MalformedResponseError = MalformedResponseError;
(function (TargetStopReason) {
/** A breakpoint was hit. */
TargetStopReason[TargetStopReason["BreakpointHit"] = 0] = "BreakpointHit";
/** A step instruction finished. */
TargetStopReason[TargetStopReason["EndSteppingRange"] = 1] = "EndSteppingRange";
/** A step-out instruction finished. */
TargetStopReason[TargetStopReason["FunctionFinished"] = 2] = "FunctionFinished";
/** The target finished executing and terminated normally. */
TargetStopReason[TargetStopReason["ExitedNormally"] = 3] = "ExitedNormally";
/** The target was signalled. */
TargetStopReason[TargetStopReason["SignalReceived"] = 4] = "SignalReceived";
/** The target encountered an exception (this is LLDB specific). */
TargetStopReason[TargetStopReason["ExceptionReceived"] = 5] = "ExceptionReceived";
/** Catch-all for any of the other numerous reasons. */
TargetStopReason[TargetStopReason["Unrecognized"] = 6] = "Unrecognized";
})(exports.TargetStopReason || (exports.TargetStopReason = {}));
var TargetStopReason = exports.TargetStopReason;
/** Indicates how much information should be retrieved when calling
* [[DebugSession.getLocalVariables]].
*/
(function (VariableDetailLevel) {
/** Only variable names will be retrieved, not their types or values. */
VariableDetailLevel[VariableDetailLevel["None"] = 0] = "None";
/** Only variable names and values will be retrieved, not their types. */
VariableDetailLevel[VariableDetailLevel["All"] = 1] = "All";
/**
* The name and type will be retrieved for all variables, however values will only be retrieved
* for simple variable types (not arrays, structures or unions).
*/
VariableDetailLevel[VariableDetailLevel["Simple"] = 2] = "Simple";
})(exports.VariableDetailLevel || (exports.VariableDetailLevel = {}));
var VariableDetailLevel = exports.VariableDetailLevel;
/** Output format specifiers for watch values. */
(function (WatchFormatSpec) {
WatchFormatSpec[WatchFormatSpec["Binary"] = 0] = "Binary";
WatchFormatSpec[WatchFormatSpec["Decimal"] = 1] = "Decimal";
WatchFormatSpec[WatchFormatSpec["Hexadecimal"] = 2] = "Hexadecimal";
WatchFormatSpec[WatchFormatSpec["Octal"] = 3] = "Octal";
/**
* This specifier is used to indicate that one of the other ones should be automatically chosen
* based on the expression type, for example `Decimal` for integers, `Hexadecimal` for pointers.
*/
WatchFormatSpec[WatchFormatSpec["Default"] = 4] = "Default";
})(exports.WatchFormatSpec || (exports.WatchFormatSpec = {}));
var WatchFormatSpec = exports.WatchFormatSpec;
/** A watch may have one or more of these attributes associated with it. */
(function (WatchAttribute) {
/** Indicates the watch value can be modified. */
WatchAttribute[WatchAttribute["Editable"] = 0] = "Editable";
/**
* Indicates the watch value can't be modified. This will be the case for any watch with
* children (at least when implemented correctly by the debugger, *cough* not LLDB-MI *cough*).
*/
WatchAttribute[WatchAttribute["NonEditable"] = 1] = "NonEditable";
})(exports.WatchAttribute || (exports.WatchAttribute = {}));
var WatchAttribute = exports.WatchAttribute;
/** Output format specifiers for register values. */
(function (RegisterValueFormatSpec) {
RegisterValueFormatSpec[RegisterValueFormatSpec["Binary"] = 0] = "Binary";
RegisterValueFormatSpec[RegisterValueFormatSpec["Decimal"] = 1] = "Decimal";
RegisterValueFormatSpec[RegisterValueFormatSpec["Hexadecimal"] = 2] = "Hexadecimal";
RegisterValueFormatSpec[RegisterValueFormatSpec["Octal"] = 3] = "Octal";
RegisterValueFormatSpec[RegisterValueFormatSpec["Raw"] = 4] = "Raw";
/**
* This specifier is used to indicate that one of the other ones should be automatically chosen.
*/
RegisterValueFormatSpec[RegisterValueFormatSpec["Default"] = 5] = "Default";
})(exports.RegisterValueFormatSpec || (exports.RegisterValueFormatSpec = {}));
var RegisterValueFormatSpec = exports.RegisterValueFormatSpec;
/**
* A debug session provides two-way communication with a debugger process via the GDB/LLDB
* machine interface.
*
* Currently commands are queued and executed one at a time in the order they are issued,
* a command will not be executed until all the previous commands have been acknowledged by the
* debugger.
*
* Out of band notifications from the debugger are emitted via events, the names of these events
* are provided by the EVENT_XXX static constants.
*/
var DebugSession = (function (_super) {
__extends(DebugSession, _super);
/**
* In most cases [[startDebugSession]] should be used to construct new instances.
*
* @param inStream Debugger responses and notifications will be read from this stream.
* @param outStream Debugger commands will be written to this stream.
*/
function DebugSession(inStream, outStream) {
_super.call(this);
this.outStream = outStream;
this.lineReader = readline.createInterface({
input: inStream,
output: null
});
this.lineReader.on('line', this.parseDebbugerOutput.bind(this));
this.nextCmdId = 1;
this.cmdQueue = [];
this.cleanupWasCalled = false;
}
Object.defineProperty(DebugSession.prototype, "logger", {
get: function () {
return this._logger;
},
set: function (logger) {
this._logger = logger;
},
enumerable: true,
configurable: true
});
/**
* Ends the debugging session.
*
* @param notifyDebugger If **false** the session is cleaned up immediately without waiting for
* the debugger to respond (useful in cases where the debugger terminates
* unexpectedly). If **true** the debugger is asked to exit, and once the
* request is acknowldeged the session is cleaned up.
*/
DebugSession.prototype.end = function (notifyDebugger) {
var _this = this;
if (notifyDebugger === void 0) { notifyDebugger = true; }
return new Promise(function (resolve, reject) {
var cleanup = function (err, data) {
_this.cleanupWasCalled = true;
_this.lineReader.close();
err ? reject(err) : resolve();
};
if (!_this.cleanupWasCalled) {
notifyDebugger ? _this.enqueueCommand(new DebugCommand('gdb-exit', null, cleanup))
: cleanup(null, null);
}
;
});
};
/**
* Returns `true` if [[EVENT_FUNCTION_FINISHED]] can be emitted during this debugging session.
*
* LLDB-MI currently doesn't emit [[EVENT_FUNCTION_FINISHED]] after stepping out of a function,
* instead it emits [[EVENT_STEP_FINISHED]] just like it does for any other stepping operation.
*/
DebugSession.prototype.canEmitFunctionFinishedNotification = function () {
return false;
};
DebugSession.prototype.emitExecNotification = function (name, data) {
switch (name) {
case 'running':
this.emit(DebugSession.EVENT_TARGET_RUNNING, data['thread-id']);
break;
case 'stopped':
if (this.logger) {
this.logger.debug(data);
}
var standardNotify = {
reason: parseTargetStopReason(data.reason),
threadId: parseInt(data['thread-id'], 10),
stoppedThreads: parseStoppedThreadsList(data['stopped-threads']),
processCore: data.core
};
this.emit(DebugSession.EVENT_TARGET_STOPPED, standardNotify);
// emit a more specialized event for notifications that contain additional info
switch (standardNotify.reason) {
case TargetStopReason.BreakpointHit:
var breakpointNotify = {
reason: standardNotify.reason,
threadId: standardNotify.threadId,
stoppedThreads: standardNotify.stoppedThreads,
processorCore: standardNotify.processorCore,
breakpointId: parseInt(data.bkptno, 10),
frame: extractFrameInfo(data.frame)
};
this.emit(DebugSession.EVENT_BREAKPOINT_HIT, breakpointNotify);
break;
case TargetStopReason.EndSteppingRange:
var stepNotify = {
reason: standardNotify.reason,
threadId: standardNotify.threadId,
stoppedThreads: standardNotify.stoppedThreads,
processorCore: standardNotify.processorCore,
frame: extractFrameInfo(data.frame)
};
this.emit(DebugSession.EVENT_STEP_FINISHED, stepNotify);
break;
case TargetStopReason.FunctionFinished:
var stepOutNotify = {
reason: standardNotify.reason,
threadId: standardNotify.threadId,
stoppedThreads: standardNotify.stoppedThreads,
processorCore: standardNotify.processorCore,
frame: extractFrameInfo(data.frame),
resultVar: data['gdb-result-var'],
returnValue: data['return-value']
};
this.emit(DebugSession.EVENT_FUNCTION_FINISHED, stepOutNotify);
break;
case TargetStopReason.SignalReceived:
var signalNotify = {
reason: standardNotify.reason,
threadId: standardNotify.threadId,
stoppedThreads: standardNotify.stoppedThreads,
processorCore: standardNotify.processorCore,
signalCode: data.signal,
signalName: data['signal-name'],
signalMeaning: data['signal-meaning']
};
this.emit(DebugSession.EVENT_SIGNAL_RECEIVED, signalNotify);
break;
case TargetStopReason.ExceptionReceived:
var exceptionNotify = {
reason: standardNotify.reason,
threadId: standardNotify.threadId,
stoppedThreads: standardNotify.stoppedThreads,
processorCore: standardNotify.processorCore,
exception: data.exception
};
this.emit(DebugSession.EVENT_EXCEPTION_RECEIVED, exceptionNotify);
break;
}
break;
default:
// TODO: log and keep on going
break;
}
};
DebugSession.prototype.emitAsyncNotification = function (name, data) {
var shlibInfo;
switch (name) {
case 'thread-group-added':
this.emit(DebugSession.EVENT_THREAD_GROUP_ADDED, data);
break;
case 'thread-group-removed':
this.emit(DebugSession.EVENT_THREAD_GROUP_REMOVED, data);
break;
case 'thread-group-started':
this.emit(DebugSession.EVENT_THREAD_GROUP_STARTED, data);
break;
case 'thread-group-exited':
this.emit(DebugSession.EVENT_THREAD_GROUP_EXITED, { id: data.id, exitCode: data['exit-code'] });
break;
case 'thread-created':
this.emit(DebugSession.EVENT_THREAD_CREATED, { id: data.id, groupId: data['group-id'] });
break;
case 'thread-exited':
this.emit(DebugSession.EVENT_THREAD_EXITED, { id: data.id, groupId: data['group-id'] });
break;
case 'thread-selected':
this.emit(DebugSession.EVENT_THREAD_SELECTED, data);
break;
case 'library-loaded':
this.emit(DebugSession.EVENT_LIB_LOADED, {
id: data.id,
targetName: data['target-name'],
hostName: data['host-name'],
threadGroup: data['thread-group'],
symbolsPath: data['symbols-path'],
loadAddress: data.loaded_addr
});
break;
case 'library-unloaded':
this.emit(DebugSession.EVENT_LIB_UNLOADED, {
id: data.id,
targetName: data['target-name'],
hostName: data['host-name'],
threadGroup: data['thread-group'],
symbolsPath: data['symbols-path'],
loadAddress: data.loaded_addr
});
break;
default:
// TODO: log and keep on going
break;
}
;
};
/**
* Parse a single line containing a response to a MI command or some sort of async notification.
*/
DebugSession.prototype.parseDebbugerOutput = function (line) {
// '(gdb)' (or '(gdb) ' in some cases) is used to indicate the end of a set of output lines
// from the debugger, but since we process each line individually as it comes in this
// particular marker is of no use
if (line.match(/^\(gdb\)\s*/) || (line === '')) {
return;
}
var cmdQueuePopped = false;
try {
var result = parser.parse(line);
}
catch (err) {
if (this.logger) {
this.logger.error(err, 'Attempted to parse: ->' + line + '<-');
}
throw err;
}
switch (result.recordType) {
case mi_output_1.RecordType.Done:
case mi_output_1.RecordType.Running:
case mi_output_1.RecordType.Connected:
case mi_output_1.RecordType.Exit:
case mi_output_1.RecordType.Error:
// this record is a response for the last command that was sent to the debugger,
// which is the command at the front of the queue
var cmd = this.cmdQueue.shift();
cmdQueuePopped = true;
// todo: check that the token in the response matches the one sent with the command
if (cmd.done) {
if (result.recordType === mi_output_1.RecordType.Error) {
cmd.done(new CommandFailedError(result.data.msg, cmd.text, result.data.code, cmd.token), null);
}
else {
cmd.done(null, result.data);
}
}
break;
case mi_output_1.RecordType.AsyncExec:
this.emitExecNotification(result.data[0], result.data[1]);
break;
case mi_output_1.RecordType.AsyncNotify:
this.emitAsyncNotification(result.data[0], result.data[1]);
break;
case mi_output_1.RecordType.DebuggerConsoleOutput:
this.emit(DebugSession.EVENT_DBG_CONSOLE_OUTPUT, result.data);
break;
case mi_output_1.RecordType.TargetOutput:
this.emit(DebugSession.EVENT_TARGET_OUTPUT, result.data);
break;
case mi_output_1.RecordType.DebuggerLogOutput:
this.emit(DebugSession.EVENT_DBG_LOG_OUTPUT, result.data);
break;
}
// if a command was popped from the qeueu we can send through the next command
if (cmdQueuePopped && (this.cmdQueue.length > 0)) {
this.sendCommandToDebugger(this.cmdQueue[0]);
}
};
/**
* Sends an MI command to the debugger process.
*/
DebugSession.prototype.sendCommandToDebugger = function (command) {
var cmdStr;
if (command.token) {
cmdStr = command.token + "-" + command.text;
}
else {
cmdStr = '-' + command.text;
}
if (this.logger) {
this.logger.info(cmdStr);
}
this.outStream.write(cmdStr + '\n');
};
/**
* Adds an MI command to the back of the command queue.
*
* If the command queue is empty when this method is called then the command is dispatched
* immediately, otherwise it will be dispatched after all the previously queued commands are
* processed.
*/
DebugSession.prototype.enqueueCommand = function (command) {
this.cmdQueue.push(command);
if (this.cmdQueue.length === 1) {
this.sendCommandToDebugger(this.cmdQueue[0]);
}
};
/**
* Sends an MI command to the debugger.
*
* @param command Full MI command string, excluding the optional token and dash prefix.
* @param token Token to be prefixed to the command string (must consist only of digits).
* @returns A promise that will be resolved when the command response is received.
*/
DebugSession.prototype.executeCommand = function (command, token) {
var _this = this;
return new Promise(function (resolve, reject) {
_this.enqueueCommand(new DebugCommand(command, token, function (err, data) { err ? reject(err) : resolve(); }));
});
};
/**
* Sends an MI command to the debugger and returns the response.
*
* @param command Full MI command string, excluding the optional token and dash prefix.
* @param token Token to be prefixed to the command string (must consist only of digits).
* @param transformOutput This function will be invoked with the output of the MI Output parser
* and should transform that output into an instance of type `T`.
* @returns A promise that will be resolved when the command response is received.
*/
DebugSession.prototype.getCommandOutput = function (command, token, transformOutput) {
var _this = this;
return new Promise(function (resolve, reject) {
_this.enqueueCommand(new DebugCommand(command, token, function (err, data) {
if (err) {
reject(err);
}
else {
try {
resolve(transformOutput ? transformOutput(data) : data);
}
catch (err) {
reject(err);
}
}
}));
});
};
/**
* Sets the executable file to be debugged, the symbol table will also be read from this file.
*
* This must be called prior to [[connectToRemoteTarget]] when setting up a remote debugging
* session.
*
* @param file This would normally be a full path to the host's copy of the executable to be
* debugged.
*/
DebugSession.prototype.setExecutableFile = function (file) {
// NOTE: While the GDB/MI spec. contains multiple -file-XXX commands that allow the
// executable and symbol files to be specified separately the LLDB MI driver
// currently (30-Mar-2015) only supports this one command.
return this.executeCommand("file-exec-and-symbols " + file);
};
/**
* Sets the terminal to be used by the next inferior that's launched.
*
* @param slaveName Name of the slave end of a pseudoterminal that should be associated with
* the inferior, see `man pty` for an overview of pseudoterminals.
*/
DebugSession.prototype.setInferiorTerminal = function (slaveName) {
return this.executeCommand('inferior-tty-set ' + slaveName);
};
/**
* Connects the debugger to a remote target.
*
* @param host
* @param port
*/
DebugSession.prototype.connectToRemoteTarget = function (host, port) {
return this.executeCommand("target-select remote " + host + ":" + port);
};
//
// Breakpoint Commands
//
/**
* Adds a new breakpoint.
*
* @param location The location at which a breakpoint should be added, can be specified in the
* following formats:
* - function_name
* - filename:line_number
* - filename:function_name
* - address
* @param options.isTemp Set to **true** to create a temporary breakpoint which will be
* automatically removed after being hit.
* @param options.isHardware Set to **true** to create a hardware breakpoint
* (presently not supported by LLDB MI).
* @param options.isPending Set to **true** if the breakpoint should still be created even if
* the location cannot be parsed (e.g. it refers to uknown files or
* functions).
* @param options.isDisabled Set to **true** to create a breakpoint that is initially disabled,
* otherwise the breakpoint will be enabled by default.
* @param options.isTracepoint Set to **true** to create a tracepoint
* (presently not supported by LLDB MI).
* @param options.condition The debugger will only stop the program execution when this
* breakpoint is hit if the condition evaluates to **true**.
* @param options.ignoreCount The number of times the breakpoint should be hit before it takes
* effect, zero (the default) means the breakpoint will stop the
* program every time it's hit.
* @param options.threadId Restricts the new breakpoint to the given thread.
*/
DebugSession.prototype.addBreakpoint = function (location, options) {
var cmd = 'break-insert';
if (options) {
if (options.isTemp) {
cmd = cmd + ' -t';
}
if (options.isHardware) {
cmd = cmd + ' -h';
}
if (options.isPending) {
cmd = cmd + ' -f';
}
if (options.isDisabled) {
cmd = cmd + ' -d';
}
if (options.isTracepoint) {
cmd = cmd + ' -a';
}
if (options.condition) {
cmd = cmd + ' -c ' + options.condition;
}
if (options.ignoreCount !== undefined) {
cmd = cmd + ' -i ' + options.ignoreCount;
}
if (options.threadId !== undefined) {
cmd = cmd + ' -p ' + options.threadId;
}
}
return this.getCommandOutput(cmd + ' ' + location, null, function (output) {
return extractBreakpointInfo(output);
});
};
/**
* Removes a breakpoint.
*/
DebugSession.prototype.removeBreakpoint = function (breakId) {
return this.executeCommand('break-delete ' + breakId);
};
/**
* Removes multiple breakpoints.
*/
DebugSession.prototype.removeBreakpoints = function (breakIds) {
// FIXME: LLDB MI driver only supports removing one breakpoint at a time,
// so multiple breakpoints need to be removed one by one.
return this.executeCommand('break-delete ' + breakIds.join(' '));
};
/**
* Enables a breakpoint.
*/
DebugSession.prototype.enableBreakpoint = function (breakId) {
return this.executeCommand('break-enable ' + breakId);
};
/**
* Disables a breakpoint.
*/
DebugSession.prototype.disableBreakpoint = function (breakId) {
return this.executeCommand('break-disable ' + breakId);
};
/**
* Tells the debugger to ignore a breakpoint the next `ignoreCount` times it's hit.
*
* @param breakId Identifier of the breakpoint for which the ignore count should be set.
* @param ignoreCount The number of times the breakpoint should be hit before it takes effect,
* zero means the breakpoint will stop the program every time it's hit.
*/
DebugSession.prototype.ignoreBreakpoint = function (breakId, ignoreCount) {
return this.getCommandOutput("break-after " + breakId + " " + ignoreCount, null, function (output) { return extractBreakpointInfo(output); });
};
/**
* Sets the condition under which a breakpoint should take effect when hit.
*
* @param breakId Identifier of the breakpoint for which the condition should be set.
* @param condition Expression to evaluate when the breakpoint is hit, if it evaluates to
* **true** the breakpoint will stop the program, otherwise the breakpoint
* will have no effect.
*/
DebugSession.prototype.setBreakpointCondition = function (breakId, condition) {
return this.executeCommand("break-condition " + breakId + " " + condition);
};
//
// Program Execution Commands
//
/**
* Sets the commandline arguments to be passed to the target process next time it is started
* using [[startTarget]].
*/
DebugSession.prototype.setTargetArguments = function (args) {
return this.executeCommand('exec-arguments ' + args);
};
/**
* Executes an inferior from the beginning until it exits.
*
* Execution may stop before the inferior finishes running due to a number of reasons,
* for example a breakpoint being hit.
* [[EVENT_TARGET_STOPPED]] will be emitted when execution stops.
*
* @param options.threadGroup *(GDB specific)* The identifier of the thread group to start,
* if omitted the currently selected inferior will be started.
* @param options.stopAtStart *(GDB specific)* If `true` then execution will stop at the start
* of the main function.
*/
DebugSession.prototype.startInferior = function (options) {
var fullCmd = 'exec-run';
if (options) {
if (options.threadGroup) {
fullCmd = fullCmd + ' --thread-group ' + options.threadGroup;
}
if (options.stopAtStart) {
fullCmd = fullCmd + ' --start';
}
}
return this.executeCommand(fullCmd, null);
};
/**
* Executes all inferiors from the beginning until they exit.
*
* Execution may stop before an inferior finishes running due to a number of reasons,
* for example a breakpoint being hit.
* [[EVENT_TARGET_STOPPED]] will be emitted when execution stops.
*
* @param stopAtStart *(GDB specific)* If `true` then execution will stop at the start
* of the main function.
*/
DebugSession.prototype.startAllInferiors = function (stopAtStart) {
var fullCmd = 'exec-run --all';
if (stopAtStart) {
fullCmd = fullCmd + ' --start';
}
return this.executeCommand(fullCmd, null);
};
/**
* Kills the currently selected inferior.
*/
DebugSession.prototype.abortInferior = function () {
return this.executeCommand('exec-abort');
};
/**
* Resumes execution of an inferior, execution may stop at any time due to a number of reasons,
* for example a breakpoint being hit.
* [[EVENT_TARGET_STOPPED]] will be emitted when execution stops.
*
* @param options.threadGroup *(GDB specific)* Identifier of the thread group to resume,
* if omitted the currently selected inferior is resumed.
* @param options.reverse *(GDB specific)* If **true** the inferior is executed in reverse.
*/
DebugSession.prototype.resumeInferior = function (options) {
var fullCmd = 'exec-continue';
if (options) {
if (options.threadGroup) {
fullCmd = fullCmd + ' --thread-group ' + options.threadGroup;
}
if (options.reverse) {
fullCmd = fullCmd + ' --reverse';
}
}
return this.executeCommand(fullCmd, null);
};
/**
* Resumes execution of all inferiors.
*
* @param reverse *(GDB specific)* If `true` the inferiors are executed in reverse.
*/
DebugSession.prototype.resumeAllInferiors = function (reverse) {
var fullCmd = 'exec-continue --all';
if (reverse) {
fullCmd = fullCmd + ' --reverse';
}
return this.executeCommand(fullCmd, null);
};
/**
* Interrupts execution of an inferior.
* [[EVENT_TARGET_STOPPED]] will be emitted when execution stops.
*
* @param options.threadGroup The identifier of the thread group to interrupt, if omitted the
* currently selected inferior will be interrupted.
*/
DebugSession.prototype.interruptInferior = function (threadGroup) {
var fullCmd = 'exec-interrupt';
if (threadGroup) {
fullCmd = fullCmd + ' --thread-group ' + threadGroup;
}
return this.executeCommand(fullCmd, null);
};
/**
* Interrupts execution of all threads in all inferiors.
*
* [[EVENT_TARGET_STOPPED]] will be emitted when execution stops.
*/
DebugSession.prototype.interruptAllInferiors = function () {
return this.executeCommand('exec-interrupt --all', null);
};
/**
* Resumes execution of the target until the beginning of the next source line is reached.
* If a function is called while the target is running then execution stops on the first
* source line of the called function.
* [[EVENT_TARGET_STOPPED]] will be emitted when execution stops.
*
* @param options.threadId Identifier of the thread to execute the command on.
* @param options.reverse *(GDB specific)* If **true** the target is executed in reverse.
*/
DebugSession.prototype.stepIntoLine = function (options) {
return this.executeCommand(appendExecCmdOptions('exec-step', options));
};
/**
* Resumes execution of the target until the beginning of the next source line is reached.
* [[EVENT_TARGET_STOPPED]] will be emitted when execution stops.
*
* @param options.threadId Identifier of the thread to execute the command on.
* @param options.reverse *(GDB specific)* If **true** the target is executed in reverse until
* the beginning of the previous source line is reached.
*/
DebugSession.prototype.stepOverLine = function (options) {
return this.executeCommand(appendExecCmdOptions('exec-next', options));
};
/**
* Executes one instruction, if the instruction is a function call then execution stops at the
* beginning of the function.
* [[EVENT_TARGET_STOPPED]] will be emitted when execution stops.
*
* @param options.threadId Identifier of the thread to execute the command on.
* @param options.reverse *(GDB specific)* If **true** the target is executed in reverse until
* the previous instruction is reached.
*/
DebugSession.prototype.stepIntoInstruction = function (options) {
return this.executeCommand(appendExecCmdOptions('exec-step-instruction', options));
};
/**
* Executes one instruction, if the instruction is a function call then execution continues
* until the function returns.
* [[EVENT_TARGET_STOPPED]] will be emitted when execution stops.
*
* @param options.threadId Identifier of the thread to execute the command on.
* @param options.reverse *(GDB specific)* If **true** the target is executed in reverse until
* the previous instruction is reached.
*/
DebugSession.prototype.stepOverInstruction = function (options) {
return this.executeCommand(appendExecCmdOptions('exec-next-instruction', options));
};
/**
* Resumes execution of the target until the current function returns.
* [[EVENT_TARGET_STOPPED]] will be emitted when execution stops.
*
* @param options.threadId Identifier of the thread to execute the command on.
* @param options.reverse *(GDB specific)* If **true** the target is executed in reverse.
*/
DebugSession.prototype.stepOut = function (options) {
return this.executeCommand(appendExecCmdOptions('exec-finish', options));
};
//
// Stack Inspection Commands
//
/**
* Retrieves information about a stack frame.
*
* @param options.threadId The thread for which the stack depth should be retrieved,
* defaults to the currently selected thread if not specified.
* @param options.frameLevel Stack index of the frame for which to retrieve locals,
* zero for the innermost frame, one for the frame from which the call
* to the innermost frame originated, etc. Defaults to the currently
* selected frame if not specified. If a value is provided for this
* option then `threadId` must be specified as well.
*/
DebugSession.prototype.getStackFrame = function (options) {
var fullCmd = 'stack-info-frame';
if (options) {
if (options.threadId !== undefined) {
fullCmd = fullCmd + ' --thread ' + options.threadId;
}
if (options.frameLevel !== undefined) {
fullCmd = fullCmd + ' --frame ' + options.frameLevel;
}
}
return this.getCommandOutput(fullCmd, null, function (output) {
return extractStackFrameInfo(output.frame);
});
};
/**
* Retrieves the current depth of the stack.
*
* @param options.threadId The thread for which the stack depth should be retrieved,
* defaults to the currently selected thread if not specified.
* @param options.maxDepth *(GDB specific)* If specified the returned stack depth will not exceed
* this number.
*/
DebugSession.prototype.getStackDepth = function (options) {
var fullCmd = 'stack-info-depth';
if (options) {
if (options.threadId !== undefined) {
fullCmd = fullCmd + ' --thread ' + options.threadId;
}
if (options.maxDepth !== undefined) {
fullCmd = fullCmd + ' ' + options.maxDepth;
}
}
return this.getCommandOutput(fullCmd, null, function (output) {
return parseInt(output.depth);
});
};
/**
* Retrieves the frames currently on the stack.
*
* The `lowFrame` and `highFrame` options can be used to limit the number of frames retrieved,
* if both are supplied only the frame with levels in that range (inclusive) are retrieved.
* If either `lowFrame` or `highFrame` option is omitted (but not both) then only a single
* frame corresponding to that level is retrieved.
*
* @param options.threadId The thread for which the stack frames should be retrieved,
* defaults to the currently selected thread if not specified.
* @param options.noFrameFilters *(GDB specific)* If `true` the Python frame filters will not be
* executed.
* @param options.lowFrame Must not be larger than the actual number of frames on the stack.
* @param options.highFrame May be larger than the actual number of frames on the stack, in which
* case only the existing frames will be retrieved.
*/
DebugSession.prototype.getStackFrames = function (options) {
var fullCmd = 'stack-list-frames';
if (options) {
if (options.threadId !== undefined) {
fullCmd = fullCmd + ' --thread' + options.threadId;
}
if (options.noFrameFilters === true) {
fullCmd = fullCmd + ' --no-frame-filters';
}
if ((options.lowFrame !== undefined) && (options.highFrame !== undefined)) {
fullCmd = fullCmd + (" " + options.lowFrame + " " + options.highFrame);
}
else if (options.lowFrame !== undefined) {
fullCmd = fullCmd + (" " + options.lowFrame + " " + options.lowFrame);
}
else if (options.highFrame !== undefined) {
fullCmd = fullCmd + (" " + options.highFrame + " " + options.highFrame);
}
}
return this.getCommandOutput(fullCmd, null, function (output) {
var data = output.stack.frame;
if (Array.isArray(data)) {
return data.map(function (frame) { return extractStackFrameInfo(frame); });
}
else {
return [extractStackFrameInfo(data)];
}
});
};
/**
* Retrieves a list of all the arguments for the specified frames.
*
* The `lowFrame` and `highFrame` options can be used to limit the frames for which arguments
* are retrieved. If both are supplied only the frames with levels in that range (inclusive) are
* taken into account, if both are omitted the arguments of all frames currently on the stack
* will be retrieved.
*
* Note that while it's possible to specify a frame range of one frame in order to retrieve the
* arguments of a single frame it's better to just use [[getStackFrameVariables]] instead.
*
* @param detail Specifies what information should be retrieved for each argument.
* @param options.threadId The thread for which arguments should be retrieved,
* defaults to the currently selected thread if not specified.
* @param options.noFrameFilters *(GDB specific)* If `true` then Python frame filters will not be
* executed.
* @param options.skipUnavailable If `true` information about arguments that are not available
* will not be retrieved.
* @param options.lowFrame Must not be larger than the actual number of frames on the stack.
* @param options.highFrame May be larger than the actual number of frames on the stack, in which
* case only the existing frames will be retrieved.
*/
DebugSession.prototype.getStackFrameArgs = function (detail, options) {
var fullCmd = 'stack-list-arguments';
if (options) {
if (options.threadId !== undefined) {
fullCmd = fullCmd + ' --thread ' + options.threadId;
}
if (options.noFrameFilters === true) {
fullCmd = fullCmd + ' --no-frame-filters';
}
if (options.skipUnavailable === true) {
fullCmd = fullCmd + ' --skip-unavailable';
}
}
fullCmd = fullCmd + ' ' + detail;
if (options) {
if ((options.lowFrame !== undefined) && (options.highFrame !== undefined)) {
fullCmd = fullCmd + (" " + options.lowFrame + " " + options.highFrame);
}
else if ((options.lowFrame !== undefined) && (options.highFrame === undefined)) {
throw new Error("highFrame option must be provided to getStackFrameArgs() if lowFrame option is used.");
}
else if ((options.lowFrame === undefined) && (options.highFrame !== undefined)) {
throw new Error("lowFrame option must be provided to getStackFrameArgs() if highFrame option is used.");
}
}
return this.getCommandOutput(fullCmd, null, function (output) {
var data = output['stack-args'];
if (Array.isArray(data.frame)) {
// data is in the form: { frame: [{ level: 0, args: [...] }, { level: 1, args: arg1 }, ...]
return data.frame.map(function (frame) {
return {
level: parseInt(frame.level, 10),
args: Array.isArray(frame.args) ? frame.args : [frame.args]
};
});
}
else {
// data is in the form: { frame: { level: 0, args: [...] }
return [{
level: parseInt(data.frame.level, 10),
args: Array.isArray(data.frame.args) ? data.frame.args : [data.frame.args]
}];
}
});
};
/**
* Retrieves a list of all arguments and local variables in the specified frame.
*
* @param detail Specifies what information to retrieve for each argument or local variable.
* @param options.threadId The thread for which variables should be retrieved,
* defaults to the currently selected thread if not specified.
* @param options.frameLevel Stack index of the frame for which to retrieve locals,
* zero for the innermost frame, one for the frame from which the call
* to the innermost frame originated, etc. Defaults to the currently
* selected frame if not specified.
* @param options.noFrameFilters *(GDB specific)* If `true` then Python frame filters will not be
* executed.
* @param options.skipUnavailable If `true` information about variables that are not available
* will not be retrieved.
*/
DebugSession.prototype.getStackFrameVariables = function (detail, options) {
var fullCmd = 'stack-list-variables';
if (options) {
if (options.threadId !== undefined) {
fullCmd = fullCmd + ' --thread ' + options.threadId;
}
if (options.frameLevel !== undefined) {
fullCmd = fullCmd + ' --frame ' + options.frameLevel;
}
if (options.noFrameFilters === true) {
fullCmd = fullCmd + ' --no-frame-filters';
}
if (options.skipUnavailable === true) {
fullCmd = fullCmd + ' --skip-unavailable';
}
}
fullCmd = fullCmd + ' ' + detail;
return this.getCommandOutput(fullCmd, null, function (output) {
var args = [];
var locals = [];
output.variables.forEach(function (varInfo) {
if (varInfo.arg === '1') {
args.push({ name: varInfo.name, value: varInfo.value, type: varInfo.type });
}
else {
locals.push({ name: varInfo.name, value: varInfo.value, type: varInfo.type });
}
});
return { args: args, locals: locals };
});
};
//
// Watch Manipulation (aka Variable Objects)
//
/**
* Creates a new watch to monitor the value of the given expression.
*
* @param expression Any expression valid in the current language set (so long as it doesn't
* begin with a `*`), or one of the following:
* - a memory cell address, e.g. `*0x0000000000400cd0`
* - a CPU register name, e.g. `$sp`
* @param options.id Unique identifier for the new watch, if omitted one is auto-generated.
* Auto-generated identifiers begin with the letters `var` and are followed by
* one or more digits, when providing your own identifiers it's best to use a
* different naming scheme that doesn't clash with auto-generated identifiers.
* @param options.threadId The thread within which the watch expression will be evaluated.
* *Default*: the currently selected thread.
* @param options.threadGroup
* @param options.frameLevel The index of the stack frame within which the watch expression will
* be evaluated initially, zero for the innermost stack frame. Note that
* if `frameLevel` is specified then `threadId` must also be specified.
* *Default*: the currently selected frame.
* @param options.frameAddress *(GDB specific)* Address of the frame within which the expression
* should be evaluated.
* @param options.isFloating Set to `true` if the expression should be re-evaluated every time
* within the current frame, i.e. it's not bound to a specific frame.
* Set to `false` if the expression should be bound to the frame within
* which the watch is created.
* *Default*: `false`.
*/
DebugSession.prototype.addWatch = function (expression, options) {
var fullCmd = 'var-create';
var id = '-'; // auto-generate id
var addr = '*'; // use current frame
if (options) {
if (options.id) {
id = options.id;
}
if (options.threadId !== undefined) {
fullCmd = fullCmd + ' --thread ' + options.threadId;
}
if (options.threadGroup) {
fullCmd = fullCmd + ' --thread-group ' + options.threadGroup;
}
if (options.frameLevel !== undefined) {
fullCmd = fullCmd + ' --frame ' + options.frameLevel;
}
if (options.isFloating === true) {
addr = '@';
}
else if (options.frameAddress) {
addr = options.frameAddress;
}
}
fullCmd = fullCmd + (" " + id + " " + addr + " " + expression);
return this.getCommandOutput(fullCmd, null, function (output) {
return {
id: output.name,
childCount: parseInt(output.numchild),
value: output.value,
expressionType: output['type'],
threadId: parseInt(output['thread-id']),
hasMoreChildren: output.has_more !== '0',
isDynamic: output.dynamic === '1',
displayHint: output.displayhint
};
});
};
/**
* Destroys a previously created watch.
*
* @param id Identifier of the watch to destroy.
*/
DebugSession.prototype.removeWatch = function (id) {
return this.executeCommand('var-delete ' + id);
};
/**
* Updates the state of an existing watch.
*
* @param id Identifier of the watch to update.
*/
DebugSession.prototype.updateWatch = function (id, detail) {
var fullCmd = 'var-update';
if (detail !== undefined) {
fullCmd = fullCmd + ' ' + detail;
}