Skip to content

Commit

Permalink
Handling the ContinuedEvent (#335)
Browse files Browse the repository at this point in the history
This update implements handling mechanism for "resume" gdb signal and
redirect the "resume" signal as "ContinuedEvent" as described in the
Debug Adapter Protocol

Fixes #332
  • Loading branch information
asimgunes authored Sep 27, 2024
1 parent 9a4cb9f commit 44fda62
Show file tree
Hide file tree
Showing 5 changed files with 202 additions and 5 deletions.
30 changes: 30 additions & 0 deletions src/events/continuedEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*********************************************************************
* Copyright (c) 2024 Renesas Electronics Corporation and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*********************************************************************/
import { Event } from '@vscode/debugadapter';
import { DebugProtocol } from '@vscode/debugprotocol';

export class ContinuedEvent
extends Event
implements DebugProtocol.ContinuedEvent
{
public body: {
threadId: number;
allThreadsContinued?: boolean;
};

constructor(threadId: number, allThreadsContinued = false) {
super('continued');

this.body = {
threadId,
allThreadsContinued,
};
}
}
File renamed without changes.
45 changes: 42 additions & 3 deletions src/gdb/GDBDebugSessionBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ import {
TerminatedEvent,
} from '@vscode/debugadapter';
import { DebugProtocol } from '@vscode/debugprotocol';
import { StoppedEvent } from '../stoppedEvent';
import { ContinuedEvent } from '../events/continuedEvent';
import { StoppedEvent } from '../events/stoppedEvent';
import { VarObjType } from '../varManager';
import {
FrameReference,
Expand Down Expand Up @@ -1601,6 +1602,40 @@ export abstract class GDBDebugSessionBase extends LoggingDebugSession {
}
}

protected sendContinuedEvent(
threadId: number,
allThreadsContinued?: boolean
) {
// Reset frame handles and variables for new context
this.frameHandles.reset();
this.variableHandles.reset();
// Send the event
this.sendEvent(new ContinuedEvent(threadId, allThreadsContinued));
}

protected handleGDBResume(result: any) {
const getThreadId = (resultData: any) => {
return parseInt(resultData['thread-id'], 10);
};
const getAllThreadsContinued = (resultData: any) => {
return (
!!resultData['thread-id'] && resultData['thread-id'] === 'all'
);
};

const isAllThreadsContinued = getAllThreadsContinued(result);
if (isAllThreadsContinued) {
// If all threads continued, then the value of the 'thread-id' is 'all',
// hence, there isn't a thread id number. We are sending the id of the first
// thread in the thread list along with the allThreadsContinued=true information.
// Theoratically, at least we need to have a single thread; for any unexpected case,
// we are sending thread id as '1'.
const id = this.threads[0]?.id;
return this.sendContinuedEvent(id !== undefined ? id : 1, true);
}
return this.sendContinuedEvent(getThreadId(result));
}

protected handleGDBAsync(resultClass: string, resultData: any) {
const updateIsRunning = () => {
this.isRunning = this.threads.length ? true : false;
Expand All @@ -1613,9 +1648,10 @@ export abstract class GDBDebugSessionBase extends LoggingDebugSession {
switch (resultClass) {
case 'running':
if (this.gdb.isNonStopMode()) {
const id = parseInt(resultData['thread-id'], 10);
const rawId = resultData['thread-id'];
const id = parseInt(rawId, 10);
for (const thread of this.threads) {
if (thread.id === id) {
if (thread.id === id || rawId === 'all') {
thread.running = true;
}
}
Expand All @@ -1625,6 +1661,9 @@ export abstract class GDBDebugSessionBase extends LoggingDebugSession {
}
}
updateIsRunning();
if (this.isInitialized) {
this.handleGDBResume(resultData);
}
break;
case 'stopped': {
let suppressHandleGDBStopped = false;
Expand Down
34 changes: 33 additions & 1 deletion src/integration-tests/continues.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
getScopes,
gdbNonStop,
} from './utils';
import { expect } from 'chai';
import { expect, assert } from 'chai';
import * as path from 'path';

describe('continues', async function () {
Expand Down Expand Up @@ -55,4 +55,36 @@ describe('continues', async function () {
});
expect(continueResponse.body.allThreadsContinued).to.eq(!gdbNonStop);
});

it('handles async continued events', async function () {
await dc.setBreakpointsRequest({
source: {
name: 'count.c',
path: path.join(testProgramsDir, 'count.c'),
},
breakpoints: [
{
column: 1,
line: 4,
},
],
});
await dc.configurationDoneRequest();
await dc.waitForEvent('stopped');
const scope = await getScopes(dc);
dc.send('cdt-gdb-tests/executeCommand', {
command: `-exec-continue --thread ${scope.thread.id}`,
});
const event = await dc.waitForEvent('continued');

assert.deepEqual<any>(
event.body,
gdbNonStop
? {
threadId: scope.thread.id,
allThreadsContinued: false,
}
: { threadId: 1, allThreadsContinued: true }
);
});
});
98 changes: 97 additions & 1 deletion src/integration-tests/multithread.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ import {
isRemoteTest,
gdbNonStop,
fillDefaults,
gdbAsync,
} from './utils';
import { expect } from 'chai';
import { assert, expect } from 'chai';
import * as path from 'path';
import { fail } from 'assert';
import * as os from 'os';
Expand All @@ -36,6 +37,7 @@ describe('multithread', async function () {

const lineTags = {
LINE_MAIN_ALL_THREADS_STARTED: 0,
LINE_THREAD_IN_HELLO: 0,
};

before(function () {
Expand Down Expand Up @@ -182,4 +184,98 @@ describe('multithread', async function () {
}
}
});

it('async resume for gdb-non-stop off', async function () {
if (gdbNonStop) {
// This test is covering only gdb-non-stop off mode
this.skip();
} else if (os.platform() === 'win32' && (!isRemoteTest || !gdbAsync)) {
// Only supported in win32 host with remote + mi-async targets
this.skip();
}

await dc.launchRequest(
fillDefaults(this.test, {
program,
})
);
await dc.setBreakpointsRequest({
source: {
path: source,
},
breakpoints: [
{
line: lineTags['LINE_MAIN_ALL_THREADS_STARTED'],
},
{
line: lineTags['LINE_THREAD_IN_HELLO'],
},
],
});

await dc.configurationDoneRequest();
await dc.waitForEvent('stopped');

const threads = await dc.threadsRequest();

// make sure that there is at least 2 threads.
expect(threads.body.threads).length.greaterThanOrEqual(2);

// Send continue to thread 2
dc.send('cdt-gdb-tests/executeCommand', {
command: '-exec-continue --thread 2',
});

const event = await dc.waitForEvent('continued');

// In allThreadsContinued:true case we are expecting id of the first thread no matter which thread is continued
assert.deepEqual(event.body, {
threadId: threads.body.threads[0].id,
allThreadsContinued: true,
});
});

it('async resume for gdb-non-stop on', async function () {
if (!gdbNonStop) {
// This test is covering only gdb-non-stop on
this.skip();
}

await dc.hitBreakpoint(
fillDefaults(this.test, {
program: program,
}),
{
path: source,
line: lineTags['LINE_MAIN_ALL_THREADS_STARTED'],
}
);

const threads = await dc.threadsRequest();

// make sure that there is at least 6 threads.
expect(threads.body.threads).length.greaterThanOrEqual(6);

// stop the running threads
const runningThreads = threads.body.threads.filter(
(t) => (t as unknown as { running?: boolean }).running
);
for (const thread of runningThreads) {
await dc.pauseRequest({ threadId: thread.id });
await dc.waitForEvent('stopped');
}

for (const thread of threads.body.threads) {
// Send an async continue request and wait for the continue event.
dc.send('cdt-gdb-tests/executeCommand', {
command: `-exec-continue --thread ${thread.id}`,
});
const event = await dc.waitForEvent('continued');

assert.deepEqual<any>(event.body, {
threadId: thread.id,
allThreadsContinued: false,
});
}
});
});

0 comments on commit 44fda62

Please sign in to comment.