Skip to content

Commit 225072f

Browse files
committed
Add threadId and FrameId as optional parameters
The GDB MI commands can take a threadId and frameId if specified so add that capability as an optional parameter. Add unit test to verify threadId and frameId changes. Multithread test is needed to showcase this change.
1 parent cd740cd commit 225072f

File tree

6 files changed

+214
-16
lines changed

6 files changed

+214
-16
lines changed

src/GDBDebugSession.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,11 @@ export interface CDTDisassembleArguments
117117
endMemoryReference: string;
118118
}
119119

120+
export interface ThreadContext {
121+
threadId: number;
122+
frameId: number;
123+
}
124+
120125
class ThreadWithStatus implements DebugProtocol.Thread {
121126
id: number;
122127
name: string;
@@ -279,6 +284,11 @@ export class GDBDebugSession extends LoggingDebugSession {
279284
): void {
280285
if (command === 'cdt-gdb-adapter/Memory') {
281286
this.memoryRequest(response as MemoryResponse, args);
287+
} else if (command === 'cdt-gdb-adapter/readMemoryWithContext') {
288+
this.readMemoryWithContextRequest(
289+
response as DebugProtocol.ReadMemoryResponse,
290+
args
291+
);
282292
// This custom request exists to allow tests in this repository to run arbitrary commands
283293
// Use at your own risk!
284294
} else if (command === 'cdt-gdb-tests/executeCommand') {
@@ -1177,6 +1187,7 @@ export class GDBDebugSession extends LoggingDebugSession {
11771187
args.name.replace(/^\[(\d+)\]/, '$1');
11781188
const stackDepth = await mi.sendStackInfoDepth(this.gdb, {
11791189
maxDepth: 100,
1190+
threadId: frame.threadId,
11801191
});
11811192
const depth = parseInt(stackDepth.depth, 10);
11821193
let varobj = this.gdb.varManager.getVar(
@@ -1321,6 +1332,7 @@ export class GDBDebugSession extends LoggingDebugSession {
13211332

13221333
const stackDepth = await mi.sendStackInfoDepth(this.gdb, {
13231334
maxDepth: 100,
1335+
threadId: frame.threadId,
13241336
});
13251337
const depth = parseInt(stackDepth.depth, 10);
13261338
let varobj = this.gdb.varManager.getVar(
@@ -1622,17 +1634,20 @@ export class GDBDebugSession extends LoggingDebugSession {
16221634
}
16231635
}
16241636

1625-
protected async readMemoryRequest(
1637+
protected async readMemoryWithContextRequest(
16261638
response: DebugProtocol.ReadMemoryResponse,
1627-
args: DebugProtocol.ReadMemoryArguments
1639+
fullArgs: [DebugProtocol.ReadMemoryArguments, ThreadContext?]
16281640
): Promise<void> {
1641+
const [args, context] = fullArgs;
16291642
try {
16301643
if (args.count) {
16311644
const result = await sendDataReadMemoryBytes(
16321645
this.gdb,
16331646
args.memoryReference,
16341647
args.count,
1635-
args.offset
1648+
args.offset,
1649+
context?.threadId,
1650+
context?.frameId
16361651
);
16371652
response.body = {
16381653
data: hexToBase64(result.memory[0].contents),
@@ -1651,6 +1666,13 @@ export class GDBDebugSession extends LoggingDebugSession {
16511666
}
16521667
}
16531668

1669+
protected async readMemoryRequest(
1670+
response: DebugProtocol.ReadMemoryResponse,
1671+
args: DebugProtocol.ReadMemoryArguments
1672+
): Promise<void> {
1673+
return this.readMemoryWithContextRequest(response, [args, undefined]);
1674+
}
1675+
16541676
/**
16551677
* Implement the memoryWrite request.
16561678
*/
@@ -1908,6 +1930,7 @@ export class GDBDebugSession extends LoggingDebugSession {
19081930
// stack depth necessary for differentiating between similarly named variables at different stack depths
19091931
const stackDepth = await mi.sendStackInfoDepth(this.gdb, {
19101932
maxDepth: 100,
1933+
threadId: frame.threadId,
19111934
});
19121935
const depth = parseInt(stackDepth.depth, 10);
19131936

@@ -2061,6 +2084,7 @@ export class GDBDebugSession extends LoggingDebugSession {
20612084
// fetch stack depth to obtain frameId/threadId/depth tuple
20622085
const stackDepth = await mi.sendStackInfoDepth(this.gdb, {
20632086
maxDepth: 100,
2087+
threadId: frame.threadId,
20642088
});
20652089
const depth = parseInt(stackDepth.depth, 10);
20662090
// we need to keep track of children and the parent varname in GDB

src/integration-tests/debugClient.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import * as path from 'path';
1414
import { defaultAdapter } from './utils';
1515
import * as os from 'os';
1616
import { expect } from 'chai';
17+
import { ThreadContext } from '../GDBDebugSession';
1718

1819
export type ReverseRequestHandler<
1920
A = any,
@@ -335,6 +336,12 @@ export class CdtDebugClient extends DebugClient {
335336
return this.send('readMemory', args);
336337
}
337338

339+
public readMemoryWithContextRequest(
340+
args: [DebugProtocol.ReadMemoryArguments, ThreadContext?]
341+
): Promise<DebugProtocol.ReadMemoryResponse> {
342+
return this.send('cdt-gdb-adapter/readMemoryWithContext', args);
343+
}
344+
338345
public writeMemoryRequest(
339346
args: DebugProtocol.WriteMemoryArguments
340347
): Promise<DebugProtocol.WriteMemoryResponse> {

src/integration-tests/multithread.spec.ts

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,15 @@ import { expect } from 'chai';
2020
import * as path from 'path';
2121
import { fail } from 'assert';
2222
import * as os from 'os';
23+
import { ThreadContext, base64ToHex } from '../GDBDebugSession';
24+
import { DebugProtocol } from '@vscode/debugprotocol';
25+
26+
interface VariableContext {
27+
name: string;
28+
threadId: number;
29+
varAddress: number;
30+
stackFramePosition: number;
31+
}
2332

2433
describe('multithread', async function () {
2534
let dc: CdtDebugClient;
@@ -50,6 +59,29 @@ describe('multithread', async function () {
5059
await dc.stop();
5160
});
5261

62+
/**
63+
* Verify that `resp` contains the bytes `expectedBytes` and the
64+
* `expectedAddress` start address matches. In this case we know
65+
* we're searching for a string so truncate after 0 byte.
66+
*
67+
*/
68+
function verifyReadMemoryResponse(
69+
resp: DebugProtocol.ReadMemoryResponse,
70+
expectedBytes: string,
71+
expectedAddress: number
72+
) {
73+
const memData = base64ToHex(resp.body?.data ?? '').toString();
74+
const memString = Buffer.from(memData.toString(), 'hex').toString();
75+
// Only use the data before the 0 byte (truncate after)
76+
const simpleString = memString.substring(0, memString.search(/\0/));
77+
expect(simpleString).eq(expectedBytes);
78+
expect(resp.body?.address).match(/^0x[0-9a-fA-F]+$/);
79+
if (resp.body?.address) {
80+
const actualAddress = parseInt(resp.body?.address);
81+
expect(actualAddress).eq(expectedAddress);
82+
}
83+
}
84+
5385
it('sees all threads', async function () {
5486
if (!gdbNonStop && os.platform() === 'win32' && isRemoteTest) {
5587
// The way thread names are set in remote tests on windows is unsupported
@@ -182,4 +214,85 @@ describe('multithread', async function () {
182214
}
183215
}
184216
});
217+
218+
it('verify threadId,frameID for multiple threads', async function () {
219+
if (!gdbNonStop && os.platform() === 'win32' && isRemoteTest) {
220+
// The way thread names are set in remote tests on windows is unsupported
221+
this.skip();
222+
}
223+
224+
await dc.hitBreakpoint(
225+
fillDefaults(this.test, {
226+
program: program,
227+
}),
228+
{
229+
path: source,
230+
line: lineTags['LINE_MAIN_ALL_THREADS_STARTED'],
231+
}
232+
);
233+
234+
const variableContextArray: VariableContext[] = [];
235+
const threads = await dc.threadsRequest();
236+
// cycle through the threads and create an index for later
237+
for (const threadInfo of threads.body.threads) {
238+
// threadId is the id of the thread in DAP
239+
const threadId = threadInfo.id;
240+
if (threadId === undefined) {
241+
// Shouldn't have undefined thread.
242+
fail('unreachable');
243+
}
244+
if (!(threadInfo.name in threadNames)) {
245+
continue;
246+
}
247+
248+
if (gdbNonStop) {
249+
const waitForStopped = dc.waitForEvent('stopped');
250+
const pr = dc.pauseRequest({ threadId });
251+
await Promise.all([pr, waitForStopped]);
252+
}
253+
254+
const stack = await dc.stackTraceRequest({ threadId });
255+
let nameAddress: number | undefined = undefined;
256+
let stackFramePosition = 0;
257+
// Frame Reference ID starts at 1000 but actual stack frame # is index.
258+
for (const frame of stack.body.stackFrames) {
259+
if (frame.name === 'PrintHello') {
260+
// Grab the address for "name" in this thread now because
261+
// gdb-non-stop doesn't have different frame.id's for threads.
262+
const addrOfVariableResp = await dc.evaluateRequest({
263+
expression: 'name',
264+
frameId: frame.id,
265+
});
266+
nameAddress = parseInt(addrOfVariableResp.body.result, 16);
267+
break;
268+
}
269+
stackFramePosition++;
270+
}
271+
if (nameAddress === undefined) {
272+
fail("Failed to find address of name in 'PrintHello'");
273+
}
274+
275+
variableContextArray.push({
276+
name: threadInfo.name.toString(),
277+
threadId: threadInfo.id,
278+
varAddress: nameAddress,
279+
stackFramePosition,
280+
});
281+
}
282+
// cycle through the threads and confirm each thread name (different for each thread)
283+
for (const context of variableContextArray) {
284+
// Get the address of the variable.
285+
const mem = await dc.readMemoryWithContextRequest([
286+
{
287+
memoryReference: '0x' + context.varAddress.toString(16),
288+
count: 10,
289+
},
290+
{
291+
threadId: context.threadId,
292+
frameId: context.stackFramePosition,
293+
} as ThreadContext,
294+
]);
295+
verifyReadMemoryResponse(mem, context.name, context.varAddress);
296+
}
297+
});
185298
});

src/mi/data.ts

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,21 +54,37 @@ export function sendDataReadMemoryBytes(
5454
gdb: GDBBackend,
5555
address: string,
5656
size: number,
57-
offset = 0
57+
offset = 0,
58+
threadId?: number,
59+
frameId?: number
5860
): Promise<MIDataReadMemoryBytesResponse> {
59-
return gdb.sendCommand(
60-
`-data-read-memory-bytes -o ${offset} "${address}" ${size}`
61-
);
61+
let command = `-data-read-memory-bytes`;
62+
if (threadId !== undefined) {
63+
command += ` --thread ${threadId}`;
64+
}
65+
if (frameId !== undefined) {
66+
command += ` --frame ${frameId}`;
67+
}
68+
command += ` -o ${offset} "${address}" ${size}`;
69+
return gdb.sendCommand(command);
6270
}
6371

6472
export function sendDataWriteMemoryBytes(
6573
gdb: GDBBackend,
6674
memoryReference: string,
67-
data: string
75+
data: string,
76+
threadId?: number,
77+
frameId?: number
6878
): Promise<void> {
69-
return gdb.sendCommand(
70-
`-data-write-memory-bytes "${memoryReference}" "${data}"`
71-
);
79+
let command = `-data-write-memory-bytes`;
80+
if (threadId !== undefined) {
81+
command += ` --thread ${threadId}`;
82+
}
83+
if (frameId !== undefined) {
84+
command += ` --frame ${frameId}`;
85+
}
86+
command += ` "${memoryReference}" "${data}"`;
87+
return gdb.sendCommand(command);
7288
}
7389

7490
export function sendDataEvaluateExpression(

src/mi/var.ts

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,9 +142,17 @@ export function sendVarUpdate(
142142
| MIVarPrintValues.no
143143
| MIVarPrintValues.all
144144
| MIVarPrintValues.simple;
145+
threadId?: number;
146+
frameId?: number;
145147
}
146148
): Promise<MIVarUpdateResponse> {
147149
let command = '-var-update';
150+
if (params.threadId !== undefined) {
151+
command += ` --thread ${params.threadId}`;
152+
}
153+
if (params.frameId !== undefined) {
154+
command += ` --frame ${params.frameId}`;
155+
}
148156
if (params.printValues) {
149157
command += ` ${params.printValues}`;
150158
} else {
@@ -162,9 +170,18 @@ export function sendVarDelete(
162170
gdb: GDBBackend,
163171
params: {
164172
varname: string;
173+
threadId?: number;
174+
frameId?: number;
165175
}
166176
): Promise<void> {
167-
const command = `-var-delete ${params.varname}`;
177+
let command = `-var-delete`;
178+
if (params.threadId !== undefined) {
179+
command += ` --thread ${params.threadId}`;
180+
}
181+
if (params.frameId !== undefined) {
182+
command += ` --frame ${params.frameId}`;
183+
}
184+
command += ` ${params.varname}`;
168185
return gdb.sendCommand(command);
169186
}
170187

@@ -183,9 +200,18 @@ export function sendVarEvaluateExpression(
183200
gdb: GDBBackend,
184201
params: {
185202
varname: string;
203+
threadId?: number;
204+
frameId?: number;
186205
}
187206
): Promise<MIVarEvalResponse> {
188-
const command = `-var-evaluate-expression ${params.varname}`;
207+
let command = '-var-evaluate-expression';
208+
if (params.threadId !== undefined) {
209+
command += ` --thread ${params.threadId}`;
210+
}
211+
if (params.frameId !== undefined) {
212+
command += ` --frame ${params.frameId}`;
213+
}
214+
command += ` ${params.varname}`;
189215
return gdb.sendCommand(command);
190216
}
191217

src/varManager.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,11 @@ export class VarManager {
124124
}
125125
}
126126
if (deleteme) {
127-
await sendVarDelete(this.gdb, { varname: deleteme.varname });
127+
await sendVarDelete(this.gdb, {
128+
varname: deleteme.varname,
129+
threadId,
130+
frameId,
131+
});
128132
vars.splice(vars.indexOf(deleteme), 1);
129133
for (const child of deleteme.children) {
130134
await this.removeVar(
@@ -145,7 +149,11 @@ export class VarManager {
145149
varobj: VarObjType
146150
): Promise<VarObjType> {
147151
let returnVar = varobj;
148-
const vup = await sendVarUpdate(this.gdb, { name: varobj.varname });
152+
const vup = await sendVarUpdate(this.gdb, {
153+
name: varobj.varname,
154+
threadId,
155+
frameId,
156+
});
149157
const update = vup.changelist[0];
150158
if (update) {
151159
if (update.in_scope === 'true') {
@@ -155,7 +163,11 @@ export class VarManager {
155163
}
156164
} else {
157165
this.removeVar(frameId, threadId, depth, varobj.varname);
158-
await sendVarDelete(this.gdb, { varname: varobj.varname });
166+
await sendVarDelete(this.gdb, {
167+
varname: varobj.varname,
168+
threadId,
169+
frameId,
170+
});
159171
const createResponse = await sendVarCreate(this.gdb, {
160172
frame: 'current',
161173
expression: varobj.expression,

0 commit comments

Comments
 (0)