public inbox for gdb-patches@sourceware.org
 help / color / mirror / Atom feed
From: Tom Tromey <tromey@adacore.com>
To: gdb-patches@sourceware.org
Subject: [PATCH 3/3] Implement the notStopped DAP response
Date: Tue, 07 Nov 2023 11:29:31 -0700	[thread overview]
Message-ID: <20231107-dap-not-stopped-v1-3-3d91c935255d@adacore.com> (raw)
In-Reply-To: <20231107-dap-not-stopped-v1-0-3d91c935255d@adacore.com>

DAP specifies that a request can fail with the "notStopped" message if
the inferior is running but the request requires that it first be
stopped.

This patch implements this for gdb.  Most requests are assumed to
require a stopped inferior, and the exceptions are noted by a new
'request' parameter.

You may notice that the implementation is a bit racy.  I think this is
inherent -- unless the client waits for a stop event before sending a
request, the request may be processed at any time relative to a stop.

https://sourceware.org/bugzilla/show_bug.cgi?id=31037
---
 gdb/python/lib/gdb/dap/events.py | 15 +++++++++++++++
 gdb/python/lib/gdb/dap/pause.py  |  2 +-
 gdb/python/lib/gdb/dap/server.py | 38 +++++++++++++++++++++++++++++++++++---
 gdb/testsuite/gdb.dap/pause.exp  |  7 +++++++
 4 files changed, 58 insertions(+), 4 deletions(-)

diff --git a/gdb/python/lib/gdb/dap/events.py b/gdb/python/lib/gdb/dap/events.py
index 09214ec3dc8..bfc3f9ee1dc 100644
--- a/gdb/python/lib/gdb/dap/events.py
+++ b/gdb/python/lib/gdb/dap/events.py
@@ -21,8 +21,17 @@ from .startup import exec_and_log, in_gdb_thread, log
 from .modules import is_module, make_module
 
 
+# True when the inferior is thought to be running, False otherwise.
+# This may be accessed from any thread, which can be racy.  However,
+# this unimportant because this global is only used for the
+# 'notStopped' response, which itself is inherently racy.
+inferior_running = False
+
+
 @in_gdb_thread
 def _on_exit(event):
+    global inferior_running
+    inferior_running = False
     code = 0
     if hasattr(event, "exit_code"):
         code = event.exit_code
@@ -48,6 +57,8 @@ def thread_event(event, reason):
 
 @in_gdb_thread
 def _new_thread(event):
+    global inferior_running
+    inferior_running = True
     thread_event(event, "started")
 
 
@@ -85,6 +96,8 @@ _suppress_cont = False
 
 @in_gdb_thread
 def _cont(event):
+    global inferior_running
+    inferior_running = True
     global _suppress_cont
     if _suppress_cont:
         log("_suppress_cont case")
@@ -123,6 +136,8 @@ def exec_and_expect_stop(cmd, reason):
 
 @in_gdb_thread
 def _on_stop(event):
+    global inferior_running
+    inferior_running = False
     log("entering _on_stop: " + repr(event))
     global _expected_stop
     obj = {
diff --git a/gdb/python/lib/gdb/dap/pause.py b/gdb/python/lib/gdb/dap/pause.py
index d276ab1cb92..3d9b4ae108e 100644
--- a/gdb/python/lib/gdb/dap/pause.py
+++ b/gdb/python/lib/gdb/dap/pause.py
@@ -17,6 +17,6 @@ from .events import StopKinds, exec_and_expect_stop
 from .server import request
 
 
-@request("pause", response=False)
+@request("pause", response=False, must_be_stopped=False)
 def pause(**args):
     exec_and_expect_stop("interrupt -a", StopKinds.PAUSE)
diff --git a/gdb/python/lib/gdb/dap/server.py b/gdb/python/lib/gdb/dap/server.py
index d2980ad4031..d3b7f75c224 100644
--- a/gdb/python/lib/gdb/dap/server.py
+++ b/gdb/python/lib/gdb/dap/server.py
@@ -13,6 +13,7 @@
 # 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 functools
 import inspect
 import json
 import queue
@@ -163,7 +164,27 @@ def send_event(event, body=None):
     _server.send_event(event, body)
 
 
-def request(name: str, *, response: bool = True, server: bool = False):
+# A helper decorator that checks whether the inferior is running.
+def _check_not_running(func):
+    @functools.wraps(func)
+    def check(*args, **kwargs):
+        # Import this as late as possible.  FIXME.
+        from .events import inferior_running
+
+        if inferior_running:
+            raise Exception("notStopped")
+        return func(*args, **kwargs)
+
+    return check
+
+
+def request(
+    name: str,
+    *,
+    response: bool = True,
+    server: bool = False,
+    must_be_stopped: bool = True
+):
     """A decorator for DAP requests.
 
     This registers the function as the implementation of the DAP
@@ -178,6 +199,11 @@ def request(name: str, *, response: bool = True, server: bool = False):
 
     If SERVER is True, the function will be invoked in the DAP thread.
     When SERVER is True, RESPONSE may not be False.
+
+    If MUST_BE_STOPPED is True (the default), then the request will
+    fail with the 'notStopped' reason if it is processed while the
+    inferior is running.  When MUST_BE_STOPPED is False, the request
+    will proceed regardless of the inferior's state.
     """
 
     # Validate the parameters.
@@ -217,6 +243,12 @@ def request(name: str, *, response: bool = True, server: bool = False):
 
                 cmd = non_sync_call
 
+        # If needed, check that the inferior is not running.  This
+        # wrapping is done last, so the check is done first, before
+        # trying to dispatch the request to another thread.
+        if must_be_stopped:
+            cmd = _check_not_running(cmd)
+
         global _commands
         _commands[name] = cmd
         return cmd
@@ -255,13 +287,13 @@ def initialize(**args):
     return _capabilities.copy()
 
 
-@request("terminate")
+@request("terminate", must_be_stopped=False)
 @capability("supportsTerminateRequest")
 def terminate(**args):
     exec_and_log("kill")
 
 
-@request("disconnect", server=True)
+@request("disconnect", server=True, must_be_stopped=False)
 @capability("supportTerminateDebuggee")
 def disconnect(*, terminateDebuggee: bool = False, **args):
     if terminateDebuggee:
diff --git a/gdb/testsuite/gdb.dap/pause.exp b/gdb/testsuite/gdb.dap/pause.exp
index 27955d31526..558ede982ee 100644
--- a/gdb/testsuite/gdb.dap/pause.exp
+++ b/gdb/testsuite/gdb.dap/pause.exp
@@ -32,6 +32,13 @@ if {[dap_launch $testfile] == ""} {
 dap_check_request_and_response "start inferior" configurationDone
 dap_wait_for_event_and_check "inferior started" thread "body reason" started
 
+set resp [lindex [dap_request_and_response evaluate {o expression [s 23]}] \
+	      0]
+gdb_assert {[dict get $resp success] == "false"} \
+    "evaluate failed while inferior executing"
+gdb_assert {[dict get $resp message] == "notStopped"} \
+    "evaluate issued notStopped"
+
 dap_check_request_and_response pause pause \
     {o threadId [i 1]}
 

-- 
2.41.0


  parent reply	other threads:[~2023-11-07 18:29 UTC|newest]

Thread overview: 11+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-11-07 18:29 [PATCH 0/3] Implement the DAP notStopped response Tom Tromey
2023-11-07 18:29 ` [PATCH 1/3] Automatically run (most) DAP requests in gdb thread Tom Tromey
2023-11-13 12:59   ` Alexandra Petlanova Hajkova
2023-11-07 18:29 ` [PATCH 2/3] Remove ExecutionInvoker Tom Tromey
2023-11-13 13:57   ` Alexandra Petlanova Hajkova
2023-11-07 18:29 ` Tom Tromey [this message]
2023-11-08  8:23   ` [PATCH 3/3] Implement the notStopped DAP response Kévin Le Gouguec
2023-11-10 14:52     ` Tom Tromey
2023-11-10 15:14       ` Kévin Le Gouguec
2023-11-10 15:18         ` Tom Tromey
2023-11-10 15:08     ` Tom Tromey

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20231107-dap-not-stopped-v1-3-3d91c935255d@adacore.com \
    --to=tromey@adacore.com \
    --cc=gdb-patches@sourceware.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).