[3/3] Simplify DAP stop-reason code

Message ID 20231114-py-stop-reason-v1-3-4ff440c956a9@adacore.com
State New
Headers
Series Simplify DAP stop reason emission |

Checks

Context Check Description
linaro-tcwg-bot/tcwg_gdb_build--master-arm success Testing passed
linaro-tcwg-bot/tcwg_gdb_build--master-aarch64 success Testing passed

Commit Message

Tom Tromey Nov. 14, 2023, 6:44 p.m. UTC
  Now that gdb adds stop-reason details to stop events, we can simplify
the DAP code to emit correct stop reasons in its own events.  For the
most part a simple renaming of gdb reasons is sufficient; however,
"pause" must still be handled specially.
---
 gdb/python/lib/gdb/dap/events.py | 79 +++++++++++++++++++++++++---------------
 gdb/python/lib/gdb/dap/launch.py |  4 +-
 gdb/python/lib/gdb/dap/next.py   | 10 ++---
 gdb/python/lib/gdb/dap/pause.py  |  4 +-
 4 files changed, 57 insertions(+), 40 deletions(-)
  

Patch

diff --git a/gdb/python/lib/gdb/dap/events.py b/gdb/python/lib/gdb/dap/events.py
index e9ddcab135f..bdd2a6ebadc 100644
--- a/gdb/python/lib/gdb/dap/events.py
+++ b/gdb/python/lib/gdb/dap/events.py
@@ -99,60 +99,79 @@  def _cont(event):
         )
 
 
-class StopKinds(enum.Enum):
-    # The values here are chosen to follow the DAP spec.
-    STEP = "step"
-    BREAKPOINT = "breakpoint"
-    PAUSE = "pause"
-    EXCEPTION = "exception"
-
-
-_expected_stop = None
-
-
-@in_gdb_thread
-def expect_stop(reason):
-    """Indicate that a stop is expected, for the reason given."""
-    global _expected_stop
-    _expected_stop = reason
+_expected_pause = False
 
 
 # A wrapper for Invoker that also sets the expected stop.
 class ExecutionInvoker(Invoker):
-    """A subclass of Invoker that sets the expected stop.
-    Note that this assumes that the command will restart the inferior,
-    so it will also cause ContinuedEvents to be suppressed."""
+    """A subclass of Invoker that sets the continue-suppression flag.
 
-    def __init__(self, cmd, expected):
+    When EXPECTED_PAUSE is True, a stop that looks like a pause (e.g.,
+    a SIGINT) will be reported as "pause" instead.
+    """
+
+    def __init__(self, cmd, expected_pause=False):
         super().__init__(cmd)
-        self.expected = expected
+        self.expected_pause = expected_pause
 
     @in_gdb_thread
     def __call__(self):
-        expect_stop(self.expected)
+        global _expected_pause
+        _expected_pause = self.expected_pause
         global _suppress_cont
         _suppress_cont = True
         # FIXME if the call fails should we clear _suppress_cont?
         super().__call__()
 
 
+# Map from gdb stop reasons to DAP stop reasons.  Some of these can't
+# be seen ordinarily in DAP -- only if the client lets the user toggle
+# some settings (e.g. stop-on-solib-events) or enter commands (e.g.,
+# 'until').
+stop_reason_map = {
+    "breakpoint-hit": "breakpoint",
+    "watchpoint-trigger": "data breakpoint",
+    "read-watchpoint-trigger": "data breakpoint",
+    "access-watchpoint-trigger": "data breakpoint",
+    "function-finished": "step",
+    "location-reached": "step",
+    "watchpoint-scope": "data breakpoint",
+    "end-stepping-range": "step",
+    "exited-signalled": "exited",
+    "exited": "exited",
+    "exited-normally": "exited",
+    "signal-received": "signal",
+    "solib-event": "solib",
+    "fork": "fork",
+    "vfork": "vfork",
+    "syscall-entry": "syscall-entry",
+    "syscall-return": "syscall-return",
+    "exec": "exec",
+    "no-history": "no-history",
+}
+
+
 @in_gdb_thread
 def _on_stop(event):
     log("entering _on_stop: " + repr(event))
