diff --git a/CHANGELOG.md b/CHANGELOG.md index c9961b7a..0bc6e89c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # ChangeLog -# V1.13.0-pre3 +# V1.13.0-pre4 +* 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 * 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) * It Stops the current session. This means the current GDB and any GDB-server (openocd, stlink, etc. are also terminated) * If then Starts a new session using the same configuration. diff --git a/package-lock.json b/package-lock.json index 4d0f2d6f..5cbca827 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cortex-debug", - "version": "1.13.0-pre3", + "version": "1.13.0-pre4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cortex-debug", - "version": "1.13.0-pre3", + "version": "1.13.0-pre4", "license": "MIT", "dependencies": { "@vscode/extension-telemetry": "^0.9.8", diff --git a/package.json b/package.json index 1ac6ead4..44638f0f 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "1.13.0-pre3", + "version": "1.13.0-pre4", "preview": false, "activationEvents": [ "onDebugResolve:cortex-debug", diff --git a/src/backend/mi2/mi2.ts b/src/backend/mi2/mi2.ts index 91819bd6..5033cf6a 100644 --- a/src/backend/mi2/mi2.ts +++ b/src/backend/mi2/mi2.ts @@ -892,22 +892,27 @@ export class MI2 extends EventEmitter implements IBackend { x: 'hexadecimal' }; - public async varCreate( - parent: number, expression: string, name: string = '-', scope: string = '@', - threadId?: number, frameId?: number): Promise { - if (trace) { - this.log('stderr', 'varCreate'); - } - let fmt = null; + private getExprAndFmt(expression: string): [string, string] { + let fmt = ''; expression = expression.trim(); if (/,[bdhonx]$/i.test(expression)) { fmt = expression.substring(expression.length - 1).toLocaleLowerCase(); expression = expression.substring(0, expression.length - 2); } expression = expression.replace(/"/g, '\\"'); + return [expression, fmt]; + } + + public async varCreate( + parent: number, expression: string, name: string = '-', scope: string = '@', + threadId?: number, frameId?: number): Promise { + if (trace) { + this.log('stderr', 'varCreate'); + } + const [expr, fmt] = this.getExprAndFmt(expression); const thFr = ((threadId !== undefined) && (frameId !== undefined)) ? `--thread ${threadId} --frame ${frameId}` : ''; - const createResp = await this.sendCommand(`var-create ${thFr} ${name} ${scope} "${expression}"`); + const createResp = await this.sendCommand(`var-create ${thFr} ${name} ${scope} "${expr}"`); let overrideVal: string = null; if (fmt && name !== '-') { 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 { return this.sendCommand(`var-assign ${MI2.getThreadFrameStr(threadId, frameId)} ${name} ${rawValue}`); } + public async exprAssign(expr: string, rawValue: string, threadId: number, frameId: number): Promise { + if (trace) { + this.log('stderr', 'exprAssign'); + } + const [lhs, fmt] = this.getExprAndFmt(expr); + return this.sendCommand(`var-assign ${MI2.getThreadFrameStr(threadId, frameId)} ${lhs} ${rawValue}`); + } + public logNoNewLine(type: string, msg: string) { this.emit('msg', type, msg); } diff --git a/src/gdb.ts b/src/gdb.ts index 75b32ad3..eb1f2cda 100755 --- a/src/gdb.ts +++ b/src/gdb.ts @@ -317,6 +317,7 @@ export class GDBDebugSession extends LoggingDebugSession { response.body.supportsFunctionBreakpoints = true; response.body.supportsEvaluateForHovers = true; response.body.supportsSetVariable = true; + response.body.supportsSetExpression = true; // We no longer support a 'Restart' request. However, VSCode will implement a replacement by terminating the // 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 { } } + // Note that setVariableRequest is called to set member variables of watched expressions. So, don't + // make assumptions of what names you might see protected async setVariableRequest(response: DebugProtocol.SetVariableResponse, args: DebugProtocol.SetVariableArguments): Promise { try { let name = args.name; @@ -1985,7 +1988,36 @@ export class GDBDebugSession extends LoggingDebugSession { }; this.sendResponse(response); } catch (err) { - this.sendErrorResponse(response, 11, `Could not set variable: ${err}`); + this.sendErrorResponse(response, 11, `Could not set variable '${args.name}'\n${err}`); + } + } + + protected async setExpressionRequest(response: DebugProtocol.SetVariableResponse, args: DebugProtocol.SetExpressionArguments): Promise { + try { + const [threadId, frameId] = args.frameId ? decodeReference(args.frameId) : [undefined, undefined]; + const exp = args.expression; + const varObjName = this.createVarNameFromExpr(args.expression, args.frameId, 'watch'); + let varId = this.variableHandlesReverse.get(varObjName); + if (varId === undefined) { + let varObj: VariableObject; + if (args.frameId === undefined) { + varObj = await this.miDebugger.varCreate(0, exp, varObjName, '@'); // Create floating variable + } else { + varObj = await this.miDebugger.varCreate(0, exp, varObjName, '@', threadId, frameId); + } + varId = this.findOrCreateVariable(varObj); + varObj.exp = exp; + varObj.id = varId; + } + await this.miDebugger.exprAssign(varObjName, args.value, threadId, frameId); + const evalRsp = await this.evalExprInternal(args.expression, args.frameId, 'watch', threadId, frameId); + response.body = { + ...evalRsp, + value: evalRsp.result, + }; + this.sendResponse(response); + } catch (err) { + this.sendErrorResponse(response, 11, `Could not set expression '${args.expression}'\n${err}`); } } @@ -3204,61 +3236,7 @@ export class GDBDebugSession extends LoggingDebugSession { if (args.context !== 'repl') { try { - const exp = args.expression; - const hasher = crypto.createHash('sha256'); - hasher.update(exp); - if (args.frameId !== undefined) { - hasher.update(args.frameId.toString(16)); - } - const exprName = hasher.digest('hex'); - const varObjName = `${args.context}_${exprName}`; - let varObj: VariableObject; - let varId = this.variableHandlesReverse.get(varObjName); - let createNewVar = varId === undefined; - let updateError; - if (!createNewVar) { - try { - const changes = await this.miDebugger.varUpdate(varObjName, threadId, frameId); - const changelist = changes.result('changelist'); - for (const change of changelist || []) { - const inScope = MINode.valueOf(change, 'in_scope'); - if (inScope === 'true') { - const name = MINode.valueOf(change, 'name'); - const vId = this.variableHandlesReverse.get(name); - const v = this.variableHandles.get(vId) as any; - v.applyChanges(change); - } else { - const msg = `${exp} currently not in scope`; - await this.miDebugger.sendCommand(`var-delete ${varObjName}`); - if (this.args.showDevDebugOutput) { - this.handleMsg('log', `Expression ${msg}. Will try to create again\n`); - } - createNewVar = true; - throw new Error(msg); - } - } - varObj = this.variableHandles.get(varId) as any; - } catch (err) { - updateError = err; - } - } - if (!this.isBusy() && (createNewVar || ((updateError instanceof MIError && updateError.message === VarNotFoundMsg)))) { - // We always create a floating variable so it will be updated in the context of the current frame - // Technicall, we should be able to bind this to this frame but for some reason gdb gets confused - // from previous stack frames and returns the wrong results or says nothing changed when in fact it has - if (args.frameId === undefined) { - varObj = await this.miDebugger.varCreate(0, exp, varObjName, '@'); // Create floating variable - } else { - varObj = await this.miDebugger.varCreate(0, exp, varObjName, '@', threadId, frameId); - } - - varId = this.findOrCreateVariable(varObj); - varObj.exp = exp; - varObj.id = varId; - } else if (!varObj) { - throw updateError || new Error('evaluateRequest: unknown error'); - } - response.body = varObj.toProtocolEvaluateResponseBody(); + response.body = await this.evalExprInternal(args.expression, args.frameId, args.context, threadId, frameId); this.sendResponse(response); } catch (err) { if (this.isBusy()) { @@ -3315,6 +3293,70 @@ export class GDBDebugSession extends LoggingDebugSession { return this.evaluateQ.add(doit, r, a); } + private async evalExprInternal( + exp: string, frameRef: number | undefined, context: string, + threadId: number, frameId: number): Promise { + const varObjName = this.createVarNameFromExpr(exp, frameRef, context); + let varObj: VariableObject; + let varId = this.variableHandlesReverse.get(varObjName); + let createNewVar = varId === undefined; + let updateError; + if (!createNewVar) { + try { + const changes = await this.miDebugger.varUpdate(varObjName, threadId, frameId); + const changelist = changes.result('changelist'); + for (const change of changelist || []) { + const inScope = MINode.valueOf(change, 'in_scope'); + if (inScope === 'true') { + const name = MINode.valueOf(change, 'name'); + const vId = this.variableHandlesReverse.get(name); + const v = this.variableHandles.get(vId) as any; + v.applyChanges(change); + } else { + const msg = `${exp} currently not in scope`; + await this.miDebugger.sendCommand(`var-delete ${varObjName}`); + if (this.args.showDevDebugOutput) { + this.handleMsg('log', `Expression ${msg}. Will try to create again\n`); + } + createNewVar = true; + throw new Error(msg); + } + } + varObj = this.variableHandles.get(varId) as any; + } catch (err) { + updateError = err; + } + } + if (!this.isBusy() && (createNewVar || ((updateError instanceof MIError && updateError.message === VarNotFoundMsg)))) { + // We always create a floating variable so it will be updated in the context of the current frame + // Technicall, we should be able to bind this to this frame but for some reason gdb gets confused + // from previous stack frames and returns the wrong results or says nothing changed when in fact it has + if (frameRef === undefined) { + varObj = await this.miDebugger.varCreate(0, exp, varObjName, '@'); // Create floating variable + } else { + varObj = await this.miDebugger.varCreate(0, exp, varObjName, '@', threadId, frameId); + } + + varId = this.findOrCreateVariable(varObj); + varObj.exp = exp; + varObj.id = varId; + } else if (!varObj) { + throw updateError || new Error('evaluateRequest: unknown error'); + } + return varObj.toProtocolEvaluateResponseBody(); + } + + private createVarNameFromExpr(exp: string, encodedFrameId: number | undefined, context: string): string { + const hasher = crypto.createHash('sha256'); + hasher.update(exp); + if (encodedFrameId !== undefined) { + hasher.update(encodedFrameId.toString(16)); + } + const exprName = hasher.digest('hex'); + const varObjName = `${context || 'watch'}_${exprName}`; + return varObjName; + } + protected async gotoTargetsRequest(response: DebugProtocol.GotoTargetsResponse, args: DebugProtocol.GotoTargetsArguments): Promise { try { const done = await this.miDebugger.goto(args.source.path, args.line);