mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-07-25 20:26:38 +00:00
Ran first debugging session
This commit is contained in:
parent
e189a8c33e
commit
3d6d2ce814
4
.vscode/arduino.json
vendored
Normal file
4
.vscode/arduino.json
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"board": "arduino:samd:arduino_zero_edbg",
|
||||||
|
"port": "/dev/cu.usbmodem141402"
|
||||||
|
}
|
14
.vscode/c_cpp_properties.json
vendored
Normal file
14
.vscode/c_cpp_properties.json
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Mac",
|
||||||
|
"includePath": [
|
||||||
|
"/Users/csweichel/Library/Arduino15/packages/arduino/tools/**",
|
||||||
|
"/Users/csweichel/Library/Arduino15/packages/arduino/hardware/samd/1.8.4/**"
|
||||||
|
],
|
||||||
|
"forcedInclude": [
|
||||||
|
"/Users/csweichel/Library/Arduino15/packages/arduino/hardware/samd/1.8.4/cores/arduino/Arduino.h"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
14
.vscode/launch.json
vendored
14
.vscode/launch.json
vendored
@ -10,6 +10,13 @@
|
|||||||
"name": "Attach by Process ID",
|
"name": "Attach by Process ID",
|
||||||
"processId": "${command:PickProcess}"
|
"processId": "${command:PickProcess}"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Launch Electron Packager",
|
||||||
|
"program": "${workspaceRoot}/electron/packager/index.js",
|
||||||
|
"cwd": "${workspaceFolder}/electron/packager"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "node",
|
"type": "node",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
@ -99,13 +106,6 @@
|
|||||||
"smartStep": true,
|
"smartStep": true,
|
||||||
"internalConsoleOptions": "openOnSessionStart",
|
"internalConsoleOptions": "openOnSessionStart",
|
||||||
"outputCapture": "std"
|
"outputCapture": "std"
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "node",
|
|
||||||
"request": "launch",
|
|
||||||
"name": "Packager",
|
|
||||||
"program": "${workspaceRoot}/electron/packager/index.js",
|
|
||||||
"cwd": "${workspaceFolder}/electron/packager"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
40
arduino-debugger-extension/package.json
Normal file
40
arduino-debugger-extension/package.json
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"name": "arduino-debugger-extension",
|
||||||
|
"version": "0.0.2",
|
||||||
|
"description": "An extension for debugging Arduino programs",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10.10.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@theia/core": "next",
|
||||||
|
"@theia/debug": "next",
|
||||||
|
|
||||||
|
"cdt-gdb-adapter": "^0.0.14-next.4783033.0",
|
||||||
|
"vscode-debugadapter": "^1.26.0",
|
||||||
|
"vscode-debugprotocol": "^1.26.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"prepare": "yarn run clean && yarn run build",
|
||||||
|
"clean": "rimraf lib",
|
||||||
|
"lint": "tslint -c ./tslint.json --project ./tsconfig.json",
|
||||||
|
"build": "tsc && yarn lint",
|
||||||
|
"watch": "tsc -w"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"rimraf": "^2.6.1",
|
||||||
|
"tslint": "^5.5.0",
|
||||||
|
"typescript": "3.5.1"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"lib",
|
||||||
|
"src",
|
||||||
|
"build",
|
||||||
|
"data"
|
||||||
|
],
|
||||||
|
"theiaExtensions": [
|
||||||
|
{
|
||||||
|
"backend": "lib/node/backend-module"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
0
arduino-debugger-extension/src/common/index.ts
Normal file
0
arduino-debugger-extension/src/common/index.ts
Normal file
2
arduino-debugger-extension/src/common/openocd/gdb.cfg
Normal file
2
arduino-debugger-extension/src/common/openocd/gdb.cfg
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
gdb_port 50000
|
||||||
|
telnet_port 44444
|
12
arduino-debugger-extension/src/common/openocd/openocd.cfg
Normal file
12
arduino-debugger-extension/src/common/openocd/openocd.cfg
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
source [find interface/cmsis-dap.cfg]
|
||||||
|
|
||||||
|
# chip name
|
||||||
|
set CHIPNAME at91samd21g18
|
||||||
|
set ENDIAN little
|
||||||
|
|
||||||
|
# choose a port here
|
||||||
|
set telnet_port 0
|
||||||
|
|
||||||
|
source [find target/at91samdXX.cfg]
|
||||||
|
|
||||||
|
echo "GDB server started"
|
@ -0,0 +1,109 @@
|
|||||||
|
import { injectable } from 'inversify';
|
||||||
|
import { DebugAdapterContribution, DebugAdapterExecutable, DebugAdapterSessionFactory } from '@theia/debug/lib/common/debug-model';
|
||||||
|
import { DebugConfiguration } from "@theia/debug/lib/common/debug-configuration";
|
||||||
|
import { MaybePromise } from "@theia/core/lib/common/types";
|
||||||
|
import { IJSONSchema, IJSONSchemaSnippet } from "@theia/core/lib/common/json-schema";
|
||||||
|
import * as path from 'path';
|
||||||
|
|
||||||
|
@injectable()
|
||||||
|
export class ArduinoDebugAdapterContribution implements DebugAdapterContribution {
|
||||||
|
type = "arduino";
|
||||||
|
|
||||||
|
label = "Arduino";
|
||||||
|
|
||||||
|
languages = ["c", "cpp", "ino"];
|
||||||
|
|
||||||
|
debugAdapterSessionFactory?: DebugAdapterSessionFactory;
|
||||||
|
|
||||||
|
getSchemaAttributes?(): MaybePromise<IJSONSchema[]> {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"required": [
|
||||||
|
"program"
|
||||||
|
],
|
||||||
|
"properties": {
|
||||||
|
"program": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path to the program to be launched",
|
||||||
|
"default": "${workspaceFolder}/${command:askProgramPath}"
|
||||||
|
},
|
||||||
|
"arguments": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Arguments for the program"
|
||||||
|
},
|
||||||
|
"runToMain": {
|
||||||
|
"description": "If enabled the debugger will run until the start of the main function.",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"gdb": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path to gdb",
|
||||||
|
"default": "arm-none-eabi-gdb"
|
||||||
|
},
|
||||||
|
"gdbArguments": {
|
||||||
|
"description": "Additional arguments to pass to GDB command line",
|
||||||
|
"type": "array",
|
||||||
|
"default": []
|
||||||
|
},
|
||||||
|
"gdbServer": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path to gdb server",
|
||||||
|
"default": "pyocd"
|
||||||
|
},
|
||||||
|
"gdbServerArguments": {
|
||||||
|
"description": "Additional arguments to pass to GDB server",
|
||||||
|
"type": "array",
|
||||||
|
"default": []
|
||||||
|
},
|
||||||
|
"objdump": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path to objdump executable",
|
||||||
|
"default": "arm-none-eabi-objdump"
|
||||||
|
},
|
||||||
|
"initCommands": {
|
||||||
|
"description": "Extra gdb commands to run after initialisation",
|
||||||
|
"type": "array",
|
||||||
|
"default": []
|
||||||
|
},
|
||||||
|
"verbose": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Produce verbose log output",
|
||||||
|
"default": "false"
|
||||||
|
},
|
||||||
|
"debugDebugAdapter": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "Start the debug adapter in debug mode (with --inspect-brk)",
|
||||||
|
"default": "false"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
getConfigurationSnippets?(): MaybePromise<IJSONSchemaSnippet[]> {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
provideDebugAdapterExecutable?(config: DebugConfiguration): MaybePromise<DebugAdapterExecutable> {
|
||||||
|
let args: string[] = [];
|
||||||
|
if (!!config.debugDebugAdapter) {
|
||||||
|
args.push('--inspect-brk')
|
||||||
|
}
|
||||||
|
args = args.concat([path.join(__dirname, 'debug-adapter', 'index.js')]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
command: "node",
|
||||||
|
args: args,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
provideDebugConfigurations?(workspaceFolderUri?: string): MaybePromise<DebugConfiguration[]> {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
resolveDebugConfiguration?(config: DebugConfiguration, workspaceFolderUri?: string): MaybePromise<DebugConfiguration> {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
7
arduino-debugger-extension/src/node/backend-module.ts
Normal file
7
arduino-debugger-extension/src/node/backend-module.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { ContainerModule } from 'inversify';
|
||||||
|
import { DebugAdapterContribution } from '@theia/debug/lib/common/debug-model';
|
||||||
|
import { ArduinoDebugAdapterContribution } from './arduino-debug-adapter-contribution';
|
||||||
|
|
||||||
|
export default new ContainerModule((bind, unbind, isBound, rebind) => {
|
||||||
|
bind(DebugAdapterContribution).to(ArduinoDebugAdapterContribution).inSingletonScope();
|
||||||
|
});
|
@ -0,0 +1,151 @@
|
|||||||
|
/*
|
||||||
|
* CMSIS Debug Adapter
|
||||||
|
* Copyright (c) 2017-2019 Marcel Ball
|
||||||
|
* Copyright (c) 2019 Arm Limited
|
||||||
|
*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { EOL } from 'os';
|
||||||
|
import { spawn, ChildProcess } from 'child_process';
|
||||||
|
import { EventEmitter } from 'events';
|
||||||
|
import { dirname } from 'path';
|
||||||
|
import { CmsisRequestArguments } from './cmsis-debug-session';
|
||||||
|
|
||||||
|
const TIMEOUT = 1000 * 10; // 10 seconds
|
||||||
|
|
||||||
|
export abstract class AbstractServer extends EventEmitter {
|
||||||
|
|
||||||
|
protected process?: ChildProcess;
|
||||||
|
protected outBuffer: string = '';
|
||||||
|
protected errBuffer: string = '';
|
||||||
|
protected launchResolve?: () => void;
|
||||||
|
protected launchReject?: (error: any) => void;
|
||||||
|
protected timer?: NodeJS.Timer;
|
||||||
|
|
||||||
|
public spawn(args: CmsisRequestArguments): Promise<void> {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
this.launchResolve = resolve;
|
||||||
|
this.launchReject = reject;
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.timer = setTimeout(() => this.onSpawnError(new Error('Timeout waiting for gdb server to start')), TIMEOUT);
|
||||||
|
|
||||||
|
const command = args.gdbServer || 'gdb-server';
|
||||||
|
const serverArguments = await this.resolveServerArguments(args.gdbServerArguments);
|
||||||
|
this.process = spawn(command, serverArguments, {
|
||||||
|
cwd: dirname(command),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!this.process) {
|
||||||
|
throw new Error('Unable to spawn gdb server');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.process.on('exit', this.onExit.bind(this));
|
||||||
|
this.process.on('error', this.onSpawnError.bind(this));
|
||||||
|
|
||||||
|
if (this.process.stdout) {
|
||||||
|
this.process.stdout.on('data', this.onStdout.bind(this));
|
||||||
|
}
|
||||||
|
if (this.process.stderr) {
|
||||||
|
this.process.stderr.on('data', this.onStderr.bind(this));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.onSpawnError(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public kill() {
|
||||||
|
if (this.process) {
|
||||||
|
this.process.kill('SIGINT');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async resolveServerArguments(serverArguments?: string[]): Promise<string[]> {
|
||||||
|
return serverArguments || [];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onExit(code: number, signal: string) {
|
||||||
|
this.emit('exit', code, signal);
|
||||||
|
|
||||||
|
// Code can be undefined, null or 0 and we want to ignore those values
|
||||||
|
if (!!code) {
|
||||||
|
this.emit('error', `GDB server stopped unexpectedly with exit code ${code}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onSpawnError(error: Error) {
|
||||||
|
if (this.launchReject) {
|
||||||
|
this.clearTimer();
|
||||||
|
this.launchReject(error);
|
||||||
|
this.clearPromises();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onStdout(chunk: string | Buffer) {
|
||||||
|
this.onData(chunk, this.outBuffer, 'stdout');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onStderr(chunk: string | Buffer) {
|
||||||
|
this.onData(chunk, this.errBuffer, 'stderr');
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onData(chunk: string | Buffer, buffer: string, event: string) {
|
||||||
|
buffer += typeof chunk === 'string' ? chunk
|
||||||
|
: chunk.toString('utf8');
|
||||||
|
|
||||||
|
const end = buffer.lastIndexOf('\n');
|
||||||
|
if (end !== -1) {
|
||||||
|
const data = buffer.substring(0, end);
|
||||||
|
this.emit(event, data);
|
||||||
|
this.handleData(data);
|
||||||
|
buffer = buffer.substring(end + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected handleData(data: string) {
|
||||||
|
if (this.launchResolve && this.serverStarted(data)) {
|
||||||
|
this.clearTimer();
|
||||||
|
this.launchResolve();
|
||||||
|
this.clearPromises();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.serverError(data)) {
|
||||||
|
this.emit('error', data.split(EOL)[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected clearTimer() {
|
||||||
|
if (this.timer) {
|
||||||
|
clearTimeout(this.timer);
|
||||||
|
this.timer = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected clearPromises() {
|
||||||
|
this.launchResolve = undefined;
|
||||||
|
this.launchReject = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract serverStarted(data: string): boolean;
|
||||||
|
protected abstract serverError(data: string): boolean;
|
||||||
|
}
|
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* CMSIS Debug Adapter
|
||||||
|
* Copyright (c) 2019 Arm Limited
|
||||||
|
*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { GDBBackend } from 'cdt-gdb-adapter/dist/GDBBackend';
|
||||||
|
import * as mi from './mi';
|
||||||
|
|
||||||
|
export class CmsisBackend extends GDBBackend {
|
||||||
|
|
||||||
|
public get isRunning(): boolean {
|
||||||
|
return !!this.out;
|
||||||
|
}
|
||||||
|
|
||||||
|
public pause() {
|
||||||
|
mi.sendExecInterrupt(this);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,388 @@
|
|||||||
|
/*
|
||||||
|
* CMSIS Debug Adapter
|
||||||
|
* Copyright (c) 2019 Arm Limited
|
||||||
|
*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { normalize } from 'path';
|
||||||
|
import { DebugProtocol } from 'vscode-debugprotocol';
|
||||||
|
import { Logger, logger, InitializedEvent, OutputEvent, Scope, TerminatedEvent } from 'vscode-debugadapter';
|
||||||
|
import { GDBDebugSession, RequestArguments, FrameVariableReference, FrameReference, ObjectVariableReference } from 'cdt-gdb-adapter/dist/GDBDebugSession';
|
||||||
|
import { GDBBackend } from 'cdt-gdb-adapter/dist/GDBBackend';
|
||||||
|
import { CmsisBackend } from './cmsis-backend';
|
||||||
|
import { PyocdServer } from './pyocd-server';
|
||||||
|
import { PortScanner } from './port-scanner';
|
||||||
|
import { SymbolTable } from './symbols';
|
||||||
|
import * as varMgr from 'cdt-gdb-adapter/dist/varManager';
|
||||||
|
import * as mi from './mi';
|
||||||
|
|
||||||
|
export interface CmsisRequestArguments extends RequestArguments {
|
||||||
|
runToMain?: boolean;
|
||||||
|
gdbServer?: string;
|
||||||
|
gdbServerArguments?: string[];
|
||||||
|
objdump?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const GLOBAL_HANDLE_ID = 0xFE;
|
||||||
|
const STATIC_HANDLES_START = 0x010000;
|
||||||
|
const STATIC_HANDLES_FINISH = 0x01FFFF;
|
||||||
|
|
||||||
|
export class CmsisDebugSession extends GDBDebugSession {
|
||||||
|
|
||||||
|
protected gdbServer = new PyocdServer();
|
||||||
|
protected portScanner = new PortScanner();
|
||||||
|
protected symbolTable!: SymbolTable;
|
||||||
|
protected globalHandle!: number;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected createBackend(): GDBBackend {
|
||||||
|
return new CmsisBackend();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async launchRequest(response: DebugProtocol.LaunchResponse, args: CmsisRequestArguments): Promise<void> {
|
||||||
|
try {
|
||||||
|
await this.runSession(args);
|
||||||
|
this.sendResponse(response);
|
||||||
|
} catch (err) {
|
||||||
|
this.sendErrorResponse(response, 1, err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async attachRequest(response: DebugProtocol.AttachResponse, args: CmsisRequestArguments): Promise<void> {
|
||||||
|
try {
|
||||||
|
await this.runSession(args);
|
||||||
|
this.sendResponse(response);
|
||||||
|
} catch (err) {
|
||||||
|
this.sendErrorResponse(response, 1, err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async configurationDoneRequest(response: DebugProtocol.ConfigurationDoneResponse): Promise<void> {
|
||||||
|
try {
|
||||||
|
await mi.sendExecContinue(this.gdb);
|
||||||
|
this.sendResponse(response);
|
||||||
|
} catch (err) {
|
||||||
|
this.sendErrorResponse(response, 100, err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async pauseRequest(response: DebugProtocol.PauseResponse, args: DebugProtocol.PauseArguments): Promise<void> {
|
||||||
|
try {
|
||||||
|
await mi.sendExecInterrupt(this.gdb, args.threadId);
|
||||||
|
this.sendResponse(response);
|
||||||
|
} catch (err) {
|
||||||
|
this.sendErrorResponse(response, 1, err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): Promise<void> {
|
||||||
|
try {
|
||||||
|
this.globalHandle = this.frameHandles.create({
|
||||||
|
threadId: -1,
|
||||||
|
frameId: -1
|
||||||
|
});
|
||||||
|
|
||||||
|
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: args.frameId,
|
||||||
|
}
|
||||||
|
|
||||||
|
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: new Array<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.getGlobalVariables(this.globalHandle);
|
||||||
|
} 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.getStaticVariables(frameHandle);
|
||||||
|
} else if (ref && ref.type === 'frame') {
|
||||||
|
// List variables for current frame
|
||||||
|
response.body.variables = await this.handleVariableRequestFrame(ref);
|
||||||
|
} 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): Promise<void> {
|
||||||
|
try {
|
||||||
|
if (args.context === 'repl') {
|
||||||
|
const command = args.expression;
|
||||||
|
const output = await mi.sendUserInput(this.gdb, command);
|
||||||
|
if (typeof output === 'undefined') {
|
||||||
|
response.body = {
|
||||||
|
result: '',
|
||||||
|
variablesReference: 0
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
response.body = {
|
||||||
|
result: JSON.stringify(output),
|
||||||
|
variablesReference: 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sendResponse(response);
|
||||||
|
} else {
|
||||||
|
return super.evaluateRequest(response, args);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
this.sendErrorResponse(response, 1, err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async disconnectRequest(response: DebugProtocol.DisconnectResponse, args: DebugProtocol.DisconnectArguments): Promise<void> {
|
||||||
|
try {
|
||||||
|
this.stopSession();
|
||||||
|
if (!args || !args.restart) {
|
||||||
|
this.sendEvent(new TerminatedEvent());
|
||||||
|
}
|
||||||
|
this.sendResponse(response);
|
||||||
|
} catch (err) {
|
||||||
|
this.sendErrorResponse(response, 1, err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async runSession(args: CmsisRequestArguments): Promise<void> {
|
||||||
|
logger.setup(args.verbose ? Logger.LogLevel.Verbose : Logger.LogLevel.Warn, args.logFile || false);
|
||||||
|
|
||||||
|
this.gdb.on('consoleStreamOutput', (output, category) => this.sendEvent(new OutputEvent(output, category)));
|
||||||
|
this.gdb.on('execAsync', (resultClass, resultData) => this.handleGDBAsync(resultClass, resultData));
|
||||||
|
this.gdb.on('notifyAsync', (resultClass, resultData) => this.handleGDBNotify(resultClass, resultData));
|
||||||
|
|
||||||
|
// gdb server has main info channel on stderr
|
||||||
|
this.gdbServer.on('stderr', data => {
|
||||||
|
this.sendEvent(new OutputEvent(data, 'stdout'))
|
||||||
|
});
|
||||||
|
this.gdbServer.on('error', message => {
|
||||||
|
this.sendEvent(new TerminatedEvent());
|
||||||
|
throw message;
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.symbolTable = new SymbolTable(args.program, args.objdump);
|
||||||
|
await this.symbolTable.loadSymbols();
|
||||||
|
} catch (error) {
|
||||||
|
this.sendEvent(new OutputEvent(`Unable to load debug symbols: ${error.message}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
const port = await this.portScanner.findFreePort();
|
||||||
|
if (!port) {
|
||||||
|
throw new Error('Unable to find a free port to use for debugging');
|
||||||
|
}
|
||||||
|
this.sendEvent(new OutputEvent(`Selected port ${port} for debugging`));
|
||||||
|
|
||||||
|
const remote = `localhost:${port}`;
|
||||||
|
|
||||||
|
// Set gdb arguments
|
||||||
|
if (!args.gdbArguments) {
|
||||||
|
args.gdbArguments = [];
|
||||||
|
}
|
||||||
|
args.gdbArguments.push('-q', args.program);
|
||||||
|
|
||||||
|
// Set gdb server arguments
|
||||||
|
if (!args.gdbServerArguments) {
|
||||||
|
args.gdbServerArguments = [];
|
||||||
|
}
|
||||||
|
args.gdbServerArguments.push('--port', port.toString());
|
||||||
|
|
||||||
|
// Start gdb client and server
|
||||||
|
this.progressEvent(0, 'Starting Debugger');
|
||||||
|
await this.gdbServer.spawn(args);
|
||||||
|
await this.spawn(args);
|
||||||
|
|
||||||
|
// Send commands
|
||||||
|
await mi.sendTargetAsyncOn(this.gdb);
|
||||||
|
await mi.sendTargetSelectRemote(this.gdb, remote);
|
||||||
|
await mi.sendMonitorResetHalt(this.gdb);
|
||||||
|
this.sendEvent(new OutputEvent(`Attached to debugger on port ${port}`));
|
||||||
|
|
||||||
|
// Download image
|
||||||
|
const progressListener = (percent: number) => this.progressEvent(percent, 'Loading Image');
|
||||||
|
progressListener(0);
|
||||||
|
this.gdbServer.on('progress', progressListener);
|
||||||
|
await mi.sendTargetDownload(this.gdb);
|
||||||
|
this.gdbServer.off('progress', progressListener);
|
||||||
|
progressListener(100);
|
||||||
|
|
||||||
|
// Halt after image download
|
||||||
|
await mi.sendMonitorResetHalt(this.gdb);
|
||||||
|
await this.gdb.sendEnablePrettyPrint();
|
||||||
|
|
||||||
|
if (args.runToMain === true) {
|
||||||
|
await mi.sendBreakOnFunction(this.gdb);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sendEvent(new OutputEvent(`Image loaded: ${args.program}`));
|
||||||
|
this.sendEvent(new InitializedEvent());
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getGlobalVariables(frameHandle: number): Promise<DebugProtocol.Variable[]> {
|
||||||
|
const frame = this.frameHandles.get(frameHandle);
|
||||||
|
const symbolInfo = 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 getStaticVariables(frameHandle: number): Promise<DebugProtocol.Variable[]> {
|
||||||
|
const frame = this.frameHandles.get(frameHandle);
|
||||||
|
const result = await mi.sendStackInfoFrame(this.gdb, frame.threadId, frame.frameId);
|
||||||
|
const file = normalize(result.frame.file || '');
|
||||||
|
const symbolInfo = 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getVariables(frame: FrameReference, name: string, expression: string, depth: number): Promise<DebugProtocol.Variable> {
|
||||||
|
let global = 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 = 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private progressEvent(percent: number, message: string) {
|
||||||
|
this.sendEvent(new OutputEvent('progress', 'telemetry', {
|
||||||
|
percent,
|
||||||
|
message
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async stopSession() {
|
||||||
|
// Pause debugging
|
||||||
|
if (this.isRunning) {
|
||||||
|
// Need to pause first
|
||||||
|
const waitPromise = new Promise(resolve => this.waitPaused = resolve);
|
||||||
|
this.gdb.pause();
|
||||||
|
await waitPromise;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detach
|
||||||
|
if ((this.gdb as CmsisBackend).isRunning) {
|
||||||
|
try {
|
||||||
|
await mi.sendTargetDetach(this.gdb);
|
||||||
|
} catch (e) {
|
||||||
|
// Need to catch here as the command result being returned will never exist as it's detached
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop gdb client and server
|
||||||
|
try {
|
||||||
|
await this.gdb.sendGDBExit();
|
||||||
|
} catch (e) {
|
||||||
|
// Need to catch here in case the connection has already been closed
|
||||||
|
}
|
||||||
|
this.gdbServer.kill();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async shutdown() {
|
||||||
|
await this.stopSession();
|
||||||
|
super.shutdown();
|
||||||
|
}
|
||||||
|
}
|
34
arduino-debugger-extension/src/node/debug-adapter/index.ts
Normal file
34
arduino-debugger-extension/src/node/debug-adapter/index.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* CMSIS Debug Adapter
|
||||||
|
* Copyright (c) 2019 Arm Limited
|
||||||
|
*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as process from 'process';
|
||||||
|
import { logger } from 'vscode-debugadapter/lib/logger';
|
||||||
|
import { CmsisDebugSession } from './cmsis-debug-session';
|
||||||
|
|
||||||
|
process.on('uncaughtException', (err: any) => {
|
||||||
|
logger.error(JSON.stringify(err));
|
||||||
|
});
|
||||||
|
|
||||||
|
CmsisDebugSession.run(CmsisDebugSession);
|
80
arduino-debugger-extension/src/node/debug-adapter/mi.ts
Normal file
80
arduino-debugger-extension/src/node/debug-adapter/mi.ts
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
* CMSIS Debug Adapter
|
||||||
|
* Copyright (c) 2019 Arm Limited
|
||||||
|
*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { MIFrameInfo } from 'cdt-gdb-adapter/dist/mi';
|
||||||
|
import { GDBBackend } from 'cdt-gdb-adapter/dist/GDBBackend';
|
||||||
|
|
||||||
|
export function sendTargetAsyncOn(gdb: GDBBackend) {
|
||||||
|
const set = 'target-async on';
|
||||||
|
return gdb.sendGDBSet(set);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sendMonitorResetHalt(gdb: GDBBackend) {
|
||||||
|
const command = '-interpreter-exec console "monitor reset halt"';
|
||||||
|
return gdb.sendCommand(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sendTargetSelectRemote(gdb: GDBBackend, remote: string) {
|
||||||
|
const command = `-target-select extended-remote ${remote}`;
|
||||||
|
return gdb.sendCommand(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sendTargetDownload(gdb: GDBBackend) {
|
||||||
|
const command = '-target-download';
|
||||||
|
return gdb.sendCommand(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sendBreakOnFunction(gdb: GDBBackend, fn: string = 'main') {
|
||||||
|
const command = `-break-insert -t --function ${fn}`;
|
||||||
|
return gdb.sendCommand(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sendExecInterrupt(gdb: GDBBackend, threadId?: number) {
|
||||||
|
let command = '-exec-interrupt';
|
||||||
|
if (threadId) {
|
||||||
|
command += ` --thread ${threadId}`;
|
||||||
|
}
|
||||||
|
return gdb.sendCommand(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sendStackInfoFrame(gdb: GDBBackend, threadId: number, frameId: number): Promise<{frame: MIFrameInfo}> {
|
||||||
|
const command = `-stack-info-frame --thread ${threadId} --frame ${frameId}`;
|
||||||
|
return gdb.sendCommand(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sendUserInput(gdb: GDBBackend, command: string): Promise<any> {
|
||||||
|
if (!command.startsWith('-')) {
|
||||||
|
command = `interpreter-exec console "${command}"`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return gdb.sendCommand(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sendTargetDetach(gdb: GDBBackend) {
|
||||||
|
const command = '-target-detach';
|
||||||
|
return gdb.sendCommand(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
export * from 'cdt-gdb-adapter/dist/mi';
|
@ -0,0 +1,51 @@
|
|||||||
|
import { AbstractServer } from './abstract-server';
|
||||||
|
import { PortScanner } from './port-scanner';
|
||||||
|
|
||||||
|
const LAUNCH_REGEX = /GDB server started/;
|
||||||
|
const ERROR_REGEX = /:ERROR:gdbserver:/;
|
||||||
|
const PERCENT_MULTIPLIER = 100 / 40; // pyOCD outputs 40 markers for progress
|
||||||
|
|
||||||
|
export class OpenocdServer extends AbstractServer {
|
||||||
|
|
||||||
|
protected portScanner = new PortScanner();
|
||||||
|
protected progress = 0;
|
||||||
|
|
||||||
|
protected async resolveServerArguments(serverArguments?: string[]): Promise<string[]> {
|
||||||
|
if (!serverArguments) {
|
||||||
|
serverArguments = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const telnetPort = await this.portScanner.findFreePort(4444);
|
||||||
|
|
||||||
|
if (!telnetPort) {
|
||||||
|
return serverArguments;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
...serverArguments,
|
||||||
|
'--telnet-port',
|
||||||
|
telnetPort.toString()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onStdout(chunk: string | Buffer) {
|
||||||
|
super.onStdout(chunk);
|
||||||
|
const buffer = typeof chunk === 'string' ? chunk : chunk.toString('utf8');
|
||||||
|
const match = buffer.match(/=/g);
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
this.progress += match.length;
|
||||||
|
const percent = Math.round(this.progress * PERCENT_MULTIPLIER);
|
||||||
|
this.emit('progress', percent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected serverStarted(data: string): boolean {
|
||||||
|
return LAUNCH_REGEX.test(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected serverError(data: string): boolean {
|
||||||
|
return ERROR_REGEX.test(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,192 @@
|
|||||||
|
/*
|
||||||
|
* CMSIS Debug Adapter
|
||||||
|
* Copyright (c) 2016 Zoujie
|
||||||
|
* Copyright (c) 2019 Arm Limited
|
||||||
|
*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { exec } from 'child_process';
|
||||||
|
|
||||||
|
const maxBuffer = 2 * 1024 * 1024;
|
||||||
|
|
||||||
|
export class PortScanner {
|
||||||
|
|
||||||
|
public async findFreePort(start: number = 50000, length: number = 100): Promise<number | undefined> {
|
||||||
|
const fn = this.getFunction().bind(this);
|
||||||
|
|
||||||
|
for (let i = start; i <= start + length; i++) {
|
||||||
|
try {
|
||||||
|
// Try to find pid of port
|
||||||
|
await fn(i);
|
||||||
|
} catch (_e) {
|
||||||
|
// Port is free when pid not found
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getFunction(): (port: number) => Promise<number> {
|
||||||
|
switch (process.platform) {
|
||||||
|
case 'darwin':
|
||||||
|
case 'freebsd':
|
||||||
|
case 'sunos': {
|
||||||
|
return this.darwin;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'linux': {
|
||||||
|
return this.linux;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'win32': {
|
||||||
|
return this.windows;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => Promise.resolve(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async darwin(port: number): Promise<number> {
|
||||||
|
const result = await this.execute('netstat -anv -p TCP && netstat -anv -p UDP');
|
||||||
|
|
||||||
|
// Replace header
|
||||||
|
const data = this.stripLine(result.toString(), 2);
|
||||||
|
|
||||||
|
const found = this.extractColumns(data, [0, 3, 8], 10)
|
||||||
|
.filter(row => !!String(row[0]).match(/^(udp|tcp)/))
|
||||||
|
.find(row => {
|
||||||
|
const matches = String(row[1]).match(/\.(\d+)$/);
|
||||||
|
return (matches && matches[1] === String(port));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (found && found[2].length) {
|
||||||
|
return parseInt(found[2], 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`pid of port (${port}) not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async linux(port: number): Promise<number> {
|
||||||
|
// netstat -p ouputs warning if user is no-root
|
||||||
|
const result = await this.execute('netstat -tunlp');
|
||||||
|
|
||||||
|
// Replace header
|
||||||
|
const data = this.stripLine(result.toString(), 2);
|
||||||
|
|
||||||
|
const columns = this.extractColumns(data, [3, 6], 7)
|
||||||
|
.find(column => {
|
||||||
|
const matches = String(column[0]).match(/:(\d+)$/);
|
||||||
|
return (matches && matches[1] === String(port));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (columns && columns[1]) {
|
||||||
|
const pid = columns[1].split('/', 1)[0];
|
||||||
|
|
||||||
|
if (pid.length) {
|
||||||
|
return parseInt(pid, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`pid of port (${port}) not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async windows(port: number): Promise<number> {
|
||||||
|
const result = await this.execute('netstat -ano');
|
||||||
|
|
||||||
|
// Replace header
|
||||||
|
const data = this.stripLine(result.toString(), 4);
|
||||||
|
|
||||||
|
const columns = this.extractColumns(data, [1, 4], 5)
|
||||||
|
.find(column => {
|
||||||
|
const matches = String(column[0]).match(/:(\d+)$/);
|
||||||
|
return (matches && matches[1] === String(port));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (columns && columns[1].length && parseInt(columns[1], 10) > 0) {
|
||||||
|
return parseInt(columns[1], 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`pid of port (${port}) not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private execute(cmd: string): Promise<string> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
exec(cmd, {
|
||||||
|
maxBuffer,
|
||||||
|
windowsHide: true
|
||||||
|
}, (error: Error | null, stdout: string) => {
|
||||||
|
if (error) {
|
||||||
|
return reject(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolve(stdout);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private stripLine(text: string, num: number): string {
|
||||||
|
let idx = 0;
|
||||||
|
|
||||||
|
while (num-- > 0) {
|
||||||
|
const nIdx = text.indexOf('\n', idx);
|
||||||
|
if (nIdx >= 0) {
|
||||||
|
idx = nIdx + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return idx > 0 ? text.substring(idx) : text;
|
||||||
|
}
|
||||||
|
|
||||||
|
private extractColumns(text: string, idxes: number[], max: number): string[][] {
|
||||||
|
const lines = text.split(/(\r\n|\n|\r)/);
|
||||||
|
const columns: string[][] = [];
|
||||||
|
|
||||||
|
if (!max) {
|
||||||
|
max = Math.max.apply(null, idxes) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.forEach(line => {
|
||||||
|
const cols = this.split(line, max);
|
||||||
|
const column: string[] = [];
|
||||||
|
|
||||||
|
idxes.forEach(idx => {
|
||||||
|
column.push(cols[idx] || '');
|
||||||
|
});
|
||||||
|
|
||||||
|
columns.push(column);
|
||||||
|
});
|
||||||
|
|
||||||
|
return columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
private split(line: string, max: number): string[] {
|
||||||
|
const cols = line.trim().split(/\s+/);
|
||||||
|
|
||||||
|
if (cols.length > max) {
|
||||||
|
cols[max - 1] = cols.slice(max - 1).join(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
return cols;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
/*
|
||||||
|
* CMSIS Debug Adapter
|
||||||
|
* Copyright (c) 2019 Arm Limited
|
||||||
|
*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { AbstractServer } from './abstract-server';
|
||||||
|
import { PortScanner } from './port-scanner';
|
||||||
|
|
||||||
|
const LAUNCH_REGEX = /GDB server started/;
|
||||||
|
const ERROR_REGEX = /:ERROR:gdbserver:/;
|
||||||
|
const PERCENT_MULTIPLIER = 100 / 40; // pyOCD outputs 40 markers for progress
|
||||||
|
|
||||||
|
export class PyocdServer extends AbstractServer {
|
||||||
|
|
||||||
|
protected portScanner = new PortScanner();
|
||||||
|
protected progress = 0;
|
||||||
|
|
||||||
|
protected async resolveServerArguments(serverArguments?: string[]): Promise<string[]> {
|
||||||
|
if (!serverArguments) {
|
||||||
|
serverArguments = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const telnetPort = await this.portScanner.findFreePort(4444);
|
||||||
|
|
||||||
|
if (!telnetPort) {
|
||||||
|
return serverArguments;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
...serverArguments,
|
||||||
|
'--telnet-port',
|
||||||
|
telnetPort.toString()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected onStdout(chunk: string | Buffer) {
|
||||||
|
super.onStdout(chunk);
|
||||||
|
const buffer = typeof chunk === 'string' ? chunk : chunk.toString('utf8');
|
||||||
|
const match = buffer.match(/=/g);
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
this.progress += match.length;
|
||||||
|
const percent = Math.round(this.progress * PERCENT_MULTIPLIER);
|
||||||
|
this.emit('progress', percent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected serverStarted(data: string): boolean {
|
||||||
|
return LAUNCH_REGEX.test(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected serverError(data: string): boolean {
|
||||||
|
return ERROR_REGEX.test(data);
|
||||||
|
}
|
||||||
|
}
|
151
arduino-debugger-extension/src/node/debug-adapter/symbols.ts
Normal file
151
arduino-debugger-extension/src/node/debug-adapter/symbols.ts
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
/*
|
||||||
|
* CMSIS Debug Adapter
|
||||||
|
* Copyright (c) 2017-2019 Marcel Ball
|
||||||
|
* Copyright (c) 2019 Arm Limited
|
||||||
|
*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { spawnSync } from 'child_process';
|
||||||
|
import { platform, EOL } from 'os';
|
||||||
|
import { dirname, normalize, basename } from 'path';
|
||||||
|
|
||||||
|
export enum SymbolType {
|
||||||
|
Function,
|
||||||
|
File,
|
||||||
|
Object,
|
||||||
|
Normal
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum SymbolScope {
|
||||||
|
Local,
|
||||||
|
Global,
|
||||||
|
Neither,
|
||||||
|
Both
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SymbolInformation {
|
||||||
|
address: number;
|
||||||
|
length: number;
|
||||||
|
name: string;
|
||||||
|
section: string;
|
||||||
|
type: SymbolType;
|
||||||
|
scope: SymbolScope;
|
||||||
|
file?: string;
|
||||||
|
hidden: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_OBJDUMP = platform() !== 'win32' ? 'arm-none-eabi-objdump' : 'arm-none-eabi-objdump.exe';
|
||||||
|
const SYMBOL_REGEX = /^([0-9a-f]{8})\s([lg\ !])([w\ ])([C\ ])([W\ ])([I\ ])([dD\ ])([FfO\ ])\s([^\s]+)\s([0-9a-f]+)\s(.*)\r?$/;
|
||||||
|
|
||||||
|
const TYPE_MAP: { [id: string]: SymbolType } = {
|
||||||
|
'F': SymbolType.Function,
|
||||||
|
'f': SymbolType.File,
|
||||||
|
'O': SymbolType.Object,
|
||||||
|
' ': SymbolType.Normal
|
||||||
|
};
|
||||||
|
|
||||||
|
const SCOPE_MAP: { [id: string]: SymbolScope } = {
|
||||||
|
'l': SymbolScope.Local,
|
||||||
|
'g': SymbolScope.Global,
|
||||||
|
' ': SymbolScope.Neither,
|
||||||
|
'!': SymbolScope.Both
|
||||||
|
};
|
||||||
|
|
||||||
|
export class SymbolTable {
|
||||||
|
|
||||||
|
private symbols: SymbolInformation[] = [];
|
||||||
|
|
||||||
|
constructor(private program: string, private objdump: string = DEFAULT_OBJDUMP) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public async loadSymbols(): Promise<void> {
|
||||||
|
const results = await this.execute();
|
||||||
|
const output = results.toString();
|
||||||
|
const lines = output.split(EOL);
|
||||||
|
let currentFile: string | undefined;
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
const match = line.match(SYMBOL_REGEX);
|
||||||
|
if (match) {
|
||||||
|
if (match[7] === 'd' && match[8] === 'f') {
|
||||||
|
currentFile = match[11].trim();
|
||||||
|
}
|
||||||
|
const type = TYPE_MAP[match[8]];
|
||||||
|
const scope = SCOPE_MAP[match[2]];
|
||||||
|
let name = match[11].trim();
|
||||||
|
let hidden = false;
|
||||||
|
|
||||||
|
if (name.startsWith('.hidden')) {
|
||||||
|
name = name.substring(7).trim();
|
||||||
|
hidden = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.symbols.push({
|
||||||
|
address: parseInt(match[1], 16),
|
||||||
|
type: type,
|
||||||
|
scope: scope,
|
||||||
|
section: match[9].trim(),
|
||||||
|
length: parseInt(match[10], 16),
|
||||||
|
name: name,
|
||||||
|
file: scope === SymbolScope.Local ? currentFile : undefined,
|
||||||
|
hidden: hidden
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getGlobalVariables(): SymbolInformation[] {
|
||||||
|
const matches = this.symbols.filter(s => s.type === SymbolType.Object && s.scope === SymbolScope.Global);
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getStaticVariables(file: string): SymbolInformation[] {
|
||||||
|
return this.symbols.filter(s =>
|
||||||
|
s.type === SymbolType.Object // Only load objects
|
||||||
|
&& s.scope === SymbolScope.Local // Scoped to this file
|
||||||
|
&& !s.name.startsWith('.') // Ignore names beginning with '.'
|
||||||
|
&& (normalize(s.file || '') === normalize(file) || normalize(s.file || '') === basename(file))); // Match full path or file name
|
||||||
|
}
|
||||||
|
|
||||||
|
private execute(): Promise<string> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
const { stdout, stderr } = spawnSync(this.objdump, [
|
||||||
|
'--syms',
|
||||||
|
this.program
|
||||||
|
], {
|
||||||
|
cwd: dirname(this.objdump),
|
||||||
|
windowsHide: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const error = stderr.toString('utf8');
|
||||||
|
if (error) {
|
||||||
|
return reject(new Error(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(stdout.toString('utf8'));
|
||||||
|
} catch (error) {
|
||||||
|
return reject(new Error(error));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
31
arduino-debugger-extension/tsconfig.json
Normal file
31
arduino-debugger-extension/tsconfig.json
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"noEmitOnError": true,
|
||||||
|
"noImplicitThis": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"downlevelIteration": true,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"module": "commonjs",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"target": "es6",
|
||||||
|
"outDir": "lib",
|
||||||
|
"lib": [
|
||||||
|
"es6",
|
||||||
|
"dom"
|
||||||
|
],
|
||||||
|
"jsx": "react",
|
||||||
|
"sourceMap": true,
|
||||||
|
"skipLibCheck": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
"../node_modules/@theia/monaco/src/typings/monaco/index.d.ts"
|
||||||
|
]
|
||||||
|
}
|
37
arduino-debugger-extension/tslint.json
Normal file
37
arduino-debugger-extension/tslint.json
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"rules": {
|
||||||
|
"class-name": true,
|
||||||
|
"comment-format": [true, "check-space"],
|
||||||
|
"curly": false,
|
||||||
|
"forin": false,
|
||||||
|
"indent": [true, "spaces"],
|
||||||
|
"max-line-length": [true, 180],
|
||||||
|
"no-trailing-whitespace": false,
|
||||||
|
"no-unused-expression": true,
|
||||||
|
"no-var-keyword": true,
|
||||||
|
"one-line": [true,
|
||||||
|
"check-open-brace",
|
||||||
|
"check-catch",
|
||||||
|
"check-else",
|
||||||
|
"check-whitespace"
|
||||||
|
],
|
||||||
|
"radix": true,
|
||||||
|
"trailing-comma": [false],
|
||||||
|
"triple-equals": [true, "allow-null-check"],
|
||||||
|
"typedef-whitespace": [true, {
|
||||||
|
"call-signature": "nospace",
|
||||||
|
"index-signature": "nospace",
|
||||||
|
"parameter": "nospace",
|
||||||
|
"property-declaration": "nospace",
|
||||||
|
"variable-declaration": "nospace"
|
||||||
|
}],
|
||||||
|
"variable-name": false,
|
||||||
|
"whitespace": [true,
|
||||||
|
"check-branch",
|
||||||
|
"check-decl",
|
||||||
|
"check-operator",
|
||||||
|
"check-separator",
|
||||||
|
"check-type"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
@ -19,14 +19,15 @@
|
|||||||
"@theia/terminal": "next",
|
"@theia/terminal": "next",
|
||||||
"@theia/workspace": "next",
|
"@theia/workspace": "next",
|
||||||
"@theia/textmate-grammars": "next",
|
"@theia/textmate-grammars": "next",
|
||||||
"arduino-ide-extension": "0.0.4"
|
"arduino-ide-extension": "0.0.4",
|
||||||
|
"arduino-debugger-extension": "0.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@theia/cli": "next"
|
"@theia/cli": "next"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prepare": "theia build --mode development",
|
"prepare": "theia build --mode development",
|
||||||
"start": "theia start",
|
"start": "theia start --plugins=local-dir:../",
|
||||||
"watch": "theia build --watch --mode development"
|
"watch": "theia build --watch --mode development"
|
||||||
},
|
},
|
||||||
"theia": {
|
"theia": {
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
},
|
},
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"arduino-ide-extension",
|
"arduino-ide-extension",
|
||||||
|
"arduino-debugger-extension",
|
||||||
"electron-app",
|
"electron-app",
|
||||||
"browser-app"
|
"browser-app"
|
||||||
]
|
]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user