Emit DAP "process" event

Message ID 20231114192521.3710322-1-tromey@adacore.com
State New
Headers
Series Emit DAP "process" event |

Checks

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

Commit Message

Tom Tromey Nov. 14, 2023, 7:25 p.m. UTC
  DAP specifies a "process" event that is sent when a process is started
or attached to.  gdb was not emitting this (several DAP clients appear
to ignore it entirely), but it looked easy and harmless to implement.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=30473
---
 gdb/python/lib/gdb/dap/events.py | 36 ++++++++++++++++++++++++++++++++
 gdb/python/lib/gdb/dap/launch.py |  4 +++-
 gdb/testsuite/gdb.dap/pause.exp  |  2 ++
 3 files changed, 41 insertions(+), 1 deletion(-)
  

Comments

Tom Tromey Nov. 28, 2023, 3:56 p.m. UTC | #1
>>>>> "Tom" == Tom Tromey <tromey@adacore.com> writes:

Tom> DAP specifies a "process" event that is sent when a process is started
Tom> or attached to.  gdb was not emitting this (several DAP clients appear
Tom> to ignore it entirely), but it looked easy and harmless to implement.

Tom> Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=30473

I've rebased this and I'm checking this in.

Because my sense is that the process event is not really relied on
(e.g., VS Code ignores it), I am not going to apply this one to the
gdb-14 branch.

Tom
  

Patch

diff --git a/gdb/python/lib/gdb/dap/events.py b/gdb/python/lib/gdb/dap/events.py
index e9ddcab135f..2c0d08cf653 100644
--- a/gdb/python/lib/gdb/dap/events.py
+++ b/gdb/python/lib/gdb/dap/events.py
@@ -35,8 +35,43 @@  def _on_exit(event):
     send_event("terminated")
 
 
+# When None, a "process" event has already been sent.  When a string,
+# it is the "startMethod" for that event.
+_process_event_kind = None
+
+
+@in_gdb_thread
+def send_process_event_once():
+    global _process_event_kind
+    if _process_event_kind is not None:
+        inf = gdb.selected_inferior()
+        is_local = inf.connection.type == "native"
+        data = {
+            "isLocalProcess": is_local,
+            "startMethod": _process_event_kind,
+            # Could emit 'pointerSize' here too if we cared to.
+        }
+        if inf.progspace.filename:
+            data["name"] = inf.progspace.filename
+        if is_local:
+            data["systemProcessId"] = inf.pid
+        send_event("process", data)
+        _process_event_kind = None
+
+
+@in_gdb_thread
+def expect_process(reason):
+    """Indicate that DAP is starting or attaching to a process.
+
+    REASON is the "startMethod" to include in the "process" event.
+    """
+    global _process_event_kind
+    _process_event_kind = reason
+
+
 @in_gdb_thread
 def thread_event(event, reason):
+    send_process_event_once()
     send_event(
         "thread",
         {
@@ -70,6 +105,7 @@  def _new_objfile(event):
 
 @in_gdb_thread
 def _objfile_removed(event):
+    send_process_event_once()
     if is_module(event.objfile):
         send_event(
             "module",
diff --git a/gdb/python/lib/gdb/dap/launch.py b/gdb/python/lib/gdb/dap/launch.py
index d13037fa476..84ff1b4aadb 100644
--- a/gdb/python/lib/gdb/dap/launch.py
+++ b/gdb/python/lib/gdb/dap/launch.py
@@ -18,7 +18,7 @@  import gdb
 # These are deprecated in 3.9, but required in older versions.
 from typing import Mapping, Optional, Sequence
 
-from .events import ExecutionInvoker
+from .events import ExecutionInvoker, expect_process
 from .server import request, capability
 from .startup import send_gdb, send_gdb_with_response, in_gdb_thread, exec_and_log
 
@@ -77,6 +77,7 @@  def attach(*, pid: Optional[int] = None, target: Optional[str] = None, **args):
         cmd = "target remote " + target
     else:
         raise Exception("attach requires either 'pid' or 'target'")
+    send_gdb(lambda: expect_process("attach"))
     # Use send_gdb_with_response to ensure we get an error if the
     # attach fails.
     send_gdb_with_response(cmd)
@@ -88,6 +89,7 @@  def attach(*, pid: Optional[int] = None, target: Optional[str] = None, **args):
 def config_done(**args):
     global _program
     if _program is not None:
+        send_gdb(lambda: expect_process("process"))
         # Suppress the continue event, but don't set any particular
         # expected stop.
         send_gdb(ExecutionInvoker("run", None))
diff --git a/gdb/testsuite/gdb.dap/pause.exp b/gdb/testsuite/gdb.dap/pause.exp
index 27955d31526..b16b294be77 100644
--- a/gdb/testsuite/gdb.dap/pause.exp
+++ b/gdb/testsuite/gdb.dap/pause.exp
@@ -30,6 +30,8 @@  if {[dap_launch $testfile] == ""} {
 }
 
 dap_check_request_and_response "start inferior" configurationDone
+dap_wait_for_event_and_check "process event generated" process \
+    "body startMethod" process
 dap_wait_for_event_and_check "inferior started" thread "body reason" started
 
 dap_check_request_and_response pause pause \