diff mbox series

[v2,3/8] devtool: ide-sdk: synchronize debugger server readiness

Message ID 20260809093629.3457107-4-adrian.freihofer@siemens.com
State New
Headers show
Series oe-selftest: devtool ide-sdk: fix lldb connect race in _lldb_server_debugging_once | expand

Commit Message

AdrianF Aug. 9, 2026, 9:35 a.m. UTC
From: Adrian Freihofer <adrian.freihofer@siemens.com>

Have GDB ONCE and ATTACH target commands wait for the listening socket,
report a generated readiness marker, and remain active for the debug
session. Make the VS Code background-task matcher wait for that marker
instead of accepting arbitrary output.

Centralize TCP readiness checks, timeout cleanup, server names, and
marker generation in DebuggerCrossConfig. Reuse the TCP wait for
persistent GDB and LLDB servers, and reject the unused LLDB one-shot
mode explicitly.

Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com>
---
 meta/lib/oeqa/selftest/cases/devtool.py     |  7 ++--
 scripts/lib/devtool/ide_plugins/__init__.py | 41 ++++++++++++++-------
 scripts/lib/devtool/ide_plugins/ide_code.py |  7 +++-
 3 files changed, 37 insertions(+), 18 deletions(-)
diff mbox series

Patch

diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py
index d84a18e8b6..04a42b8906 100644
--- a/meta/lib/oeqa/selftest/cases/devtool.py
+++ b/meta/lib/oeqa/selftest/cases/devtool.py
@@ -3508,10 +3508,11 @@  class DevtoolIdeSdkTests(DevtoolBase):
 
         # Start gdbserver on target using the task command (keep the ssh connection open while debugging)
         ssh_gdbserver_cmd = [task_command] + task_args
-        # Fix shell command escaping - remove extra quotes from the last argument
-        # The task_args likely contains a quoted shell command that needs to be unquoted
+        # The tasks.json argument is formatted for an intermediate shell. Strip
+        # its quotes and restore dollar expansions before passing it directly to
+        # SSH via subprocess.
         if len(ssh_gdbserver_cmd) > 0 and ssh_gdbserver_cmd[-1].startswith('"') and ssh_gdbserver_cmd[-1].endswith('"'):
-            ssh_gdbserver_cmd[-1] = ssh_gdbserver_cmd[-1][1:-1]  # Remove surrounding quotes
+            ssh_gdbserver_cmd[-1] = ssh_gdbserver_cmd[-1][1:-1].replace('\\$', '$')
         self.logger.debug(f"Starting gdbserver with command: {' '.join(ssh_gdbserver_cmd)}")
         with RunCmdBackground(ssh_gdbserver_cmd, output_log=self._cmd_logger):
             # Give gdbserver a moment to start
diff --git a/scripts/lib/devtool/ide_plugins/__init__.py b/scripts/lib/devtool/ide_plugins/__init__.py
index deb6050907..6c88e0e34e 100644
--- a/scripts/lib/devtool/ide_plugins/__init__.py
+++ b/scripts/lib/devtool/ide_plugins/__init__.py
@@ -101,6 +101,12 @@  class DebuggerCrossConfig:
         hex_port = "%04X" % self.debug_server_port
         return "grep -q :%s /proc/net/tcp /proc/net/tcp6 2>/dev/null" % hex_port
 
+    def get_debug_server_ready_marker(self, port):
+        return "%s ready on port %s" % (self.DEBUG_SERVER_NAME, port)
+
+    def get_debug_server_ready_marker_pattern(self):
+        return "^%s$" % self.get_debug_server_ready_marker("[0-9]+")
+
     def _target_wait_for_tcp_port_cmd(self, pid_var=None):
         cleanup = ""
         if pid_var:
@@ -130,6 +136,7 @@  class GdbCrossConfig(DebuggerCrossConfig):
     gdbinit / gdb wrapper scripts used by ide=none as well as the
     target-side tmp/pid/log paths consumed by the gdbserver start command.
     """
+    DEBUG_SERVER_NAME = "gdbserver"
 
     def __init__(self, image_recipe, modified_recipe, binary,
                  default_mode=DebuggerServerModes.MULTI):
@@ -172,26 +179,35 @@  class GdbCrossConfig(DebuggerCrossConfig):
           "\"/bin/sh -c '/usr/bin/gdbserver --once :1234 /usr/bin/cmake-example'\""
         """
         if server_mode == DebuggerServerModes.ONCE:
