Added LLDB support (fix #39)

This commit is contained in:
WebFreak001 2016-03-06 12:02:11 +01:00
commit 35afccfd77
12 changed files with 756 additions and 376 deletions

View file

@ -4,18 +4,20 @@ Native VSCode debugger. Currently only using GDB.
## Installation ## Installation
Run `ext install debug` (Ctrl-Shift-P -> install extension + make sure its just called `Debug` and at the right it should say `webfreak`) in visual studio code and install GDB and add the executable to your PATH variable. If you have changed your PATH, don't forget to restart vscode. Then follow the usage tutorial below. Run `ext install debug` (Ctrl-Shift-P -> install extension + make sure its just called `Debug` and at the right it should say `webfreak`) in visual studio code and install GDB/LLDB and add the executable to your PATH variable. If you have changed your PATH, don't forget to restart vscode. Then follow the usage tutorial below.
![Preview](images/preview.png) ![Preview](images/preview.png)
## Usage ## Usage
![Image with red circle around a cog and an orange arrow pointing at GDB](images/tutorial1.png) ![Image with red circle around a gear and a red arrow pointing at GDB and LLDB](images/tutorial1.png)
Open your project and click the debug button in your sidebar. At the top right press Open your project and click the debug button in your sidebar. At the top right press
the little gear icon and select GDB. It will automatically generate the configuration the little gear icon and select GDB or LLDB. It will automatically generate the configuration
you need. you need.
*Note: for LLDB you need to have lldb-mi in your PATH*
![Default config with a red circle around the target](images/tutorial2.png) ![Default config with a red circle around the target](images/tutorial2.png)
Now you need to change `target` to the application you want to debug relative Now you need to change `target` to the application you want to debug relative
@ -35,12 +37,12 @@ while its paused works as expected.
Extending variables is very limited as it does not support child values of variables. Extending variables is very limited as it does not support child values of variables.
Watching expressions works partially but the result does not get properly parsed and Watching expressions works partially but the result does not get properly parsed and
it shows the raw GDB output of the command. It will run `data-evaluate-expression` it shows the raw output of the command. It will run `data-evaluate-expression`
to check for variables. to check for variables.
While running you will get a console where you can manually type GDB commands or GDB/MI While running you will get a console where you can manually type GDB/LLDB commands or MI
commands prepended with a hyphen `-`. The console shows all output GDB gives separated commands prepended with a hyphen `-`. The console shows all output separated
in `stdout` for the application, `stderr` for errors and `log` for GDB log messages. in `stdout` for the application, `stderr` for errors and `log` for log messages.
Some exceptions/signals like segmentation faults will be catched and displayed but Some exceptions/signals like segmentation faults will be catched and displayed but
it does not support for example most D exceptions. it does not support for example most D exceptions.
@ -49,7 +51,7 @@ it does not support for example most D exceptions.
Attaching to existing processes currently only works by specifying the PID in the Attaching to existing processes currently only works by specifying the PID in the
`launch.json` and setting `request` to `"attach"`. You also need to specify the executable `launch.json` and setting `request` to `"attach"`. You also need to specify the executable
path for GDB to find the debug symbols. path for the debugger to find the debug symbols.
``` ```
"request": "attach", "request": "attach",
@ -57,9 +59,9 @@ path for GDB to find the debug symbols.
"target": "4285" "target": "4285"
``` ```
This will attach to PID 4285 which should already run. GDB will pause the program on entering. This will attach to PID 4285 which should already run. GDB will pause the program on entering and LLDB will keep it running.
### Using `gdbserver` for remote debugging ### Using `gdbserver` for remote debugging (GDB only)
You can also connect to a gdbserver instance and debug using that. For that modify the You can also connect to a gdbserver instance and debug using that. For that modify the
`launch.json` by setting `request` to `"attach"` and `remote` to `true` and specifing the `launch.json` by setting `request` to `"attach"` and `remote` to `true` and specifing the

Binary file not shown.

Before

Width:  |  Height:  |  Size: 211 KiB

After

Width:  |  Height:  |  Size: 89 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 53 KiB

Before After
Before After

View file

@ -1,7 +1,7 @@
{ {
"name": "debug", "name": "debug",
"displayName": "Debug", "displayName": "Debug",
"description": "Native debugging for VSCode - Currently in GDB only beta", "description": "GDB & LLDB support for VSCode",
"version": "0.5.0", "version": "0.5.0",
"publisher": "webfreak", "publisher": "webfreak",
"icon": "images/icon-plain.svg", "icon": "images/icon-plain.svg",
@ -172,7 +172,170 @@
"name": "Debug", "name": "Debug",
"type": "gdb", "type": "gdb",
"request": "launch", "request": "launch",
"target": "./output", "target": "./bin/executable",
"cwd": "${workspaceRoot}"
}
]
},
{
"type": "lldb-mi",
"extensions": [],
"program": "./out/src/lldb.js",
"runtime": "node",
"label": "LLDB",
"enableBreakpointsFor": {
"languageIds": [
"c",
"ada",
"cpp",
"cobol",
"fortran",
"pascal",
"modula",
"java",
"pli",
"objective-c",
"objective-cpp",
"d",
"python",
"opencl",
"modula3",
"haskell",
"ocaml",
"swift",
"julia",
"dylan",
"mips",
"renderscript"
]
},
"configurationAttributes": {
"launch": {
"required": [
"target"
],
"properties": {
"target": {
"type": "string",
"description": "Path of executable"
},
"arguments": {
"type": "string",
"description": "Arguments to append after the executable"
},
"cwd": {
"type": "string",
"description": "Path of project"
},
"printCalls": {
"type": "boolean",
"description": "Prints all lldb calls to console",
"default": false
},
"autorun": {
"type": "array",
"description": "lldb commands to run when starting to debug",
"default": []
},
"ssh": {
"required": [
"host",
"cwd",
"user"
],
"type": "object",
"description": "If this is set then the extension will connect to an ssh host and run lldb there",
"properties": {
"host": {
"type": "string",
"description": "Remote host name/ip to connect to"
},
"cwd": {
"type": "string",
"description": "Path of project on the remote"
},
"port": {
"type": "number",
"description": "Remote port number",
"default": 22
},
"user": {
"type": "string",
"description": "Username to connect as"
},
"password": {
"type": "string",
"description": "Plain text password (unsafe; if possible use keyfile instead)"
},
"keyfile": {
"type": "string",
"description": "Absolute path to private key"
},
"forwardX11": {
"type": "boolean",
"description": "If true, the server will redirect x11 to the local host",
"default": true
},
"x11port": {
"type": "number",
"description": "Port to redirect X11 data to (by default port = display + 6000)",
"default": 6000
},
"x11host": {
"type": "string",
"description": "Hostname/ip to redirect X11 data to",
"default": "localhost"
},
"remotex11screen": {
"type": "number",
"description": "Screen to start the application on the remote side",
"default": 0
},
"bootstrap": {
"type": "string",
"description": "Content will be executed on the SSH host before the debugger call."
}
}
}
}
},
"attach": {
"required": [
"target"
],
"properties": {
"target": {
"type": "string",
"description": "PID of running program or program name"
},
"printCalls": {
"type": "boolean",
"description": "Prints all LLDB calls to console",
"default": false
},
"executable": {
"type": "string",
"description": "Path of executable for debugging symbols"
},
"cwd": {
"type": "string",
"description": "Path of project",
"default": "${workspaceRoot}"
},
"autorun": {
"type": "array",
"description": "LLDB commands to run when starting to debug",
"default": []
}
}
}
},
"initialConfigurations": [
{
"name": "Debug",
"type": "lldb-mi",
"request": "launch",
"target": "./bin/executable",
"cwd": "${workspaceRoot}" "cwd": "${workspaceRoot}"
} }
] ]
@ -193,4 +356,4 @@
"typescript": "^1.7.5", "typescript": "^1.7.5",
"vscode": "0.11.x" "vscode": "0.11.x"
} }
} }