-    global _expected_stop
+    log("   details: " + repr(event.details))
     obj = {
         "threadId": gdb.selected_thread().global_num,
         "allThreadsStopped": True,
     }
     if isinstance(event, gdb.BreakpointEvent):
-        # Ignore the expected stop, we hit a breakpoint instead.
-        _expected_stop = StopKinds.BREAKPOINT
         obj["hitBreakpointIds"] = [x.number for x in event.breakpoints]
-    elif _expected_stop is None:
-        # FIXME what is even correct here
-        _expected_stop = StopKinds.EXCEPTION
-    obj["reason"] = _expected_stop.value
-    _expected_stop = None
+    global stop_reason_map
+    reason = event.details["reason"]
+    global _expected_pause
+    if (
+        _expected_pause
+        and reason == "signal-received"
+        and event.details["signal-name"] in ("SIGINT", "SIGSTOP")
+    ):
+        obj["reason"] = "pause"
+    else:
+        obj["reason"] = stop_reason_map[reason]
     send_event("stopped", obj)
 
 
diff --git a/gdb/python/lib/gdb/dap/launch.py b/gdb/python/lib/gdb/dap/launch.py
index d13037fa476..7dead0c7c35 100644
--- a/gdb/python/lib/gdb/dap/launch.py
+++ b/gdb/python/lib/gdb/dap/launch.py
@@ -88,6 +88,4 @@  def attach(*, pid: Optional[int] = None, target: Optional[str] = None, **args):
 def config_done(**args):
     global _program
     if _program is not None:
-        # Suppress the continue event, but don't set any particular
-        # expected stop.
-        send_gdb(ExecutionInvoker("run", None))
+        send_gdb(ExecutionInvoker("run"))
diff --git a/gdb/python/lib/gdb/dap/next.py b/gdb/python/lib/gdb/dap/next.py
index e5bb8d64da0..76c9360383a 100644
--- a/gdb/python/lib/gdb/dap/next.py
+++ b/gdb/python/lib/gdb/dap/next.py
@@ -15,7 +15,7 @@ 
 
 import gdb
 
-from .events import StopKinds, ExecutionInvoker
+from .events import ExecutionInvoker
 from .server import capability, request
 from .startup import in_gdb_thread, send_gdb, send_gdb_with_response
 from .state import set_thread
@@ -57,7 +57,7 @@  def next(
     cmd = "next"
     if granularity == "instruction":
         cmd += "i"
-    send_gdb(ExecutionInvoker(cmd, StopKinds.STEP))
+    send_gdb(ExecutionInvoker(cmd))
 
 
 @capability("supportsSteppingGranularity")
@@ -70,17 +70,17 @@  def step_in(
     cmd = "step"
     if granularity == "instruction":
         cmd += "i"
-    send_gdb(ExecutionInvoker(cmd, StopKinds.STEP))
+    send_gdb(ExecutionInvoker(cmd))
 
 
 @request("stepOut")
 def step_out(*, threadId: int, singleThread: bool = False, **args):
     send_gdb(lambda: _handle_thread_step(threadId, singleThread, True))
-    send_gdb(ExecutionInvoker("finish", StopKinds.STEP))
+    send_gdb(ExecutionInvoker("finish"))
 
 
 @request("continue")
 def continue_request(*, threadId: int, singleThread: bool = False, **args):
     locked = send_gdb_with_response(lambda: _handle_thread_step(threadId, singleThread))
-    send_gdb(ExecutionInvoker("continue", None))
+    send_gdb(ExecutionInvoker("continue"))
     return {"allThreadsContinued": not locked}
diff --git a/gdb/python/lib/gdb/dap/pause.py b/gdb/python/lib/gdb/dap/pause.py
index 1e59d630523..a89741cbf69 100644
--- a/gdb/python/lib/gdb/dap/pause.py
+++ b/gdb/python/lib/gdb/dap/pause.py
@@ -13,11 +13,11 @@ 
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
-from .events import StopKinds, ExecutionInvoker
+from .events import ExecutionInvoker
 from .server import request
 from .startup import send_gdb
 
 
 @request("pause")
 def pause(**args):
-    send_gdb(ExecutionInvoker("interrupt -a", StopKinds.PAUSE))
+    send_gdb(ExecutionInvoker("interrupt -a", True))