aboutsummaryrefslogtreecommitdiff
path: root/gdb/python/lib/gdb/dap/launch.py
diff options
context:
space:
mode:
Diffstat (limited to 'gdb/python/lib/gdb/dap/launch.py')
-rw-r--r--gdb/python/lib/gdb/dap/launch.py163
1 files changed, 126 insertions, 37 deletions
diff --git a/gdb/python/lib/gdb/dap/launch.py b/gdb/python/lib/gdb/dap/launch.py
index 2674e02..8ac4c77 100644
--- a/gdb/python/lib/gdb/dap/launch.py
+++ b/gdb/python/lib/gdb/dap/launch.py
@@ -1,4 +1,4 @@
-# Copyright 2022-2024 Free Software Foundation, Inc.
+# Copyright 2022-2025 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
@@ -13,20 +13,65 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+import re
+
# These are deprecated in 3.9, but required in older versions.
from typing import Mapping, Optional, Sequence
import gdb
from .events import exec_and_expect_stop, expect_process, expect_stop
-from .server import capability, request
-from .startup import DAPException, exec_and_log
+from .server import (
+ DeferredRequest,
+ call_function_later,
+ capability,
+ request,
+ send_gdb,
+ send_gdb_with_response,
+)
+from .startup import DAPException, exec_and_log, in_dap_thread, in_gdb_thread
+
+# A launch or attach promise that that will be fulfilled after a
+# configurationDone request has been processed.
+_launch_or_attach_promise = None
+
+
+# A DeferredRequest that handles either a "launch" or "attach"
+# request.
+class _LaunchOrAttachDeferredRequest(DeferredRequest):
+ def __init__(self, callback):
+ self._callback = callback
+ global _launch_or_attach_promise
+ if _launch_or_attach_promise is not None:
+ raise DAPException("launch or attach already specified")
+ _launch_or_attach_promise = self
+
+ # Invoke the callback and return the result.
+ @in_dap_thread
+ def invoke(self):
+ return self._callback()
+
+ # Override this so we can clear the global when rescheduling.
+ @in_dap_thread
+ def reschedule(self):
+ global _launch_or_attach_promise
+ _launch_or_attach_promise = None
+ super().reschedule()
+
+
+# A wrapper for the 'file' command that correctly quotes its argument.
+@in_gdb_thread
+def file_command(program):
+ # Handle whitespace, quotes, and backslashes here. Exactly what
+ # to quote depends on libiberty's buildargv and safe-ctype.
+ program = re.sub("[ \t\n\r\f\v\\\\'\"]", "\\\\\\g<0>", program)
+ exec_and_log("file " + program)
# Any parameters here are necessarily extensions -- DAP requires this
# from implementations. Any additions or changes here should be
# documented in the gdb manual.
-@request("launch", response=False)
+@request("launch", on_dap_thread=True)
def launch(
*,
program: Optional[str] = None,
@@ -34,27 +79,54 @@ def launch(
args: Sequence[str] = (),
env: Optional[Mapping[str, str]] = None,
stopAtBeginningOfMainSubprogram: bool = False,
+ stopOnEntry: bool = False,
**extra,
):
- if cwd is not None:
- exec_and_log("cd " + cwd)
- if program is not None:
- exec_and_log("file " + program)
- inf = gdb.selected_inferior()
- if stopAtBeginningOfMainSubprogram:
- main = inf.main_name
- if main is not None:
- exec_and_log("tbreak " + main)
- inf.arguments = args
- if env is not None:
- inf.clear_env()
- for name, value in env.items():
- inf.set_env(name, value)
- expect_process("process")
- exec_and_expect_stop("run")
-
-
-@request("attach")
+ # Launch setup is handled here. This is done synchronously so
+ # that errors can be reported in a natural way.
+ @in_gdb_thread
+ def _setup_launch():
+ if cwd is not None:
+ exec_and_log("cd " + cwd)
+ if program is not None:
+ file_command(program)
+ inf = gdb.selected_inferior()
+ inf.arguments = args
+ if env is not None:
+ inf.clear_env()
+ for name, value in env.items():
+ inf.set_env(name, value)
+
+ # Actual launching done here. See below for more info.
+ @in_gdb_thread
+ def _do_launch():
+ expect_process("process")
+ if stopAtBeginningOfMainSubprogram:
+ cmd = "start"
+ elif stopOnEntry:
+ cmd = "starti"
+ else:
+ cmd = "run"
+ exec_and_expect_stop(cmd)
+
+ @in_dap_thread
+ def _launch_impl():
+ send_gdb_with_response(_setup_launch)
+ # We do not wait for the result here. It might be a little
+ # nicer if we did -- perhaps the various thread events would
+ # occur in a more logical sequence -- but if the inferior does
+ # not stop, then the launch response will not be seen either,
+ # which seems worse.
+ send_gdb(_do_launch)
+ # Launch response does not have a body.
+ return None
+
+ # The launch itself is deferred until the configurationDone
+ # request.
+ return _LaunchOrAttachDeferredRequest(_launch_impl)
+
+
+@request("attach", on_dap_thread=True)
def attach(
*,
program: Optional[str] = None,
@@ -62,21 +134,38 @@ def attach(
target: Optional[str] = None,
**args,
):
- if program is not None:
- exec_and_log("file " + program)
- if pid is not None:
- cmd = "attach " + str(pid)
- elif target is not None:
- cmd = "target remote " + target
- else:
- raise DAPException("attach requires either 'pid' or 'target'")
- expect_process("attach")
- expect_stop("attach")
- exec_and_log(cmd)
+ # The actual attach is handled by this function.
+ @in_gdb_thread
+ def _do_attach():
+ if program is not None:
+ file_command(program)
+ if pid is not None:
+ cmd = "attach " + str(pid)
+ elif target is not None:
+ cmd = "target remote " + target
+ else:
+ raise DAPException("attach requires either 'pid' or 'target'")
+ expect_process("attach")
+ expect_stop("attach")
+ exec_and_log(cmd)
+ # Attach response does not have a body.
+ return None
+
+ @in_dap_thread
+ def _attach_impl():
+ return send_gdb_with_response(_do_attach)
+
+ # The attach itself is deferred until the configurationDone
+ # request.
+ return _LaunchOrAttachDeferredRequest(_attach_impl)
@capability("supportsConfigurationDoneRequest")
-@request("configurationDone")
+@request("configurationDone", on_dap_thread=True)
def config_done(**args):
- # Nothing to do.
- return None
+ # Handle the launch or attach.
+ if _launch_or_attach_promise is None:
+ raise DAPException("launch or attach not specified")
+ # Resolve the launch or attach, but only after the
+ # configurationDone response has been sent.
+ call_function_later(_launch_or_attach_promise.reschedule)