-            gdbserver_cmd_start = "%s --once :%s %s" % (
+            gdbserver_cmd_start = "%s --once :%s %s & " % (
                 self.debugger_cross.debug_server_path, self.debug_server_port, self.binary.binary_path)
+            gdbserver_cmd_start += "_gdbserver_pid=\\$!; "
+            gdbserver_cmd_start += self._target_wait_for_tcp_port_cmd(
+                "gdbserver_pid") + " "
+            gdbserver_cmd_start += "echo %s; wait \\$_gdbserver_pid" % (
+                self.get_debug_server_ready_marker(self.debug_server_port))
         elif server_mode == DebuggerServerModes.ATTACH:
             pid_command = self.binary.pid_command
             if pid_command:
-                gdbserver_cmd_start = "%s --attach :%s \\$(%s)" % (
+                gdbserver_cmd_start = "%s --attach :%s \\$(%s) & " % (
                     self.debugger_cross.debug_server_path,
                     self.debug_server_port,
                     pid_command)
+                gdbserver_cmd_start += "_gdbserver_pid=\\$!; "
+                gdbserver_cmd_start += self._target_wait_for_tcp_port_cmd(
+                    "gdbserver_pid") + " "
+                gdbserver_cmd_start += "echo %s; wait \\$_gdbserver_pid" % (
+                    self.get_debug_server_ready_marker(self.debug_server_port))
             else:
                 raise DevtoolError("Cannot use gdbserver attach mode for binary %s. No PID found." % self.binary.binary_path)
         elif server_mode == DebuggerServerModes.MULTI:
-            hex_port = "%04X" % self.debug_server_port
-            gdbserver_cmd_start = "grep -q :%s /proc/net/tcp /proc/net/tcp6 2>/dev/null && exit 0; " % hex_port
+            gdbserver_cmd_start = self._target_tcp_port_check_cmd() + " && exit 0; "
             gdbserver_cmd_start += "mkdir -p %s; " % self._gdbserver_tmp_dir(server_mode)
-            gdbserver_cmd_start += "%s --multi :%s > %s 2>&1 & " % (
+            gdbserver_cmd_start += "%s --multi :%s > %s 2>&1 & _gdbserver_pid=\\$!; " % (
                 self.debugger_cross.debug_server_path, self.debug_server_port, self._gdbserver_log_file(server_mode))
-            gdbserver_cmd_start += "echo \\$! > %s; " % self._gdbserver_pid_file(server_mode)
-            gdbserver_cmd_start += "_w=0; while ! grep -q :%s /proc/net/tcp /proc/net/tcp6 2>/dev/null; " % hex_port
-            gdbserver_cmd_start += "do _w=\\$((_w+1)); [ \\$_w -lt 100 ] || exit 1; sleep 0.1; done;"
+            gdbserver_cmd_start += "echo \\$_gdbserver_pid > %s; " % self._gdbserver_pid_file(server_mode)
+            gdbserver_cmd_start += self._target_wait_for_tcp_port_cmd(
+                "gdbserver_pid")
         else:
             raise DevtoolError("Unsupported gdbserver mode: %s" % server_mode)
         return "\"/bin/sh -c '" + gdbserver_cmd_start + "'\""
@@ -235,10 +251,7 @@  class LldbServerConfig(DebuggerCrossConfig):
         # lldb-server 21.x and the remote lldb client connects from the host.
         # Start from /tmp because lldb-server creates temp files in its cwd and
         # the SSH default cwd (/home/root) may not exist on a minimal image.
-        if mode == DebuggerServerModes.ONCE:
-            cmd = "cd /tmp && %s platform --one-shot --server --listen *:%s" % (
-                lldb_server, self.debug_server_port)
-        elif mode == DebuggerServerModes.MULTI:
+        if mode == DebuggerServerModes.MULTI:
             pid_file = self._lldb_server_pid_file(mode)
             tmp_dir = self._lldb_server_tmp_dir(mode)
             log_file = self._lldb_server_log_file(mode)
@@ -252,8 +265,8 @@  class LldbServerConfig(DebuggerCrossConfig):
                 "lldb_server_pid")
         else:
             raise DevtoolError(
-                "lldb-server does not support mode %s "
-                "(ATTACH is handled client-side with 'process attach')" % mode)
+                "lldb-server only supports MULTI mode; "
+                "ATTACH is handled client-side with 'process attach': %s" % mode)
         return "\"/bin/sh -c '" + cmd + "'\""
 
     def _target_kill_cmd(self):
diff --git a/scripts/lib/devtool/ide_plugins/ide_code.py b/scripts/lib/devtool/ide_plugins/ide_code.py
index 9faba3f2d1..5b66c56e35 100644
--- a/scripts/lib/devtool/ide_plugins/ide_code.py
+++ b/scripts/lib/devtool/ide_plugins/ide_code.py
@@ -609,6 +609,11 @@  class IdeVSCode(IdeBase):
                     # ONCE / ATTACH: gdbserver runs in the foreground for the
                     # whole session, so VSCode needs isBackground + a pattern
                     # matcher to avoid waiting for the task to exit.
+                    if server_mode in (DebuggerServerModes.ONCE,
+                                       DebuggerServerModes.ATTACH):
+                        ends_pattern = cross_debug_config.get_debug_server_ready_marker_pattern()
+                    else:
+                        ends_pattern = "."
                     new_task = {
                         "label": cross_debug_config.id_pretty_mode(server_mode),
                         "type": "shell",
@@ -628,7 +633,7 @@  class IdeVSCode(IdeBase):
                                 "background": {
                                     "activeOnStart": True,
                                     "beginsPattern": ".",
-                                    "endsPattern": ".",
+                                    "endsPattern": ends_pattern,
                                 }
                             }
                         ]