]>
git.ipfire.org Git - thirdparty/binutils-gdb.git/blob - gdb/python/lib/gdb/dap/breakpoint.py
1 # Copyright 2022-2024 Free Software Foundation, Inc.
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 3 of the License, or
6 # (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 from contextlib
import contextmanager
22 # These are deprecated in 3.9, but required in older versions.
23 from typing
import Optional
, Sequence
25 from .server
import request
, capability
, send_event
26 from .sources
import make_source
27 from .startup
import in_gdb_thread
, log_stack
, parse_and_eval
, LogLevel
, DAPException
28 from .typecheck
import type_check
32 def _bp_modified(event
):
37 "breakpoint": _breakpoint_descriptor(event
),
42 # True when suppressing new breakpoint events.
47 def suppress_new_breakpoint_event():
48 """Return a new context manager that suppresses new breakpoint events."""
58 def _bp_created(event
):
65 "breakpoint": _breakpoint_descriptor(event
),
71 def _bp_deleted(event
):
76 "breakpoint": _breakpoint_descriptor(event
),
81 gdb
.events
.breakpoint_created
.connect(_bp_created
)
82 gdb
.events
.breakpoint_modified
.connect(_bp_modified
)
83 gdb
.events
.breakpoint_deleted
.connect(_bp_deleted
)
86 # Map from the breakpoint "kind" (like "function") to a second map, of
87 # breakpoints of that type. The second map uses the breakpoint spec
88 # as a key, and the gdb.Breakpoint itself as a value. This is used to
89 # implement the clearing behavior specified by the protocol, while
90 # allowing for reuse when a breakpoint can be kept.
95 def _breakpoint_descriptor(bp
):
96 "Return the Breakpoint object descriptor given a gdb Breakpoint."
99 # We always use True here, because this field just indicates
100 # that breakpoint creation was successful -- and if we have a
101 # breakpoint, the creation succeeded.
105 # Just choose the first location, because DAP doesn't allow
106 # multiple locations. See
107 # https://github.com/microsoft/debug-adapter-protocol/issues/13
108 loc
= bp
.locations
[0]
110 (filename
, line
) = loc
.source
111 if loc
.fullname
is not None:
112 filename
= loc
.fullname
116 "source": make_source(filename
, os
.path
.basename(filename
)),
122 result
["instructionReference"] = hex(loc
.address
)
127 # Extract entries from a hash table and return a list of them. Each
128 # entry is a string. If a key of that name appears in the hash table,
129 # it is removed and pushed on the result list; if it does not appear,
130 # None is pushed on the list.
131 def _remove_entries(table
, *names
):
132 return [table
.pop(name
, None) for name
in names
]
135 # Helper function to set some breakpoints according to a list of
136 # specifications and a callback function to do the work of creating
139 def _set_breakpoints_callback(kind
, specs
, creator
):
140 global breakpoint_map
141 # Try to reuse existing breakpoints if possible.
142 if kind
in breakpoint_map
:
143 saved_map
= breakpoint_map
[kind
]
146 breakpoint_map
[kind
] = {}
149 # It makes sense to reuse a breakpoint even if the condition
150 # or ignore count differs, so remove these entries from the
152 (condition
, hit_condition
) = _remove_entries(spec
, "condition", "hitCondition")
153 keyspec
= frozenset(spec
.items())
155 # Create or reuse a breakpoint. If asked, set the condition
156 # or the ignore count. Catch errors coming from gdb and
157 # report these as an "unverified" breakpoint.
160 if keyspec
in saved_map
:
161 bp
= saved_map
.pop(keyspec
)
163 with
suppress_new_breakpoint_event():
166 bp
.condition
= condition
167 if hit_condition
is None:
170 bp
.ignore_count
= int(
171 parse_and_eval(hit_condition
, global_context
=True)
174 # Reaching this spot means success.
175 breakpoint_map
[kind
][keyspec
] = bp
176 result
.append(_breakpoint_descriptor(bp
))
177 # Exceptions other than gdb.error are possible here.
178 except Exception as e
:
179 # Don't normally want to see this, as it interferes with
181 log_stack(LogLevel
.FULL
)
182 # Maybe the breakpoint was made but setting an attribute
183 # failed. We still want this to fail.
186 # Breakpoint creation failed.
194 # Delete any breakpoints that were not reused.
195 for entry
in saved_map
.values():
200 class _PrintBreakpoint(gdb
.Breakpoint
):
201 def __init__(self
, logMessage
, **args
):
202 super().__init
__(**args
)
203 # Split the message up for easier processing.
204 self
.message
= re
.split("{(.*?)}", logMessage
)
208 for idx
, item
in enumerate(self
.message
):
210 # Even indices are plain text.
213 # Odd indices are expressions to substitute. The {}
214 # have already been stripped by the placement of the
215 # regex capture in the 'split' call.
217 # No real need to use the DAP parse_and_eval here.
218 val
= gdb
.parse_and_eval(item
)
220 except Exception as e
:
221 output
+= "<" + str(e
) + ">"
225 "category": "console",
233 # Set a single breakpoint or a log point. Returns the new breakpoint.
234 # Note that not every spec will pass logMessage, so here we use a
237 def _set_one_breakpoint(*, logMessage
=None, **args
):
238 if logMessage
is not None:
239 return _PrintBreakpoint(logMessage
, **args
)
241 return gdb
.Breakpoint(**args
)
244 # Helper function to set ordinary breakpoints according to a list of
247 def _set_breakpoints(kind
, specs
):
248 return _set_breakpoints_callback(kind
, specs
, _set_one_breakpoint
)
251 # A helper function that rewrites a SourceBreakpoint into the internal
252 # form passed to the creator. This function also allows for
253 # type-checking of each SourceBreakpoint.
255 def _rewrite_src_breakpoint(
257 # This is a Source but we don't type-check it.
260 condition
: Optional
[str] = None,
261 hitCondition
: Optional
[str] = None,
262 logMessage
: Optional
[str] = None,
266 "source": source
["path"],
268 "condition": condition
,
269 "hitCondition": hitCondition
,
270 "logMessage": logMessage
,
274 # FIXME we do not specify a type for 'source'.
275 @request("setBreakpoints")
276 @capability("supportsHitConditionalBreakpoints")
277 @capability("supportsConditionalBreakpoints")
278 @capability("supportsLogPoints")
279 def set_breakpoint(*, source
, breakpoints
: Sequence
= (), **args
):
280 if "path" not in source
:
283 # Setting 'source' in BP avoids any Python error if BP already
284 # has a 'source' parameter. Setting this isn't in the spec,
285 # but it is better to be safe. See PR dap/30820.
287 for bp
in breakpoints
:
288 bp
["source"] = source
289 specs
.append(_rewrite_src_breakpoint(**bp
))
290 # Be sure to include the path in the key, so that we only
291 # clear out breakpoints coming from this same source.
292 key
= "source:" + source
["path"]
293 result
= _set_breakpoints(key
, specs
)
295 "breakpoints": result
,
299 # A helper function that rewrites a FunctionBreakpoint into the
300 # internal form passed to the creator. This function also allows for
301 # type-checking of each FunctionBreakpoint.
303 def _rewrite_fn_breakpoint(
306 condition
: Optional
[str] = None,
307 hitCondition
: Optional
[str] = None,
312 "condition": condition
,
313 "hitCondition": hitCondition
,
317 @request("setFunctionBreakpoints")
318 @capability("supportsFunctionBreakpoints")
319 def set_fn_breakpoint(*, breakpoints
: Sequence
, **args
):
320 specs
= [_rewrite_fn_breakpoint(**bp
) for bp
in breakpoints
]
322 "breakpoints": _set_breakpoints("function", specs
),
326 # A helper function that rewrites an InstructionBreakpoint into the
327 # internal form passed to the creator. This function also allows for
328 # type-checking of each InstructionBreakpoint.
330 def _rewrite_insn_breakpoint(
332 instructionReference
: str,
333 offset
: Optional
[int] = None,
334 condition
: Optional
[str] = None,
335 hitCondition
: Optional
[str] = None,
338 # There's no way to set an explicit address breakpoint from
339 # Python, so we rely on "spec" instead.
340 val
= "*" + instructionReference
341 if offset
is not None:
342 val
= val
+ " + " + str(offset
)
345 "condition": condition
,
346 "hitCondition": hitCondition
,
350 @request("setInstructionBreakpoints")
351 @capability("supportsInstructionBreakpoints")
352 def set_insn_breakpoints(
353 *, breakpoints
: Sequence
, offset
: Optional
[int] = None, **args
355 specs
= [_rewrite_insn_breakpoint(**bp
) for bp
in breakpoints
]
357 "breakpoints": _set_breakpoints("instruction", specs
),
362 def _catch_exception(filterId
, **args
):
363 if filterId
in ("assert", "exception", "throw", "rethrow", "catch"):
364 cmd
= "-catch-" + filterId
366 raise DAPException("Invalid exception filterID: " + str(filterId
))
367 result
= gdb
.execute_mi(cmd
)
368 # A little lame that there's no more direct way.
369 for bp
in gdb
.breakpoints():
370 if bp
.number
== result
["bkptno"]:
372 # Not a DAPException because this is definitely unexpected.
373 raise Exception("Could not find catchpoint after creating")
377 def _set_exception_catchpoints(filter_options
):
378 return _set_breakpoints_callback("exception", filter_options
, _catch_exception
)
381 # A helper function that rewrites an ExceptionFilterOptions into the
382 # internal form passed to the creator. This function also allows for
383 # type-checking of each ExceptionFilterOptions.
385 def _rewrite_exception_breakpoint(
388 condition
: Optional
[str] = None,
389 # Note that exception breakpoints do not support a hit count.
393 "filterId": filterId
,
394 "condition": condition
,
398 @request("setExceptionBreakpoints")
399 @capability("supportsExceptionFilterOptions")
401 "exceptionBreakpointFilters",
405 "label": "Ada assertions",
406 "supportsCondition": True,
409 "filter": "exception",
410 "label": "Ada exceptions",
411 "supportsCondition": True,
415 "label": "C++ exceptions, when thrown",
416 "supportsCondition": True,
420 "label": "C++ exceptions, when re-thrown",
421 "supportsCondition": True,
425 "label": "C++ exceptions, when caught",
426 "supportsCondition": True,
430 def set_exception_breakpoints(
431 *, filters
: Sequence
[str], filterOptions
: Sequence
= (), **args
433 # Convert the 'filters' to the filter-options style.
434 options
= [{"filterId": filter} for filter in filters
]
435 options
.extend(filterOptions
)
436 options
= [_rewrite_exception_breakpoint(**bp
) for bp
in options
]
438 "breakpoints": _set_exception_catchpoints(options
),