Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/debug/browser/debugMemory.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { VSBuffer } from '../../../../base/common/buffer.js';
7
import { Emitter, Event } from '../../../../base/common/event.js';
8
import { Disposable, DisposableStore, toDisposable } from '../../../../base/common/lifecycle.js';
9
import { clamp } from '../../../../base/common/numbers.js';
10
import { assertNever } from '../../../../base/common/assert.js';
11
import { URI } from '../../../../base/common/uri.js';
12
import { FileChangeType, IFileOpenOptions, FilePermission, FileSystemProviderCapabilities, FileSystemProviderErrorCode, FileType, IFileChange, IFileSystemProvider, IStat, IWatchOptions, createFileSystemProviderError } from '../../../../platform/files/common/files.js';
13
import { DEBUG_MEMORY_SCHEME, IDebugService, IDebugSession, IMemoryInvalidationEvent, IMemoryRegion, MemoryRange, MemoryRangeType, State } from '../common/debug.js';
14
15
const rangeRe = /range=([0-9]+):([0-9]+)/;
16
17
export class DebugMemoryFileSystemProvider extends Disposable implements IFileSystemProvider {
18
private memoryFdCounter = 0;
19
private readonly fdMemory = new Map<number, { session: IDebugSession; region: IMemoryRegion }>();
20
private readonly changeEmitter = new Emitter<readonly IFileChange[]>();
21
22
/** @inheritdoc */
23
public readonly onDidChangeCapabilities = Event.None;
24
25
/** @inheritdoc */
26
public readonly onDidChangeFile = this.changeEmitter.event;
27
28
/** @inheritdoc */
29
public readonly capabilities = 0
30
| FileSystemProviderCapabilities.PathCaseSensitive
31
| FileSystemProviderCapabilities.FileOpenReadWriteClose;
32
33
constructor(private readonly debugService: IDebugService) {
34
super();
35
36
this._register(debugService.onDidEndSession(({ session }) => {
37
for (const [fd, memory] of this.fdMemory) {
38
if (memory.session === session) {
39
this.close(fd);
40
}
41
}
42
}));
43
}
44
45
public watch(resource: URI, opts: IWatchOptions) {
46
if (opts.recursive) {
47
return toDisposable(() => { });
48
}
49
50
const { session, memoryReference, offset } = this.parseUri(resource);
51
const disposable = new DisposableStore();
52
53
disposable.add(session.onDidChangeState(() => {
54
if (session.state === State.Running || session.state === State.Inactive) {
55
this.changeEmitter.fire([{ type: FileChangeType.DELETED, resource }]);
56
}
57
}));
58
59
disposable.add(session.onDidInvalidateMemory(e => {
60
if (e.body.memoryReference !== memoryReference) {
61
return;
62
}
63
64
if (offset && (e.body.offset >= offset.toOffset || e.body.offset + e.body.count < offset.fromOffset)) {
65
return;
66
}
67
68
this.changeEmitter.fire([{ resource, type: FileChangeType.UPDATED }]);
69
}));
70
71
return disposable;
72
}
73
74
/** @inheritdoc */
75
public stat(file: URI): Promise<IStat> {
76
const { readOnly } = this.parseUri(file);
77
return Promise.resolve({
78
type: FileType.File,
79
mtime: 0,
80
ctime: 0,
81
size: 0,
82
permissions: readOnly ? FilePermission.Readonly : undefined,
83
});
84
}
85
86
/** @inheritdoc */
87
public mkdir(): never {
88
throw createFileSystemProviderError(`Not allowed`, FileSystemProviderErrorCode.NoPermissions);
89
}
90
91
/** @inheritdoc */
92
public readdir(): never {
93
throw createFileSystemProviderError(`Not allowed`, FileSystemProviderErrorCode.NoPermissions);
94
}
95
96
/** @inheritdoc */
97
public delete(): never {
98
throw createFileSystemProviderError(`Not allowed`, FileSystemProviderErrorCode.NoPermissions);
99
}
100
101
/** @inheritdoc */
102
public rename(): never {
103
throw createFileSystemProviderError(`Not allowed`, FileSystemProviderErrorCode.NoPermissions);
104
}
105
106
/** @inheritdoc */
107
public open(resource: URI, _opts: IFileOpenOptions): Promise<number> {
108
const { session, memoryReference, offset } = this.parseUri(resource);
109
const fd = this.memoryFdCounter++;
110
let region = session.getMemory(memoryReference);
111
if (offset) {
112
region = new MemoryRegionView(region, offset);
113
}
114
115
this.fdMemory.set(fd, { session, region });
116
return Promise.resolve(fd);
117
}
118
119
/** @inheritdoc */
120
public close(fd: number) {
121
this.fdMemory.get(fd)?.region.dispose();
122
this.fdMemory.delete(fd);
123
return Promise.resolve();
124
}
125
126
/** @inheritdoc */
127
public async writeFile(resource: URI, content: Uint8Array) {
128
const { offset } = this.parseUri(resource);
129
if (!offset) {
130
throw createFileSystemProviderError(`Range must be present to read a file`, FileSystemProviderErrorCode.FileNotFound);
131
}
132
133
const fd = await this.open(resource, { create: false });
134
135
try {
136
await this.write(fd, offset.fromOffset, content, 0, content.length);
137
} finally {
138
this.close(fd);
139
}
140
}
141
142
/** @inheritdoc */
143
public async readFile(resource: URI) {
144
const { offset } = this.parseUri(resource);
145
if (!offset) {
146
throw createFileSystemProviderError(`Range must be present to read a file`, FileSystemProviderErrorCode.FileNotFound);
147
}
148
149
const data = new Uint8Array(offset.toOffset - offset.fromOffset);
150
const fd = await this.open(resource, { create: false });
151
152
try {
153
await this.read(fd, offset.fromOffset, data, 0, data.length);
154
return data;
155
} finally {
156
this.close(fd);
157
}
158
}
159
160
/** @inheritdoc */
161
public async read(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> {
162
const memory = this.fdMemory.get(fd);
163
if (!memory) {
164
throw createFileSystemProviderError(`No file with that descriptor open`, FileSystemProviderErrorCode.Unavailable);
165
}
166
167
const ranges = await memory.region.read(pos, length);
168
let readSoFar = 0;
169
for (const range of ranges) {
170
switch (range.type) {
171
case MemoryRangeType.Unreadable:
172
return readSoFar;
173
case MemoryRangeType.Error:
174
if (readSoFar > 0) {
175
return readSoFar;
176
} else {
177
throw createFileSystemProviderError(range.error, FileSystemProviderErrorCode.Unknown);
178
}
179
case MemoryRangeType.Valid: {
180
const start = Math.max(0, pos - range.offset);
181
const toWrite = range.data.slice(start, Math.min(range.data.byteLength, start + (length - readSoFar)));
182
data.set(toWrite.buffer, offset + readSoFar);
183
readSoFar += toWrite.byteLength;
184
break;
185
}
186
default:
187
assertNever(range);
188
}
189
}
190
191
return readSoFar;
192
}
193
194
/** @inheritdoc */
195
public write(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> {
196
const memory = this.fdMemory.get(fd);
197
if (!memory) {
198
throw createFileSystemProviderError(`No file with that descriptor open`, FileSystemProviderErrorCode.Unavailable);
199
}
200
201
return memory.region.write(pos, VSBuffer.wrap(data).slice(offset, offset + length));
202
}
203
204
protected parseUri(uri: URI) {
205
if (uri.scheme !== DEBUG_MEMORY_SCHEME) {
206
throw createFileSystemProviderError(`Cannot open file with scheme ${uri.scheme}`, FileSystemProviderErrorCode.FileNotFound);
207
}
208
209
const session = this.debugService.getModel().getSession(uri.authority);
210
if (!session) {
211
throw createFileSystemProviderError(`Debug session not found`, FileSystemProviderErrorCode.FileNotFound);
212
}
213
214
let offset: { fromOffset: number; toOffset: number } | undefined;
215
const rangeMatch = rangeRe.exec(uri.query);
216
if (rangeMatch) {
217
offset = { fromOffset: Number(rangeMatch[1]), toOffset: Number(rangeMatch[2]) };
218
}
219
220
const [, memoryReference] = uri.path.split('/');
221
222
return {
223
session,
224
offset,
225
readOnly: !session.capabilities.supportsWriteMemoryRequest,
226
sessionId: uri.authority,
227
memoryReference: decodeURIComponent(memoryReference),
228
};
229
}
230
}
231
232
/** A wrapper for a MemoryRegion that references a subset of data in another region. */
233
class MemoryRegionView extends Disposable implements IMemoryRegion {
234
private readonly invalidateEmitter = new Emitter<IMemoryInvalidationEvent>();
235
236
public readonly onDidInvalidate = this.invalidateEmitter.event;
237
public readonly writable: boolean;
238
private readonly width: number;
239
240
constructor(private readonly parent: IMemoryRegion, public readonly range: { fromOffset: number; toOffset: number }) {
241
super();
242
this.writable = parent.writable;
243
this.width = range.toOffset - range.fromOffset;
244
245
this._register(parent);
246
this._register(parent.onDidInvalidate(e => {
247
const fromOffset = clamp(e.fromOffset - range.fromOffset, 0, this.width);
248
const toOffset = clamp(e.toOffset - range.fromOffset, 0, this.width);
249
if (toOffset > fromOffset) {
250
this.invalidateEmitter.fire({ fromOffset, toOffset });
251
}
252
}));
253
}
254
255
public read(fromOffset: number, toOffset: number): Promise<MemoryRange[]> {
256
if (fromOffset < 0) {
257
throw new RangeError(`Invalid fromOffset: ${fromOffset}`);
258
}
259
260
return this.parent.read(
261
this.range.fromOffset + fromOffset,
262
this.range.fromOffset + Math.min(toOffset, this.width),
263
);
264
}
265
266
public write(offset: number, data: VSBuffer): Promise<number> {
267
return this.parent.write(this.range.fromOffset + offset, data);
268
}
269
}
270
271