aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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
-rw-r--r--gdb/testsuite/gdb.dap/pause.exp7
4 files changed, 59 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:
diff --git a/gdb/testsuite/gdb.dap/pause.exp b/gdb/testsuite/gdb.dap/pause.exp
index 27955d3..558ede9 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]}