diff options
-rw-r--r-- | gdb/python/lib/gdb/dap/events.py | 15 | ||||
-rw-r--r-- | gdb/python/lib/gdb/dap/pause.py | 2 | ||||
-rw-r--r-- | gdb/python/lib/gdb/dap/server.py | 39 | ||||
-rw-r--r-- | gdb/testsuite/gdb.dap/pause.exp | 7 |
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]} |