From 6713545970479d86070595b903c3206f0f44aca4 Mon Sep 17 00:00:00 2001 From: gentoo90 Date: Sat, 20 May 2017 11:22:25 +0300 Subject: [PATCH 1/7] Use async/await in getStackVariables --- src/backend/mi2/mi2.ts | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/src/backend/mi2/mi2.ts b/src/backend/mi2/mi2.ts index 84e562af..9caf3d5b 100644 --- a/src/backend/mi2/mi2.ts +++ b/src/backend/mi2/mi2.ts @@ -617,27 +617,25 @@ export class MI2 extends EventEmitter implements IBackend { }); } - getStackVariables(thread: number, frame: number): Thenable { + async getStackVariables(thread: number, frame: number): Promise { if (trace) this.log("stderr", "getStackVariables"); - 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: Variable[] = []; - variables.forEach(element => { - const key = MINode.valueOf(element, "name"); - const value = MINode.valueOf(element, "value"); - const type = MINode.valueOf(element, "type"); - ret.push({ - name: key, - valueStr: value, - type: type, - raw: element - }); - }); - resolve(ret); - }, reject); - }); + + const result = await this.sendCommand(`stack-list-variables --thread ${thread} --frame ${frame} --simple-values`); + const variables = result.result("variables"); + let ret: Variable[] = []; + for (const element of variables) { + const key = MINode.valueOf(element, "name"); + const value = MINode.valueOf(element, "value"); + const type = MINode.valueOf(element, "type"); + ret.push({ + name: key, + valueStr: value, + type: type, + raw: element + }); + } + return ret; } examineMemory(from: number, length: number): Thenable { From 18f1a101c8ee39417123bd18b7166373bdcf1cfb Mon Sep 17 00:00:00 2001 From: gentoo90 Date: Sat, 20 May 2017 11:33:47 +0300 Subject: [PATCH 2/7] Use async/await in variablesRequest --- src/mibase.ts | 103 ++++++++++++++++++++++++++------------------------ 1 file changed, 54 insertions(+), 49 deletions(-) diff --git a/src/mibase.ts b/src/mibase.ts index 03819ea4..960412f7 100644 --- a/src/mibase.ts +++ b/src/mibase.ts @@ -1,6 +1,6 @@ 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 { Breakpoint, IBackend, Variable } from './backend/backend'; import { MINode } from './backend/mi_parse'; import { expandValue, isExpandable } from './backend/gdb_expansion'; import { MI2 } from './backend/mi2/mi2'; @@ -261,7 +261,7 @@ export class MI2DebugSession extends DebugSession { this.sendResponse(response); } - protected variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): void { + protected async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): Promise { const variables: DebugProtocol.Variable[] = []; const id = this.variableHandles.get(args.variablesReference); @@ -274,10 +274,12 @@ export class MI2DebugSession extends DebugSession { if (typeof id == "string") { if (id.startsWith("@frame:")) { - this.miDebugger.getStackVariables(this.threadID, parseInt(id.substr("@frame:".length))).then(stack => { - stack.forEach(variable => { + let stack: Variable[]; + try { + stack = await this.miDebugger.getStackVariables(this.threadID, parseInt(id.substr("@frame:".length))); + for (const variable of stack) { if (variable.valueStr !== undefined) { - let expanded = expandValue(createVariable, "{" + variable.name + "=" + variable.valueStr + ")", "", variable.raw); + let expanded = expandValue(createVariable, `{${variable.name}=${variable.valueStr})`, "", variable.raw); if (expanded) { if (typeof expanded[0] == "string") expanded = [ @@ -296,18 +298,21 @@ export class MI2DebugSession extends DebugSession { value: "", variablesReference: createVariable(variable.name) }); - }); + } response.body = { variables: variables }; this.sendResponse(response); - }, err => { - this.sendErrorResponse(response, 1, "Could not expand variable: " + err); - }); + } + catch (err) { + this.sendErrorResponse(response, 1, `Could not expand variable: ${err}`); + } } else { // Variable members - this.miDebugger.evalExpression(JSON.stringify(id)).then(variable => { + let variable; + try { + variable = await this.miDebugger.evalExpression(JSON.stringify(id)); try { let expanded = expandValue(createVariable, variable.result("value"), id, variable); if (!expanded) { @@ -329,11 +334,12 @@ export class MI2DebugSession extends DebugSession { } } catch (e) { - this.sendErrorResponse(response, 2, `Could not expand variable: ` + e); + this.sendErrorResponse(response, 2, `Could not expand variable: ${e}`); } - }, err => { - this.sendErrorResponse(response, 1, `Could not expand variable`); - }); + } + catch (err) { + this.sendErrorResponse(response, 1, `Could not expand variable: ${err}`); + } } } else if (typeof id == "object") { @@ -349,55 +355,54 @@ export class MI2DebugSession extends DebugSession { }; this.sendResponse(response); }; - let addOne = () => { - this.miDebugger.evalExpression(JSON.stringify(varReq.name + "+" + arrIndex + ")")).then(variable => { - try { - let expanded = expandValue(createVariable, variable.result("value"), varReq.name, variable); - if (!expanded) { - this.sendErrorResponse(response, 15, `Could not expand variable`); - } - else { - if (typeof expanded == "string") { - if (expanded == "") { - if (argsPart) - argsPart = false; - else - return submit(); - } - else if (expanded[0] != '"') { - strArr.push({ - name: "[err]", - value: expanded, - variablesReference: 0 - }); + let addOne = async () => { + const variable = await this.miDebugger.evalExpression(JSON.stringify(`${varReq.name}+${arrIndex})`)); + try { + let expanded = expandValue(createVariable, variable.result("value"), varReq.name, variable); + if (!expanded) { + this.sendErrorResponse(response, 15, `Could not expand variable`); + } + else { + if (typeof expanded == "string") { + if (expanded == "") { + if (argsPart) + argsPart = false; + else return submit(); - } - strArr.push({ - name: "[" + (arrIndex++) + "]", - value: expanded, - variablesReference: 0 - }); - addOne(); } - else { + else if (expanded[0] != '"') { strArr.push({ name: "[err]", value: expanded, variablesReference: 0 }); - submit(); + return submit(); } + strArr.push({ + name: `[${(arrIndex++)}]`, + value: expanded, + variablesReference: 0 + }); + addOne(); + } + else { + strArr.push({ + name: "[err]", + value: expanded, + variablesReference: 0 + }); + submit(); } } - catch (e) { - this.sendErrorResponse(response, 14, `Could not expand variable: ` + e); - } - }); + } + catch (e) { + this.sendErrorResponse(response, 14, `Could not expand variable: ${e}`); + } }; addOne(); } else - this.sendErrorResponse(response, 13, `Unimplemented variable request options: ` + JSON.stringify(varReq.options)); + this.sendErrorResponse(response, 13, `Unimplemented variable request options: ${JSON.stringify(varReq.options)}`); } else { response.body = { From f171d9f4acaf130991d3614d60d04a158760ec06 Mon Sep 17 00:00:00 2001 From: gentoo90 Date: Sat, 20 May 2017 19:19:15 +0300 Subject: [PATCH 3/7] Utilize GDB/MI Variable Objects for local variables Replace own value parser with GDB's variable objects interface. See https://sourceware.org/gdb/onlinedocs/gdb/GDB_002fMI-Variable-Objects.html --- src/backend/mi2/mi2.ts | 16 ++++++++ src/mibase.ts | 87 ++++++++++++++++++++++-------------------- 2 files changed, 61 insertions(+), 42 deletions(-) diff --git a/src/backend/mi2/mi2.ts b/src/backend/mi2/mi2.ts index 9caf3d5b..f9b0a263 100644 --- a/src/backend/mi2/mi2.ts +++ b/src/backend/mi2/mi2.ts @@ -196,6 +196,9 @@ export class MI2 extends EventEmitter implements IBackend { ]; if (!attach) cmds.push(this.sendCommand("file-exec-and-symbols \"" + escape(target) + "\"")); + + // TODO: add extension parameter for enabling/disabling pretty printers + cmds.push(this.sendCommand("enable-pretty-printing")); return cmds; } @@ -658,6 +661,19 @@ export class MI2 extends EventEmitter implements IBackend { }); } + async varCreate(expression: string): Promise { + if (trace) + this.log("stderr", "varCreate"); + return this.sendCommand(`var-create - * "${expression}"`); + } + + async varListChildren(name: string): Promise { + if (trace) + this.log("stderr", "varListChildren"); + //TODO: add `from` and `to` arguments + return this.sendCommand(`var-list-children --simple-values ${name}`); + } + logNoNewLine(type: string, msg: string) { this.emit("msg", type, msg); } diff --git a/src/mibase.ts b/src/mibase.ts index 960412f7..98b4e082 100644 --- a/src/mibase.ts +++ b/src/mibase.ts @@ -272,32 +272,49 @@ export class MI2DebugSession extends DebugSession { return this.variableHandles.create(arg); }; + let miVarObjToVariable = (varObj: any): DebugProtocol.Variable => { + const evaluateName = MINode.valueOf(varObj, "name"); + const value = MINode.valueOf(varObj, "value"); + const numChild = parseInt(MINode.valueOf(varObj, "numchild")); + const dynamic = MINode.valueOf(varObj, "dynamic") || 0; + let displayHint, hasMore; + if (dynamic) { + displayHint = MINode.valueOf(varObj, "displayhint"); + hasMore = parseInt(MINode.valueOf(varObj, "has_more")); + } + const isCompound = numChild > 0 || + value === "{...}" || + (dynamic > 0 && (displayHint === "array" || displayHint === "map")); + + let res = { + name: MINode.valueOf(varObj, "exp"), + evaluateName, + type: MINode.valueOf(varObj, "type"), + value: value || "", + variablesReference: isCompound ? createVariable(evaluateName) : 0 + } as DebugProtocol.Variable; + return res; + }; + if (typeof id == "string") { if (id.startsWith("@frame:")) { let stack: Variable[]; try { stack = await this.miDebugger.getStackVariables(this.threadID, parseInt(id.substr("@frame:".length))); for (const variable of stack) { - if (variable.valueStr !== undefined) { - let expanded = expandValue(createVariable, `{${variable.name}=${variable.valueStr})`, "", variable.raw); - if (expanded) { - if (typeof expanded[0] == "string") - expanded = [ - { - name: "", - value: prettyStringArray(expanded), - variablesReference: 0 - } - ]; - variables.push(expanded[0]); - } - } else + try { + const varObj = await this.miDebugger.varCreate(variable.name); + let v = miVarObjToVariable(varObj.resultRecords.results); + v.name = variable.name; + variables.push(v); + } + catch (err) { variables.push({ name: variable.name, - type: variable.type, - value: "", - variablesReference: createVariable(variable.name) + value: err, + variablesReference: 0 }); + } } response.body = { variables: variables @@ -310,32 +327,18 @@ export class MI2DebugSession extends DebugSession { } else { // Variable members - let variable; + let listChildren; try { - variable = await this.miDebugger.evalExpression(JSON.stringify(id)); - try { - let expanded = expandValue(createVariable, variable.result("value"), id, variable); - if (!expanded) { - this.sendErrorResponse(response, 2, `Could not expand variable`); - } - else { - if (typeof expanded[0] == "string") - expanded = [ - { - name: "", - value: prettyStringArray(expanded), - variablesReference: 0 - } - ]; - response.body = { - variables: expanded - }; - this.sendResponse(response); - } - } - catch (e) { - this.sendErrorResponse(response, 2, `Could not expand variable: ${e}`); + listChildren = await this.miDebugger.varListChildren(id); + const children: any[] = listChildren.result("children"); + // TODO: use hasMore when it's > 0 + // const hasMore = parseInt(listChildren.result("has_more")); + const vars = children.map(child => miVarObjToVariable(child[1])); + + response.body = { + variables: vars } + this.sendResponse(response); } catch (err) { this.sendErrorResponse(response, 1, `Could not expand variable: ${err}`); @@ -514,4 +517,4 @@ function prettyStringArray(strings) { return JSON.stringify(strings); } else return strings; -} \ No newline at end of file +} From 77b689443fa8c339ae7d4440cdbefe4d900e7f7a Mon Sep 17 00:00:00 2001 From: gentoo90 Date: Thu, 25 May 2017 23:02:26 +0300 Subject: [PATCH 4/7] Reuse ids of stask frames, add some type inference Use GDB's stask frames number as its id in vscode --- src/mibase.ts | 99 +++++++++++++++++++++++++++------------------------ 1 file changed, 53 insertions(+), 46 deletions(-) diff --git a/src/mibase.ts b/src/mibase.ts index 98b4e082..e9500fb7 100644 --- a/src/mibase.ts +++ b/src/mibase.ts @@ -18,8 +18,11 @@ class ExtendedVariable { } } +const STACK_HANDLES_START = 1000; +const VAR_HANDLES_START = 2000; + export class MI2DebugSession extends DebugSession { - protected variableHandles = new Handles(); + protected variableHandles = new Handles(VAR_HANDLES_START); protected quit: boolean; protected attached: boolean; protected needContinue: boolean; @@ -253,7 +256,7 @@ export class MI2DebugSession extends DebugSession { protected scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void { const scopes = new Array(); - scopes.push(new Scope("Local", this.variableHandles.create("@frame:" + (args.frameId || 0)), false)); + scopes.push(new Scope("Local", STACK_HANDLES_START + (parseInt(args.frameId as any) || 0), false)); response.body = { scopes: scopes @@ -263,7 +266,13 @@ export class MI2DebugSession extends DebugSession { protected async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): Promise { const variables: DebugProtocol.Variable[] = []; - const id = this.variableHandles.get(args.variablesReference); + let id: number | string | ExtendedVariable; + if (args.variablesReference < VAR_HANDLES_START) { + id = args.variablesReference - STACK_HANDLES_START; + } + else { + id = this.variableHandles.get(args.variablesReference); + } let createVariable = (arg, options?) => { if (options) @@ -296,58 +305,56 @@ export class MI2DebugSession extends DebugSession { return res; }; - if (typeof id == "string") { - if (id.startsWith("@frame:")) { - let stack: Variable[]; - try { - stack = await this.miDebugger.getStackVariables(this.threadID, parseInt(id.substr("@frame:".length))); - for (const variable of stack) { - try { - const varObj = await this.miDebugger.varCreate(variable.name); - let v = miVarObjToVariable(varObj.resultRecords.results); - v.name = variable.name; - variables.push(v); - } - catch (err) { - variables.push({ - name: variable.name, - value: err, - variablesReference: 0 - }); - } + if (typeof id == "number") { + let stack: Variable[]; + try { + stack = await this.miDebugger.getStackVariables(this.threadID, id); + for (const variable of stack) { + try { + const varObj = await this.miDebugger.varCreate(variable.name); + let v = miVarObjToVariable(varObj.resultRecords.results); + v.name = variable.name; + variables.push(v); + } + catch (err) { + variables.push({ + name: variable.name, + value: `<${err}>`, + variablesReference: 0 + }); } - response.body = { - variables: variables - }; - this.sendResponse(response); - } - catch (err) { - this.sendErrorResponse(response, 1, `Could not expand variable: ${err}`); } + response.body = { + variables: variables + }; + this.sendResponse(response); } - else { - // Variable members - let listChildren; - try { - listChildren = await this.miDebugger.varListChildren(id); - const children: any[] = listChildren.result("children"); - // TODO: use hasMore when it's > 0 - // const hasMore = parseInt(listChildren.result("has_more")); - const vars = children.map(child => miVarObjToVariable(child[1])); + catch (err) { + this.sendErrorResponse(response, 1, `Could not expand variable: ${err}`); + } + } + else if (typeof id == "string") { + // Variable members + let listChildren; + try { + listChildren = await this.miDebugger.varListChildren(id); + const children: any[] = listChildren.result("children"); + // TODO: use hasMore when it's > 0 + // const hasMore = parseInt(listChildren.result("has_more")); + const vars = children.map(child => miVarObjToVariable(child[1])); - response.body = { - variables: vars - } - this.sendResponse(response); - } - catch (err) { - this.sendErrorResponse(response, 1, `Could not expand variable: ${err}`); + response.body = { + variables: vars } + this.sendResponse(response); + } + catch (err) { + this.sendErrorResponse(response, 1, `Could not expand variable: ${err}`); } } else if (typeof id == "object") { if (id instanceof ExtendedVariable) { - let varReq = id; + let varReq = id; if (varReq.options.arg) { let strArr = []; let argsPart = true; From 2551dba72531d089235af0f58ad9fbd9e1537b13 Mon Sep 17 00:00:00 2001 From: gentoo90 Date: Thu, 25 May 2017 23:49:19 +0300 Subject: [PATCH 5/7] Reuse variable objects, utilize var-update Same variables now have same ids in each VariablesResponse. This allows vscode to keep track of changes properly and prevents collapsing of all variables after every step. --- src/backend/backend.ts | 63 ++++++++++++++++++++++++++- src/backend/mi2/mi2.ts | 26 ++++++++--- src/mibase.ts | 98 ++++++++++++++++++++++-------------------- 3 files changed, 135 insertions(+), 52 deletions(-) diff --git a/src/backend/backend.ts b/src/backend/backend.ts index d2738a70..ada42320 100644 --- a/src/backend/backend.ts +++ b/src/backend/backend.ts @@ -1,3 +1,6 @@ +import { MINode } from "./mi_parse"; +import { DebugProtocol } from "vscode-debugprotocol/lib/debugProtocol"; + export interface Breakpoint { file?: string; line?: number; @@ -59,4 +62,62 @@ export interface IBackend { isReady(): boolean; changeVariable(name: string, rawValue: string): Thenable; examineMemory(from: number, to: number): Thenable; -} \ No newline at end of file +} + +export class VariableObject { + name: string; + exp: string; + numchild: number; + type: string; + value: string; + threadId: string; + frozen: boolean; + dynamic: boolean; + displayhint: string; + has_more: boolean; + id: number; + constructor(node: any) { + this.name = MINode.valueOf(node, "name"); + this.exp = MINode.valueOf(node, "exp"); + this.numchild = parseInt(MINode.valueOf(node, "numchild")); + this.type = MINode.valueOf(node, "type"); + this.value = MINode.valueOf(node, "value"); + this.threadId = MINode.valueOf(node, "thread-id"); + this.frozen = !!MINode.valueOf(node, "frozen"); + this.dynamic = !!MINode.valueOf(node, "dynamic"); + this.displayhint = MINode.valueOf(node, "displayhint"); + // TODO: use has_more when it's > 0 + this.has_more = !!MINode.valueOf(node, "has_more"); + } + + public applyChanges(node: MINode) { + this.value = MINode.valueOf(node, "value"); + if (!!MINode.valueOf(node, "type_changed")) { + this.type = MINode.valueOf(node, "new_type"); + } + this.dynamic = !!MINode.valueOf(node, "dynamic"); + this.displayhint = MINode.valueOf(node, "displayhint"); + this.has_more = !!MINode.valueOf(node, "has_more"); + } + + public isCompound(): boolean { + return this.numchild > 0 || + this.value === "{...}" || + (this.dynamic && (this.displayhint === "array" || this.displayhint === "map")); + } + + public toProtocolVariable(): DebugProtocol.Variable { + let res: DebugProtocol.Variable = { + name: this.exp, + evaluateName: this.name, + value: (this.value === void 0) ? "" : this.value, + type: this.type, + // kind: this.displayhint, + variablesReference: this.id + }; + if (this.displayhint) { + res.kind = this.displayhint; + } + return res; + } +} diff --git a/src/backend/mi2/mi2.ts b/src/backend/mi2/mi2.ts index f9b0a263..0bcc6bd2 100644 --- a/src/backend/mi2/mi2.ts +++ b/src/backend/mi2/mi2.ts @@ -1,4 +1,4 @@ -import { Breakpoint, IBackend, Stack, SSHArguments, Variable } from "../backend" +import { Breakpoint, IBackend, Stack, SSHArguments, Variable, VariableObject } from "../backend" import * as ChildProcess from "child_process" import { EventEmitter } from "events" import { parseMI, MINode } from '../mi_parse'; @@ -661,17 +661,33 @@ export class MI2 extends EventEmitter implements IBackend { }); } - async varCreate(expression: string): Promise { + async varCreate(expression: string, name: string = "-"): Promise { if (trace) this.log("stderr", "varCreate"); - return this.sendCommand(`var-create - * "${expression}"`); + const res = await this.sendCommand(`var-create ${name} @ "${expression}"`); + return new VariableObject(res.result("")); } - async varListChildren(name: string): Promise { + async varEvalExpression(name: string): Promise < MINode > { + if (trace) + this.log("stderr", "varEvalExpression"); + return this.sendCommand(`var-evaluate-expression ${name}`); + } + + async varListChildren(name: string): Promise { if (trace) this.log("stderr", "varListChildren"); //TODO: add `from` and `to` arguments - return this.sendCommand(`var-list-children --simple-values ${name}`); + const res = await this.sendCommand(`var-list-children --all-values ${name}`); + const children = res.result("children"); + let omg: VariableObject[] = children.map(child => new VariableObject(child[1])); + return omg; + } + + async varUpdate(name: string = "*"): Promise { + if (trace) + this.log("stderr", "varUpdate"); + return this.sendCommand(`var-update --all-values ${name}`) } logNoNewLine(type: string, msg: string) { diff --git a/src/mibase.ts b/src/mibase.ts index e9500fb7..16785e23 100644 --- a/src/mibase.ts +++ b/src/mibase.ts @@ -1,6 +1,6 @@ import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, OutputEvent, Thread, StackFrame, Scope, Source, Handles } from 'vscode-debugadapter'; import { DebugProtocol } from 'vscode-debugprotocol'; -import { Breakpoint, IBackend, Variable } from './backend/backend'; +import { Breakpoint, IBackend, Variable, VariableObject } from './backend/backend'; import { MINode } from './backend/mi_parse'; import { expandValue, isExpandable } from './backend/gdb_expansion'; import { MI2 } from './backend/mi2/mi2'; @@ -22,7 +22,8 @@ const STACK_HANDLES_START = 1000; const VAR_HANDLES_START = 2000; export class MI2DebugSession extends DebugSession { - protected variableHandles = new Handles(VAR_HANDLES_START); + protected variableHandles = new Handles(VAR_HANDLES_START); + protected variableHandlesReverse: { [id: string]: number } = {}; protected quit: boolean; protected attached: boolean; protected needContinue: boolean; @@ -266,7 +267,7 @@ export class MI2DebugSession extends DebugSession { protected async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): Promise { const variables: DebugProtocol.Variable[] = []; - let id: number | string | ExtendedVariable; + let id: number | string | VariableObject | ExtendedVariable; if (args.variablesReference < VAR_HANDLES_START) { id = args.variablesReference - STACK_HANDLES_START; } @@ -281,28 +282,16 @@ export class MI2DebugSession extends DebugSession { return this.variableHandles.create(arg); }; - let miVarObjToVariable = (varObj: any): DebugProtocol.Variable => { - const evaluateName = MINode.valueOf(varObj, "name"); - const value = MINode.valueOf(varObj, "value"); - const numChild = parseInt(MINode.valueOf(varObj, "numchild")); - const dynamic = MINode.valueOf(varObj, "dynamic") || 0; - let displayHint, hasMore; - if (dynamic) { - displayHint = MINode.valueOf(varObj, "displayhint"); - hasMore = parseInt(MINode.valueOf(varObj, "has_more")); + let findOrCreateVariable = (varObj: VariableObject): number => { + let id: number; + if (this.variableHandlesReverse.hasOwnProperty(varObj.name)) { + id = this.variableHandlesReverse[varObj.name]; } - const isCompound = numChild > 0 || - value === "{...}" || - (dynamic > 0 && (displayHint === "array" || displayHint === "map")); - - let res = { - name: MINode.valueOf(varObj, "exp"), - evaluateName, - type: MINode.valueOf(varObj, "type"), - value: value || "", - variablesReference: isCompound ? createVariable(evaluateName) : 0 - } as DebugProtocol.Variable; - return res; + else { + id = createVariable(varObj); + this.variableHandlesReverse[varObj.name] = id; + } + return varObj.isCompound() ? id : 0; }; if (typeof id == "number") { @@ -311,10 +300,26 @@ export class MI2DebugSession extends DebugSession { stack = await this.miDebugger.getStackVariables(this.threadID, id); for (const variable of stack) { try { - const varObj = await this.miDebugger.varCreate(variable.name); - let v = miVarObjToVariable(varObj.resultRecords.results); - v.name = variable.name; - variables.push(v); + let varObj: VariableObject; + try { + const changes = await this.miDebugger.varUpdate(variable.name); + const changelist = changes.result("changelist"); + changelist.forEach((change) => { + const name = MINode.valueOf(change, "name"); + const vId = this.variableHandlesReverse[variable.name]; + const v = this.variableHandles.get(vId) as any; + v.applyChanges(change); + }); + const varId = this.variableHandlesReverse[variable.name]; + varObj = this.variableHandles.get(varId) as any; + } + catch (err) { + varObj = await this.miDebugger.varCreate(variable.name, variable.name); + const varId = findOrCreateVariable(varObj); + varObj.exp = variable.name; + varObj.id = varId; + } + variables.push(varObj.toProtocolVariable()); } catch (err) { variables.push({ @@ -333,27 +338,28 @@ export class MI2DebugSession extends DebugSession { this.sendErrorResponse(response, 1, `Could not expand variable: ${err}`); } } - else if (typeof id == "string") { - // Variable members - let listChildren; - try { - listChildren = await this.miDebugger.varListChildren(id); - const children: any[] = listChildren.result("children"); - // TODO: use hasMore when it's > 0 - // const hasMore = parseInt(listChildren.result("has_more")); - const vars = children.map(child => miVarObjToVariable(child[1])); + else if (typeof id == "object") { + if (id instanceof VariableObject) { + // Variable members + let children: VariableObject[]; + try { + children = await this.miDebugger.varListChildren(id.name); + const vars = children.map(child => { + const varId = findOrCreateVariable(child); + child.id = varId; + return child.toProtocolVariable(); + }); - response.body = { - variables: vars + response.body = { + variables: vars + } + this.sendResponse(response); + } + catch (err) { + this.sendErrorResponse(response, 1, `Could not expand variable: ${err}`); } - this.sendResponse(response); - } - catch (err) { - this.sendErrorResponse(response, 1, `Could not expand variable: ${err}`); } - } - else if (typeof id == "object") { - if (id instanceof ExtendedVariable) { + else if (id instanceof ExtendedVariable) { let varReq = id; if (varReq.options.arg) { let strArr = []; From d498b381bf0f8efd3fded22e2395cec3f155ea6a Mon Sep 17 00:00:00 2001 From: gentoo90 Date: Fri, 26 May 2017 11:14:42 +0300 Subject: [PATCH 6/7] Use var-assign to set variables values --- src/backend/mi2/mi2.ts | 6 ++++++ src/mibase.ts | 18 +++++++++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/backend/mi2/mi2.ts b/src/backend/mi2/mi2.ts index 0bcc6bd2..f5f28f13 100644 --- a/src/backend/mi2/mi2.ts +++ b/src/backend/mi2/mi2.ts @@ -690,6 +690,12 @@ export class MI2 extends EventEmitter implements IBackend { return this.sendCommand(`var-update --all-values ${name}`) } + async varAssign(name: string, rawValue: string): Promise { + if (trace) + this.log("stderr", "varAssign"); + return this.sendCommand(`var-assign ${name} ${rawValue}`); + } + logNoNewLine(type: string, msg: string) { this.emit("msg", type, msg); } diff --git a/src/mibase.ts b/src/mibase.ts index 16785e23..92caccac 100644 --- a/src/mibase.ts +++ b/src/mibase.ts @@ -130,15 +130,23 @@ export class MI2DebugSession extends DebugSession { this.sendResponse(response); } - protected setVariableRequest(response: DebugProtocol.SetVariableResponse, args: DebugProtocol.SetVariableArguments): void { - this.miDebugger.changeVariable(args.name, args.value).then(() => { + protected async setVariableRequest(response: DebugProtocol.SetVariableResponse, args: DebugProtocol.SetVariableArguments): Promise { + try { + let name = args.name; + if (args.variablesReference >= VAR_HANDLES_START) { + const parent = this.variableHandles.get(args.variablesReference) as VariableObject; + name = `${parent.name}.${name}`; + } + + let res = await this.miDebugger.varAssign(name, args.value); response.body = { - value: args.value + value: res.result("value") }; this.sendResponse(response); - }, err => { + } + catch (err) { this.sendErrorResponse(response, 11, `Could not continue: ${err}`); - }); + }; } protected setFunctionBreakPointsRequest(response: DebugProtocol.SetFunctionBreakpointsResponse, args: DebugProtocol.SetFunctionBreakpointsArguments): void { From beb80f737590ef559f5f101a13acbd4a88168410 Mon Sep 17 00:00:00 2001 From: gentoo90 Date: Fri, 26 May 2017 22:48:33 +0300 Subject: [PATCH 7/7] Add valuesFormatting parameter to launch config --- package.json | 62 ++++++++++++++++- src/backend/backend.ts | 2 + src/backend/mi2/mi2.ts | 7 +- src/gdb.ts | 8 ++- src/lldb.ts | 8 ++- src/mago.ts | 8 ++- src/mibase.ts | 149 ++++++++++++++++++++++++++++++++--------- 7 files changed, 201 insertions(+), 43 deletions(-) diff --git a/package.json b/package.json index c74ff8b1..fde1185e 100644 --- a/package.json +++ b/package.json @@ -101,6 +101,16 @@ "description": "Additional arguments to pass to GDB", "default": [] }, + "valuesFormatting": { + "type": "string", + "description": "Set the way of showing variable values. 'disabled' - show value as is, 'parseText' - parse debuggers output text into structure, 'prettyPrinters' - enable debuggers custom pretty-printers if there are any", + "default": "parseText", + "enum": [ + "disabled", + "parseText", + "prettyPrinters" + ] + }, "printCalls": { "type": "boolean", "description": "Prints all GDB calls to the console", @@ -192,6 +202,16 @@ "description": "If true this will connect to a gdbserver instead of attaching to a PID", "default": false }, + "valuesFormatting": { + "type": "string", + "description": "Set the way of showing variable values. 'disabled' - show value as is, 'parseText' - parse debuggers output text into structure, 'prettyPrinters' - enable debuggers custom pretty-printers if there are any", + "default": "parseText", + "enum": [ + "disabled", + "parseText", + "prettyPrinters" + ] + }, "printCalls": { "type": "boolean", "description": "Prints all GDB calls to the console", @@ -466,6 +486,16 @@ "description": "Additional arguments to pass to LLDB", "default": [] }, + "valuesFormatting": { + "type": "string", + "description": "Set the way of showing variable values. 'disabled' - show value as is, 'parseText' - parse debuggers output text into structure, 'prettyPrinters' - enable debuggers custom pretty-printers if there are any", + "default": "parseText", + "enum": [ + "disabled", + "parseText", + "prettyPrinters" + ] + }, "printCalls": { "type": "boolean", "description": "Prints all lldb calls to the console", @@ -552,6 +582,16 @@ "type": "string", "description": "PID of running program or program name" }, + "valuesFormatting": { + "type": "string", + "description": "Set the way of showing variable values. 'disabled' - show value as is, 'parseText' - parse debuggers output text into structure, 'prettyPrinters' - enable debuggers custom pretty-printers if there are any", + "default": "parseText", + "enum": [ + "disabled", + "parseText", + "prettyPrinters" + ] + }, "printCalls": { "type": "boolean", "description": "Prints all LLDB calls to the console", @@ -713,6 +753,16 @@ "description": "Additional arguments to pass to mago", "default": [] }, + "valuesFormatting": { + "type": "string", + "description": "Set the way of showing variable values. 'disabled' - show value as is, 'parseText' - parse debuggers output text into structure, 'prettyPrinters' - enable debuggers custom pretty-printers if there are any", + "default": "parseText", + "enum": [ + "disabled", + "parseText", + "prettyPrinters" + ] + }, "printCalls": { "type": "boolean", "description": "Prints all mago calls to the console", @@ -739,6 +789,16 @@ "type": "string", "description": "PID of running program or program name" }, + "valuesFormatting": { + "type": "string", + "description": "Set the way of showing variable values. 'disabled' - show value as is, 'parseText' - parse debuggers output text into structure, 'prettyPrinters' - enable debuggers custom pretty-printers if there are any", + "default": "parseText", + "enum": [ + "disabled", + "parseText", + "prettyPrinters" + ] + }, "printCalls": { "type": "boolean", "description": "Prints all mago calls to the console", @@ -834,4 +894,4 @@ "@types/node": "^7.0.5", "@types/mocha": "^2.2.39" } -} \ No newline at end of file +} diff --git a/src/backend/backend.ts b/src/backend/backend.ts index ada42320..d85cd267 100644 --- a/src/backend/backend.ts +++ b/src/backend/backend.ts @@ -1,6 +1,8 @@ import { MINode } from "./mi_parse"; import { DebugProtocol } from "vscode-debugprotocol/lib/debugProtocol"; +export type ValuesFormattingMode = "disabled" | "parseText" | "prettyPrinters"; + export interface Breakpoint { file?: string; line?: number; diff --git a/src/backend/mi2/mi2.ts b/src/backend/mi2/mi2.ts index f5f28f13..9a895940 100644 --- a/src/backend/mi2/mi2.ts +++ b/src/backend/mi2/mi2.ts @@ -196,9 +196,9 @@ export class MI2 extends EventEmitter implements IBackend { ]; if (!attach) cmds.push(this.sendCommand("file-exec-and-symbols \"" + escape(target) + "\"")); + if (this.prettyPrint) + cmds.push(this.sendCommand("enable-pretty-printing")); - // TODO: add extension parameter for enabling/disabling pretty printers - cmds.push(this.sendCommand("enable-pretty-printing")); return cmds; } @@ -668,7 +668,7 @@ export class MI2 extends EventEmitter implements IBackend { return new VariableObject(res.result("")); } - async varEvalExpression(name: string): Promise < MINode > { + async varEvalExpression(name: string): Promise { if (trace) this.log("stderr", "varEvalExpression"); return this.sendCommand(`var-evaluate-expression ${name}`); @@ -746,6 +746,7 @@ export class MI2 extends EventEmitter implements IBackend { return this.isSSH ? this.sshReady : !!this.process; } + prettyPrint: boolean = true; printCalls: boolean; debugOutput: boolean; public procEnv: any; diff --git a/src/gdb.ts b/src/gdb.ts index 2bc2812f..d4d06822 100644 --- a/src/gdb.ts +++ b/src/gdb.ts @@ -2,7 +2,7 @@ import { MI2DebugSession } from './mibase'; import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, OutputEvent, Thread, StackFrame, Scope, Source, Handles } from 'vscode-debugadapter'; import { DebugProtocol } from 'vscode-debugprotocol'; import { MI2 } from "./backend/mi2/mi2"; -import { SSHArguments } from './backend/backend'; +import { SSHArguments, ValuesFormattingMode } from './backend/backend'; export interface LaunchRequestArguments { cwd: string; @@ -14,6 +14,7 @@ export interface LaunchRequestArguments { terminal: string; autorun: string[]; ssh: SSHArguments; + valuesFormatting: ValuesFormattingMode; printCalls: boolean; showDevDebugOutput: boolean; } @@ -28,6 +29,7 @@ export interface AttachRequestArguments { remote: boolean; autorun: string[]; ssh: SSHArguments; + valuesFormatting: ValuesFormattingMode; printCalls: boolean; showDevDebugOutput: boolean; } @@ -54,6 +56,7 @@ class GDBDebugSession extends MI2DebugSession { this.started = false; this.crashed = false; this.debugReady = false; + this.setValuesFormattingMode(args.valuesFormatting); this.miDebugger.printCalls = !!args.printCalls; this.miDebugger.debugOutput = !!args.showDevDebugOutput; if (args.ssh !== undefined) { @@ -121,6 +124,7 @@ class GDBDebugSession extends MI2DebugSession { this.needContinue = true; this.isSSH = false; this.debugReady = false; + this.setValuesFormattingMode(args.valuesFormatting); this.miDebugger.printCalls = !!args.printCalls; this.miDebugger.debugOutput = !!args.showDevDebugOutput; if (args.ssh !== undefined) { @@ -177,4 +181,4 @@ class GDBDebugSession extends MI2DebugSession { } } -DebugSession.run(GDBDebugSession); \ No newline at end of file +DebugSession.run(GDBDebugSession); diff --git a/src/lldb.ts b/src/lldb.ts index f634c0fd..28a92e80 100644 --- a/src/lldb.ts +++ b/src/lldb.ts @@ -2,7 +2,7 @@ import { MI2DebugSession } from './mibase'; import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, OutputEvent, Thread, StackFrame, Scope, Source, Handles } from 'vscode-debugadapter'; import { DebugProtocol } from 'vscode-debugprotocol'; import { MI2_LLDB } from "./backend/mi2/mi2lldb"; -import { SSHArguments } from './backend/backend'; +import { SSHArguments, ValuesFormattingMode } from './backend/backend'; export interface LaunchRequestArguments { cwd: string; @@ -13,6 +13,7 @@ export interface LaunchRequestArguments { arguments: string; autorun: string[]; ssh: SSHArguments; + valuesFormatting: ValuesFormattingMode; printCalls: boolean; showDevDebugOutput: boolean; } @@ -25,6 +26,7 @@ export interface AttachRequestArguments { debugger_args: string[]; executable: string; autorun: string[]; + valuesFormatting: ValuesFormattingMode; printCalls: boolean; showDevDebugOutput: boolean; } @@ -49,6 +51,7 @@ class LLDBDebugSession extends MI2DebugSession { this.started = false; this.crashed = false; this.debugReady = false; + this.setValuesFormattingMode(args.valuesFormatting); this.miDebugger.printCalls = !!args.printCalls; this.miDebugger.debugOutput = !!args.showDevDebugOutput; if (args.ssh !== undefined) { @@ -108,6 +111,7 @@ class LLDBDebugSession extends MI2DebugSession { this.needContinue = true; this.isSSH = false; this.debugReady = false; + this.setValuesFormattingMode(args.valuesFormatting); this.miDebugger.printCalls = !!args.printCalls; this.miDebugger.debugOutput = !!args.showDevDebugOutput; this.miDebugger.attach(args.cwd, args.executable, args.target).then(() => { @@ -120,4 +124,4 @@ class LLDBDebugSession extends MI2DebugSession { } } -DebugSession.run(LLDBDebugSession); \ No newline at end of file +DebugSession.run(LLDBDebugSession); diff --git a/src/mago.ts b/src/mago.ts index bd6d1660..e06c3e1c 100644 --- a/src/mago.ts +++ b/src/mago.ts @@ -2,7 +2,7 @@ import { MI2DebugSession } from './mibase'; import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, OutputEvent, Thread, StackFrame, Scope, Source, Handles } from 'vscode-debugadapter'; import { DebugProtocol } from 'vscode-debugprotocol'; import { MI2_Mago } from "./backend/mi2/mi2mago"; -import { SSHArguments } from './backend/backend'; +import { SSHArguments, ValuesFormattingMode } from './backend/backend'; export interface LaunchRequestArguments { cwd: string; @@ -12,6 +12,7 @@ export interface LaunchRequestArguments { debugger_args: string[]; arguments: string; autorun: string[]; + valuesFormatting: ValuesFormattingMode; printCalls: boolean; showDevDebugOutput: boolean; } @@ -24,6 +25,7 @@ export interface AttachRequestArguments { debugger_args: string[]; executable: string; autorun: string[]; + valuesFormatting: ValuesFormattingMode; printCalls: boolean; showDevDebugOutput: boolean; } @@ -56,6 +58,7 @@ class MagoDebugSession extends MI2DebugSession { this.started = false; this.crashed = false; this.debugReady = false; + this.setValuesFormattingMode(args.valuesFormatting); this.miDebugger.printCalls = !!args.printCalls; this.miDebugger.debugOutput = !!args.showDevDebugOutput; this.miDebugger.load(args.cwd, args.target, args.arguments, undefined).then(() => { @@ -83,6 +86,7 @@ class MagoDebugSession extends MI2DebugSession { this.needContinue = true; this.isSSH = false; this.debugReady = false; + this.setValuesFormattingMode(args.valuesFormatting); this.miDebugger.printCalls = !!args.printCalls; this.miDebugger.debugOutput = !!args.showDevDebugOutput; this.miDebugger.attach(args.cwd, args.executable, args.target).then(() => { @@ -95,4 +99,4 @@ class MagoDebugSession extends MI2DebugSession { } } -DebugSession.run(MagoDebugSession); \ No newline at end of file +DebugSession.run(MagoDebugSession); diff --git a/src/mibase.ts b/src/mibase.ts index 92caccac..aaeef587 100644 --- a/src/mibase.ts +++ b/src/mibase.ts @@ -1,6 +1,6 @@ import { DebugSession, InitializedEvent, TerminatedEvent, StoppedEvent, OutputEvent, Thread, StackFrame, Scope, Source, Handles } from 'vscode-debugadapter'; import { DebugProtocol } from 'vscode-debugprotocol'; -import { Breakpoint, IBackend, Variable, VariableObject } from './backend/backend'; +import { Breakpoint, IBackend, Variable, VariableObject, ValuesFormattingMode } from './backend/backend'; import { MINode } from './backend/mi_parse'; import { expandValue, isExpandable } from './backend/gdb_expansion'; import { MI2 } from './backend/mi2/mi2'; @@ -24,6 +24,7 @@ const VAR_HANDLES_START = 2000; export class MI2DebugSession extends DebugSession { protected variableHandles = new Handles(VAR_HANDLES_START); protected variableHandlesReverse: { [id: string]: number } = {}; + protected useVarObjects: boolean; protected quit: boolean; protected attached: boolean; protected needContinue: boolean; @@ -82,6 +83,23 @@ export class MI2DebugSession extends DebugSession { } } + protected setValuesFormattingMode(mode: ValuesFormattingMode) { + switch (mode) { + case "disabled": + this.useVarObjects = true; + this.miDebugger.prettyPrint = false; + break; + case "prettyPrinters": + this.useVarObjects = true; + this.miDebugger.prettyPrint = true; + break; + case "parseText": + default: + this.useVarObjects = false; + this.miDebugger.prettyPrint = false; + } + } + protected handleMsg(type: string, msg: string) { if (type == "target") type = "stdout"; @@ -132,16 +150,24 @@ export class MI2DebugSession extends DebugSession { protected async setVariableRequest(response: DebugProtocol.SetVariableResponse, args: DebugProtocol.SetVariableArguments): Promise { try { - let name = args.name; - if (args.variablesReference >= VAR_HANDLES_START) { - const parent = this.variableHandles.get(args.variablesReference) as VariableObject; - name = `${parent.name}.${name}`; - } + if (this.useVarObjects) { + let name = args.name; + if (args.variablesReference >= VAR_HANDLES_START) { + const parent = this.variableHandles.get(args.variablesReference) as VariableObject; + name = `${parent.name}.${name}`; + } - let res = await this.miDebugger.varAssign(name, args.value); - response.body = { - value: res.result("value") - }; + let res = await this.miDebugger.varAssign(name, args.value); + response.body = { + value: res.result("value") + }; + } + else { + await this.miDebugger.changeVariable(args.name, args.value); + response.body = { + value: args.value + }; + } this.sendResponse(response); } catch (err) { @@ -307,34 +333,58 @@ export class MI2DebugSession extends DebugSession { try { stack = await this.miDebugger.getStackVariables(this.threadID, id); for (const variable of stack) { - try { - let varObj: VariableObject; + if (this.useVarObjects) { try { - const changes = await this.miDebugger.varUpdate(variable.name); - const changelist = changes.result("changelist"); - changelist.forEach((change) => { - const name = MINode.valueOf(change, "name"); - const vId = this.variableHandlesReverse[variable.name]; - const v = this.variableHandles.get(vId) as any; - v.applyChanges(change); - }); - const varId = this.variableHandlesReverse[variable.name]; - varObj = this.variableHandles.get(varId) as any; + let varObj: VariableObject; + try { + const changes = await this.miDebugger.varUpdate(variable.name); + const changelist = changes.result("changelist"); + changelist.forEach((change) => { + const name = MINode.valueOf(change, "name"); + const vId = this.variableHandlesReverse[variable.name]; + const v = this.variableHandles.get(vId) as any; + v.applyChanges(change); + }); + const varId = this.variableHandlesReverse[variable.name]; + varObj = this.variableHandles.get(varId) as any; + } + catch (err) { + varObj = await this.miDebugger.varCreate(variable.name, variable.name); + const varId = findOrCreateVariable(varObj); + varObj.exp = variable.name; + varObj.id = varId; + } + variables.push(varObj.toProtocolVariable()); } catch (err) { - varObj = await this.miDebugger.varCreate(variable.name, variable.name); - const varId = findOrCreateVariable(varObj); - varObj.exp = variable.name; - varObj.id = varId; + variables.push({ + name: variable.name, + value: `<${err}>`, + variablesReference: 0 + }); } - variables.push(varObj.toProtocolVariable()); } - catch (err) { - variables.push({ - name: variable.name, - value: `<${err}>`, - variablesReference: 0 - }); + else { + if (variable.valueStr !== undefined) { + let expanded = expandValue(createVariable, `{${variable.name}=${variable.valueStr})`, "", variable.raw); + if (expanded) { + if (typeof expanded[0] == "string") + expanded = [ + { + name: "", + value: prettyStringArray(expanded), + variablesReference: 0 + } + ]; + variables.push(expanded[0]); + } + } else + variables.push({ + name: variable.name, + type: variable.type, + value: "", + variablesReference: createVariable(variable.name) + }); } } response.body = { @@ -346,6 +396,39 @@ export class MI2DebugSession extends DebugSession { this.sendErrorResponse(response, 1, `Could not expand variable: ${err}`); } } + else if (typeof id == "string") { + // Variable members + let variable; + try { + variable = await this.miDebugger.evalExpression(JSON.stringify(id)); + try { + let expanded = expandValue(createVariable, variable.result("value"), id, variable); + if (!expanded) { + this.sendErrorResponse(response, 2, `Could not expand variable`); + } + else { + if (typeof expanded[0] == "string") + expanded = [ + { + name: "", + value: prettyStringArray(expanded), + variablesReference: 0 + } + ]; + response.body = { + variables: expanded + }; + this.sendResponse(response); + } + } + catch (e) { + this.sendErrorResponse(response, 2, `Could not expand variable: ${e}`); + } + } + catch (err) { + this.sendErrorResponse(response, 1, `Could not expand variable: ${err}`); + } + } else if (typeof id == "object") { if (id instanceof VariableObject) { // Variable members