Skip to content

Commit cbfcb9e

Browse files
authored
Allow to set data breakpoints (#139)
* Allow to create data breakpoints * Use experimental flag and use vscode context / decoration contributions to change columns - Show error message when the debugger fails to set the breakpoint * Introduce inspect command * PR Feedback * Log breakpoint inspect to output channel and remove internal from command title
1 parent 15a22ce commit cbfcb9e

33 files changed

Lines changed: 1216 additions & 59 deletions

media/memory-table.css

Lines changed: 72 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,44 @@
3232
outline-offset: -1px;
3333
}
3434

35+
.memory-inspector-table tbody .column-address {
36+
position: relative;
37+
}
38+
39+
.memory-inspector-table tbody .address-status {
40+
position: absolute;
41+
left: -1px;
42+
align-items: center;
43+
display: flex;
44+
justify-content: center;
45+
}
46+
47+
.memory-inspector-table tbody .address-status.codicon {
48+
font-size: 12px;
49+
}
50+
51+
.memory-inspector-table tbody .address-status.codicon-debug-breakpoint {
52+
color: var(--vscode-debugIcon-breakpointForeground);
53+
}
54+
55+
.memory-inspector-table tbody .address-status.codicon-debug-stackframe {
56+
color: var(--vscode-debugIcon-breakpointCurrentStackframeForeground);
57+
}
58+
59+
.memory-inspector-table
60+
tbody
61+
.address-status.codicon-debug-breakpoint.codicon-debug-stackframe:after {
62+
content: "\ea71";
63+
position: absolute;
64+
left: 3px;
65+
font-size: 6px;
66+
color: var(--vscode-debugIcon-breakpointForeground);
67+
}
68+
69+
.memory-inspector-table tbody .debug-hit {
70+
outline-color: var(--vscode-debugIcon-breakpointCurrentStackframeForeground);
71+
}
72+
3573
/* == MoreMemorySelect == */
3674

