Added LLDB support (fix #39)
This commit is contained in:
parent
bcf46a7e81
commit
35afccfd77
12 changed files with 756 additions and 376 deletions
22
README.md
22
README.md
|
|
@ -4,18 +4,20 @@ Native VSCode debugger. Currently only using GDB.
|
|||
|
||||
## 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.
|
||||
|
||||

|
||||
|
||||
## Usage
|
||||
|
||||

|
||||

|
||||
|
||||
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.
|
||||
|
||||
*Note: for LLDB you need to have lldb-mi in your PATH*
|
||||
|
||||

|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
While running you will get a console where you can manually type GDB commands or GDB/MI
|
||||
commands prepended with a hyphen `-`. The console shows all output GDB gives separated
|
||||
in `stdout` for the application, `stderr` for errors and `log` for GDB log messages.
|
||||
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 separated
|
||||
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
|
||||
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
|
||||
`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",
|
||||
|
|
@ -57,9 +59,9 @@ path for GDB to find the debug symbols.
|
|||
"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
|
||||
`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 |
Binary file not shown.
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 43 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 53 KiB |
169
package.json
169
package.json
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "debug",
|
||||
"displayName": "Debug",
|
||||
"description": "Native debugging for VSCode - Currently in GDB only beta",
|
||||
"description": "GDB & LLDB support for VSCode",
|
||||
"version": "0.5.0",
|
||||
"publisher": "webfreak",
|
||||
"icon": "images/icon-plain.svg",
|
||||
|
|
@ -172,7 +172,170 @@
|
|||
"name": "Debug",
|
||||
"type": "gdb",
|
||||
"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}"
|
||||
}
|
||||
]
|
||||
|
|
@ -193,4 +356,4 @@
|
|||
"typescript": "^1.7.5",
|
||||
"vscode": "0.11.x"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 errorRegex = /^\<.+?\>/;
|
||||
const referenceRegex = /^0x[0-9a-fA-F]+/;
|
||||
const nullpointerRegex = /^0x0+\b/;
|
||||
const charRegex = /^([0-9]+) ['"]/;
|
||||
const numberRegex = /^[0-9]+/;
|
||||
const pointerCombineChar = ".";
|
||||
|
||||
|
|
@ -10,11 +12,13 @@ export function isExpandable(value: string): number {
|
|||
let match;
|
||||
value = value.trim();
|
||||
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.startsWith("true")) 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 = charRegex.exec(value)) return 0;
|
||||
else if (match = numberRegex.exec(value)) return 0;
|
||||
else if (match = variableRegex.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 {
|
||||
let parseCString = () => {
|
||||
value = value.trim();
|
||||
if (value[0] != '"')
|
||||
if (value[0] != '"' && value[0] != '\'')
|
||||
return "";
|
||||
let stringEnd = 1;
|
||||
let inString = true;
|
||||
let charStr = value[0];
|
||||
let remaining = value.substr(1);
|
||||
let escaped = false;
|
||||
while (inString) {
|
||||
|
|
@ -35,7 +40,7 @@ export function expandValue(variableCreate: Function, value: string, root: strin
|
|||
escaped = false;
|
||||
else if (remaining[0] == '\\')
|
||||
escaped = true;
|
||||
else if (remaining[0] == '"')
|
||||
else if (remaining[0] == charStr)
|
||||
inString = false;
|
||||
|
||||
remaining = remaining.substr(1);
|
||||
|
|
@ -82,8 +87,17 @@ export function expandValue(variableCreate: Function, value: string, root: strin
|
|||
return undefined;
|
||||
let oldContent = value;
|
||||
value = value.substr(1).trim();
|
||||
if (value[0] == '}')
|
||||
if (value[0] == '}') {
|
||||
value = value.substr(1).trim();
|
||||
return [];
|
||||
}
|
||||
if (value.startsWith("...")) {
|
||||
value = value.substr(3).trim();
|
||||
if (value[0] == '}') {
|
||||
value = value.substr(1).trim();
|
||||
return <any>"<...>";
|
||||
}
|
||||
}
|
||||
let eqPos = value.indexOf("=");
|
||||
let newValPos1 = value.indexOf("{");
|
||||
let newValPos2 = value.indexOf(",");
|
||||
|
|
@ -138,7 +152,7 @@ export function expandValue(variableCreate: Function, value: string, root: strin
|
|||
primitive = "false";
|
||||
value = value.substr(5).trim();
|
||||
}
|
||||
else if (value.startsWith("0x0")) {
|
||||
else if (match = nullpointerRegex.exec(value)) {
|
||||
primitive = "<nullptr>";
|
||||
value = value.substr(3).trim();
|
||||
}
|
||||
|
|
@ -146,6 +160,11 @@ export function expandValue(variableCreate: Function, value: string, root: strin
|
|||
primitive = "*" + match[0];
|
||||
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)) {
|
||||
primitive = match[0];
|
||||
value = value.substr(match[0].length).trim();
|
||||
|
|
@ -199,6 +218,10 @@ export function expandValue(variableCreate: Function, value: string, root: strin
|
|||
ref = variableCreate(getNamespace("*" + name));
|
||||
val = "Object@" + val;
|
||||
}
|
||||
if (typeof val == "string" && val.startsWith("<...>")) {
|
||||
ref = variableCreate(getNamespace(name));
|
||||
val = "...";
|
||||
}
|
||||
return {
|
||||
name: name,
|
||||
value: val,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import * as nativePath from "path"
|
|||
let path = posix;
|
||||
var Client = require("ssh2").Client;
|
||||
|
||||
function escape(str: string) {
|
||||
export function escape(str: string) {
|
||||
return str.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
|
||||
}
|
||||
|
||||
|
|
@ -32,14 +32,11 @@ export class MI2 extends EventEmitter implements IBackend {
|
|||
target = nativePath.join(cwd, target);
|
||||
return new Promise((resolve, reject) => {
|
||||
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.stderr.on("data", this.stdout.bind(this));
|
||||
this.process.on("exit", (() => { this.emit("quit"); }).bind(this));
|
||||
let promises = [
|
||||
this.sendCommand("gdb-set target-async on"),
|
||||
this.sendCommand("environment-directory \"" + escape(cwd) + "\"")
|
||||
];
|
||||
let promises = this.initCommands(target, cwd);
|
||||
if (procArgs && procArgs.length)
|
||||
promises.push(this.sendCommand("exec-arguments " + procArgs));
|
||||
if (process.platform == "win32") {
|
||||
|
|
@ -139,12 +136,8 @@ export class MI2 extends EventEmitter implements IBackend {
|
|||
this.emit("quit");
|
||||
this.sshConn.end();
|
||||
}).bind(this));
|
||||
let promises = [
|
||||
this.sendCommand("gdb-set target-async on"),
|
||||
this.sendCommand("environment-directory \"" + escape(cwd) + "\""),
|
||||
this.sendCommand("environment-cd \"" + escape(cwd) + "\""),
|
||||
this.sendCommand("file-exec-and-symbols \"" + escape(target) + "\"")
|
||||
];
|
||||
let promises = this.initCommands(target, cwd);
|
||||
promises.push(this.sendCommand("environment-cd \"" + escape(cwd) + "\""));
|
||||
if (procArgs && procArgs.length)
|
||||
promises.push(this.sendCommand("exec-arguments " + procArgs));
|
||||
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> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let args = [];
|
||||
|
|
@ -380,6 +381,10 @@ export class MI2 extends EventEmitter implements IBackend {
|
|||
return Promise.all(promisses);
|
||||
}
|
||||
|
||||
setBreakPointCondition(bkptNum, condition): Thenable<any> {
|
||||
return this.sendCommand("break-condition " + bkptNum + " " + condition);
|
||||
}
|
||||
|
||||
addBreakPoint(breakpoint: Breakpoint): Thenable<[boolean, Breakpoint]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.breakpoints.has(breakpoint))
|
||||
|
|
@ -393,7 +398,7 @@ export class MI2 extends EventEmitter implements IBackend {
|
|||
condition: 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") {
|
||||
this.breakpoints.set(newBrk, bkptNum);
|
||||
resolve([true, newBrk]);
|
||||
|
|
@ -467,7 +472,7 @@ export class MI2 extends EventEmitter implements IBackend {
|
|||
});
|
||||
});
|
||||
resolve(ret);
|
||||
});
|
||||
}, reject);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -520,18 +525,15 @@ export class MI2 extends EventEmitter implements IBackend {
|
|||
}
|
||||
|
||||
sendCommand(command: string): Thenable<MINode> {
|
||||
let sel = this.currentToken++;
|
||||
return new Promise((resolve, reject) => {
|
||||
this.handlers[this.currentToken] = (node: MINode) => {
|
||||
if (node.resultRecords && node.resultRecords.resultClass == "error") {
|
||||
let msg = node.result("msg") || "Internal error";
|
||||
this.log("stderr", "Failed to run command `" + command + "`: " + msg);
|
||||
reject(msg);
|
||||
}
|
||||
this.handlers[sel] = (node: MINode) => {
|
||||
if (node && node.resultRecords && node.resultRecords.resultClass === "error")
|
||||
reject(node.result("msg") || "Internal error");
|
||||
else
|
||||
resolve(node);
|
||||
};
|
||||
this.sendRaw(this.currentToken + "-" + command);
|
||||
this.currentToken++;
|
||||
this.sendRaw(sel + "-" + command);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -540,13 +542,13 @@ export class MI2 extends EventEmitter implements IBackend {
|
|||
}
|
||||
|
||||
printCalls: boolean;
|
||||
private isSSH: boolean;
|
||||
private sshReady: boolean;
|
||||
private currentToken: number = 1;
|
||||
private handlers: { [index: number]: (info: MINode) => any } = {};
|
||||
private breakpoints: Map<Breakpoint, Number> = new Map();
|
||||
private buffer: string;
|
||||
private process: ChildProcess.ChildProcess;
|
||||
private stream;
|
||||
private sshConn;
|
||||
protected isSSH: boolean;
|
||||
protected sshReady: boolean;
|
||||
protected currentToken: number = 1;
|
||||
protected handlers: { [index: number]: (info: MINode) => any } = {};
|
||||
protected breakpoints: Map<Breakpoint, Number> = new Map();
|
||||
protected buffer: string;
|
||||
protected process: ChildProcess.ChildProcess;
|
||||
protected stream;
|
||||
protected sshConn;
|
||||
}
|
||||
50
src/backend/mi2/mi2lldb.ts
Normal file
50
src/backend/mi2/mi2lldb.ts
Normal 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");
|
||||
}
|
||||
}
|
||||
351
src/gdb.ts
351
src/gdb.ts
|
|
@ -1,14 +1,8 @@
|
|||
import { MI2DebugSession } from './mibase';
|
||||
import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, OutputEvent, Thread, StackFrame, Scope, Source, Handles } from 'vscode-debugadapter';
|
||||
import { DebugProtocol } from 'vscode-debugprotocol';
|
||||
import { Breakpoint, IBackend, SSHArguments } 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;
|
||||
import { MI2 } from "./backend/mi2/mi2";
|
||||
import { SSHArguments } from './backend/backend';
|
||||
|
||||
export interface LaunchRequestArguments {
|
||||
cwd: string;
|
||||
|
|
@ -29,70 +23,14 @@ export interface AttachRequestArguments {
|
|||
printCalls: boolean;
|
||||
}
|
||||
|
||||
class MI2DebugSession extends DebugSession {
|
||||
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);
|
||||
}
|
||||
|
||||
class GDBDebugSession 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.gdbDebugger = new MI2("gdb", ["-q", "--interpreter=mi2"]);
|
||||
this.gdbDebugger.on("quit", this.quitEvent.bind(this));
|
||||
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());
|
||||
this.miDebugger = new MI2("gdb", ["-q", "--interpreter=mi2"]);
|
||||
this.initDebugger();
|
||||
}
|
||||
|
||||
protected launchRequest(response: DebugProtocol.LaunchResponse, args: LaunchRequestArguments): void {
|
||||
|
|
@ -102,7 +40,7 @@ class MI2DebugSession extends DebugSession {
|
|||
this.isSSH = false;
|
||||
this.started = false;
|
||||
this.crashed = false;
|
||||
this.gdbDebugger.printCalls = !!args.printCalls;
|
||||
this.miDebugger.printCalls = !!args.printCalls;
|
||||
if (args.ssh !== undefined) {
|
||||
if (args.ssh.forwardX11 === undefined)
|
||||
args.ssh.forwardX11 = true;
|
||||
|
|
@ -117,28 +55,34 @@ class MI2DebugSession extends DebugSession {
|
|||
this.isSSH = true;
|
||||
this.trimCWD = args.cwd.replace(/\\/g, "/");
|
||||
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)
|
||||
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.sendResponse(response);
|
||||
if (this.crashed)
|
||||
this.handlePause(undefined);
|
||||
});
|
||||
});
|
||||
}
|
||||
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)
|
||||
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.sendResponse(response);
|
||||
if (this.crashed)
|
||||
this.handlePause(undefined);
|
||||
});
|
||||
|
|
@ -151,267 +95,26 @@ class MI2DebugSession extends DebugSession {
|
|||
this.attached = !args.remote;
|
||||
this.needContinue = true;
|
||||
this.isSSH = false;
|
||||
this.gdbDebugger.printCalls = !!args.printCalls;
|
||||
this.miDebugger.printCalls = !!args.printCalls;
|
||||
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)
|
||||
args.autorun.forEach(command => {
|
||||
this.gdbDebugger.sendUserInput(command);
|
||||
this.miDebugger.sendUserInput(command);
|
||||
});
|
||||
this.sendResponse(response);
|
||||
});
|
||||
}
|
||||
else {
|
||||
this.gdbDebugger.attach(args.cwd, args.executable, args.target).then(() => {
|
||||
this.miDebugger.attach(args.cwd, args.executable, args.target).then(() => {
|
||||
if (args.autorun)
|
||||
args.autorun.forEach(command => {
|
||||
this.gdbDebugger.sendUserInput(command);
|
||||
this.miDebugger.sendUserInput(command);
|
||||
});
|
||||
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[]) {
|
||||
return strings.join(", ");
|
||||
}
|
||||
|
||||
DebugSession.run(MI2DebugSession);
|
||||
DebugSession.run(GDBDebugSession);
|
||||
107
src/lldb.ts
Normal file
107
src/lldb.ts
Normal 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
314
src/mibase.ts
Normal 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(", ");
|
||||
}
|
||||
|
|
@ -12,8 +12,12 @@ suite("GDB Value Expansion", () => {
|
|||
assert.equal(expandValue(variableCreate, `"hello world!"`), `"hello world!"`);
|
||||
assert.strictEqual(isExpandable(`0x0`), 0);
|
||||
assert.equal(expandValue(variableCreate, `0x0`), "<nullptr>");
|
||||
assert.strictEqual(isExpandable(`0xabc`), 2);
|
||||
assert.equal(expandValue(variableCreate, `0x7ffff7ecb480`), "*0x7ffff7ecb480");
|
||||
assert.strictEqual(isExpandable(`0x000000`), 0);
|
||||
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.deepEqual(expandValue(variableCreate, `{a = b, c = d}`), [
|
||||
{
|
||||
|
|
@ -262,5 +266,17 @@ suite("GDB Value Expansion", () => {
|
|||
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" }
|
||||
}
|
||||
]);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue