diff --git a/arduino-debugger-extension/src/node/debug-adapter/abstract-server.ts b/arduino-debugger-extension/src/node/debug-adapter/abstract-server.ts deleted file mode 100644 index 6ae6fc2b..00000000 --- a/arduino-debugger-extension/src/node/debug-adapter/abstract-server.ts +++ /dev/null @@ -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 { - 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 { - 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; -} diff --git a/arduino-debugger-extension/src/node/debug-adapter/cmsis-backend.ts b/arduino-debugger-extension/src/node/debug-adapter/cmsis-backend.ts deleted file mode 100644 index fd96566e..00000000 --- a/arduino-debugger-extension/src/node/debug-adapter/cmsis-backend.ts +++ /dev/null @@ -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; - } -} diff --git a/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts b/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts deleted file mode 100644 index 5b9d3f77..00000000 --- a/arduino-debugger-extension/src/node/debug-adapter/cmsis-debug-session.ts +++ /dev/null @@ -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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - try { - response.body = { - variables: new Array() - }; - - 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 { - 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 { - try { - this.stopSession(); - this.sendResponse(response); - } catch (err) { - this.sendErrorResponse(response, 1, err.message); - } - } - - private async runSession(args: CmsisRequestArguments): Promise { - 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 { - 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 { - 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 { - const variables: DebugProtocol.Variable[] = []; - variables.push({ - name: "D2", - type: "gpio", - value: "0x00", - variablesReference: 0 - }) - return variables; - } - - private async getStaticVariables(frameHandle: number): Promise { - 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 { - let global = this.varMgr.getVar(frame.frameId, frame.threadId, depth, name); - - if (global) { - // Update value if it is already loaded - const vup = await mi.sendVarUpdate(this.gdb, { name }); - const update = vup.changelist[0]; - if (update && update.in_scope === 'true' && update.name === global.varname) { - global.value = update.value; - } - } else { - // create var in GDB and store it in the varMgr - const varCreateResponse = await mi.sendVarCreate(this.gdb, { - name, - frame: 'current', - expression, - }); - - global = this.varMgr.addVar(frame.frameId, frame.threadId, depth, name, true, false, varCreateResponse); - } - - return { - name: expression, - value: (global.value === void 0) ? '' : global.value, - type: global.type, - variablesReference: parseInt(global.numchild, 10) > 0 - ? this.variableHandles.create({ - frameHandle: this.globalHandle, - type: 'object', - varobjName: global.varname, - }) - : 0, - }; - } - - 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, '
'); - } - 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(); - } -} diff --git a/arduino-debugger-extension/src/node/debug-adapter/mi.ts b/arduino-debugger-extension/src/node/debug-adapter/mi.ts deleted file mode 100644 index d962e0d6..00000000 --- a/arduino-debugger-extension/src/node/debug-adapter/mi.ts +++ /dev/null @@ -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 { - 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'; diff --git a/arduino-debugger-extension/src/node/debug-adapter/openocd-server.ts b/arduino-debugger-extension/src/node/debug-adapter/openocd-server.ts deleted file mode 100644 index e574ce98..00000000 --- a/arduino-debugger-extension/src/node/debug-adapter/openocd-server.ts +++ /dev/null @@ -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 { - 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); - } - -} diff --git a/arduino-debugger-extension/src/node/debug-adapter/port-scanner.ts b/arduino-debugger-extension/src/node/debug-adapter/port-scanner.ts deleted file mode 100644 index 8e811627..00000000 --- a/arduino-debugger-extension/src/node/debug-adapter/port-scanner.ts +++ /dev/null @@ -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 { - 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 { - 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 { - 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 { - // 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 { - 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 { - 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; - } -} diff --git a/arduino-debugger-extension/src/node/debug-adapter/pyocd-server.ts b/arduino-debugger-extension/src/node/debug-adapter/pyocd-server.ts deleted file mode 100644 index f9570fc4..00000000 --- a/arduino-debugger-extension/src/node/debug-adapter/pyocd-server.ts +++ /dev/null @@ -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 { - 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); - } -} diff --git a/arduino-debugger-extension/src/node/debug-adapter/symbols.ts b/arduino-debugger-extension/src/node/debug-adapter/symbols.ts deleted file mode 100644 index 244d9f52..00000000 --- a/arduino-debugger-extension/src/node/debug-adapter/symbols.ts +++ /dev/null @@ -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 { - 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 { - if (!this.objdump) { - return Promise.reject(new Error('Missing parameter: objdump')); - } - return spawnCommand(this.objdump, ['--syms', this.program]); - } -}