* [PATCH v2 1/8] devtool: ide-sdk: wait for lldb-server readiness
2026-08-09 9:35 [PATCH v2 0/8] oe-selftest: devtool ide-sdk: fix lldb connect race in _lldb_server_debugging_once AdrianF
@ 2026-08-09 9:35 ` AdrianF
2026-08-09 9:35 ` [PATCH v2 2/8] oe-selftest: devtool " AdrianF
` (6 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: AdrianF @ 2026-08-09 9:35 UTC (permalink / raw)
To: openembedded-core; +Cc: Adrian Freihofer
From: Adrian Freihofer <adrian.freihofer@siemens.com>
Start lldb-server in the target background, record its PID, and wait for
the listener before the generated MULTI pre-launch task returns. This
ensures a client can connect only after lldb-server is accepting
connections and preserves timeout cleanup.
Move reusable TCP listener checks and bounded wait generation into
DebuggerCrossConfig to make it reusable for GDB as well.
Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com>
---
scripts/lib/devtool/ide_plugins/__init__.py | 26 ++++++++++++++++-----
1 file changed, 20 insertions(+), 6 deletions(-)
diff --git a/scripts/lib/devtool/ide_plugins/__init__.py b/scripts/lib/devtool/ide_plugins/__init__.py
index d50ba7bc65..deb6050907 100644
--- a/scripts/lib/devtool/ide_plugins/__init__.py
+++ b/scripts/lib/devtool/ide_plugins/__init__.py
@@ -97,6 +97,20 @@ class DebuggerCrossConfig:
modes.append(DebuggerServerModes.ATTACH)
return modes
+ def _target_tcp_port_check_cmd(self):
+ hex_port = "%04X" % self.debug_server_port
+ return "grep -q :%s /proc/net/tcp /proc/net/tcp6 2>/dev/null" % hex_port
+
+ def _target_wait_for_tcp_port_cmd(self, pid_var=None):
+ cleanup = ""
+ if pid_var:
+ cleanup = "kill \\$_%s 2>/dev/null; " % pid_var
+ return (
+ "_w=0; while ! %s; do _w=\\$((_w+1)); [ \\$_w -lt 100 ] || { "
+ "%secho %s did not start on port %s >&2; exit 1; }; sleep 0.1; done;"
+ % (self._target_tcp_port_check_cmd(), cleanup, self.DEBUG_SERVER_NAME,
+ self.debug_server_port))
+
def initialize(self):
"""Called after construction to generate any required config files."""
pass
@@ -197,6 +211,7 @@ class LldbServerConfig(DebuggerCrossConfig):
The ATTACH mode is not supported because lldb-server platform does not take a
PID argument; attaching is done client-side via 'process attach'.
"""
+ DEBUG_SERVER_NAME = "lldb-server"
def __init__(self, image_recipe, modified_recipe, binary,
default_mode=DebuggerServerModes.MULTI):
@@ -224,18 +239,17 @@ class LldbServerConfig(DebuggerCrossConfig):
cmd = "cd /tmp && %s platform --one-shot --server --listen *:%s" % (
lldb_server, self.debug_server_port)
elif mode == DebuggerServerModes.MULTI:
- hex_port = "%04X" % self.debug_server_port
pid_file = self._lldb_server_pid_file(mode)
tmp_dir = self._lldb_server_tmp_dir(mode)
log_file = self._lldb_server_log_file(mode)
- cmd = "grep -q :%s /proc/net/tcp /proc/net/tcp6 2>/dev/null && exit 0; " % hex_port
+ cmd = self._target_tcp_port_check_cmd() + " && exit 0; "
cmd += "mkdir -p %s; " % tmp_dir
cmd += "cd %s; " % tmp_dir
- cmd += "%s platform --server --listen *:%s > %s 2>&1 & " % (
+ cmd += "%s platform --server --listen *:%s > %s 2>&1 & _lldb_server_pid=\\$!; " % (
lldb_server, self.debug_server_port, log_file)
- cmd += "echo \\$! > %s; " % pid_file
- cmd += "_w=0; while ! grep -q :%s /proc/net/tcp /proc/net/tcp6 2>/dev/null; " % hex_port
- cmd += "do _w=\\$((_w+1)); [ \\$_w -lt 100 ] || { echo lldb-server did not start on port %s >&2; exit 1; }; sleep 0.1; done;" % self.debug_server_port
+ cmd += "echo \\$_lldb_server_pid > %s; " % pid_file
+ cmd += self._target_wait_for_tcp_port_cmd(
+ "lldb_server_pid")
else:
raise DevtoolError(
"lldb-server does not support mode %s "
--
2.55.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH v2 2/8] oe-selftest: devtool ide-sdk: wait for lldb-server readiness
2026-08-09 9:35 [PATCH v2 0/8] oe-selftest: devtool ide-sdk: fix lldb connect race in _lldb_server_debugging_once AdrianF
2026-08-09 9:35 ` [PATCH v2 1/8] devtool: ide-sdk: wait for lldb-server readiness AdrianF
@ 2026-08-09 9:35 ` AdrianF
2026-08-09 9:35 ` [PATCH v2 3/8] devtool: ide-sdk: synchronize debugger server readiness AdrianF
` (5 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: AdrianF @ 2026-08-09 9:35 UTC (permalink / raw)
To: openembedded-core; +Cc: Adrian Freihofer
From: Adrian Freihofer <adrian.freihofer@siemens.com>
The generated MULTI preLaunchTask starts lldb-server on the target and waits
for its TCP port to appear before its SSH command completes. The selftest
bypassed that contract by starting SSH in the background, sleeping for one
second, and only checking that lldb-server appeared in ps. On a loaded
autobuilder this allowed lldb --batch to connect before lldb-server had bound
its listening socket.
Run the generated preLaunchTask SSH command synchronously before lldb --batch.
This follows VS Code's foreground-task behavior and leaves listener readiness
and timeout handling in _target_start_cmd(), where the server is started.
Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com>
---
meta/lib/oeqa/selftest/cases/devtool.py | 78 ++++++++++++-------------
1 file changed, 36 insertions(+), 42 deletions(-)
diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py
index 7145755fed..d84a18e8b6 100644
--- a/meta/lib/oeqa/selftest/cases/devtool.py
+++ b/meta/lib/oeqa/selftest/cases/devtool.py
@@ -4175,8 +4175,6 @@ class DevtoolIdeSdkTests(DevtoolBase):
(see _lldb_debug_cpp_example_batch_commands), and checks that the
expected magic string and variable values are visible.
"""
- sshargs = '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'
-
with open(os.path.join(tempdir, '.vscode', 'launch.json')) as f:
launch_d = json.load(f)
with open(os.path.join(tempdir, '.vscode', 'tasks.json')) as f:
@@ -4208,7 +4206,9 @@ class DevtoolIdeSdkTests(DevtoolBase):
# to SSH via subprocess without an intermediate shell.
ssh_cmd[-1] = ssh_cmd[-1][1:-1].replace('\\$', '$')
- # Extract connection details from initCommands
+ # The generated foreground preLaunchTask does not complete until its
+ # target-side command has observed lldb-server listening. Run it
+ # synchronously so this selftest follows the same ordering as VS Code.
init_commands = lldb_config["initCommands"]
connect_cmd = next((c for c in init_commands if "platform connect" in c), None)
self.assertIsNotNone(connect_cmd, "initCommands should contain a platform connect command")
@@ -4218,47 +4218,41 @@ class DevtoolIdeSdkTests(DevtoolBase):
lldb_binary = os.path.join(lldb_native_sysroot, 'usr', 'bin', 'lldb')
self.assertExists(lldb_binary, "lldb binary should exist in lldb-native sysroot")
- with RunCmdBackground(ssh_cmd, output_log=self._cmd_logger):
- time.sleep(1)
+ self.logger.debug("Starting lldb-server via SSH: %s", " ".join(ssh_cmd))
+ runCmd(ssh_cmd, output_log=self._cmd_logger)
- # Verify lldb-server is running on the target
- r = runCmd('ssh %s root@%s ps' % (sshargs, qemu.ip),
- output_log=self._cmd_logger)
- self.assertIn("lldb-server", r.output,
- "lldb-server should be running on target")
+ # Run lldb --batch: connect to platform, create target with remote-file,
+ # set a source-level breakpoint, and run.
+ # targetCreateCommands replaces the "program" field; each entry is
+ # passed as a separate -o command in batch mode.
+ target_create_commands = lldb_config.get("targetCreateCommands", [])
+ source_map = lldb_config.get("sourceMap", {})
- # Run lldb --batch: connect to platform, create target with remote-file,
- # set a source-level breakpoint, and run.
- # targetCreateCommands replaces the "program" field; each entry is
- # passed as a separate -o command in batch mode.
- target_create_commands = lldb_config.get("targetCreateCommands", [])
- source_map = lldb_config.get("sourceMap", {})
-
- lldb_batch = [lldb_binary, "--batch"]
- for cmd in init_commands:
- lldb_batch += ["-o", cmd]
- for cmd in target_create_commands:
- lldb_batch += ["-o", cmd]
- if source_map:
- # "settings set target.source-map" replaces the *entire*
- # mapping list rather than appending to it. Issuing one
- # "-o settings set target.source-map ..." per entry (as done
- # previously) silently discards all but the last mapping, so
- # LLDB ends up resolving source files (and verifying their
- # DWARF MD5 checksum) against the wrong location, e.g. a
- # stale rootfs-dbg copy of a devtool-modified recipe's own
- # sources instead of the freshly edited workspace srctree.
- # All pairs must therefore be set together in a single
- # command, exactly like CodeLLDB itself does.
- source_map_args = []
- for k, v in source_map.items():
- v_resolved = v.replace("${workspaceFolder}", tempdir)
- source_map_args += [k, v_resolved]
- lldb_batch += ["-o", "settings set target.source-map %s" % " ".join(source_map_args)]
- lldb_batch += self._lldb_debug_cpp_example_batch_commands(tempdir)
- r = runCmd(lldb_batch, output_log=self._cmd_logger)
- self.assertEqual(r.status, 0, "lldb batch session failed: %s" % r.output)
- self._lldb_debug_cpp_example_check(r.output, magic_string)
+ lldb_batch = [lldb_binary, "--batch"]
+ for cmd in init_commands:
+ lldb_batch += ["-o", cmd]
+ for cmd in target_create_commands:
+ lldb_batch += ["-o", cmd]
+ if source_map:
+ # "settings set target.source-map" replaces the *entire*
+ # mapping list rather than appending to it. Issuing one
+ # "-o settings set target.source-map ..." per entry (as done
+ # previously) silently discards all but the last mapping, so
+ # LLDB ends up resolving source files (and verifying their
+ # DWARF MD5 checksum) against the wrong location, e.g. a
+ # stale rootfs-dbg copy of a devtool-modified recipe's own
+ # sources instead of the freshly edited workspace srctree.
+ # All pairs must therefore be set together in a single
+ # command, exactly like CodeLLDB itself does.
+ source_map_args = []
+ for k, v in source_map.items():
+ v_resolved = v.replace("${workspaceFolder}", tempdir)
+ source_map_args += [k, v_resolved]
+ lldb_batch += ["-o", "settings set target.source-map %s" % " ".join(source_map_args)]
+ lldb_batch += self._lldb_debug_cpp_example_batch_commands(tempdir)
+ r = runCmd(lldb_batch, output_log=self._cmd_logger)
+ self.assertEqual(r.status, 0, "lldb batch session failed: %s" % r.output)
+ self._lldb_debug_cpp_example_check(r.output, magic_string)
@OETestTag("runqemu")
def test_devtool_ide_sdk_code_cmake_clang(self):
--
2.55.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH v2 3/8] devtool: ide-sdk: synchronize debugger server readiness
2026-08-09 9:35 [PATCH v2 0/8] oe-selftest: devtool ide-sdk: fix lldb connect race in _lldb_server_debugging_once AdrianF
2026-08-09 9:35 ` [PATCH v2 1/8] devtool: ide-sdk: wait for lldb-server readiness AdrianF
2026-08-09 9:35 ` [PATCH v2 2/8] oe-selftest: devtool " AdrianF
@ 2026-08-09 9:35 ` AdrianF
2026-08-09 9:35 ` [PATCH v2 4/8] oe-selftest: devtool ide-sdk: wait for gdbserver readiness AdrianF
` (4 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: AdrianF @ 2026-08-09 9:35 UTC (permalink / raw)
To: openembedded-core; +Cc: Adrian Freihofer
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 --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,
}
}
]
--
2.55.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH v2 4/8] oe-selftest: devtool ide-sdk: wait for gdbserver readiness
2026-08-09 9:35 [PATCH v2 0/8] oe-selftest: devtool ide-sdk: fix lldb connect race in _lldb_server_debugging_once AdrianF
` (2 preceding siblings ...)
2026-08-09 9:35 ` [PATCH v2 3/8] devtool: ide-sdk: synchronize debugger server readiness AdrianF
@ 2026-08-09 9:35 ` AdrianF
2026-08-11 8:41 ` [OE-core] " Mathieu Dubois-Briand
2026-08-09 9:35 ` [PATCH v2 5/8] devtool: ide-sdk: wait for debugger server shutdown AdrianF
` (3 subsequent siblings)
7 siblings, 1 reply; 10+ messages in thread
From: AdrianF @ 2026-08-09 9:35 UTC (permalink / raw)
To: openembedded-core; +Cc: Adrian Freihofer
From: Adrian Freihofer <adrian.freihofer@siemens.com>
Replace the fixed delay and process-list probe for GDB background tasks
with a wait for the readiness marker generated in tasks.json. This
verifies the same background-task contract VS Code uses before starting
the debugger session.
Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com>
---
meta/lib/oeqa/selftest/cases/devtool.py | 30 ++++++++++++++++---------
1 file changed, 20 insertions(+), 10 deletions(-)
diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py
index 04a42b8906..11a26f03bb 100644
--- a/meta/lib/oeqa/selftest/cases/devtool.py
+++ b/meta/lib/oeqa/selftest/cases/devtool.py
@@ -2747,6 +2747,20 @@ class RunCmdBackground:
def __enter__(self):
self.cmd.run()
+ return self
+
+ def wait_for_output(self, pattern, timeout):
+ pattern = re.compile(pattern, re.MULTILINE)
+ deadline = time.monotonic() + timeout
+ while time.monotonic() < deadline:
+ output = b"".join(self.cmd._output_chunks).decode(
+ "utf-8", errors="replace")
+ if pattern.search(output):
+ return True
+ if self.cmd.process.poll() is not None:
+ break
+ time.sleep(0.1)
+ return False
def __exit__(self, exc_type, exc_val, exc_tb):
self.cmd.stop()
@@ -3514,16 +3528,12 @@ class DevtoolIdeSdkTests(DevtoolBase):
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].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
- time.sleep(1)
-
- # Verify gdbserver is running on target and listening on expected port
- result = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, 'ps'), output_log=self._cmd_logger)
- self.assertEqual(result.status, 0, "Failed to check processes on target")
- self.assertIn("gdbserver", result.output, "gdbserver should be running on target")
- _, server_port = server_addr.split(':')
- self.assertIn(server_port, result.output, f"gdbserver should be listening on port {server_port}")
+ _, server_port = server_addr.split(':')
+ with RunCmdBackground(ssh_gdbserver_cmd, output_log=self._cmd_logger) as gdbserver:
+ ready_pattern = prelaunch_task["problemMatcher"][0]["background"]["endsPattern"]
+ self.assertTrue(
+ gdbserver.wait_for_output(ready_pattern, timeout=15),
+ "gdbserver did not report readiness on port %s" % server_port)
if debug_func and debug_check_func:
# Do a gdb remote session using the once configuration
--
2.55.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* Re: [OE-core] [PATCH v2 4/8] oe-selftest: devtool ide-sdk: wait for gdbserver readiness
2026-08-09 9:35 ` [PATCH v2 4/8] oe-selftest: devtool ide-sdk: wait for gdbserver readiness AdrianF
@ 2026-08-11 8:41 ` Mathieu Dubois-Briand
0 siblings, 0 replies; 10+ messages in thread
From: Mathieu Dubois-Briand @ 2026-08-11 8:41 UTC (permalink / raw)
To: adrian.freihofer, openembedded-core
On Sun Aug 9, 2026 at 11:35 AM CEST, Adrian Freihofer via lists.openembedded.org wrote:
> From: Adrian Freihofer <adrian.freihofer@siemens.com>
>
> Replace the fixed delay and process-list probe for GDB background tasks
> with a wait for the readiness marker generated in tasks.json. This
> verifies the same background-task contract VS Code uses before starting
> the debugger session.
>
> Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com>
> ---
Hi Adrian,
I know you already discussed the issue with Richard, just adding a
comment here to keep a trace of the issue.
Before this patch we had intermittent failures of the
devtool.DevtoolIdeSdkTests.test_devtool_ide_sdk_code_cmake and
devtool.DevtoolIdeSdkTests.test_devtool_ide_sdk_code_meson tests.
Starting with this patch, the same tests are always failing:
2026-08-11 08:17:37,903 - oe-selftest - INFO - devtool.DevtoolIdeSdkTests.test_devtool_ide_sdk_code_cmake (subunit.RemotedTestCase)
2026-08-11 08:17:37,904 - oe-selftest - INFO - ... FAIL
...
2026-08-11 08:17:37,904 - oe-selftest - INFO - testtools.testresult.real._StringException: Traceback (most recent call last):
File "/srv/pokybuild/yocto-worker/oe-selftest-debian/build/layers/openembedded-core/meta/lib/oeqa/selftest/cases/devtool.py", line 3655, in test_devtool_ide_sdk_code_cmake
self._verify_launch_json_debugging(tempdir, qemu, example_exe)
File "/srv/pokybuild/yocto-worker/oe-selftest-debian/build/layers/openembedded-core/meta/lib/oeqa/selftest/cases/devtool.py", line 3527, in _verify_launch_json_debugging
self._verify_launch_config(tempdir, config, tasks, qemu, example_exe,
File "/srv/pokybuild/yocto-worker/oe-selftest-debian/build/layers/openembedded-core/meta/lib/oeqa/selftest/cases/devtool.py", line 3598, in _verify_launch_config
self.assertTrue(
File "/usr/lib/python3.11/unittest/case.py", line 715, in assertTrue
raise self.failureException(msg)
AssertionError: False is not true : gdbserver did not report readiness on port 1234
https://autobuilder.yoctoproject.org/valkyrie/#/builders/35/builds/4529
Thanks,
Mathieu
--
Mathieu Dubois-Briand, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v2 5/8] devtool: ide-sdk: wait for debugger server shutdown
2026-08-09 9:35 [PATCH v2 0/8] oe-selftest: devtool ide-sdk: fix lldb connect race in _lldb_server_debugging_once AdrianF
` (3 preceding siblings ...)
2026-08-09 9:35 ` [PATCH v2 4/8] oe-selftest: devtool ide-sdk: wait for gdbserver readiness AdrianF
@ 2026-08-09 9:35 ` AdrianF
2026-08-09 9:35 ` [PATCH v2 6/8] oe-selftest: devtool ide-sdk: verify debugger shutdown AdrianF
` (2 subsequent siblings)
7 siblings, 0 replies; 10+ messages in thread
From: AdrianF @ 2026-08-09 9:35 UTC (permalink / raw)
To: openembedded-core; +Cc: Adrian Freihofer
From: Adrian Freihofer <adrian.freihofer@siemens.com>
Have the ide=none GDB and LLDB stop scripts wait for their recorded
server PID to exit before removing their state. This makes a successful
stop command a reliable target-side lifecycle boundary and reports a
timeout when the server cannot be stopped.
Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com>
---
scripts/lib/devtool/ide_plugins/__init__.py | 7 +++++++
scripts/lib/devtool/ide_plugins/ide_none.py | 17 ++++++++++++-----
2 files changed, 19 insertions(+), 5 deletions(-)
diff --git a/scripts/lib/devtool/ide_plugins/__init__.py b/scripts/lib/devtool/ide_plugins/__init__.py
index 6c88e0e34e..1c4288a8b5 100644
--- a/scripts/lib/devtool/ide_plugins/__init__.py
+++ b/scripts/lib/devtool/ide_plugins/__init__.py
@@ -117,6 +117,13 @@ class DebuggerCrossConfig:
% (self._target_tcp_port_check_cmd(), cleanup, self.DEBUG_SERVER_NAME,
self.debug_server_port))
+ def _target_wait_for_process_exit_cmd(self, pid_var):
+ return (
+ "_w=0; while kill -0 \\$_%s 2>/dev/null; do _w=\\$((_w+1)); "
+ "[ \\$_w -lt 100 ] || { echo %s did not stop >&2; exit 1; }; "
+ "sleep 0.1; done;"
+ % (pid_var, self.DEBUG_SERVER_NAME))
+
def initialize(self):
"""Called after construction to generate any required config files."""
pass
diff --git a/scripts/lib/devtool/ide_plugins/ide_none.py b/scripts/lib/devtool/ide_plugins/ide_none.py
index a8ddc3f39f..8e01c2fc41 100644
--- a/scripts/lib/devtool/ide_plugins/ide_none.py
+++ b/scripts/lib/devtool/ide_plugins/ide_none.py
@@ -24,9 +24,13 @@ class GdbCrossConfigNone(GdbCrossConfig):
"""Kill a gdbserver process"""
# This is the usual behavior: gdbserver is stopped on demand
if server_mode == DebuggerServerModes.MULTI:
- gdbserver_cmd_stop = "test -f %s && kill \\$(cat %s);" % (
- self._gdbserver_pid_file(server_mode), self._gdbserver_pid_file(server_mode))
- gdbserver_cmd_stop += " rm -rf %s" % self._gdbserver_tmp_dir(server_mode)
+ pid_file = self._gdbserver_pid_file(server_mode)
+ gdbserver_cmd_stop = "if test -f %s; then _gdbserver_pid=\\$(cat %s); " % (
+ pid_file, pid_file)
+ gdbserver_cmd_stop += "kill \\$_gdbserver_pid 2>/dev/null; "
+ gdbserver_cmd_stop += self._target_wait_for_process_exit_cmd(
+ "gdbserver_pid")
+ gdbserver_cmd_stop += " fi; rm -rf %s" % self._gdbserver_tmp_dir(server_mode)
# This is unexpected since gdbserver should terminate after each debug session
# Just kill all gdbserver instances to keep it simple
else:
@@ -169,8 +173,11 @@ class LldbServerConfigNone(LldbServerConfig):
if server_mode == DebuggerServerModes.MULTI:
pid_file = self._lldb_server_pid_file(server_mode)
tmp_dir = self._lldb_server_tmp_dir(server_mode)
- cmd = ("test -f %(pf)s && kill \\$(cat %(pf)s) 2>/dev/null; rm -rf %(td)s"
- % {'pf': pid_file, 'td': tmp_dir})
+ cmd = "if test -f %s; then _lldb_server_pid=\\$(cat %s); " % (
+ pid_file, pid_file)
+ cmd += "kill \\$_lldb_server_pid 2>/dev/null; "
+ cmd += self._target_wait_for_process_exit_cmd("lldb_server_pid")
+ cmd += " fi; rm -rf %s" % tmp_dir
else:
cmd = "killall lldb-server 2>/dev/null || true"
return "\"/bin/sh -c '" + cmd + "'\""
--
2.55.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH v2 6/8] oe-selftest: devtool ide-sdk: verify debugger shutdown
2026-08-09 9:35 [PATCH v2 0/8] oe-selftest: devtool ide-sdk: fix lldb connect race in _lldb_server_debugging_once AdrianF
` (4 preceding siblings ...)
2026-08-09 9:35 ` [PATCH v2 5/8] devtool: ide-sdk: wait for debugger server shutdown AdrianF
@ 2026-08-09 9:35 ` AdrianF
2026-08-09 9:35 ` [PATCH v2 7/8] oe-selftest: devtool: use QEMU target commands AdrianF
2026-08-09 9:35 ` [PATCH v2 8/8] cpp-example: prevent compiler from eliminating debugger test code AdrianF
7 siblings, 0 replies; 10+ messages in thread
From: AdrianF @ 2026-08-09 9:35 UTC (permalink / raw)
To: openembedded-core; +Cc: Adrian Freihofer
From: Adrian Freihofer <adrian.freihofer@siemens.com>
Use the QEMU target command interface to inspect the generated ide=none
server PID files and process command lines. Verify that the stop helpers
remove both the tracked process and its PID file, without relying on
broad ps output.
Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com>
---
meta/lib/oeqa/selftest/cases/devtool.py | 61 +++++++++++--------------
1 file changed, 27 insertions(+), 34 deletions(-)
diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py
index 11a26f03bb..df77c628bb 100644
--- a/meta/lib/oeqa/selftest/cases/devtool.py
+++ b/meta/lib/oeqa/selftest/cases/devtool.py
@@ -3112,7 +3112,6 @@ class DevtoolIdeSdkTests(DevtoolBase):
numbers after recompiling, to prove the breakpoints resolve via the
freshly rebuilt debug info.
"""
- sshargs = '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'
gdbserver_script = os.path.join(self._workspace_scripts_dir(
recipe_name), 'gdbserver_1234_usr-bin-' + example_exe + '_multi')
gdb_script = os.path.join(self._workspace_scripts_dir(
@@ -3122,19 +3121,18 @@ class DevtoolIdeSdkTests(DevtoolBase):
r = runCmd(gdbserver_script, output_log=self._cmd_logger)
self.assertEqual(r.status, 0)
- # Check there is a gdbserver running
- r = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, 'ps'), output_log=self._cmd_logger)
- self.assertEqual(r.status, 0)
- self.assertIn("gdbserver ", r.output)
+ pid_file = '/tmp/gdbserver_1234_usr-bin-%s_multi/gdbserver.pid' % example_exe
+ status, output = qemu.run('cat %s' % pid_file)
+ self.assertEqual(status, 0)
+ gdbserver_pid = output.strip()
+ self.assertRegex(gdbserver_pid, r'^\d+$')
- # Check the pid file is correct
- test_cmd = "'cat /proc/$(cat /tmp/gdbserver_1234_usr-bin-" + \
- example_exe + "_multi/gdbserver.pid)/cmdline'"
- r = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, test_cmd), output_log=self._cmd_logger)
- self.assertEqual(r.status, 0)
- self.assertIn("gdbserver", r.output)
- self.assertIn("--multi", r.output)
- self.assertIn("1234", r.output)
+ # Check the pid file identifies the expected gdbserver process
+ status, output = qemu.run('cat /proc/%s/cmdline' % gdbserver_pid)
+ self.assertEqual(status, 0)
+ self.assertIn("gdbserver", output)
+ self.assertIn("--multi", output)
+ self.assertIn("1234", output)
# Test remote debugging works
gdb_batch_cmd = " --batch " + self._gdb_debug_cpp_example(
@@ -3151,10 +3149,10 @@ class DevtoolIdeSdkTests(DevtoolBase):
r = runCmd(gdbserver_script + ' stop', output_log=self._cmd_logger)
self.assertEqual(r.status, 0)
- # Check there is no gdbserver running
- r = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, 'ps'), output_log=self._cmd_logger)
- self.assertEqual(r.status, 0)
- self.assertNotIn("gdbserver ", r.output)
+ # The stop script waits for its recorded PID before it succeeds.
+ status, _ = qemu.run('test ! -d /proc/%s && test ! -e %s' % (
+ gdbserver_pid, pid_file))
+ self.assertEqual(status, 0)
def _verify_cmake_preset(self, tempdir):
"""Verify the generated cmake preset works as expected
@@ -4396,7 +4394,6 @@ class DevtoolIdeSdkTests(DevtoolBase):
lldbinit source map / debug-file-search-paths setup for the library's
own debug info specifically.
"""
- sshargs = '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'
scripts_dir = self._workspace_scripts_dir(recipe_name)
binary_pretty = 'usr-bin-' + example_exe
lldb_server_script = os.path.join(
@@ -4410,19 +4407,16 @@ class DevtoolIdeSdkTests(DevtoolBase):
r = runCmd(lldb_server_script, output_log=self._cmd_logger)
self.assertEqual(r.status, 0)
- # Verify lldb-server is running on the target
- r = runCmd('ssh %s root@%s ps' % (sshargs, qemu.ip),
- output_log=self._cmd_logger)
- self.assertEqual(r.status, 0)
- self.assertIn('lldb-server', r.output)
+ pid_file = '/tmp/lldb_server_1234_%s_multi/lldb_server.pid' % binary_pretty
+ status, output = qemu.run('cat %s' % pid_file)
+ self.assertEqual(status, 0)
+ lldb_server_pid = output.strip()
+ self.assertRegex(lldb_server_pid, r'^\d+$')
# Verify the pid file points at the running lldb-server process
- pid_file = '/tmp/lldb_server_1234_%s_multi/lldb_server.pid' % binary_pretty
- test_cmd = "'cat /proc/$(cat %s)/cmdline'" % pid_file
- r = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, test_cmd),
- output_log=self._cmd_logger)
- self.assertEqual(r.status, 0)
- self.assertIn('lldb-server', r.output)
+ status, output = qemu.run('cat /proc/%s/cmdline' % lldb_server_pid)
+ self.assertEqual(status, 0)
+ self.assertIn('lldb-server', output)
# Run an lldb batch session covering the executable, library and
# header breakpoints, then continue to completion
@@ -4437,11 +4431,10 @@ class DevtoolIdeSdkTests(DevtoolBase):
r = runCmd(lldb_server_script + ' stop', output_log=self._cmd_logger)
self.assertEqual(r.status, 0)
- # Verify lldb-server is no longer running
- r = runCmd('ssh %s root@%s ps' % (sshargs, qemu.ip),
- output_log=self._cmd_logger)
- self.assertEqual(r.status, 0)
- self.assertNotIn('lldb-server', r.output)
+ # The stop script waits for its recorded PID before it succeeds.
+ status, _ = qemu.run('test ! -d /proc/%s && test ! -e %s' % (
+ lldb_server_pid, pid_file))
+ self.assertEqual(status, 0)
@OETestTag("runqemu")
def test_devtool_ide_sdk_none_cmake_clang(self):
--
2.55.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH v2 7/8] oe-selftest: devtool: use QEMU target commands
2026-08-09 9:35 [PATCH v2 0/8] oe-selftest: devtool ide-sdk: fix lldb connect race in _lldb_server_debugging_once AdrianF
` (5 preceding siblings ...)
2026-08-09 9:35 ` [PATCH v2 6/8] oe-selftest: devtool ide-sdk: verify debugger shutdown AdrianF
@ 2026-08-09 9:35 ` AdrianF
2026-08-09 9:35 ` [PATCH v2 8/8] cpp-example: prevent compiler from eliminating debugger test code AdrianF
7 siblings, 0 replies; 10+ messages in thread
From: AdrianF @ 2026-08-09 9:35 UTC (permalink / raw)
To: openembedded-core; +Cc: Adrian Freihofer
From: Adrian Freihofer <adrian.freihofer@siemens.com>
Use the QEMU target API for target-side verification in devtool selftests
instead of invoking SSH directly. Copy the generated file list through
the target API before comparing deployed file metadata.
Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com>
---
meta/lib/oeqa/selftest/cases/devtool.py | 29 ++++++++++++++-----------
1 file changed, 16 insertions(+), 13 deletions(-)
diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py
index df77c628bb..06be7ef2df 100644
--- a/meta/lib/oeqa/selftest/cases/devtool.py
+++ b/meta/lib/oeqa/selftest/cases/devtool.py
@@ -2012,8 +2012,8 @@ class DevtoolDeployTargetTests(DevtoolBase):
self.logger.debug(deploy_cmd)
result = runCmd(deploy_cmd)
# Run a test command to see if it was installed properly
- sshargs = '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'
- result = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, testcommand))
+ status, _ = qemu.run(testcommand)
+ self.assertEqual(status, 0)
# Check if it deployed all of the files with the right ownership/perms
# First look on the host - need to do this under pseudo to get the correct ownership/perms
bb_vars = get_bb_vars(['D', 'FAKEROOTENV', 'FAKEROOTCMD', 'PATH'], testrecipe)
@@ -2032,15 +2032,21 @@ class DevtoolDeployTargetTests(DevtoolBase):
for line in filelist1:
splitline = line.split()
f.write(splitline[-1] + '\n')
- result = runCmd('cat %s | ssh -q %s root@%s \'xargs ls -l\'' % (tmpfilelist, sshargs, qemu.ip))
- filelist2 = self._process_ls_output(result.output)
+ remotefilelist = '/tmp/%s' % os.path.basename(tmpfilelist)
+ status, _ = qemu.copy_to(tmpfilelist, remotefilelist)
+ self.assertEqual(status, 0)
+ status, output = qemu.run(
+ 'xargs ls -l < %s; status=$?; rm -f %s; exit $status' % (
+ remotefilelist, remotefilelist))
+ self.assertEqual(status, 0)
+ filelist2 = self._process_ls_output(output)
filelist1.sort(key=lambda item: item.split()[-1])
filelist2.sort(key=lambda item: item.split()[-1])
self.assertEqual(filelist1, filelist2)
# Test undeploy-target
result = runCmd('devtool undeploy-target -c %s root@%s' % (testrecipe, qemu.ip))
- result = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, testcommand), ignore_status=True)
- self.assertNotEqual(result, 0, 'undeploy-target did not remove command as it should have')
+ status, _ = qemu.run(testcommand)
+ self.assertNotEqual(status, 0, 'undeploy-target did not remove command as it should have')
class DevtoolBuildImageTests(DevtoolBase):
@@ -3510,13 +3516,10 @@ class DevtoolIdeSdkTests(DevtoolBase):
self.assertEqual(task_command, "ssh", f"Task '{prelaunch_task_name}' should use ssh command")
self.assertTrue(len(task_args) >= 2, f"Task '{prelaunch_task_name}' should have at least 2 args (ssh options and remote command)")
- sshargs = '-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'
- result = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, 'test -x /usr/bin/gdbserver'),
- output_log=self._cmd_logger)
- self.assertEqual(result.status, 0, "gdbserver should be installed on target")
- result = runCmd('ssh %s root@%s %s' % (sshargs, qemu.ip, 'test -x ' + os.path.join('/usr/bin', example_exe)),
- output_log=self._cmd_logger)
- self.assertEqual(result.status, 0, "Example binary should be installed on target")
+ status, _ = qemu.run('test -x /usr/bin/gdbserver')
+ self.assertEqual(status, 0, "gdbserver should be installed on target")
+ status, _ = qemu.run('test -x ' + os.path.join('/usr/bin', example_exe))
+ self.assertEqual(status, 0, "Example binary should be installed on target")
# Start gdbserver on target using the task command (keep the ssh connection open while debugging)
ssh_gdbserver_cmd = [task_command] + task_args
--
2.55.0
^ permalink raw reply related [flat|nested] 10+ messages in thread* [PATCH v2 8/8] cpp-example: prevent compiler from eliminating debugger test code
2026-08-09 9:35 [PATCH v2 0/8] oe-selftest: devtool ide-sdk: fix lldb connect race in _lldb_server_debugging_once AdrianF
` (6 preceding siblings ...)
2026-08-09 9:35 ` [PATCH v2 7/8] oe-selftest: devtool: use QEMU target commands AdrianF
@ 2026-08-09 9:35 ` AdrianF
7 siblings, 0 replies; 10+ messages in thread
From: AdrianF @ 2026-08-09 9:35 UTC (permalink / raw)
To: openembedded-core; +Cc: Adrian Freihofer
From: Adrian Freihofer <adrian.freihofer@siemens.com>
Be more paranoid about compiler optimizations that can eliminate code
that is only used for debugger inspection.
scale_number() in cpp-example-lib.hpp: use 'volatile int scaled' to
prevent the compiler from inlining the function body away entirely,
which left no out-of-line address for a breakpoint.
cpp-example.cpp: initialize the std::vector from volatile variables so
the compiler cannot constant-fold the values and eliminate the for-loop
body. Pass numbers[0]+numbers[1]+numbers[2] (= 6) as the scale_number
argument to keep the vector live at the call site (exe_break_line=63);
the test assertion '$4 = 6' is unchanged since 1+2+3 == 6.
Update the test accordingly: exe_break_line moves from 56 to 63, the
LINE_SHIFT anchor changes to the volatile declaration line, and the
list-output assertion matches '{n1, n2, n3}'.
Note: the test was originally written to also check that the compiler
uses e.g. -O0 to avoid eliminating e.g. the vector, but that should
probably be tested separately. Changing this ide-sdk test to use
volatile variables is a more robust way to ensure the vector is not
eliminated, regardless of compiler flags.
Signed-off-by: Adrian Freihofer <adrian.freihofer@siemens.com>
---
.../recipes-test/cpp/files/cpp-example-lib.hpp | 6 ++++--
.../recipes-test/cpp/files/cpp-example.cpp | 10 +++++-----
meta/lib/oeqa/selftest/cases/devtool.py | 17 +++++++++--------
3 files changed, 18 insertions(+), 15 deletions(-)
diff --git a/meta-selftest/recipes-test/cpp/files/cpp-example-lib.hpp b/meta-selftest/recipes-test/cpp/files/cpp-example-lib.hpp
index d1c9bca416..5af30e2a79 100644
--- a/meta-selftest/recipes-test/cpp/files/cpp-example-lib.hpp
+++ b/meta-selftest/recipes-test/cpp/files/cpp-example-lib.hpp
@@ -15,10 +15,12 @@ struct CppExample
inline static const std::string test_string = "cpp-example-lib Magic: 123456789";
/* Header-only function, to exercise breakpoint resolution against
- * header-only debug info. */
+ * header-only debug info. volatile prevents compiler optimization from
+ * eliminating the function body, ensuring a concrete code location exists
+ * for debugger breakpoints. */
inline static int scale_number(int n)
{
- int scaled = n * 7;
+ volatile int scaled = n * 7;
std::cout << "scale_number(" << n << ") = " << scaled << std::endl;
return scaled;
}
diff --git a/meta-selftest/recipes-test/cpp/files/cpp-example.cpp b/meta-selftest/recipes-test/cpp/files/cpp-example.cpp
index ad1abae257..a376419c13 100644
--- a/meta-selftest/recipes-test/cpp/files/cpp-example.cpp
+++ b/meta-selftest/recipes-test/cpp/files/cpp-example.cpp
@@ -50,17 +50,17 @@ int main(int argc, char* argv[])
sleep(1);
}
} while (endless_mode);
-
+ volatile int n1 = 1, n2 = 2, n3 = 3;
// Example: Demonstrate std::vector traversal for debugger inspection
- std::vector<int> numbers = {1, 2, 3};
+ std::vector<int> numbers = {n1, n2, n3};
std::cout << "Traversing std::vector<int> numbers:" << std::endl;
for (size_t i = 0; i < numbers.size(); ++i) {
std::cout << "numbers[" << i << "] = " << numbers[i] << std::endl;
}
- // Example: call a header-only function once, to exercise breakpoint
- // resolution against header-only debug info.
- CppExample::scale_number(6);
+ // Pass numbers elements as the argument so the compiler cannot eliminate
+ // the vector; 1+2+3 == 6, so the scale_number(n) check is unchanged.
+ CppExample::scale_number(numbers[0] + numbers[1] + numbers[2]);
return 0;
}
diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py
index 06be7ef2df..7338b85d7f 100644
--- a/meta/lib/oeqa/selftest/cases/devtool.py
+++ b/meta/lib/oeqa/selftest/cases/devtool.py
@@ -2975,8 +2975,8 @@ class DevtoolIdeSdkTests(DevtoolBase):
with open(cpp_example_cpp, 'r') as file:
cpp_code = file.read()
cpp_code = cpp_code.replace(
- " std::vector<int> numbers = {1, 2, 3};",
- extra_lines + " std::vector<int> numbers = {1, 2, 3};")
+ " volatile int n1 = 1, n2 = 2, n3 = 3;",
+ extra_lines + " volatile int n1 = 1, n2 = 2, n3 = 3;")
with open(cpp_example_cpp, 'w') as file:
file.write(cpp_code)
@@ -3016,7 +3016,7 @@ class DevtoolIdeSdkTests(DevtoolBase):
# the first _gdb_cross_debugging_multi call above.
self._gdb_cross_debugging_multi(
qemu, recipe_name, example_exe, MAGIC_STRING_NEW,
- exe_break_line=56 + LINE_SHIFT, exe_list_line=55 + LINE_SHIFT,
+ exe_break_line=63 + LINE_SHIFT, exe_list_line=55 + LINE_SHIFT,
hpp_break_line=21 + LINE_SHIFT, lib_break_line=31 + LINE_SHIFT)
def _gdb_cross(self):
@@ -3033,7 +3033,7 @@ class DevtoolIdeSdkTests(DevtoolBase):
self.assertIn("GNU gdb", r.output)
def _gdb_debug_cpp_example(self, magic_string, gdb_start_cmd="run",
- exe_break_line=56, exe_list_line=55, hpp_break_line=21,
+ exe_break_line=63, exe_list_line=55, hpp_break_line=21,
lib_break_line=31):
"""Get a series of gdb commands to debug the cpp-example-lib example"""
gdb_batch_cmd = " -ex 'break main' -ex '%s'" % gdb_start_cmd
@@ -3057,8 +3057,9 @@ class DevtoolIdeSdkTests(DevtoolBase):
# check if resolving std::vector works with python scripts
gdb_batch_cmd += " -ex 'list cpp-example.cpp:%d,%d'" % (exe_list_line, exe_list_line)
- # Break on exe_break_line (the std::cout after the declaration) so the
- # vector constructor on exe_list_line has already run when GDB stops.
+ # Break on exe_break_line (the scale_number call) so the vector on
+ # exe_list_line is both constructed and referenced; the compiler cannot
+ # eliminate the vector because its elements are passed as the argument.
# These line numbers shift after the test inserts extra lines and
# recompiles, proving the breakpoint resolves via the freshly rebuilt
# debug info rather than a stale, cached line-to-address mapping.
@@ -3095,7 +3096,7 @@ class DevtoolIdeSdkTests(DevtoolBase):
# check if resolving std::vector works with python scripts
self.assertRegex(
- gdb_output, r"%d\s+std::vector<int> numbers = \{1, 2, 3\};" % exe_list_line)
+ gdb_output, r"%d\s+std::vector<int> numbers = \{n1, n2, n3\};" % exe_list_line)
self.assertIn("$3 = std::vector of length 3, capacity 3 = {1, 2, 3}", gdb_output)
# check that a breakpoint in an inline function defined directly in
@@ -3106,7 +3107,7 @@ class DevtoolIdeSdkTests(DevtoolBase):
self.assertIn("exited normally", gdb_output)
def _gdb_cross_debugging_multi(self, qemu, recipe_name, example_exe, magic_string,
- exe_break_line=56, exe_list_line=55, hpp_break_line=21,
+ exe_break_line=63, exe_list_line=55, hpp_break_line=21,
lib_break_line=31):
"""Verify gdb-cross is working
--
2.55.0
^ permalink raw reply related [flat|nested] 10+ messages in thread