View file

@ -2,6 +2,8 @@ const resultRegex = /^([a-zA-Z_\-][a-zA-Z0-9_\-]*)\s*=\s*/;
const variableRegex = /^[a-zA-Z_\-][a-zA-Z0-9_\-]*/; const variableRegex = /^[a-zA-Z_\-][a-zA-Z0-9_\-]*/;
const errorRegex = /^\<.+?\>/; const errorRegex = /^\<.+?\>/;
const referenceRegex = /^0x[0-9a-fA-F]+/; const referenceRegex = /^0x[0-9a-fA-F]+/;
const nullpointerRegex = /^0x0+\b/;
const charRegex = /^([0-9]+) ['"]/;
const numberRegex = /^[0-9]+/; const numberRegex = /^[0-9]+/;
const pointerCombineChar = "."; const pointerCombineChar = ".";
@ -10,11 +12,13 @@ export function isExpandable(value: string): number {
let match; let match;
value = value.trim(); value = value.trim();
if (value.length == 0) return 0; if (value.length == 0) return 0;
else if (value.startsWith("{...}")) return 2; // lldb string/array
else if (value[0] == '{') return 1; // object else if (value[0] == '{') return 1; // object
else if (value.startsWith("true")) return 0; else if (value.startsWith("true")) return 0;
else if (value.startsWith("false")) return 0; else if (value.startsWith("false")) return 0;
else if (value.startsWith("0x0")) return 0; else if (match = nullpointerRegex.exec(value)) return 0;
else if (match = referenceRegex.exec(value)) return 2; // reference else if (match = referenceRegex.exec(value)) return 2; // reference
else if (match = charRegex.exec(value)) return 0;
else if (match = numberRegex.exec(value)) return 0; else if (match = numberRegex.exec(value)) return 0;
else if (match = variableRegex.exec(value)) return 0; else if (match = variableRegex.exec(value)) return 0;
else if (match = errorRegex.exec(value)) return 0; else if (match = errorRegex.exec(value)) return 0;
@ -24,10 +28,11 @@ export function isExpandable(value: string): number {
export function expandValue(variableCreate: Function, value: string, root: string = ""): any { export function expandValue(variableCreate: Function, value: string, root: string = ""): any {
let parseCString = () => { let parseCString = () => {
value = value.trim(); value = value.trim();
if (value[0] != '"') if (value[0] != '"' && value[0] != '\'')
return ""; return "";
let stringEnd = 1; let stringEnd = 1;
let inString = true; let inString = true;
let charStr = value[0];
let remaining = value.substr(1); let remaining = value.substr(1);
let escaped = false; let escaped = false;
while (inString) { while (inString) {
@ -35,7 +40,7 @@ export function expandValue(variableCreate: Function, value: string, root: strin
escaped = false; escaped = false;
else if (remaining[0] == '\\') else if (remaining[0] == '\\')
escaped = true; escaped = true;
else if (remaining[0] == '"') else if (remaining[0] == charStr)
inString = false; inString = false;
remaining = remaining.substr(1); remaining = remaining.substr(1);
@ -82,8 +87,17 @@ export function expandValue(variableCreate: Function, value: string, root: strin
return undefined; return undefined;
let oldContent = value; let oldContent = value;
value = value.substr(1).trim(); value = value.substr(1).trim();
if (value[0] == '}') if (value[0] == '}') {
value = value.substr(1).trim();
return []; return [];
}
if (value.startsWith("...")) {
value = value.substr(3).trim();
if (value[0] == '}') {
value = value.substr(1).trim();
return <any>"<...>";
}
}
let eqPos = value.indexOf("="); let eqPos = value.indexOf("=");
let newValPos1 = value.indexOf("{"); let newValPos1 = value.indexOf("{");
let newValPos2 = value.indexOf(","); let newValPos2 = value.indexOf(",");
@ -138,7 +152,7 @@ export function expandValue(variableCreate: Function, value: string, root: strin
primitive = "false"; primitive = "false";
value = value.substr(5).trim(); value = value.substr(5).trim();
} }
else if (value.startsWith("0x0")) { else if (match = nullpointerRegex.exec(value)) {
primitive = "<nullptr>"; primitive = "<nullptr>";
value = value.substr(3).trim(); value = value.substr(3).trim();
} }
@ -146,6 +160,11 @@ export function expandValue(variableCreate: Function, value: string, root: strin
primitive = "*" + match[0]; primitive = "*" + match[0];
value = value.substr(match[0].length).trim(); value = value.substr(match[0].length).trim();
} }
else if (match = charRegex.exec(value)) {
primitive = match[1];
value = value.substr(match[0].length - 1);
primitive += " " + parseCString();
}
else if (match = numberRegex.exec(value)) { else if (match = numberRegex.exec(value)) {
primitive = match[0]; primitive = match[0];
value = value.substr(match[0].length).trim(); value = value.substr(match[0].length).trim();
@ -199,6 +218,10 @@ export function expandValue(variableCreate: Function, value: string, root: strin
ref = variableCreate(getNamespace("*" + name)); ref = variableCreate(getNamespace("*" + name));
val = "Object@" + val; val = "Object@" + val;
} }
if (typeof val == "string" && val.startsWith("<...>")) {
ref = variableCreate(getNamespace(name));
val = "...";
}
return { return {
name: name, name: name,
value: val, value: val,

View file

@ -10,7 +10,7 @@ import * as nativePath from "path"
let path = posix; let path = posix;
var Client = require("ssh2").Client; var Client = require("ssh2").Client;
function escape(str: string) { export function escape(str: string) {
return str.replace(/\\/g, "\\\\").replace(/"/g, "\\\""); return str.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
} }
@ -32,14 +32,11 @@ export class MI2 extends EventEmitter implements IBackend {
target = nativePath.join(cwd, target); target = nativePath.join(cwd, target);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.isSSH = false; this.isSSH = false;
this.process = ChildProcess.spawn(this.application, this.preargs.concat([target]), { cwd: cwd }); this.process = ChildProcess.spawn(this.application, this.preargs, { cwd: cwd });
this.process.stdout.on("data", this.stdout.bind(this)); this.process.stdout.on("data", this.stdout.bind(this));
this.process.stderr.on("data", this.stdout.bind(this)); this.process.stderr.on("data", this.stdout.bind(this));
this.process.on("exit", (() => { this.emit("quit"); }).bind(this)); this.process.on("exit", (() => { this.emit("quit"); }).bind(this));
let promises = [ let promises = this.initCommands(target, cwd);
this.sendCommand("gdb-set target-async on"),
this.sendCommand("environment-directory \"" + escape(cwd) + "\"")
];
if (procArgs && procArgs.length) if (procArgs && procArgs.length)
promises.push(this.sendCommand("exec-arguments " + procArgs)); promises.push(this.sendCommand("exec-arguments " + procArgs));
if (process.platform == "win32") { if (process.platform == "win32") {
@ -139,12 +136,8 @@ export class MI2 extends EventEmitter implements IBackend {
this.emit("quit"); this.emit("quit");
this.sshConn.end(); this.sshConn.end();
}).bind(this)); }).bind(this));
let promises = [ let promises = this.initCommands(target, cwd);
this.sendCommand("gdb-set target-async on"), promises.push(this.sendCommand("environment-cd \"" + escape(cwd) + "\""));
this.sendCommand("environment-directory \"" + escape(cwd) + "\""),
this.sendCommand("environment-cd \"" + escape(cwd) + "\""),
this.sendCommand("file-exec-and-symbols \"" + escape(target) + "\"")
];
if (procArgs && procArgs.length) if (procArgs && procArgs.length)
promises.push(this.sendCommand("exec-arguments " + procArgs)); promises.push(this.sendCommand("exec-arguments " + procArgs));
Promise.all(promises).then(() => { Promise.all(promises).then(() => {
@ -161,6 +154,14 @@ export class MI2 extends EventEmitter implements IBackend {
}); });
} }
protected initCommands(target: string, cwd: string) {
return [
this.sendCommand("gdb-set target-async on"),
this.sendCommand("environment-directory \"" + escape(cwd) + "\""),
this.sendCommand("file-exec-and-symbols \"" + escape(target) + "\"")
];
}
attach(cwd: string, executable: string, target: string): Thenable<any> { attach(cwd: string, executable: string, target: string): Thenable<any> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let args = []; let args = [];
@ -380,6 +381,10 @@ export class MI2 extends EventEmitter implements IBackend {
return Promise.all(promisses); return Promise.all(promisses);
} }
setBreakPointCondition(bkptNum, condition): Thenable<any> {
return this.sendCommand("break-condition " + bkptNum + " " + condition);
}
addBreakPoint(breakpoint: Breakpoint): Thenable<[boolean, Breakpoint]> { addBreakPoint(breakpoint: Breakpoint): Thenable<[boolean, Breakpoint]> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (this.breakpoints.has(breakpoint)) if (this.breakpoints.has(breakpoint))
@ -393,7 +398,7 @@ export class MI2 extends EventEmitter implements IBackend {
condition: breakpoint.condition condition: breakpoint.condition
}; };
if (breakpoint.condition) { if (breakpoint.condition) {
this.sendCommand("break-condition " + bkptNum + " " + breakpoint.condition).then((result) => { this.setBreakPointCondition(bkptNum, breakpoint.condition).then((result) => {
if (result.resultRecords.resultClass == "done") { if (result.resultRecords.resultClass == "done") {
this.breakpoints.set(newBrk, bkptNum); this.breakpoints.set(newBrk, bkptNum);
resolve([true, newBrk]); resolve([true, newBrk]);
@ -467,7 +472,7 @@ export class MI2 extends EventEmitter implements IBackend {
}); });
}); });
resolve(ret); resolve(ret);
}); }, reject);
}); });
} }
@ -520,18 +525,15 @@ export class MI2 extends EventEmitter implements IBackend {
} }
sendCommand(command: string): Thenable<MINode> { sendCommand(command: string): Thenable<MINode> {
let sel = this.currentToken++;
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.handlers[this.currentToken] = (node: MINode) => { this.handlers[sel] = (node: MINode) => {
if (node.resultRecords && node.resultRecords.resultClass == "error") { if (node && node.resultRecords && node.resultRecords.resultClass === "error")
let msg = node.result("msg") || "Internal error"; reject(node.result("msg") || "Internal error");
this.log("stderr", "Failed to run command `" + command + "`: " + msg);
reject(msg);
}
else else
resolve(node); resolve(node);
}; };
this.sendRaw(this.currentToken + "-" + command); this.sendRaw(sel + "-" + command);
this.currentToken++;
}); });
} }
@ -540,13 +542,13 @@ export class MI2 extends EventEmitter implements IBackend {
} }
printCalls: boolean; printCalls: boolean;
private isSSH: boolean; protected isSSH: boolean;
private sshReady: boolean; protected sshReady: boolean;
private currentToken: number = 1; protected currentToken: number = 1;
private handlers: { [index: number]: (info: MINode) => any } = {}; protected handlers: { [index: number]: (info: MINode) => any } = {};
private breakpoints: Map<Breakpoint, Number> = new Map(); protected breakpoints: Map<Breakpoint, Number> = new Map();
private buffer: string; protected buffer: string;
private process: ChildProcess.ChildProcess; protected process: ChildProcess.ChildProcess;
private stream; protected stream;
private sshConn; protected sshConn;
} }

