diff --git a/src/GDBDebugSession.ts b/src/GDBDebugSession.ts index 9a581788..5eb21e66 100644 --- a/src/GDBDebugSession.ts +++ b/src/GDBDebugSession.ts @@ -103,6 +103,7 @@ export class GDBDebugSession extends LoggingDebugSession { protected frameHandles = new Handles(); protected variableHandles = new Handles(); + protected functionBreakpoints: string[] = []; protected logPointMessages: { [ key: string ]: string } = {}; protected threads: Thread[] = []; @@ -139,6 +140,7 @@ export class GDBDebugSession extends LoggingDebugSession { response.body.supportsConditionalBreakpoints = true; response.body.supportsHitConditionalBreakpoints = true; response.body.supportsLogPoints = true; + response.body.supportsFunctionBreakpoints = true; // response.body.supportsSetExpression = true; response.body.supportsDisassembleRequest = true; this.sendResponse(response); @@ -262,8 +264,8 @@ export class GDBDebugSession extends LoggingDebugSession { let inserts = breakpoints.slice(); const deletes = new Array(); - const actual = new Array(); + const createActual = (breakpoint: mi.MIBreakpointInfo) => { return { id: parseInt(breakpoint.number, 10), @@ -273,7 +275,12 @@ export class GDBDebugSession extends LoggingDebugSession { }; const result = await mi.sendBreakList(this.gdb); - result.BreakpointTable.body.forEach((gdbbp) => { + const gdbbps = result.BreakpointTable.body.filter((gdbbp) => { + // Ignore function breakpoints + return this.functionBreakpoints.indexOf(gdbbp.number) === -1; + }); + + gdbbps.forEach((gdbbp) => { if (gdbbp.fullname === file && gdbbp.line) { // TODO probably need more thorough checks than just line number const line = parseInt(gdbbp.line, 10); @@ -352,6 +359,71 @@ export class GDBDebugSession extends LoggingDebugSession { } } + protected async setFunctionBreakPointsRequest(response: DebugProtocol.SetFunctionBreakpointsResponse, + args: DebugProtocol.SetFunctionBreakpointsArguments) { + + const neededPause = this.isRunning; + if (neededPause) { + // Need to pause first + const waitPromise = new Promise((resolve) => { + this.waitPaused = resolve; + }); + this.gdb.pause(); + await waitPromise; + } + + try { + const vsbps = args.breakpoints || []; + const result = await mi.sendBreakList(this.gdb); + const gdbbps = result.BreakpointTable.body.filter((gdbbp) => { + // Only function breakpoints + return this.functionBreakpoints.indexOf(gdbbp.number) > -1; + }); + + const inserts = vsbps.filter((vsbp) => { + return !gdbbps.find((gdbbp) => gdbbp.originalLocation === `-function ${vsbp.name}`); + }); + + const existing = gdbbps.filter((gdbbp) => { + return !!vsbps.find((vsbp) => gdbbp.originalLocation === `-function ${vsbp.name}`); + }); + + const deletes = gdbbps.filter((gdbbp) => { + return !vsbps.find((vsbp) => gdbbp.originalLocation === `-function ${vsbp.name}`); + }).map((gdbbp) => gdbbp.number); + + const createActual = (breakpoint: mi.MIBreakpointInfo) => ({ + id: parseInt(breakpoint.number, 10), + verified: true, + }); + + const actual = existing.map((vsbp) => createActual(vsbp)); + + for (const vsbp of inserts) { + const gdbbp = await mi.sendBreakFunctionInsert(this.gdb, vsbp.name); + this.functionBreakpoints.push(gdbbp.bkpt.number); + actual.push(createActual(gdbbp.bkpt)); + } + + if (deletes.length > 0) { + await mi.sendBreakDelete(this.gdb, { breakpoints: deletes }); + this.functionBreakpoints = this.functionBreakpoints.filter((fnbp) => deletes.indexOf(fnbp) === -1); + } + + response.body = { + breakpoints: actual, + }; + + this.sendResponse(response); + } catch (err) { + this.sendErrorResponse(response, 1, err.message); + } + + if (neededPause) { + mi.sendExecContinue(this.gdb); + } + } + protected async configurationDoneRequest(response: DebugProtocol.ConfigurationDoneResponse, args: DebugProtocol.ConfigurationDoneArguments): Promise { try { diff --git a/src/integration-tests/evaluate.spec.ts b/src/integration-tests/evaluate.spec.ts index e35c2ac1..733f0666 100644 --- a/src/integration-tests/evaluate.spec.ts +++ b/src/integration-tests/evaluate.spec.ts @@ -57,7 +57,7 @@ describe('evaluate request', function() { const res = await dc.evaluateRequest({ context: 'repl', expression: '2 + 2', - frameId: scope.frameId, + frameId: scope.frame.id, }); expect(res.body.result).eq('4'); @@ -76,7 +76,7 @@ describe('evaluate request', function() { const err = await expectRejection(dc.evaluateRequest({ context: 'repl', expression: '2 +', - frameId: scope.frameId, + frameId: scope.frame.id, })); expect(err.message).eq('-var-create: unable to create variable object'); diff --git a/src/integration-tests/functionBreakpoints.spec.ts b/src/integration-tests/functionBreakpoints.spec.ts new file mode 100644 index 00000000..d3131b57 --- /dev/null +++ b/src/integration-tests/functionBreakpoints.spec.ts @@ -0,0 +1,68 @@ +/********************************************************************* + * Copyright (c) 2019 Arm 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 { join } from 'path'; +import { expect } from 'chai'; +import { CdtDebugClient } from './debugClient'; +import { LaunchRequestArguments } from '../GDBDebugSession'; +import { + standardBeforeEach, + gdbPath, + testProgramsDir, + openGdbConsole, + getScopes, +} from './utils'; + +describe('function breakpoints', async () => { + let dc: CdtDebugClient; + + beforeEach(async () => { + dc = await standardBeforeEach(); + + await dc.launchRequest({ + verbose: true, + gdb: gdbPath, + program: join(testProgramsDir, 'functions'), + openGdbConsole, + } as LaunchRequestArguments); + }); + + afterEach(async () => { + await dc.stop(); + }); + + it('hits the main function breakpoint', async () => { + await dc.setFunctionBreakpointsRequest({ + breakpoints: [ + { + name: 'main', + }, + ], + }); + await dc.configurationDoneRequest(); + dc.waitForEvent('stopped'); + const scope = await getScopes(dc); + expect(scope.frame.line).to.eq(6); + }); + + it('hits the sub function breakpoint', async () => { + await dc.setFunctionBreakpointsRequest({ + breakpoints: [ + { + name: 'sub', + }, + ], + }); + await dc.configurationDoneRequest(); + dc.waitForEvent('stopped'); + const scope = await getScopes(dc); + expect(scope.frame.line).to.eq(2); + }); +}); diff --git a/src/integration-tests/logpoints.spec.ts b/src/integration-tests/logpoints.spec.ts index ac9f2893..0b7dec7d 100644 --- a/src/integration-tests/logpoints.spec.ts +++ b/src/integration-tests/logpoints.spec.ts @@ -16,7 +16,7 @@ import { standardBeforeEach, gdbPath, testProgramsDir, - openGdbConsole + openGdbConsole, } from './utils'; describe('logpoints', async () => { diff --git a/src/integration-tests/test-programs/.gitignore b/src/integration-tests/test-programs/.gitignore index 1f5b7a73..6fce56a7 100644 --- a/src/integration-tests/test-programs/.gitignore +++ b/src/integration-tests/test-programs/.gitignore @@ -1,4 +1,5 @@ count +functions disassemble empty empty space diff --git a/src/integration-tests/test-programs/Makefile b/src/integration-tests/test-programs/Makefile index 202944ad..9d9f6ced 100644 --- a/src/integration-tests/test-programs/Makefile +++ b/src/integration-tests/test-programs/Makefile @@ -1,4 +1,4 @@ -BINS = empty empty\ space evaluate vars vars_cpp mem segv count disassemble +BINS = empty empty\ space evaluate vars vars_cpp mem segv count disassemble functions .PHONY: all all: $(BINS) @@ -8,6 +8,9 @@ CXX = g++ LINK = $(CC) -o $@ $^ LINK_CXX = $(CXX) -o $@ $^ +functions: functions.o + $(LINK) + count: count.o $(LINK) diff --git a/src/integration-tests/test-programs/functions.c b/src/integration-tests/test-programs/functions.c new file mode 100644 index 00000000..41dcccfc --- /dev/null +++ b/src/integration-tests/test-programs/functions.c @@ -0,0 +1,8 @@ +int sub() { + return 0; +} + +int main() { + sub(); + return 0; +} diff --git a/src/integration-tests/utils.ts b/src/integration-tests/utils.ts index 3d71472e..7af3d06f 100644 --- a/src/integration-tests/utils.ts +++ b/src/integration-tests/utils.ts @@ -16,8 +16,8 @@ import { DebugProtocol } from 'vscode-debugprotocol'; import { CdtDebugClient } from './debugClient'; export interface Scope { - threadId: number; - frameId: number; + thread: DebugProtocol.Thread; + frame: DebugProtocol.StackFrame; scopes: DebugProtocol.ScopesResponse; } @@ -29,13 +29,15 @@ export async function getScopes( // threads const threads = await dc.threadsRequest(); expect(threads.body.threads.length, 'There are fewer threads than expected.').to.be.at.least(threadIndex + 1); - const threadId = threads.body.threads[threadIndex].id; + const thread = threads.body.threads[threadIndex]; + const threadId = thread.id; // stack trace const stack = await dc.stackTraceRequest({ threadId }); expect(stack.body.stackFrames.length, 'There are fewer stack frames than expected.').to.be.at.least(stackIndex + 1); - const frameId = stack.body.stackFrames[stackIndex].id; + const frame = stack.body.stackFrames[stackIndex]; + const frameId = frame.id; const scopes = await dc.scopesRequest({ frameId }); - return Promise.resolve({ threadId, frameId, scopes }); + return Promise.resolve({ thread, frame, scopes }); } /** diff --git a/src/integration-tests/var.spec.ts b/src/integration-tests/var.spec.ts index 13558d3a..235d990a 100644 --- a/src/integration-tests/var.spec.ts +++ b/src/integration-tests/var.spec.ts @@ -78,7 +78,7 @@ describe('Variables Test Suite', function() { verifyVariable(vars.body.variables[0], 'a', 'int', '25'); verifyVariable(vars.body.variables[1], 'b', 'int', '10'); // step the program and see that the values were passed to the program and evaluated. - await dc.next({ threadId: scope.threadId }, { path: varsSrc, line: lineTags['STOP HERE'] + 1 }); + await dc.next({ threadId: scope.thread.id }, { path: varsSrc, line: lineTags['STOP HERE'] + 1 }); scope = await getScopes(dc); expect(scope.scopes.body.scopes.length, 'Unexpected number of scopes returned').to.equal(1); vr = scope.scopes.body.scopes[0].variablesReference; @@ -89,8 +89,8 @@ describe('Variables Test Suite', function() { it('can read and set struct variables in a program', async function() { // step past the initialization for the structure - await dc.next({ threadId: scope.threadId }, { path: varsSrc, line: lineTags['STOP HERE'] + 1 }); - await dc.next({ threadId: scope.threadId }, { path: varsSrc, line: lineTags['STOP HERE'] + 2 }); + await dc.next({ threadId: scope.thread.id }, { path: varsSrc, line: lineTags['STOP HERE'] + 1 }); + await dc.next({ threadId: scope.thread.id }, { path: varsSrc, line: lineTags['STOP HERE'] + 2 }); scope = await getScopes(dc); expect(scope.scopes.body.scopes.length, 'Unexpected number of scopes returned').to.equal(1); // assert we can see the struct and its elements @@ -119,7 +119,7 @@ describe('Variables Test Suite', function() { verifyVariable(children.body.variables[0], 'x', 'int', '25'); verifyVariable(children.body.variables[1], 'y', 'int', '10'); // step the program and see that the values were passed to the program and evaluated. - await dc.next({ threadId: scope.threadId }, { path: varsSrc, line: lineTags['STOP HERE'] + 3 }); + await dc.next({ threadId: scope.thread.id }, { path: varsSrc, line: lineTags['STOP HERE'] + 3 }); scope = await getScopes(dc); expect(scope.scopes.body.scopes.length, 'Unexpected number of scopes returned').to.equal(1); vr = scope.scopes.body.scopes[0].variablesReference; @@ -130,8 +130,8 @@ describe('Variables Test Suite', function() { it('can read and set nested struct variables in a program', async function() { // step past the initialization for the structure - await dc.next({ threadId: scope.threadId }, { path: varsSrc, line: lineTags['STOP HERE'] + 1 }); - await dc.next({ threadId: scope.threadId }, { path: varsSrc, line: lineTags['STOP HERE'] + 2 }); + await dc.next({ threadId: scope.thread.id }, { path: varsSrc, line: lineTags['STOP HERE'] + 1 }); + await dc.next({ threadId: scope.thread.id }, { path: varsSrc, line: lineTags['STOP HERE'] + 2 }); scope = await getScopes(dc); expect(scope.scopes.body.scopes.length, 'Unexpected number of scopes returned').to.equal(1); // assert we can see the 'foo' struct and its child 'bar' struct @@ -167,8 +167,8 @@ describe('Variables Test Suite', function() { verifyVariable(subChildren.body.variables[0], 'a', 'int', '25'); verifyVariable(subChildren.body.variables[1], 'b', 'int', '10'); // step the program and see that the values were passed to the program and evaluated. - await dc.next({ threadId: scope.threadId }, { path: varsSrc, line: lineTags['STOP HERE'] + 3 }); - await dc.next({ threadId: scope.threadId }, { path: varsSrc, line: lineTags['STOP HERE'] + 4 }); + await dc.next({ threadId: scope.thread.id }, { path: varsSrc, line: lineTags['STOP HERE'] + 3 }); + await dc.next({ threadId: scope.thread.id }, { path: varsSrc, line: lineTags['STOP HERE'] + 4 }); scope = await getScopes(dc); expect(scope.scopes.body.scopes.length, 'Unexpected number of scopes returned').to.equal(1); vr = scope.scopes.body.scopes[0].variablesReference; @@ -181,7 +181,7 @@ describe('Variables Test Suite', function() { // skip ahead to array initialization const br = await dc.setBreakpointsRequest({ source: { path: varsSrc }, breakpoints: [{ line: 24 }] }); expect(br.success).to.equal(true); - await dc.continueRequest({ threadId: scope.threadId }); + await dc.continueRequest({ threadId: scope.thread.id }); scope = await getScopes(dc); expect(scope.scopes.body.scopes.length, 'Unexpected number of scopes returned').to.equal(1); // assert we can see the array and its elements @@ -214,7 +214,7 @@ describe('Variables Test Suite', function() { verifyVariable(children.body.variables[1], '[1]', 'int', '22'); verifyVariable(children.body.variables[2], '[2]', 'int', '33'); // step the program and see that the values were passed to the program and evaluated. - await dc.next({ threadId: scope.threadId }, { path: varsSrc, line: 25 }); + await dc.next({ threadId: scope.thread.id }, { path: varsSrc, line: 25 }); scope = await getScopes(dc); expect(scope.scopes.body.scopes.length, 'Unexpected number of scopes returned').to.equal(1); vr = scope.scopes.body.scopes[0].variablesReference; diff --git a/src/mi/breakpoint.ts b/src/mi/breakpoint.ts index dd6fd248..c25d08ae 100644 --- a/src/mi/breakpoint.ts +++ b/src/mi/breakpoint.ts @@ -68,3 +68,8 @@ export function sendBreakDelete(gdb: GDBBackend, request: { export function sendBreakList(gdb: GDBBackend): Promise { return gdb.sendCommand('-break-list'); } + +export function sendBreakFunctionInsert(gdb: GDBBackend, fn: string): Promise { + const command = `-break-insert --function ${fn}`; + return gdb.sendCommand(command); +}