Skip to content

Commit b892d37

Browse files
authored
Adding Data Breakpoint support for complex data types (#448)
* changing philosophy of data breakpoint eligibility, now based on ability to get an address and a size for an expression (`&` and `sizeof` operators) * exclude function names from data breakpoint eligibility * adding a layer of catching errors to print meaningful error messages
1 parent 3552f89 commit b892d37

File tree

5 files changed

+137
-59
lines changed

5 files changed

+137
-59
lines changed

src/gdb/GDBDebugSessionBase.ts

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,7 @@ export abstract class GDBDebugSessionBase extends LoggingDebugSession {
595595
): Promise<void> {
596596
// The args.name is the expression to watch
597597
let varExpression = args.name;
598+
// If the expression is an address, then directly allow data breakpoint setting
598599
if (args.asAddress && args.bytes) {
599600
// Make sure the address is in hex format, if not convert it to hex
600601
if (!varExpression.startsWith('0x')) {
@@ -612,18 +613,25 @@ export abstract class GDBDebugSessionBase extends LoggingDebugSession {
612613
canPersist: true,
613614
};
614615
} else {
615-
// Send the varExpression as a query to the symbol info variables command
616+
// Try to evaluate the address of the expression to see if it's a valid symbol
616617
try {
617-
const symbols = await mi.sendSymbolInfoVars(this.gdb, {
618+
const isFunction = await mi.sendSymbolInfoFunctions(this.gdb, {
618619
name: `^${varExpression}$`,
619620
});
620-
/** If there are debug symbols matching the varExpression, then we can set a data breakpoint.
621-
* We are currently supporting primitive expressions only. ie. no pointer dereferencing, no struct members, no arrays.
622-
* The plan for the forseeable future is to expand our support for arrays, struct/union data types, and classes.
623-
* Also a guard should be added to prevent setting data breakpoints on invalid expressions.
624-
*/
625-
626-
if (symbols.symbols.debug.length > 0) {
621+
if (isFunction.symbols.debug) {
622+
throw new Error(
623+
`Cannot set data breakpoint for function ${varExpression}`
624+
);
625+
}
626+
const address = await mi.sendDataEvaluateExpression(
627+
this.gdb,
628+
`&${varExpression}`
629+
);
630+
const size = await mi.sendDataEvaluateExpression(
631+
this.gdb,
632+
`sizeof(${varExpression})`
633+
);
634+
if (address.value && size.value) {
627635
response.body = {
628636
dataId: varExpression,
629637
description: `Data breakpoint for ${varExpression}`,
@@ -721,7 +729,20 @@ export abstract class GDBDebugSessionBase extends LoggingDebugSession {
721729
if (numberRegex.test(bp.dataId) || bp.dataId.startsWith('0x')) {
722730
bp.dataId = `*(${bp.dataId})`;
723731
}
724-
await mi.sendBreakWatchpoint(this.gdb, bp.dataId, bp.accessType);
732+
try {
733+
await mi.sendBreakWatchpoint(
734+
this.gdb,
735+
bp.dataId,
736+
bp.accessType
737+
);
738+
} catch (err) {
739+
this.sendErrorResponse(
740+
response,
741+
1,
742+
'Data breakpoint could not be created: ' +
743+
(err instanceof Error ? err.message : String(err))
744+
);
745+
}
725746
}
726747
// Get the updated list of GDB watchpoints
727748
const gdbWatchpoints = await this.getWatchpointList();

src/integration-tests/breakpoints.spec.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,22 @@ describe('breakpoints', async function () {
251251
expect(bpResp.body.breakpoints.length).eq(1);
252252
});
253253

254+
it('fails to set a data breakpoint for a non symbol', async function () {
255+
const isEligible = await dc.dataBreakpointInfoRequest({
256+
name: 'r0',
257+
});
258+
expect(isEligible.body).not.eq(undefined);
259+
expect(isEligible.body.dataId).eq(null);
260+
});
261+
262+
it('fails to set a data breakpoint for a function', async function () {
263+
const isEligible = await dc.dataBreakpointInfoRequest({
264+
name: 'main',
265+
});
266+
expect(isEligible.body).not.eq(undefined);
267+
expect(isEligible.body.dataId).eq(null);
268+
});
269+
254270
it('sets a data breakpoint for an address', async function () {
255271
await dc.setBreakpointsRequest({
256272
source: {

src/mi/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,4 @@ export * from './target';
1616
export * from './thread';
1717
export * from './var';
1818
export * from './interpreter';
19+
export * from './symbols';

src/mi/symbols.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*********************************************************************
2+
* Copyright (c) 2025 QNX Software Systems, Arm Limited and others
3+
*
4+
* This program and the accompanying materials are made
5+
* available under the terms of the Eclipse Public License 2.0
6+
* which is available at https://www.eclipse.org/legal/epl-2.0/
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*********************************************************************/
10+
import { IGDBBackend } from '../types/gdb';
11+
12+
export interface MIDebugSymbol {
13+
line: string;
14+
name: string;
15+
type: string;
16+
description: string;
17+
}
18+
19+
export interface MINonDebugSymbol {
20+
address: string;
21+
name: string;
22+
}
23+
24+
export interface MISymbolInfoDebug {
25+
filename: string;
26+
fullname: string;
27+
symbols: MIDebugSymbol[];
28+
}
29+
30+
export interface MISymbolInfoResponse {
31+
symbols: {
32+
debug: MISymbolInfoDebug[];
33+
nondebug: MINonDebugSymbol[];
34+
};
35+
}
36+
37+
export function sendSymbolInfoVars(
38+
gdb: IGDBBackend,
39+
params?: {
40+
name?: string;
41+
type?: string;
42+
max_result?: string;
43+
non_debug?: boolean;
44+
}
45+
): Promise<MISymbolInfoResponse> {
46+
let command = '-symbol-info-variables';
47+
if (params) {
48+
if (params.name) {
49+
command += ` --name ${params.name}`;
50+
}
51+
if (params.type) {
52+
command += ` --type ${params.type}`;
53+
}
54+
if (params.max_result) {
55+
command += ` --max-result ${params.max_result}`;
56+
}
57+
if (params.non_debug) {
58+
command += ' --include-nondebug';
59+
}
60+
}
61+
return gdb.sendCommand(command);
62+
}
63+
64+
export function sendSymbolInfoFunctions(
65+
gdb: IGDBBackend,
66+
params?: {
67+
name?: string;
68+
type?: string;
69+
max_result?: string;
70+
non_debug?: boolean;
71+
}
72+
): Promise<MISymbolInfoResponse> {
73+
let command = '-symbol-info-functions';
74+
if (params) {
75+
if (params.name) {
76+
command += ` --name ${params.name}`;
77+
}
78+
if (params.type) {
79+
command += ` --type ${params.type}`;
80+
}
81+
if (params.max_result) {
82+
command += ` --max-result ${params.max_result}`;
83+
}
84+
if (params.non_debug) {
85+
command += ' --include-nondebug';
86+
}
87+
}
88+
return gdb.sendCommand(command);
89+
}

src/mi/var.ts

Lines changed: 0 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -69,28 +69,6 @@ export interface MIVarPathInfoResponse {
6969
path_expr: string;
7070
}
7171

72-
export interface MIDebugSymbol {
73-
line: string;
74-
name: string;
75-
type: string;
76-
description: string;
77-
}
78-
79-
export interface MINonDebugSymbol {
80-
address: string;
81-
name: string;
82-
}
83-
export interface MISymbolInfoVarsDebug {
84-
filename: string;
85-
fullname: string;
86-
symbols: MIDebugSymbol[];
87-
}
88-
export interface MISymbolInfoVarsResponse {
89-
symbols: {
90-
debug: MISymbolInfoVarsDebug[];
91-
nondebug: MINonDebugSymbol[];
92-
};
93-
}
9472
function quote(expression: string) {
9573
return `"${expression}"`;
9674
}
@@ -228,30 +206,3 @@ export function sendVarSetFormatToHex(
228206
const command = `-var-set-format ${name} hexadecimal`;
229207
return gdb.sendCommand(command);
230208
}
231-
232-
export function sendSymbolInfoVars(
233-
gdb: IGDBBackend,
234-
params?: {
235-
name?: string;
236-
type?: string;
237-
max_result?: string;
238-
non_debug?: boolean;
239-
}
240-
): Promise<MISymbolInfoVarsResponse> {
241-
let command = '-symbol-info-variables';
242-
if (params) {
243-
if (params.name) {
244-
command += ` --name ${params.name}`;
245-
}
246-
if (params.type) {
247-
command += ` --type ${params.type}`;
248-
}
249-
if (params.max_result) {
250-
command += ` --max-result ${params.max_result}`;
251-
}
252-
if (params.non_debug) {
253-
command += ' --include-nondebug';
254-
}
255-
}
256-
return gdb.sendCommand(command);
257-
}

0 commit comments

Comments
 (0)