Skip to content

Support multi-threading #129

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Feb 21, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions src/backend/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ export interface Breakpoint {
countCondition?: string;
}

export interface Thread {
id: number;
targetId: string;
name?: string;
}

export interface Stack {
level: number;
address: string;
Expand Down Expand Up @@ -58,9 +64,10 @@ export interface IBackend {
addBreakPoint(breakpoint: Breakpoint): Thenable<[boolean, Breakpoint]>;
removeBreakPoint(breakpoint: Breakpoint): Thenable<boolean>;
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[]>;
evalExpression(name: string): Thenable<any>;
evalExpression(name: string, thread: number, frame: number): Thenable<any>;
isReady(): boolean;
changeVariable(name: string, rawValue: string): Thenable<any>;
examineMemory(from: number, to: number): Thenable<any>;
Expand Down Expand Up @@ -114,12 +121,8 @@ export class VariableObject {
evaluateName: this.name,
value: (this.value === void 0) ? "<unknown>" : this.value,
type: this.type,
// kind: this.displayhint,
variablesReference: this.id
};
if (this.displayhint) {
res.kind = this.displayhint;
}
return res;
}
}
Expand Down
19 changes: 13 additions & 6 deletions src/backend/gdb_expansion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const variableRegex = /^[a-zA-Z_\-][a-zA-Z0-9_\-]*/;
const errorRegex = /^\<.+?\>/;
const referenceStringRegex = /^(0x[0-9a-fA-F]+\s*)"/;
const referenceRegex = /^0x[0-9a-fA-F]+/;
const cppReferenceRegex = /^@0x[0-9a-fA-F]+/;
const nullpointerRegex = /^0x0+\b/;
const charRegex = /^(\d+) ['"]/;
const numberRegex = /^\d+(\.\d+)?/;
Expand Down Expand Up @@ -168,6 +169,10 @@ export function expandValue(variableCreate: Function, value: string, root: strin
primitive = "*" + match[0];
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)) {
primitive = match[1];
value = value.substr(match[0].length - 1);
Expand Down Expand Up @@ -222,19 +227,21 @@ export function expandValue(variableCreate: Function, value: string, root: strin
ref = variableCreate(val);
val = "Object";
}
if (typeof val == "string" && val.startsWith("*0x")) {
if (extra && MINode.valueOf(extra, "arg") == "1")
{
else if (typeof val == "string" && val.startsWith("*0x")) {
if (extra && MINode.valueOf(extra, "arg") == "1") {
ref = variableCreate(getNamespace("*(" + name), { arg: true });
val = "<args>";
}
else
{
else {
ref = variableCreate(getNamespace("*" + name));
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));
val = "...";
}
Expand Down
122 changes: 80 additions & 42 deletions src/backend/mi2/mi2.ts
Original file line number Diff line number Diff line change
@@ -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 { EventEmitter } from "events"
import { parseMI, MINode } from '../mi_parse';
Expand Down Expand Up @@ -365,6 +365,12 @@ export class MI2 extends EventEmitter implements IBackend {
}
} else
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);
}
}
}
});
Expand Down Expand Up @@ -584,39 +590,60 @@ export class MI2 extends EventEmitter implements IBackend {
});
}

getStack(maxLevels: number): Thenable<Stack[]> {
if (trace)
this.log("stderr", "getStack");
return new Promise((resolve, reject) => {
let command = "stack-list-frames";
if (maxLevels) {
command += " 0 " + maxLevels;
async getThreads(): Promise<Thread[]> {
if (trace) this.log("stderr", "getThreads");

let command = "thread-info";
let result = await this.sendCommand(command);
let threads = result.result("threads");
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");
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 = 0;
let lnstr = MINode.valueOf(element, "@frame.line");
if (lnstr)
line = parseInt(lnstr);
let from = parseInt(MINode.valueOf(element, "@frame.from"));
ret.push({
address: addr,
fileName: filename,
file: file,
function: func || from,
level: level,
line: line
});
});
resolve(ret);
}, reject);

return ret;
});
}

async getStack(maxLevels: number, thread: number): Promise<Stack[]> {
if (trace) this.log("stderr", "getStack");

let command = "stack-list-frames";
if (thread != 0) {
command += ` --thread ${thread}`;
}
if (maxLevels) {
command += " 0 " + maxLevels;
}
let result = await this.sendCommand(command);
let stack = result.result("stack");
let ret: Stack[] = [];
return stack.map(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 = 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
};
});
}

Expand Down Expand Up @@ -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)
this.log("stderr", "evalExpression");
return new Promise((resolve, reject) => {
this.sendCommand("data-evaluate-expression " + name).then((result) => {
resolve(result);
}, reject);
});

let command = "data-evaluate-expression ";
if (thread != 0) {
command += `--thread ${thread} --frame ${frame} `;
}
command += name;

return await this.sendCommand(command);
}

async varCreate(expression: string, name: string = "-"): Promise<VariableObject> {
Expand Down Expand Up @@ -704,13 +734,12 @@ export class MI2 extends EventEmitter implements IBackend {
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("-")) {
return this.sendCommand(command.substr(1));
}
else {
this.sendRaw(command);
return Promise.resolve(undefined);
return this.sendCliCommand(command, threadId, frameLevel);
}
}

Expand All @@ -723,6 +752,15 @@ export class MI2 extends EventEmitter implements IBackend {
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> {
let sel = this.currentToken++;
return new Promise((resolve, reject) => {
Expand Down
2 changes: 1 addition & 1 deletion src/backend/mi2/mi2mago.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Stack } from "../backend"
import { MINode } from "../mi_parse"

export class MI2_Mago extends MI2_LLDB {
getStack(maxLevels: number): Thenable<Stack[]> {
getStack(maxLevels: number, thread: number): Promise<Stack[]> {
return new Promise((resolve, reject) => {
let command = "stack-list-frames";
this.sendCommand(command).then((result) => {
Expand Down
4 changes: 3 additions & 1 deletion src/backend/mi_parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,10 @@ export function parseMI(output: string): MINode {
let oldContent = output;
let canBeValueList = output[0] == '[';
output = output.substr(1);
if (output[0] == '}' || output[0] == ']')
if (output[0] == '}' || output[0] == ']') {
output = output.substr(1); // ] or }
return [];
}
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uh any reason for this change? Could you include some test what this is supposed to fix or what didn't work in the tests file please

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't remember the exact command that caused it, but it was to do with an mi command returning {} followed by other data, where the other data was getting lost

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added an (artificial) test, which I can confirm did fail before this patch.

if (canBeValueList) {
let value = parseValue();
if (value) { // is value list
Expand Down
2 changes: 1 addition & 1 deletion src/mago.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export interface AttachRequestArguments extends DebugProtocol.AttachRequestArgum

class MagoDebugSession extends MI2DebugSession {
public constructor(debuggerLinesStartAt1: boolean, isServer: boolean = false) {
super(debuggerLinesStartAt1, isServer, 0);
super(debuggerLinesStartAt1, isServer);
}

protected initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void {
Expand Down
Loading