Skip to content

Commit

Permalink
Improvements for Breakpoint Logic (#268)
Browse files Browse the repository at this point in the history
In this update there are update for breakpoint insert logic.

We introduce a new method getBreakpointOptions to GDBBackend class.
With this new functionality, any child class could override the
breakpoint options (including changing the type hardware/software).

This will enable a new feature for embedded systems to manage the
limited breakpoint capability more efficiently. Also, any error thrown
in the getBreakpointOptions will disable the breakpoint, thus, limited
capabilities controlled without having errors in the user interface.

There are also some refactoring performed during the operation. Perhaps,
not all the refactorings are must to have, but tried to handle the
namings of functions/methods and object structure to be semantically
coherent after the update.
  • Loading branch information
asimgunes committed Jul 10, 2023
1 parent f116a97 commit 250881c
Show file tree
Hide file tree
Showing 5 changed files with 300 additions and 57 deletions.
11 changes: 11 additions & 0 deletions src/GDBBackend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,17 @@ export class GDBBackend extends events.EventEmitter {
return this.gdbNonStop;
}

// getBreakpointOptions called before inserting the breakpoint and this
// method could overridden in derived classes to dynamically control the
// breakpoint insert options. If an error thrown from this method, then
// the breakpoint will not be inserted.
public async getBreakpointOptions(
_: mi.MIBreakpointLocation,
initialOptions: mi.MIBreakpointInsertOptions
): Promise<mi.MIBreakpointInsertOptions> {
return initialOptions;
}

public isUseHWBreakpoint() {
return this.hardwareBreakpoint;
}
Expand Down
46 changes: 32 additions & 14 deletions src/GDBDebugSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ import {
} from './mi/data';
import { StoppedEvent } from './stoppedEvent';
import { VarObjType } from './varManager';
import { breakpointFunctionLocation, breakpointLocation } from './mi';

export interface RequestArguments extends DebugProtocol.LaunchRequestArguments {
gdb?: string;
Expand Down Expand Up @@ -499,7 +498,7 @@ export class GDBDebugSession extends LoggingDebugSession {

const result = await mi.sendBreakList(this.gdb);
const file = args.source.path as string;
const gdbOriginalLocationPrefix = await breakpointLocation(
const gdbOriginalLocationPrefix = await mi.sourceBreakpointLocation(
this.gdb,
file
);
Expand Down Expand Up @@ -615,14 +614,26 @@ export class GDBDebugSession extends LoggingDebugSession {
}

try {
const gdbbp = await mi.sendBreakInsert(this.gdb, {
source: file,
line: vsbp.line,
condition: vsbp.condition,
temporary,
ignoreCount,
hardware: this.gdb.isUseHWBreakpoint(),
});
const line = vsbp.line.toString();
const options = await this.gdb.getBreakpointOptions(
{
locationType: 'source',
source: file,
line,
},
{
condition: vsbp.condition,
temporary,
ignoreCount,
hardware: this.gdb.isUseHWBreakpoint(),
}
);
const gdbbp = await mi.sendSourceBreakpointInsert(
this.gdb,
file,
line,
options
);
actual.push(createState(vsbp, gdbbp.bkpt));
} catch (err) {
actual.push({
Expand Down Expand Up @@ -699,7 +710,7 @@ export class GDBDebugSession extends LoggingDebugSession {
const vsbpCond = vsbp.condition || undefined;
const gdbbpCond = gdbbp.cond || undefined;

const originalLocation = breakpointFunctionLocation(
const originalLocation = mi.functionBreakpointLocation(
this.gdb,
vsbp.name
);
Expand Down Expand Up @@ -735,13 +746,20 @@ export class GDBDebugSession extends LoggingDebugSession {
}

try {
const gdbbp = await mi.sendBreakFunctionInsert(
this.gdb,
bp.vsbp.name,
const options = await this.gdb.getBreakpointOptions(
{
locationType: 'function',
fn: bp.vsbp.name,
},
{
hardware: this.gdb.isUseHWBreakpoint(),
}
);
const gdbbp = await mi.sendFunctionBreakpointInsert(
this.gdb,
bp.vsbp.name,
options
);
this.functionBreakpoints.push(gdbbp.bkpt.number);
actual.push(createActual(gdbbp.bkpt));
} catch (err) {
Expand Down
156 changes: 156 additions & 0 deletions src/integration-tests/dynamicBreakpointOptions.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/*********************************************************************
* Copyright (c) 2023 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 * as path from 'path';
import * as os from 'os';
import { expect } from 'chai';
import { CdtDebugClient } from './debugClient';
import { standardBeforeEach, testProgramsDir, fillDefaults } from './utils';

// This mock adapter is overriding the getBreakpointOptions method.
const adapter =
'integration-tests/mocks/debugAdapters/dynamicBreakpointOptions.js';
const argHardwareBreakpointTrue = '--hardware-breakpoint-true';
const argHardwareBreakpointFalse = '--hardware-breakpoint-false';
const argThrowError = '--throw-error';

describe('dynamic breakpoint options with hardware set to false', async () => {
let dc: CdtDebugClient;

beforeEach(async function () {
// Overriding breakpoint option hardware to false
dc = await standardBeforeEach(adapter, [argHardwareBreakpointFalse]);
await dc.launchRequest(
fillDefaults(this.currentTest, {
program: path.join(testProgramsDir, 'count'),
hardwareBreakpoint: true,
})
);
});

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

it('insert breakpoint as software breakpoint', async () => {
const bpResp = await dc.setBreakpointsRequest({
source: {
name: 'count.c',
path: path.join(testProgramsDir, 'count.c'),
},
breakpoints: [
{
column: 1,
line: 4,
},
],
});
expect(bpResp.body.breakpoints.length).eq(1);
expect(bpResp.body.breakpoints[0].verified).eq(true);
expect(bpResp.body.breakpoints[0].message).eq(undefined);
await dc.configurationDoneRequest();
let isCorrect;
let outputs;
while (!isCorrect) {
// Cover the case of getting event in Linux environment.
// If cannot get correct event, program timeout and test case failed.
outputs = await dc.waitForEvent('output');
isCorrect = outputs.body.output.includes('breakpoint-modified');
}
expect(outputs?.body.output).includes('type="breakpoint"');
});
});

describe('dynamic breakpoint options with hardware set to true', async () => {
let dc: CdtDebugClient;

beforeEach(async function () {
// Overriding breakpoint option hardware to true
dc = await standardBeforeEach(adapter, [argHardwareBreakpointTrue]);
await dc.launchRequest(
fillDefaults(this.currentTest, {
program: path.join(testProgramsDir, 'count'),
hardwareBreakpoint: false,
})
);
});

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

it('insert breakpoint as hardware breakpoint', async function () {
// Hardware breakpoints are not supported for Windows
if (os.platform() === 'win32') {
this.skip();
}
const bpResp = await dc.setBreakpointsRequest({
source: {
name: 'count.c',
path: path.join(testProgramsDir, 'count.c'),
},
breakpoints: [
{
column: 1,
line: 4,
},
],
});
expect(bpResp.body.breakpoints.length).eq(1);
expect(bpResp.body.breakpoints[0].verified).eq(true);
expect(bpResp.body.breakpoints[0].message).eq(undefined);
await dc.configurationDoneRequest();
let isCorrect;
let outputs;
while (!isCorrect) {
// Cover the case of getting event in Linux environment.
// If cannot get correct event, program timeout and test case failed.
outputs = await dc.waitForEvent('output');
isCorrect = outputs.body.output.includes('breakpoint-modified');
}
expect(outputs?.body.output).includes('type="hw breakpoint"');
});
});

describe('dynamic breakpoint options with throwing error', async () => {
let dc: CdtDebugClient;

beforeEach(async function () {
// Overriding breakpoint options and throwing error when getBreakpointOptions invoked
dc = await standardBeforeEach(adapter, [argThrowError]);
await dc.launchRequest(
fillDefaults(this.currentTest, {
program: path.join(testProgramsDir, 'count'),
hardwareBreakpoint: false,
})
);
});

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

it('insert breakpoint is not performed', async () => {
const bpResp = await dc.setBreakpointsRequest({
source: {
name: 'count.c',
path: path.join(testProgramsDir, 'count.c'),
},
breakpoints: [
{
column: 1,
line: 4,
},
],
});
expect(bpResp.body.breakpoints.length).eq(1);
expect(bpResp.body.breakpoints[0].verified).eq(false);
expect(bpResp.body.breakpoints[0].message).not.eq(undefined);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#!/usr/bin/env node
/*********************************************************************
* Copyright (c) 2023 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 * as process from 'process';
import { logger } from '@vscode/debugadapter/lib/logger';
import { GDBBackend } from '../../../GDBBackend';
import { GDBTargetDebugSession } from '../../../GDBTargetDebugSession';
import { MIBreakpointLocation, MIBreakpointInsertOptions } from '../../../mi';

process.on('uncaughtException', (err: any) => {
logger.error(JSON.stringify(err));
});

// Breakpoint options to override
const hardwareBreakpointTrue = process.argv.includes(
'--hardware-breakpoint-true'
);
const hardwareBreakpointFalse = process.argv.includes(
'--hardware-breakpoint-false'
);
const throwError = process.argv.includes('--throw-error');

class DynamicBreakpointOptionsGDBBackend extends GDBBackend {
public async getBreakpointOptions(
_: MIBreakpointLocation,
initialOptions: MIBreakpointInsertOptions
): Promise<MIBreakpointInsertOptions> {
if (throwError) {
throw new Error(
'Some error message providing information that the breakpoint is not valid!'
);
}
const hardware = hardwareBreakpointTrue
? true
: hardwareBreakpointFalse
? false
: initialOptions.hardware;
return { ...initialOptions, hardware };
}
}

class DynamicBreakpointOptionsGDBDebugSession extends GDBTargetDebugSession {
gdb = this.createBackend();
protected createBackend(): GDBBackend {
return new DynamicBreakpointOptionsGDBBackend();
}
}

DynamicBreakpointOptionsGDBDebugSession.run(
DynamicBreakpointOptionsGDBDebugSession
);
Loading

0 comments on commit 250881c

Please sign in to comment.