Skip to content

Commit bc82c4a

Browse files
authored
Filter GDB warnings about not finding PC value in symtab entry (#688)
* Adds OutputEventFilter class * Add output event filter instance to GDBTargetDebugSession. Per session to be consistent with other handlers and to allow different filter flavours in future if needed. * OutputEventFilter tests --------- Signed-off-by: Jens Reinecke <jens.reinecke@arm.com>
1 parent 3be36a7 commit bc82c4a

6 files changed

Lines changed: 235 additions & 0 deletions

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

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import * as vscode from 'vscode';
1818
import { debugSessionFactory } from '../__test__/vscode.factory';
1919
import { GDBTargetDebugSession } from './gdbtarget-debug-session';
2020
import { logger } from '../logger';
21+
import { DebugProtocol } from '@vscode/debugprotocol';
2122

2223
const TEST_CBUILD_RUN_FILE = 'test-data/multi-core.cbuild-run.yml'; // Relative to repo root
2324

@@ -109,4 +110,58 @@ describe('GDBTargetDebugSession', () => {
109110
const result = await gdbTargetSession.readMemoryU32(0xABABABAB);
110111
expect(result).toEqual(0x78563412);
111112
});
113+
114+
it('filters output events correctly', () => {
115+
const makeEvent = (output: string, category: string): DebugProtocol.OutputEvent => ({
116+
seq: 1,
117+
type: 'event',
118+
event: 'output',
119+
body: {
120+
output,
121+
category
122+
}
123+
});
124+
const makeEvents = (): DebugProtocol.OutputEvent[] => [
125+
makeEvent('warning: (Internal error: pc 0x12345678 in read in CU, but not in symtab.)\n', 'log'),
126+
makeEvent('warning: (Error: pc 0x12345678 in address map, but not in symtab.)\n', 'log')
127+
];
128+
const eventsToFilter = makeEvents();
129+
const referenceEvents = makeEvents();
130+
// Update to expected event names
131+
referenceEvents.forEach(event => event.event = 'cmsis-debugger-discarded');
132+
// Look out for logger output
133+
const logDebugSpy = jest.spyOn(logger, 'debug');
134+
// Call the filter
135+
eventsToFilter.forEach(event => gdbTargetSession.filterOutputEvent(event));
136+
// Check if logger was called (2 lines = event info + output)
137+
expect(logDebugSpy).toHaveBeenCalledTimes(eventsToFilter.length*2);
138+
// Compare the outputs
139+
expect(eventsToFilter).toEqual(referenceEvents);
140+
});
141+
142+
it('does not filter output events if they do not match criteria', () => {
143+
const makeEvent = (output: string, category: string): DebugProtocol.OutputEvent => ({
144+
seq: 1,
145+
type: 'event',
146+
event: 'output',
147+
body: {
148+
output,
149+
category
150+
}
151+
});
152+
const makeEvents = (): DebugProtocol.OutputEvent[] => [
153+
makeEvent('warning: (Internal error: pc 0x12345678 in read in CU, but not in symtab.)\n', 'foo'),
154+
makeEvent('Error: pc 0x12345678 in address map, but not in symtab.', 'log')
155+
];
156+
const eventsToFilter = makeEvents();
157+
const referenceEvents = makeEvents();
158+
// Look out for logger output
159+
const logDebugSpy = jest.spyOn(logger, 'debug');
160+
// Call the filter
161+
eventsToFilter.forEach(event => gdbTargetSession.filterOutputEvent(event));
162+
// Check if logger was called (should not be called if not filtered)
163+
expect(logDebugSpy).not.toHaveBeenCalled();
164+
// Compare the outputs, should be exact matches as inputs should not cause a match
165+
expect(eventsToFilter).toEqual(referenceEvents);
166+
});
112167
});

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { DebugProtocol } from '@vscode/debugprotocol';
1919
import { logger } from '../logger';
2020
import { CbuildRunReader } from '../cbuild-run';
2121
import { PeriodicRefreshTimer } from './periodic-refresh-timer';
22+
import { OutputEventFilter } from './output-event-filter';
2223

