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
@in_gdb_thread
def _new_thread(event):
+ global inferior_running
+ inferior_running = True
thread_event(event, "started")
@in_gdb_thread
def _cont(event):
+ global inferior_running
+ inferior_running = True
global _suppress_cont
if _suppress_cont:
log("_suppress_cont case")
@in_gdb_thread
def _on_stop(event):
+ global inferior_running
+ inferior_running = False
log("entering _on_stop: " + repr(event))
global _expected_stop
obj = {
# 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
_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
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.
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
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:
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]}