View file

@ -0,0 +1,50 @@
import { MI2, escape } from "./mi2"
import { Breakpoint } from "../backend"
import * as ChildProcess from "child_process"
import { posix } from "path"
import * as nativePath from "path"
let path = posix;
export class MI2_LLDB extends MI2 {
protected initCommands(target: string, cwd: string) {
return [
this.sendCommand("gdb-set target-async on"),
this.sendCommand("file-exec-and-symbols \"" + escape(target) + "\"")
];
}
attach(cwd: string, executable: string, target: string): Thenable<any> {
return new Promise((resolve, reject) => {
this.process = ChildProcess.spawn(this.application, this.preargs, { cwd: cwd });
this.process.stdout.on("data", this.stdout.bind(this));
this.process.stderr.on("data", this.stdout.bind(this));
this.process.on("exit", (() => { this.emit("quit"); }).bind(this));
Promise.all([
this.sendCommand("gdb-set target-async on"),
this.sendCommand("file-exec-and-symbols \"" + escape(executable) + "\""),
this.sendCommand("target-attach " + target)
]).then(() => {
this.emit("debug-ready");
resolve();
}, reject);
});
}
clearBreakPoints(): Thenable<any> {
return new Promise((resolve, reject) => {
let promises = [];
for (let k in this.breakpoints.values) {
promises.push(this.sendCommand("break-delete " + k).then((result) => {
if (result.resultRecords.resultClass == "done") resolve(true);
else resolve(false);
}));
}
this.breakpoints.clear();
Promise.all(promises).then(resolve, reject);
});
}
setBreakPointCondition(bkptNum, condition): Thenable<any> {
return this.sendCommand("break-condition " + bkptNum + " \"" + escape(condition) + "\" 1");
}
}

