Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
[![Maintainability](https://api.codeclimate.com/v1/badges/0f12a7e73736f8bbfb9d/maintainability)](https://codeclimate.com/github/Open-CMSIS-Pack/vscode-cmsis-debugger/maintainability)
[![Test Coverage](https://api.codeclimate.com/v1/badges/0f12a7e73736f8bbfb9d/test_coverage)](https://codeclimate.com/github/Open-CMSIS-Pack/vscode-cmsis-debugger/test_coverage)

# Arm CMSIS Debugger Extension for Visual Studio Code (In Progress)

The Arm CMSIS Debugger extension for Visual Studio Code is an extension pack demonstrating how to combine technologies from various open source projects to create a comprehensive debug platform for Arm based IoT solutions.
The Arm CMSIS Debugger extension for Visual Studio Code is an extension pack demonstrating how to combine technologies from various open source projects to create a comprehensive debug platform for Arm based IoT solutions.

Related open source projects are

- [Open-CMSIS-Pack](https://www.open-cmsis-pack.org/) of which this extension is part of.
- [Eclipse CDT Cloud](https://eclipse.dev/cdt-cloud/), an open-source project that hosts a number of components and best practices for building customizable web-based C/C++ tools.
- [pyOCD](https://pyocd.io/), a Python based tool and API for debugging, programming, and exploring Arm Cortex microcontrollers.
Expand All @@ -15,6 +19,7 @@ The Arm CMSIS Debugger extension is actually an [extension pack](https://code.vi
## Included Extensions

The following extensions are included in this extension pack:

- [Arm Tools Environment Manager](https://marketplace.visualstudio.com/items?itemName=Arm.environment-manager), an extension that allows to download, install, and manage software development tools using [Microsoft vcpkg](https://vcpkg.io/en/index.html) artifacts.
- [CDT GDB Debug Adapter Extension](https://marketplace.visualstudio.com/items?itemName=eclipse-cdt.cdt-gdb-vscode), an Eclipse CDT Cloud extension that supports debugging using gdb and any other debuggers that supports the MI protocol.
- [Memory Inspector](https://marketplace.visualstudio.com/items?itemName=eclipse-cdt.memory-inspector), an Eclipse CDT Cloud extension that provides a powerful and configurable memory viewer that works with debug adapters.
Expand All @@ -23,10 +28,11 @@ The following extensions are included in this extension pack:
## pyOCD Debug Setup

- Install `GCC compiler for ARM CPUs` with the `Arm Tools Environment Manager` to get access to a GDB (`arm-none-eabi-gdb`).
- **Temporary** - should become obsolete with full `*.cbuild-run.yml` support in pyOCD:<br>
- **Temporary** - should become obsolete with full `*.cbuild-run.yml` support in pyOCD:<br>
Make sure to set up your CMSIS Pack installation root folder by one of the following methods:
- Set your system environment variable `CMSIS_PACK_ROOT`.
- Add the following to your debug launch configuration

```
"environment": {
"CMSIS_PACK_ROOT": "</path/to/your/pack/cache>"
Expand All @@ -41,6 +47,7 @@ The following extensions are included in this extension pack:
## Additional Extension Functionality

This extension contributes additional functionality to more seamlessly integrate the included extensions:

- The pseudo debugger types `cmsis-debug-pyocd` and `cmsis-debug-jlink`. These types allow a more seamless integration into the VS Code IDE. However, these are not full debug adapters but generate debug configurations of type `gdbtarget` which comes with the [CDT GDB Debug Adapter Extension](https://marketplace.visualstudio.com/items?itemName=eclipse-cdt.cdt-gdb-vscode).
- A [debug configuration provider](https://code.visualstudio.com/api/references/vscode-api#DebugConfigurationProvider) for the type `gdbtarget` which comes with the [CDT GDB Debug Adapter Extension](https://marketplace.visualstudio.com/items?itemName=eclipse-cdt.cdt-gdb-vscode). This provider automatically fills in default values for known remote GDB servers when launching a debug session.
- CMSIS specific launch configuration items for the `*` debugger type, i.e. visible for all debugger types. It depends on the actually used debug adapter type if this information is known and utilized.
Expand All @@ -54,6 +61,7 @@ This section describes the contributed pseudo debugger types and their support t
The `cmsis-debug-pyocd` debugger type allows to add default debug configurations to the workspace's `launch.json` file to debug via GDB and pyOCD. The actually used debugger type is `gdbtarget`.

In addition this extension contributes a debug configuration resolver which automatically fills the following gaps during debug launch:

- If option `target`.`server` is set to `pyocd`, then it expands this option to the absolute path of the built-in pyOCD distribution.
- Adds/extends the `target`.`serverParameters` list of `pyocd` command line arguments:
- Prepends `gdbserver` if not present.
Expand All @@ -69,12 +77,15 @@ The `cmsis-debug-jlink` debugger type allows to add default debug configurations
**Note**: The generated default debug configuration uses `JLinkGDBServer` as `target`.`server` setting. The executable with this name has slightly differing behavior depending on your host platform. It launches a GUI-less server on Linux and MacOS. Whereas a GDB server with GUI is launched on Windows. Please change the value to `JLinkGDBServerCL` to suppress the GUI on Windows.

In addition this extension contributes a debug configuration resolver which automatically fills the following gaps during debug launch:

- Adds/extends the `target`.`serverParameters` list of `JLinkGDBServer`/`JLinkGDBServerCL` command line arguments:
- Appends `--port` and the corresponding `port` value if `target`.`port` is set.

## Known Limitations

- Requires ELF files built with GCC and DWARF5 debug information to operate seamlessly.
- The shipped pyOCD version accepts the new command line option `--cbuild-run`. But only extracts device and DFP names.

## Trademarks

Visual Studio is a trademark of the Microsoft group of companies.
27 changes: 26 additions & 1 deletion __mocks__/vscode.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,32 @@
* limitations under the License.
*/

require('domain');
const { URI } = require('vscode-uri');

module.exports = {
EventEmitter: jest.fn(() => {
const callbacks = [];
return {
dispose: jest.fn(),
event: (callback, thisArg) => {
callbacks.push(thisArg ? callback.bind(thisArg) : callback);
return { dispose: jest.fn() };
},
fire: event => callbacks.forEach(callback => callback(event))
};
}),
Uri: URI,
window: {
createOutputChannel: jest.fn(() => ({
appendLine: jest.fn(),
trace: jest.fn(),
debug: jest.fn(),
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
})),
},
workspace: {
getConfiguration: jest.fn(() => ({
get: jest.fn(),
Expand All @@ -28,5 +50,8 @@ module.exports = {
},
commands: {
executeCommand: jest.fn(),
}
},
debug: {
registerDebugConfigurationProvider: jest.fn(),
},
};
84 changes: 84 additions & 0 deletions src/__test__/test-data-factory.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/**
* Copyright 2025 Arm Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { makeFactory, StubEvents } from './test-data-factory';
import { Event, EventEmitter } from 'vscode';

describe('makeFactory', () => {

it('returns default values when used without options', () => {
type Type = {
value: number;
text: string;
}

const expected : Type = {
value: 42,
text: 'the answer'
};

const factory = makeFactory<Type>({
value: () => expected.value,
text: () => expected.text,
});

const value = factory();

expect(value).toEqual(expected);
});

it('returns explicit values passed by options', () => {
type Type = {
value: number;
text: string;
}

const expected : Type = {
value: 42,
text: 'the answer'
};

const factory = makeFactory<Type>({
value: () => 43,
text: () => expected.text,
});

const value = factory({ value: expected.value });

expect(value).toEqual(expected);
});

it('stubs vscode.Event|s by exposing vscode.EventEmitter', () => {
type Type = {
event: Event<number>,
}

const factory = makeFactory<StubEvents<Type>>({
eventEmitter: () => new EventEmitter(),
event: (r) => jest.fn(r.eventEmitter?.event),
});

const value = factory();

const listener = jest.fn();
value.event(listener);
value.eventEmitter.fire(42);

expect(listener).toHaveBeenCalledTimes(1);
expect(listener).toHaveBeenCalledWith(42);
});

});
50 changes: 50 additions & 0 deletions src/__test__/test-data-factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* Copyright 2025 Arm Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { Event, EventEmitter } from 'vscode';

type Mutable<T> = {
-readonly [P in keyof T]: T[P];
}

type Factory<T> = (options?: Partial<T>) => Mutable<T>;

type Initializer<T> = {
[P in keyof T]: (result: Partial<T>) => T[P]
};

export type StubEvents<T, S extends string = 'Emitter'> = {
[P in keyof T as T[P] extends Event<unknown> ? `${string & P}${S}` : never]: T[P] extends Event<infer U> ? EventEmitter<U> : never
} & T;

export function makeFactory<T extends object>(initializer: Initializer<T>): Factory<T> {
const factory = (options?: Partial<T>) => {
const result = { ...options } as Mutable<T>;
for (const key in initializer) {
if (!(key in result)) {
result[key] = initializer[key].call(result, result);
}
}
return result;
};
return factory;
}

export function makeGenerator<T>(factory: Factory<T>) {
return (count: number = 1, options?: Partial<T>): T[] => {
return [...Array(count)].map(_ => factory(options));
};
}
70 changes: 70 additions & 0 deletions src/__test__/vscode.factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* Copyright 2025 Arm Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import * as vscode from 'vscode';

export function extensionContextFactory(): jest.Mocked<vscode.ExtensionContext> {
return {
subscriptions: [],
workspaceState: {
get: jest.fn(),
update: jest.fn(),
} as unknown as vscode.Memento,
globalState: {
get: jest.fn(),
update: jest.fn(),
setKeysForSync: jest.fn(),
} as unknown as vscode.Memento & { setKeysForSync(keys: readonly string[]): void } ,
secrets: {
store: jest.fn(),
get: jest.fn(),
delete: jest.fn(),
} as unknown as vscode.SecretStorage,
extensionUri: vscode.Uri.file('/mock/uri'),
extensionPath: '/mock/path',
environmentVariableCollection: {
persistent: true,
replace: jest.fn(),
append: jest.fn(),
prepend: jest.fn(),
get: jest.fn(),
forEach: jest.fn(),
getScoped: jest.fn(),
} as unknown as vscode.GlobalEnvironmentVariableCollection,
storageUri: vscode.Uri.file('/mock/storageUri'),
globalStorageUri: vscode.Uri.file('/mock/globalStorageUri'),
logUri: vscode.Uri.file('/mock/logUri'),
storagePath: '/mock/storagePath',
globalStoragePath: '/mock/globalStoragePath',
logPath: '/mock/logPath',
asAbsolutePath: jest.fn((relativePath: string) => `/mock/path/${relativePath}`),
extensionMode: 3,
extension: {
id: 'mock.extension',
extensionUri: vscode.Uri.file('/mock/uri'),
extensionPath: '/mock/path',
isActive: true,
packageJSON: {},
activate: jest.fn(),
exports: {},
extensionKind: 2,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as unknown as vscode.Extension<any>,
languageModelAccessInformation: {
getLanguageModel: jest.fn(),
} as unknown as vscode.LanguageModelAccessInformation,
};
};
60 changes: 60 additions & 0 deletions src/debug-configuration/debug-configuration.factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* Copyright 2025 Arm Limited
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import * as vscode from 'vscode';
import { makeFactory } from '../__test__/test-data-factory';
import { GDBTargetConfiguration, TargetConfiguration } from './gdbtarget-configuration';

export const debugConfigurationFactory = makeFactory<vscode.DebugConfiguration>({
type: () => 'cppdbg',
name: () => 'Debug',
request: () => 'launch',
});

export const targetConfigurationFactory = makeFactory<TargetConfiguration>({
type: () => undefined,
parameters: () => undefined,
host: () => undefined,
port: () => undefined,
cwd: () => undefined,
environment: () => undefined,
server: () => undefined,
serverParameters: () => undefined,
serverPortRegExp: () => undefined,
serverStartupDelay: () => undefined,
automaticallyKillServer: () => false,
uart: () => undefined,
});

export const gdbTargetConfiguration = makeFactory<GDBTargetConfiguration>({
type: () => 'gdb',
name: () => 'Debug',
request: () => 'launch',
program: () => undefined,
gdb: () => undefined,
cwd: () => undefined,
environment: () => undefined,
gdbAsync: () => undefined,
gdbNonStop: () => undefined,
verbose: () => undefined,
logFile: () => undefined,
openGdbConsole: () => undefined,
initCommands: () => undefined,
preRunCommands: () => undefined,
imageAndSymbols: () => undefined,
target: () => undefined,
cmsis: () => undefined,
});
Loading