UNPKG

asmimproved-dbgmits

Version:

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

1,088 lines 63 kB
// Copyright (c) 2015 Vadim Macagon // MIT License, see LICENSE file for full terms. "use strict"; var __extends = (this && this.__extends) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; var readline = require('readline'); var events = require('events'); var parser = require('./mi_output_parser'); var mi_output_1 = require('./mi_output'); var Events = require('./events'); var types_1 = require('./types'); var extractors_1 = require('./extractors'); var errors_1 = require('./errors'); 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; }()); /** * 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) { var _this = this; var events = Events.createEventsForExecNotification(name, data); events.forEach(function (event) { _this.emit(event.name, event.data); }); }; DebugSession.prototype.emitAsyncNotification = function (name, data) { var event = Events.createEventForAsyncNotification(name, data); if (event) { this.emit(event.name, event.data); } else { if (this.logger) { this.logger.warn({ name: name, data: data }, 'Unhandled notification.'); } } }; /** * 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 errors_1.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(Events.EVENT_DBG_CONSOLE_OUTPUT, result.data); break; case mi_output_1.RecordType.TargetOutput: this.emit(Events.EVENT_TARGET_OUTPUT, result.data); break; case mi_output_1.RecordType.DebuggerLogOutput: this.emit(Events.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, extractors_1.extractBreakpointInfo); }; /** * 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); }; /** * Enables multiple breakpoints. */ DebugSession.prototype.enableBreakpoints = function (breakIds) { return this.executeCommand('break-enable ' + breakIds.join(' ')); }; /** * Disables a breakpoint. */ DebugSession.prototype.disableBreakpoint = function (breakId) { return this.executeCommand('break-disable ' + breakId); }; /** * Disables multiple breakpoints. */ DebugSession.prototype.disableBreakpoints = function (breakIds) { return this.executeCommand('break-disable ' + breakIds.join(' ')); }; /** * 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, extractors_1.extractBreakpointInfo); }; /** * 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 inferior next time it is started * using [[startInferior]]. */ DebugSession.prototype.setInferiorArguments = 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)); }; /** * Allows to set internal GDB variables */ DebugSession.prototype.gdbSet = function (variable, value) { return this.executeCommand("gdb-set " + variable + " " + value); }; /** * Break when the expression changes, behaves like a breakpoint */ DebugSession.prototype.breakExpression = function (expression) { return this.getCommandOutput("break-watch " + expression, null, function (data) { return { id: parseInt(data.wpt.number, 10) }; }); }; // // 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 extractors_1.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, 10); }); }; /** * 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 extractors_1.extractStackFrameInfo(frame); }); } else { return [extractors_1.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, 10), value: output.value, expressionType: output['type'], threadId: parseInt(output['thread-id'], 10), 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; } fullCmd = fullCmd + ' ' + id; return this.getCommandOutput(fullCmd, null, function (output) { return output.changelist.map(function (data) { return { id: data.name, childCount: (data.new_num_children ? parseInt(data.new_num_children, 10) : undefined), value: data.value, expressionType: data.new_type, isInScope: data.in_scope === 'true', isObsolete: data.in_scope === 'invalid', hasTypeChanged: data.type_changed === 'true', isDynamic: data.dynamic === '1', displayHint: data.displayhint, hasMoreChildren: data.has_more === '1', newChildren: data.new_children }; }); }); }; /** * Retrieves a list of direct children of the specified watch. * * A watch is automatically created for each child that is retrieved (if one doesn't already exist). * The `from` and `to` options can be used to retrieve a subset of children starting from child * index `from` and up to (but excluding) child index `to`, note that this currently doesn't work * on LLDB. * * @param id Identifier of the watch whose children should be retrieved. * @param options.detail One of: * - [[VariableDetailLevel.None]]: Do not retrieve values of children, this is the default. * - [[VariableDetailLevel.All]]: Retrieve values for all children. * - [[VariableDetailLevel.Simple]]: Only retrieve values of children that have a simple type. * @param options.from Zero-based index of the first child to retrieve, if less than zero the * range is reset. `to` must also be set in order for this option to have any * effect. * @param options.to Zero-based index +1 of the last child to retrieve, if less than zero the * range is reset. `from` must also be set in order for this option to have any * effect. */ DebugSession.prototype.getWatchChildren = function (id, options) { var fullCmd = 'var-list-children'; if (options && (options.detail !== undefined)) { fullCmd = fullCmd + ' ' + options.detail; } fullCmd = fullCmd + ' ' + id; if (options && (options.from !== undefined) && (options.to !== undefined)) { fullCmd = fullCmd + ' ' + options.from + ' ' + options.to; } return this.getCommandOutput(fullCmd, null, function (output) { return extractors_1.extractWatchChildren(output.children); }); }; /** * Sets the output format for the value of a watch. * * @param id Identifier of the watch for which the format specifier should be set. * @param formatSpec The output format for the watch value. * @returns A promise that will be resolved with the value of the watch formatted using the * provided `formatSpec`. */ DebugSession.prototype.setWatchValueFormat = function (id, formatSpec) { var fullCmd = ("var-set-format " + id + " ") + watchFormatSpecToStringMap.get(formatSpec); return this.getCommandOutput(fullCmd, null, function (output) { if (output.value) { return output.value; // GDB-MI } else { return output.changelist[0].value; // LLDB-MI } }); }; /** * Evaluates the watch expression and returns the result. * * @param id Identifier of the watch whose value should be retrieved. * @param formatSpec The output format for the watch value. * @returns A promise that will be resolved with the value of the watch. */ DebugSession.prototype.getWatchValue = function (id, formatSpec) { var fullCmd = 'var-evaluate-expression'; if (formatSpec !== undefined) { fullCmd = fullCmd + ' -f ' + watchFormatSpecToStringMap.get(formatSpec); } fullCmd = fullCmd + ' ' + id; return this.getCommandOutput(fullCmd, null, function (output) { return output.value; }); }; /** * Sets the value of the watch expression to the value of the given expression. * * @param id Identifier of the watch whose value should be modified. * @param expression The value of this expression will be assigned to the watch expression. * @returns A promise that will be resolved with the new value of the watch. */ DebugSession.prototype.setWatchValue = function (id, expression) { return this.getCommandOutput("var-assign " + id + " \"" + expression + "\"", null, function (output) { return output.value; }); }; /** * Retrives a list of attributes for the given watch. * * @param id Identifier of the watch whose attributes should be retrieved. * @returns A promise that will be resolved with the list of watch attributes. */ DebugSession.prototype.getWatchAttributes = function (id) { var cmd = 'var-show-attributes ' + id; return this.getCommandOutput(cmd, null, function (output) { if (output.status) { return [stringToWatchAttributeMap.get(output.status)]; } else if (output.attr) { if (Array.isArray(output.attr)) { return output.attr.map(function (attr) { return stringToWatchAttributeMap.get(attr); }); } else { return [stringToWatchAttributeMap.get(output.attr)]; } } throw new errors_1.MalformedResponseError('Expected to find "status" or "attr", found neither.', output, cmd); }); }; /** * Retrieves an expression that can be evaluated in the current context to obtain the watch value. * * @param id Identifier of the watch whose path expression should be retrieved. * @returns A promise that will be resolved with the path expression of the watch. */ DebugSession.prototype.getWatchExpression = function (id) { var cmd = 'var-info-path-expression ' + id; return this.getCommandOutput(cmd, null, function (output) { if (output.path_expr) { return output.path_expr; } throw new errors_1.MalformedResponseError('Expected to find "path_expr".', output, cmd); }); }; // // Data Inspection & Manipulation // /** * Evaluates the given expression within the target process and returns the result. * * The expression may contain function calls, which will be executed synchronously. * * @param expression The expression to evaluate. * @param options.threadId The thread within which the expression should be evaluated. * *Default*: the currently selected thread. * @param options.frameLevel The index of the stack frame within which the expression should * be evaluated, zero for the innermost stack frame. Note that * if `frameLevel` is specified then `threadId` must also be specified. * *Default*: the currently selected frame. * @returns A promise that will be resolved with the value of the expression. */ DebugSession.prototype.evaluateExpression = function (expression, options) { var fullCmd = 'data-evaluate-expression'; if (options) { if (options.threadId !== undefined) { fullCmd = fullCmd + ' --thread ' + options.threadId; } if (options.frameLevel !== undefined) { fullCmd = fullCmd + ' --frame ' + options.frameLevel; } } fullCmd = fullCmd + (" \"" + expression + "\""); return this.getCommandOutput(fullCmd, null, function (output) { if (output.value) { return output.value; } throw new errors_1.MalformedResponseError('Expected to find "value".', output, fullCmd); }); }; /** * Attempts to read all accessible memory regions in the given range. * * @param address Start of the range from which memory should be read, this can be a literal * address (e.g. `0x00007fffffffed30`) or an expression (e.g. `&someBuffer`) that * evaluates to the desired address. * @param numBytesToRead Number of bytes that should be read. * @param options.byteOffset Offset in bytes relative to `address` from which to begin reading. * @returns A promise that will be resolved with a list of memory blocks that were read. */ DebugSession.prototype.readMemory = function (address, numBytesToRead, options) { var fullCmd = 'data-read-memory-bytes'; if (options && options.byteOffset) { fullCmd = fullCmd + ' -o ' + options.byteOffset; } fullCmd = fullCmd + (" \"" + address + "\" " + numBytesToRead); return this.getCommandOutput(fullCmd, null, function (output) { if (output.memory) { return output.memory; } throw new errors_1.MalformedResponseError('Expected to find "memory".', output, fullCmd); }); }; /** * Retrieves a list of register names for the current target. * * @param registers List of numbers corresponding to the register names to be retrieved. * If this argument is omitted all register names will be retrieved. * @returns A promise that will be resolved with a list of register names. */ DebugSession.prototype.getRegisterNames = function (registers) { var fullCmd = 'data-list-register-names'; if (registers && (registers.length > 0)) { fullCmd = fullCmd + ' ' + registers