Implement DAP setExceptionBreakpoints request

This implements the DAP setExceptionBreakpoints request for Ada.  This
is a somewhat minimal implementation, in that "exceptionOptions" are
not implemented (or advertised) -- I wasn't completely sure how this
feature is supposed to work.

I haven't added C++ exception handling here, but it's easy to do if
needed.

This patch relies on the new MI command execution support to do its
work.
This commit is contained in:
Tom Tromey
2023-04-17 08:08:54 -06:00
parent 2c4c710f56
commit 69ed07d546
5 changed files with 189 additions and 7 deletions

View File

@@ -37,12 +37,11 @@ def breakpoint_descriptor(bp):
# https://github.com/microsoft/debug-adapter-protocol/issues/13
loc = bp.locations[0]
(basename, line) = loc.source
return {
result = {
"id": bp.number,
"verified": True,
"source": {
"name": os.path.basename(basename),
"path": loc.fullname,
# We probably don't need this but it doesn't hurt to
# be explicit.
"sourceReference": 0,
@@ -50,6 +49,10 @@ def breakpoint_descriptor(bp):
"line": line,
"instructionReference": hex(loc.address),
}
path = loc.fullname
if path is not None:
result["source"]["path"] = path
return result
else:
return {
"id": bp.number,
@@ -58,9 +61,10 @@ def breakpoint_descriptor(bp):
# Helper function to set some breakpoints according to a list of
# specifications.
# specifications and a callback function to do the work of creating
# the breakpoint.
@in_gdb_thread
def _set_breakpoints(kind, specs):
def _set_breakpoints_callback(kind, specs, creator):
global breakpoint_map
# Try to reuse existing breakpoints if possible.
if kind in breakpoint_map:
@@ -75,7 +79,7 @@ def _set_breakpoints(kind, specs):
bp = saved_map.pop(keyspec)
else:
# FIXME handle exceptions here
bp = gdb.Breakpoint(**spec)
bp = creator(**spec)
breakpoint_map[kind][keyspec] = bp
result.append(breakpoint_descriptor(bp))
# Delete any breakpoints that were not reused.
@@ -84,6 +88,13 @@ def _set_breakpoints(kind, specs):
return result
# Helper function to set odinary breakpoints according to a list of
# specifications.
@in_gdb_thread
def _set_breakpoints(kind, specs):
return _set_breakpoints_callback(kind, specs, gdb.Breakpoint)
@request("setBreakpoints")
def set_breakpoint(*, source, breakpoints=[], **args):
if "path" not in source:
@@ -141,3 +152,47 @@ def set_insn_breakpoints(*, breakpoints, offset=None, **args):
return {
"breakpoints": result,
}
@in_gdb_thread
def _catch_exception(filterId, condition=None, **args):
if filterId == "assert":
args = ["-catch-assert"]
elif filterId == "exception":
args = ["-catch-exception"]
else:
raise Exception(f"Invalid exception filterID: {filterId}")
if condition is not None:
args.extend(["-c", condition])
result = gdb.execute_mi(*args)
# A little lame that there's no more direct way.
for bp in gdb.breakpoints():
if bp.number == result["bkptno"]:
return bp
raise Exception("Could not find catchpoint after creating")
@in_gdb_thread
def _set_exception_catchpoints(filter_options):
return _set_breakpoints_callback("exception", filter_options, _catch_exception)
@request("setExceptionBreakpoints")
@capability("supportsExceptionFilterOptions")
@capability("exceptionBreakpointFilters", ({
"filter": "assert",
"label": "Ada assertions",
"supportsCondition": True,
}, {
"filter": "exception",
"label": "Ada exceptions",
"supportsCondition": True,
}))
def set_exception_breakpoints(*, filters, filterOptions=[], **args):
# Convert the 'filters' to the filter-options style.
options = [{"filterId": filter} for filter in filters]
options.extend(filterOptions)
result = send_gdb_with_response(lambda: _set_exception_catchpoints(options))
return {
"breakpoints": result,
}

View File

@@ -171,13 +171,13 @@ def request(name):
return wrap
def capability(name):
def capability(name, value=True):
"""A decorator that indicates that the wrapper function implements
the DAP capability NAME."""
def wrap(func):
global _capabilities
_capabilities[name] = True
_capabilities[name] = value
return func
return wrap

View File

@@ -0,0 +1,65 @@
# Copyright 2023 Free Software Foundation, Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
load_lib ada.exp
load_lib dap-support.exp
require allow_ada_tests allow_dap_tests gnat_runtime_has_debug_info
standard_ada_testfile prog
if {[gdb_compile_ada "${srcfile}" "${binfile}" executable \
{debug additional_flags=-gnata}] != ""} {
return -1
}
if {[dap_launch $binfile] == ""} {
return
}
set obj [dap_check_request_and_response "set exception catchpoints" \
setExceptionBreakpoints \
{o filters [a [s assert]] \
filterOptions [a [o filterId [s exception] \
condition [s "Global_Var = 23"]]]}]
set bps [dict get [lindex $obj 0] body breakpoints]
gdb_assert {[llength $bps] == 2} "two breakpoints"
# The "path" should never be "null".
set i 1
foreach spec $bps {
# If "path" does not exist, then that is fine as well.
if {![dict exists $spec source path]} {
pass "breakpoint $i path"
} else {
gdb_assert {[dict get $spec source path] != "null"} \
"breakpoint $i path"
}
incr i
}
dap_check_request_and_response "start inferior" configurationDone
dap_wait_for_event_and_check "stopped at first raise" stopped \
"body reason" breakpoint \
"body hitBreakpointIds" 2
dap_check_request_and_response "continue to assert" continue
dap_wait_for_event_and_check "stopped at assert" stopped \
"body reason" breakpoint \
"body hitBreakpointIds" 1
dap_shutdown

View File

@@ -0,0 +1,18 @@
-- Copyright 2023 Free Software Foundation, Inc.
--
-- This program is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
package Pck is
Global_Var : Integer := 91;
end Pck;

View File

@@ -0,0 +1,44 @@
-- Copyright 2023 Free Software Foundation, Inc.
--
-- This program is free software; you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation; either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
with Pck; use Pck;
procedure Prog is
begin
begin
raise Program_Error;
exception
when others =>
null;
end;
begin
Global_Var := 23;
raise Program_Error;
exception
when others =>
null;
end;
begin
pragma Assert (False);
null;
exception
when others =>
null;
end;
end Prog;