Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 10 additions & 22 deletions src/debug-session/gdbtarget-debug-tracker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ import {
ContinuedEvent,
GDBTargetDebugTracker,
SessionStackItem,
StackTraceRequest,
StackTraceResponse,
SessionStackTrace,
StoppedEvent
} from './gdbtarget-debug-tracker';
import { debugSessionFactory, extensionContextFactory } from '../__test__/vscode.factory';
Expand Down Expand Up @@ -160,13 +159,12 @@ describe('GDBTargetDebugTracker', () => {
expect(connectedSession).toBeDefined();
});

it('sends an onStackTraceRequest event', async () => {
it('sends an onStackTrace event', async () => {
const tracker = await adapterFactory!.createDebugAdapterTracker(debugSessionFactory(debugConfigurationFactory()));
let gdbSession: GDBTargetDebugSession|undefined = undefined;
debugTracker.onWillStartSession(session => gdbSession = session);
let result: StackTraceRequest|undefined = undefined;
debugTracker.onStackTraceRequest(request => result = request);
tracker!.onWillStartSession!();
let result: SessionStackTrace;
debugTracker.onStackTrace(request => result = request);
const stackTraceRequest: DebugProtocol.StackTraceRequest = {
command: 'stackTrace',
type: 'request',
Expand All @@ -175,20 +173,6 @@ describe('GDBTargetDebugTracker', () => {
threadId: 1
}
};
tracker!.onWillReceiveMessage!(stackTraceRequest);
expect(gdbSession).toBeDefined();
expect(result).toBeDefined();
expect(result!.session).toEqual(gdbSession);
expect(result!.request).toEqual(stackTraceRequest);
});

it('sends an onStackTraceResponse event', async () => {
let gdbSession: GDBTargetDebugSession|undefined = undefined;
debugTracker.onWillStartSession(session => gdbSession = session);
let result: StackTraceResponse|undefined = undefined;
debugTracker.onStackTraceResponse(response => result = response);
const tracker = await adapterFactory!.createDebugAdapterTracker(debugSessionFactory(debugConfigurationFactory()));
tracker!.onWillStartSession!();
const stackTraceResponse: DebugProtocol.StackTraceResponse = {
command: 'stackTrace',
type: 'response',
Expand All @@ -206,11 +190,15 @@ describe('GDBTargetDebugTracker', () => {
]
}
};
tracker!.onWillStartSession!();
tracker!.onWillReceiveMessage!(stackTraceRequest);
tracker!.onDidSendMessage!(stackTraceResponse);
expect(gdbSession).toBeDefined();
expect(result).toBeDefined();
expect(result!).toBeDefined();
expect(result!.session).toEqual(gdbSession);
expect(result!.response).toEqual(stackTraceResponse);
expect(result!.threadId).toEqual(stackTraceRequest.arguments.threadId);
expect(result!.stackFrames).toEqual(stackTraceResponse.body!.stackFrames);
expect(result!.totalFrames).toEqual(stackTraceResponse.body!.totalFrames);
});

it('sends a session continued event', async () => {
Expand Down
170 changes: 102 additions & 68 deletions src/debug-session/gdbtarget-debug-tracker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,13 @@ export interface SessionEvent<T extends DebugProtocol.Event> {
export type ContinuedEvent = SessionEvent<DebugProtocol.ContinuedEvent>;
export type StoppedEvent = SessionEvent<DebugProtocol.StoppedEvent>;

export interface SessionRequest<T extends DebugProtocol.Request> {
export interface SessionStackTrace {
session: GDBTargetDebugSession;
request: T;
threadId: number;
stackFrames: DebugProtocol.StackFrame[];
totalFrames?: number|undefined;
}

export interface SessionResponse<T extends DebugProtocol.Response> {
session: GDBTargetDebugSession;
response: T;
}

export type StackTraceRequest = SessionRequest<DebugProtocol.StackTraceRequest>;
export type StackTraceResponse = SessionResponse<DebugProtocol.StackTraceResponse>;

export type StackItem = vscode.DebugThread | vscode.DebugStackFrame | undefined;

export interface SessionStackItem {
Expand All @@ -50,6 +44,7 @@ export interface SessionStackItem {

export class GDBTargetDebugTracker {
private sessions: Map<string, GDBTargetDebugSession> = new Map();
private stackTraceRequests: Map<string, Map<number, number>> = new Map();

private readonly _onWillStartSession: vscode.EventEmitter<GDBTargetDebugSession> = new vscode.EventEmitter<GDBTargetDebugSession>();
public readonly onWillStartSession: vscode.Event<GDBTargetDebugSession> = this._onWillStartSession.event;
Expand All @@ -72,28 +67,14 @@ export class GDBTargetDebugTracker {
private readonly _onStopped: vscode.EventEmitter<StoppedEvent> = new vscode.EventEmitter<StoppedEvent>();
public readonly onStopped: vscode.Event<StoppedEvent> = this._onStopped.event;

private readonly _onStackTraceRequest: vscode.EventEmitter<StackTraceRequest> = new vscode.EventEmitter<StackTraceRequest>();
public readonly onStackTraceRequest: vscode.Event<StackTraceRequest> = this._onStackTraceRequest.event;

private readonly _onStackTraceResponse: vscode.EventEmitter<StackTraceResponse> = new vscode.EventEmitter<StackTraceResponse>();
public readonly onStackTraceResponse: vscode.Event<StackTraceResponse> = this._onStackTraceResponse.event;
private readonly _onStackTrace: vscode.EventEmitter<SessionStackTrace> = new vscode.EventEmitter<SessionStackTrace>();
public readonly onStackTrace: vscode.Event<SessionStackTrace> = this._onStackTrace.event;

public activate(context: vscode.ExtensionContext) {
const createDebugAdapterTracker = (session: vscode.DebugSession): vscode.ProviderResult<vscode.DebugAdapterTracker> => {
return {
onWillStartSession: () => {
const gdbTargetSession = new GDBTargetDebugSession(session);
this.sessions.set(session.id, gdbTargetSession);
this.bringConsoleToFront.apply(this);
this._onWillStartSession.fire(gdbTargetSession);
},
onWillStopSession: () => {
const gdbTargetSession = this.sessions.get(session.id);
if (gdbTargetSession) {
this.sessions.delete(session.id);
this._onWillStopSession.fire(gdbTargetSession);
}
},
onWillStartSession: () => this.handleOnWillStartSession(session),
onWillStopSession: () => this.handleOnWillStopSession(session),
onDidSendMessage: (message) => this.handleOnDidSendMessage(session, message),
onWillReceiveMessage: (message) => this.handleOnWillReceiveMessage(session, message),
};
Expand All @@ -107,39 +88,81 @@ export class GDBTargetDebugTracker {
);
};

private handleOnWillStartSession(session: vscode.DebugSession): void {
const gdbTargetSession = new GDBTargetDebugSession(session);
this.sessions.set(session.id, gdbTargetSession);
this.bringConsoleToFront();
this._onWillStartSession.fire(gdbTargetSession);
}

private handleOnWillStopSession(session: vscode.DebugSession): void {
const gdbTargetSession = this.sessions.get(session.id);
if (gdbTargetSession) {
this.sessions.delete(session.id);
this._onWillStopSession.fire(gdbTargetSession);
}
this.stackTraceRequests.delete(session.id);
}

protected handleOnDidSendMessage(session: vscode.DebugSession, message?: DebugProtocol.ProtocolMessage): void {
if (!message) {
return;
}
if (message.type === 'event') {
const event = message as DebugProtocol.Event;
const gdbTargetSession = this.sessions.get(session.id);
switch (event.event) {
case 'continued':
this._onContinued.fire({ session: gdbTargetSession, event } as ContinuedEvent);
break;
case 'stopped':
this._onStopped.fire({ session: gdbTargetSession, event } as StoppedEvent);
break;
}
this.handleEvent(session, message as DebugProtocol.Event);
} else if (message.type === 'response') {
const response = message as DebugProtocol.Response;
const gdbTargetSession = this.sessions.get(session.id);
switch (response.command) {
case 'stackTrace':
if (!gdbTargetSession) {
break;
}
this._onStackTraceResponse.fire({ session: gdbTargetSession, response } as StackTraceResponse);
break;
case 'launch':
case 'attach':
if (response.success && gdbTargetSession) {
this._onConnected.fire(gdbTargetSession);
}
break;
}
this.handleResponse(session, message as DebugProtocol.Response);
}
}

private handleEvent(session: vscode.DebugSession, event: DebugProtocol.Event): void {
const gdbTargetSession = this.sessions.get(session.id);
switch (event.event) {
case 'continued':
this._onContinued.fire({ session: gdbTargetSession, event } as ContinuedEvent);
break;
case 'stopped':
this._onStopped.fire({ session: gdbTargetSession, event } as StoppedEvent);
break;
}
}

private handleResponse(session: vscode.DebugSession, response: DebugProtocol.Response): void {
const gdbTargetSession = this.sessions.get(session.id);
if (!gdbTargetSession) {
return;
}
switch (response.command) {
case 'launch':
case 'attach':
this.handleLaunchAttachResponse(gdbTargetSession, response);
break;
case 'stackTrace':
this.handleStackTraceResponse(gdbTargetSession, response as DebugProtocol.StackTraceResponse);
break;
}
}

private handleLaunchAttachResponse(gdbTargetSession: GDBTargetDebugSession, response: DebugProtocol.Response): void {
if (response.success) {
this._onConnected.fire(gdbTargetSession);
}
}

private handleStackTraceResponse(gdbTargetSession: GDBTargetDebugSession, response: DebugProtocol.StackTraceResponse): void {
const stackTraceRequest = this.stackTraceRequests.get(gdbTargetSession.session.id);
const threadId = stackTraceRequest?.get(response.request_seq);
stackTraceRequest?.delete(response.request_seq);
if (!response.success || threadId === undefined) {
return;
}
const stackTrace = {
session: gdbTargetSession,
threadId,
stackFrames: response.body.stackFrames,
totalFrames: response.body.totalFrames
};
this._onStackTrace.fire(stackTrace);
}

protected handleOnWillReceiveMessage(session: vscode.DebugSession, message?: DebugProtocol.ProtocolMessage): void {
Expand All @@ -149,30 +172,41 @@ export class GDBTargetDebugTracker {
if (message.type === 'request') {
const request = message as DebugProtocol.Request;
const gdbTargetSession = this.sessions.get(session.id);
if (!gdbTargetSession) {
return;
}
switch (request.command) {
case 'launch':
case 'attach':
{
const gdbTargetConfig = request.arguments as GDBTargetConfiguration;
const cbuildRunFile = gdbTargetConfig.cmsis?.cbuildRunFile;
if (cbuildRunFile && gdbTargetSession) {
// Do not wait for it to keep the message flowing.
// Session class will do the waiting in case requests
// come early.
gdbTargetSession.parseCbuildRun(cbuildRunFile);
}
}
this.handleLaunchAttachRequest(gdbTargetSession, request);
break;
case 'stackTrace':
if (!gdbTargetSession) {
break;
}
this._onStackTraceRequest.fire({ session: gdbTargetSession, request } as StackTraceRequest);
this.handleStackTraceRequest(gdbTargetSession, request as DebugProtocol.StackTraceRequest);
break;
}
}
}

private handleLaunchAttachRequest(gdbTargetSession: GDBTargetDebugSession, request: DebugProtocol.Request): void {
const gdbTargetConfig = request.arguments as GDBTargetConfiguration;
const cbuildRunFile = gdbTargetConfig.cmsis?.cbuildRunFile;
if (cbuildRunFile) {
// Do not wait for it to keep the message flowing.
// Session class will do the waiting in case requests
// come early.
gdbTargetSession.parseCbuildRun(cbuildRunFile);
}
}

private handleStackTraceRequest(gdbTargetSession: GDBTargetDebugSession, request: DebugProtocol.StackTraceRequest): void {
let stackTraceRequests = this.stackTraceRequests.get(gdbTargetSession.session.id);
if (stackTraceRequests === undefined) {
stackTraceRequests = new Map();
this.stackTraceRequests.set(gdbTargetSession.session.id, stackTraceRequests);
}
stackTraceRequests.set(request.seq, request.arguments.threadId);
}

protected handleOnDidChangeActiveStackItem(item: StackItem): void {
const gdbTargetSession = item?.session.id ? this.sessions.get(item?.session.id) : undefined;
if (!gdbTargetSession) {
Expand Down
59 changes: 8 additions & 51 deletions src/features/cpu-states/cpu-states.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import * as vscode from 'vscode';
import { debugSessionFactory } from '../../__test__/vscode.factory';
import { GDBTargetConfiguration } from '../../debug-configuration';
import { ContinuedEvent, GDBTargetDebugSession, GDBTargetDebugTracker, SessionStackItem, StackTraceRequest, StackTraceResponse, StoppedEvent } from '../../debug-session';
import { ContinuedEvent, GDBTargetDebugSession, GDBTargetDebugTracker, SessionStackItem, SessionStackTrace, StoppedEvent } from '../../debug-session';
import { CpuStates } from './cpu-states';
import { DebugProtocol } from '@vscode/debugprotocol';
import { waitForMs } from '../../utils';
Expand Down Expand Up @@ -45,18 +45,6 @@ describe('CpuStates', () => {
} as unknown as DebugProtocol.StoppedEvent
});

const createStackTraceRequest = (session: GDBTargetDebugSession, seq: number, threadId: number): StackTraceRequest => ({
session,
request: {
command: 'stackTrace',
seq,
type: 'request',
arguments: {
threadId
}
}
});

const createStackFrame = (): DebugProtocol.StackFrame => ({
column: 0,
id: 1,
Expand All @@ -68,21 +56,13 @@ describe('CpuStates', () => {
}
});

const createStackTraceResponse = (session: GDBTargetDebugSession, seq: number, request_seq: number): StackTraceResponse => ({
const createSessionStackTrace = (session: GDBTargetDebugSession, threadId: number): SessionStackTrace => ({
session,
response: {
command: 'stackTrace',
type: 'response',
seq,
request_seq,
success: true,
body: {
totalFrames: 1,
stackFrames: [
createStackFrame()
]
}
}
threadId,
stackFrames: [
createStackFrame()
],
totalFrames: 1
});

let debugConfig: GDBTargetConfiguration;
Expand Down Expand Up @@ -347,35 +327,12 @@ describe('CpuStates', () => {
(tracker as any)._onStopped.fire(createStoppedEvent(gdbtargetDebugSession, 'step', 1 /*threadId*/));
await waitForMs(0);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(tracker as any)._onStackTraceRequest.fire(createStackTraceRequest(gdbtargetDebugSession, 1, 1));
await waitForMs(0);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(tracker as any)._onStackTraceResponse.fire(createStackTraceResponse(gdbtargetDebugSession, 4, 1));
(tracker as any)._onStackTrace.fire(createSessionStackTrace(gdbtargetDebugSession, 1));
await waitForMs(0);
cpuStates.showStatesHistory();
expect(debugConsoleOutput.find(line => line.includes('(PC=0x08000396 <myframe>, myfunction::2)'))).toBeDefined();
});

it('does not assign frame location to captured stop point due to threadId mismatch', async () => {
const debugConsoleOutput: string[] = [];
(vscode.debug.activeDebugConsole.appendLine as jest.Mock).mockImplementation(line => debugConsoleOutput.push(line));
(debugSession.customRequest as jest.Mock).mockResolvedValueOnce({
address: '0xE0001004',
data: new Uint8Array([ 10, 0x00, 0x00, 0x00 ]).buffer
});
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(tracker as any)._onStopped.fire(createStoppedEvent(gdbtargetDebugSession, 'step', 2 /*threadId*/));
await waitForMs(0);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(tracker as any)._onStackTraceRequest.fire(createStackTraceRequest(gdbtargetDebugSession, 1, 1));
await waitForMs(0);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(tracker as any)._onStackTraceResponse.fire(createStackTraceResponse(gdbtargetDebugSession, 4, 1));
await waitForMs(0);
cpuStates.showStatesHistory();
expect(debugConsoleOutput.find(line => line.includes('(PC=0x08000396 <myframe>::2)'))).toBeUndefined();
});

});

describe('tests with established connection and CPU states not supported', () => {
Expand Down
Loading