Skip to content

Commit

Permalink
Support SteppingGranularity & instruction stepping
Browse files Browse the repository at this point in the history
  • Loading branch information
colin-grant-work committed Nov 3, 2023
1 parent acc14b5 commit 4a3154a
Show file tree
Hide file tree
Showing 8 changed files with 234 additions and 41 deletions.
9 changes: 7 additions & 2 deletions src/GDBDebugSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ export class GDBDebugSession extends LoggingDebugSession {
response.body.supportsDisassembleRequest = true;
response.body.supportsReadMemoryRequest = true;
response.body.supportsWriteMemoryRequest = true;
response.body.supportsSteppingGranularity = true;
this.sendResponse(response);
}

Expand Down Expand Up @@ -1003,7 +1004,9 @@ export class GDBDebugSession extends LoggingDebugSession {
args: DebugProtocol.NextArguments
): Promise<void> {
try {
await mi.sendExecNext(this.gdb, args.threadId);
await (args.granularity === 'instruction'
? mi.sendExecNextInstruction(this.gdb, args.threadId)
: mi.sendExecNext(this.gdb, args.threadId));
this.sendResponse(response);
} catch (err) {
this.sendErrorResponse(
Expand All @@ -1019,7 +1022,9 @@ export class GDBDebugSession extends LoggingDebugSession {
args: DebugProtocol.StepInArguments
): Promise<void> {
try {
await mi.sendExecStep(this.gdb, args.threadId);
await (args.granularity === 'instruction'
? mi.sendExecStepInstruction(this.gdb, args.threadId)
: mi.sendExecStep(this.gdb, args.threadId));
this.sendResponse(response);
} catch (err) {
this.sendErrorResponse(
Expand Down
26 changes: 7 additions & 19 deletions src/MIParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,27 +177,15 @@ export class MIParser {
const result: any = {};
if (c === '{') {
c = this.next();
if (c !== '"') {
// oject contains name-value pairs
while (c !== '}') {
if (c !== ',') {
this.back();
}
const name = this.handleString();
if (this.next() === '=') {
result[name] = this.handleValue();
}
c = this.next();
while (c !== '}') {
if (c !== ',') {
this.back();
}
} else {
// "object" contains just values
this.back();
let key = 0;
while (c !== '}') {
let value = this.handleCString();
if (value) result[key++] = value;
c = this.next();
const name = this.handleString();
if (this.next() === '=') {
result[name] = this.handleValue();
}
c = this.next();
}
}

Expand Down
19 changes: 0 additions & 19 deletions src/integration-tests/miparser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,23 +154,4 @@ describe('MI Parser Test Suite', function () {
'stdout'
);
});

it('structure that starts with a curly bracket and contains values but not keys', async function () {
parser.parseLine(
'+message,bkpt={number="1",type="breakpoint",thread-groups=["i1"],script={"p }123","p 321","p 789"}}'
);
sinon.assert.calledOnceWithExactly(
gdbBackendMock.emit as sinon.SinonStub,
'statusAsync',
'message',
{
bkpt: {
number: '1',
type: 'breakpoint',
'thread-groups': ['i1'],
script: { '0': 'p }123', '1': 'p 321', '2': 'p 789' },
},
}
);
});
});
177 changes: 177 additions & 0 deletions src/integration-tests/stepping-granularity.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
/*********************************************************************
* Copyright (c) 2023 Ericsson 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 * as path from 'path';
import { expect } from 'chai';
import { CdtDebugClient } from './debugClient';
import { standardBeforeEach, testProgramsDir, fillDefaults } from './utils';
import { DebugProtocol } from '@vscode/debugprotocol';

interface StackState {
main: DebugProtocol.StackFrame | undefined;
elsewhere: DebugProtocol.StackFrame | undefined;
}

interface StackStateCheck {
elsewhereDefined: boolean;
line: number;
}

describe('Stepping', async function () {
let dc: CdtDebugClient;
const steppingProgram = path.join(testProgramsDir, 'stepping');
const steppingSource = path.join(testProgramsDir, 'stepping.c');

beforeEach(async function () {
dc = await standardBeforeEach();

await dc.hitBreakpoint(
fillDefaults(this.currentTest, { program: steppingProgram }),
{
path: steppingSource,
line: 8,
}
);
});

afterEach(async () => {
await dc.stop();
});

async function getFrameState(threadId: number) {
const stack = await dc.stackTraceRequest({ threadId });
const main = stack.body.stackFrames.find(
(frame) => frame.name === 'main'
);
const elsewhere = stack.body.stackFrames.find(
(frame) => frame.name === 'getFromElsewhere'
);
return { main, elsewhere };
}

function expectStackState(state: StackState, check: StackStateCheck) {
if (check.elsewhereDefined) {
expect(state.elsewhere).not.to.be.undefined;
} else {
expect(state.elsewhere).to.be.undefined;
}
const target = check.elsewhereDefined ? 'elsewhere' : 'main';
expect(state[target]).not.to.be.undefined;
expect(state[target]?.line).equal(
check.line,
`It should have stopped at line ${check.line}`
);
}

it('steps in by line', async () => {
const threads = await dc.threadsRequest();
const threadId = threads.body.threads[0].id;
expectStackState(await getFrameState(threadId), {
elsewhereDefined: false,
line: 8,
});
await Promise.all([
dc.stepInRequest({ threadId, granularity: 'statement' }),
dc.waitForEvent('stopped'),
]);
expectStackState(await getFrameState(threadId), {
elsewhereDefined: true,
line: 15,
});
await Promise.all([
dc.stepInRequest({ threadId, granularity: 'statement' }),
dc.waitForEvent('stopped'),
]);
expectStackState(await getFrameState(threadId), {
elsewhereDefined: true,
line: 16,
});
});

it('steps in by instruction', async () => {
const threads = await dc.threadsRequest();
const threadId = threads.body.threads[0].id;
let state = await getFrameState(threadId);
expectStackState(state, {
elsewhereDefined: false,
line: 8,
});
await Promise.all([
dc.stepInRequest({ threadId, granularity: 'instruction' }),
dc.waitForEvent('stopped'),
]);
// First step should not take us straight to the function.
expectStackState((state = await getFrameState(threadId)), {
elsewhereDefined: false,
line: 8,
});
// Step until we leave that line.
while (state.main?.line === 8 && !state.elsewhere) {
await Promise.all([
dc.stepInRequest({ threadId, granularity: 'instruction' }),
dc.waitForEvent('stopped'),
]);
state = await getFrameState(threadId);
}
// First line we see should be inside `getFromElsewhere`
expectStackState(state, {
elsewhereDefined: true,
line: 14,
});
});

it('steps next by line and skips a function', async () => {
const threads = await dc.threadsRequest();
const threadId = threads.body.threads[0].id;
expectStackState(await getFrameState(threadId), {
elsewhereDefined: false,
line: 8,
});
await Promise.all([
dc.nextRequest({ threadId, granularity: 'statement' }),
dc.waitForEvent('stopped'),
]);
expectStackState(await getFrameState(threadId), {
elsewhereDefined: false,
line: 9,
});
await Promise.all([
dc.nextRequest({ threadId, granularity: 'statement' }),
dc.waitForEvent('stopped'),
]);
expectStackState(await getFrameState(threadId), {
elsewhereDefined: false,
line: 6,
});
});

it('steps next by instruction and skips a function', async () => {
const threads = await dc.threadsRequest();
const threadId = threads.body.threads[0].id;
let state = await getFrameState(threadId);
expectStackState(state, {
elsewhereDefined: false,
line: 8,
});
// Step until we get off line 8.
while (state.main?.line === 8 && !state.elsewhere) {
await Promise.all([
dc.nextRequest({ threadId, granularity: 'instruction' }),
dc.waitForEvent('stopped'),
]);
state = await getFrameState(threadId);
}
// The first line we should see after 8 is nine, not something in `getFromElsewhere`.
expectStackState(state, {
elsewhereDefined: false,
line: 9,
});
});
});
1 change: 1 addition & 0 deletions src/integration-tests/test-programs/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,4 @@ MultiThreadRunControl
/log
stderr
bug275-测试
stepping
5 changes: 4 additions & 1 deletion src/integration-tests/test-programs/Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
BINS = empty empty\ space evaluate vars vars_cpp vars_env mem segv count disassemble functions loopforever MultiThread MultiThreadRunControl stderr bug275-测试 cwd.exe
BINS = empty empty\ space evaluate vars vars_cpp vars_env mem segv count disassemble functions loopforever MultiThread MultiThreadRunControl stderr bug275-测试 cwd.exe stepping

.PHONY: all
all: $(BINS)
Expand Down Expand Up @@ -86,6 +86,9 @@ MultiThreadRunControl: MultiThreadRunControl.o
stderr: stderr.o
$(LINK)

stepping: stepping.o
$(LINK)

%.o: %.c
$(CC) -c $< -g3 -O0

Expand Down
22 changes: 22 additions & 0 deletions src/integration-tests/test-programs/stepping.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#include <stdio.h>

int main (int argc, char *argv[]) {
char knownLocally = 10;
int i;
for (i = 0; i < 3; i++) {
knownLocally += 1;
int gottenFromElsewhere = getFromElsewhere(knownLocally);
printf("Saw it here first: %d", knownLocally);
}
return 0;
}

// make the line of code the same as opening brace to account for different gdb/gcc combinations
int getFromElsewhere(int start)
{ int result = start; int i;
for (i = 1; i <= 5; i++) {
result += i;
printf("Eventually, I'll return something like... %d", result);
}
return result;
}
16 changes: 16 additions & 0 deletions src/mi/exec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ export function sendExecNext(gdb: GDBBackend, threadId?: number) {
return gdb.sendCommand(command);
}

export function sendExecNextInstruction(gdb: GDBBackend, threadId?: number) {
let command = '-exec-next-instruction';
if (threadId !== undefined) {
command += ` --thread ${threadId}`;
}
return gdb.sendCommand(command);
}

export function sendExecStep(gdb: GDBBackend, threadId?: number) {
let command = '-exec-step';
if (threadId !== undefined) {
Expand All @@ -47,6 +55,14 @@ export function sendExecStep(gdb: GDBBackend, threadId?: number) {
return gdb.sendCommand(command);
}

export function sendExecStepInstruction(gdb: GDBBackend, threadId?: number) {
let command = '-exec-step-instruction';
if (threadId !== undefined) {
command += ` --thread ${threadId}`;
}
return gdb.sendCommand(command);
}

export function sendExecFinish(gdb: GDBBackend, threadId?: number) {
let command = '-exec-finish';
if (threadId !== undefined) {
Expand Down

0 comments on commit 4a3154a

Please sign in to comment.