diff --git a/sampleWorkspace/readme.md b/sampleWorkspace/readme.md index 04b68c9..2c7c6b8 100644 --- a/sampleWorkspace/readme.md +++ b/sampleWorkspace/readme.md @@ -42,6 +42,8 @@ If a Mock Debug session is active, breakpoints are "validated" according to thes * if a line starts with `!`, "hardware" breakpoints can be set on the line * a breakpoint on a line containing the word `lazy` is not immediately validated, but only after hitting it once. +!some line + ## Data Breakpoints Data Breakpoints can be set for different access modes in the VARIABLES view of the editor via the context menu. @@ -51,6 +53,10 @@ Examples: - Read Access: $i - Write Access: $i=999 +You can also set data breakpoints on "memory addresses". In mock debug, the "address" of a variable is the zero-indexed order in which it was made, so asking to pause on read/writes to `0x2` will pause when the third variable is written to, which is `b1` in this readme: + +- Read Access: $b1 + ## Disassembly View If a markdown line contains the word 'disassembly', the context menu's "Open Disassembly View" command is enabled and the Disassembly view shows (fake) assembly instructions and "instruction stepping" and "instruction breakpoints" are supported. diff --git a/src/mockDebug.ts b/src/mockDebug.ts index 6a63aab..7ebc095 100644 --- a/src/mockDebug.ts +++ b/src/mockDebug.ts @@ -161,6 +161,7 @@ export class MockDebugSession extends LoggingDebugSession { // make VS Code support data breakpoints response.body.supportsDataBreakpoints = true; + response.body.supportsDataBreakpointBytes = true; // make VS Code support completion in REPL response.body.supportsCompletionsRequest = true; @@ -688,6 +689,11 @@ export class MockDebugSession extends LoggingDebugSession { response.body.accessTypes = ["read", "write", "readWrite"]; response.body.canPersist = true; } + } else if (args.asAddress) { + response.body.dataId = `range:${args.name}:${args.bytes || 1}`; + response.body.description = `${args.bytes} variables after ${args.name}`; + response.body.accessTypes = ["read", "write", "readWrite"]; + response.body.canPersist = true; } this.sendResponse(response); @@ -703,7 +709,17 @@ export class MockDebugSession extends LoggingDebugSession { }; for (const dbp of args.breakpoints) { - const ok = this._runtime.setDataBreakpoint(dbp.dataId, dbp.accessType || 'write'); + let ok = false; + if (dbp.dataId.startsWith('range:')) { + const [, start, bytes] = dbp.dataId.split(':').map(Number); + for (let i = 0; i < bytes; i++) { + this._runtime.setDataBreakpoint(start + i, dbp.accessType || 'write') + ok = true; + } + } else { + ok = this._runtime.setDataBreakpoint(dbp.dataId, dbp.accessType || 'write'); + } + response.body.breakpoints.push({ verified: ok }); diff --git a/src/mockRuntime.ts b/src/mockRuntime.ts index 898923b..80c932e 100644 --- a/src/mockRuntime.ts +++ b/src/mockRuntime.ts @@ -48,6 +48,7 @@ export class RuntimeVariable { private _memory?: Uint8Array; public reference?: number; + public ordinal?: number; public get value() { return this._value; @@ -144,7 +145,7 @@ export class MockRuntime extends EventEmitter { // so that the frontend can match events with breakpoints. private breakpointId = 1; - private breakAddresses = new Map(); + private breakAddresses = new Map(); private namedException: string | undefined; private otherExceptions = false; @@ -371,17 +372,17 @@ export class MockRuntime extends EventEmitter { this.breakPoints.delete(this.normalizePathAndCasing(path)); } - public setDataBreakpoint(address: string, accessType: 'read' | 'write' | 'readWrite'): boolean { + public setDataBreakpoint(nameOrOrdinal: string | number, accessType: 'read' | 'write' | 'readWrite'): boolean { const x = accessType === 'readWrite' ? 'read write' : accessType; - const t = this.breakAddresses.get(address); + const t = this.breakAddresses.get(nameOrOrdinal); if (t) { if (t !== x) { - this.breakAddresses.set(address, 'read write'); + this.breakAddresses.set(nameOrOrdinal, 'read write'); } } else { - this.breakAddresses.set(address, x); + this.breakAddresses.set(nameOrOrdinal, x); } return true; } @@ -589,15 +590,18 @@ export class MockRuntime extends EventEmitter { // the first write access to a variable is the "declaration" and not a "write access" access = 'write'; } + v.ordinal = this.variables.size; this.variables.set(name, v); } else { - if (this.variables.has(name)) { + const existing = this.variables.get(name); + if (existing) { // variable must exist in order to trigger a read access access = 'read'; + v.ordinal = existing.ordinal; } } - const accessType = this.breakAddresses.get(name); + const accessType = this.breakAddresses.get(name) || this.breakAddresses.get(v.ordinal!); if (access && accessType && accessType.indexOf(access) >= 0) { this.sendEvent('stopOnDataBreakpoint', access); return true;