Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support SteppingGranularity & instruction stepping #309

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
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