Skip to content

Commit e4747e2

Browse files
committed
Added maxBreakpoints and watchpoint functionality
1 parent 11cd64d commit e4747e2

8 files changed

Lines changed: 108 additions & 104 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
# ChangeLog
2+
# V1.0.3
3+
4+
* Implemented Debug watchpoints (watch breakpoints).
5+
- use Breakpoints window and tap + to give the name of the variable to watch
6+
* Fix: showServerOutput default = switch
7+
28
# V1.0.2
39
* Implemented default port setting for GDB - server communication
410
- use "gdbPort" setting in launch.json to change the default port.
@@ -9,4 +15,4 @@
915
- use "showServerOutput" setting in launch.json to change the behaviour.
1016

1117
# V1.0.0
12-
Initial release forked from Marus cortex-debug V1.13.0-pre6.
18+
Initial release forked from Marus cortex-debug V1.13.0-pre6.

debug_attributes.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ If the type is marked as `{...}` it means that it is a complex item can have mul
3333
| device | string | Both | Target Device Identifier |
3434
| executable | string | Both | Path of executable for symbols and program information. See also `loadFiles`, `symbolFiles` |
3535
| gdbPath | string | Both | This setting can be used to override the GDB path user/workspace setting for a particular launch configuration. This should be the full pathname to the executable (or name of the executable if it is in your PATH). Note that other toolchain executables with the configured prefix must still be available. |
36-
| gdbPort | number | Launch | Port to use for internal GDB server (cortex-gdb will scan a range of 2000 ports starting at gdbPort and use the first free port) |
37-
| gdbTarget | string | Both | For externally (servertype = "external") controlled GDB Servers you must specify the GDB target to connect to. This can either be a "hostname:port" combination or path to a serial port |
36+
| gdbPort | number | Both | Port to use for internal GDB server (cortex-gdb will scan a range of 2000 ports starting at gdbPort and use the first free port). |
37+
| gdbTarget | string | Both | For externally (servertype = "external") controlled GDB Servers you must specify the GDB target to connect to. This can either be a "hostname:port" combination or path to a serial port. |
3838
| graphConfig | {object} | Both | Description of how graphing can be done. See our Wiki for details |
3939
| interface | string | Both | Debug Interface type to use for connections (defaults to SWD) - Used for J-Link, ST-LINK and BMP probes. |
4040
| ipAddress | string | Both | IP Address for networked J-Link Adapter |
@@ -44,6 +44,7 @@ If the type is marked as `{...}` it means that it is a complex item can have mul
4444
| liveWatch<br>.samplesPerSecond | number | Both | Maximum number of samples per second. Different from GUI refresh-rate, which is a user/workspace setting |
4545
| loadFiles | string[] | Launch | List of files (hex/bin/elf files) to load/program instead of the executable file. Symbols are not loaded (see `symbolFiles`). Can be an empty list to specify none. If this property does not exist, then the executable is used to program the device |
4646
| machine | string | Both | Machine Type Selection - used for QEMU server type |
47+
| maxBreakpoints | number | Both | The maximum amount of breakpoints available on this core. |
4748
| numberOfProcessors | number | Both | Number of processors/cores in the target device. |
4849
| objdumpPath | string | Both | This setting can be used to override the objdump (used to find globals/statics) path user/workspace setting for a particular launch configuration. This should be the full pathname to the executable (or name of the executable if it is in your PATH). Note that other toolchain executables with the configured prefix must still be available. The program 'nm' is also expected alongside |
4950
| openOCDLaunchCommands | string[] | Both | OpenOCD command(s) after configuration files are loaded (-c options) |

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -613,7 +613,7 @@
613613
"default": null,
614614
"type": "array",
615615
"items": "string",
616-
"description": "Override the commands that are normally executed as part of reset-ing the target. When undefined the deprecated overrideRestartCommands is used."
616+
"description": "Override the commands that are normally executed as part of reset-ing the target. When undefined the deprecated overrideRestartCommands is used if it exists."
617617
},
618618
"postStartSessionCommands": {
619619
"default": [],
@@ -756,9 +756,19 @@
756756
},
757757
"gdbTarget": {
758758
"default": null,
759-
"description": "For externally (servertype = \"external\") controlled GDB Servers you must specify the GDB target to connect to. This can either be a \"hostname:port\" combination or path to a serial port",
759+
"description": "For externally (servertype = \"external\") controlled GDB Servers you must specify the GDB target to connect to. This can either be a \"hostname:port\" combination or path to a serial port.",
760760
"type": "string"
761761
},
762+
"gdbPort": {
763+
"default": 50000,
764+
"type": "number",
765+
"description": "Port to use for internal GDB server (cortex-gdb will scan a range of 2000 ports starting at gdbPort and use the first free port)."
766+
},
767+
"maxBreakpoints": {
768+
"default": 8,
769+
"type": "number",
770+
"description": "The maximum amount of breakpoints available on this core."
771+
},
762772
"breakAfterReset": {
763773
"default": false,
764774
"type": "boolean",
@@ -1895,13 +1905,18 @@
18951905
},
18961906
"gdbTarget": {
18971907
"default": null,
1898-
"description": "For externally (servertype = \"external\") controlled GDB Servers you must specify the GDB target to connect to. This can either be a \"hostname:port\" combination or path to a serial port",
1908+
"description": "For externally (servertype = \"external\") controlled GDB Servers you must specify the GDB target to connect to. This can either be a \"hostname:port\" combination or path to a serial port.",
18991909
"type": "string"
19001910
},
19011911
"gdbPort": {
19021912
"default": 50000,
19031913
"type": "number",
1904-
"description": "Port to use for internal GDB server (cortex-gdb will scan a range of 2000 ports starting at gdbPort and use the first free port)"
1914+
"description": "Port to use for internal GDB server (cortex-gdb will scan a range of 2000 ports starting at gdbPort and use the first free port)."
1915+
},
1916+
"maxBreakpoints": {
1917+
"default": 100,
1918+
"type": "number",
1919+
"description": "The maximum amount of breakpoints available on this core."
19051920
},
19061921
"runToMain": {
19071922
"description": "Deprecated: please use 'runToEntryPoint' instead.",

src/backend/mi2/mi2.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -634,13 +634,15 @@ export class MI2 extends EventEmitter implements IBackend {
634634
bkptArgs += `-c "${breakpoint.condition}" `;
635635
}
636636

637+
let cmd = breakpoint.logMessage ? 'dprintf-insert' : 'break-insert';
638+
637639
if (breakpoint.raw) {
638-
bkptArgs += '*' + escape(breakpoint.raw);
640+
bkptArgs += escape(breakpoint.raw);
641+
cmd = 'break-watch';
639642
} else {
640643
bkptArgs += '"' + escape(breakpoint.file) + ':' + breakpoint.line + '"';
641644
}
642645

643-
const cmd = breakpoint.logMessage ? 'dprintf-insert' : 'break-insert';
644646
if (breakpoint.logMessage) {
645647
bkptArgs += ' ' + breakpoint.logMessage;
646648
}

src/common.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,7 @@ export interface ConfigurationArguments extends DebugProtocol.LaunchRequestArgum
262262
serverpath: string;
263263
gdbPath: string;
264264
gdbPort: number;
265+
maxBreakpoints: number;
265266
gdbServerConsolePort: number;
266267
objdumpPath: string;
267268
serverArgs: string[];

src/frontend/extension.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,9 @@ export class CortexDebugExtension {
497497
case 'error':
498498
vscode.window.showErrorMessage(msg);
499499
break;
500+
case 'error-confirm':
501+
vscode.window.showErrorMessage(msg, { modal: true });
502+
break;
500503
default:
501504
vscode.window.showInformationMessage(msg);
502505
break;

src/gdb.ts

Lines changed: 69 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// * Update setDataBreakpoints to check for frame-id if the 'name' is an expression
55
// * Return the new type of busy error for evaluate/memory-requests/disassembly and certain other responses
66
//
7+
78
import {
89
Logger, logger, LoggingDebugSession, InitializedEvent, TerminatedEvent,
910
ContinuedEvent, OutputEvent, Thread, ThreadEvent,
@@ -211,7 +212,6 @@ export class GDBDebugSession extends LoggingDebugSession {
211212
public symbolTable: SymbolTable;
212213
private usingParentServer = false;
213214
private debugLogFd = -1;
214-
215215
protected variableHandles = new Handles<string | VariableObject | ExtendedVariable>(HandleRegions.VAR_HANDLES_START);
216216
protected variableHandlesReverse = new Map<string, number>();
217217
protected quit: boolean;
@@ -2196,110 +2196,86 @@ export class GDBDebugSession extends LoggingDebugSession {
21962196
}
21972197

21982198
protected setBreakPointsRequest(
2199-
r: DebugProtocol.SetBreakpointsResponse,
2200-
a: DebugProtocol.SetBreakpointsArguments): Promise<void> {
2199+
response: DebugProtocol.SetBreakpointsResponse,
2200+
args: DebugProtocol.SetBreakpointsArguments
2201+
): Promise<void> {
22012202
const doit = (
22022203
response: DebugProtocol.SetBreakpointsResponse,
22032204
args: DebugProtocol.SetBreakpointsArguments,
2204-
pendContinue: PendingContinue): Promise<void> => {
2205+
pendContinue: PendingContinue
2206+
): Promise<void> => {
22052207
return new Promise(async (resolve) => {
22062208
const createBreakpoints = async () => {
2207-
const currentBreakpoints = (this.breakpointMap.get(args.source.path) || []).map((bp) => bp.number);
2208-
2209-
try {
2210-
await this.miDebugger.removeBreakpoints(currentBreakpoints);
2211-
for (const old of currentBreakpoints) {
2212-
this.breakpointById.delete(old);
2213-
}
2214-
this.breakpointMap.set(args.source.path, []);
2215-
2216-
const all: Promise<OurSourceBreakpoint | MIError>[] = [];
2217-
const sourcepath = decodeURIComponent(args.source.path);
2218-
2219-
if (sourcepath.startsWith('disassembly:/')) {
2220-
let sidx = 13;
2221-
if (sourcepath.startsWith('disassembly:///')) { sidx = 15; }
2222-
const path = sourcepath.substring(sidx, sourcepath.length - 6); // Account for protocol and extension
2223-
const parts = path.split(':::');
2224-
let func: string;
2225-
let file: string;
2226-
2227-
if (parts.length === 2) {
2228-
func = parts[1];
2229-
file = parts[0];
2230-
} else {
2231-
func = parts[0];
2232-
}
2233-
2234-
const symbol: SymbolInformation = await this.disassember.getDisassemblyForFunction(func, file);
2235-
2236-
if (symbol) {
2237-
args.breakpoints.forEach((brk) => {
2238-
if (brk.line <= symbol.instructions.length) {
2239-
const line = symbol.instructions[brk.line - 1];
2240-
const arg: OurSourceBreakpoint = {
2241-
...brk,
2242-
file: args.source.path,
2243-
raw: line.address
2244-
};
2245-
all.push(this.miDebugger.addBreakPoint(arg).catch((err: MIError) => err));
2246-
} else {
2247-
all.push(
2248-
Promise.resolve(
2249-
new MIError(
2250-
`${func} only contains ${symbol.instructions.length} instructions`,
2251-
'Set breakpoint'
2252-
)
2253-
)
2254-
);
2255-
}
2256-
});
2257-
}
2209+
const src = args.source.path;
2210+
const maxBkpts = this.args.maxBreakpoints?? 8;
2211+
// 1) What we currently have in this file
2212+
const existing: OurSourceBreakpoint[] = this.breakpointMap.get(src) || [];
2213+
const toDeleteIds = existing.map((bp) => bp.number);
2214+
2215+
// 2) How many slots remain (excluding this file)
2216+
const totalOther = Array
2217+
.from(this.breakpointMap.entries())
2218+
.filter(([path]) => path !== src)
2219+
.reduce((sum, [, arr]) => sum + arr.length, 0);
2220+
const slotsLeft = maxBkpts - totalOther;
2221+
2222+
// 4) Delete all old BPs for this file on the target
2223+
await this.miDebugger.removeBreakpoints(toDeleteIds);
2224+
for (const id of toDeleteIds) {
2225+
this.breakpointById.delete(id);
2226+
}
2227+
this.breakpointMap.set(src, []);
2228+
2229+
const ops: Promise<OurSourceBreakpoint | MIError>[] = [];
2230+
// Build ops, up to slotsLeft
2231+
for (let i = 0; i < args.breakpoints.length; i++) {
2232+
if (i < slotsLeft) {
2233+
const arg: OurSourceBreakpoint = { ...args.breakpoints[i], file: src };
2234+
ops.push(
2235+
this.miDebugger.addBreakPoint(arg).catch((e: MIError) => e)
2236+
);
22582237
} else {
2259-
args.breakpoints.forEach((brk) => {
2260-
const arg: OurSourceBreakpoint = {
2261-
...brk,
2262-
file: args.source.path
2263-
};
2264-
all.push(this.miDebugger.addBreakPoint(arg).catch((err: MIError) => err));
2265-
});
2238+
ops.push(
2239+
Promise.resolve(new MIError(`Reached breakpoint limit of ${maxBkpts}`, 'Set breakpoint'))
2240+
);
22662241
}
2242+
}
22672243

2268-
const brkpoints = await Promise.all(all);
2269-
2270-
response.body = {
2271-
breakpoints: brkpoints.map((bp) => {
2272-
if (bp instanceof MIError) {
2273-
/* Failed breakpoints should be reported with
2274-
* verified: false, so they can be greyed out
2275-
* in the UI. The attached message will be
2276-
* presented as a tooltip.
2277-
*/
2278-
return {
2279-
verified: false,
2280-
message: bp.message
2281-
} as DebugProtocol.Breakpoint;
2282-
}
2244+
// only warn once if they requested more than slotsLeft
2245+
if (args.breakpoints.length > slotsLeft) {
2246+
const warn = `Warning: reached breakpoint limit of ${maxBkpts}.\n`;
2247+
this.sendEvent(new OutputEvent(warn, 'stderr'));
2248+
this.sendEvent(new GenericCustomEvent('popup', {
2249+
type: 'error-confirm', message: `Warning: reached breakpoint limit.\n${maxBkpts} max.` }));
2250+
}
22832251

2252+
// 7) Await the MI calls and build the DAP response
2253+
const results = await Promise.all(ops);
2254+
response.body = {
2255+
breakpoints: results.map((r) => {
2256+
if (r instanceof MIError) {
22842257
return {
2285-
line: bp.line,
2286-
id: bp.number,
2287-
instructionReference: bp.address,
2288-
verified: true
2289-
};
2290-
})
2291-
};
2258+
verified: false,
2259+
message: r.message
2260+
} as DebugProtocol.Breakpoint;
2261+
}
2262+
return {
2263+
line: r.line,
2264+
id: r.number,
2265+
instructionReference: r.address,
2266+
verified: true
2267+
};
2268+
})
2269+
};
22922270

2293-
const bpts: OurSourceBreakpoint[] = brkpoints.filter((bp) => !(bp instanceof MIError)) as OurSourceBreakpoint[];
2294-
for (const bpt of bpts) {
2295-
this.breakpointById.set(bpt.number, bpt);
2296-
}
2297-
this.breakpointMap.set(args.source.path, bpts);
2298-
this.sendResponse(response);
2299-
} catch (msg) {
2300-
this.sendErrorResponse(response, 9, msg.toString());
2271+
// 8) Update our maps with the successful ones
2272+
const success = results.filter((r) => !(r instanceof MIError)) as OurSourceBreakpoint[];
2273+
for (const bpt of success) {
2274+
this.breakpointById.set(bpt.number, bpt);
23012275
}
2276+
this.breakpointMap.set(src, success);
23022277

2278+
this.sendResponse(response);
23032279
this.continueIfNoMore(pendContinue);
23042280
resolve();
23052281
};
@@ -2308,7 +2284,7 @@ export class GDBDebugSession extends LoggingDebugSession {
23082284
});
23092285
};
23102286

2311-
return this.allBreakPointsQ.add(doit, r, a);
2287+
return this.allBreakPointsQ.add(doit, response, args);
23122288
}
23132289

23142290
protected setInstructionBreakpointsRequest(

0 commit comments

Comments
 (0)