aboutsummaryrefslogtreecommitdiff
path: root/gdb/python
diff options
context:
space:
mode:
authorTom Tromey <tromey@adacore.com>2023-11-07 08:44:44 -0700
committerTom Tromey <tromey@adacore.com>2023-11-17 08:26:03 -0700
commitcfd00e8050a58aacc6489ec0379908be1a12be73 (patch)
tree55a069a9d7ab99c21625378701e763b90a37403e /gdb/python
parent68caad9d0b06d0ac231ce083ff62410a5a1806c1 (diff)
downloadgdb-cfd00e8050a58aacc6489ec0379908be1a12be73.zip
gdb-cfd00e8050a58aacc6489ec0379908be1a12be73.tar.gz
gdb-cfd00e8050a58aacc6489ec0379908be1a12be73.tar.bz2
Implement the notStopped DAP response
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 Reviewed-by: Kévin Le Gouguec <legouguec@adacore.com>
Diffstat (limited to 'gdb/python')
-rw-r--r--gdb/python/lib/gdb/dap/events.py15
-rw-r--r--gdb/python/lib/gdb/dap/pause.py2
-rw-r--r--gdb/python/lib/gdb/dap/server.py39
3 files changed, 52 insertions, 4 deletions
diff --git a/gdb/python/lib/gdb/dap/events.py b/gdb/python/lib/gdb/dap/events.py
index 09214ec..bfc3f9e 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 d276ab1..b7e2145 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, expect_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 4430d2a..031bf49 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,28 @@ def send_event(event, body=None):
_server.send_event(event, body)
-def request(name: str, *, response: bool = True, on_dap_thread: 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. This is done to avoid
+ # circular imports.
+ from .events import inferior_running
+
+ if inferior_running:
+ raise Exception("notStopped")
+ return func(*args, **kwargs)
+
+ return check
+
+
+def request(
+ name: str,
+ *,
+ response: bool = True,
+ on_dap_thread: bool = False,
+ expect_stopped: bool = True
+):
"""A decorator for DAP requests.
This registers the function as the implementation of the DAP
@@ -178,6 +200,11 @@ def request(name: str, *, response: bool = True, on_dap_thread: bool = False):
If ON_DAP_THREAD is True, the function will be invoked in the DAP
thread. When ON_DAP_THREAD is True, RESPONSE may not be False.
+
+ If EXPECT_STOPPED is True (the default), then the request will
+ fail with the 'notStopped' reason if it is processed while the
+ inferior is running. When EXPECT_STOPPED is False, the request
+ will proceed regardless of the inferior's state.
"""
# Validate the parameters.
@@ -217,6 +244,12 @@ def request(name: str, *, response: bool = True, on_dap_thread: 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 expect_stopped:
+ cmd = _check_not_running(cmd)
+
global _commands
_commands[name] = cmd
return cmd
@@ -255,13 +288,13 @@ def initialize(**args):
return _capabilities.copy()
-@request("terminate")
+@request("terminate", expect_stopped=False)
@capability("supportsTerminateRequest")
def terminate(**args):
exec_and_log("kill")
-@request("disconnect", on_dap_thread=True)
+@request("disconnect", on_dap_thread=True, expect_stopped=False)
@capability("supportTerminateDebuggee")
def disconnect(*, terminateDebuggee: bool = False, **args):
if terminateDebuggee: