Skip to content

Commit d727d68

Browse files
committed
Support for setting values on expression from the Watch panel
1 parent 3e6bb5f commit d727d68

File tree

5 files changed

+124
-68
lines changed

5 files changed

+124
-68
lines changed

CHANGELOG.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# ChangeLog
22

3-
# V1.13.0-pre3
3+
# V1.13.0-pre4
4+
* Feature: Support for setting values on expression from the Watch panel. The new value has to be something that GDB understands, so setting values on arrays, structures won't work but it should work on any scalar
45
* MAJOR Change. The `Restart` button functionality has been completely changed. This was not a stable function and VSCode kept changing its definition over the years multiple times. However they provide a default functionality, so the `Restart` button still works but very differently from our implementation. As of today, VSCode seems to do the following (and this extension is not involved)
56
* It Stops the current session. This means the current GDB and any GDB-server (openocd, stlink, etc. are also terminated)
67
* If then Starts a new session using the same configuration.

package-lock.json

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"version": "1.13.0-pre3",
2+
"version": "1.13.0-pre4",
33
"preview": false,
44
"activationEvents": [
55
"onDebugResolve:cortex-debug",

src/backend/mi2/mi2.ts

+21-8
Original file line numberDiff line numberDiff line change
@@ -892,22 +892,27 @@ export class MI2 extends EventEmitter implements IBackend {
892892
x: 'hexadecimal'
893893
};
894894

895-
public async varCreate(
896-
parent: number, expression: string, name: string = '-', scope: string = '@',
897-
threadId?: number, frameId?: number): Promise<VariableObject> {
898-
if (trace) {
899-
this.log('stderr', 'varCreate');
900-
}
901-
let fmt = null;
895+
private getExprAndFmt(expression: string): [string, string] {
896+
let fmt = '';
902897
expression = expression.trim();
903898
if (/,[bdhonx]$/i.test(expression)) {
904899
fmt = expression.substring(expression.length - 1).toLocaleLowerCase();
905900
expression = expression.substring(0, expression.length - 2);
906901
}
907902
expression = expression.replace(/"/g, '\\"');
903+
return [expression, fmt];
904+
}
905+
906+
public async varCreate(
907+
parent: number, expression: string, name: string = '-', scope: string = '@',
908+
threadId?: number, frameId?: number): Promise<VariableObject> {
909+
if (trace) {
910+
this.log('stderr', 'varCreate');
911+
}
908912

913+
const [expr, fmt] = this.getExprAndFmt(expression);
909914
const thFr = ((threadId !== undefined) && (frameId !== undefined)) ? `--thread ${threadId} --frame ${frameId}` : '';
910-
const createResp = await this.sendCommand(`var-create ${thFr} ${name} ${scope} "${expression}"`);
915+
const createResp = await this.sendCommand(`var-create ${thFr} ${name} ${scope} "${expr}"`);
911916
let overrideVal: string = null;
912917
if (fmt && name !== '-') {
913918
const formatResp = await this.sendCommand(`var-set-format ${name} ${MI2.FORMAT_SPEC_MAP[fmt]}`);
@@ -972,6 +977,14 @@ export class MI2 extends EventEmitter implements IBackend {
972977
return this.sendCommand(`var-assign ${MI2.getThreadFrameStr(threadId, frameId)} ${name} ${rawValue}`);
973978
}
974979

980+
public async exprAssign(expr: string, rawValue: string, threadId: number, frameId: number): Promise<MINode> {
981+
if (trace) {
982+
this.log('stderr', 'exprAssign');
983+
}
984+
const [lhs, fmt] = this.getExprAndFmt(expr);
985+
return this.sendCommand(`var-assign ${MI2.getThreadFrameStr(threadId, frameId)} ${lhs} ${rawValue}`);
986+
}
987+
975988
public logNoNewLine(type: string, msg: string) {
976989
this.emit('msg', type, msg);
977990
}

src/gdb.ts

+98-56
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,7 @@ export class GDBDebugSession extends LoggingDebugSession {
317317
response.body.supportsFunctionBreakpoints = true;
318318
response.body.supportsEvaluateForHovers = true;
319319
response.body.supportsSetVariable = true;
320+
response.body.supportsSetExpression = true;
320321

321322
// We no longer support a 'Restart' request. However, VSCode will implement a replacement by terminating the
322323
// current session and starting a new one from scratch. But, we still have to support the launch.json
@@ -1938,6 +1939,8 @@ export class GDBDebugSession extends LoggingDebugSession {
19381939
}
19391940
}
19401941

1942+
// Note that setVariableRequest is called to set member variables of watched expressions. So, don't
1943+
// make assumptions of what names you might see
19411944
protected async setVariableRequest(response: DebugProtocol.SetVariableResponse, args: DebugProtocol.SetVariableArguments): Promise<void> {
19421945
try {
19431946
let name = args.name;
@@ -1985,7 +1988,36 @@ export class GDBDebugSession extends LoggingDebugSession {
19851988
};
19861989
this.sendResponse(response);
19871990
} catch (err) {
1988-
this.sendErrorResponse(response, 11, `Could not set variable: ${err}`);
1991+
this.sendErrorResponse(response, 11, `Could not set variable '${args.name}'\n${err}`);
1992+
}
1993+
}
1994+
1995+
protected async setExpressionRequest(response: DebugProtocol.SetVariableResponse, args: DebugProtocol.SetExpressionArguments): Promise<void> {
1996+
try {
1997+
const [threadId, frameId] = args.frameId ? decodeReference(args.frameId) : [undefined, undefined];
1998+
const exp = args.expression;
1999+
const varObjName = this.createVarNameFromExpr(args.expression, args.frameId, 'watch');
2000+
let varId = this.variableHandlesReverse.get(varObjName);
2001+
if (varId === undefined) {
2002+
let varObj: VariableObject;
2003+
if (args.frameId === undefined) {
2004+
varObj = await this.miDebugger.varCreate(0, exp, varObjName, '@'); // Create floating variable
2005+
} else {
2006+
varObj = await this.miDebugger.varCreate(0, exp, varObjName, '@', threadId, frameId);
2007+
}
2008+
varId = this.findOrCreateVariable(varObj);
2009+
varObj.exp = exp;
2010+
varObj.id = varId;
2011+
}
2012+
await this.miDebugger.exprAssign(varObjName, args.value, threadId, frameId);
2013+
const evalRsp = await this.evalExprInternal(args.expression, args.frameId, 'watch', threadId, frameId);
2014+
response.body = {
2015+
...evalRsp,
2016+
value: evalRsp.result,
2017+
};
2018+
this.sendResponse(response);
2019+
} catch (err) {
2020+
this.sendErrorResponse(response, 11, `Could not set expression '${args.expression}'\n${err}`);
19892021
}
19902022
}
19912023

@@ -3204,61 +3236,7 @@ export class GDBDebugSession extends LoggingDebugSession {
32043236

32053237
if (args.context !== 'repl') {
32063238
try {
3207-
const exp = args.expression;
3208-
const hasher = crypto.createHash('sha256');
3209-
hasher.update(exp);
3210-
if (args.frameId !== undefined) {
3211-
hasher.update(args.frameId.toString(16));
3212-
}
3213-
const exprName = hasher.digest('hex');
3214-
const varObjName = `${args.context}_${exprName}`;
3215-
let varObj: VariableObject;
3216-
let varId = this.variableHandlesReverse.get(varObjName);
3217-
let createNewVar = varId === undefined;
3218-
let updateError;
3219-
if (!createNewVar) {
3220-
try {
3221-
const changes = await this.miDebugger.varUpdate(varObjName, threadId, frameId);
3222-
const changelist = changes.result('changelist');
3223-
for (const change of changelist || []) {
3224-
const inScope = MINode.valueOf(change, 'in_scope');
3225-
if (inScope === 'true') {
3226-
const name = MINode.valueOf(change, 'name');
3227-
const vId = this.variableHandlesReverse.get(name);
3228-
const v = this.variableHandles.get(vId) as any;
3229-
v.applyChanges(change);
3230-
} else {
3231-
const msg = `${exp} currently not in scope`;
3232-
await this.miDebugger.sendCommand(`var-delete ${varObjName}`);
3233-
if (this.args.showDevDebugOutput) {
3234-
this.handleMsg('log', `Expression ${msg}. Will try to create again\n`);
3235-
}
3236-
createNewVar = true;
3237-
throw new Error(msg);
3238-
}
3239-
}
3240-
varObj = this.variableHandles.get(varId) as any;
3241-
} catch (err) {
3242-
updateError = err;
3243-
}
3244-
}
3245-
if (!this.isBusy() && (createNewVar || ((updateError instanceof MIError && updateError.message === VarNotFoundMsg)))) {
3246-
// We always create a floating variable so it will be updated in the context of the current frame
3247-
// Technicall, we should be able to bind this to this frame but for some reason gdb gets confused
3248-
// from previous stack frames and returns the wrong results or says nothing changed when in fact it has
3249-
if (args.frameId === undefined) {
3250-
varObj = await this.miDebugger.varCreate(0, exp, varObjName, '@'); // Create floating variable
3251-
} else {
3252-
varObj = await this.miDebugger.varCreate(0, exp, varObjName, '@', threadId, frameId);
3253-
}
3254-
3255-
varId = this.findOrCreateVariable(varObj);
3256-
varObj.exp = exp;
3257-
varObj.id = varId;
3258-
} else if (!varObj) {
3259-
throw updateError || new Error('evaluateRequest: unknown error');
3260-
}
3261-
response.body = varObj.toProtocolEvaluateResponseBody();
3239+
response.body = await this.evalExprInternal(args.expression, args.frameId, args.context, threadId, frameId);
32623240
this.sendResponse(response);
32633241
} catch (err) {
32643242
if (this.isBusy()) {
@@ -3315,6 +3293,70 @@ export class GDBDebugSession extends LoggingDebugSession {
33153293
return this.evaluateQ.add(doit, r, a);
33163294
}
33173295

3296+
private async evalExprInternal(
3297+
exp: string, frameRef: number | undefined, context: string,
3298+
threadId: number, frameId: number): Promise<DebugProtocol.EvaluateResponse['body']> {
3299+
const varObjName = this.createVarNameFromExpr(exp, frameRef, context);
3300+
let varObj: VariableObject;
3301+
let varId = this.variableHandlesReverse.get(varObjName);
3302+
let createNewVar = varId === undefined;
3303+
let updateError;
3304+
if (!createNewVar) {
3305+
try {
3306+
const changes = await this.miDebugger.varUpdate(varObjName, threadId, frameId);
3307+
const changelist = changes.result('changelist');
3308+
for (const change of changelist || []) {
3309+
const inScope = MINode.valueOf(change, 'in_scope');
3310+
if (inScope === 'true') {
3311+
const name = MINode.valueOf(change, 'name');
3312+
const vId = this.variableHandlesReverse.get(name);
3313+
const v = this.variableHandles.get(vId) as any;
3314+
v.applyChanges(change);
3315+
} else {
3316+
const msg = `${exp} currently not in scope`;
3317+
await this.miDebugger.sendCommand(`var-delete ${varObjName}`);
3318+
if (this.args.showDevDebugOutput) {
3319+
this.handleMsg('log', `Expression ${msg}. Will try to create again\n`);
3320+
}
3321+
createNewVar = true;
3322+
throw new Error(msg);
3323+
}
3324+
}
3325+
varObj = this.variableHandles.get(varId) as any;
3326+
} catch (err) {
3327+
updateError = err;
3328+
}
3329+
}
3330+
if (!this.isBusy() && (createNewVar || ((updateError instanceof MIError && updateError.message === VarNotFoundMsg)))) {
3331+
// We always create a floating variable so it will be updated in the context of the current frame
3332+
// Technicall, we should be able to bind this to this frame but for some reason gdb gets confused
3333+
// from previous stack frames and returns the wrong results or says nothing changed when in fact it has
3334+
if (frameRef === undefined) {
3335+
varObj = await this.miDebugger.varCreate(0, exp, varObjName, '@'); // Create floating variable
3336+
} else {
3337+
varObj = await this.miDebugger.varCreate(0, exp, varObjName, '@', threadId, frameId);
3338+
}
3339+
3340+
varId = this.findOrCreateVariable(varObj);
3341+
varObj.exp = exp;
3342+
varObj.id = varId;
3343+
} else if (!varObj) {
3344+
throw updateError || new Error('evaluateRequest: unknown error');
3345+
}
3346+
return varObj.toProtocolEvaluateResponseBody();
3347+
}
3348+
3349+
private createVarNameFromExpr(exp: string, encodedFrameId: number | undefined, context: string): string {
3350+
const hasher = crypto.createHash('sha256');
3351+
hasher.update(exp);
3352+
if (encodedFrameId !== undefined) {
3353+
hasher.update(encodedFrameId.toString(16));
3354+
}
3355+
const exprName = hasher.digest('hex');
3356+
const varObjName = `${context || 'watch'}_${exprName}`;
3357+
return varObjName;
3358+
}
3359+
33183360
protected async gotoTargetsRequest(response: DebugProtocol.GotoTargetsResponse, args: DebugProtocol.GotoTargetsArguments): Promise<void> {
33193361
try {
33203362
const done = await this.miDebugger.goto(args.source.path, args.line);

0 commit comments

Comments
 (0)