Skip to content

Commit

Permalink
feat: implement DAP memory Event
Browse files Browse the repository at this point in the history
  • Loading branch information
Ricbet committed May 16, 2022
1 parent 091015c commit ddd2e82
Show file tree
Hide file tree
Showing 5 changed files with 324 additions and 4 deletions.
40 changes: 39 additions & 1 deletion packages/debug/src/browser/debug-session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,12 @@ import {
IRuntimeBreakpoint,
BreakpointsChangeEvent,
IDebugBreakpoint,
IMemoryRegion,
} from '../common';
import { DebugConfiguration } from '../common';

import { DebugEditor } from './../common/debug-editor';
import { IDebugModel } from './../common/debug-model';
import { IDebugModel, MemoryRegion } from './../common/debug-model';
import { BreakpointManager, DebugBreakpoint } from './breakpoint';
import { DebugSessionConnection } from './debug-session-connection';
import { DebugModelManager } from './editor/debug-model-manager';
Expand Down Expand Up @@ -92,6 +93,9 @@ export class DebugSession implements IDebugSession {
private readonly _onDidChangeState = new Emitter<DebugState>();
readonly onDidChangeState: Event<DebugState> = this._onDidChangeState.event;

private readonly _onDidInvalidMemory = new Emitter<DebugProtocol.MemoryEvent>();
readonly onDidInvalidateMemory: Event<DebugProtocol.MemoryEvent> = this._onDidInvalidMemory.event;

protected readonly toDispose = new DisposableCollection();

protected _capabilities: DebugProtocol.Capabilities = {};
Expand Down Expand Up @@ -241,6 +245,9 @@ export class DebugSession implements IDebugSession {
this.on('progressEnd', (event: DebugProtocol.ProgressEndEvent) => {
this._onDidProgressEnd.fire(event);
}),
this.on('memory', (event: DebugProtocol.MemoryEvent) => {
this._onDidInvalidMemory.fire(event);
}),
this.on('invalidated', async (event: DebugProtocol.InvalidatedEvent) => {
this._onDidInvalidated.fire(event);

Expand Down Expand Up @@ -271,6 +278,10 @@ export class DebugSession implements IDebugSession {
]);
}

getMemory(memoryReference: string): IMemoryRegion {
return new MemoryRegion(memoryReference, this);
}

get configuration(): DebugConfiguration {
return this.options.configuration;
}
Expand Down Expand Up @@ -1119,4 +1130,31 @@ export class DebugSession implements IDebugSession {
public getModel(): IDebugModel | undefined {
return this.modelManager.model;
}

// memory

public async readMemory(
memoryReference: string,
offset: number,
count: number,
): Promise<DebugProtocol.ReadMemoryResponse | undefined> {
if (this.capabilities.supportsReadMemoryRequest) {
return await this.sendRequest('readMemory', { count, memoryReference, offset });
}
return Promise.resolve(undefined);
}

public async writeMemory(
memoryReference: string,
offset: number,
data: string,
allowPartial?: boolean,
): Promise<DebugProtocol.WriteMemoryResponse | undefined> {
if (this.capabilities.supportsWriteMemoryRequest) {
return await this.sendRequest('writeMemory', { memoryReference, offset, allowPartial, data });
}
return Promise.resolve(undefined);
}

// memory end
}
87 changes: 86 additions & 1 deletion packages/debug/src/common/debug-model.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
import stream from 'stream';

import { IDisposable, MaybePromise, IJSONSchema, IJSONSchemaSnippet, URI } from '@opensumi/ide-core-common';
import {
IDisposable,
MaybePromise,
IJSONSchema,
IJSONSchemaSnippet,
URI,
Disposable,
Emitter,
BinaryBuffer,
decodeBase64,
encodeBase64,
} from '@opensumi/ide-core-common';
import type { editor } from '@opensumi/monaco-editor-core';
import * as monaco from '@opensumi/monaco-editor-core/esm/vs/editor/editor.api';

Expand All @@ -13,6 +24,8 @@ import {
import { DebugConfiguration } from './debug-configuration';
import { DebugEditor } from './debug-editor';
import { IDebugHoverWidget } from './debug-hover';
import { IMemoryInvalidationEvent, IMemoryRegion, MemoryRange, MemoryRangeType } from './debug-service';
import { IDebugSession } from './debug-session';

export interface IDebugBreakpointWidget extends IDisposable {
position: monaco.Position | undefined;
Expand Down Expand Up @@ -160,3 +173,75 @@ export interface IDebugModel extends IDisposable {
getDebugHoverWidget: () => IDebugHoverWidget;
render: () => void;
}

export class MemoryRegion extends Disposable implements IMemoryRegion {
private readonly invalidateEmitter = this.registerDispose(new Emitter<IMemoryInvalidationEvent>());

/** @inheritdoc */
public readonly onDidInvalidate = this.invalidateEmitter.event;

/** @inheritdoc */
public readonly writable = !!this.session.capabilities.supportsWriteMemoryRequest;

constructor(private readonly memoryReference: string, private readonly session: IDebugSession) {
super();
this.registerDispose(
session.onDidInvalidateMemory((e) => {
if (e.body.memoryReference === memoryReference) {
this.invalidate(e.body.offset, e.body.count - e.body.offset);
}
}),
);
}

public async read(fromOffset: number, toOffset: number): Promise<MemoryRange[]> {
const length = toOffset - fromOffset;
const offset = fromOffset;
const result = await this.session.readMemory(this.memoryReference, offset, length);

if (result === undefined || !result.body?.data) {
return [{ type: MemoryRangeType.Unreadable, offset, length }];
}

let data: BinaryBuffer;
try {
data = decodeBase64(result.body.data);
} catch {
return [{ type: MemoryRangeType.Error, offset, length, error: 'Invalid base64 data from debug adapter' }];
}

const unreadable = result.body.unreadableBytes || 0;
const dataLength = length - unreadable;
if (data.byteLength < dataLength) {
const pad = BinaryBuffer.alloc(dataLength - data.byteLength);
pad.buffer.fill(0);
data = BinaryBuffer.concat([data, pad], dataLength);
} else if (data.byteLength > dataLength) {
data = data.slice(0, dataLength);
}

if (!unreadable) {
return [{ type: MemoryRangeType.Valid, offset, length, data }];
}

return [
{ type: MemoryRangeType.Valid, offset, length: dataLength, data },
{ type: MemoryRangeType.Unreadable, offset: offset + dataLength, length: unreadable },
];
}

public async write(offset: number, data: BinaryBuffer): Promise<number> {
const result = await this.session.writeMemory(this.memoryReference, offset, encodeBase64(data), true);
const written = result?.body?.bytesWritten ?? data.byteLength;
this.invalidate(offset, offset + written);
return written;
}

public override dispose() {
super.dispose();
}

private invalidate(fromOffset: number, toOffset: number) {
this.invalidateEmitter.fire({ fromOffset, toOffset });
}
}
78 changes: 77 additions & 1 deletion packages/debug/src/common/debug-service.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,84 @@
import { IDisposable, IJSONSchema, IJSONSchemaSnippet, ApplicationError, Event } from '@opensumi/ide-core-common';
import {
IDisposable,
IJSONSchema,
IJSONSchemaSnippet,
ApplicationError,
Event,
BinaryBuffer,
} from '@opensumi/ide-core-common';

import { DebugConfiguration } from './debug-configuration';
import { IDebugSessionDTO } from './debug-session-options';

export interface IMemoryInvalidationEvent {
fromOffset: number;
toOffset: number;
}

export const enum MemoryRangeType {
Valid,
Unreadable,
Error,
}

export interface IMemoryRange {
type: MemoryRangeType;
offset: number;
length: number;
}

export interface IUnreadableMemoryRange extends IMemoryRange {
type: MemoryRangeType.Unreadable;
}

export interface IErrorMemoryRange extends IMemoryRange {
type: MemoryRangeType.Error;
error: string;
}

export interface IValidMemoryRange extends IMemoryRange {
type: MemoryRangeType.Valid;
offset: number;
length: number;
data: BinaryBuffer;
}

/**
* Union type of memory that can be returned from read(). Since a read request
* could encompass multiple previously-read ranges, multiple of these types
* are possible to return.
*/
export type MemoryRange = IValidMemoryRange | IUnreadableMemoryRange | IErrorMemoryRange;

/**
* An IMemoryRegion corresponds to a contiguous range of memory referred to
* by a DAP `memoryReference`.
*/
export interface IMemoryRegion extends IDisposable {
/**
* Event that fires when memory changes. Can be a result of memory events or
* `write` requests.
*/
readonly onDidInvalidate: Event<IMemoryInvalidationEvent>;

/**
* Whether writes are supported on this memory region.
*/
readonly writable: boolean;

/**
* Requests memory ranges from the debug adapter. It returns a list of memory
* ranges that overlap (but may exceed!) the given offset. Use the `offset`
* and `length` of each range for display.
*/
read(fromOffset: number, toOffset: number): Promise<MemoryRange[]>;

/**
* Writes memory to the debug adapter at the given offset.
*/
write(offset: number, data: BinaryBuffer): Promise<number>;
}

export interface DebuggerDescription {
type: string;
label: string;
Expand Down
18 changes: 17 additions & 1 deletion packages/debug/src/common/debug-session.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { CancellationToken, IDisposable } from '@opensumi/ide-core-common';
import { CancellationToken, Event, IDisposable } from '@opensumi/ide-core-common';
import { DebugProtocol } from '@opensumi/vscode-debugprotocol';

import { DebugConfiguration } from './debug-configuration';
Expand Down Expand Up @@ -45,6 +45,19 @@ export interface IDebugSession extends IDisposable {
state: DebugState;
parentSession: IDebugSession | undefined;
id: string;
capabilities: DebugProtocol.Capabilities;
onDidInvalidateMemory: Event<DebugProtocol.MemoryEvent>;
readMemory(
memoryReference: string,
offset: number,
count: number,
): Promise<DebugProtocol.ReadMemoryResponse | undefined>;
writeMemory(
memoryReference: string,
offset: number,
data: string,
allowPartial?: boolean | undefined,
): Promise<DebugProtocol.WriteMemoryResponse | undefined>;
hasSeparateRepl: () => boolean;
getDebugProtocolBreakpoint(breakpointId: string): DebugProtocol.Breakpoint | undefined;
compact: boolean;
Expand Down Expand Up @@ -155,6 +168,8 @@ export interface DebugRequestTypes {
threads: [DebugProtocol.ThreadsArguments | null, DebugProtocol.ThreadsResponse];
variables: [DebugProtocol.VariablesArguments, DebugProtocol.VariablesResponse];
cancel: [DebugProtocol.CancelArguments, DebugProtocol.CancelResponse];
readMemory: [DebugProtocol.ReadMemoryArguments, DebugProtocol.ReadMemoryResponse];
writeMemory: [DebugProtocol.WriteMemoryArguments, DebugProtocol.WriteMemoryResponse];
}

export interface DebugEventTypes {
Expand All @@ -173,5 +188,6 @@ export interface DebugEventTypes {
progressStart: DebugProtocol.ProgressStartEvent;
progressUpdate: DebugProtocol.ProgressUpdateEvent;
progressEnd: DebugProtocol.ProgressEndEvent;
memory: DebugProtocol.MemoryEvent;
invalidated: DebugProtocol.InvalidatedEvent;
}
Loading

0 comments on commit ddd2e82

Please sign in to comment.