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 cbb16d2
Show file tree
Hide file tree
Showing 6 changed files with 252 additions and 3 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
200 changes: 200 additions & 0 deletions src/integration-tests/stepping-granularity.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
/*********************************************************************
* 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,
resolveLineTagLocations,
} 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');
const lineTags = {
'main for': 0,
'main getFromElsewhere call': 0,
'main printf call': 0,
'getFromElsewhere entry': 0,
'getFromElsewhere for': 0,
};

before(function () {
resolveLineTagLocations(steppingSource, lineTags);
});

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

await dc.hitBreakpoint(
fillDefaults(this.currentTest, { program: steppingProgram }),
{
path: steppingSource,
line: lineTags['main getFromElsewhere call'],
}
);
});

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: lineTags['main getFromElsewhere call'],
});
await Promise.all([
dc.stepInRequest({ threadId, granularity: 'statement' }),
dc.waitForEvent('stopped'),
]);
expectStackState(await getFrameState(threadId), {
elsewhereDefined: true,
line: lineTags['getFromElsewhere entry'],
});
await Promise.all([
dc.stepInRequest({ threadId, granularity: 'statement' }),
dc.waitForEvent('stopped'),
]);
expectStackState(await getFrameState(threadId), {
elsewhereDefined: true,
line: lineTags['getFromElsewhere for'],
});
});

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: lineTags['main getFromElsewhere call'],
});
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: lineTags['main getFromElsewhere call'],
});
// Step until we leave that line.
while (
state.main?.line === lineTags['main getFromElsewhere call'] &&
!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: lineTags['getFromElsewhere entry'],
});
});

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: lineTags['main getFromElsewhere call'],
});
await Promise.all([
dc.nextRequest({ threadId, granularity: 'statement' }),
dc.waitForEvent('stopped'),
]);
expectStackState(await getFrameState(threadId), {
elsewhereDefined: false,
line: lineTags['main printf call'],
});
await Promise.all([
dc.nextRequest({ threadId, granularity: 'statement' }),
dc.waitForEvent('stopped'),
]);
expectStackState(await getFrameState(threadId), {
elsewhereDefined: false,
line: lineTags['main for'],
});
});

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: lineTags['main getFromElsewhere call'],
});
// Step until we get off line 'main getFromElsewhere call'.
while (
state.main?.line === lineTags['main getFromElsewhere call'] &&
!state.elsewhere
) {
await Promise.all([
dc.nextRequest({ threadId, granularity: 'instruction' }),
dc.waitForEvent('stopped'),
]);
state = await getFrameState(threadId);
}
// The first line we should see after 'main getFromElsewhere call'
// is 'main printf call', not something in `getFromElsewhere`.
expectStackState(state, {
elsewhereDefined: false,
line: lineTags['main printf call'],
});
});
});
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
24 changes: 24 additions & 0 deletions src/integration-tests/test-programs/stepping.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#include <stdio.h>

extern int getFromElsewhere(int start);

int main (int argc, char *argv[])
{ char knownLocally = 10;
int i;
for (i = 0; i < 3; i++) { // main for
knownLocally += 1;
int gottenFromElsewhere = getFromElsewhere(knownLocally); // main getFromElsewhere call
printf("Saw it here first: %d", knownLocally); // main printf call
}
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; // getFromElsewhere entry
for (i = 1; i <= 5; i++) { // getFromElsewhere for
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 cbb16d2

Please sign in to comment.