View file

@ -1,14 +1,8 @@
import { MI2DebugSession } from './mibase';
import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, OutputEvent, Thread, StackFrame, Scope, Source, Handles } from 'vscode-debugadapter'; import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, OutputEvent, Thread, StackFrame, Scope, Source, Handles } from 'vscode-debugadapter';
import { DebugProtocol } from 'vscode-debugprotocol'; import { DebugProtocol } from 'vscode-debugprotocol';
import { Breakpoint, IBackend, SSHArguments } from './backend/backend' import { MI2 } from "./backend/mi2/mi2";
import { MINode } from './backend/mi_parse' import { SSHArguments } from './backend/backend';
import { expandValue, isExpandable } from './backend/gdb_expansion'
import { MI2 } from './backend/mi2/mi2'
import { posix } from "path"
import * as systemPath from "path"
let resolve = posix.resolve;
let relative = posix.relative;
export interface LaunchRequestArguments { export interface LaunchRequestArguments {
cwd: string; cwd: string;
@ -29,70 +23,14 @@ export interface AttachRequestArguments {
printCalls: boolean; printCalls: boolean;
} }
class MI2DebugSession extends DebugSession { class GDBDebugSession extends MI2DebugSession {
private static THREAD_ID = 1;
private gdbDebugger: MI2;
private variableHandles = new Handles<any>();
private quit: boolean;
private attached: boolean;
private needContinue: boolean;
private isSSH: boolean;
private trimCWD: string;
private switchCWD: string;
private started: boolean;
private crashed: boolean;
public constructor(debuggerLinesStartAt1: boolean, isServer: boolean = false) {
super(debuggerLinesStartAt1, isServer);
}
protected initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void { protected initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void {
response.body.supportsConfigurationDoneRequest = true; response.body.supportsConfigurationDoneRequest = true;
response.body.supportsEvaluateForHovers = true; // Assume working in future releases response.body.supportsEvaluateForHovers = true; // Assume working in future releases
response.body.supportsFunctionBreakpoints = true; // TODO: Implement in future release response.body.supportsFunctionBreakpoints = true; // TODO: Implement in future release
this.sendResponse(response); this.sendResponse(response);
this.gdbDebugger = new MI2("gdb", ["-q", "--interpreter=mi2"]); this.miDebugger = new MI2("gdb", ["-q", "--interpreter=mi2"]);
this.gdbDebugger.on("quit", this.quitEvent.bind(this)); this.initDebugger();
this.gdbDebugger.on("exited-normally", this.quitEvent.bind(this));
this.gdbDebugger.on("stopped", this.stopEvent.bind(this));
this.gdbDebugger.on("msg", this.handleMsg.bind(this));
this.gdbDebugger.on("breakpoint", this.handleBreakpoint.bind(this));
this.gdbDebugger.on("step-end", this.handleBreak.bind(this));
this.gdbDebugger.on("step-out-end", this.handleBreak.bind(this));
this.gdbDebugger.on("signal-stop", this.handlePause.bind(this));
this.sendEvent(new InitializedEvent());
}
private handleMsg(type: string, msg: string) {
if (type == "target")
type = "stdout";
if (type == "log")
type = "stderr";
this.sendEvent(new OutputEvent(msg, type));
}
private handleBreakpoint(info: MINode) {
this.sendEvent(new StoppedEvent("breakpoint", MI2DebugSession.THREAD_ID));
}
private handleBreak(info: MINode) {
this.sendEvent(new StoppedEvent("step", MI2DebugSession.THREAD_ID));
}
private handlePause(info: MINode) {
this.sendEvent(new StoppedEvent("user request", MI2DebugSession.THREAD_ID));
}
private stopEvent(info: MINode) {
if (!this.started)
this.crashed = true;
if (!this.quit)
this.sendEvent(new StoppedEvent("exception", MI2DebugSession.THREAD_ID));
}
private quitEvent() {
this.quit = true;
this.sendEvent(new TerminatedEvent());
} }
protected launchRequest(response: DebugProtocol.LaunchResponse, args: LaunchRequestArguments): void { protected launchRequest(response: DebugProtocol.LaunchResponse, args: LaunchRequestArguments): void {
@ -102,7 +40,7 @@ class MI2DebugSession extends DebugSession {
this.isSSH = false; this.isSSH = false;
this.started = false; this.started = false;
this.crashed = false; this.crashed = false;
this.gdbDebugger.printCalls = !!args.printCalls; this.miDebugger.printCalls = !!args.printCalls;
if (args.ssh !== undefined) { if (args.ssh !== undefined) {
if (args.ssh.forwardX11 === undefined) if (args.ssh.forwardX11 === undefined)
args.ssh.forwardX11 = true; args.ssh.forwardX11 = true;
@ -117,28 +55,34 @@ class MI2DebugSession extends DebugSession {
this.isSSH = true; this.isSSH = true;
this.trimCWD = args.cwd.replace(/\\/g, "/"); this.trimCWD = args.cwd.replace(/\\/g, "/");
this.switchCWD = args.ssh.cwd; this.switchCWD = args.ssh.cwd;
this.gdbDebugger.ssh(args.ssh, args.ssh.cwd, args.target, args.arguments, args.terminal).then(() => { this.miDebugger.ssh(args.ssh, args.ssh.cwd, args.target, args.arguments, args.terminal).then(() => {
if (args.autorun) if (args.autorun)
args.autorun.forEach(command => { args.autorun.forEach(command => {
this.gdbDebugger.sendUserInput(command); this.miDebugger.sendUserInput(command);
}); });
this.gdbDebugger.start().then(() => { setTimeout(() => {
this.miDebugger.emit("ui-break-done");
}, 50);
this.sendResponse(response);
this.miDebugger.start().then(() => {
this.started = true; this.started = true;
this.sendResponse(response);
if (this.crashed) if (this.crashed)
this.handlePause(undefined); this.handlePause(undefined);
}); });
}); });
} }
else { else {
this.gdbDebugger.load(args.cwd, args.target, args.arguments, args.terminal).then(() => { this.miDebugger.load(args.cwd, args.target, args.arguments, args.terminal).then(() => {
if (args.autorun) if (args.autorun)
args.autorun.forEach(command => { args.autorun.forEach(command => {
this.gdbDebugger.sendUserInput(command); this.miDebugger.sendUserInput(command);
}); });
this.gdbDebugger.start().then(() => { setTimeout(() => {
this.miDebugger.emit("ui-break-done");
}, 50);
this.sendResponse(response);
this.miDebugger.start().then(() => {
this.started = true; this.started = true;
this.sendResponse(response);
if (this.crashed) if (this.crashed)
this.handlePause(undefined); this.handlePause(undefined);
}); });
@ -151,267 +95,26 @@ class MI2DebugSession extends DebugSession {
this.attached = !args.remote; this.attached = !args.remote;
this.needContinue = true; this.needContinue = true;
this.isSSH = false; this.isSSH = false;
this.gdbDebugger.printCalls = !!args.printCalls; this.miDebugger.printCalls = !!args.printCalls;
if (args.remote) { if (args.remote) {
this.gdbDebugger.connect(args.cwd, args.executable, args.target).then(() => { this.miDebugger.connect(args.cwd, args.executable, args.target).then(() => {
if (args.autorun) if (args.autorun)
args.autorun.forEach(command => { args.autorun.forEach(command => {
this.gdbDebugger.sendUserInput(command); this.miDebugger.sendUserInput(command);
}); });
this.sendResponse(response); this.sendResponse(response);
}); });
} }
else { else {
this.gdbDebugger.attach(args.cwd, args.executable, args.target).then(() => { this.miDebugger.attach(args.cwd, args.executable, args.target).then(() => {
if (args.autorun) if (args.autorun)
args.autorun.forEach(command => { args.autorun.forEach(command => {
this.gdbDebugger.sendUserInput(command); this.miDebugger.sendUserInput(command);
}); });
this.sendResponse(response); this.sendResponse(response);
}); });
} }
} }
protected disconnectRequest(response: DebugProtocol.DisconnectResponse, args: DebugProtocol.DisconnectArguments): void {
if (this.attached)
this.gdbDebugger.detach();
else
this.gdbDebugger.stop();
this.sendResponse(response);
}
protected setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments): void {
this.gdbDebugger.once("debug-ready", (() => {
this.gdbDebugger.clearBreakPoints().then(() => {
let path = args.source.path;
if (this.isSSH) {
path = relative(this.trimCWD.replace(/\\/g, "/"), path.replace(/\\/g, "/"));
path = resolve(this.switchCWD.replace(/\\/g, "/"), path.replace(/\\/g, "/"));
}
let all = [];
args.breakpoints.forEach(brk => {
all.push(this.gdbDebugger.addBreakPoint({ file: path, line: brk.line, condition: brk.condition }));
});
Promise.all(all).then(brkpoints => {
let finalBrks = [];
brkpoints.forEach(brkp => {
if (brkp[0])
finalBrks.push({ line: brkp[1].line });
});
response.body = {
breakpoints: finalBrks
};
setTimeout(() => {
this.gdbDebugger.emit("ui-break-done");
}, 50);
this.sendResponse(response);
});
});
}).bind(this));
}
protected threadsRequest(response: DebugProtocol.ThreadsResponse): void {
response.body = {
threads: [
new Thread(MI2DebugSession.THREAD_ID, "Thread 1")
]
};
this.sendResponse(response);
}
protected stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void {
this.gdbDebugger.getStack(args.levels).then(stack => {
let ret: StackFrame[] = [];
stack.forEach(element => {
let file = element.file;
if (this.isSSH) {
file = relative(this.switchCWD.replace(/\\/g, "/"), file.replace(/\\/g, "/"));
file = systemPath.resolve(this.trimCWD.replace(/\\/g, "/"), file.replace(/\\/g, "/"));
}
ret.push(new StackFrame(element.level, element.function + "@" + element.address, new Source(element.fileName, file), element.line, 0));
});
response.body = {
stackFrames: ret
};
this.sendResponse(response);
});
}
protected configurationDoneRequest(response: DebugProtocol.ConfigurationDoneResponse, args: DebugProtocol.ConfigurationDoneArguments): void {
// FIXME: Does not seem to get called in january release
if (this.needContinue) {
this.gdbDebugger.continue().then(done => {
this.sendResponse(response);
}, msg => {
this.sendResponse(response);
this.sendEvent(new OutputEvent(`Could not continue: ${msg}\n`, 'stderr'));
});
}
else
this.sendResponse(response);
}
protected scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void {
const scopes = new Array<Scope>();
scopes.push(new Scope("Local", this.variableHandles.create("@frame:" + args.frameId), false));
response.body = {
scopes: scopes
};
this.sendResponse(response);
}
protected variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): void {
const variables = [];
const id = this.variableHandles.get(args.variablesReference);
let createVariable = (arg) => {
return this.variableHandles.create(arg);
};
if (typeof id == "string") {
if (id.startsWith("@frame:")) {
this.gdbDebugger.getStackVariables(1, 0).then(stack => {
stack.forEach(variable => {
if (variable[1] !== undefined) {
let expanded = expandValue(createVariable, `{${variable[0]} = ${variable[1]}}`);
if (!expanded)
new OutputEvent("Could not expand " + variable[1] + "\n", "stderr");
else if (typeof expanded[0] == "string")
expanded = [
{
name: "<value>",
value: prettyStringArray(expanded),
variablesReference: 0
}
];
variables.push(expanded[0]);
} else
variables.push({
name: variable[0],
value: "<unknown>",
variablesReference: createVariable(variable[0])
});
});
response.body = {
variables: variables
};
this.sendResponse(response);
}, err => {
this.handleMsg("stderr", "Could not expand variable\n");
response.body = {
variables: []
};
this.sendResponse(response);
});
}
else {
// Variable members
this.gdbDebugger.evalExpression(JSON.stringify(id)).then(variable => {
let expanded = expandValue(createVariable, variable.result("value"), id);
if (!expanded)
this.sendEvent(new OutputEvent("Could not expand " + variable.result("value") + "\n", "stderr"));
else if (typeof expanded[0] == "string")
expanded = [
{
name: "<value>",
value: prettyStringArray(expanded),
variablesReference: 0
}
];
response.body = {
variables: expanded
};
this.sendResponse(response);
}, err => {
this.handleMsg("stderr", "Could not expand variable\n");
response.body = {
variables: []
};
this.sendResponse(response);
});
}
}
else if (typeof id == "object") {
response.body = {
variables: id
};
this.sendResponse(response);
}
else {
response.body = {
variables: variables
};
this.sendResponse(response);
}
}
protected pauseRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments): void {
this.gdbDebugger.interrupt().then(done => {
this.sendResponse(response);
}, msg => {
this.sendResponse(response);
this.sendEvent(new OutputEvent(`Could not pause: ${msg}\n`, 'stderr'));
});
}
protected continueRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments): void {
this.gdbDebugger.continue().then(done => {
this.sendResponse(response);
}, msg => {
this.sendResponse(response);
this.sendEvent(new OutputEvent(`Could not continue: ${msg}\n`, 'stderr'));
});
}
protected stepInRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void {
this.gdbDebugger.step().then(done => {
this.sendResponse(response);
}, msg => {
this.sendResponse(response);
this.sendEvent(new OutputEvent(`Could not step in: ${msg}\n`, 'stderr'));
});
}
protected stepOutRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void {
this.gdbDebugger.stepOut().then(done => {
this.sendResponse(response);
}, msg => {
this.sendResponse(response);
this.sendEvent(new OutputEvent(`Could not step out: ${msg}\n`, 'stderr'));
});
}
protected nextRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void {
this.gdbDebugger.next().then(done => {
this.sendResponse(response);
}, msg => {
this.sendResponse(response);
this.sendEvent(new OutputEvent(`Could not step over: ${msg}\n`, 'stderr'));
});
}
protected evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): void {
if (args.context == "watch" || args.context == "hover")
this.gdbDebugger.evalExpression(args.expression).then((res) => {
response.body = {
variablesReference: 0,
result: res.result("value")
}
this.sendResponse(response);
});
else {
this.gdbDebugger.sendUserInput(args.expression).then(output => {
if (output)
response.body.result = JSON.stringify(output);
this.sendResponse(response);
});
}
}
} }
function prettyStringArray(strings: string[]) { DebugSession.run(GDBDebugSession);
return strings.join(", ");
}
DebugSession.run(MI2DebugSession);

107
src/lldb.ts Normal file
View file

@ -0,0 +1,107 @@
import { MI2DebugSession } from './mibase';
import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, OutputEvent, Thread, StackFrame, Scope, Source, Handles } from 'vscode-debugadapter';
import { DebugProtocol } from 'vscode-debugprotocol';
import { MI2_LLDB } from "./backend/mi2/mi2lldb";
import { SSHArguments } from './backend/backend';
export interface LaunchRequestArguments {
cwd: string;
target: string;
arguments: string;
autorun: string[];
ssh: SSHArguments;
printCalls: boolean;
}
export interface AttachRequestArguments {
cwd: string;
target: string;
executable: string;
autorun: string[];
printCalls: boolean;
}
class LLDBDebugSession extends MI2DebugSession {
protected initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void {
response.body.supportsConfigurationDoneRequest = true;
response.body.supportsEvaluateForHovers = true; // Assume working in future releases
response.body.supportsFunctionBreakpoints = true; // TODO: Implement in future release
this.sendResponse(response);
this.miDebugger = new MI2_LLDB("lldb-mi", []);
this.initDebugger();
}
protected launchRequest(response: DebugProtocol.LaunchResponse, args: LaunchRequestArguments): void {
this.quit = false;
this.attached = false;
this.needContinue = false;
this.isSSH = false;
this.started = false;
this.crashed = false;
this.miDebugger.printCalls = !!args.printCalls;
if (args.ssh !== undefined) {
if (args.ssh.forwardX11 === undefined)
args.ssh.forwardX11 = true;
if (args.ssh.port === undefined)
args.ssh.port = 22;
if (args.ssh.x11port === undefined)
args.ssh.x11port = 6000;
if (args.ssh.x11host === undefined)
args.ssh.x11host = "localhost";
if (args.ssh.remotex11screen === undefined)
args.ssh.remotex11screen = 0;
this.isSSH = true;
this.trimCWD = args.cwd.replace(/\\/g, "/");
this.switchCWD = args.ssh.cwd;
this.miDebugger.ssh(args.ssh, args.ssh.cwd, args.target, args.arguments, undefined).then(() => {
if (args.autorun)
args.autorun.forEach(command => {
this.miDebugger.sendUserInput(command);
});
setTimeout(() => {
this.miDebugger.emit("ui-break-done");
}, 50);
this.sendResponse(response);
this.miDebugger.start().then(() => {
this.started = true;
if (this.crashed)
this.handlePause(undefined);
});
});
}
else {
this.miDebugger.load(args.cwd, args.target, args.arguments, undefined).then(() => {
if (args.autorun)
args.autorun.forEach(command => {
this.miDebugger.sendUserInput(command);
});
setTimeout(() => {
this.miDebugger.emit("ui-break-done");
}, 50);
this.sendResponse(response);
this.miDebugger.start().then(() => {
this.started = true;
if (this.crashed)
this.handlePause(undefined);
});
});
}
}
protected attachRequest(response: DebugProtocol.AttachResponse, args: AttachRequestArguments): void {
this.quit = false;
this.attached = true;
this.needContinue = true;
this.isSSH = false;
this.miDebugger.printCalls = !!args.printCalls;
this.miDebugger.attach(args.cwd, args.executable, args.target).then(() => {
if (args.autorun)
args.autorun.forEach(command => {
this.miDebugger.sendUserInput(command);
});
this.sendResponse(response);
});
}
}
DebugSession.run(LLDBDebugSession);

314
src/mibase.ts Normal file
View file

@ -0,0 +1,314 @@
import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, OutputEvent, Thread, StackFrame, Scope, Source, Handles } from 'vscode-debugadapter';
import { DebugProtocol } from 'vscode-debugprotocol';
import { Breakpoint, IBackend } from './backend/backend';
import { MINode } from './backend/mi_parse';
import { expandValue, isExpandable } from './backend/gdb_expansion';
import { MI2 } from './backend/mi2/mi2';
import { posix } from "path";
import * as systemPath from "path";
let resolve = posix.resolve;
let relative = posix.relative;
export class MI2DebugSession extends DebugSession {
protected static THREAD_ID = 1;
protected variableHandles = new Handles<any>();
protected quit: boolean;
protected attached: boolean;
protected needContinue: boolean;
protected isSSH: boolean;
protected trimCWD: string;
protected switchCWD: string;
protected started: boolean;
protected crashed: boolean;
protected miDebugger: MI2;
public constructor(debuggerLinesStartAt1: boolean, isServer: boolean = false) {
super(debuggerLinesStartAt1, isServer);
}
protected initDebugger() {
this.miDebugger.on("quit", this.quitEvent.bind(this));
this.miDebugger.on("exited-normally", this.quitEvent.bind(this));
this.miDebugger.on("stopped", this.stopEvent.bind(this));
this.miDebugger.on("msg", this.handleMsg.bind(this));
this.miDebugger.on("breakpoint", this.handleBreakpoint.bind(this));
this.miDebugger.on("step-end", this.handleBreak.bind(this));
this.miDebugger.on("step-out-end", this.handleBreak.bind(this));
this.miDebugger.on("signal-stop", this.handlePause.bind(this));
this.sendEvent(new InitializedEvent());
}
protected handleMsg(type: string, msg: string) {
if (type == "target")
type = "stdout";
if (type == "log")
type = "stderr";
this.sendEvent(new OutputEvent(msg, type));
}
protected handleBreakpoint(info: MINode) {
this.sendEvent(new StoppedEvent("breakpoint", MI2DebugSession.THREAD_ID));
}
protected handleBreak(info: MINode) {
this.sendEvent(new StoppedEvent("step", MI2DebugSession.THREAD_ID));
}
protected handlePause(info: MINode) {
this.sendEvent(new StoppedEvent("user request", MI2DebugSession.THREAD_ID));
}
protected stopEvent(info: MINode) {
if (!this.started)
this.crashed = true;
if (!this.quit)
this.sendEvent(new StoppedEvent("exception", MI2DebugSession.THREAD_ID));
}
protected quitEvent() {
this.quit = true;
this.sendEvent(new TerminatedEvent());
}
protected disconnectRequest(response: DebugProtocol.DisconnectResponse, args: DebugProtocol.DisconnectArguments): void {
if (this.attached)
this.miDebugger.detach();
else
this.miDebugger.stop();
this.sendResponse(response);
}
protected setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments): void {
this.miDebugger.once("debug-ready", (() => {
this.miDebugger.clearBreakPoints().then(() => {
let path = args.source.path;
if (this.isSSH) {
path = relative(this.trimCWD.replace(/\\/g, "/"), path.replace(/\\/g, "/"));
path = resolve(this.switchCWD.replace(/\\/g, "/"), path.replace(/\\/g, "/"));
}
let all = [];
args.breakpoints.forEach(brk => {
all.push(this.miDebugger.addBreakPoint({ file: path, line: brk.line, condition: brk.condition }));
});
Promise.all(all).then(brkpoints => {
let finalBrks = [];
brkpoints.forEach(brkp => {
if (brkp[0])
finalBrks.push({ line: brkp[1].line });
});
response.body = {
breakpoints: finalBrks
};
setTimeout(() => {
this.miDebugger.emit("ui-break-done");
}, 50);
this.sendResponse(response);
});
});
}).bind(this));
}
protected threadsRequest(response: DebugProtocol.ThreadsResponse): void {
response.body = {
threads: [
new Thread(MI2DebugSession.THREAD_ID, "Thread 1")
]
};
this.sendResponse(response);
}
protected stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void {
this.miDebugger.getStack(args.levels).then(stack => {
let ret: StackFrame[] = [];
stack.forEach(element => {
let file = element.file;
if (this.isSSH) {
file = relative(this.switchCWD.replace(/\\/g, "/"), file.replace(/\\/g, "/"));
file = systemPath.resolve(this.trimCWD.replace(/\\/g, "/"), file.replace(/\\/g, "/"));
}
ret.push(new StackFrame(element.level, element.function + "@" + element.address, new Source(element.fileName, file), element.line, 0));
});
response.body = {
stackFrames: ret
};
this.sendResponse(response);
});
}
protected configurationDoneRequest(response: DebugProtocol.ConfigurationDoneResponse, args: DebugProtocol.ConfigurationDoneArguments): void {
// FIXME: Does not seem to get called in january release
if (this.needContinue) {
this.miDebugger.continue().then(done => {
this.sendResponse(response);
}, msg => {
this.sendResponse(response);
this.sendEvent(new OutputEvent(`Could not continue: ${msg}\n`, 'stderr'));
});
}
else
this.sendResponse(response);
}
protected scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void {
const scopes = new Array<Scope>();
scopes.push(new Scope("Local", this.variableHandles.create("@frame:" + args.frameId), false));
response.body = {
scopes: scopes
};
this.sendResponse(response);
}
protected variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): void {
const variables = [];
const id = this.variableHandles.get(args.variablesReference);
let createVariable = (arg) => {
return this.variableHandles.create(arg);
};
if (typeof id == "string") {
if (id.startsWith("@frame:")) {
this.miDebugger.getStackVariables(1, 0).then(stack => {
stack.forEach(variable => {
if (variable[1] !== undefined) {
let expanded = expandValue(createVariable, `{${variable[0]} = ${variable[1]}}`);
if (!expanded)
new OutputEvent("Could not expand " + variable[1] + "\n", "stderr");
else if (typeof expanded[0] == "string")
expanded = [
{
name: "<value>",
value: prettyStringArray(expanded),
variablesReference: 0
}
];
variables.push(expanded[0]);
} else
variables.push({
name: variable[0],
value: "<unknown>",
variablesReference: createVariable(variable[0])
});
});
response.body = {
variables: variables
};
this.sendResponse(response);
}, err => {
this.handleMsg("stderr", "Could not expand variable\n");
response.body = {
variables: []
};
this.sendResponse(response);
});
}
else {
// Variable members
this.miDebugger.evalExpression(JSON.stringify(id)).then(variable => {
let expanded = expandValue(createVariable, variable.result("value"), id);
if (!expanded)
this.sendEvent(new OutputEvent("Could not expand " + variable.result("value") + "\n", "stderr"));
else if (typeof expanded[0] == "string")
expanded = [
{
name: "<value>",
value: prettyStringArray(expanded),
variablesReference: 0
}
];
response.body = {
variables: expanded
};
this.sendResponse(response);
}, err => {
this.handleMsg("stderr", "Could not expand variable\n");
response.body = {
variables: []
};
this.sendResponse(response);
});
}
}
else if (typeof id == "object") {
response.body = {
variables: id
};
this.sendResponse(response);
}
else {
response.body = {
variables: variables
};
this.sendResponse(response);
}
}
protected pauseRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments): void {
this.miDebugger.interrupt().then(done => {
this.sendResponse(response);
}, msg => {
this.sendResponse(response);
this.sendEvent(new OutputEvent(`Could not pause: ${msg}\n`, 'stderr'));
});
}
protected continueRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments): void {
this.miDebugger.continue().then(done => {
this.sendResponse(response);
}, msg => {
this.sendResponse(response);
this.sendEvent(new OutputEvent(`Could not continue: ${msg}\n`, 'stderr'));
});
}
protected stepInRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void {
this.miDebugger.step().then(done => {
this.sendResponse(response);
}, msg => {
this.sendResponse(response);
this.sendEvent(new OutputEvent(`Could not step in: ${msg}\n`, 'stderr'));
});
}
protected stepOutRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void {
this.miDebugger.stepOut().then(done => {
this.sendResponse(response);
}, msg => {
this.sendResponse(response);
this.sendEvent(new OutputEvent(`Could not step out: ${msg}\n`, 'stderr'));
});
}
protected nextRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void {
this.miDebugger.next().then(done => {
this.sendResponse(response);
}, msg => {
this.sendResponse(response);
this.sendEvent(new OutputEvent(`Could not step over: ${msg}\n`, 'stderr'));
});
}
protected evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): void {
if (args.context == "watch" || args.context == "hover")
this.miDebugger.evalExpression(args.expression).then((res) => {
response.body = {
variablesReference: 0,
result: res.result("value")
}
this.sendResponse(response);
});
else {
this.miDebugger.sendUserInput(args.expression).then(output => {
if (output)
response.body.result = JSON.stringify(output);
this.sendResponse(response);
});
}
}
}
function prettyStringArray(strings: string[]) {
return strings.join(", ");
}

