Added basic support for global and static variables

This commit is contained in:
Miro Spönemann 2020-02-24 14:33:51 +01:00
parent acf7b6a8da
commit a72533b208
6 changed files with 223 additions and 33 deletions

View File

@ -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);
}
});

View File

@ -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) {

View File

@ -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<void> {
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<void> {
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<void> {
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<void> {
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);
}
}
}

View File

@ -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<void> {

View File

@ -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<FrameReference>,
protected variableHandles: Handles<VariableReference>) {
this.gdb = session.arduinoBackend;
this.varMgr = new VarManager(this.gdb);
}
createGlobalHandle() {
this.globalHandle = this.frameHandles.create({
threadId: -1,
frameId: -1
});
}
/** TODO */
async getGlobalVariables(): Promise<DebugProtocol.Variable[]> {
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<DebugProtocol.Variable> {
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) ? '<unknown>' : 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<DebugProtocol.Variable[]> {
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<DebugProtocol.Variable[]> {
const variables: DebugProtocol.Variable[] = [];
variables.push({
name: "D2",
type: "gpio",
value: "0x00",
variablesReference: 0
})
return variables;
}
}

View File

@ -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, {