2324
/**
2425
* GDBTargetDebugSession - Wrapper class to provide session state/details
@@ -28,13 +29,32 @@ export class GDBTargetDebugSession {
2829
public readonly canAccessWhileRunning: boolean;
2930
private _cbuildRun: CbuildRunReader|undefined;
3031
private _cbuildRunParsePromise: Promise<void>|undefined;
32+
private outputEventFilter = new OutputEventFilter();
3133

3234
constructor(public session: vscode.DebugSession) {
3335
this.refreshTimer = new PeriodicRefreshTimer(this);
3436
this.canAccessWhileRunning = this.session.configuration.type === 'gdbtarget' && this.session.configuration['auxiliaryGdb'] === true;
3537
this.refreshTimer.enabled = this.canAccessWhileRunning;
3638
}
3739

40+
/**
41+
* Filters and renames specific output events, logs those events to 'Arm CMSIS Debugger' output channel.
42+
*
43+
* Renaming the events to 'cmsis-debugger-discarded' makes VS Code and loaded debug view
44+
* extensions ignore them.
45+
*
46+
* @param event The output event to process in the filter.
47+
*/
48+
public filterOutputEvent(event: DebugProtocol.OutputEvent): void {
49+
if (this.outputEventFilter.filterOutputEvent(event)) {
50+
// Log original event properties for potential diagnostics purposes.
51+
logger.debug(`[Filtered output event]: category='${event.body.category}', seq='${event.seq}', session='${this.session.name}'`);
52+
logger.debug(`\t'${event.body.output}'`);
53+
event.event = 'cmsis-debugger-discarded'; // Discard the event by changing the event name
54+
return;
55+
}
56+
}
57+
3858
public async getCbuildRun(): Promise<CbuildRunReader|undefined> {
3959
if (!this._cbuildRun) {
4060
return;

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,24 @@ describe('GDBTargetDebugTracker', () => {
245245
expect(result!.event).toEqual(stoppedEvent);
246246
});
247247

248+
it('calls a session output event filter', async () => {
249+
const tracker = await adapterFactory!.createDebugAdapterTracker(debugSessionFactory(debugConfigurationFactory()));
250+
// Create GDB target session object
251+
tracker!.onWillStartSession!();
252+
const ouptutEvent: DebugProtocol.OutputEvent = {
253+
event: 'output',
254+
type: 'event',
255+
seq: 1,
256+
body: {
257+
category: 'log',
258+
output: 'warning: (Internal error: pc 0x12345678 in read in CU, but not in symtab.)\n'
259+
}
260+
};
261+
tracker!.onDidSendMessage!(ouptutEvent);
262+
// Expect event name was changed by filter
263+
expect(ouptutEvent.event).toEqual('cmsis-debugger-discarded');
264+
});
265+
248266
});
249267

