Skip to content

Commit 2b0d9ff

Browse files
Component Viewer initial commit (#725)
* Component Viewer initial commit --------- Authored-by: Thorsten de Buhr <thorsten.deBuhr@arm.com> Co-authored-by: Omar Elkhouly <omar.elkhouly@arm.com>
1 parent 4f0b978 commit 2b0d9ff

File tree

174 files changed

+42091
-12
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

174 files changed

+42091
-12
lines changed

TPIP.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
# TPIP Report for vscode-cmsis-debugger
22

3-
Report prepared at: 13/01/2026, 08:36:58
3+
Report prepared at: 20/01/2026, 15:39:57
44

55
| *Package* | *Version* | *Repository* | *License* |
66
|---|---|---|---|
77
| arm-none-eabi-gdb | 14.3.1 | https://artifacts.tools.arm.com/arm-none-eabi-gdb/14.3.1/ | https://developer.arm.com/GetEula?Id=15d9660a-2059-4985-85e9-c01cdd4b1ba0 |
88
| pyocd | 0.42.0 | https://github.com/pyocd/pyOCD | https://github.com/pyocd/pyOCD/blob/v0.42.0/LICENSE |
9+
| xml2js | 0.6.2 | https://github.com/Leonidas-from-XIV/node-xml2js | https://github.com/Leonidas-from-XIV/node-xml2js/blob/master/LICENSE |
910
| yaml | 2.8.2 | https://github.com/eemeli/yaml | https://github.com/eemeli/yaml/blob/main/LICENSE |

docs/third-party-licenses.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@
1313
"url": "https://github.com/pyocd/pyOCD",
1414
"license": "https://github.com/pyocd/pyOCD/blob/v0.42.0/LICENSE"
1515
},
16+
{
17+
"name": "xml2js",
18+
"version": "0.6.2",
19+
"spdx": "MIT",
20+
"url": "https://github.com/Leonidas-from-XIV/node-xml2js",
21+
"license": "https://github.com/Leonidas-from-XIV/node-xml2js/blob/master/LICENSE"
22+
},
1623
{
1724
"name": "yaml",
1825
"version": "2.8.2",

package.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@
6060
"id": "cmsis-debugger.liveWatch",
6161
"name": "Live Watch",
6262
"icon": "media/trace-and-live-light.svg"
63+
},
64+
{
65+
"id": "cmsis-debugger.componentViewer",
66+
"name": "Component Viewer",
67+
"icon": "media/trace-and-live-light.svg"
6368
}
6469
]
6570
},
@@ -362,7 +367,8 @@
362367
"baseContentUrl": "https://github.com/Open-CMSIS-Pack/vscode-cmsis-debugger/blob/main/README.md"
363368
},
364369
"dependencies": {
365-
"yaml": "^2.8.2"
370+
"yaml": "^2.8.2",
371+
"xml2js": "^0.6.2"
366372
},
367373
"devDependencies": {
368374
"@open-cmsis-pack/vsce-helper": "^0.2.2",
@@ -373,6 +379,7 @@
373379
"@types/node": "^20.19.25",
374380
"@types/node-fetch": "^2.6.13",
375381
"@types/vscode": "^1.106.1",
382+
"@types/xml2js": "^0.4.14",
376383
"@types/yargs": "^17.0.35",
377384
"@types/yarnpkg__lockfile": "^1.1.9",
378385
"@typescript-eslint/eslint-plugin": "8.53.0",

src/cbuild-run/cbuild-run-reader.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,23 +44,33 @@ export class CbuildRunReader {
4444
}
4545

4646
public getSvdFilePaths(cmsisPackRoot?: string, pname?: string): string[] {
47+
const svdFilePaths = this.getFilePathsByType('svd', cmsisPackRoot, pname);
48+
return svdFilePaths;
49+
}
50+
51+
public getScvdFilePaths(cmsisPackRoot?: string, pname?: string): string[] {
52+
const scvdFilePaths = this.getFilePathsByType('scvd', cmsisPackRoot, pname);
53+
return scvdFilePaths;
54+
}
55+
56+
private getFilePathsByType(type: 'svd' | 'scvd', cmsisPackRoot?: string, pname?: string): string[] {
4757
if (!this.cbuildRun) {
4858
return [];
4959
}
50-
// Get SVD file descriptors
60+
// Get file descriptors
5161
const systemDescriptions = this.cbuildRun['system-descriptions'];
52-
const svdFileDescriptors = systemDescriptions?.filter(descriptor => descriptor.type === 'svd') ?? [];
53-
if (svdFileDescriptors.length === 0) {
62+
const fileDescriptors = systemDescriptions?.filter(descriptor => descriptor.type === type) ?? [];
63+
if (fileDescriptors.length === 0) {
5464
return [];
5565
}
5666
// Replace potential ${CMSIS_PACK_ROOT} placeholder
5767
const effectiveCmsisPackRoot = cmsisPackRoot ?? getCmsisPackRootPath();
5868
// Map to copies, leave originals untouched
59-
const filteredSvdDescriptors = pname ? svdFileDescriptors.filter(descriptor => descriptor.pname === pname): svdFileDescriptors;
60-
const svdFilePaths = filteredSvdDescriptors.map(descriptor => `${effectiveCmsisPackRoot
69+
const filteredDescriptors = pname ? fileDescriptors.filter(descriptor => descriptor.pname === pname): fileDescriptors;
70+
const filePaths = filteredDescriptors.map(descriptor => `${effectiveCmsisPackRoot
6171
? descriptor.file.replaceAll(CMSIS_PACK_ROOT_ENVVAR, effectiveCmsisPackRoot)
6272
: descriptor.file}`);
63-
return svdFilePaths;
73+
return filePaths;
6474
}
6575

6676
public getPnames(): string[] {

src/debug-session/gdbtarget-debug-session.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ export class GDBTargetDebugSession {
106106
return this.session.configuration.request === 'launch' && this.capabilities?.supportsTerminateRequest === true;
107107
}
108108

109-
/** Function returns string only in case of failure */
109+
// Function returns string only in case of failure
110110
public async evaluateGlobalExpression(expression: string, context = 'hover'): Promise<DebugProtocol.EvaluateResponse['body'] | string> {
111111
try {
112112
const frameId = (vscode.debug.activeStackItem as vscode.DebugStackFrame)?.frameId ?? 0;

src/desktop/extension.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2025 Arm Limited
2+
* Copyright 2025-2026 Arm Limited
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -24,6 +24,7 @@ import { CpuStates } from '../features/cpu-states/cpu-states';
2424
import { CpuStatesCommands } from '../features/cpu-states/cpu-states-commands';
2525
import { LiveWatchTreeDataProvider } from '../views/live-watch/live-watch';
2626
import { GenericCommands } from '../features/generic-commands';
27+
import { ComponentViewer } from '../views/component-viewer/component-viewer-main';
2728

2829
const BUILTIN_TOOLS_PATHS = [
2930
'tools/pyocd/pyocd',
@@ -39,6 +40,7 @@ export const activate = async (context: vscode.ExtensionContext): Promise<void>
3940
const cpuStates = new CpuStates();
4041
const cpuStatesCommands = new CpuStatesCommands();
4142
const cpuStatesStatusBarItem = new CpuStatesStatusBarItem();
43+
const componentViewer = new ComponentViewer(context);
4244
// Register the Tree View under the id from package.json
4345
liveWatchTreeDataProvider = new LiveWatchTreeDataProvider(context);
4446

@@ -54,6 +56,8 @@ export const activate = async (context: vscode.ExtensionContext): Promise<void>
5456
cpuStatesStatusBarItem.activate(context, cpuStates);
5557
// Live Watch view
5658
liveWatchTreeDataProvider.activate(gdbtargetDebugTracker);
59+
// Component Viewer
60+
componentViewer.activate(gdbtargetDebugTracker);
5761

5862
logger.debug('Extension Pack activated');
5963
};

src/manifest.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,5 @@ export const PUBLISHER_NAME = 'arm';
1818
export const EXTENSION_NAME = 'vscode-cmsis-debugger';
1919
export const EXTENSION_ID = `${PUBLISHER_NAME}.${EXTENSION_NAME}`;
2020
export const DISPLAY_NAME = 'Arm CMSIS Debugger';
21+
22+
export const COMPONENT_VIEWER_DISPLAY_NAME = 'Arm CMSIS Component Viewer';
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
/**
2+
* Copyright 2026 Arm Limited
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import * as vscode from 'vscode';
18+
import { URI } from 'vscode-uri';
19+
import { parseStringPromise, ParserOptions } from 'xml2js';
20+
import { Json } from './model/scvd-base';
21+
import { Resolver } from './resolver';
22+
import { ScvdComponentViewer } from './model/scvd-component-viewer';
23+
import { ScvdBase } from './model/scvd-base';
24+
import { StatementEngine } from './statement-engine/statement-engine';
25+
import { ScvdEvalContext } from './scvd-eval-context';
26+
import { GDBTargetDebugSession, GDBTargetDebugTracker } from '../../debug-session';
27+
import { ScvdGuiTree } from './scvd-gui-tree';
28+
29+
30+
const xmlOpts: ParserOptions = {
31+
explicitArray: false,
32+
mergeAttrs: true,
33+
explicitRoot: true,
34+
trim: true,
35+
// Add child objects that carry their original tag name via '#name'
36+
explicitChildren: true,
37+
preserveChildrenOrder: true
38+
};
39+
40+
export class ComponentViewerInstance {
41+
private _model: ScvdComponentViewer | undefined;
42+
private _memUsageStart: number = 0;
43+
private _memUsageLast: number = 0;
44+
private _timeUsageLast: number = 0;
45+
private _statementEngine: StatementEngine | undefined;
46+
private _guiTree: ScvdGuiTree | undefined;
47+
48+
public constructor(
49+
) {
50+
}
51+
52+
private injectLineNumbers(xml: string): string {
53+
const lines = xml.split(/\r?\n/);
54+
const result: string[] = [];
55+
for (const [idx, line] of lines.entries()) {
56+
result.push(line.replace(
57+
/<((?!\/|!|\?)([A-Za-z_][A-Za-z0-9._:-]*))/g,
58+
`<$1 __line="${idx + 1}"`
59+
));
60+
}
61+
return result.join('\n');
62+
}
63+
64+
public getGuiTree(): ScvdGuiTree[] | undefined {
65+
return this._guiTree?.children;
66+
}
67+
68+
public getStats(text: string): string {
69+
const mem = process.memoryUsage();
70+
const memCurrent = Math.round(mem.heapUsed / 1024 / 1024);
71+
const timeCurrent = Date.now();
72+
73+
if (this._timeUsageLast === 0) {
74+
this._timeUsageLast = timeCurrent;
75+
}
76+
if (this._memUsageStart === 0) {
77+
this._memUsageStart = memCurrent;
78+
this._memUsageLast = memCurrent;
79+
}
80+
81+
const memUsage = memCurrent - this._memUsageLast;
82+
const timeUsage = timeCurrent - this._timeUsageLast;
83+
const memIncrease = memCurrent - this._memUsageStart;
84+
85+
this._memUsageLast = memCurrent;
86+
this._timeUsageLast = timeCurrent;
87+
88+
return `${text}, Time: ${timeUsage} ms, Mem: ${memUsage}, Mem Increase: ${memIncrease} MB, (Total: ${memCurrent} MB)`;
89+
}
90+
91+
public async readModel(filename: URI, debugSession: GDBTargetDebugSession, debugTracker: GDBTargetDebugTracker): Promise<void> {
92+
const stats: string[] = [];
93+
94+
stats.push(this.getStats(` Start reading SCVD file ${filename}`));
95+
const buf = (await this.readFileToBuffer(filename)).toString('utf-8');
96+
stats.push(this.getStats(' read'));
97+
const bufLineNo = this.injectLineNumbers(buf);
98+
stats.push(this.getStats(' inject'));
99+
const xml: Json = await this.parseXml(bufLineNo);
100+
stats.push(this.getStats(' parse'));
101+
102+
if (xml === undefined) {
103+
console.error('Failed to parse SCVD XML');
104+
return;
105+
}
106+
107+
ScvdBase.resetIds();
108+
this.model = new ScvdComponentViewer(undefined);
109+
if (!this.model) {
110+
console.error('Failed to create SCVD model');
111+
return;
112+
}
113+
114+
this.model.readXml(xml);
115+
stats.push(this.getStats(' model.readXml'));
116+
117+
const scvdEvalContext = new ScvdEvalContext(this.model);
118+
scvdEvalContext.init(debugSession, debugTracker);
119+
stats.push(this.getStats(' evalContext.init'));
120+
121+
const executionContext = scvdEvalContext.getExecutionContext();
122+
this.model.setExecutionContextAll(executionContext);
123+
stats.push(this.getStats(' model.setExecutionContextAll'));
124+
125+
this.model.configureAll();
126+
stats.push(this.getStats(' model.configureAll'));
127+
this.model.validateAll(true);
128+
stats.push(this.getStats(' model.validateAll'));
129+
130+
const resolver = new Resolver(this.model);
131+
resolver.resolve();
132+
stats.push(this.getStats(' resolver.resolve'));
133+
134+
await this.model.calculateTypedefs();
135+
stats.push(this.getStats(' model.calculateTypedefs'));
136+
137+
this.statementEngine = new StatementEngine(this.model, executionContext);
138+
this.statementEngine.initialize();
139+
stats.push(this.getStats(' statementEngine.initialize'));
140+
this._guiTree = new ScvdGuiTree(undefined, 'component-viewer-root');
141+
142+
console.log('ComponentViewerInstance readModel stats:\n' + stats.join('\n '));
143+
}
144+
145+
public async update(): Promise<void> {
146+
const stats: string[] = [];
147+
stats.push(this.getStats(' start'));
148+
if (this._statementEngine === undefined || this._guiTree === undefined) {
149+
return;
150+
}
151+
const epoch = this._guiTree.beginUpdate();
152+
await this._statementEngine.executeAll(this._guiTree);
153+
this._guiTree.finalizeUpdate(epoch);
154+
stats.push(this.getStats('end'));
155+
console.log('ComponentViewerInstance update stats:\n' + stats.join('\n '));
156+
}
157+
158+
private async readFileToBuffer(filePath: URI): Promise<Buffer> {
159+
try {
160+
const buffer = await vscode.workspace.fs.readFile(filePath);
161+
return Buffer.from(buffer);
162+
} catch (error) {
163+
console.error('Error reading file:', error);
164+
throw error;
165+
}
166+
}
167+
168+
private async parseXml(text: string) {
169+
try {
170+
const json = await parseStringPromise(text, xmlOpts);
171+
//console.log(JSON.stringify(json, null, 2));
172+
return json;
173+
} catch (err) {
174+
console.error('Error parsing XML:', err);
175+
}
176+
}
177+
178+
public get model(): ScvdComponentViewer | undefined {
179+
return this._model;
180+
}
181+
182+
private set model(value: ScvdComponentViewer | undefined) {
183+
this._model = value;
184+
}
185+
186+
public get statementEngine(): StatementEngine | undefined {
187+
return this._statementEngine;
188+
}
189+
190+
private set statementEngine(value: StatementEngine | undefined) {
191+
this._statementEngine = value;
192+
}
193+
194+
public async executeStatements(guiTree: ScvdGuiTree): Promise<void> {
195+
if (this._statementEngine !== undefined) {
196+
await this._statementEngine.executeAll(guiTree);
197+
}
198+
}
199+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* Copyright 2025-2026 Arm Limited
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import * as vscode from 'vscode';
18+
import * as manifest from '../../manifest';
19+
20+
export const componentViewerLogger = vscode.window.createOutputChannel(manifest.COMPONENT_VIEWER_DISPLAY_NAME, { log: true });

0 commit comments

Comments
 (0)