aboutsummaryrefslogtreecommitdiff
path: root/gdb/python/lib
diff options
context:
space:
mode:
authorTom Tromey <tromey@adacore.com>2024-01-18 07:35:48 -0700
committerTom Tromey <tromey@adacore.com>2024-02-12 10:43:17 -0700
commit25558d2fc0e8b77d4c522c322012b048db4c1485 (patch)
tree7f4321b8f28ea77cae202a53d69c360af5824355 /gdb/python/lib
parent95fc420a40ebcbd510b41df77b93b11ec819f0a1 (diff)
downloadgdb-25558d2fc0e8b77d4c522c322012b048db4c1485.zip
gdb-25558d2fc0e8b77d4c522c322012b048db4c1485.tar.gz
gdb-25558d2fc0e8b77d4c522c322012b048db4c1485.tar.bz2
Fix DAP launch and configurationDone requests
Co-workers at AdaCore pointed out that gdb incorrectly implements the DAP launch and configurationDone requests. It's somewhat strange to me, but the spec does in fact say that configuration requests should occur before the executable is known to gdb. This was clarified in this bug report against the spec: https://github.com/microsoft/debug-adapter-protocol/issues/452 Fixing 'launch' to start the inferior was straightforward, but this then required some changes to how breakpoints are handled. In particular, now gdb will emit the "pending" reason on a breakpoint, and will suppress breakpoint events during breakpoint setting.
Diffstat (limited to 'gdb/python/lib')
-rw-r--r--gdb/python/lib/gdb/dap/breakpoint.py134
-rw-r--r--gdb/python/lib/gdb/dap/launch.py29
2 files changed, 75 insertions, 88 deletions
diff --git a/gdb/python/lib/gdb/dap/breakpoint.py b/gdb/python/lib/gdb/dap/breakpoint.py
index 87e7464..b4fb112 100644
--- a/gdb/python/lib/gdb/dap/breakpoint.py
+++ b/gdb/python/lib/gdb/dap/breakpoint.py
@@ -28,17 +28,6 @@ from .startup import in_gdb_thread, log_stack, parse_and_eval, LogLevel, DAPExce
from .typecheck import type_check
-@in_gdb_thread
-def _bp_modified(event):
- send_event(
- "breakpoint",
- {
- "reason": "changed",
- "breakpoint": _breakpoint_descriptor(event),
- },
- )
-
-
# True when suppressing new breakpoint events.
_suppress_bp = False
@@ -56,6 +45,19 @@ def suppress_new_breakpoint_event():
@in_gdb_thread
+def _bp_modified(event):
+ global _suppress_bp
+ if not _suppress_bp:
+ send_event(
+ "breakpoint",
+ {
+ "reason": "changed",
+ "breakpoint": _breakpoint_descriptor(event),
+ },
+ )
+
+
+@in_gdb_thread
def _bp_created(event):
global _suppress_bp
if not _suppress_bp:
@@ -70,13 +72,15 @@ def _bp_created(event):
@in_gdb_thread
def _bp_deleted(event):
- send_event(
- "breakpoint",
- {
- "reason": "removed",
- "breakpoint": _breakpoint_descriptor(event),
- },
- )
+ global _suppress_bp
+ if not _suppress_bp:
+ send_event(
+ "breakpoint",
+ {
+ "reason": "removed",
+ "breakpoint": _breakpoint_descriptor(event),
+ },
+ )
gdb.events.breakpoint_created.connect(_bp_created)
@@ -97,11 +101,10 @@ def _breakpoint_descriptor(bp):
"Return the Breakpoint object descriptor given a gdb Breakpoint."
result = {
"id": bp.number,
- # We always use True here, because this field just indicates
- # that breakpoint creation was successful -- and if we have a
- # breakpoint, the creation succeeded.
- "verified": True,
+ "verified": not bp.pending,
}
+ if bp.pending:
+ result["reason"] = "pending"
if bp.locations:
# Just choose the first location, because DAP doesn't allow
# multiple locations. See
@@ -146,52 +149,55 @@ def _set_breakpoints_callback(kind, specs, creator):
saved_map = {}
breakpoint_map[kind] = {}
result = []
- for spec in specs:
- # It makes sense to reuse a breakpoint even if the condition
- # or ignore count differs, so remove these entries from the
- # spec first.
- (condition, hit_condition) = _remove_entries(spec, "condition", "hitCondition")
- keyspec = frozenset(spec.items())
-
- # Create or reuse a breakpoint. If asked, set the condition
- # or the ignore count. Catch errors coming from gdb and
- # report these as an "unverified" breakpoint.
- bp = None
- try:
- if keyspec in saved_map:
- bp = saved_map.pop(keyspec)
- else:
- with suppress_new_breakpoint_event():
+ with suppress_new_breakpoint_event():
+ for spec in specs:
+ # It makes sense to reuse a breakpoint even if the condition
+ # or ignore count differs, so remove these entries from the
+ # spec first.
+ (condition, hit_condition) = _remove_entries(
+ spec, "condition", "hitCondition"
+ )
+ keyspec = frozenset(spec.items())
+
+ # Create or reuse a breakpoint. If asked, set the condition
+ # or the ignore count. Catch errors coming from gdb and
+ # report these as an "unverified" breakpoint.
+ bp = None
+ try:
+ if keyspec in saved_map:
+ bp = saved_map.pop(keyspec)
+ else:
bp = creator(**spec)
- bp.condition = condition
- if hit_condition is None:
- bp.ignore_count = 0
- else:
- bp.ignore_count = int(
- parse_and_eval(hit_condition, global_context=True)
+ bp.condition = condition
+ if hit_condition is None:
+ bp.ignore_count = 0
+ else:
+ bp.ignore_count = int(
+ parse_and_eval(hit_condition, global_context=True)
+ )
+
+ # Reaching this spot means success.
+ breakpoint_map[kind][keyspec] = bp
+ result.append(_breakpoint_descriptor(bp))
+ # Exceptions other than gdb.error are possible here.
+ except Exception as e:
+ # Don't normally want to see this, as it interferes with
+ # the test suite.
+ log_stack(LogLevel.FULL)
+ # Maybe the breakpoint was made but setting an attribute
+ # failed. We still want this to fail.
+ if bp is not None:
+ bp.delete()
+ # Breakpoint creation failed.
+ result.append(
+ {
+ "verified": False,
+ "reason": "failed",
+ "message": str(e),
+ }
)
- # Reaching this spot means success.
- breakpoint_map[kind][keyspec] = bp
- result.append(_breakpoint_descriptor(bp))
- # Exceptions other than gdb.error are possible here.
- except Exception as e:
- # Don't normally want to see this, as it interferes with
- # the test suite.
- log_stack(LogLevel.FULL)
- # Maybe the breakpoint was made but setting an attribute
- # failed. We still want this to fail.
- if bp is not None:
- bp.delete()
- # Breakpoint creation failed.
- result.append(
- {
- "verified": False,
- "message": str(e),
- }
- )
-
# Delete any breakpoints that were not reused.
for entry in saved_map.values():
entry.delete()
diff --git a/gdb/python/lib/gdb/dap/launch.py b/gdb/python/lib/gdb/dap/launch.py
index 184af16..6783d99 100644
--- a/gdb/python/lib/gdb/dap/launch.py
+++ b/gdb/python/lib/gdb/dap/launch.py
@@ -23,16 +23,6 @@ from .server import request, capability
from .startup import exec_and_log, DAPException
-# The program being launched, or None. This should only be accessed
-# from the gdb thread.
-_program = None
-
-
-# True if the program was attached, False otherwise. This should only
-# be accessed from the gdb thread.
-_attach = False
-
-
# Any parameters here are necessarily extensions -- DAP requires this
# from implementations. Any additions or changes here should be
# documented in the gdb manual.
@@ -46,10 +36,6 @@ def launch(
stopAtBeginningOfMainSubprogram: bool = False,
**extra,
):
- global _program
- _program = program
- global _attach
- _attach = False
if cwd is not None:
exec_and_log("cd " + cwd)
if program is not None:
@@ -64,6 +50,8 @@ def launch(
inf.clear_env()
for name, value in env.items():
inf.set_env(name, value)
+ expect_process("process")
+ exec_and_expect_stop("run")
@request("attach")
@@ -74,11 +62,6 @@ def attach(
target: Optional[str] = None,
**args,
):
- # Ensure configurationDone does not try to run.
- global _attach
- _attach = True
- global _program
- _program = program
if program is not None:
exec_and_log("file " + program)
if pid is not None:
@@ -93,9 +76,7 @@ def attach(
@capability("supportsConfigurationDoneRequest")
-@request("configurationDone", response=False)
+@request("configurationDone")
def config_done(**args):
- global _attach
- if not _attach:
- expect_process("process")
- exec_and_expect_stop("run")
+ # Nothing to do.
+ return None