250268
describe('refresh timer management', () => {

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,9 @@ export class GDBTargetDebugTracker {
130130
case 'exited':
131131
gdbTargetSession?.refreshTimer.stop();
132132
break;
133+
case 'output':
134+
gdbTargetSession?.filterOutputEvent(event as DebugProtocol.OutputEvent);
135+
break;
133136
}
134137
}
135138

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/**
2+
* Copyright 2025 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 { DebugProtocol } from '@vscode/debugprotocol';
18+
import { OutputEventFilter } from './output-event-filter';
19+
20+
const makeOutputEvent = (output: string, category: string|undefined): DebugProtocol.OutputEvent => {
21+
const event = {
22+
seq: 1,
23+
type: 'event',
24+
event: 'output',
25+
body: {
26+
output
27+
}
28+
};
29+
if (category) {
30+
Object.assign(event.body, { category });
31+
}
32+
return event;
33+
};
34+
35+
describe('OutputEventFilter', () => {
36+
let eventFilter: OutputEventFilter;
37+
38+
beforeEach(() => {
39+
eventFilter = new OutputEventFilter();
40+
});
41+
42+
it('filters events correctly', () => {
43+
const events = [
44+
makeOutputEvent('warning: (Internal error: pc 0x12345678 in read in CU, but not in symtab.)\n', 'log'),
45+
makeOutputEvent('warning: (Internal error: pc 0x12345678 in read in CU, but not in symtab.)\r\n', 'log'),
46+
makeOutputEvent('warning: (Error: pc 0x12345678 in address map, but not in symtab.)\n', 'log'),
47+
];
48+
events.forEach(event => expect(eventFilter.filterOutputEvent(event)).toBe(true));
49+
});
50+
51+
it('does not filter events with unexpected category', () => {
52+
const events = [
53+
makeOutputEvent('warning: (Internal error: pc 0x12345678 in read in CU, but not in symtab.)\n', 'stdout'),
54+
makeOutputEvent('warning: (Internal error: pc 0x12345678 in read in CU, but not in symtab.)\n', 'stderr'),
55+
makeOutputEvent('warning: (Internal error: pc 0x12345678 in read in CU, but not in symtab.)\n', 'console'),
56+
makeOutputEvent('warning: (Internal error: pc 0x12345678 in read in CU, but not in symtab.)\n', undefined),
57+
];
58+
events.forEach(event => expect(eventFilter.filterOutputEvent(event)).toBe(false));
59+
});
60+
61+
it('does not filter events that do not match the regular expressions', () => {
62+
const events = [
63+
makeOutputEvent('foo', 'log'),
64+
makeOutputEvent('warning: Internal error: pc 0x12345678 in read in CU, but not in symtab.\n', 'log'),
65+
makeOutputEvent('Internal error: pc 0x12345678 in read in CU, but not in symtab.', 'log'),
66+
makeOutputEvent('warning: (Internal error: pc in read in CU, but not in symtab.)\n', 'log'),
67+
];
68+
events.forEach(event => expect(eventFilter.filterOutputEvent(event)).toBe(false));
69+
});
70+
});
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/**
2+
* Copyright 2025 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+
import { DebugProtocol } from '@vscode/debugprotocol';
17+
18+
/**
19+
* OutputEventFilterSet - Defines a set of categories and messages that can apply together.
20+
*
21+
* Possible categories for `gdbtarget` debug adapter type:
22+
* 'stdout' : GDB console stream
23+
* 'stderr' : GDB stderr stream
24+
* 'log' : GDB log stream
25+
*/
26+
interface OutputEventFilterSet {
27+
categories: string[];
28+
messages: RegExp[];
29+
}
30+
31+
/**
32+
* OutputEventFilter - Class to filter out specific output events based on
33+
* active filter sets.
34+
*
35+
* Note: Not configurable at this time, extend constructor if more flexibility needed in future.
36+
*
37+
*/
38+
export class OutputEventFilter {
39+
private filterSets: OutputEventFilterSet[];
40+
41+
constructor() {
42+
this.filterSets = [
43+
{
44+
// GDB warnings seen for (valid!!) DWARF 5 output where debug range
45+
// addresses are assumed to be always code addresses.
46+
categories: ['log'],
47+
messages: [
48+
/warning: \(Internal error: pc 0x[0-9A-Fa-f]+ in read in CU, but not in symtab\.\)/,
49+
/warning: \(Error: pc 0x[0-9A-Fa-f]+ in address map, but not in symtab\.\)/,
50+
]
51+
}
52+
];
53+
}
54+
55+
/**
56+
* Filters output event by specified filter sets.
57+
*
58+
* @param event The output event to process in the filter.
59+
* @returns True if the event is to be discarded, false otherwise.
60+
*/
61+
public filterOutputEvent(event: DebugProtocol.OutputEvent): boolean {
62+
return this.filterSets.some(set => {
63+
if (!set.categories.includes(event.body.category ?? '')) {
64+
return false;
65+
}
66+
return set.messages.some(message => message.test(event.body.output));
67+
});
68+
}
69+
}

0 commit comments

Comments
 (0)