Initial commit
This commit is contained in:
commit
4440bfbe18
18 changed files with 1616 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
out
|
||||||
|
node_modules
|
||||||
28
.vscode/launch.json
vendored
Normal file
28
.vscode/launch.json
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
// A launch configuration that compiles the extension and then opens it inside a new window
|
||||||
|
{
|
||||||
|
"version": "0.1.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Launch Extension",
|
||||||
|
"type": "extensionHost",
|
||||||
|
"request": "launch",
|
||||||
|
"runtimeExecutable": "${execPath}",
|
||||||
|
"args": ["--extensionDevelopmentPath=${workspaceRoot}" ],
|
||||||
|
"stopOnEntry": false,
|
||||||
|
"sourceMaps": true,
|
||||||
|
"outDir": "out/src",
|
||||||
|
"preLaunchTask": "npm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Launch Tests",
|
||||||
|
"type": "extensionHost",
|
||||||
|
"request": "launch",
|
||||||
|
"runtimeExecutable": "${execPath}",
|
||||||
|
"args": ["--extensionDevelopmentPath=${workspaceRoot}", "--extensionTestsPath=${workspaceRoot}/out/test" ],
|
||||||
|
"stopOnEntry": false,
|
||||||
|
"sourceMaps": true,
|
||||||
|
"outDir": "out/test",
|
||||||
|
"preLaunchTask": "npm"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
10
.vscode/settings.json
vendored
Normal file
10
.vscode/settings.json
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
// Place your settings in this file to overwrite default and user settings.
|
||||||
|
{
|
||||||
|
"files.exclude": {
|
||||||
|
"out": false // set this to true to hide the "out" folder with the compiled JS files
|
||||||
|
},
|
||||||
|
"search.exclude": {
|
||||||
|
"out": true // set this to false to include "out" folder in search results
|
||||||
|
},
|
||||||
|
"typescript.tsdk": "./node_modules/typescript/lib" // we want to use the TS server from our node_modules folder to control its version
|
||||||
|
}
|
||||||
30
.vscode/tasks.json
vendored
Normal file
30
.vscode/tasks.json
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
// Available variables which can be used inside of strings.
|
||||||
|
// ${workspaceRoot}: the root folder of the team
|
||||||
|
// ${file}: the current opened file
|
||||||
|
// ${fileBasename}: the current opened file's basename
|
||||||
|
// ${fileDirname}: the current opened file's dirname
|
||||||
|
// ${fileExtname}: the current opened file's extension
|
||||||
|
// ${cwd}: the current working directory of the spawned process
|
||||||
|
|
||||||
|
// A task runner that calls a custom npm script that compiles the extension.
|
||||||
|
{
|
||||||
|
"version": "0.1.0",
|
||||||
|
|
||||||
|
// we want to run npm
|
||||||
|
"command": "npm",
|
||||||
|
|
||||||
|
// the command is a shell script
|
||||||
|
"isShellCommand": true,
|
||||||
|
|
||||||
|
// show the output window only if unrecognized errors occur.
|
||||||
|
"showOutput": "silent",
|
||||||
|
|
||||||
|
// we run the custom script "compile" as defined in package.json
|
||||||
|
"args": ["run", "compile", "--loglevel", "silent"],
|
||||||
|
|
||||||
|
// The tsc compiler is started in watching mode
|
||||||
|
"isWatching": true,
|
||||||
|
|
||||||
|
// use the standard tsc in watch mode problem matcher to find compile problems in the output.
|
||||||
|
"problemMatcher": "$tsc-watch"
|
||||||
|
}
|
||||||
9
.vscodeignore
Normal file
9
.vscodeignore
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
.vscode/**
|
||||||
|
typings/**
|
||||||
|
out/test/**
|
||||||
|
test/**
|
||||||
|
src/**
|
||||||
|
**/*.map
|
||||||
|
.gitignore
|
||||||
|
tsconfig.json
|
||||||
|
vsc-extension-quickstart.md
|
||||||
3
README.md
Normal file
3
README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Debug
|
||||||
|
|
||||||
|
Native VSCode debugger. Currently only using GDB.
|
||||||
73
package.json
Normal file
73
package.json
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
{
|
||||||
|
"name": "debug",
|
||||||
|
"displayName": "Debug",
|
||||||
|
"description": "Native debugging for VSCode - Currently in GDB only beta",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"publisher": "WebFreak",
|
||||||
|
"engines": {
|
||||||
|
"vscode": "^0.10.1"
|
||||||
|
},
|
||||||
|
"categories": [
|
||||||
|
"Debuggers"
|
||||||
|
],
|
||||||
|
"contributes": {
|
||||||
|
"debuggers": [
|
||||||
|
{
|
||||||
|
"type": "gdb",
|
||||||
|
"extensions": [],
|
||||||
|
"program": "./out/src/gdb.js",
|
||||||
|
"runtime": "node",
|
||||||
|
"label": "GDB",
|
||||||
|
"enableBreakpointsFor": {
|
||||||
|
"languageIds": [
|
||||||
|
"c",
|
||||||
|
"cpp",
|
||||||
|
"d",
|
||||||
|
"objective-c",
|
||||||
|
"fortan",
|
||||||
|
"pascal",
|
||||||
|
"ada"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"configurationAttributes": {
|
||||||
|
"launch": {
|
||||||
|
"required": [
|
||||||
|
"target"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"target": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path of executable"
|
||||||
|
},
|
||||||
|
"cwd": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path of project"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"initialConfigurations": [
|
||||||
|
{
|
||||||
|
"name": "Debug",
|
||||||
|
"type": "gdb",
|
||||||
|
"request": "launch",
|
||||||
|
"target": "./output",
|
||||||
|
"cwd": "${workspaceRoot}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"vscode:prepublish": "node ./node_modules/vscode/bin/compile",
|
||||||
|
"compile": "node ./node_modules/vscode/bin/compile -watch -p ./"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"vscode-debugadapter": "^1.0.1",
|
||||||
|
"vscode-debugprotocol": "^1.0.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "^1.6.2",
|
||||||
|
"vscode": "0.10.x"
|
||||||
|
}
|
||||||
|
}
|
||||||
32
src/backend/backend.ts
Normal file
32
src/backend/backend.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
export interface Breakpoint {
|
||||||
|
file: string;
|
||||||
|
line: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Stack {
|
||||||
|
level: number;
|
||||||
|
address: string;
|
||||||
|
function: string;
|
||||||
|
fileName: string;
|
||||||
|
file: string;
|
||||||
|
line: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IBackend {
|
||||||
|
load(cwd: string, target: string): Thenable<any>;
|
||||||
|
start(): Thenable<boolean>;
|
||||||
|
stop();
|
||||||
|
interrupt(): Thenable<boolean>;
|
||||||
|
continue(): Thenable<boolean>;
|
||||||
|
next(): Thenable<boolean>;
|
||||||
|
step(): Thenable<boolean>;
|
||||||
|
stepOut(): Thenable<boolean>;
|
||||||
|
loadBreakPoints(breakpoints: Breakpoint[]): Thenable<[boolean, Breakpoint][]>;
|
||||||
|
addBreakPoint(breakpoint: Breakpoint): Thenable<[boolean, Breakpoint]>;
|
||||||
|
removeBreakPoint(breakpoint: Breakpoint): Thenable<boolean>;
|
||||||
|
clearBreakPoints(): Thenable<any>;
|
||||||
|
getStack(maxLevels: number): Thenable<Stack[]>;
|
||||||
|
getStackVariables(thread: number, frame: number): Thenable<[string, string][]>;
|
||||||
|
evalExpression(name: string): Thenable<any>;
|
||||||
|
isReady(): boolean;
|
||||||
|
}
|
||||||
201
src/backend/gdb_expansion.ts
Normal file
201
src/backend/gdb_expansion.ts
Normal file
|
|
@ -0,0 +1,201 @@
|
||||||
|
/*
|
||||||
|
{
|
||||||
|
quit = false,
|
||||||
|
_views = {
|
||||||
|
{
|
||||||
|
view = 0x7ffff7ece1e8,
|
||||||
|
renderer = 0x7ffff7eccc50,
|
||||||
|
world = 0x7ffff7ece480
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deltaTimer = {
|
||||||
|
_flagStarted = false,
|
||||||
|
_timeStart = {length = 0},
|
||||||
|
_timeMeasured = {length = 0}
|
||||||
|
},
|
||||||
|
_start = {callbacks = 0x0},
|
||||||
|
_stop = {callbacks = 0x0}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
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 numberRegex = /^[0-9]+/;
|
||||||
|
|
||||||
|
export function isExpandable(value: string): number {
|
||||||
|
let primitive: any;
|
||||||
|
let match;
|
||||||
|
value = value.trim();
|
||||||
|
if (value.length == 0) return 0;
|
||||||
|
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 = referenceRegex.exec(value)) return 2; // reference
|
||||||
|
else if (match = numberRegex.exec(value)) return 0;
|
||||||
|
else if (match = variableRegex.exec(value)) return 0;
|
||||||
|
else if (match = errorRegex.exec(value)) return 0;
|
||||||
|
else return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function expandValue(variableCreate: Function, value: string): any {
|
||||||
|
let parseCString = () => {
|
||||||
|
value = value.trim();
|
||||||
|
if (value[0] != '"')
|
||||||
|
return "";
|
||||||
|
let stringEnd = 1;
|
||||||
|
let inString = true;
|
||||||
|
let remaining = value.substr(1);
|
||||||
|
let escaped = false;
|
||||||
|
while (inString) {
|
||||||
|
if (escaped)
|
||||||
|
escaped = false;
|
||||||
|
else if (remaining[0] == '\\')
|
||||||
|
escaped = true;
|
||||||
|
else if (remaining[0] == '"')
|
||||||
|
inString = false;
|
||||||
|
|
||||||
|
remaining = remaining.substr(1);
|
||||||
|
stringEnd++;
|
||||||
|
}
|
||||||
|
let str = value.substr(0, stringEnd).trim();
|
||||||
|
value = value.substr(stringEnd).trim();
|
||||||
|
return str;
|
||||||
|
};
|
||||||
|
|
||||||
|
let parseValue, parseCommaResult, parseCommaValue, parseResult;
|
||||||
|
|
||||||
|
let parseTupleOrList = () => {
|
||||||
|
value = value.trim();
|
||||||
|
if (value[0] != '{')
|
||||||
|
return undefined;
|
||||||
|
let oldContent = value;
|
||||||
|
value = value.substr(1).trim();
|
||||||
|
if (value[0] == '}')
|
||||||
|
return [];
|
||||||
|
let eqPos = value.indexOf("=");
|
||||||
|
let newValPos1 = value.indexOf("{");
|
||||||
|
let newValPos2 = value.indexOf(",");
|
||||||
|
let newValPos = newValPos1;
|
||||||
|
if (newValPos2 != -1 && newValPos2 < newValPos1)
|
||||||
|
newValPos = newValPos2;
|
||||||
|
if (newValPos != -1 && eqPos > newValPos || eqPos == -1) { // is value list
|
||||||
|
let values = [];
|
||||||
|
let val = parseValue();
|
||||||
|
values.push(val);
|
||||||
|
let remaining = value;
|
||||||
|
while (val = parseCommaValue())
|
||||||
|
values.push(val);
|
||||||
|
value = value.substr(1).trim(); // }
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = parseResult();
|
||||||
|
if (result) {
|
||||||
|
let results = [];
|
||||||
|
results.push(result);
|
||||||
|
while (result = parseCommaResult())
|
||||||
|
results.push(result);
|
||||||
|
value = value.substr(1).trim(); // }
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
let parsePrimitive = () => {
|
||||||
|
let primitive: any;
|
||||||
|
let match;
|
||||||
|
value = value.trim();
|
||||||
|
if (value.length == 0)
|
||||||
|
primitive = undefined;
|
||||||
|
else if (value.startsWith("true")) {
|
||||||
|
primitive = "true";
|
||||||
|
value = value.substr(4).trim();
|
||||||
|
}
|
||||||
|
else if (value.startsWith("false")) {
|
||||||
|
primitive = "false";
|
||||||
|
value = value.substr(5).trim();
|
||||||
|
}
|
||||||
|
else if (value.startsWith("0x0")) {
|
||||||
|
primitive = "<nullptr>";
|
||||||
|
value = value.substr(3).trim();
|
||||||
|
}
|
||||||
|
else if (match = referenceRegex.exec(value)) {
|
||||||
|
primitive = "*" + match[0];
|
||||||
|
value = value.substr(match[0].length).trim();
|
||||||
|
}
|
||||||
|
else if (match = numberRegex.exec(value)) {
|
||||||
|
primitive = match[0];
|
||||||
|
value = value.substr(match[0].length).trim();
|
||||||
|
}
|
||||||
|
else if (match = variableRegex.exec(value)) {
|
||||||
|
primitive = match[0];
|
||||||
|
value = value.substr(match[0].length).trim();
|
||||||
|
}
|
||||||
|
else if (match = errorRegex.exec(value)) {
|
||||||
|
primitive = match[0];
|
||||||
|
value = value.substr(match[0].length).trim();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
primitive = "<???>";
|
||||||
|
}
|
||||||
|
return primitive;
|
||||||
|
};
|
||||||
|
|
||||||
|
parseValue = () => {
|
||||||
|
value = value.trim();
|
||||||
|
if (value[0] == '"')
|
||||||
|
return parseCString();
|
||||||
|
else if (value[0] == '{')
|
||||||
|
return parseTupleOrList();
|
||||||
|
else
|
||||||
|
return parsePrimitive();
|
||||||
|
};
|
||||||
|
|
||||||
|
parseResult = () => {
|
||||||
|
value = value.trim();
|
||||||
|
let variableMatch = resultRegex.exec(value);
|
||||||
|
if (!variableMatch)
|
||||||
|
return undefined;
|
||||||
|
value = value.substr(variableMatch[0].length).trim();
|
||||||
|
let variable = variableMatch[1];
|
||||||
|
let val = parseValue();
|
||||||
|
let ref = 0;
|
||||||
|
if (typeof val == "object") {
|
||||||
|
ref = variableCreate(val);
|
||||||
|
val = "Object";
|
||||||
|
}
|
||||||
|
if (typeof val == "string" && val.startsWith("*0x")) {
|
||||||
|
ref = variableCreate("*" + variable);
|
||||||
|
val = "Object@" + val;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
name: variable,
|
||||||
|
value: val,
|
||||||
|
variablesReference: ref
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
parseCommaValue = () => {
|
||||||
|
value = value.trim();
|
||||||
|
if (value[0] != ',')
|
||||||
|
return undefined;
|
||||||
|
value = value.substr(1).trim();
|
||||||
|
return parseValue();
|
||||||
|
};
|
||||||
|
|
||||||
|
parseCommaResult = () => {
|
||||||
|
value = value.trim();
|
||||||
|
if (value[0] != ',')
|
||||||
|
return undefined;
|
||||||
|
value = value.substr(1).trim();
|
||||||
|
return parseResult();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
value = value.trim();
|
||||||
|
return parseValue();
|
||||||
|
}
|
||||||
278
src/backend/mi2/mi2.ts
Normal file
278
src/backend/mi2/mi2.ts
Normal file
|
|
@ -0,0 +1,278 @@
|
||||||
|
import { Breakpoint, IBackend, Stack } from "../backend.ts"
|
||||||
|
import * as ChildProcess from "child_process"
|
||||||
|
import { EventEmitter } from "events"
|
||||||
|
import { parseMI, MINode } from '../mi_parse';
|
||||||
|
|
||||||
|
export class MI2 extends EventEmitter implements IBackend {
|
||||||
|
constructor(public application: string, public preargs: string[]) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
load(cwd: string, target: string): Thenable<any> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.process = ChildProcess.spawn(this.application, this.preargs.concat([target]), { cwd: cwd });
|
||||||
|
this.process.stdout.on("data", this.stdout.bind(this));
|
||||||
|
this.process.on("exit", (() => { this.emit("quit"); }).bind(this));
|
||||||
|
Promise.all([
|
||||||
|
this.sendCommand("environment-directory \"" + cwd + "\"")
|
||||||
|
]).then(resolve, reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
stdout(data) {
|
||||||
|
this.buffer += data.toString("utf8");
|
||||||
|
let end = this.buffer.lastIndexOf('\n');
|
||||||
|
if (end != -1) {
|
||||||
|
this.onOutput(this.buffer.substr(0, end));
|
||||||
|
this.buffer = this.buffer.substr(end + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onOutput(lines) {
|
||||||
|
lines = <string[]>lines.split('\n');
|
||||||
|
lines.forEach(line => {
|
||||||
|
let parsed = parseMI(line);
|
||||||
|
//this.log("log", JSON.stringify(parsed));
|
||||||
|
let handled = false;
|
||||||
|
if (parsed.token !== undefined) {
|
||||||
|
if (this.handlers[parsed.token]) {
|
||||||
|
this.handlers[parsed.token](parsed);
|
||||||
|
delete this.handlers[parsed.token];
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (parsed.resultRecords && parsed.resultRecords.resultClass == "error") {
|
||||||
|
this.log("log", "An error occured: " + parsed.result("msg"));
|
||||||
|
}
|
||||||
|
if (parsed.outOfBandRecord) {
|
||||||
|
parsed.outOfBandRecord.forEach(record => {
|
||||||
|
if (record.isStream) {
|
||||||
|
this.log(record.type, record.content);
|
||||||
|
} else {
|
||||||
|
if (record.type == "exec") {
|
||||||
|
this.emit("exec-async-output", parsed);
|
||||||
|
if (record.asyncClass == "running")
|
||||||
|
this.emit("running", parsed);
|
||||||
|
else if (record.asyncClass == "stopped") {
|
||||||
|
if (parsed.record("reason") == "breakpoint-hit")
|
||||||
|
this.emit("breakpoint", parsed);
|
||||||
|
else if (parsed.record("reason") == "end-stepping-range")
|
||||||
|
this.emit("step-end", parsed);
|
||||||
|
else if (parsed.record("reason") == "function-finished")
|
||||||
|
this.emit("step-out-end", parsed);
|
||||||
|
else
|
||||||
|
this.emit("stopped", parsed);
|
||||||
|
} else
|
||||||
|
this.log("log", JSON.stringify(parsed));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
if (parsed.token == undefined && parsed.resultRecords == undefined && parsed.outOfBandRecord.length == 0)
|
||||||
|
handled = true;
|
||||||
|
if (!handled)
|
||||||
|
this.log("log", "Unhandled: " + JSON.stringify(parsed));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
start(): Thenable<boolean> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.log("console", "Running executable");
|
||||||
|
this.sendCommand("exec-run").then((info) => {
|
||||||
|
resolve(info.resultRecords.resultClass == "running");
|
||||||
|
}, reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
let proc = this.process;
|
||||||
|
let to = setTimeout(() => {
|
||||||
|
proc.kill();
|
||||||
|
}, 2222);
|
||||||
|
this.process.on("exit", function(code) {
|
||||||
|
clearTimeout(to);
|
||||||
|
});
|
||||||
|
this.sendRaw("-gdb-exit");
|
||||||
|
}
|
||||||
|
|
||||||
|
interrupt(): Thenable<boolean> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.sendCommand("exec-interrupt").then((info) => {
|
||||||
|
resolve(info.resultRecords.resultClass == "done");
|
||||||
|
}, reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
continue(): Thenable<boolean> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.sendCommand("exec-continue").then((info) => {
|
||||||
|
resolve(info.resultRecords.resultClass == "running");
|
||||||
|
}, reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
next(): Thenable<boolean> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.sendCommand("exec-next").then((info) => {
|
||||||
|
resolve(info.resultRecords.resultClass == "running");
|
||||||
|
}, reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
step(): Thenable<boolean> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.sendCommand("exec-step").then((info) => {
|
||||||
|
resolve(info.resultRecords.resultClass == "running");
|
||||||
|
}, reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
stepOut(): Thenable<boolean> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.sendCommand("exec-finish").then((info) => {
|
||||||
|
resolve(info.resultRecords.resultClass == "running");
|
||||||
|
}, reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
loadBreakPoints(breakpoints: Breakpoint[]): Thenable<[boolean, Breakpoint][]> {
|
||||||
|
let promisses = [];
|
||||||
|
breakpoints.forEach(breakpoint => {
|
||||||
|
promisses.push(this.addBreakPoint(breakpoint));
|
||||||
|
});
|
||||||
|
return Promise.all(promisses);
|
||||||
|
}
|
||||||
|
|
||||||
|
addBreakPoint(breakpoint: Breakpoint): Thenable<[boolean, Breakpoint]> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (this.breakpoints.has(breakpoint))
|
||||||
|
return resolve(false);
|
||||||
|
this.sendCommand("break-insert " + breakpoint.file + ":" + breakpoint.line).then((result) => {
|
||||||
|
if (result.resultRecords.resultClass == "done") {
|
||||||
|
let newBrk = {
|
||||||
|
file: result.result("bkpt.file"),
|
||||||
|
line: parseInt(result.result("bkpt.line"))
|
||||||
|
};
|
||||||
|
this.breakpoints.set(newBrk, parseInt(result.result("bkpt.number")));
|
||||||
|
resolve([true, newBrk]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
resolve([false, null]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
removeBreakPoint(breakpoint: Breakpoint): Thenable<boolean> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!this.breakpoints.has(breakpoint))
|
||||||
|
return resolve(false);
|
||||||
|
this.sendCommand("break-delete " + this.breakpoints.get(breakpoint)).then((result) => {
|
||||||
|
if (result.resultRecords.resultClass == "done") {
|
||||||
|
this.breakpoints.delete(breakpoint);
|
||||||
|
resolve(true);
|
||||||
|
}
|
||||||
|
else resolve(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
clearBreakPoints(): Thenable<any> {
|
||||||
|
let promisses = [];
|
||||||
|
let it = this.breakpoints.keys();
|
||||||
|
let value;
|
||||||
|
while (!(value = it.next()).done) {
|
||||||
|
promisses.push(this.removeBreakPoint(value.value));
|
||||||
|
}
|
||||||
|
return Promise.all(promisses);
|
||||||
|
}
|
||||||
|
|
||||||
|
getStack(maxLevels: number): Thenable<Stack[]> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let command = "stack-list-frames";
|
||||||
|
if (maxLevels) {
|
||||||
|
command += " 0 " + maxLevels;
|
||||||
|
}
|
||||||
|
this.sendCommand(command).then((result) => {
|
||||||
|
let stack = result.result("stack");
|
||||||
|
let ret: Stack[] = [];
|
||||||
|
stack.forEach(element => {
|
||||||
|
let level = MINode.valueOf(element, "@frame.level");
|
||||||
|
let addr = MINode.valueOf(element, "@frame.addr");
|
||||||
|
let func = MINode.valueOf(element, "@frame.func");
|
||||||
|
let filename = MINode.valueOf(element, "@frame.file");
|
||||||
|
let file = MINode.valueOf(element, "@frame.fullname");
|
||||||
|
let line = parseInt(MINode.valueOf(element, "@frame.line"));
|
||||||
|
let from = parseInt(MINode.valueOf(element, "@frame.from"));
|
||||||
|
ret.push({
|
||||||
|
address: addr,
|
||||||
|
fileName: filename || "",
|
||||||
|
file: file || from || "<unknown>",
|
||||||
|
function: func,
|
||||||
|
level: level,
|
||||||
|
line: line
|
||||||
|
});
|
||||||
|
});
|
||||||
|
resolve(ret);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getStackVariables(thread: number, frame: number): Thenable<[string, string][]> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.sendCommand("stack-list-variables --thread " + thread + " --frame " + frame + " --simple-values").then((result) => {
|
||||||
|
let variables = result.result("variables");
|
||||||
|
let ret: [string, string][] = [];
|
||||||
|
variables.forEach(element => {
|
||||||
|
const key = MINode.valueOf(element, "name");
|
||||||
|
const value = MINode.valueOf(element, "value");
|
||||||
|
ret.push([key, value]);
|
||||||
|
});
|
||||||
|
resolve(ret);
|
||||||
|
}, reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
evalExpression(name: string): Thenable<any> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.sendCommand("data-evaluate-expression " + name).then((result) => {
|
||||||
|
resolve(result);
|
||||||
|
}, reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
log(type: string, msg: string) {
|
||||||
|
this.emit("msg", type, msg[msg.length - 1] == '\n' ? msg : (msg + "\n"));
|
||||||
|
}
|
||||||
|
|
||||||
|
sendRaw(raw: string) {
|
||||||
|
this.process.stdin.write(raw + "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
sendCommand(command: string): Thenable<MINode> {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
resolve(node);
|
||||||
|
};
|
||||||
|
this.process.stdin.write(this.currentToken + "-" + command + "\n");
|
||||||
|
this.currentToken++;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
isReady(): boolean {
|
||||||
|
return !!this.process;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
262
src/backend/mi_parse.ts
Normal file
262
src/backend/mi_parse.ts
Normal file
|
|
@ -0,0 +1,262 @@
|
||||||
|
export interface MIInfo {
|
||||||
|
token: number;
|
||||||
|
outOfBandRecord: { isStream: boolean, type: string, asyncClass: string, output: [string, any][], content: string }[];
|
||||||
|
resultRecords: { resultClass: string, results: [string, any][] };
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MINode implements MIInfo {
|
||||||
|
token: number;
|
||||||
|
outOfBandRecord: { isStream: boolean, type: string, asyncClass: string, output: [string, any][], content: string }[];
|
||||||
|
resultRecords: { resultClass: string, results: [string, any][] };
|
||||||
|
|
||||||
|
constructor(token: number, info: { isStream: boolean, type: string, asyncClass: string, output: [string, any][], content: string }[], result: { resultClass: string, results: [string, any][] }) {
|
||||||
|
this.token = token;
|
||||||
|
this.outOfBandRecord = info;
|
||||||
|
this.resultRecords = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
record(path: string): any {
|
||||||
|
if (!this.outOfBandRecord)
|
||||||
|
return undefined;
|
||||||
|
return MINode.valueOf(this.outOfBandRecord[0].output, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
result(path: string): any {
|
||||||
|
if (!this.resultRecords)
|
||||||
|
return undefined;
|
||||||
|
return MINode.valueOf(this.resultRecords.results, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
static valueOf(start: any, path: string): any {
|
||||||
|
if (!start)
|
||||||
|
return undefined;
|
||||||
|
let pathRegex = /^\.?([a-zA-Z_\-][a-zA-Z0-9_\-]*)/;
|
||||||
|
let indexRegex = /^\[(\d+)\](?:$|\.)/;
|
||||||
|
path = path.trim();
|
||||||
|
if (!path)
|
||||||
|
return start;
|
||||||
|
let current = start;
|
||||||
|
do {
|
||||||
|
let target = pathRegex.exec(path);
|
||||||
|
if (target) {
|
||||||
|
path = path.substr(target[0].length);
|
||||||
|
if (current.length && typeof current != "string") {
|
||||||
|
let found = [];
|
||||||
|
for (let i = 0; i < current.length; i++) {
|
||||||
|
let element = current[i];
|
||||||
|
if (element[0] == target[1]) {
|
||||||
|
found.push(element[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (found.length > 1) {
|
||||||
|
current = found;
|
||||||
|
} else if (found.length == 1) {
|
||||||
|
current = found[0];
|
||||||
|
} else return undefined;
|
||||||
|
} else return undefined;
|
||||||
|
}
|
||||||
|
else if (path[0] == '@') {
|
||||||
|
current = [current];
|
||||||
|
path = path.substr(1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
target = indexRegex.exec(path);
|
||||||
|
if (target) {
|
||||||
|
path = path.substr(target[0].length);
|
||||||
|
let i = parseInt(target[1]);
|
||||||
|
if (current.length && typeof current != "string" && i >= 0 && i < current.length) {
|
||||||
|
current = current[i];
|
||||||
|
} else if (i == 0) {
|
||||||
|
} else return undefined;
|
||||||
|
}
|
||||||
|
else return undefined;
|
||||||
|
}
|
||||||
|
path = path.trim();
|
||||||
|
} while (path);
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const tokenRegex = /^[0-9]+/;
|
||||||
|
const outOfBandRecordRegex = /^(?:([0-9]*)([\*\+\=])|([\~\@\&]))/;
|
||||||
|
const resultRecordRegex = /^([0-9]*)\^(done|running|connected|error|exit)/;
|
||||||
|
const newlineRegex = /^\r\n?/;
|
||||||
|
const endRegex = /^\(gdb\)\r\n?/;
|
||||||
|
const variableRegex = /^([a-zA-Z_\-][a-zA-Z0-9_\-]*)/;
|
||||||
|
const asyncClassRegex = /^(.*?),/;
|
||||||
|
|
||||||
|
export function parseMI(output: string): MINode {
|
||||||
|
/*
|
||||||
|
output ==>
|
||||||
|
(
|
||||||
|
exec-async-output = [ token ] "*" ("stopped" | others) ( "," variable "=" (const | tuple | list) )* \n
|
||||||
|
status-async-output = [ token ] "+" ("stopped" | others) ( "," variable "=" (const | tuple | list) )* \n
|
||||||
|
notify-async-output = [ token ] "=" ("stopped" | others) ( "," variable "=" (const | tuple | list) )* \n
|
||||||
|
console-stream-output = "~" c-string \n
|
||||||
|
target-stream-output = "@" c-string \n
|
||||||
|
log-stream-output = "&" c-string \n
|
||||||
|
)*
|
||||||
|
[
|
||||||
|
[ token ] "^" ("done" | "running" | "connected" | "error" | "exit") ( "," variable "=" (const | tuple | list) )* \n
|
||||||
|
]
|
||||||
|
"(gdb)" \n
|
||||||
|
*/
|
||||||
|
|
||||||
|
let token = undefined;
|
||||||
|
let outOfBandRecord = [];
|
||||||
|
let resultRecords = undefined;
|
||||||
|
|
||||||
|
let asyncRecordType = {
|
||||||
|
"*": "exec",
|
||||||
|
"+": "status",
|
||||||
|
"=": "notify"
|
||||||
|
};
|
||||||
|
let streamRecordType = {
|
||||||
|
"~": "console",
|
||||||
|
"@": "target",
|
||||||
|
"&": "log"
|
||||||
|
};
|
||||||
|
|
||||||
|
let parseCString = () => {
|
||||||
|
if (output[0] != '"')
|
||||||
|
return "";
|
||||||
|
let stringEnd = 1;
|
||||||
|
let inString = true;
|
||||||
|
let remaining = output.substr(1);
|
||||||
|
let escaped = false;
|
||||||
|
while (inString) {
|
||||||
|
if (escaped)
|
||||||
|
escaped = false;
|
||||||
|
else if (remaining[0] == '\\')
|
||||||
|
escaped = true;
|
||||||
|
else if (remaining[0] == '"')
|
||||||
|
inString = false;
|
||||||
|
|
||||||
|
remaining = remaining.substr(1);
|
||||||
|
stringEnd++;
|
||||||
|
}
|
||||||
|
// hax
|
||||||
|
let str = JSON.parse(output.substr(0, stringEnd));
|
||||||
|
output = output.substr(stringEnd);
|
||||||
|
return str;
|
||||||
|
};
|
||||||
|
|
||||||
|
let parseValue, parseCommaResult, parseCommaValue, parseResult;
|
||||||
|
|
||||||
|
let parseTupleOrList = () => {
|
||||||
|
if (output[0] != '{' && output[0] != '[')
|
||||||
|
return undefined;
|
||||||
|
let oldContent = output;
|
||||||
|
let canBeValueList = output[0] == '[';
|
||||||
|
output = output.substr(1);
|
||||||
|
if (output[0] == '}' || output[0] == ']')
|
||||||
|
return [];
|
||||||
|
if (canBeValueList) {
|
||||||
|
let value = parseValue();
|
||||||
|
if (value) { // is value list
|
||||||
|
let values = [];
|
||||||
|
values.push(value);
|
||||||
|
let remaining = output;
|
||||||
|
while (value = parseCommaValue())
|
||||||
|
values.push(value);
|
||||||
|
output = output.substr(1); // ]
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let result = parseResult();
|
||||||
|
if (result) {
|
||||||
|
let results = [];
|
||||||
|
results.push(result);
|
||||||
|
while (result = parseCommaResult())
|
||||||
|
results.push(result);
|
||||||
|
output = output.substr(1); // }
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
output = (canBeValueList ? '[' : '{') + output;
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
parseValue = () => {
|
||||||
|
if (output[0] == '"')
|
||||||
|
return parseCString();
|
||||||
|
else if (output[0] == '{' || output[0] == '[')
|
||||||
|
return parseTupleOrList();
|
||||||
|
else
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
parseResult = () => {
|
||||||
|
let variableMatch = variableRegex.exec(output);
|
||||||
|
if (!variableMatch)
|
||||||
|
return undefined;
|
||||||
|
output = output.substr(variableMatch[0].length + 1);
|
||||||
|
let variable = variableMatch[1];
|
||||||
|
return [variable, parseValue()];
|
||||||
|
};
|
||||||
|
|
||||||
|
parseCommaValue = () => {
|
||||||
|
if (output[0] != ',')
|
||||||
|
return undefined;
|
||||||
|
output = output.substr(1);
|
||||||
|
return parseValue();
|
||||||
|
};
|
||||||
|
|
||||||
|
parseCommaResult = () => {
|
||||||
|
if (output[0] != ',')
|
||||||
|
return undefined;
|
||||||
|
output = output.substr(1);
|
||||||
|
return parseResult();
|
||||||
|
};
|
||||||
|
|
||||||
|
let match = undefined;
|
||||||
|
|
||||||
|
while (match = outOfBandRecordRegex.exec(output)) {
|
||||||
|
output = output.substr(match[0].length);
|
||||||
|
if (match[1] && token === undefined) {
|
||||||
|
token = parseInt(match[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match[2]) {
|
||||||
|
let classMatch = asyncClassRegex.exec(output);
|
||||||
|
output = output.substr(classMatch[1].length);
|
||||||
|
let asyncRecord = {
|
||||||
|
isStream: false,
|
||||||
|
type: asyncRecordType[match[2]],
|
||||||
|
asyncClass: classMatch[1],
|
||||||
|
output: []
|
||||||
|
};
|
||||||
|
let result;
|
||||||
|
while (result = parseCommaResult())
|
||||||
|
asyncRecord.output.push(result);
|
||||||
|
outOfBandRecord.push(asyncRecord);
|
||||||
|
}
|
||||||
|
else if (match[3]) {
|
||||||
|
let streamRecord = {
|
||||||
|
isStream: true,
|
||||||
|
type: streamRecordType[match[3]],
|
||||||
|
content: parseCString()
|
||||||
|
};
|
||||||
|
outOfBandRecord.push(streamRecord);
|
||||||
|
}
|
||||||
|
|
||||||
|
output = output.replace(newlineRegex, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match = resultRecordRegex.exec(output)) {
|
||||||
|
output = output.substr(match[0].length);
|
||||||
|
if (match[1] && token === undefined) {
|
||||||
|
token = parseInt(match[1]);
|
||||||
|
}
|
||||||
|
resultRecords = {
|
||||||
|
resultClass: match[2],
|
||||||
|
results: []
|
||||||
|
};
|
||||||
|
let result;
|
||||||
|
while (result = parseCommaResult())
|
||||||
|
resultRecords.results.push(result);
|
||||||
|
|
||||||
|
output = output.replace(newlineRegex, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MINode(token, outOfBandRecord || [], resultRecords);
|
||||||
|
}
|
||||||
255
src/gdb.ts
Normal file
255
src/gdb.ts
Normal file
|
|
@ -0,0 +1,255 @@
|
||||||
|
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'
|
||||||
|
|
||||||
|
export interface LaunchRequestArguments {
|
||||||
|
cwd: string;
|
||||||
|
target: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class MI2DebugSession extends DebugSession {
|
||||||
|
private static THREAD_ID = 1;
|
||||||
|
private gdbDebugger: MI2;
|
||||||
|
private variableHandles = new Handles<any>();
|
||||||
|
private quit: boolean;
|
||||||
|
|
||||||
|
public constructor(debuggerLinesStartAt1: boolean, isServer: boolean = false) {
|
||||||
|
super(debuggerLinesStartAt1, isServer);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void {
|
||||||
|
this.sendResponse(response);
|
||||||
|
this.gdbDebugger = new MI2("gdb", ["-q", "--interpreter=mi2"]);
|
||||||
|
this.gdbDebugger.on("quit", 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.handleBreak.bind(this));
|
||||||
|
this.gdbDebugger.on("step-end", this.handleBreak.bind(this));
|
||||||
|
this.gdbDebugger.on("step-out-end", this.handleBreak.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 handleBreak(info: MINode) {
|
||||||
|
this.sendEvent(new StoppedEvent("step", MI2DebugSession.THREAD_ID));
|
||||||
|
}
|
||||||
|
|
||||||
|
private stopEvent(info: MINode) {
|
||||||
|
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 {
|
||||||
|
this.gdbDebugger.load(args.cwd, args.target).then(() => {
|
||||||
|
this.gdbDebugger.start().then(() => {
|
||||||
|
this.sendResponse(response);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected disconnectRequest(response: DebugProtocol.DisconnectResponse, args: DebugProtocol.DisconnectArguments): void {
|
||||||
|
this.gdbDebugger.stop();
|
||||||
|
this.sendResponse(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments): void {
|
||||||
|
this.gdbDebugger.clearBreakPoints().then(() => {
|
||||||
|
let path = args.source.path;
|
||||||
|
let lines = args.lines;
|
||||||
|
let all = [];
|
||||||
|
lines.forEach(line => {
|
||||||
|
all.push(this.gdbDebugger.addBreakPoint({ file: path, line: line }));
|
||||||
|
});
|
||||||
|
Promise.all(all).then(brkpoints => {
|
||||||
|
response.body.breakpoints = brkpoints;
|
||||||
|
this.sendResponse(response);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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 => {
|
||||||
|
ret.push(new StackFrame(element.level, element.function + "@" + element.address, new Source(element.fileName, element.file), element.line, 0));
|
||||||
|
});
|
||||||
|
response.body = {
|
||||||
|
stackFrames: ret
|
||||||
|
};
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Variable members
|
||||||
|
this.gdbDebugger.evalExpression(JSON.stringify(id)).then(variable => {
|
||||||
|
let expanded = expandValue(createVariable, variable.result("value"));
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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")
|
||||||
|
this.gdbDebugger.evalExpression(args.expression).then((res) => {
|
||||||
|
response.body = {
|
||||||
|
variablesReference: 0,
|
||||||
|
result: res.result("value")
|
||||||
|
}
|
||||||
|
this.sendResponse(response);
|
||||||
|
});
|
||||||
|
else {
|
||||||
|
this.gdbDebugger.sendRaw(args.expression);
|
||||||
|
this.sendResponse(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function prettyStringArray(strings: string[]) {
|
||||||
|
return strings.join(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
DebugSession.run(MI2DebugSession);
|
||||||
237
test/gdb_expansion.test.ts
Normal file
237
test/gdb_expansion.test.ts
Normal file
|
|
@ -0,0 +1,237 @@
|
||||||
|
import * as assert from 'assert';
|
||||||
|
import { expandValue, isExpandable } from '../src/backend/gdb_expansion';
|
||||||
|
|
||||||
|
suite("GDB Value Expansion", () => {
|
||||||
|
let variableCreate = (variable) => { return { expanded: variable }; };
|
||||||
|
test("Various values", () => {
|
||||||
|
assert.strictEqual(isExpandable(`false`), 0);
|
||||||
|
assert.equal(expandValue(variableCreate, `false`), "false");
|
||||||
|
assert.strictEqual(isExpandable(`5`), 0);
|
||||||
|
assert.equal(expandValue(variableCreate, `5`), "5");
|
||||||
|
assert.strictEqual(isExpandable(`"hello world!"`), 0);
|
||||||
|
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(`{a = b, c = d}`), 1);
|
||||||
|
assert.deepEqual(expandValue(variableCreate, `{a = b, c = d}`), [
|
||||||
|
{
|
||||||
|
name: "a",
|
||||||
|
value: "b",
|
||||||
|
variablesReference: 0
|
||||||
|
}, {
|
||||||
|
name: "c",
|
||||||
|
value: "d",
|
||||||
|
variablesReference: 0
|
||||||
|
}]);
|
||||||
|
assert.strictEqual(isExpandable(`{{a = b}}`), 1);
|
||||||
|
assert.deepEqual(expandValue(variableCreate, `{{a = b}}`), [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: "a",
|
||||||
|
value: "b",
|
||||||
|
variablesReference: 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
assert.deepEqual(expandValue(variableCreate, `{1, 2, 3, 4}`), [1, 2, 3, 4]);
|
||||||
|
});
|
||||||
|
test("Error values", () => {
|
||||||
|
assert.strictEqual(isExpandable(`<No data fields>`), 0);
|
||||||
|
assert.equal(expandValue(variableCreate, `<No data fields>`), "<No data fields>");
|
||||||
|
});
|
||||||
|
test("Nested values", () => {
|
||||||
|
assert.strictEqual(isExpandable(`{a = {b = e}, c = d}`), 1);
|
||||||
|
assert.deepEqual(expandValue(variableCreate, `{a = {b = e}, c = d}`), [
|
||||||
|
{
|
||||||
|
name: "a",
|
||||||
|
value: "Object",
|
||||||
|
variablesReference: {
|
||||||
|
expanded: [
|
||||||
|
{
|
||||||
|
name: "b",
|
||||||
|
value: "e",
|
||||||
|
variablesReference: 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
name: "c",
|
||||||
|
value: "d",
|
||||||
|
variablesReference: 0
|
||||||
|
}]);
|
||||||
|
});
|
||||||
|
test("Simple node", () => {
|
||||||
|
assert.strictEqual(isExpandable(`{a = false, b = 5, c = 0x0, d = "foobar"}`), 1);
|
||||||
|
let variables = expandValue(variableCreate, `{a = false, b = 5, c = 0x0, d = "foobar"}`);
|
||||||
|
assert.equal(variables.length, 4);
|
||||||
|
assert.equal(variables[0].name, "a");
|
||||||
|
assert.equal(variables[0].value, "false");
|
||||||
|
assert.equal(variables[1].name, "b");
|
||||||
|
assert.equal(variables[1].value, "5");
|
||||||
|
assert.equal(variables[2].name, "c");
|
||||||
|
assert.equal(variables[2].value, "<nullptr>");
|
||||||
|
assert.equal(variables[3].name, "d");
|
||||||
|
assert.equal(variables[3].value, `"foobar"`);
|
||||||
|
});
|
||||||
|
test("Complex node", () => {
|
||||||
|
let node = `{quit = false, _views = {{view = 0x7ffff7ece1e8, renderer = 0x7ffff7eccc50, world = 0x7ffff7ece480}}, deltaTimer = {_flagStarted = false, _timeStart = {length = 0}, _timeMeasured = {length = 0}}, _start = {callbacks = 0x0}, _stop = {callbacks = 0x0}}`;
|
||||||
|
assert.strictEqual(isExpandable(node), 1);
|
||||||
|
let variables = expandValue(variableCreate, node);
|
||||||
|
assert.deepEqual(variables, [
|
||||||
|
{
|
||||||
|
name: "quit",
|
||||||
|
value: "false",
|
||||||
|
variablesReference: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "_views",
|
||||||
|
value: "Object",
|
||||||
|
variablesReference: {
|
||||||
|
expanded: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
name: "view",
|
||||||
|
value: "Object@*0x7ffff7ece1e8",
|
||||||
|
variablesReference: { expanded: "*view" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "renderer",
|
||||||
|
value: "Object@*0x7ffff7eccc50",
|
||||||
|
variablesReference: { expanded: "*renderer" }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "world",
|
||||||
|
value: "Object@*0x7ffff7ece480",
|
||||||
|
variablesReference: { expanded: "*world" }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "deltaTimer",
|
||||||
|
value: "Object",
|
||||||
|
variablesReference: {
|
||||||
|
expanded: [
|
||||||
|
{
|
||||||
|
name: "_flagStarted",
|
||||||
|
value: "false",
|
||||||
|
variablesReference: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "_timeStart",
|
||||||
|
value: "Object",
|
||||||
|
variablesReference: {
|
||||||
|
expanded: [
|
||||||
|
{
|
||||||
|
name: "length",
|
||||||
|
value: "0",
|
||||||
|
variablesReference: 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "_timeMeasured",
|
||||||
|
value: "Object",
|
||||||
|
variablesReference: {
|
||||||
|
expanded: [
|
||||||
|
{
|
||||||
|
name: "length",
|
||||||
|
value: "0",
|
||||||
|
variablesReference: 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "_start",
|
||||||
|
value: "Object",
|
||||||
|
variablesReference: {
|
||||||
|
expanded: [
|
||||||
|
{
|
||||||
|
name: "callbacks",
|
||||||
|
value: "<nullptr>",
|
||||||
|
variablesReference: 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "_stop",
|
||||||
|
value: "Object",
|
||||||
|
variablesReference: {
|
||||||
|
expanded: [
|
||||||
|
{
|
||||||
|
name: "callbacks",
|
||||||
|
value: "<nullptr>",
|
||||||
|
variablesReference: 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
test("Simple node with errors", () => {
|
||||||
|
let node = `{_enableMipMaps = false, _minFilter = <incomplete type>, _magFilter = <incomplete type>, _wrapX = <incomplete type>, _wrapY = <incomplete type>, _inMode = 6408, _mode = 6408, _id = 1, _width = 1024, _height = 1024}`;
|
||||||
|
assert.strictEqual(isExpandable(node), 1);
|
||||||
|
let variables = expandValue(variableCreate, node);
|
||||||
|
assert.deepEqual(variables, [
|
||||||
|
{
|
||||||
|
name: "_enableMipMaps",
|
||||||
|
value: "false",
|
||||||
|
variablesReference: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "_minFilter",
|
||||||
|
value: "<incomplete type>",
|
||||||
|
variablesReference: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "_magFilter",
|
||||||
|
value: "<incomplete type>",
|
||||||
|
variablesReference: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "_wrapX",
|
||||||
|
value: "<incomplete type>",
|
||||||
|
variablesReference: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "_wrapY",
|
||||||
|
value: "<incomplete type>",
|
||||||
|
variablesReference: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "_inMode",
|
||||||
|
value: "6408",
|
||||||
|
variablesReference: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "_mode",
|
||||||
|
value: "6408",
|
||||||
|
variablesReference: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "_id",
|
||||||
|
value: "1",
|
||||||
|
variablesReference: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "_width",
|
||||||
|
value: "1024",
|
||||||
|
variablesReference: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "_height",
|
||||||
|
value: "1024",
|
||||||
|
variablesReference: 0
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
})
|
||||||
|
});
|
||||||
22
test/index.ts
Normal file
22
test/index.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
//
|
||||||
|
// PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING
|
||||||
|
//
|
||||||
|
// This file is providing the test runner to use when running extension tests.
|
||||||
|
// By default the test runner in use is Mocha based.
|
||||||
|
//
|
||||||
|
// You can provide your own test runner if you want to override it by exporting
|
||||||
|
// a function run(testRoot: string, clb: (error:Error) => void) that the extension
|
||||||
|
// host can call to run the tests. The test runner is expected to use console.log
|
||||||
|
// to report the results back to the caller. When the tests are finished, return
|
||||||
|
// a possible error to the callback or null if none.
|
||||||
|
|
||||||
|
var testRunner = require('vscode/lib/testrunner');
|
||||||
|
|
||||||
|
// You can directly control Mocha options by uncommenting the following lines
|
||||||
|
// See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options for more info
|
||||||
|
testRunner.configure({
|
||||||
|
ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.)
|
||||||
|
useColors: true // colored output from test results
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = testRunner;
|
||||||
160
test/mi_parse.test.ts
Normal file
160
test/mi_parse.test.ts
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
import * as assert from 'assert';
|
||||||
|
import { parseMI, MINode } from '../src/backend/mi_parse';
|
||||||
|
|
||||||
|
suite("MI Parse", () => {
|
||||||
|
test("Simple out of band record", () => {
|
||||||
|
let parsed = parseMI(`4=thread-exited,id="3",group-id="i1"`);
|
||||||
|
assert.ok(parsed);
|
||||||
|
assert.equal(parsed.token, 4);
|
||||||
|
assert.equal(parsed.outOfBandRecord.length, 1);
|
||||||
|
assert.equal(parsed.outOfBandRecord[0].isStream, false);
|
||||||
|
assert.equal(parsed.outOfBandRecord[0].asyncClass, "thread-exited");
|
||||||
|
assert.equal(parsed.outOfBandRecord[0].output.length, 2);
|
||||||
|
assert.deepEqual(parsed.outOfBandRecord[0].output[0], ["id", "3"]);
|
||||||
|
assert.deepEqual(parsed.outOfBandRecord[0].output[1], ["group-id", "i1"]);
|
||||||
|
assert.equal(parsed.resultRecords, undefined);
|
||||||
|
});
|
||||||
|
test("Console stream output with new line", () => {
|
||||||
|
let parsed = parseMI(`~"[Thread 0x7fffe993a700 (LWP 11002) exited]\\n"`);
|
||||||
|
assert.ok(parsed);
|
||||||
|
assert.equal(parsed.token, undefined);
|
||||||
|
assert.equal(parsed.outOfBandRecord.length, 1);
|
||||||
|
assert.equal(parsed.outOfBandRecord[0].isStream, true);
|
||||||
|
assert.equal(parsed.outOfBandRecord[0].content, "[Thread 0x7fffe993a700 (LWP 11002) exited]\n");
|
||||||
|
assert.equal(parsed.resultRecords, undefined);
|
||||||
|
});
|
||||||
|
test("Empty line", () => {
|
||||||
|
let parsed = parseMI(``);
|
||||||
|
assert.ok(parsed);
|
||||||
|
assert.equal(parsed.token, undefined);
|
||||||
|
assert.equal(parsed.outOfBandRecord.length, 0);
|
||||||
|
assert.equal(parsed.resultRecords, undefined);
|
||||||
|
});
|
||||||
|
test("'(gdb)' line", () => {
|
||||||
|
let parsed = parseMI(`(gdb)`);
|
||||||
|
assert.ok(parsed);
|
||||||
|
assert.equal(parsed.token, undefined);
|
||||||
|
assert.equal(parsed.outOfBandRecord.length, 0);
|
||||||
|
assert.equal(parsed.resultRecords, undefined);
|
||||||
|
});
|
||||||
|
test("Simple result record", () => {
|
||||||
|
let parsed = parseMI(`1^running`);
|
||||||
|
assert.ok(parsed);
|
||||||
|
assert.equal(parsed.token, 1);
|
||||||
|
assert.equal(parsed.outOfBandRecord.length, 0);
|
||||||
|
assert.notEqual(parsed.resultRecords, undefined);
|
||||||
|
assert.equal(parsed.resultRecords.resultClass, "running");
|
||||||
|
assert.equal(parsed.resultRecords.results.length, 0);
|
||||||
|
});
|
||||||
|
test("Advanced out of band record (Breakpoint hit)", () => {
|
||||||
|
let parsed = parseMI(`*stopped,reason="breakpoint-hit",disp="keep",bkptno="1",frame={addr="0x00000000004e807f",func="D main",args=[{name="args",value="..."}],file="source/app.d",fullname="/path/to/source/app.d",line="157"},thread-id="1",stopped-threads="all",core="0"`);
|
||||||
|
assert.ok(parsed);
|
||||||
|
assert.equal(parsed.token, undefined);
|
||||||
|
assert.equal(parsed.outOfBandRecord.length, 1);
|
||||||
|
assert.equal(parsed.outOfBandRecord[0].isStream, false);
|
||||||
|
assert.equal(parsed.outOfBandRecord[0].asyncClass, "stopped");
|
||||||
|
assert.equal(parsed.outOfBandRecord[0].output.length, 7);
|
||||||
|
assert.deepEqual(parsed.outOfBandRecord[0].output[0], ["reason", "breakpoint-hit"]);
|
||||||
|
assert.deepEqual(parsed.outOfBandRecord[0].output[1], ["disp", "keep"]);
|
||||||
|
assert.deepEqual(parsed.outOfBandRecord[0].output[2], ["bkptno", "1"]);
|
||||||
|
let frame = [
|
||||||
|
["addr", "0x00000000004e807f"],
|
||||||
|
["func", "D main"],
|
||||||
|
["args", [[["name", "args"], ["value", "..."]]]],
|
||||||
|
["file", "source/app.d"],
|
||||||
|
["fullname", "/path/to/source/app.d"],
|
||||||
|
["line", "157"]
|
||||||
|
];
|
||||||
|
assert.deepEqual(parsed.outOfBandRecord[0].output[3], ["frame", frame]);
|
||||||
|
assert.deepEqual(parsed.outOfBandRecord[0].output[4], ["thread-id", "1"]);
|
||||||
|
assert.deepEqual(parsed.outOfBandRecord[0].output[5], ["stopped-threads", "all"]);
|
||||||
|
assert.deepEqual(parsed.outOfBandRecord[0].output[6], ["core", "0"]);
|
||||||
|
assert.equal(parsed.resultRecords, undefined);
|
||||||
|
});
|
||||||
|
test("Advanced result record", () => {
|
||||||
|
let parsed = parseMI(`2^done,asm_insns=[src_and_asm_line={line="134",file="source/app.d",fullname="/path/to/source/app.d",line_asm_insn=[{address="0x00000000004e7da4",func-name="_Dmain",offset="0",inst="push %rbp"},{address="0x00000000004e7da5",func-name="_Dmain",offset="1",inst="mov %rsp,%rbp"}]}]`);
|
||||||
|
assert.ok(parsed);
|
||||||
|
assert.equal(parsed.token, 2);
|
||||||
|
assert.equal(parsed.outOfBandRecord.length, 0);
|
||||||
|
assert.notEqual(parsed.resultRecords, undefined);
|
||||||
|
assert.equal(parsed.resultRecords.resultClass, "done");
|
||||||
|
assert.equal(parsed.resultRecords.results.length, 1);
|
||||||
|
let asm_insns = [
|
||||||
|
"asm_insns",
|
||||||
|
[
|
||||||
|
[
|
||||||
|
"src_and_asm_line",
|
||||||
|
[
|
||||||
|
["line", "134"],
|
||||||
|
["file", "source/app.d"],
|
||||||
|
["fullname", "/path/to/source/app.d"],
|
||||||
|
[
|
||||||
|
"line_asm_insn",
|
||||||
|
[
|
||||||
|
[
|
||||||
|
["address", "0x00000000004e7da4"],
|
||||||
|
["func-name", "_Dmain"],
|
||||||
|
["offset", "0"],
|
||||||
|
["inst", "push %rbp"]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
["address", "0x00000000004e7da5"],
|
||||||
|
["func-name", "_Dmain"],
|
||||||
|
["offset", "1"],
|
||||||
|
["inst", "mov %rsp,%rbp"]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
assert.deepEqual(parsed.resultRecords.results[0], asm_insns);
|
||||||
|
assert.equal(parsed.result("asm_insns.src_and_asm_line.line_asm_insn[1].address"), "0x00000000004e7da5");
|
||||||
|
});
|
||||||
|
test("valueof children", () => {
|
||||||
|
let obj = [
|
||||||
|
[
|
||||||
|
"frame",
|
||||||
|
[
|
||||||
|
["level", "0"],
|
||||||
|
["addr", "0x0000000000435f70"],
|
||||||
|
["func", "D main"],
|
||||||
|
["file", "source/app.d"],
|
||||||
|
["fullname", "/path/to/source/app.d"],
|
||||||
|
["line", "5"]
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"frame",
|
||||||
|
[
|
||||||
|
["level", "1"],
|
||||||
|
["addr", "0x00000000004372d3"],
|
||||||
|
["func", "rt.dmain2._d_run_main()"]
|
||||||
|
]
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"frame",
|
||||||
|
[
|
||||||
|
["level", "2"],
|
||||||
|
["addr", "0x0000000000437229"],
|
||||||
|
["func", "rt.dmain2._d_run_main()"]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
assert.equal(MINode.valueOf(obj[0], "@frame.level"), "0");
|
||||||
|
assert.equal(MINode.valueOf(obj[0], "@frame.addr"), "0x0000000000435f70");
|
||||||
|
assert.equal(MINode.valueOf(obj[0], "@frame.func"), "D main");
|
||||||
|
assert.equal(MINode.valueOf(obj[0], "@frame.file"), "source/app.d");
|
||||||
|
assert.equal(MINode.valueOf(obj[0], "@frame.fullname"), "/path/to/source/app.d");
|
||||||
|
assert.equal(MINode.valueOf(obj[0], "@frame.line"), "5");
|
||||||
|
|
||||||
|
assert.equal(MINode.valueOf(obj[1], "@frame.level"), "1");
|
||||||
|
assert.equal(MINode.valueOf(obj[1], "@frame.addr"), "0x00000000004372d3");
|
||||||
|
assert.equal(MINode.valueOf(obj[1], "@frame.func"), "rt.dmain2._d_run_main()");
|
||||||
|
assert.equal(MINode.valueOf(obj[1], "@frame.file"), undefined);
|
||||||
|
assert.equal(MINode.valueOf(obj[1], "@frame.fullname"), undefined);
|
||||||
|
assert.equal(MINode.valueOf(obj[1], "@frame.line"), undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
12
tsconfig.json
Normal file
12
tsconfig.json
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "ES5",
|
||||||
|
"outDir": "out",
|
||||||
|
"noLib": true,
|
||||||
|
"sourceMap": true
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
|
}
|
||||||
1
typings/node.d.ts
vendored
Normal file
1
typings/node.d.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/// <reference path="../node_modules/vscode/typings/node.d.ts" />
|
||||||
1
typings/vscode-typings.d.ts
vendored
Normal file
1
typings/vscode-typings.d.ts
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
/// <reference path="../node_modules/vscode/typings/index.d.ts" />
|
||||||
Loading…
Add table
Add a link
Reference in a new issue