View file

@ -12,8 +12,12 @@ suite("GDB Value Expansion", () => {
assert.equal(expandValue(variableCreate, `"hello world!"`), `"hello world!"`); assert.equal(expandValue(variableCreate, `"hello world!"`), `"hello world!"`);
assert.strictEqual(isExpandable(`0x0`), 0); assert.strictEqual(isExpandable(`0x0`), 0);
assert.equal(expandValue(variableCreate, `0x0`), "<nullptr>"); assert.equal(expandValue(variableCreate, `0x0`), "<nullptr>");
assert.strictEqual(isExpandable(`0xabc`), 2); assert.strictEqual(isExpandable(`0x000000`), 0);
assert.equal(expandValue(variableCreate, `0x7ffff7ecb480`), "*0x7ffff7ecb480"); assert.equal(expandValue(variableCreate, `0x000000`), "<nullptr>");
assert.strictEqual(isExpandable(`{...}`), 2);
assert.equal(expandValue(variableCreate, `{...}`), "<...>");
assert.strictEqual(isExpandable(`0x00abc`), 2);
assert.equal(expandValue(variableCreate, `0x007ffff7ecb480`), "*0x007ffff7ecb480");
assert.strictEqual(isExpandable(`{a = b, c = d}`), 1); assert.strictEqual(isExpandable(`{a = b, c = d}`), 1);
assert.deepEqual(expandValue(variableCreate, `{a = b, c = d}`), [ assert.deepEqual(expandValue(variableCreate, `{a = b, c = d}`), [
{ {
@ -262,5 +266,17 @@ suite("GDB Value Expansion", () => {
variablesReference: 0 variablesReference: 0
} }
]); ]);
}) });
test("lldb strings", () => {
let node = `{ name = {...} }`;
assert.strictEqual(isExpandable(node), 1);
let variables = expandValue(variableCreate, node);
assert.deepEqual(variables, [
{
name: "name",
value: "...",
variablesReference: { expanded: "name" }
}
]);
});
}); });