mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-06-05 03:36:35 +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",
|
||||
"processId": "${command:PickProcess}"
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Launch Electron Packager",
|
||||
"program": "${workspaceRoot}/electron/packager/index.js",
|
||||
"cwd": "${workspaceFolder}/electron/packager"
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
@ -99,13 +106,6 @@
|
||||
"smartStep": true,
|
||||
"internalConsoleOptions": "openOnSessionStart",
|
||||
"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/workspace": "next",
|
||||
"@theia/textmate-grammars": "next",
|
||||
"arduino-ide-extension": "0.0.4"
|
||||
"arduino-ide-extension": "0.0.4",
|
||||
"arduino-debugger-extension": "0.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@theia/cli": "next"
|
||||
},
|
||||
"scripts": {
|
||||
"prepare": "theia build --mode development",
|
||||
"start": "theia start",
|
||||
"start": "theia start --plugins=local-dir:../",
|
||||
"watch": "theia build --watch --mode development"
|
||||
},
|
||||
"theia": {
|
||||
|
@ -20,6 +20,7 @@
|
||||
},
|
||||
"workspaces": [
|
||||
"arduino-ide-extension",
|
||||
"arduino-debugger-extension",
|
||||
"electron-app",
|
||||
"browser-app"
|
||||
]
|
||||
|
Loading…
x
Reference in New Issue
Block a user