GDB (xrefs)
Loading...
Searching...
No Matches
breakpoint.py
Go to the documentation of this file.
1# Copyright 2022, 2023 Free Software Foundation, Inc.
2
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.
7#
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.
12#
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/>.
15
16import gdb
17import os
18import re
19
20from contextlib import contextmanager
21
22# These are deprecated in 3.9, but required in older versions.
23from typing import Optional, Sequence
24
25from .server import request, capability, send_event
26from .sources import make_source
27from .startup import in_gdb_thread, log_stack
28from .typecheck import type_check
29
30
31@in_gdb_thread
32def _bp_modified(event):
33 send_event(
34 "breakpoint",
35 {
36 "reason": "changed",
37 "breakpoint": _breakpoint_descriptor(event),
38 },
39 )
40
41
42# True when suppressing new breakpoint events.
43_suppress_bp = False
44
45
46@contextmanager
48 """Return a new context manager that suppresses new breakpoint events."""
49 global _suppress_bp
50 _suppress_bp = True
51 try:
52 yield None
53 finally:
54 _suppress_bp = False
55
56
57@in_gdb_thread
58def _bp_created(event):
59 global _suppress_bp
60 if not _suppress_bp:
61 send_event(
62 "breakpoint",
63 {
64 "reason": "new",
65 "breakpoint": _breakpoint_descriptor(event),
66 },
67 )
68
69
70@in_gdb_thread
71def _bp_deleted(event):
72 send_event(
73 "breakpoint",
74 {
75 "reason": "removed",
76 "breakpoint": _breakpoint_descriptor(event),
77 },
78 )
79
80
81gdb.events.breakpoint_created.connect(_bp_created)
82gdb.events.breakpoint_modified.connect(_bp_modified)
83gdb.events.breakpoint_deleted.connect(_bp_deleted)
84
85
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.
91breakpoint_map = {}
92
93
94@in_gdb_thread
96 "Return the Breakpoint object descriptor given a gdb Breakpoint."
97 result = {
98 "id": bp.number,
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.
102 "verified": True,
103 }
104 if bp.locations:
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]
109 if loc.source:
110 (filename, line) = loc.source
111 if loc.fullname is not None:
112 filename = loc.fullname
113
114 result.update(
115 {
116 "source": make_source(filename, os.path.basename(filename)),
117 "line": line,
118 }
119 )
120
121 if loc.address:
122 result["instructionReference"] = hex(loc.address)
123
124 return result
125
126
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.
131def _remove_entries(table, *names):
132 return [table.pop(name, None) for name in names]
133
134
135# Helper function to set some breakpoints according to a list of
136# specifications and a callback function to do the work of creating
137# the breakpoint.
138@in_gdb_thread
139def _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]
144 else:
145 saved_map = {}
146 breakpoint_map[kind] = {}
147 result = []
148 for spec in specs:
149 # It makes sense to reuse a breakpoint even if the condition
150 # or ignore count differs, so remove these entries from the
151 # spec first.
152 (condition, hit_condition) = _remove_entries(spec, "condition", "hitCondition")
153 keyspec = frozenset(spec.items())
154
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.
158 bp = None
159 try:
160 if keyspec in saved_map:
161 bp = saved_map.pop(keyspec)
162 else:
164 bp = creator(**spec)
165
166 bp.condition = condition
167 if hit_condition is None:
168 bp.ignore_count = 0
169 else:
170 bp.ignore_count = int(
171 gdb.parse_and_eval(hit_condition, global_context=True)
172 )
173
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 log_stack()
180 # Maybe the breakpoint was made but setting an attribute
181 # failed. We still want this to fail.
182 if bp is not None:
183 bp.delete()
184 # Breakpoint creation failed.
185 result.append(
186 {
187 "verified": False,
188 "message": str(e),
189 }
190 )
191
192 # Delete any breakpoints that were not reused.
193 for entry in saved_map.values():
194 entry.delete()
195 return result
196
197
198class _PrintBreakpoint(gdb.Breakpoint):
199 def __init__(self, logMessage, **args):
200 super().__init__(**args)
201 # Split the message up for easier processing.
202 self.message = re.split("{(.*?)}", logMessage)
203
204 def stop(self):
205 output = ""
206 for idx, item in enumerate(self.message):
207 if idx % 2 == 0:
208 # Even indices are plain text.
209 output += item
210 else:
211 # Odd indices are expressions to substitute. The {}
212 # have already been stripped by the placement of the
213 # regex capture in the 'split' call.
214 try:
215 val = gdb.parse_and_eval(item)
216 output += str(val)
217 except Exception as e:
218 output += "<" + str(e) + ">"
219 send_event(
220 "output",
221 {
222 "category": "console",
223 "output": output,
224 },
225 )
226 # Do not stop.
227 return False
228
229
230# Set a single breakpoint or a log point. Returns the new breakpoint.
231# Note that not every spec will pass logMessage, so here we use a
232# default.
233@in_gdb_thread
234def _set_one_breakpoint(*, logMessage=None, **args):
235 if logMessage is not None:
236 return _PrintBreakpoint(logMessage, **args)
237 else:
238 return gdb.Breakpoint(**args)
239
240
241# Helper function to set ordinary breakpoints according to a list of
242# specifications.
243@in_gdb_thread
244def _set_breakpoints(kind, specs):
245 return _set_breakpoints_callback(kind, specs, _set_one_breakpoint)
246
247
248# A helper function that rewrites a SourceBreakpoint into the internal
249# form passed to the creator. This function also allows for
250# type-checking of each SourceBreakpoint.
251@type_check
253 *,
254 # This is a Source but we don't type-check it.
255 source,
256 line: int,
257 condition: Optional[str] = None,
258 hitCondition: Optional[str] = None,
259 logMessage: Optional[str] = None,
260 **args,
261):
262 return {
263 "source": source["path"],
264 "line": line,
265 "condition": condition,
266 "hitCondition": hitCondition,
267 "logMessage": logMessage,
268 }
269
270
271# FIXME we do not specify a type for 'source'.
272@request("setBreakpoints")
273@capability("supportsHitConditionalBreakpoints")
274@capability("supportsConditionalBreakpoints")
275@capability("supportsLogPoints")
276def set_breakpoint(*, source, breakpoints: Sequence = (), **args):
277 if "path" not in source:
278 result = []
279 else:
280 # Setting 'source' in BP avoids any Python error if BP already
281 # has a 'source' parameter. Setting this isn't in the spec,
282 # but it is better to be safe. See PR dap/30820.
283 specs = []
284 for bp in breakpoints:
285 bp["source"] = source
286 specs.append(_rewrite_src_breakpoint(**bp))
287 # Be sure to include the path in the key, so that we only
288 # clear out breakpoints coming from this same source.
289 key = "source:" + source["path"]
290 result = _set_breakpoints(key, specs)
291 return {
292 "breakpoints": result,
293 }
294
295
296# A helper function that rewrites a FunctionBreakpoint into the
297# internal form passed to the creator. This function also allows for
298# type-checking of each FunctionBreakpoint.
299@type_check
301 *,
302 name: str,
303 condition: Optional[str] = None,
304 hitCondition: Optional[str] = None,
305 **args,
306):
307 return {
308 "function": name,
309 "condition": condition,
310 "hitCondition": hitCondition,
311 }
312
313
314@request("setFunctionBreakpoints")
315@capability("supportsFunctionBreakpoints")
316def set_fn_breakpoint(*, breakpoints: Sequence, **args):
317 specs = [_rewrite_fn_breakpoint(**bp) for bp in breakpoints]
318 return {
319 "breakpoints": _set_breakpoints("function", specs),
320 }
321
322
323# A helper function that rewrites an InstructionBreakpoint into the
324# internal form passed to the creator. This function also allows for
325# type-checking of each InstructionBreakpoint.
326@type_check
328 *,
329 instructionReference: str,
330 offset: Optional[int] = None,
331 condition: Optional[str] = None,
332 hitCondition: Optional[str] = None,
333 **args,
334):
335 # There's no way to set an explicit address breakpoint from
336 # Python, so we rely on "spec" instead.
337 val = "*" + instructionReference
338 if offset is not None:
339 val = val + " + " + str(offset)
340 return {
341 "spec": val,
342 "condition": condition,
343 "hitCondition": hitCondition,
344 }
345
346
347@request("setInstructionBreakpoints")
348@capability("supportsInstructionBreakpoints")
350 *, breakpoints: Sequence, offset: Optional[int] = None, **args
351):
352 specs = [_rewrite_insn_breakpoint(**bp) for bp in breakpoints]
353 return {
354 "breakpoints": _set_breakpoints("instruction", specs),
355 }
356
357
358@in_gdb_thread
359def _catch_exception(filterId, **args):
360 if filterId in ("assert", "exception", "throw", "rethrow", "catch"):
361 cmd = "-catch-" + filterId
362 else:
363 raise Exception("Invalid exception filterID: " + str(filterId))
364 result = gdb.execute_mi(cmd)
365 # A little lame that there's no more direct way.
366 for bp in gdb.breakpoints():
367 if bp.number == result["bkptno"]:
368 return bp
369 raise Exception("Could not find catchpoint after creating")
370
371
372@in_gdb_thread
373def _set_exception_catchpoints(filter_options):
374 return _set_breakpoints_callback("exception", filter_options, _catch_exception)
375
376
377# A helper function that rewrites an ExceptionFilterOptions into the
378# internal form passed to the creator. This function also allows for
379# type-checking of each ExceptionFilterOptions.
380@type_check
382 *,
383 filterId: str,
384 condition: Optional[str] = None,
385 # Note that exception breakpoints do not support a hit count.
386 **args,
387):
388 return {
389 "filterId": filterId,
390 "condition": condition,
391 }
392
393
394@request("setExceptionBreakpoints")
395@capability("supportsExceptionFilterOptions")
396@capability(
397 "exceptionBreakpointFilters",
398 (
399 {
400 "filter": "assert",
401 "label": "Ada assertions",
402 "supportsCondition": True,
403 },
404 {
405 "filter": "exception",
406 "label": "Ada exceptions",
407 "supportsCondition": True,
408 },
409 {
410 "filter": "throw",
411 "label": "C++ exceptions, when thrown",
412 "supportsCondition": True,
413 },
414 {
415 "filter": "rethrow",
416 "label": "C++ exceptions, when re-thrown",
417 "supportsCondition": True,
418 },
419 {
420 "filter": "catch",
421 "label": "C++ exceptions, when caught",
422 "supportsCondition": True,
423 },
424 ),
425)
427 *, filters: Sequence[str], filterOptions: Sequence = (), **args
428):
429 # Convert the 'filters' to the filter-options style.
430 options = [{"filterId": filter} for filter in filters]
431 options.extend(filterOptions)
432 options = [_rewrite_exception_breakpoint(**bp) for bp in options]
433 return {
434 "breakpoints": _set_exception_catchpoints(options),
435 }
__init__(self, logMessage, **args)
set_fn_breakpoint(*Sequence breakpoints, **args)
_remove_entries(table, *names)
set_insn_breakpoints(*Sequence breakpoints, Optional[int] offset=None, **args)
_set_breakpoints_callback(kind, specs, creator)
_set_breakpoints(kind, specs)
_rewrite_exception_breakpoint(*str filterId, Optional[str] condition=None, **args)
_rewrite_fn_breakpoint(*str name, Optional[str] condition=None, Optional[str] hitCondition=None, **args)
_set_one_breakpoint(*logMessage=None, **args)
_set_exception_catchpoints(filter_options)
_rewrite_src_breakpoint(*source, int line, Optional[str] condition=None, Optional[str] hitCondition=None, Optional[str] logMessage=None, **args)
set_breakpoint(*source, Sequence breakpoints=(), **args)
set_exception_breakpoints(*Sequence[str] filters, Sequence filterOptions=(), **args)
_rewrite_insn_breakpoint(*str instructionReference, Optional[int] offset=None, Optional[str] condition=None, Optional[str] hitCondition=None, **args)
_catch_exception(filterId, **args)