mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-07-15 15:26:32 +00:00
Removed unused CMSIS code
This commit is contained in:
parent
a72533b208
commit
beb529cf15
@ -1,164 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
serverErrorEmitted = false;
|
|
||||||
|
|
||||||
get isRunning(): boolean {
|
|
||||||
return !!this.process && !this.process.killed;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
if (!command) {
|
|
||||||
throw new Error('Missing parameter: gdbServer');
|
|
||||||
}
|
|
||||||
const varRegexp = /\$\{.*\}/;
|
|
||||||
if (varRegexp.test(command)) {
|
|
||||||
throw new Error(`Unresolved variable: ${command}`)
|
|
||||||
}
|
|
||||||
const serverArguments = await this.resolveServerArguments(args);
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
kill() {
|
|
||||||
if (this.process) {
|
|
||||||
this.process.kill('SIGINT');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async resolveServerArguments(req: CmsisRequestArguments): Promise<string[]> {
|
|
||||||
return req.gdbServerArguments || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
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.serverErrorEmitted) {
|
|
||||||
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]);
|
|
||||||
this.serverErrorEmitted = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,458 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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, ErrorDestination } from 'vscode-debugadapter';
|
|
||||||
import { GDBDebugSession, RequestArguments, FrameVariableReference, FrameReference } 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 { VarManager } from 'cdt-gdb-adapter/dist/varManager';
|
|
||||||
import * as mi from './mi';
|
|
||||||
import { OpenocdServer } from './openocd-server';
|
|
||||||
|
|
||||||
export interface CmsisRequestArguments extends RequestArguments {
|
|
||||||
runToMain?: boolean;
|
|
||||||
gdbServer?: string;
|
|
||||||
gdbServerArguments?: string[];
|
|
||||||
gdbServerPort?: number;
|
|
||||||
objdump?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const GLOBAL_HANDLE_ID = 0xFE;
|
|
||||||
const STATIC_HANDLES_START = 0x010000;
|
|
||||||
const STATIC_HANDLES_FINISH = 0x01FFFF;
|
|
||||||
|
|
||||||
export class CmsisDebugSession extends GDBDebugSession {
|
|
||||||
|
|
||||||
protected readonly gdbServer = new OpenocdServer();
|
|
||||||
protected readonly portScanner = new PortScanner();
|
|
||||||
protected symbolTable!: SymbolTable;
|
|
||||||
protected globalHandle!: number;
|
|
||||||
protected varMgr: VarManager;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.addProcessListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected addProcessListeners(): void {
|
|
||||||
process.on('exit', () => {
|
|
||||||
if (this.gdbServer.isRunning) {
|
|
||||||
this.gdbServer.kill();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const signalHandler = () => {
|
|
||||||
if (this.gdbServer.isRunning) {
|
|
||||||
this.gdbServer.kill();
|
|
||||||
}
|
|
||||||
process.exit();
|
|
||||||
}
|
|
||||||
process.on('SIGINT', signalHandler);
|
|
||||||
process.on('SIGTERM', signalHandler);
|
|
||||||
process.on('SIGHUP', signalHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected createBackend(): GDBBackend {
|
|
||||||
const gdb = new CmsisBackend();
|
|
||||||
this.varMgr = new VarManager(gdb);
|
|
||||||
return gdb;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async launchRequest(response: DebugProtocol.LaunchResponse, args: CmsisRequestArguments): Promise<void> {
|
|
||||||
try {
|
|
||||||
await this.runSession(args);
|
|
||||||
this.sendResponse(response);
|
|
||||||
} catch (err) {
|
|
||||||
const message = `Failed to launch the debugger:\n${err.message}`;
|
|
||||||
this.sendErrorResponse(response, 1, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async attachRequest(response: DebugProtocol.AttachResponse, args: CmsisRequestArguments): Promise<void> {
|
|
||||||
try {
|
|
||||||
await this.runSession(args);
|
|
||||||
this.sendResponse(response);
|
|
||||||
} catch (err) {
|
|
||||||
const message = `Failed to attach the debugger:\n${err.message}`;
|
|
||||||
this.sendErrorResponse(response, 1, 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 async setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments): Promise<void> {
|
|
||||||
await super.setBreakPointsRequest(response, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void {
|
|
||||||
try {
|
|
||||||
const frame: FrameVariableReference = {
|
|
||||||
type: 'frame',
|
|
||||||
frameHandle: args.frameId,
|
|
||||||
};
|
|
||||||
// const pins: ObjectVariableReference = {
|
|
||||||
// type: "object",
|
|
||||||
// varobjName: "__pins",
|
|
||||||
// frameHandle: 42000,
|
|
||||||
// }
|
|
||||||
|
|
||||||
response.body = {
|
|
||||||
scopes: [
|
|
||||||
// new Scope('Pins', this.variableHandles.create(pins), false),
|
|
||||||
new Scope('Local', this.variableHandles.create(frame), false),
|
|
||||||
new Scope('Global', GLOBAL_HANDLE_ID, false),
|
|
||||||
new Scope('Static', STATIC_HANDLES_START + parseInt(args.frameId as any, 10), false)
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
this.sendResponse(response);
|
|
||||||
} catch (err) {
|
|
||||||
this.sendErrorResponse(response, 1, err.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected async variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): Promise<void> {
|
|
||||||
try {
|
|
||||||
response.body = {
|
|
||||||
variables: 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.varobjName === '__pins') {
|
|
||||||
response.body.variables = await this.handlePinStatusRequest();
|
|
||||||
} else if (ref && ref.type === 'object') {
|
|
||||||
// List data under any variable
|
|
||||||
response.body.variables = await this.handleVariableRequestObject(ref);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.sendResponse(response);
|
|
||||||
} catch (err) {
|
|
||||||
this.sendErrorResponse(response, 1, err.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
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')));
|
|
||||||
const gdbServerErrors: any[] = []
|
|
||||||
const gdbServerErrorAccumulator = (message: any) => gdbServerErrors.push(message);
|
|
||||||
this.gdbServer.on('error', gdbServerErrorAccumulator);
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.symbolTable = new SymbolTable(args.program, args.objdump);
|
|
||||||
await this.symbolTable.loadSymbols();
|
|
||||||
} catch (error) {
|
|
||||||
throw new Error(`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.gdbServerPort = port;
|
|
||||||
|
|
||||||
// Start gdb client and server
|
|
||||||
this.progressEvent(0, 'Starting Debugger');
|
|
||||||
this.sendEvent(new OutputEvent(`Starting debugger: ${JSON.stringify(args)}`));
|
|
||||||
await this.gdbServer.spawn(args);
|
|
||||||
await this.spawn(args);
|
|
||||||
this.checkServerErrors(gdbServerErrors);
|
|
||||||
|
|
||||||
// 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}`));
|
|
||||||
this.checkServerErrors(gdbServerErrors);
|
|
||||||
|
|
||||||
// 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.removeListener('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.checkServerErrors(gdbServerErrors);
|
|
||||||
this.sendEvent(new OutputEvent(`Image loaded: ${args.program}`));
|
|
||||||
this.sendEvent(new InitializedEvent());
|
|
||||||
|
|
||||||
this.gdbServer.removeListener('error', gdbServerErrorAccumulator);
|
|
||||||
this.gdbServer.on('error', message => {
|
|
||||||
logger.error(JSON.stringify(message));
|
|
||||||
if (!this.gdbServer.serverErrorEmitted) {
|
|
||||||
this.sendEvent(new TerminatedEvent());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private checkServerErrors(errors: any[]): void {
|
|
||||||
if (errors.length > 0) {
|
|
||||||
throw new Error(errors.join('\n'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected spawn(args: CmsisRequestArguments): Promise<void> {
|
|
||||||
const varRegexp = /\$\{.*\}/;
|
|
||||||
if (args.gdb && varRegexp.test(args.gdb)) {
|
|
||||||
throw new Error(`Unresolved variable: ${args.gdb}`)
|
|
||||||
}
|
|
||||||
return super.spawn(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 handlePinStatusRequest(): Promise<DebugProtocol.Variable[]> {
|
|
||||||
const variables: DebugProtocol.Variable[] = [];
|
|
||||||
variables.push({
|
|
||||||
name: "D2",
|
|
||||||
type: "gpio",
|
|
||||||
value: "0x00",
|
|
||||||
variablesReference: 0
|
|
||||||
})
|
|
||||||
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 = this.varMgr.getVar(frame.frameId, frame.threadId, depth, name);
|
|
||||||
|
|
||||||
if (global) {
|
|
||||||
// Update value if it is already loaded
|
|
||||||
const vup = await mi.sendVarUpdate(this.gdb, { name });
|
|
||||||
const update = vup.changelist[0];
|
|
||||||
if (update && update.in_scope === 'true' && update.name === global.varname) {
|
|
||||||
global.value = update.value;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// create var in GDB and store it in the varMgr
|
|
||||||
const varCreateResponse = await mi.sendVarCreate(this.gdb, {
|
|
||||||
name,
|
|
||||||
frame: 'current',
|
|
||||||
expression,
|
|
||||||
});
|
|
||||||
|
|
||||||
global = this.varMgr.addVar(frame.frameId, frame.threadId, depth, name, true, false, varCreateResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: expression,
|
|
||||||
value: (global.value === void 0) ? '<unknown>' : global.value,
|
|
||||||
type: global.type,
|
|
||||||
variablesReference: parseInt(global.numchild, 10) > 0
|
|
||||||
? this.variableHandles.create({
|
|
||||||
frameHandle: this.globalHandle,
|
|
||||||
type: 'object',
|
|
||||||
varobjName: global.varname,
|
|
||||||
})
|
|
||||||
: 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private progressEvent(percent: number, message: string) {
|
|
||||||
this.sendEvent(new OutputEvent('progress', 'telemetry', {
|
|
||||||
percent,
|
|
||||||
message
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected sendErrorResponse(response: DebugProtocol.Response,
|
|
||||||
codeOrMessage: number | DebugProtocol.Message, format?: string,
|
|
||||||
variables?: any, dest?: ErrorDestination): void {
|
|
||||||
if (!!format && (dest === undefined || dest === ErrorDestination.User)) {
|
|
||||||
format = format.replace(/\n/g, '<br>');
|
|
||||||
}
|
|
||||||
super.sendErrorResponse(response, codeOrMessage, format, variables, dest);
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
try {
|
|
||||||
await this.gdb.sendGDBExit();
|
|
||||||
} catch (e) {
|
|
||||||
// Need to catch here in case the connection has already been closed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async shutdown() {
|
|
||||||
await this.stopSession();
|
|
||||||
super.shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,80 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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';
|
|
@ -1,54 +0,0 @@
|
|||||||
import { AbstractServer } from './abstract-server';
|
|
||||||
import { PortScanner } from './port-scanner';
|
|
||||||
import { CmsisRequestArguments } from './cmsis-debug-session';
|
|
||||||
import * as fs from 'fs-extra';
|
|
||||||
import * as path from 'path';
|
|
||||||
import * as os from 'os';
|
|
||||||
|
|
||||||
const LAUNCH_REGEX = /GDB server started/;
|
|
||||||
const ERROR_REGEX = /^error:/mi;
|
|
||||||
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(req: CmsisRequestArguments): Promise<string[]> {
|
|
||||||
let sessionConfigFile = `gdb_port ${req.gdbServerPort!}${"\n"}`;
|
|
||||||
const telnetPort = await this.portScanner.findFreePort(4444);
|
|
||||||
if (!!telnetPort) {
|
|
||||||
sessionConfigFile += `telnet_port ${telnetPort}${"\n"}`
|
|
||||||
}
|
|
||||||
sessionConfigFile += `echo "GDB server started"${"\n"}`
|
|
||||||
|
|
||||||
const tmpdir = await fs.mkdtemp(path.join(os.tmpdir(), "arduino-debugger"));
|
|
||||||
const sessionCfgPath = path.join(tmpdir, "gdb.cfg");
|
|
||||||
await fs.writeFile(sessionCfgPath, sessionConfigFile);
|
|
||||||
|
|
||||||
let serverArguments = req.gdbServerArguments || [];
|
|
||||||
serverArguments.push("--file", sessionCfgPath);
|
|
||||||
|
|
||||||
return serverArguments;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,192 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,71 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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';
|
|
||||||
import { CmsisRequestArguments } from './cmsis-debug-session';
|
|
||||||
|
|
||||||
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(req: CmsisRequestArguments): Promise<string[]> {
|
|
||||||
let serverArguments = req.gdbServerArguments || [];
|
|
||||||
|
|
||||||
serverArguments.push('--port', req.gdbServerPort!.toString())
|
|
||||||
|
|
||||||
const telnetPort = await this.portScanner.findFreePort(4444);
|
|
||||||
if (!!telnetPort) {
|
|
||||||
serverArguments.push('--telnet-port', telnetPort.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
return serverArguments;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,141 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 { normalize, basename } from 'path';
|
|
||||||
import { spawnCommand } from 'arduino-ide-extension/lib/node/exec-util';
|
|
||||||
|
|
||||||
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 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) {
|
|
||||||
const varRegexp = /\$\{.*\}/;
|
|
||||||
if (varRegexp.test(program)) {
|
|
||||||
throw new Error(`Unresolved variable: ${program}`);
|
|
||||||
}
|
|
||||||
if (objdump && varRegexp.test(objdump)) {
|
|
||||||
throw new Error(`Unresolved variable: ${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> {
|
|
||||||
if (!this.objdump) {
|
|
||||||
return Promise.reject(new Error('Missing parameter: objdump'));
|
|
||||||
}
|
|
||||||
return spawnCommand(this.objdump, ['--syms', this.program]);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user