From a72533b2085d40cc806503f312d5a2d44ae2d5ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miro=20Sp=C3=B6nemann?= Date: Mon, 24 Feb 2020 14:33:51 +0100 Subject: [PATCH] Added basic support for global and static variables --- ...debug-frontend-application-contribution.ts | 14 +-- .../arduino-debug-adapter-contribution.ts | 3 +- .../debug-adapter/arduino-debug-session.ts | 107 +++++++++++++--- .../node/debug-adapter/arduino-gdb-backend.ts | 15 ++- .../debug-adapter/arduino-variable-handler.ts | 115 ++++++++++++++++++ .../browser/arduino-frontend-contribution.tsx | 2 +- 6 files changed, 223 insertions(+), 33 deletions(-) create mode 100644 arduino-debugger-extension/src/node/debug-adapter/arduino-variable-handler.ts diff --git a/arduino-debugger-extension/src/browser/arduino-debug-frontend-application-contribution.ts b/arduino-debugger-extension/src/browser/arduino-debug-frontend-application-contribution.ts index 8f9b6ea0..60f9dd21 100644 --- a/arduino-debugger-extension/src/browser/arduino-debug-frontend-application-contribution.ts +++ b/arduino-debugger-extension/src/browser/arduino-debug-frontend-application-contribution.ts @@ -1,17 +1,17 @@ import { injectable, inject } from 'inversify'; import { MenuModelRegistry, Path, MessageService, Command, CommandRegistry } from '@theia/core'; -import { KeybindingRegistry, Widget } from '@theia/core/lib/browser'; +import { KeybindingRegistry } from '@theia/core/lib/browser'; import { TabBarToolbarRegistry } from '@theia/core/lib/browser/shell/tab-bar-toolbar'; import { DebugFrontendApplicationContribution, DebugCommands } from '@theia/debug/lib/browser/debug-frontend-application-contribution'; import { DebugSessionOptions } from "@theia/debug/lib/browser/debug-session-options"; import { WorkspaceService } from '@theia/workspace/lib/browser/workspace-service'; -import { EditorMode } from "arduino-ide-extension/lib/browser/editor-mode"; -import { ArduinoDebugConfigurationManager } from './arduino-debug-configuration-manager'; -import { SketchesService } from 'arduino-ide-extension/lib/common/protocol/sketches-service'; import { FileSystem } from '@theia/filesystem/lib/common'; import URI from '@theia/core/lib/common/uri'; import { EditorManager } from '@theia/editor/lib/browser'; +import { EditorMode } from "arduino-ide-extension/lib/browser/editor-mode"; +import { SketchesService } from 'arduino-ide-extension/lib/common/protocol/sketches-service'; import { ArduinoToolbar } from 'arduino-ide-extension/lib/browser/toolbar/arduino-toolbar'; +import { ArduinoDebugConfigurationManager } from './arduino-debug-configuration-manager'; export namespace ArduinoDebugCommands { export const START_DEBUG: Command = { @@ -109,9 +109,7 @@ export class ArduinoDebugFrontendApplicationContribution extends DebugFrontendAp } registerToolbarItems(toolbar: TabBarToolbarRegistry): void { - if (this.editorMode.proMode) { - super.registerToolbarItems(toolbar); - } + super.registerToolbarItems(toolbar); toolbar.registerItem({ id: ArduinoDebugCommands.START_DEBUG.id, command: ArduinoDebugCommands.START_DEBUG.id, @@ -125,7 +123,7 @@ export class ArduinoDebugFrontendApplicationContribution extends DebugFrontendAp registry.registerCommand(ArduinoDebugCommands.START_DEBUG, { isVisible: widget => ArduinoToolbar.is(widget) && widget.side === 'left', isEnabled: widget => ArduinoToolbar.is(widget) && widget.side === 'left', - execute: async (widget: Widget, target: EventTarget) => { + execute: () => { registry.executeCommand(DebugCommands.START.id); } }); diff --git a/arduino-debugger-extension/src/node/arduino-debug-adapter-contribution.ts b/arduino-debugger-extension/src/node/arduino-debug-adapter-contribution.ts index 9f6ee487..92ddf504 100644 --- a/arduino-debugger-extension/src/node/arduino-debug-adapter-contribution.ts +++ b/arduino-debugger-extension/src/node/arduino-debug-adapter-contribution.ts @@ -70,7 +70,8 @@ export class ArduinoDebugAdapterContribution implements DebugAdapterContribution fqbn: '${fqbn}', uploadPort: '${port}', initCommands: [ - `-break-insert -t --function ${startFunction}` + `-break-insert -t --function ${startFunction}`, + '-interpreter-exec console "monitor reset halt"' ] } if (!res.sketch) { diff --git a/arduino-debugger-extension/src/node/debug-adapter/arduino-debug-session.ts b/arduino-debugger-extension/src/node/debug-adapter/arduino-debug-session.ts index 670a9e93..ef69bf67 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/arduino-debug-session.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/arduino-debug-session.ts @@ -1,8 +1,10 @@ -import * as mi from 'cdt-gdb-adapter/dist/mi'; import { DebugProtocol } from 'vscode-debugprotocol'; -import { GDBDebugSession, LaunchRequestArguments } from 'cdt-gdb-adapter/dist/GDBDebugSession'; +import { GDBDebugSession, FrameVariableReference } from 'cdt-gdb-adapter/dist/GDBDebugSession'; import { GDBBackend } from 'cdt-gdb-adapter/dist/GDBBackend'; +import * as mi from 'cdt-gdb-adapter/dist/mi'; import { ArduinoGDBBackend } from './arduino-gdb-backend'; +import { ArduinoVariableHandler } from './arduino-variable-handler'; +import { Scope } from 'vscode-debugadapter'; export interface ArduinoLaunchRequestArguments extends DebugProtocol.LaunchRequestArguments { arduinoCli?: string; @@ -11,28 +13,34 @@ export interface ArduinoLaunchRequestArguments extends DebugProtocol.LaunchReque uploadPort?: string; } +const GLOBAL_HANDLE_ID = 0xFE; +const STATIC_HANDLES_START = 0x010000; +const STATIC_HANDLES_FINISH = 0x01FFFF; + export class ArduinoDebugSession extends GDBDebugSession { - protected get arduinoBackend(): ArduinoGDBBackend { + private _variableHandler: ArduinoVariableHandler; + + get arduinoBackend(): ArduinoGDBBackend { return this.gdb as ArduinoGDBBackend; } + protected get variableHandler() { + if (this._variableHandler) { + return this._variableHandler; + } + if (!this.gdb) { + throw new Error("GDB backend is not ready."); + } + const handler = new ArduinoVariableHandler(this, this.frameHandles, this.variableHandles); + this._variableHandler = handler; + return handler; + } + protected createBackend(): GDBBackend { return new ArduinoGDBBackend(); } - protected launchRequest(response: DebugProtocol.LaunchResponse, args: LaunchRequestArguments): Promise { - const additionalCommands = [ - '-interpreter-exec console "monitor reset halt"' - ]; - if (!args.initCommands) { - args.initCommands = additionalCommands; - } else { - args.initCommands.push(...additionalCommands); - } - return super.launchRequest(response, args); - } - protected async configurationDoneRequest(response: DebugProtocol.ConfigurationDoneResponse): Promise { try { await mi.sendExecContinue(this.gdb); @@ -50,11 +58,6 @@ export class ArduinoDebugSession extends GDBDebugSession { this.gdb.pause(); await waitPromise; } - try { - await this.arduinoBackend.sendTargetDetach(); - } catch (e) { - // Need to catch here as the command result being returned will never exist as it's detached - } await this.gdb.sendGDBExit(); this.sendResponse(response); } catch (err) { @@ -62,4 +65,68 @@ export class ArduinoDebugSession extends GDBDebugSession { } } + protected async stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): Promise { + try { + + return super.stackTraceRequest(response, args); + } catch (err) { + this.sendErrorResponse(response, 1, err.message); + } + } + + protected scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void { + try { + const frame: FrameVariableReference = { + type: 'frame', + frameHandle: args.frameId, + }; + // const pins: ObjectVariableReference = { + // type: "object", + // varobjName: "__pins", + // frameHandle: 42000, + // } + + response.body = { + scopes: [ + // new Scope('Pins', this.variableHandles.create(pins), false), + new Scope('Local', this.variableHandles.create(frame), false), + new Scope('Global', GLOBAL_HANDLE_ID, false), + new Scope('Static', STATIC_HANDLES_START + parseInt(args.frameId as any, 10), false) + ], + }; + + this.sendResponse(response); + } catch (err) { + this.sendErrorResponse(response, 1, err.message); + } + } + + protected async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): Promise { + try { + response.body = { + variables: [] as DebugProtocol.Variable[] + }; + const ref = this.variableHandles.get(args.variablesReference); + if (args.variablesReference === GLOBAL_HANDLE_ID) { + // Use hardcoded global handle to load and store global variables + response.body.variables = await this.variableHandler.getGlobalVariables(); + } else if (args.variablesReference >= STATIC_HANDLES_START && args.variablesReference <= STATIC_HANDLES_FINISH) { + // Use STATIC_HANDLES_START to shift the framehandles back + const frameHandle = args.variablesReference - STATIC_HANDLES_START; + response.body.variables = await this.variableHandler.getStaticVariables(frameHandle); + } else if (ref && ref.type === 'frame') { + // List variables for current frame + response.body.variables = await this.handleVariableRequestFrame(ref); + } else if (ref && ref.varobjName === '__pins') { + response.body.variables = await this.variableHandler.handlePinStatusRequest(); + } else if (ref && ref.type === 'object') { + // List data under any variable + response.body.variables = await this.handleVariableRequestObject(ref); + } + this.sendResponse(response); + } catch (err) { + this.sendErrorResponse(response, 1, err.message); + } + } + } diff --git a/arduino-debugger-extension/src/node/debug-adapter/arduino-gdb-backend.ts b/arduino-debugger-extension/src/node/debug-adapter/arduino-gdb-backend.ts index 2630eeb4..a805522f 100644 --- a/arduino-debugger-extension/src/node/debug-adapter/arduino-gdb-backend.ts +++ b/arduino-debugger-extension/src/node/debug-adapter/arduino-gdb-backend.ts @@ -2,6 +2,7 @@ import * as path from 'path'; import * as fs from 'arduino-ide-extension/lib/node/fs-extra' import { spawn } from 'child_process'; import { GDBBackend } from 'cdt-gdb-adapter/dist/GDBBackend'; +import { MIFrameInfo } from 'cdt-gdb-adapter/dist/mi'; import { ArduinoLaunchRequestArguments } from './arduino-debug-session'; export class ArduinoGDBBackend extends GDBBackend { @@ -33,9 +34,17 @@ export class ArduinoGDBBackend extends GDBBackend { return Promise.resolve(); } - pause(): boolean { - this.sendCommand('-exec-interrupt'); - return true; + sendExecInterrupt(threadId?: number) { + let command = '-exec-interrupt'; + if (threadId) { + command += ` --thread ${threadId}`; + } + return this.sendCommand(command); + } + + sendStackInfoFrame(gdb: GDBBackend, threadId: number, frameId: number): Promise<{ frame: MIFrameInfo }> { + const command = `-stack-info-frame --thread ${threadId} --frame ${frameId}`; + return gdb.sendCommand(command); } sendTargetDetach(): Promise { diff --git a/arduino-debugger-extension/src/node/debug-adapter/arduino-variable-handler.ts b/arduino-debugger-extension/src/node/debug-adapter/arduino-variable-handler.ts new file mode 100644 index 00000000..c3705f95 --- /dev/null +++ b/arduino-debugger-extension/src/node/debug-adapter/arduino-variable-handler.ts @@ -0,0 +1,115 @@ +import * as path from 'path'; +import { DebugProtocol } from "vscode-debugprotocol"; +import { Handles } from 'vscode-debugadapter/lib/handles'; +import { FrameReference, VariableReference } from "cdt-gdb-adapter/dist/GDBDebugSession"; +import { VarManager } from 'cdt-gdb-adapter/dist/varManager'; +import * as mi from 'cdt-gdb-adapter/dist/mi'; +import { ArduinoDebugSession } from "./arduino-debug-session"; +import { ArduinoGDBBackend } from './arduino-gdb-backend'; + +export class ArduinoVariableHandler { + + protected readonly gdb: ArduinoGDBBackend; + protected readonly varMgr: VarManager; + + protected globalHandle: number; + + constructor(protected readonly session: ArduinoDebugSession, + protected frameHandles: Handles, + protected variableHandles: Handles) { + this.gdb = session.arduinoBackend; + this.varMgr = new VarManager(this.gdb); + } + + createGlobalHandle() { + this.globalHandle = this.frameHandles.create({ + threadId: -1, + frameId: -1 + }); + } + + /** TODO */ + async getGlobalVariables(): Promise { + throw new Error('Global variables are not supported yet.'); + const frame = this.frameHandles.get(this.globalHandle); + const symbolInfo: any[] = [] // this.symbolTable.getGlobalVariables(); + const variables: DebugProtocol.Variable[] = []; + + for (const symbol of symbolInfo) { + const name = `global_var_${symbol.name}`; + const variable = await this.getVariables(frame, name, symbol.name, -1); + variables.push(variable); + } + + return variables; + } + + private async getVariables(frame: FrameReference, name: string, expression: string, depth: number): Promise { + let global = this.varMgr.getVar(frame.frameId, frame.threadId, depth, name); + + if (global) { + // Update value if it is already loaded + const vup = await mi.sendVarUpdate(this.gdb, { name }); + const update = vup.changelist[0]; + if (update && update.in_scope === 'true' && update.name === global.varname) { + global.value = update.value; + } + } else { + // create var in GDB and store it in the varMgr + const varCreateResponse = await mi.sendVarCreate(this.gdb, { + name, + frame: 'current', + expression, + }); + + global = this.varMgr.addVar(frame.frameId, frame.threadId, depth, name, true, false, varCreateResponse); + } + + return { + name: expression, + value: (global.value === void 0) ? '' : global.value, + type: global.type, + variablesReference: parseInt(global.numchild, 10) > 0 + ? this.variableHandles.create({ + frameHandle: this.globalHandle, + type: 'object', + varobjName: global.varname, + }) + : 0, + }; + } + + /** TODO */ + async getStaticVariables(frameHandle: number): Promise { + throw new Error('Static variables are not supported yet.'); + const frame = this.frameHandles.get(frameHandle); + const result = await this.gdb.sendStackInfoFrame(this.gdb, frame.threadId, frame.frameId); + const file = path.normalize(result.frame.file || ''); + const symbolInfo: any[] = [] // this.symbolTable.getStaticVariables(file); + const variables: DebugProtocol.Variable[] = []; + + // Fetch stack depth to obtain frameId/threadId/depth tuple + const stackDepth = await mi.sendStackInfoDepth(this.gdb, { maxDepth: 100 }); + const depth = parseInt(stackDepth.depth, 10); + + for (const symbol of symbolInfo) { + const name = `${file}_static_var_${symbol.name}`; + const variable = await this.getVariables(frame, name, symbol.name, depth); + variables.push(variable); + } + + return variables; + } + + async handlePinStatusRequest(): Promise { + const variables: DebugProtocol.Variable[] = []; + variables.push({ + name: "D2", + type: "gpio", + value: "0x00", + variablesReference: 0 + }) + return variables; + } + +} diff --git a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx index c89e2b96..37f0997b 100644 --- a/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx +++ b/arduino-ide-extension/src/browser/arduino-frontend-contribution.tsx @@ -461,7 +461,7 @@ export class ArduinoFrontendContribution implements FrontendApplicationContribut }); registry.registerMenuAction(ArduinoMenus.SKETCH, { commandId: ArduinoCommands.TOGGLE_COMPILE_FOR_DEBUG.id, - label: 'Optimize for Debug', + label: 'Optimize for Debugging', order: '2' }); registry.registerMenuAction(ArduinoMenus.SKETCH, {