Merge pull request #129 from LeszekSwirski/multi-threading
Support multi-threading
This commit is contained in:
commit
bcbbda1eea
8 changed files with 203 additions and 90 deletions
|
|
@ -11,6 +11,12 @@ export interface Breakpoint {
|
||||||
countCondition?: string;
|
countCondition?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Thread {
|
||||||
|
id: number;
|
||||||
|
targetId: string;
|
||||||
|
name?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Stack {
|
export interface Stack {
|
||||||
level: number;
|
level: number;
|
||||||
address: string;
|
address: string;
|
||||||
|
|
@ -58,9 +64,10 @@ export interface IBackend {
|
||||||
addBreakPoint(breakpoint: Breakpoint): Thenable<[boolean, Breakpoint]>;
|
addBreakPoint(breakpoint: Breakpoint): Thenable<[boolean, Breakpoint]>;
|
||||||
removeBreakPoint(breakpoint: Breakpoint): Thenable<boolean>;
|
removeBreakPoint(breakpoint: Breakpoint): Thenable<boolean>;
|
||||||
clearBreakPoints(): Thenable<any>;
|
clearBreakPoints(): Thenable<any>;
|
||||||
getStack(maxLevels: number): Thenable<Stack[]>;
|
getThreads(): Thenable<Thread[]>;
|
||||||
|
getStack(maxLevels: number, thread: number): Thenable<Stack[]>;
|
||||||
getStackVariables(thread: number, frame: number): Thenable<Variable[]>;
|
getStackVariables(thread: number, frame: number): Thenable<Variable[]>;
|
||||||
evalExpression(name: string): Thenable<any>;
|
evalExpression(name: string, thread: number, frame: number): Thenable<any>;
|
||||||
isReady(): boolean;
|
isReady(): boolean;
|
||||||
changeVariable(name: string, rawValue: string): Thenable<any>;
|
changeVariable(name: string, rawValue: string): Thenable<any>;
|
||||||
examineMemory(from: number, to: number): Thenable<any>;
|
examineMemory(from: number, to: number): Thenable<any>;
|
||||||
|
|
@ -114,12 +121,8 @@ export class VariableObject {
|
||||||
evaluateName: this.name,
|
evaluateName: this.name,
|
||||||
value: (this.value === void 0) ? "<unknown>" : this.value,
|
value: (this.value === void 0) ? "<unknown>" : this.value,
|
||||||
type: this.type,
|
type: this.type,
|
||||||
// kind: this.displayhint,
|
|
||||||
variablesReference: this.id
|
variablesReference: this.id
|
||||||
};
|
};
|
||||||
if (this.displayhint) {
|
|
||||||
res.kind = this.displayhint;
|
|
||||||
}
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ const variableRegex = /^[a-zA-Z_\-][a-zA-Z0-9_\-]*/;
|
||||||
const errorRegex = /^\<.+?\>/;
|
const errorRegex = /^\<.+?\>/;
|
||||||
const referenceStringRegex = /^(0x[0-9a-fA-F]+\s*)"/;
|
const referenceStringRegex = /^(0x[0-9a-fA-F]+\s*)"/;
|
||||||
const referenceRegex = /^0x[0-9a-fA-F]+/;
|
const referenceRegex = /^0x[0-9a-fA-F]+/;
|
||||||
|
const cppReferenceRegex = /^@0x[0-9a-fA-F]+/;
|
||||||
const nullpointerRegex = /^0x0+\b/;
|
const nullpointerRegex = /^0x0+\b/;
|
||||||
const charRegex = /^(\d+) ['"]/;
|
const charRegex = /^(\d+) ['"]/;
|
||||||
const numberRegex = /^\d+(\.\d+)?/;
|
const numberRegex = /^\d+(\.\d+)?/;
|
||||||
|
|
@ -168,6 +169,10 @@ export function expandValue(variableCreate: Function, value: string, root: strin
|
||||||
primitive = "*" + match[0];
|
primitive = "*" + match[0];
|
||||||
value = value.substr(match[0].length).trim();
|
value = value.substr(match[0].length).trim();
|
||||||
}
|
}
|
||||||
|
else if (match = cppReferenceRegex.exec(value)) {
|
||||||
|
primitive = match[0];
|
||||||
|
value = value.substr(match[0].length).trim();
|
||||||
|
}
|
||||||
else if (match = charRegex.exec(value)) {
|
else if (match = charRegex.exec(value)) {
|
||||||
primitive = match[1];
|
primitive = match[1];
|
||||||
value = value.substr(match[0].length - 1);
|
value = value.substr(match[0].length - 1);
|
||||||
|
|
@ -222,19 +227,21 @@ export function expandValue(variableCreate: Function, value: string, root: strin
|
||||||
ref = variableCreate(val);
|
ref = variableCreate(val);
|
||||||
val = "Object";
|
val = "Object";
|
||||||
}
|
}
|
||||||
if (typeof val == "string" && val.startsWith("*0x")) {
|
else if (typeof val == "string" && val.startsWith("*0x")) {
|
||||||
if (extra && MINode.valueOf(extra, "arg") == "1")
|
if (extra && MINode.valueOf(extra, "arg") == "1") {
|
||||||
{
|
|
||||||
ref = variableCreate(getNamespace("*(" + name), { arg: true });
|
ref = variableCreate(getNamespace("*(" + name), { arg: true });
|
||||||
val = "<args>";
|
val = "<args>";
|
||||||
}
|
}
|
||||||
else
|
else {
|
||||||
{
|
|
||||||
ref = variableCreate(getNamespace("*" + name));
|
ref = variableCreate(getNamespace("*" + name));
|
||||||
val = "Object@" + val;
|
val = "Object@" + val;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (typeof val == "string" && val.startsWith("<...>")) {
|
else if (typeof val == "string" && val.startsWith("@0x")) {
|
||||||
|
ref = variableCreate(getNamespace("*&" + name.substr));
|
||||||
|
val = "Ref" + val;
|
||||||
|
}
|
||||||
|
else if (typeof val == "string" && val.startsWith("<...>")) {
|
||||||
ref = variableCreate(getNamespace(name));
|
ref = variableCreate(getNamespace(name));
|
||||||
val = "...";
|
val = "...";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { Breakpoint, IBackend, Stack, SSHArguments, Variable, VariableObject, MIError } from "../backend"
|
import { Breakpoint, IBackend, Thread, Stack, SSHArguments, Variable, VariableObject, MIError } from "../backend"
|
||||||
import * as ChildProcess from "child_process"
|
import * as ChildProcess from "child_process"
|
||||||
import { EventEmitter } from "events"
|
import { EventEmitter } from "events"
|
||||||
import { parseMI, MINode } from '../mi_parse';
|
import { parseMI, MINode } from '../mi_parse';
|
||||||
|
|
@ -365,6 +365,12 @@ export class MI2 extends EventEmitter implements IBackend {
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
this.log("log", JSON.stringify(parsed));
|
this.log("log", JSON.stringify(parsed));
|
||||||
|
} else if (record.type == "notify") {
|
||||||
|
if (record.asyncClass == "thread-created") {
|
||||||
|
this.emit("thread-created", parsed);
|
||||||
|
} else if (record.asyncClass == "thread-exited") {
|
||||||
|
this.emit("thread-exited", parsed);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -584,39 +590,60 @@ export class MI2 extends EventEmitter implements IBackend {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getStack(maxLevels: number): Thenable<Stack[]> {
|
async getThreads(): Promise<Thread[]> {
|
||||||
if (trace)
|
if (trace) this.log("stderr", "getThreads");
|
||||||
this.log("stderr", "getStack");
|
|
||||||
return new Promise((resolve, reject) => {
|
let command = "thread-info";
|
||||||
let command = "stack-list-frames";
|
let result = await this.sendCommand(command);
|
||||||
if (maxLevels) {
|
let threads = result.result("threads");
|
||||||
command += " 0 " + maxLevels;
|
let ret: Thread[] = [];
|
||||||
|
return threads.map(element => {
|
||||||
|
let ret : Thread = {
|
||||||
|
id: parseInt(MINode.valueOf(element, "id")),
|
||||||
|
targetId: MINode.valueOf(element, "target-id")
|
||||||
|
};
|
||||||
|
|
||||||
|
let name = MINode.valueOf(element, "name");
|
||||||
|
if (name) {
|
||||||
|
ret.name = name;
|
||||||
}
|
}
|
||||||
this.sendCommand(command).then((result) => {
|
|
||||||
let stack = result.result("stack");
|
return ret;
|
||||||
let ret: Stack[] = [];
|
});
|
||||||
stack.forEach(element => {
|
}
|
||||||
let level = MINode.valueOf(element, "@frame.level");
|
|
||||||
let addr = MINode.valueOf(element, "@frame.addr");
|
async getStack(maxLevels: number, thread: number): Promise<Stack[]> {
|
||||||
let func = MINode.valueOf(element, "@frame.func");
|
if (trace) this.log("stderr", "getStack");
|
||||||
let filename = MINode.valueOf(element, "@frame.file");
|
|
||||||
let file = MINode.valueOf(element, "@frame.fullname");
|
let command = "stack-list-frames";
|
||||||
let line = 0;
|
if (thread != 0) {
|
||||||
let lnstr = MINode.valueOf(element, "@frame.line");
|
command += ` --thread ${thread}`;
|
||||||
if (lnstr)
|
}
|
||||||
line = parseInt(lnstr);
|
if (maxLevels) {
|
||||||
let from = parseInt(MINode.valueOf(element, "@frame.from"));
|
command += " 0 " + maxLevels;
|
||||||
ret.push({
|
}
|
||||||
address: addr,
|
let result = await this.sendCommand(command);
|
||||||
fileName: filename,
|
let stack = result.result("stack");
|
||||||
file: file,
|
let ret: Stack[] = [];
|
||||||
function: func || from,
|
return stack.map(element => {
|
||||||
level: level,
|
let level = MINode.valueOf(element, "@frame.level");
|
||||||
line: line
|
let addr = MINode.valueOf(element, "@frame.addr");
|
||||||
});
|
let func = MINode.valueOf(element, "@frame.func");
|
||||||
});
|
let filename = MINode.valueOf(element, "@frame.file");
|
||||||
resolve(ret);
|
let file = MINode.valueOf(element, "@frame.fullname");
|
||||||
}, reject);
|
let line = 0;
|
||||||
|
let lnstr = MINode.valueOf(element, "@frame.line");
|
||||||
|
if (lnstr)
|
||||||
|
line = parseInt(lnstr);
|
||||||
|
let from = parseInt(MINode.valueOf(element, "@frame.from"));
|
||||||
|
return {
|
||||||
|
address: addr,
|
||||||
|
fileName: filename,
|
||||||
|
file: file,
|
||||||
|
function: func || from,
|
||||||
|
level: level,
|
||||||
|
line: line
|
||||||
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -651,14 +678,17 @@ export class MI2 extends EventEmitter implements IBackend {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
evalExpression(name: string): Thenable<any> {
|
async evalExpression(name: string, thread: number, frame: number): Promise<MINode> {
|
||||||
if (trace)
|
if (trace)
|
||||||
this.log("stderr", "evalExpression");
|
this.log("stderr", "evalExpression");
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
this.sendCommand("data-evaluate-expression " + name).then((result) => {
|
let command = "data-evaluate-expression ";
|
||||||
resolve(result);
|
if (thread != 0) {
|
||||||
}, reject);
|
command += `--thread ${thread} --frame ${frame} `;
|
||||||
});
|
}
|
||||||
|
command += name;
|
||||||
|
|
||||||
|
return await this.sendCommand(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
async varCreate(expression: string, name: string = "-"): Promise<VariableObject> {
|
async varCreate(expression: string, name: string = "-"): Promise<VariableObject> {
|
||||||
|
|
@ -704,13 +734,12 @@ export class MI2 extends EventEmitter implements IBackend {
|
||||||
this.emit("msg", type, msg[msg.length - 1] == '\n' ? msg : (msg + "\n"));
|
this.emit("msg", type, msg[msg.length - 1] == '\n' ? msg : (msg + "\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
sendUserInput(command: string): Thenable<any> {
|
sendUserInput(command: string, threadId: number = 0, frameLevel: number = 0): Thenable<any> {
|
||||||
if (command.startsWith("-")) {
|
if (command.startsWith("-")) {
|
||||||
return this.sendCommand(command.substr(1));
|
return this.sendCommand(command.substr(1));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.sendRaw(command);
|
return this.sendCliCommand(command, threadId, frameLevel);
|
||||||
return Promise.resolve(undefined);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -723,6 +752,15 @@ export class MI2 extends EventEmitter implements IBackend {
|
||||||
this.process.stdin.write(raw + "\n");
|
this.process.stdin.write(raw + "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async sendCliCommand(command: string, threadId: number = 0, frameLevel: number = 0) {
|
||||||
|
let mi_command = "interpreter-exec ";
|
||||||
|
if (threadId != 0) {
|
||||||
|
mi_command += `--thread ${threadId} --frame ${frameLevel} `;
|
||||||
|
}
|
||||||
|
mi_command += `console "${command.replace(/[\\"']/g, '\\$&')}"`;
|
||||||
|
await this.sendCommand(mi_command);
|
||||||
|
}
|
||||||
|
|
||||||
sendCommand(command: string, suppressFailure: boolean = false): Thenable<MINode> {
|
sendCommand(command: string, suppressFailure: boolean = false): Thenable<MINode> {
|
||||||
let sel = this.currentToken++;
|
let sel = this.currentToken++;
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { Stack } from "../backend"
|
||||||
import { MINode } from "../mi_parse"
|
import { MINode } from "../mi_parse"
|
||||||
|
|
||||||
export class MI2_Mago extends MI2_LLDB {
|
export class MI2_Mago extends MI2_LLDB {
|
||||||
getStack(maxLevels: number): Thenable<Stack[]> {
|
getStack(maxLevels: number, thread: number): Promise<Stack[]> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let command = "stack-list-frames";
|
let command = "stack-list-frames";
|
||||||
this.sendCommand(command).then((result) => {
|
this.sendCommand(command).then((result) => {
|
||||||
|
|
|
||||||
|
|
@ -205,8 +205,10 @@ export function parseMI(output: string): MINode {
|
||||||
let oldContent = output;
|
let oldContent = output;
|
||||||
let canBeValueList = output[0] == '[';
|
let canBeValueList = output[0] == '[';
|
||||||
output = output.substr(1);
|
output = output.substr(1);
|
||||||
if (output[0] == '}' || output[0] == ']')
|
if (output[0] == '}' || output[0] == ']') {
|
||||||
|
output = output.substr(1); // ] or }
|
||||||
return [];
|
return [];
|
||||||
|
}
|
||||||
if (canBeValueList) {
|
if (canBeValueList) {
|
||||||
let value = parseValue();
|
let value = parseValue();
|
||||||
if (value) { // is value list
|
if (value) { // is value list
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ export interface AttachRequestArguments extends DebugProtocol.AttachRequestArgum
|
||||||
|
|
||||||
class MagoDebugSession extends MI2DebugSession {
|
class MagoDebugSession extends MI2DebugSession {
|
||||||
public constructor(debuggerLinesStartAt1: boolean, isServer: boolean = false) {
|
public constructor(debuggerLinesStartAt1: boolean, isServer: boolean = false) {
|
||||||
super(debuggerLinesStartAt1, isServer, 0);
|
super(debuggerLinesStartAt1, isServer);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void {
|
protected initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void {
|
||||||
|
|
|
||||||
117
src/mibase.ts
117
src/mibase.ts
|
|
@ -1,4 +1,5 @@
|
||||||
import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, OutputEvent, Thread, StackFrame, Scope, Source, Handles } from 'vscode-debugadapter';
|
import * as DebugAdapter from 'vscode-debugadapter';
|
||||||
|
import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, ThreadEvent, OutputEvent, Thread, StackFrame, Scope, Source, Handles } from 'vscode-debugadapter';
|
||||||
import { DebugProtocol } from 'vscode-debugprotocol';
|
import { DebugProtocol } from 'vscode-debugprotocol';
|
||||||
import { Breakpoint, IBackend, Variable, VariableObject, ValuesFormattingMode, MIError } from './backend/backend';
|
import { Breakpoint, IBackend, Variable, VariableObject, ValuesFormattingMode, MIError } from './backend/backend';
|
||||||
import { MINode } from './backend/mi_parse';
|
import { MINode } from './backend/mi_parse';
|
||||||
|
|
@ -19,7 +20,7 @@ class ExtendedVariable {
|
||||||
}
|
}
|
||||||
|
|
||||||
const STACK_HANDLES_START = 1000;
|
const STACK_HANDLES_START = 1000;
|
||||||
const VAR_HANDLES_START = 2000;
|
const VAR_HANDLES_START = 512 * 256 + 1000;
|
||||||
|
|
||||||
export class MI2DebugSession extends DebugSession {
|
export class MI2DebugSession extends DebugSession {
|
||||||
protected variableHandles = new Handles<string | VariableObject | ExtendedVariable>(VAR_HANDLES_START);
|
protected variableHandles = new Handles<string | VariableObject | ExtendedVariable>(VAR_HANDLES_START);
|
||||||
|
|
@ -35,12 +36,10 @@ export class MI2DebugSession extends DebugSession {
|
||||||
protected crashed: boolean;
|
protected crashed: boolean;
|
||||||
protected debugReady: boolean;
|
protected debugReady: boolean;
|
||||||
protected miDebugger: MI2;
|
protected miDebugger: MI2;
|
||||||
protected threadID: number = 1;
|
|
||||||
protected commandServer: net.Server;
|
protected commandServer: net.Server;
|
||||||
|
|
||||||
public constructor(debuggerLinesStartAt1: boolean, isServer: boolean = false, threadID: number = 1) {
|
public constructor(debuggerLinesStartAt1: boolean, isServer: boolean = false) {
|
||||||
super(debuggerLinesStartAt1, isServer);
|
super(debuggerLinesStartAt1, isServer);
|
||||||
this.threadID = threadID;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected initDebugger() {
|
protected initDebugger() {
|
||||||
|
|
@ -53,6 +52,8 @@ export class MI2DebugSession extends DebugSession {
|
||||||
this.miDebugger.on("step-end", this.handleBreak.bind(this));
|
this.miDebugger.on("step-end", this.handleBreak.bind(this));
|
||||||
this.miDebugger.on("step-out-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.miDebugger.on("signal-stop", this.handlePause.bind(this));
|
||||||
|
this.miDebugger.on("thread-created", this.threadCreatedEvent.bind(this));
|
||||||
|
this.miDebugger.on("thread-exited", this.threadExitedEvent.bind(this));
|
||||||
this.sendEvent(new InitializedEvent());
|
this.sendEvent(new InitializedEvent());
|
||||||
try {
|
try {
|
||||||
this.commandServer = net.createServer(c => {
|
this.commandServer = net.createServer(c => {
|
||||||
|
|
@ -109,22 +110,39 @@ export class MI2DebugSession extends DebugSession {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected handleBreakpoint(info: MINode) {
|
protected handleBreakpoint(info: MINode) {
|
||||||
this.sendEvent(new StoppedEvent("breakpoint", this.threadID));
|
let event = new StoppedEvent("breakpoint", parseInt(info.record("thread-id")));
|
||||||
|
(event as DebugProtocol.StoppedEvent).body.allThreadsStopped = info.record("stopped-threads") == "all";
|
||||||
|
this.sendEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected handleBreak(info: MINode) {
|
protected handleBreak(info: MINode) {
|
||||||
this.sendEvent(new StoppedEvent("step", this.threadID));
|
let event = new StoppedEvent("step", parseInt(info.record("thread-id")));
|
||||||
|
(event as DebugProtocol.StoppedEvent).body.allThreadsStopped = info.record("stopped-threads") == "all";
|
||||||
|
this.sendEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected handlePause(info: MINode) {
|
protected handlePause(info: MINode) {
|
||||||
this.sendEvent(new StoppedEvent("user request", this.threadID));
|
let event = new StoppedEvent("user request", parseInt(info.record("thread-id")));
|
||||||
|
(event as DebugProtocol.StoppedEvent).body.allThreadsStopped = info.record("stopped-threads") == "all";
|
||||||
|
this.sendEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected stopEvent(info: MINode) {
|
protected stopEvent(info: MINode) {
|
||||||
if (!this.started)
|
if (!this.started)
|
||||||
this.crashed = true;
|
this.crashed = true;
|
||||||
if (!this.quit)
|
if (!this.quit) {
|
||||||
this.sendEvent(new StoppedEvent("exception", this.threadID));
|
let event = new StoppedEvent("exception", parseInt(info.record("thread-id")));
|
||||||
|
(event as DebugProtocol.StoppedEvent).body.allThreadsStopped = info.record("stopped-threads") == "all";
|
||||||
|
this.sendEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected threadCreatedEvent(info: MINode) {
|
||||||
|
this.sendEvent(new ThreadEvent("started", info.record("id")));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected threadExitedEvent(info: MINode) {
|
||||||
|
this.sendEvent(new ThreadEvent("exited", info.record("id")));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected quitEvent() {
|
protected quitEvent() {
|
||||||
|
|
@ -211,15 +229,16 @@ export class MI2DebugSession extends DebugSession {
|
||||||
path = relative(this.trimCWD.replace(/\\/g, "/"), path.replace(/\\/g, "/"));
|
path = relative(this.trimCWD.replace(/\\/g, "/"), path.replace(/\\/g, "/"));
|
||||||
path = resolve(this.switchCWD.replace(/\\/g, "/"), path.replace(/\\/g, "/"));
|
path = resolve(this.switchCWD.replace(/\\/g, "/"), path.replace(/\\/g, "/"));
|
||||||
}
|
}
|
||||||
let all = [];
|
let all = args.breakpoints.map(brk => {
|
||||||
args.breakpoints.forEach(brk => {
|
return this.miDebugger.addBreakPoint({ file: path, line: brk.line, condition: brk.condition, countCondition: brk.hitCondition });
|
||||||
all.push(this.miDebugger.addBreakPoint({ file: path, line: brk.line, condition: brk.condition, countCondition: brk.hitCondition }));
|
|
||||||
});
|
});
|
||||||
Promise.all(all).then(brkpoints => {
|
Promise.all(all).then(brkpoints => {
|
||||||
let finalBrks = [];
|
let finalBrks = [];
|
||||||
brkpoints.forEach(brkp => {
|
brkpoints.forEach(brkp => {
|
||||||
|
// TODO: Currently all breakpoints returned are marked as verified,
|
||||||
|
// which leads to verified breakpoints on a broken lldb.
|
||||||
if (brkp[0])
|
if (brkp[0])
|
||||||
finalBrks.push({ line: brkp[1].line });
|
finalBrks.push(new DebugAdapter.Breakpoint(true, brkp[1].line));
|
||||||
});
|
});
|
||||||
response.body = {
|
response.body = {
|
||||||
breakpoints: finalBrks
|
breakpoints: finalBrks
|
||||||
|
|
@ -239,18 +258,43 @@ export class MI2DebugSession extends DebugSession {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected threadsRequest(response: DebugProtocol.ThreadsResponse): void {
|
protected threadsRequest(response: DebugProtocol.ThreadsResponse): void {
|
||||||
response.body = {
|
if (!this.miDebugger) {
|
||||||
threads: [
|
this.sendResponse(response);
|
||||||
new Thread(this.threadID, "Thread 1")
|
return;
|
||||||
]
|
}
|
||||||
};
|
this.miDebugger.getThreads().then(
|
||||||
this.sendResponse(response);
|
threads => {
|
||||||
|
response.body = {
|
||||||
|
threads: []
|
||||||
|
};
|
||||||
|
for (const thread of threads) {
|
||||||
|
let threadName = thread.name;
|
||||||
|
// TODO: Thread names are undefined on LLDB
|
||||||
|
if (threadName === undefined) {
|
||||||
|
threadName = thread.targetId;
|
||||||
|
}
|
||||||
|
if (threadName === undefined) {
|
||||||
|
threadName = "<unnamed>";
|
||||||
|
}
|
||||||
|
response.body.threads.push(new Thread(thread.id, thread.id + ":" + threadName));
|
||||||
|
}
|
||||||
|
this.sendResponse(response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Supports 256 threads.
|
||||||
|
protected threadAndLevelToFrameId(threadId: number, level: number) {
|
||||||
|
return level << 8 | threadId;
|
||||||
|
}
|
||||||
|
protected frameIdToThreadAndLevel(frameId: number) {
|
||||||
|
return [frameId & 0xff, frameId >> 8];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void {
|
protected stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void {
|
||||||
this.miDebugger.getStack(args.levels).then(stack => {
|
this.miDebugger.getStack(args.levels, args.threadId).then(stack => {
|
||||||
let ret: StackFrame[] = [];
|
let ret: StackFrame[] = [];
|
||||||
stack.forEach(element => {
|
stack.forEach(element => {
|
||||||
|
let source = null;
|
||||||
let file = element.file;
|
let file = element.file;
|
||||||
if (file) {
|
if (file) {
|
||||||
if (this.isSSH) {
|
if (this.isSSH) {
|
||||||
|
|
@ -262,10 +306,15 @@ export class MI2DebugSession extends DebugSession {
|
||||||
file = file[10] + ":" + file.substr(11); // replaces /cygdrive/c/foo/bar.txt with c:/foo/bar.txt
|
file = file[10] + ":" + file.substr(11); // replaces /cygdrive/c/foo/bar.txt with c:/foo/bar.txt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ret.push(new StackFrame(element.level, element.function + "@" + element.address, new Source(element.fileName, file), element.line, 0));
|
source = new Source(element.fileName, file);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
ret.push(new StackFrame(element.level, element.function + "@" + element.address, null, element.line, 0));
|
ret.push(new StackFrame(
|
||||||
|
this.threadAndLevelToFrameId(args.threadId, element.level),
|
||||||
|
element.function + "@" + element.address,
|
||||||
|
source,
|
||||||
|
element.line,
|
||||||
|
0));
|
||||||
});
|
});
|
||||||
response.body = {
|
response.body = {
|
||||||
stackFrames: ret
|
stackFrames: ret
|
||||||
|
|
@ -331,11 +380,12 @@ export class MI2DebugSession extends DebugSession {
|
||||||
if (typeof id == "number") {
|
if (typeof id == "number") {
|
||||||
let stack: Variable[];
|
let stack: Variable[];
|
||||||
try {
|
try {
|
||||||
stack = await this.miDebugger.getStackVariables(this.threadID, id);
|
let [threadId, level] = this.frameIdToThreadAndLevel(id);
|
||||||
|
stack = await this.miDebugger.getStackVariables(threadId, level);
|
||||||
for (const variable of stack) {
|
for (const variable of stack) {
|
||||||
if (this.useVarObjects) {
|
if (this.useVarObjects) {
|
||||||
try {
|
try {
|
||||||
let varObjName = `var_${variable.name}`;
|
let varObjName = `var_${id}_${variable.name}`;
|
||||||
let varObj: VariableObject;
|
let varObj: VariableObject;
|
||||||
try {
|
try {
|
||||||
const changes = await this.miDebugger.varUpdate(varObjName);
|
const changes = await this.miDebugger.varUpdate(varObjName);
|
||||||
|
|
@ -406,7 +456,8 @@ export class MI2DebugSession extends DebugSession {
|
||||||
// Variable members
|
// Variable members
|
||||||
let variable;
|
let variable;
|
||||||
try {
|
try {
|
||||||
variable = await this.miDebugger.evalExpression(JSON.stringify(id));
|
// TODO: this evals on an (effectively) unknown thread for multithreaded programs.
|
||||||
|
variable = await this.miDebugger.evalExpression(JSON.stringify(id), 0, 0);
|
||||||
try {
|
try {
|
||||||
let expanded = expandValue(createVariable, variable.result("value"), id, variable);
|
let expanded = expandValue(createVariable, variable.result("value"), id, variable);
|
||||||
if (!expanded) {
|
if (!expanded) {
|
||||||
|
|
@ -469,7 +520,8 @@ export class MI2DebugSession extends DebugSession {
|
||||||
this.sendResponse(response);
|
this.sendResponse(response);
|
||||||
};
|
};
|
||||||
let addOne = async () => {
|
let addOne = async () => {
|
||||||
const variable = await this.miDebugger.evalExpression(JSON.stringify(`${varReq.name}+${arrIndex})`));
|
// TODO: this evals on an (effectively) unknown thread for multithreaded programs.
|
||||||
|
const variable = await this.miDebugger.evalExpression(JSON.stringify(`${varReq.name}+${arrIndex})`), 0, 0);
|
||||||
try {
|
try {
|
||||||
let expanded = expandValue(createVariable, variable.result("value"), varReq.name, variable);
|
let expanded = expandValue(createVariable, variable.result("value"), varReq.name, variable);
|
||||||
if (!expanded) {
|
if (!expanded) {
|
||||||
|
|
@ -589,8 +641,9 @@ export class MI2DebugSession extends DebugSession {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): void {
|
protected evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): void {
|
||||||
if (args.context == "watch" || args.context == "hover")
|
let [threadId, level] = this.frameIdToThreadAndLevel(args.frameId);
|
||||||
this.miDebugger.evalExpression(args.expression).then((res) => {
|
if (args.context == "watch" || args.context == "hover") {
|
||||||
|
this.miDebugger.evalExpression(args.expression, threadId, level).then((res) => {
|
||||||
response.body = {
|
response.body = {
|
||||||
variablesReference: 0,
|
variablesReference: 0,
|
||||||
result: res.result("value")
|
result: res.result("value")
|
||||||
|
|
@ -599,8 +652,8 @@ export class MI2DebugSession extends DebugSession {
|
||||||
}, msg => {
|
}, msg => {
|
||||||
this.sendErrorResponse(response, 7, msg.toString());
|
this.sendErrorResponse(response, 7, msg.toString());
|
||||||
});
|
});
|
||||||
else {
|
} else {
|
||||||
this.miDebugger.sendUserInput(args.expression).then(output => {
|
this.miDebugger.sendUserInput(args.expression, threadId, level).then(output => {
|
||||||
if (typeof output == "undefined")
|
if (typeof output == "undefined")
|
||||||
response.body = {
|
response.body = {
|
||||||
result: "",
|
result: "",
|
||||||
|
|
|
||||||
|
|
@ -178,4 +178,14 @@ suite("MI Parse", () => {
|
||||||
let result = parsed.result('register-names');
|
let result = parsed.result('register-names');
|
||||||
assert.deepEqual(result, ["r0", "pc", "", "xpsr", "", "control"]);
|
assert.deepEqual(result, ["r0", "pc", "", "xpsr", "", "control"]);
|
||||||
});
|
});
|
||||||
|
test("empty array values", () => {
|
||||||
|
let parsed = parseMI(`15^done,foo={x=[],y="y"}`);
|
||||||
|
assert.deepEqual(parsed.result('foo.x'), []);
|
||||||
|
assert.equal(parsed.result('foo.y'), "y");
|
||||||
|
});
|
||||||
|
test("empty object values", () => {
|
||||||
|
let parsed = parseMI(`15^done,foo={x={},y="y"}`);
|
||||||
|
assert.deepEqual(parsed.result('foo.x'), {});
|
||||||
|
assert.equal(parsed.result('foo.y'), "y");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
Loading…
Add table
Add a link
Reference in a new issue