3775
.bytes-select {
@@ -92,7 +130,7 @@
92130

93131
.memory-inspector-table span.p-column-resizer {
94132
border-right: 2px solid var(--vscode-editor-lineHighlightBorder);
95-
transition: border-right .1s ease-out;
133+
transition: border-right 0.1s ease-out;
96134
}
97135

98136
.memory-inspector-table span.p-column-resizer:hover {
@@ -113,7 +151,7 @@
113151
/* Basic hover formatting (copied from Monaco hovers) */
114152
.memory-hover {
115153
min-width: fit-content;
116-
max-width: var(--vscode-hover-maxWidth,500px);
154+
max-width: var(--vscode-hover-maxWidth, 500px);
117155
border: 1px solid var(--vscode-editorHoverWidget-border);
118156
border-radius: 3px;
119157

@@ -129,34 +167,40 @@
129167
border-collapse: collapse;
130168
border-style: hidden;
131169
}
170+
132171
.memory-hover table caption {
133172
padding: 4px;
134173
border-bottom: 1px solid var(--vscode-editorHoverWidget-border);
135174
}
175+
136176
.memory-hover td {
137177
border: 1px solid var(--vscode-editorHoverWidget-border);
138178
padding: 2px 8px;
139179
}
180+
140181
.memory-hover td:first-child {
141182
text-align: right;
142183
}
143184

144185
/* Colors for the hover fields */
145-
.memory-hover .label-value-pair>.label {
146-
color: var(--vscode-debugTokenExpression-string);
147-
white-space: nowrap;
186+
.memory-hover .label-value-pair > .label {
187+
color: var(--vscode-debugTokenExpression-string);
188+
white-space: nowrap;
148189
}
149-
.memory-hover .label-value-pair>.value {
190+
191+
.memory-hover .label-value-pair > .value {
150192
color: var(--vscode-debugTokenExpression-number);
151193
}
152194

153195
/* Colors for specific hover fields */
154196
.memory-hover .address-hover .primary {
155197
background-color: var(--vscode-list-hoverBackground);
156198
}
199+
157200
.memory-hover table caption {
158201
color: var(--vscode-symbolIcon-variableForeground);
159202
}
203+
160204
.memory-hover .address-hover .value.utf8,
161205
.memory-hover .data-hover .value.utf8,
162206
.memory-hover .variable-hover .value.type {
@@ -171,38 +215,42 @@
171215
}
172216

173217
.p-datatable .p-datatable-tbody > tr > td[data-column="data"][role="cell"],
174-
.p-datatable .p-datatable-tbody > tr > td[data-column="variables"][role="cell"] {
218+
.p-datatable
219+
.p-datatable-tbody
220+
> tr
221+
> td[data-column="variables"][role="cell"] {
175222
padding: 0 12px;
176223
vertical-align: middle;
177224
}
178225

179226
/* Group Styles */
180227

181-
[role='group']:hover {
228+
[role="group"]:hover {
182229
border-bottom: 0px;
183230
outline: 1px solid var(--vscode-list-focusOutline);
184231
}
185232

186-
[role='group'][data-group-selected='true'] {
233+
[role="group"][data-group-selected="true"] {
187234
background: var(--vscode-list-activeSelectionBackground);
188235
color: var(--vscode-list-activeSelectionForeground);
189236
outline: 1px solid var(--vscode-list-activeSelectionBackground);
190237
}
191238

192-
[role='group']:focus-visible,
193-
[role='group']:focus {
239+
[role="group"]:focus-visible,
240+
[role="group"]:focus {
194241
outline: 1px solid var(--vscode-list-focusOutline);
195242
}
196243

197244
[data-column="address"][role="group"],
198245
[data-column="ascii"][role="group"] {
199246
padding: 4px 12px;
200247
display: flex;
248+
align-items: center;
201249
outline-offset: -1px;
202250
}
203251

204252
[data-column="data"][role="group"],
205-
[data-column="variables"][role="group"] {
253+
[data-column="variables"][role="group"] {
206254
padding: 4px 1px;
207255
line-height: 23.5px;
208256
outline-offset: -1px;
@@ -241,7 +289,17 @@
241289
text-indent: 2px;
242290
}
243291

244-
.p-datatable .p-datatable-tbody > tr > td.p-highlight:has(>.selected) {
292+
.p-datatable .p-datatable-tbody > tr > td.p-highlight:has(> .selected) {
245293
background: transparent;
246294
outline: none;
247-
}
295+
}
296+
297+
/* == Data Breakpoint == */
298+
299+
.memory-inspector-table .data-breakpoint {
300+
outline: 1px solid var(--vscode-debugIcon-breakpointForeground);
301+
}
302+
303+
.memory-inspector-table .data-breakpoint.data-breakpoint-external {
304+
outline-style: dashed;
305+
}

package.json

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,42 @@
9292
"title": "Go to value in Memory Inspector",
9393
"category": "Memory"
9494
},
95+
{
96+
"command": "memory-inspector.data-breakpoint.set.read",
97+
"title": "Break on Value Read",
98+
"enablement": "memory-inspector.canWrite && memory-inspector.dataBreakpoints",
99+
"category": "Memory"
100+
},
101+
{
102+
"command": "memory-inspector.data-breakpoint.set.readWrite",
103+
"title": "Break on Value Access",
104+
"enablement": "memory-inspector.canWrite && memory-inspector.dataBreakpoints",
105+
"category": "Memory"
106+
},
107+
{
108+
"command": "memory-inspector.data-breakpoint.set.write",
109+
"title": "Break on Value Change",
110+
"enablement": "memory-inspector.canWrite && memory-inspector.dataBreakpoints",
111+
"category": "Memory"
112+
},
113+
{
114+
"command": "memory-inspector.data-breakpoint.remove",
115+
"title": "Remove Breakpoint",
116+
"enablement": "memory-inspector.canWrite && memory-inspector.dataBreakpoints",
117+
"category": "Memory"
118+
},
119+
{
120+
"command": "memory-inspector.data-breakpoint.remove-all",
121+
"title": "Remove All Memory Inspector Breakpoints",
122+
"enablement": "memory-inspector.canWrite && memory-inspector.dataBreakpoints",
123+
"category": "Memory"
124+
},
125+
{
126+
"command": "memory-inspector.data-breakpoint.inspect-breakpoints",
127+
"title": "Inspect Breakpoints",
128+
"enablement": "memory-inspector.canWrite && memory-inspector.dataBreakpoints && (memory-inspector.loggingVerbosity == 'debug' || memory-inspector.loggingVerbosity == 'info')",
129+
"category": "Memory"
130+
},
95131
{
96132
"command": "memory-inspector.toggle-variables-column",
97133
"title": "Toggle Variables Column",
@@ -156,6 +192,22 @@
156192
},
157193
{
158194
"command": "memory-inspector.apply-file"
195+
},
196+
{
197+
"command": "memory-inspector.data-breakpoint.set.read",
198+
"when": "false"
199+
},
200+
{
201+
"command": "memory-inspector.data-breakpoint.set.readWrite",
202+
"when": "false"
203+
},
204+
{
205+
"command": "memory-inspector.data-breakpoint.set.write",
206+
"when": "false"
207+
},
208+
{
209+
"command": "memory-inspector.data-breakpoint.remove",
210+
"when": "false"
159211
}
160212
],
161213
"debug/variables/context": [
@@ -237,6 +289,31 @@
237289
"command": "memory-inspector.reset-display-options",
238290
"group": "a_reset@2",
239291
"when": "webviewId === memory-inspector.memory && optionsMenu"
292+
},
293+
{
294+
"command": "memory-inspector.data-breakpoint.set.read",
295+
"group": "breakpoints@1",
296+
"when": "memory-inspector.dataBreakpoints && webviewId === memory-inspector.memory && memory-inspector.breakpoint.isBreakable"
297+
},
298+
{
299+
"command": "memory-inspector.data-breakpoint.set.write",
300+
"group": "breakpoints@2",
301+
"when": "memory-inspector.dataBreakpoints && webviewId === memory-inspector.memory && memory-inspector.breakpoint.isBreakable"
302+
},
303+
{
304+
"command": "memory-inspector.data-breakpoint.set.readWrite",
305+
"group": "breakpoints@3",
306+
"when": "memory-inspector.dataBreakpoints && webviewId === memory-inspector.memory && memory-inspector.breakpoint.isBreakable"
307+
},
308+
{
309+
"command": "memory-inspector.data-breakpoint.remove",
310+
"group": "breakpoints@4",
311+
"when": "memory-inspector.dataBreakpoints && webviewId === memory-inspector.memory && memory-inspector.breakpoint.type === 'internal'"
312+
},
313+
{
314+
"command": "memory-inspector.data-breakpoint.remove-all",
315+
"group": "breakpoints@5",
316+
"when": "memory-inspector.dataBreakpoints && webviewId === memory-inspector.memory && memory-inspector.breakpoint.type === 'internal'"
240317
}
241318
]
242319
},
@@ -279,6 +356,14 @@
279356
],
280357
"description": "C-based debuggers to activate (requires debug session restart)"
281358
},
359+
"memory-inspector.dataBreakpoints": {
360+
"type": "boolean",
361+
"default:": false,
362+
"description": "Enable data breakpoint support.",
363+
"tags": [
364+
"experimental"
365+
]
366+
},
282367
"memory-inspector.refreshOnStop": {
283368
"type": "string",
284369
"enum": [
@@ -447,4 +532,4 @@
447532
"workspace",
448533
"ui"
449534
]
450-
}
535+
}

src/common/breakpoint.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/********************************************************************************
2+
* Copyright (C) 2024 EclipseSource.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* This Source Code may also be made available under the following Secondary
9+
* Licenses when the conditions for such availability set forth in the Eclipse
10+
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
11+
* with the GNU Classpath Exception which is available at
12+
* https://www.gnu.org/software/classpath/license.html.
13+
*
14+
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15+
********************************************************************************/
16+
17+
import { DebugProtocol } from '@vscode/debugprotocol';
18+
import { DebugRequestTypes } from './debug-requests';
19+
20+
/**
21+
* Temp. workaround till we have a proper API for this within VSCode.
22+
*
23+
* @see {@link https://github.com/microsoft/vscode/pull/226735}
24+
*/
25+
export interface ExperimentalDataBreakpoint {
26+
type: TrackedBreakpointType;
27+
breakpoint: DebugProtocol.DataBreakpoint;
28+
/**
29+
* The respective response for the breakpoint.
30+
*/
31+
response: DebugProtocol.Breakpoint;
32+
}
33+
34+
/**
35+
* Temp. workaround till we have a proper API for this within VSCode.
36+
*
37+
* @see {@link https://github.com/microsoft/vscode/pull/226735}
38+
*/
39+
export interface ExperimentalDataBreakpoints {
40+
/**
41+
* Breakpoints set from external contributors.
42+
*/
43+
external: ExperimentalDataBreakpoint[],
44+
/**
45+
* Breakpoints set from us.
46+
*/
47+
internal: ExperimentalDataBreakpoint[]
48+
}
49+
50+
/**
51+
* Temp. workaround till we have a proper API for this within VSCode.
52+
*/
53+
export type TrackedBreakpointType = 'internal' | 'external';
54+
55+
export type DataBreakpointInfoArguments = DebugRequestTypes['dataBreakpointInfo'][0];
56+
export type DataBreakpointInfoResult = DebugRequestTypes['dataBreakpointInfo'][1];
57+
export type SetDataBreakpointsArguments = DebugRequestTypes['setDataBreakpoints'][0];
58+
export type SetDataBreakpointsResult = DebugRequestTypes['setDataBreakpoints'][1];

src/common/debug-requests.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ export interface DebugRequestTypes {
2525
'scopes': [DebugProtocol.ScopesArguments, DebugProtocol.ScopesResponse['body']]
2626
'variables': [DebugProtocol.VariablesArguments, DebugProtocol.VariablesResponse['body']]
2727
'writeMemory': [DebugProtocol.WriteMemoryArguments, DebugProtocol.WriteMemoryResponse['body']]
28+
'dataBreakpointInfo': [DebugProtocol.DataBreakpointInfoArguments, DebugProtocol.DataBreakpointInfoResponse['body']]
29+
'setDataBreakpoints': [DebugProtocol.SetDataBreakpointsArguments, DebugProtocol.SetDataBreakpointsResponse['body']]
2830
}
2931

3032
export interface DebugEvents {
@@ -59,16 +61,28 @@ export function isDebugEvaluateArguments(args: DebugProtocol.EvaluateArguments |
5961
}
6062

6163
export function isDebugRequest<K extends keyof DebugRequestTypes>(command: K, message: unknown): message is DebugRequest<K, DebugRequestTypes[K][0]> {
62-
const assumed = message ? message as DebugProtocol.Request : undefined;
63-
return !!assumed && assumed.type === 'request' && assumed.command === command;
64+
return isDebugRequestType(message) && message.command === command;
6465
}
6566

6667
export function isDebugResponse<K extends keyof DebugRequestTypes>(command: K, message: unknown): message is DebugResponse<K, DebugRequestTypes[K][1]> {
67-
const assumed = message ? message as DebugProtocol.Response : undefined;
68-
return !!assumed && assumed.type === 'response' && assumed.command === command;
68+
return isDebugResponseType(message) && message.command === command;
6969
}
7070

7171
export function isDebugEvent<K extends keyof DebugEvents>(event: K, message: unknown): message is DebugEvents[K] {
72+
return isDebugEventType(message) && message.event === event;
73+
}
74+
75+
export function isDebugRequestType(message: unknown): message is DebugProtocol.Request {
76+
const assumed = message ? message as DebugProtocol.Request : undefined;
77+
return !!assumed && assumed.type === 'request';
78+
}
79+
80+
export function isDebugResponseType(message: unknown): message is DebugProtocol.Response {
81+
const assumed = message ? message as DebugProtocol.Response : undefined;
82+
return !!assumed && assumed.type === 'response';
83+
}
84+
85+
export function isDebugEventType(message: unknown): message is DebugProtocol.Event {
7286
const assumed = message ? message as DebugProtocol.Event : undefined;
73-
return !!assumed && assumed.type === 'event' && assumed.event === event;
87+
return !!assumed && assumed.type === 'event';
7488
}

0 commit comments

Comments
 (0)