From patchwork Sun Aug 16 19:40:44 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 95466 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 12E76C5DF7E for ; Sun, 16 Aug 2026 19:41:44 +0000 (UTC) Received: from mta-64-228.siemens.flowmailer.net (mta-64-228.siemens.flowmailer.net [185.136.64.228]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.15976.1786909295039986467 for ; Sun, 16 Aug 2026 12:41:36 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=RO7ExbJm; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.64.228, mailfrom: fm-1329275-20260816194131bce9ea56e30002079a-wat99d@rts-flowmailer.siemens.com) Received: by mta-64-228.siemens.flowmailer.net with ESMTPSA id 20260816194131bce9ea56e30002079a for ; Sun, 16 Aug 2026 21:41:32 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm2; d=siemens.com; i=adrian.freihofer@siemens.com; h=Date:From:Subject:To:Message-ID:MIME-Version:Content-Type:Content-Transfer-Encoding:Cc:References:In-Reply-To; bh=5VQ2D8P3tTzFlOGYHLE+KG4twPBVxqhSmR48g+AKgM4=; b=RO7ExbJm0616lNpH3l/UtGF2t9SNdFbBFhvEZD5xPLAbkqLDhxA11coFCOz4mO8YMRr1ov zmbeZMmqNDOGxDB/6XVpa5fgR4YMN/8OXP4/Y8D+bmbTWTN6/77nrZhuLZ3VyrXkTH8Ip1vE BHi5SHr0DoNttOSHnOdf3PTZNYOW7j11zrm08t/tBlTZGuGTQ7ndu0Vugzw9hukkqDkYsO8J VlaIu+LugHuigHG+YBlcqkrDNjHbPCq20GIxQFUmgQySal0eNY9XaCJaoYCjGOtoAvC0my0Q hDZTkSpH+2wVgEY7hUQyIHWVnVV9Butf/eMxggtCzqYIquPusc5sdncQ==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 01/14] devtool: deploy-target: pass tar -m to avoid future timestamp warnings Date: Sun, 16 Aug 2026 21:40:44 +0200 Message-ID: <20260816194127.86607-2-adrian.freihofer@siemens.com> In-Reply-To: <20260816194127.86607-1-adrian.freihofer@siemens.com> References: <20260816194127.86607-1-adrian.freihofer@siemens.com> MIME-Version: 1.0 X-Flowmailer-Platform: Siemens Feedback-ID: 519:519-1329275:519-21489:flowmailer List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Sun, 16 Aug 2026 19:41:44 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/243549 From: Adrian Freihofer devtool deploy-target tars up the build output and pipes it through ssh to the target, where it's extracted with "tar xv". If the target's clock is behind the host, tar prints a "time stamp ... is ... in the future" warning per file, since the archived mtimes come from the host. Add -m (--touch) so tar sets mtimes to the current time on extraction instead of restoring them from the archive, avoiding the warning. Supported identically by GNU tar and BusyBox tar. Signed-off-by: Adrian Freihofer --- scripts/lib/devtool/deploy.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/lib/devtool/deploy.py b/scripts/lib/devtool/deploy.py index 8228d9914f..35ec0b1b4d 100644 --- a/scripts/lib/devtool/deploy.py +++ b/scripts/lib/devtool/deploy.py @@ -108,9 +108,10 @@ def _prepare_remote_script(deploy, destdir='/', verbose=False, dryrun=False, und lines.append('mkdir -p `dirname $manifest`') lines.append('mkdir -p $2') if verbose: - lines.append(' tar xv -C $2 -f - | tee $manifest') + # -m avoids "time stamp is in the future" warnings if the target's clock is behind + lines.append(' tar xvm -C $2 -f - | tee $manifest') else: - lines.append(' tar xv -C $2 -f - > $manifest') + lines.append(' tar xvm -C $2 -f - > $manifest') lines.append('sed -i "s!^./!$2!" $manifest') elif not dryrun: # Put any preserved files back From patchwork Sun Aug 16 19:40:45 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 95467 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 3EB72C5DF82 for ; Sun, 16 Aug 2026 19:41:44 +0000 (UTC) Received: from mta-65-226.siemens.flowmailer.net (mta-65-226.siemens.flowmailer.net [185.136.65.226]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.15971.1786909295039375783 for ; Sun, 16 Aug 2026 12:41:36 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=fVwU6fPj; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.65.226, mailfrom: fm-1329275-2026081619413266a8a89c810002072d-n2_urm@rts-flowmailer.siemens.com) Received: by mta-65-226.siemens.flowmailer.net with ESMTPSA id 2026081619413266a8a89c810002072d for ; Sun, 16 Aug 2026 21:41:32 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm2; d=siemens.com; i=adrian.freihofer@siemens.com; h=Date:From:Subject:To:Message-ID:MIME-Version:Content-Type:Content-Transfer-Encoding:Cc:References:In-Reply-To; bh=K565r/Jj7jbdfJ2FdeX9dSw/QECl1W/nkcvI4ZuS40s=; b=fVwU6fPjqiESlsWq73vEl4Rv74Rz7ythAXL5g1rYlQ9uXwjR3B+bczmiMFkjyHSBdIOCQ8 QRqJ0YheFtF16VjwTfTlEce+G6z5ojndNwW6uPdsW/InalJqj7LKSjKMvOIgXvk0aBwXJfbs sJEVFV4vjx5UDXnw1gWKjYC0Up/BwSIuBPriGZfUBuCEhC0a8BdN9Pe4fQF0ycYxkHkNvSy2 GU0AIf8eXdT/0xvafNNcHIHbM2L1iBNeZkpVp2UlZGsLxHMErVgJsEq/8XXu2yaSSxcYvPvh cd7v0whpjNe3a6mKgu9KEmnNoB7iyy4duP4DtNN/nm0u6njrFtHvJKFQ==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 02/14] oe-selftest: devtool deploy: use QEMU target commands Date: Sun, 16 Aug 2026 21:40:45 +0200 Message-ID: <20260816194127.86607-3-adrian.freihofer@siemens.com> In-Reply-To: <20260816194127.86607-1-adrian.freihofer@siemens.com> References: <20260816194127.86607-1-adrian.freihofer@siemens.com> MIME-Version: 1.0 X-Flowmailer-Platform: Siemens Feedback-ID: 519:519-1329275:519-21489:flowmailer List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Sun, 16 Aug 2026 19:41:44 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/243547 From: Adrian Freihofer Use the QEMU target API for target-side verification in devtool deploy selftests instead of invoking SSH directly. Copy the generated file list through the target API before comparing deployed file metadata. There are more such instances in the devtool ide-sdk selftests that could be converted to use the QEMU target API, but this will be done by follow-up patches with more changes than just this refactoring. Signed-off-by: Adrian Freihofer --- meta/lib/oeqa/selftest/cases/devtool.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py index d84a18e8b6..77965e480c 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): From patchwork Sun Aug 16 19:40:46 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 95468 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 31AD3C5B572 for ; Sun, 16 Aug 2026 19:41:44 +0000 (UTC) Received: from mta-65-225.siemens.flowmailer.net (mta-65-225.siemens.flowmailer.net [185.136.65.225]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.15973.1786909295039514126 for ; Sun, 16 Aug 2026 12:41:36 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=EbJ6oZm1; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.65.225, mailfrom: fm-1329275-202608161941320a4cf0eb5c00020703-70ojf_@rts-flowmailer.siemens.com) Received: by mta-65-225.siemens.flowmailer.net with ESMTPSA id 202608161941320a4cf0eb5c00020703 for ; Sun, 16 Aug 2026 21:41:32 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm2; d=siemens.com; i=adrian.freihofer@siemens.com; h=Date:From:Subject:To:Message-ID:MIME-Version:Content-Type:Content-Transfer-Encoding:Cc:References:In-Reply-To; bh=FGGAhY0DKd2rdOstRQmMXXqLXNy564rF3L9oEnGxayE=; b=EbJ6oZm12vOO5TDzEWcDTg3Q6RsC9Ej44ufodTkWthy4tj6MsXsAOKY1H1K6GENwyoFZOh kC3Rdie2SYsSHDXA9kgr3BcLPg90C4SCknQl3SN0qO+g8f7DqSbEzG8Iyz8+lhcnu0FmWVcs xfq380VLKbaiCA4JEvm5WlrpuutNvmu/02F5LbjTkZq5Czab+C/QLSqjUBF8I06kPson5zl+ 8tbTNgfUJxxKox2GA8E2Ahi8oO3FTdmuDRHPQfgVxf5znZ14mMbDUwQHZBv7ofeIyL/uujF8 oKUEwzKkoJKzdSV4bfJy3yrh+MhpTf5/AeA5wZBCym8ktQDY09qSsr9A==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 03/14] oe-selftest: devtool ide-sdk: Add new test classes for ide-sdk tests Date: Sun, 16 Aug 2026 21:40:46 +0200 Message-ID: <20260816194127.86607-4-adrian.freihofer@siemens.com> In-Reply-To: <20260816194127.86607-1-adrian.freihofer@siemens.com> References: <20260816194127.86607-1-adrian.freihofer@siemens.com> MIME-Version: 1.0 X-Flowmailer-Platform: Siemens Feedback-ID: 519:519-1329275:519-21489:flowmailer List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Sun, 16 Aug 2026 19:41:44 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/243548 From: Adrian Freihofer This allows to run the ide-sdk tests with different toolchains (gcc, clang) and build systems (cmake, meson) in parallel. Signed-off-by: Adrian Freihofer --- meta/lib/oeqa/selftest/cases/devtool.py | 211 +++++++++++++----------- 1 file changed, 111 insertions(+), 100 deletions(-) diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py index 77965e480c..b1539c342c 100644 --- a/meta/lib/oeqa/selftest/cases/devtool.py +++ b/meta/lib/oeqa/selftest/cases/devtool.py @@ -3005,6 +3005,105 @@ class DevtoolIdeSdkTests(DevtoolBase): exe_break_line=56 + LINE_SHIFT, exe_list_line=55 + LINE_SHIFT, hpp_break_line=21 + LINE_SHIFT, lib_break_line=31 + LINE_SHIFT) + def _verify_cmake_preset(self, tempdir): + """Verify the generated cmake preset works as expected + + Check if compiling works + Check if unit tests can be executed in qemu (not qemu-system) + """ + with open(os.path.join(tempdir, 'CMakeUserPresets.json')) as cmake_preset_j: + cmake_preset_d = json.load(cmake_preset_j) + config_presets = cmake_preset_d["configurePresets"] + self.assertEqual(len(config_presets), 1) + cmake_exe = config_presets[0]["cmakeExecutable"] + preset_name = config_presets[0]["name"] + compile_cmd = '%s --build --preset %s' % (cmake_exe, preset_name) + + # Verify the wrapper for cmake native is available + self.assertExists(cmake_exe) + + # Verify the cmake preset generated by devtool ide-sdk is available + result = runCmd('%s --list-presets' % cmake_exe, cwd=tempdir, output_log=self._cmd_logger) + self.assertIn(preset_name, result.output) + + # Verify cmake re-uses the o files compiled by bitbake + result = runCmd(compile_cmd, cwd=tempdir, output_log=self._cmd_logger) + self.assertIn("ninja: no work to do.", result.output) + + # Verify the unit tests work (in Qemu user mode) + result = runCmd('%s --target test' % compile_cmd, cwd=tempdir, output_log=self._cmd_logger) + self.assertIn("100% tests passed", result.output) + + # Verify re-building and testing works again + result = runCmd('%s --target clean' % compile_cmd, cwd=tempdir, output_log=self._cmd_logger) + self.assertIn("Cleaning", result.output) + result = runCmd(compile_cmd, cwd=tempdir, output_log=self._cmd_logger) + self.assertIn("Building", result.output) + self.assertIn("Linking", result.output) + result = runCmd('%s --target test' % compile_cmd, cwd=tempdir, output_log=self._cmd_logger) + self.assertIn("Running tests...", result.output) + self.assertIn("100% tests passed", result.output) + + return compile_cmd + + def _verify_meson_build(self, tempdir, recipe_name): + """Verify meson works as expected + + Check if compiling works + Check if unit tests can be executed in qemu (not qemu-system) + """ + meson_exe = os.path.join(self._workspace_scripts_dir(recipe_name), "meson") + self.assertExists(meson_exe) + build_dir = os.path.join(self._sources_workdir_dir(tempdir), recipe_name + "-1.0") + compile_cmd = '%s compile -C %s' % (meson_exe, build_dir) + + # Verify meson re-uses the o files compiled by bitbake + result = runCmd(compile_cmd, cwd=tempdir, output_log=self._cmd_logger) + self.assertIn("ninja: no work to do.", result.output) + + # Verify the unit tests work (in Qemu user mode) + result = runCmd('%s test -C %s' % (meson_exe, build_dir), + cwd=tempdir, output_log=self._cmd_logger) + self.assertEqual(result.status, 0) + self.assertRegex(result.output, r"Fail:\s+0") + + # Verify re-building and testing works again + result = runCmd('%s compile -C %s --clean' % (meson_exe, build_dir), + cwd=tempdir, output_log=self._cmd_logger) + self.assertIn("Cleaning...", result.output) + result = runCmd(compile_cmd, cwd=tempdir, output_log=self._cmd_logger) + self.assertIn("Linking target", result.output) + result = runCmd('%s test -C %s' % (meson_exe, build_dir), + cwd=tempdir, output_log=self._cmd_logger) + self.assertEqual(result.status, 0) + self.assertRegex(result.output, r"Fail:\s+0") + + return compile_cmd + + def _verify_service_running(self, qemu, service_name): + """Helper to verify a service is running in Qemu""" + # Use anchored regex (^name$) instead of pgrep -x because the target + # may have busybox pgrep which does not support the -x flag. + status, output = qemu.run("pgrep '^%s$'" % service_name) + self.assertEqual(status, 0, msg="%s service not running: %s" % + (service_name, output)) + self.assertTrue(output.strip().isdigit(), + f"pgrep output should be a PID integer, got: {output.strip()}") + + def _verify_conf_file(self, qemu, conf_file, owner, group): + """Helper to verify a configuration file is owned by the proper user and group""" + stat_cmd = "stat -c '%%U %%G' %s" % conf_file + status, output = qemu.run(stat_cmd) + self.assertEqual(status, 0, msg="Failed to stat %s: %s" % (conf_file, output)) + actual_owner, actual_group = output.strip().split() + self.assertEqual(actual_owner, owner, + msg="%s not owned by user %s: got %s" % (conf_file, owner, actual_owner)) + self.assertEqual(actual_group, group, + msg="%s not owned by group %s: got %s" % (conf_file, group, actual_group)) + + +class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests): + def _gdb_cross(self): """Verify gdb-cross is provided by devtool ide-sdk""" target_arch = self.td["TARGET_ARCH"] @@ -3148,102 +3247,6 @@ class DevtoolIdeSdkTests(DevtoolBase): self.assertEqual(r.status, 0) self.assertNotIn("gdbserver ", r.output) - def _verify_cmake_preset(self, tempdir): - """Verify the generated cmake preset works as expected - - Check if compiling works - Check if unit tests can be executed in qemu (not qemu-system) - """ - with open(os.path.join(tempdir, 'CMakeUserPresets.json')) as cmake_preset_j: - cmake_preset_d = json.load(cmake_preset_j) - config_presets = cmake_preset_d["configurePresets"] - self.assertEqual(len(config_presets), 1) - cmake_exe = config_presets[0]["cmakeExecutable"] - preset_name = config_presets[0]["name"] - compile_cmd = '%s --build --preset %s' % (cmake_exe, preset_name) - - # Verify the wrapper for cmake native is available - self.assertExists(cmake_exe) - - # Verify the cmake preset generated by devtool ide-sdk is available - result = runCmd('%s --list-presets' % cmake_exe, cwd=tempdir, output_log=self._cmd_logger) - self.assertIn(preset_name, result.output) - - # Verify cmake re-uses the o files compiled by bitbake - result = runCmd(compile_cmd, cwd=tempdir, output_log=self._cmd_logger) - self.assertIn("ninja: no work to do.", result.output) - - # Verify the unit tests work (in Qemu user mode) - result = runCmd('%s --target test' % compile_cmd, cwd=tempdir, output_log=self._cmd_logger) - self.assertIn("100% tests passed", result.output) - - # Verify re-building and testing works again - result = runCmd('%s --target clean' % compile_cmd, cwd=tempdir, output_log=self._cmd_logger) - self.assertIn("Cleaning", result.output) - result = runCmd(compile_cmd, cwd=tempdir, output_log=self._cmd_logger) - self.assertIn("Building", result.output) - self.assertIn("Linking", result.output) - result = runCmd('%s --target test' % compile_cmd, cwd=tempdir, output_log=self._cmd_logger) - self.assertIn("Running tests...", result.output) - self.assertIn("100% tests passed", result.output) - - return compile_cmd - - def _verify_meson_build(self, tempdir, recipe_name): - """Verify meson works as expected - - Check if compiling works - Check if unit tests can be executed in qemu (not qemu-system) - """ - meson_exe = os.path.join(self._workspace_scripts_dir(recipe_name), "meson") - self.assertExists(meson_exe) - build_dir = os.path.join(self._sources_workdir_dir(tempdir), recipe_name + "-1.0") - compile_cmd = '%s compile -C %s' % (meson_exe, build_dir) - - # Verify meson re-uses the o files compiled by bitbake - result = runCmd(compile_cmd, cwd=tempdir, output_log=self._cmd_logger) - self.assertIn("ninja: no work to do.", result.output) - - # Verify the unit tests work (in Qemu user mode) - result = runCmd('%s test -C %s' % (meson_exe, build_dir), - cwd=tempdir, output_log=self._cmd_logger) - self.assertEqual(result.status, 0) - self.assertRegex(result.output, r"Fail:\s+0") - - # Verify re-building and testing works again - result = runCmd('%s compile -C %s --clean' % (meson_exe, build_dir), - cwd=tempdir, output_log=self._cmd_logger) - self.assertIn("Cleaning...", result.output) - result = runCmd(compile_cmd, cwd=tempdir, output_log=self._cmd_logger) - self.assertIn("Linking target", result.output) - result = runCmd('%s test -C %s' % (meson_exe, build_dir), - cwd=tempdir, output_log=self._cmd_logger) - self.assertEqual(result.status, 0) - self.assertRegex(result.output, r"Fail:\s+0") - - return compile_cmd - - def _verify_service_running(self, qemu, service_name): - """Helper to verify a service is running in Qemu""" - # Use anchored regex (^name$) instead of pgrep -x because the target - # may have busybox pgrep which does not support the -x flag. - status, output = qemu.run("pgrep '^%s$'" % service_name) - self.assertEqual(status, 0, msg="%s service not running: %s" % - (service_name, output)) - self.assertTrue(output.strip().isdigit(), - f"pgrep output should be a PID integer, got: {output.strip()}") - - def _verify_conf_file(self, qemu, conf_file, owner, group): - """Helper to verify a configuration file is owned by the proper user and group""" - stat_cmd = "stat -c '%%U %%G' %s" % conf_file - status, output = qemu.run(stat_cmd) - self.assertEqual(status, 0, msg="Failed to stat %s: %s" % (conf_file, output)) - actual_owner, actual_group = output.strip().split() - self.assertEqual(actual_owner, owner, - msg="%s not owned by user %s: got %s" % (conf_file, owner, actual_owner)) - self.assertEqual(actual_group, group, - msg="%s not owned by group %s: got %s" % (conf_file, group, actual_group)) - @OETestTag("runqemu") def test_devtool_ide_sdk_none_qemu(self): """Start qemu-system and run tests for multiple recipes. ide=none is used.""" @@ -3642,6 +3645,8 @@ class DevtoolIdeSdkTests(DevtoolBase): # Verify deployment and remote debugging works self._verify_launch_json_debugging(tempdir, qemu, example_exe) +class DevtoolIdeSdkKernelTests(DevtoolIdeSdkTests): + @OETestTag("runqemu") def test_devtool_ide_sdk_code_kernel_module(self): """Verify a kernel module recipe works with ide=code mode @@ -3857,6 +3862,8 @@ class DevtoolIdeSdkTests(DevtoolBase): self.assertIn(MAGIC_STRING_NEW, output, 'New magic string not found in sysfs after rebuild: %s' % output) +class DevtoolIdeSdkSharedTests(DevtoolIdeSdkTests): + def test_devtool_ide_sdk_shared_sysroots(self): """Verify the shared sysroot SDK""" @@ -3918,6 +3925,8 @@ class DevtoolIdeSdkTests(DevtoolBase): runCmdEnv('meson setup %s' % tempdir_meson, cwd=cpp_example_src, output_log=self._cmd_logger) runCmdEnv('meson compile', cwd=tempdir_meson, output_log=self._cmd_logger) +class DevtoolIdeSdkClangTests(DevtoolIdeSdkTests): + def _verify_launch_json_lldb(self, tempdir): """Verify the launch.json file contains valid CodeLLDB (type: lldb) configurations.""" launch_json_path = os.path.join(tempdir, '.vscode', 'launch.json') @@ -4261,7 +4270,7 @@ class DevtoolIdeSdkTests(DevtoolBase): self._lldb_debug_cpp_example_check(r.output, magic_string) @OETestTag("runqemu") - def test_devtool_ide_sdk_code_cmake_clang(self): + def test_devtool_ide_sdk_code_cmake(self): """Verify a cmake recipe built with clang works with ide=code (CodeLLDB debugging). This test uses the cmake-example-clang recipe which is a cmake-example variant @@ -4316,7 +4325,7 @@ class DevtoolIdeSdkTests(DevtoolBase): tempdir, qemu, magic_string)) @OETestTag("runqemu") - def test_devtool_ide_sdk_code_meson_clang(self): + def test_devtool_ide_sdk_code_meson(self): """Verify a meson recipe built with clang works with ide=code (CodeLLDB debugging). This is the meson/ninja counterpart of test_devtool_ide_sdk_code_cmake_clang. @@ -4439,7 +4448,7 @@ class DevtoolIdeSdkTests(DevtoolBase): self.assertNotIn('lldb-server', r.output) @OETestTag("runqemu") - def test_devtool_ide_sdk_none_cmake_clang(self): + def test_devtool_ide_sdk_none_cmake(self): """Verify ide=none generates correct LLDB scripts for a clang cmake recipe. Uses cmake-example-clang (TOOLCHAIN = "clang") which is built with the @@ -4502,7 +4511,7 @@ class DevtoolIdeSdkTests(DevtoolBase): tempdir, qemu, recipe_name, example_exe, magic_string)) @OETestTag("runqemu") - def test_devtool_ide_sdk_none_meson_clang(self): + def test_devtool_ide_sdk_none_meson(self): """Verify ide=none generates correct LLDB scripts for a clang meson recipe. This is the meson/ninja counterpart of test_devtool_ide_sdk_none_cmake_clang. @@ -4570,6 +4579,8 @@ class DevtoolIdeSdkTests(DevtoolBase): lambda magic_string: self._lldb_none_debugging_multi( tempdir, qemu, recipe_name, example_exe, magic_string)) +class DevtoolIdeSdkMiscTests(DevtoolIdeSdkTests): + def test_devtool_ide_sdk_plugins(self): """Test that devtool ide-sdk can use plugins from other layers.""" From patchwork Sun Aug 16 19:40:47 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 95465 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id DFEEFC5DF6D for ; Sun, 16 Aug 2026 19:41:43 +0000 (UTC) Received: from mta-64-225.siemens.flowmailer.net (mta-64-225.siemens.flowmailer.net [185.136.64.225]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.15970.1786909295039130617 for ; Sun, 16 Aug 2026 12:41:36 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=Beup+TnM; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.64.225, mailfrom: fm-1329275-2026081619413229f0ceef9c00020755-xthelo@rts-flowmailer.siemens.com) Received: by mta-64-225.siemens.flowmailer.net with ESMTPSA id 2026081619413229f0ceef9c00020755 for ; Sun, 16 Aug 2026 21:41:32 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm2; d=siemens.com; i=adrian.freihofer@siemens.com; h=Date:From:Subject:To:Message-ID:MIME-Version:Content-Type:Content-Transfer-Encoding:Cc:References:In-Reply-To; bh=G7LVVwYWMZiWTFPbGoHBnWd++oOlopoo8Yx8tzuzJps=; b=Beup+TnME56dLy1IuUY357Rd4Jlg6pDGWvU2UGtrfaDSHzv6/jB7JP1QgNGUvvcuVbIESJ Ue8Zcz6u/0+rzvq/Eyzn5qE66sonk7cwN5lL6uPNZCrWdWHSgUNMk1aprHq0L00nb74huYb/ Nx38LbOkWXMROi0/p2gE3R87ZMLj1SncovOrU/yfhCDU1kL+bqdvOj8JeA7ETWtRya5pLq2i Bgf63t1jr+UA3Jjk47jdXjzO41RHOVxYzowVXE9NanCcdOZULVI492v9L6Iga/RegXxOIdKh cKvbrK4GeE7NEhXS4zvFKiIEAee0NojJn7YWqsUnoGIKwKLgo3IpNuwA==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 04/14] devtool: ide-sdk: fix meson IntelliSense for gcc toolchain Date: Sun, 16 Aug 2026 21:40:47 +0200 Message-ID: <20260816194127.86607-5-adrian.freihofer@siemens.com> In-Reply-To: <20260816194127.86607-1-adrian.freihofer@siemens.com> References: <20260816194127.86607-1-adrian.freihofer@siemens.com> MIME-Version: 1.0 X-Flowmailer-Platform: Siemens Feedback-ID: 519:519-1329275:519-21489:flowmailer List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Sun, 16 Aug 2026 19:41:43 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/243551 From: Adrian Freihofer The configurationProvider = "mesonbuild.mesonbuild" approach in c_cpp_properties.json worked for meson recipes built with clang (the __gen_meson_absolute_cross_file workaround absolutized the clang binary path in an extra cross-file so the mesonbuild extension could find it), but failed for the gcc toolchain: the extension could not resolve the bare compiler name in the meson.cross file and silently fell back to the host compiler for IntelliSense. The sysroot was also absent, so built-in headers were resolved against the host system. Replace the extension-based approach with explicit settings that work for both gcc and clang toolchains: - compilerPath: absolute path to the cross CXX binary in staging_bindir_toolchain, so cpptools finds the right compiler - compilerArgs: ["--sysroot="], so built-in includes are resolved against the target sysroot headers - compileCommands: path to compile_commands.json in the meson build directory B, which meson/ninja generates automatically with the exact per-file flags (include paths, defines, -march= etc.) - intelliSenseMode: derived from TARGET_ARCH and TOOLCHAIN (linux-gcc-arm64 / linux-clang-x64, etc.) With compileCommands pointing at the real compilation database, the mesonbuild extension is no longer needed for IntelliSense, so the __gen_meson_absolute_cross_file workaround is also removed. Signed-off-by: Adrian Freihofer --- scripts/lib/devtool/ide_plugins/ide_code.py | 66 +++++++-------------- 1 file changed, 23 insertions(+), 43 deletions(-) diff --git a/scripts/lib/devtool/ide_plugins/ide_code.py b/scripts/lib/devtool/ide_plugins/ide_code.py index 9faba3f2d1..d190858d44 100644 --- a/scripts/lib/devtool/ide_plugins/ide_code.py +++ b/scripts/lib/devtool/ide_plugins/ide_code.py @@ -147,45 +147,6 @@ class IdeVSCode(IdeBase): def dot_code_dir(self, modified_recipe): return os.path.join(modified_recipe.srctree, '.vscode') - def __gen_meson_absolute_cross_file(self, modified_recipe): - """Generate an extra cross file overriding c/cpp binaries with absolute paths. - - The recipe's generated meson.cross references the toolchain binaries - (e.g. "aarch64-poky-linux-clang++") by bare name, relying on PATH - being set up by the meson wrapper script at build time. Meson stores - that command exactly as configured (it never resolves it to an - absolute path itself), so it ends up unresolved in - compile_commands.json and meson-info/intro-compilers.json. cpptools - (via the mesonbuild extension) resolves compilerPath using its own - process PATH, which does not include the toolchain directory, and - silently falls back to a host compiler for IntelliSense. - - Real builds are unaffected since the wrapper script sets up PATH, so - the recipe's meson.cross is left untouched. Instead, an extra - --cross-file is layered on top with only the c/cpp [binaries] - entries absolutized, reusing the exact same flags as CC/CXX to avoid - any behavior drift. Meson merges multiple machine files, with later - files overriding matching keys from earlier ones. - """ - def absolutize(cmd): - args = cmd.split() - args[0] = os.path.join( - modified_recipe.staging_bindir_toolchain, args[0]) - return repr(args) - - lines = ["[binaries]"] - if modified_recipe.cc: - lines.append("c = %s" % absolutize(modified_recipe.cc)) - if modified_recipe.cxx: - lines.append("cpp = %s" % absolutize(modified_recipe.cxx)) - - os.makedirs(modified_recipe.ide_sdk_dir, exist_ok=True) - cross_file = os.path.join( - modified_recipe.ide_sdk_dir, 'meson-absolute-toolchain.cross') - with open(cross_file, 'w') as f: - f.write(os.linesep.join(lines) + os.linesep) - return ['--cross-file', cross_file] - def __vscode_settings_meson(self, settings_dict, modified_recipe): if modified_recipe.build_tool is not BuildTool.MESON: return @@ -193,11 +154,16 @@ class IdeVSCode(IdeBase): confopts = modified_recipe.mesonopts.split() confopts += modified_recipe.meson_cross_file.split() - if modified_recipe.meson_cross_file: - confopts += self.__gen_meson_absolute_cross_file(modified_recipe) confopts += modified_recipe.extra_oemeson.split() settings_dict["mesonbuild.configureOptions"] = confopts settings_dict["mesonbuild.buildFolder"] = modified_recipe.b + # Prevent the extension from writing C_Cpp.default.configurationProvider + # for itself once meson-info.json appears, which would take precedence + # over the compileCommands set in c_cpp_properties.json. + settings_dict["mesonbuild.modifySettings"] = False + # Clear any such value a previous run (or extension version without + # modifySettings support) already wrote to this workspace's settings. + settings_dict["C_Cpp.default.configurationProvider"] = "" def __vscode_settings_cmake(self, settings_dict, modified_recipe): """Add cmake specific settings to settings.json. @@ -328,8 +294,22 @@ class IdeVSCode(IdeBase): if modified_recipe.build_tool is BuildTool.CMAKE: properties_dict["configurationProvider"] = "ms-vscode.cmake-tools" elif modified_recipe.build_tool is BuildTool.MESON: - properties_dict["configurationProvider"] = "mesonbuild.mesonbuild" - properties_dict["compilerPath"] = os.path.join(modified_recipe.staging_bindir_toolchain, modified_recipe.cxx.split()[0]) + # configurationProvider = "mesonbuild.mesonbuild" does not work because + # the cross-compiler is not in PATH and omits the sysroot by default. + compiler_type = 'clang' if modified_recipe.toolchain == 'clang' else 'gcc' + arch_map = { + 'aarch64': 'arm64', 'x86_64': 'x64', 'arm': 'arm', + 'i686': 'x86', 'i586': 'x86', 'riscv64': 'x64', 'riscv32': 'x86', + } + intelli_arch = arch_map.get(modified_recipe.target_arch, 'x64') + properties_dict["compilerPath"] = os.path.join( + modified_recipe.staging_bindir_toolchain, + modified_recipe.cxx.split()[0]) + properties_dict["compilerArgs"] = [ + "--sysroot=" + modified_recipe.recipe_sysroot] + properties_dict["compileCommands"] = os.path.join( + modified_recipe.b, 'compile_commands.json') + properties_dict["intelliSenseMode"] = "linux-%s-%s" % (compiler_type, intelli_arch) elif modified_recipe.build_tool is BuildTool.KERNEL_MODULE: # Using e.g. configurationProvider = "ms-vscode.makefile-tools" was not successful properties_dict["compilerPath"] = os.path.join(modified_recipe.staging_bindir_toolchain, modified_recipe.kernel_cc.split()[0]) From patchwork Sun Aug 16 19:40:48 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 95463 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id CE1F3C5DF7F for ; Sun, 16 Aug 2026 19:41:43 +0000 (UTC) Received: from mta-65-228.siemens.flowmailer.net (mta-65-228.siemens.flowmailer.net [185.136.65.228]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.15972.1786909295039451254 for ; Sun, 16 Aug 2026 12:41:36 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=R93DKiEO; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.65.228, mailfrom: fm-1329275-202608161941326a0282231b0002070f-81p7oe@rts-flowmailer.siemens.com) Received: by mta-65-228.siemens.flowmailer.net with ESMTPSA id 202608161941326a0282231b0002070f for ; Sun, 16 Aug 2026 21:41:32 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm2; d=siemens.com; i=adrian.freihofer@siemens.com; h=Date:From:Subject:To:Message-ID:MIME-Version:Content-Type:Content-Transfer-Encoding:Cc:References:In-Reply-To; bh=7ooL3TjiP+EQUjZTswbaLch11E/e2v5SPkVxkoOC7Dg=; b=R93DKiEO8bPOs0/QoLrEgVWyeYfgJRNJSP8oqZW4KDHRNfcg8k2u1Al6eTJChQijcXP8zG MI7r+PfPd/68wadK2ayFf0TiEao+fULgytgqQEKLDbeqi9z6VC+u5/OxlWYe/wpgubluSaz3 JMyTSxP/ME2zwHB1zuvSvASoyJgI9HV/b++vbrCb5YQdnDTU6PMuGVRbMJWsctwisFhUXJW9 bem9phQDy5/nOnUoKdYR89FXiWkLzYfCph4GAA36vOIXXP8diKzDmJg8BwRTh2ov7KivtzsE ukMJpICvzC7vH0Rhl3XR0m/ROVS9F3cN4/NBxqjlESl8pzjWJ3+o56Pw==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 05/14] cpp-example: prevent compiler from eliminating debugger test code Date: Sun, 16 Aug 2026 21:40:48 +0200 Message-ID: <20260816194127.86607-6-adrian.freihofer@siemens.com> In-Reply-To: <20260816194127.86607-1-adrian.freihofer@siemens.com> References: <20260816194127.86607-1-adrian.freihofer@siemens.com> MIME-Version: 1.0 X-Flowmailer-Platform: Siemens Feedback-ID: 519:519-1329275:519-21489:flowmailer List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Sun, 16 Aug 2026 19:41:43 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/243552 From: Adrian Freihofer Be more paranoid about compiler optimizations that can eliminate code that is only used for debugger inspection and update the test accordingly. 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 --- .../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 numbers = {1, 2, 3}; + std::vector numbers = {n1, n2, n3}; std::cout << "Traversing std::vector 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 b1539c342c..1be727d96f 100644 --- a/meta/lib/oeqa/selftest/cases/devtool.py +++ b/meta/lib/oeqa/selftest/cases/devtool.py @@ -2961,8 +2961,8 @@ class DevtoolIdeSdkTests(DevtoolBase): with open(cpp_example_cpp, 'r') as file: cpp_code = file.read() cpp_code = cpp_code.replace( - " std::vector numbers = {1, 2, 3};", - extra_lines + " std::vector 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) @@ -3002,7 +3002,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 _verify_cmake_preset(self, tempdir): @@ -3118,7 +3118,7 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests): 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 @@ -3142,8 +3142,9 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests): # 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. @@ -3180,7 +3181,7 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests): # check if resolving std::vector works with python scripts self.assertRegex( - gdb_output, r"%d\s+std::vector numbers = \{1, 2, 3\};" % exe_list_line) + gdb_output, r"%d\s+std::vector 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 @@ -3191,7 +3192,7 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests): 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 From patchwork Sun Aug 16 19:40:49 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 95461 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id B537BC5DF7D for ; Sun, 16 Aug 2026 19:41:43 +0000 (UTC) Received: from mta-64-227.siemens.flowmailer.net (mta-64-227.siemens.flowmailer.net [185.136.64.227]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.15979.1786909295040431476 for ; Sun, 16 Aug 2026 12:41:36 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=OaWg3sop; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.64.227, mailfrom: fm-1329275-2026081619413291bd11458d0002074f-rwbn1n@rts-flowmailer.siemens.com) Received: by mta-64-227.siemens.flowmailer.net with ESMTPSA id 2026081619413291bd11458d0002074f for ; Sun, 16 Aug 2026 21:41:32 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm2; d=siemens.com; i=adrian.freihofer@siemens.com; h=Date:From:Subject:To:Message-ID:MIME-Version:Content-Type:Content-Transfer-Encoding:Cc:References:In-Reply-To; bh=0d7wl/WvSmWGtaa/PKeuZVaOtcmBCwvhFH5fBtTFSbQ=; b=OaWg3sopgzcxNmdf+Fp+zE2Mjq0lfkXoc53UIgm/MqB1CA00b35aAXbeWdsXLbnQBToUpO yAoXAknu/RLswPWe1O5C+IMxBiHElAVT8ehI0cyinSwPmYyjVdEB/d+dHlJVKTKXNY9wy2aD M+Mw0Ap6/3BXmtzI4wttiJEZTOD0qaKw4DnKsV9oEOZHIelaY53Iq73JU292cii8AG2BdKjQ 85Hk/gEgSTT4bAVRGct7aP+hSYqHIhxoWU+ty2XyWZ8dtOJHDWvtI1Q35p0wWl55fcBMFW+r i2FOr4j1H2uwzyAWjFlIPzx1W2uZQocEXwWpxA6olhG+sVvrLpZB5SFA==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 06/14] ccp-example: fix PID file handling Date: Sun, 16 Aug 2026 21:40:49 +0200 Message-ID: <20260816194127.86607-7-adrian.freihofer@siemens.com> In-Reply-To: <20260816194127.86607-1-adrian.freihofer@siemens.com> References: <20260816194127.86607-1-adrian.freihofer@siemens.com> MIME-Version: 1.0 X-Flowmailer-Platform: Siemens Feedback-ID: 519:519-1329275:519-21489:flowmailer List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Sun, 16 Aug 2026 19:41:43 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/243553 From: Adrian Freihofer Remove the PID file if the process is not running anymore. This can happen when a debugger kills the process it debugs. Signed-off-by: Adrian Freihofer --- meta-selftest/recipes-test/cpp/files/cpp-example.init | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/meta-selftest/recipes-test/cpp/files/cpp-example.init b/meta-selftest/recipes-test/cpp/files/cpp-example.init index c154fd1126..30b8486eeb 100644 --- a/meta-selftest/recipes-test/cpp/files/cpp-example.init +++ b/meta-selftest/recipes-test/cpp/files/cpp-example.init @@ -14,6 +14,11 @@ PIDFILE="/var/run/$DAEMON.pid" LOCK_FILE="/var/lock/subsys/$DAEMON" start() { + # A debugger which kills the process it debugs leaves the PID file behind + if [ -f $PIDFILE ] && ! kill -0 $(cat $PIDFILE) 2>/dev/null; then + rm -f $PIDFILE $LOCK_FILE + fi + if [ -f $PIDFILE ]; then echo "$DAEMON is already running." return 1 From patchwork Sun Aug 16 19:40:50 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 95455 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 62A73C5DF6D for ; Sun, 16 Aug 2026 19:41:41 +0000 (UTC) Received: from mta-65-227.siemens.flowmailer.net (mta-65-227.siemens.flowmailer.net [185.136.65.227]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.16145.1786909295039502537 for ; Sun, 16 Aug 2026 12:41:37 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=XVq5WyUe; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.65.227, mailfrom: fm-1329275-2026081619413293fb5464dc000207c4-fhbn3c@rts-flowmailer.siemens.com) Received: by mta-65-227.siemens.flowmailer.net with ESMTPSA id 2026081619413293fb5464dc000207c4 for ; Sun, 16 Aug 2026 21:41:32 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm2; d=siemens.com; i=adrian.freihofer@siemens.com; h=Date:From:Subject:To:Message-ID:MIME-Version:Content-Type:Content-Transfer-Encoding:Cc:References:In-Reply-To; bh=tF9kmAJxWcRoTEXgu2vR8LvSN5T/1sdatjBT61iZ390=; b=XVq5WyUeW9Y1ghMVPYWBR/NNPff0fypA5T3g8X9XKlNKAq7dDJ3ead53Flmf6pwJVucIfb dVHA84hNDN3od2OaCg1lu/PUOw+GUyF7rmHV2bopcGPc9lMSm/tTwxaH8LewriKVE5MfBltc 7UnTRqcpMgpjJ4ykpahAAotdDciwMtBqO8oFBSmKoTPV5I4s5j7RqCXs6Ms6njTEXDVB540n /hVS4Us9cVNaRzslXLTPh+kH14K84rZy74aLaNIP/1RW3CvLZpANsmnHzm4sTFeCskuYdBC6 9kfX2HFUV6JKq8qxqDgmwdQI18UfpKVsf7IdBTbbZfI9vCmRcnUdrA8Q==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 07/14] devtool: ide-sdk: default to all modified recipes Date: Sun, 16 Aug 2026 21:40:50 +0200 Message-ID: <20260816194127.86607-8-adrian.freihofer@siemens.com> In-Reply-To: <20260816194127.86607-1-adrian.freihofer@siemens.com> References: <20260816194127.86607-1-adrian.freihofer@siemens.com> MIME-Version: 1.0 X-Flowmailer-Platform: Siemens Feedback-ID: 519:519-1329275:519-21489:flowmailer List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Sun, 16 Aug 2026 19:41:41 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/243560 From: Adrian Freihofer Simplify ide-sdk usage in modified mode: keep the image recipe explicit, but when no modified recipe is passed, take all modified recipes from /appends/*.bbappend. Signed-off-by: Adrian Freihofer --- scripts/lib/devtool/ide_sdk.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/scripts/lib/devtool/ide_sdk.py b/scripts/lib/devtool/ide_sdk.py index 42b6e381c5..ab2bba5d39 100755 --- a/scripts/lib/devtool/ide_sdk.py +++ b/scripts/lib/devtool/ide_sdk.py @@ -1258,6 +1258,15 @@ def ide_setup(args, config, basepath, workspace): recipes_modified_names)) invalid_params = True if args.mode == DevtoolIdeMode.modified: + if not recipes_modified_names: + appends_dir = os.path.join(config.workspace_path, 'appends') + recipes_modified_names = sorted( + os.path.splitext(os.path.basename(path))[0] + for path in glob.glob(os.path.join(appends_dir, '*.bbappend'))) + if recipes_modified_names: + logger.info( + "No modified recipes specified, using workspace bbappends from %s: %s", + appends_dir, ', '.join(recipes_modified_names)) if len(recipes_other_names): logger.error("Only in shared sysroots mode not modified recipes %s can be handled." % str( recipes_other_names)) @@ -1266,6 +1275,11 @@ def ide_setup(args, config, basepath, workspace): logger.error( "One image recipe is required as the rootfs for the remote development.") invalid_params = True + if not recipes_modified_names: + logger.error( + "At least one modified recipe is required or must be guessable from %s." % + os.path.join(config.workspace_path, 'appends')) + invalid_params = True for modified_recipe_name in recipes_modified_names: if modified_recipe_name.startswith('nativesdk-') or modified_recipe_name.endswith('-native'): logger.error( @@ -1422,7 +1436,9 @@ def register_commands(subparsers, context): help='Setup the SDK and configure the IDE') parser_ide_sdk.add_argument( 'recipenames', nargs='+', help='Generate an IDE configuration suitable to work on the given recipes.\n' - 'Depending on the --mode parameter different types of SDKs and IDE configurations are generated.') + 'Depending on the --mode parameter different types of SDKs and IDE configurations are generated.\n' + 'In modified mode at least the image recipe is required; if no modified recipe is passed, ' + 'all modified recipes are taken from /appends/*.bbappend.') parser_ide_sdk.add_argument( '-m', '--mode', type=DevtoolIdeMode, default=DevtoolIdeMode.modified, help='Different SDK types are supported:\n' From patchwork Sun Aug 16 19:40:51 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 95456 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 42F9EC5B572 for ; Sun, 16 Aug 2026 19:41:41 +0000 (UTC) Received: from mta-65-228.siemens.flowmailer.net (mta-65-228.siemens.flowmailer.net [185.136.65.228]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.16146.1786909295039585359 for ; Sun, 16 Aug 2026 12:41:37 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=MvJY0uh/; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.65.228, mailfrom: fm-1329275-20260816194132f6bb76be2d00020772-sjdrl_@rts-flowmailer.siemens.com) Received: by mta-65-228.siemens.flowmailer.net with ESMTPSA id 20260816194132f6bb76be2d00020772 for ; Sun, 16 Aug 2026 21:41:32 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm2; d=siemens.com; i=adrian.freihofer@siemens.com; h=Date:From:Subject:To:Message-ID:MIME-Version:Content-Type:Content-Transfer-Encoding:Cc:References:In-Reply-To; bh=UB7eVCqupIConBytoa/cucXR0nlNYoUrSRPxeIrccyk=; b=MvJY0uh/jaL56q2Q/R86qrzf9eSwen1Q1sLzMuCagpIeZ10DLmHuMhh8IHv8PX+fO3C5LS nvYAc/tQMaqkXjtl5ZMaa87rbgupJFFKFLfdyE8PWWJLNjQbJeCrR/DgYp17VuwYD5oXlQtO u11JrRftQMnmIrbqk7Q5P25z3/HtPehcwoklUCpSawC7u2JzYQBv2abUVmnJoR30vsrlqHX1 9jQTZ7pGcRftWHLF3a0Hex4dgc4Nn3Wm7XPMqmBYY3nQWSPtuzChvA99hZuJbh9FNLPj2Swe 6qV3J68W+nLxndHly06rOv+s5vFsdIygOMmrfaW4FIktrsAxuSJO/tnQ==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 08/14] devtool: ide-sdk: let gdbserver reuse the shared debug server wait helper Date: Sun, 16 Aug 2026 21:40:51 +0200 Message-ID: <20260816194127.86607-9-adrian.freihofer@siemens.com> In-Reply-To: <20260816194127.86607-1-adrian.freihofer@siemens.com> References: <20260816194127.86607-1-adrian.freihofer@siemens.com> MIME-Version: 1.0 X-Flowmailer-Platform: Siemens Feedback-ID: 519:519-1329275:519-21489:flowmailer List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Sun, 16 Aug 2026 19:41:41 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/243561 From: Adrian Freihofer GdbCrossConfig open coded its own copy of the loop which waits for the debug server to listen on its port, while LldbServerConfig already used the _target_wait_for_tcp_port_cmd() helper of the common base class. Use the helper for gdbserver too, which requires capturing the gdbserver pid in a shell variable so the helper can refer to it, and defining DEBUG_SERVER_NAME for GdbCrossConfig. Beside removing the duplication this also gives gdbserver the two things the helper already did for lldb-server: the server is killed instead of left behind when it never starts listening, and the reason is printed rather than exiting silently. Signed-off-by: Adrian Freihofer --- scripts/lib/devtool/ide_plugins/__init__.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/lib/devtool/ide_plugins/__init__.py b/scripts/lib/devtool/ide_plugins/__init__.py index 63e9682e73..5211df5806 100644 --- a/scripts/lib/devtool/ide_plugins/__init__.py +++ b/scripts/lib/devtool/ide_plugins/__init__.py @@ -139,6 +139,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): @@ -193,14 +194,13 @@ class GdbCrossConfig(DebuggerCrossConfig): 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 & " % ( - 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 += "%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 \\$_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 + "'\"" From patchwork Sun Aug 16 19:40:52 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 95462 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 8D24AC5DF7B for ; Sun, 16 Aug 2026 19:41:43 +0000 (UTC) Received: from mta-64-227.siemens.flowmailer.net (mta-64-227.siemens.flowmailer.net [185.136.64.227]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.15977.1786909295040164791 for ; Sun, 16 Aug 2026 12:41:36 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=Ly/sRf3H; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.64.227, mailfrom: fm-1329275-20260816194132b64e017e93000207b7-a_fxt5@rts-flowmailer.siemens.com) Received: by mta-64-227.siemens.flowmailer.net with ESMTPSA id 20260816194132b64e017e93000207b7 for ; Sun, 16 Aug 2026 21:41:32 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm2; d=siemens.com; i=adrian.freihofer@siemens.com; h=Date:From:Subject:To:Message-ID:MIME-Version:Content-Type:Content-Transfer-Encoding:Cc:References:In-Reply-To; bh=iDONU+fNaAd4nStove8yOZy6wS8SM8EMAO0zfmeDkCs=; b=Ly/sRf3H+hKydq0lJNab8ygpcRDhRQjxDxqaot8uSKAhe9BNpxBgpVbUmQ0IyvZ0Wb5CBW A32BEmsdI3XLp2JW+UkWy6NXGiewP/FSXRwnN2NwMPY8//xfAap70tx3Kd+snHWbQIcg2lQg SNuhp5yxaPt4QShSOaBfnPVXiYZsiDUNvtkltGf1juvL/MmBw9q9k44hugOf/F218XKUyswk /aZwsnK008gnw17jr65wNF06I0pkJWFbscby4AIrFGGKXM4dWw5P9eMGOySlTTyPvkF96Hge TXpWES2VCCLq0fsmrbiZ7q5b8J/BRNbxz0lpTFZOn8N78njwP4CLKtxw==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 09/14] oe-selftest: devtool ide-sdk: support $ in gdbserver task command Date: Sun, 16 Aug 2026 21:40:52 +0200 Message-ID: <20260816194127.86607-10-adrian.freihofer@siemens.com> In-Reply-To: <20260816194127.86607-1-adrian.freihofer@siemens.com> References: <20260816194127.86607-1-adrian.freihofer@siemens.com> MIME-Version: 1.0 X-Flowmailer-Platform: Siemens Feedback-ID: 519:519-1329275:519-21489:flowmailer List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Sun, 16 Aug 2026 19:41:43 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/243554 From: Adrian Freihofer This is a preparation commit for the next commit that will add a new a variable to the tasks.json file to support gdbserver commands. Signed-off-by: Adrian Freihofer --- meta/lib/oeqa/selftest/cases/devtool.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py index 1be727d96f..b38011eac1 100644 --- a/meta/lib/oeqa/selftest/cases/devtool.py +++ b/meta/lib/oeqa/selftest/cases/devtool.py @@ -3518,10 +3518,11 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests): # 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('\\$', '$') # Remove surrounding quotes 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 From patchwork Sun Aug 16 19:40:53 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 95460 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 1D48BC5DF7A for ; Sun, 16 Aug 2026 19:41:43 +0000 (UTC) Received: from mta-64-225.siemens.flowmailer.net (mta-64-225.siemens.flowmailer.net [185.136.64.225]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.15975.1786909295039659980 for ; Sun, 16 Aug 2026 12:41:36 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=EzX0RQw7; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.64.225, mailfrom: fm-1329275-2026081619413243c139e52f00020757-ahqtec@rts-flowmailer.siemens.com) Received: by mta-64-225.siemens.flowmailer.net with ESMTPSA id 2026081619413243c139e52f00020757 for ; Sun, 16 Aug 2026 21:41:33 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm2; d=siemens.com; i=adrian.freihofer@siemens.com; h=Date:From:Subject:To:Message-ID:MIME-Version:Content-Type:Content-Transfer-Encoding:Cc:References:In-Reply-To; bh=khcxywpsXq5FAtWA8aJbX55NsHaTue76VemtUGIinmg=; b=EzX0RQw7zAY69YgYgRqVac22bOB24XZ4zEV+B9YhD99jSAUppZ/B5kbw0tV5KYO3EEkf1T gzpMrBkzqblAqpOQ7X2T4BhhOMmr1KLO3TTNYzZaZnIbZ2WdIO63qfTi01EY+A28M/W5zD+9 dARUMMWvl8Pn9dl7b74TTVbTiQ84zwv8Ub+XOEyP7+8PFFr3mlylNCy//EAiVOtpYQPugyQi ZCtPzznA4FzGSSaH5qWrzjlDLhX3yGKhLOh8j3QDDGoC2aUfUuBmJIBMqAIMycs69tII2yEj BoISd53CCIuFH6LrbHvxD9NUCZrdkSk5w5iAF34qBnedX0TM+/hMQUeg==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 10/14] devtool: ide-sdk: attach via extended-remote Date: Sun, 16 Aug 2026 21:40:53 +0200 Message-ID: <20260816194127.86607-11-adrian.freihofer@siemens.com> In-Reply-To: <20260816194127.86607-1-adrian.freihofer@siemens.com> References: <20260816194127.86607-1-adrian.freihofer@siemens.com> MIME-Version: 1.0 X-Flowmailer-Platform: Siemens Feedback-ID: 519:519-1329275:519-21489:flowmailer List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Sun, 16 Aug 2026 19:41:43 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/243556 From: Adrian Freihofer The old ATTACH mode started gdbserver with "--attach : $(pidof )" and used a plain "request": "launch" config. This is the classic cpptools attach setup, which is fragile and has several long-standing, unfixed bugs, e.g. https://github.com/microsoft/vscode-cpptools/issues/4166. { "name": "Attach with GDB", "type": "cppdbg", "request": "launch", "program": "", "stopAtEntry": true, "cwd": "${workspaceFolder}", "environment": [], "externalConsole": false, "MIMode": "gdb", "miDebuggerPath": "/usr/bin/gdb", "miDebuggerServerAddress": ":", "postDebugTask": "kill_gdbserver_..." } cppdbg also supports attaching over the extended-remote protocol: { "name": "Attach with GDB", "type": "cppdbg", "request": "attach", "program": "", "MIMode": "gdb", "miDebuggerPath": "/usr/bin/gdb", "miDebuggerServerAddress": ":", "useExtendedRemote": true } Switch to this instead: merge ATTACH into the same persistent "gdbserver --multi" server already used for MULTI mode, since both are now extended-remote sessions that only differ on the client side, and stop it by PID instead of pgrep/killall. ONCE mode gets the same readiness synchronization MULTI already had, since gdbserver now always writes a PID file that the stop commands wait on. Until https://github.com/microsoft/vscode-cpptools/pull/14684 is merged, this comes with one downside (which was the blocker for using extended-remote attach mode until now): the user is prompted to pick the process to attach to, instead of it being selected automatically. But this is a temporary limitation, and the new setup is more robust and fixes several long-standing issues with the old ATTACH mode. Signed-off-by: Adrian Freihofer --- meta/lib/oeqa/selftest/cases/devtool.py | 19 +++-- scripts/lib/devtool/ide_plugins/__init__.py | 67 ++++++++++----- scripts/lib/devtool/ide_plugins/ide_code.py | 92 ++++++++++++--------- scripts/lib/devtool/ide_plugins/ide_none.py | 44 ++++++---- 4 files changed, 137 insertions(+), 85 deletions(-) diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py index b38011eac1..a07214f8b3 100644 --- a/meta/lib/oeqa/selftest/cases/devtool.py +++ b/meta/lib/oeqa/selftest/cases/devtool.py @@ -3342,18 +3342,27 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests): for config in configurations: # Verify required fields exist - required_fields = ["name", "type", "request", "program", "cwd", "MIMode", + required_fields = ["name", "type", "request", "program", "MIMode", "miDebuggerPath", "miDebuggerServerAddress"] for field in required_fields: self.assertIn(field, config, f"Configuration '{config.get('name', 'Unknown')}' missing required field: {field}") # Verify common configuration values self.assertEqual(config["type"], "cppdbg", f"Configuration '{config['name']}' should use cppdbg type") - self.assertEqual(config["request"], "launch", f"Configuration '{config['name']}' should be launch type") - self.assertEqual(config["cwd"], "${workspaceFolder}", f"Configuration '{config['name']}' should use workspaceFolder as cwd") self.assertEqual(config["MIMode"], "gdb", f"Configuration '{config['name']}' should use gdb MIMode") - self.assertEqual(config.get("externalConsole", False), False, f"Configuration '{config['name']}' should not use external console") - self.assertEqual(config.get("stopAtEntry", True), True, f"Configuration '{config['name']}' should stop at entry") + + if config["request"] == "launch": + self.assertEqual(config["cwd"], "${workspaceFolder}", f"Configuration '{config['name']}' should use workspaceFolder as cwd") + self.assertEqual(config.get("externalConsole", False), False, f"Configuration '{config['name']}' should not use external console") + self.assertEqual(config.get("stopAtEntry", True), True, f"Configuration '{config['name']}' should stop at entry") + elif config["request"] == "attach": + # Attaching to a process running on the target requires the + # extended-remote protocol. Stopping the session then detaches + # from the process instead of killing it. + self.assertTrue(config.get("useExtendedRemote"), f"Configuration '{config['name']}' should use useExtendedRemote") + self.assertNotIn("cwd", config, f"Configuration '{config['name']}' should not set cwd in attach mode") + else: + self.fail(f"Configuration '{config['name']}' has unexpected request type: {config['request']}") # Verify program path is absolute and exists conceptually program = config["program"] diff --git a/scripts/lib/devtool/ide_plugins/__init__.py b/scripts/lib/devtool/ide_plugins/__init__.py index 5211df5806..b302f419b1 100644 --- a/scripts/lib/devtool/ide_plugins/__init__.py +++ b/scripts/lib/devtool/ide_plugins/__init__.py @@ -104,6 +104,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, log_file=None): """Shell fragment waiting until the debug server listens on its port. @@ -120,6 +126,13 @@ class DebuggerCrossConfig: % (self._target_tcp_port_check_cmd(), self.TARGET_START_RETRIES, cleanup, self.DEBUG_SERVER_NAME, self.debug_server_port, dump_log)) + 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 @@ -128,7 +141,7 @@ class DebuggerCrossConfig: def _target_start_cmd(self, mode): raise NotImplementedError - def _target_kill_cmd(self): + def _target_stop_cmd(self, mode): raise NotImplementedError @@ -182,18 +195,19 @@ 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 = "mkdir -p %s; " % self._gdbserver_tmp_dir(server_mode) + gdbserver_cmd_start += "%s --once :%s %s & " % ( self.debugger_cross.debug_server_path, self.debug_server_port, self.binary.binary_path) - elif server_mode == DebuggerServerModes.ATTACH: - pid_command = self.binary.pid_command - if pid_command: - gdbserver_cmd_start = "%s --attach :%s \\$(%s)" % ( - self.debugger_cross.debug_server_path, - self.debug_server_port, - pid_command) - else: - raise DevtoolError("Cannot use gdbserver attach mode for binary %s. No PID found." % self.binary.binary_path) - elif server_mode == DebuggerServerModes.MULTI: + gdbserver_cmd_start += "_gdbserver_pid=\\$!; " + 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") + " " + gdbserver_cmd_start += "echo %s; wait \\$_gdbserver_pid" % ( + self.get_debug_server_ready_marker(self.debug_server_port)) + elif server_mode in (DebuggerServerModes.ATTACH, DebuggerServerModes.MULTI): + # Both modes run a persistent server speaking the extended-remote + # protocol. They differ on the client side only: ATTACH attaches to + # a process that is already running on the target. 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_pid=\\$!; " % ( @@ -205,9 +219,21 @@ class GdbCrossConfig(DebuggerCrossConfig): raise DevtoolError("Unsupported gdbserver mode: %s" % server_mode) return "\"/bin/sh -c '" + gdbserver_cmd_start + "'\"" - def _target_kill_cmd(self): - """SSH command to kill gdbserver on the target device.""" - return "\"kill \\$(pgrep -o -f 'gdbserver --attach :%s') 2>/dev/null || true\"" % self.debug_server_port + def _target_stop_cmd(self, server_mode): + """SSH command to stop gdbserver on the target device. + + Stopping is based on the PID file written by the start command. Other + debug sessions run their own gdbserver on the target, so anything + matching by process name would hit them as well. + """ + 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) + return "\"/bin/sh -c '" + gdbserver_cmd_stop + "'\"" class LldbServerConfig(DebuggerCrossConfig): @@ -244,10 +270,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) @@ -261,11 +284,11 @@ class LldbServerConfig(DebuggerCrossConfig): "lldb_server_pid", log_file) 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): + def _target_stop_cmd(self, server_mode): """SSH command to stop a MULTI-mode lldb-server on the target.""" pid_file = self._lldb_server_pid_file(DebuggerServerModes.MULTI) tmp_dir = self._lldb_server_tmp_dir(DebuggerServerModes.MULTI) diff --git a/scripts/lib/devtool/ide_plugins/ide_code.py b/scripts/lib/devtool/ide_plugins/ide_code.py index d190858d44..c598772e3c 100644 --- a/scripts/lib/devtool/ide_plugins/ide_code.py +++ b/scripts/lib/devtool/ide_plugins/ide_code.py @@ -32,14 +32,12 @@ class GdbCrossConfigVSCode(GdbCrossConfig): self._target_start_cmd(mode) ] - def target_ssh_gdbserver_kill_args(self): - """Get the ssh command arguments to kill gdbserver on the target device - - returns something like: - ['-p', '2222', 'root@target', '"kill $(pgrep -o -f \'gdbserver --attach :1234\') 2>/dev/null || true"'] - """ + def target_ssh_gdbserver_stop_args(self, mode=None): + """Get the ssh command arguments to stop gdbserver on the target device""" + if mode is None: + mode = self.default_mode return self._target_ssh_args() + [ - self._target_kill_cmd() + self._target_stop_cmd(mode) ] @@ -59,10 +57,12 @@ class LldbServerConfigVSCode(LldbServerConfig): self._target_start_cmd(mode) ] - def target_ssh_gdbserver_kill_args(self): + def target_ssh_gdbserver_stop_args(self, mode=None): """SSH argument list to stop a running MULTI-mode lldb-server""" + if mode is None: + mode = self.default_mode return self._target_ssh_args() + [ - self._target_kill_cmd() + self._target_stop_cmd(mode) ] class IdeVSCode(IdeBase): @@ -338,25 +338,43 @@ class IdeVSCode(IdeBase): return self._vscode_launch_bin_dbg_lldb(cross_debug_config, server_mode) return self._vscode_launch_bin_dbg_gdb(cross_debug_config, server_mode) + @staticmethod + def _stop_task_label(cross_debug_config, server_mode): + return "stop_%s_%s" % (cross_debug_config.DEBUG_SERVER_NAME, + cross_debug_config.id_pretty_mode(server_mode)) + def _vscode_launch_bin_dbg_gdb(self, cross_debug_config, server_mode): """Generate a cppdbg (GDB) launch configuration entry for launch.json.""" modified_recipe = cross_debug_config.modified_recipe + is_attach = server_mode == DebuggerServerModes.ATTACH + launch_config = { "name": cross_debug_config.id_pretty_mode(server_mode), "type": "cppdbg", - "request": "launch", + "request": "attach" if is_attach else "launch", "program": cross_debug_config.binary.binary_host_path, - "stopAtEntry": True, - "cwd": "${workspaceFolder}", - "environment": [], - "externalConsole": False, "MIMode": "gdb", "preLaunchTask": cross_debug_config.id_pretty_mode(server_mode), "miDebuggerPath": modified_recipe.debugger_cross.gdb, "miDebuggerServerAddress": "%s:%d" % (modified_recipe.debugger_cross.host, cross_debug_config.debug_server_port) } + if is_attach: + # Without useExtendedRemote, cppdbg rejects attaching to a remote + # target. It also makes cppdbg offer a picker listing the processes + # running on the target, so the PID does not have to be known when + # this configuration is generated. Stopping the session detaches + # from the process instead of killing it. + launch_config["useExtendedRemote"] = True + else: + # cwd, environment and externalConsole configure the process the + # debugger starts, they are not part of the attach schema. + launch_config["cwd"] = "${workspaceFolder}" + launch_config["environment"] = [] + launch_config["externalConsole"] = False + launch_config["stopAtEntry"] = True + # Search for header files in recipe-sysroot. src_file_map = { "/usr/include": os.path.join(modified_recipe.recipe_sysroot, "usr", "include") @@ -415,10 +433,10 @@ class IdeVSCode(IdeBase): launch_config['sourceFileMap'] = src_file_map launch_config['setupCommands'] = setup_commands - # Add postDebugTask for attach mode to clean up gdbserver - if server_mode == DebuggerServerModes.ATTACH: - kill_task_label = "kill_gdbserver_" + cross_debug_config.id_pretty_mode(server_mode) - launch_config["postDebugTask"] = kill_task_label + if is_attach: + # The extended-remote server outlives the debug session + launch_config["postDebugTask"] = self._stop_task_label( + cross_debug_config, server_mode) return launch_config @@ -574,8 +592,9 @@ class IdeVSCode(IdeBase): if cross_debug_config.modified_recipe is not modified_recipe: continue for server_mode in cross_debug_config.server_modes(): - if server_mode == DebuggerServerModes.MULTI: - # MULTI mode: the SSH command blocks until the port is ready + if server_mode in (DebuggerServerModes.MULTI, + DebuggerServerModes.ATTACH): + # The SSH command blocks until the port is ready # (wait loop in _target_start_cmd), so VSCode treats this as # a regular non-background task. new_task = { @@ -586,7 +605,7 @@ class IdeVSCode(IdeBase): "problemMatcher": [] } else: - # ONCE / ATTACH: gdbserver runs in the foreground for the + # ONCE: gdbserver runs in the foreground for the # whole session, so VSCode needs isBackground + a pattern # matcher to avoid waiting for the task to exit. new_task = { @@ -608,7 +627,7 @@ class IdeVSCode(IdeBase): "background": { "activeOnStart": True, "beginsPattern": ".", - "endsPattern": ".", + "endsPattern": cross_debug_config.get_debug_server_ready_marker_pattern(), } } ] @@ -621,28 +640,20 @@ class IdeVSCode(IdeBase): tasks_dict['tasks'].append(new_task) - # For attach mode, add a kill task to stop a previously running gdbserver - # This is a known issue with gdbserver --attach that it does not terminate - # after detaching. With this helper task, it is possible to: - # 1. Start debugging in attach mode - # 2. Add breakpoints, step, continue, etc. - # 3. Press the Continue button - # 4. Press the Stop button which detaches gdbserver from the debugged process - # 5. Start debugging again in attach mode - # Without this kill task, step 5 would fail because gdbserver is still running + # The extended-remote server used by attach mode keeps running + # after the debug session, launch.json refers to this task as + # postDebugTask. if server_mode == DebuggerServerModes.ATTACH: - new_task_kill_label = "kill_gdbserver_"+ cross_debug_config.id_pretty_mode(server_mode) - new_task_kill = { - "label": new_task_kill_label, + tasks_dict['tasks'].append({ + "label": self._stop_task_label(cross_debug_config, server_mode), "type": "shell", "command": cross_debug_config.debugger_cross.target_device.ssh_sshexec, - "args": cross_debug_config.target_ssh_gdbserver_kill_args(), + "args": cross_debug_config.target_ssh_gdbserver_stop_args(server_mode), "presentation": { "close": True }, "problemMatcher": [] - } - tasks_dict['tasks'].append(new_task_kill) + }) tasks_file = 'tasks.json' IdeBase.update_json_file( @@ -804,8 +815,9 @@ class IdeVSCode(IdeBase): if cross_debug_config.modified_recipe is not modified_recipe: continue for server_mode in cross_debug_config.server_modes(): - if server_mode == DebuggerServerModes.MULTI: - # MULTI mode: SSH command blocks until port is ready, treat as + if server_mode in (DebuggerServerModes.MULTI, + DebuggerServerModes.ATTACH): + # SSH command blocks until port is ready, treat as # a regular non-background task (same as vscode_tasks_cpp). new_task = { "label": cross_debug_config.id_pretty_mode(server_mode), @@ -815,7 +827,7 @@ class IdeVSCode(IdeBase): "problemMatcher": [] } else: - # ONCE / ATTACH: server runs for the whole session, needs + # ONCE: server runs for the whole session, needs # isBackground so VSCode does not wait for the task to exit. new_task = { "label": cross_debug_config.id_pretty_mode(server_mode), diff --git a/scripts/lib/devtool/ide_plugins/ide_none.py b/scripts/lib/devtool/ide_plugins/ide_none.py index a8ddc3f39f..959140cedb 100644 --- a/scripts/lib/devtool/ide_plugins/ide_none.py +++ b/scripts/lib/devtool/ide_plugins/ide_none.py @@ -21,16 +21,19 @@ class GdbCrossConfigNone(GdbCrossConfig): default_mode) def _target_gdbserver_stop_cmd(self, server_mode): - """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) - # This is unexpected since gdbserver should terminate after each debug session - # Just kill all gdbserver instances to keep it simple - else: - gdbserver_cmd_stop = "killall gdbserver" + """Kill a gdbserver process + + Stopping is based on the PID file written by the start command. Other + debug sessions run their own gdbserver on the target, so anything + matching by process name would hit them as well. + """ + 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) return "\"/bin/sh -c '" + gdbserver_cmd_stop + "'\"" def _gen_gdbserver_start_script(self, server_mode=None): @@ -165,14 +168,19 @@ class LldbServerConfigNone(LldbServerConfig): return os.path.join(self.script_dir, 'lldb_' + self.id_pretty) def _target_lldb_server_stop_cmd(self, server_mode): - """SSH command to stop lldb-server on the target.""" - 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}) - else: - cmd = "killall lldb-server 2>/dev/null || true" + """SSH command to stop lldb-server on the target. + + Stopping is based on the PID file written by the start command. Other + debug sessions run their own lldb-server on the target, so anything + matching by process name would hit them as well. + """ + pid_file = self._lldb_server_pid_file(server_mode) + tmp_dir = self._lldb_server_tmp_dir(server_mode) + 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 return "\"/bin/sh -c '" + cmd + "'\"" def _gen_lldb_server_start_script(self, server_mode=None): From patchwork Sun Aug 16 19:40:54 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 95457 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id B97C2C5DF79 for ; Sun, 16 Aug 2026 19:41:42 +0000 (UTC) Received: from mta-64-225.siemens.flowmailer.net (mta-64-225.siemens.flowmailer.net [185.136.64.225]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.16147.1786909295039641110 for ; Sun, 16 Aug 2026 12:41:37 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=bWA6QbxZ; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.64.225, mailfrom: fm-1329275-202608161941337894109740000207f2-rbb012@rts-flowmailer.siemens.com) Received: by mta-64-225.siemens.flowmailer.net with ESMTPSA id 202608161941337894109740000207f2 for ; Sun, 16 Aug 2026 21:41:33 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm2; d=siemens.com; i=adrian.freihofer@siemens.com; h=Date:From:Subject:To:Message-ID:MIME-Version:Content-Type:Content-Transfer-Encoding:Cc:References:In-Reply-To; bh=QMISVk6pfFFVobY/G982l2WwDFAoVsk8Bx8dKHcsqvM=; b=bWA6QbxZu0qHcstdhdwz44614HgldHzUmVnXN5FBQtaqwurPDBEjmN0Imrrkgcb+vVCsSl s+CU11eT76VYofjmsbl7JcXfrImdKUND5pzyDldDSaB3/8kuCAGokjXmLK9i3YVU+INXhlvW DWh3XAPg6QRpwRR5dSd22XURZwHxgINwZC9zftjBhmRq3eMZZP2klfl5Y7eArkV/Qfx6fLUP o/YUwolZftGl8yjn0mLjJEswGpzK27qLegsewdDHHTN02KEUoIr5PlXFe4Ypl2K4ewtoDl4L zq5HrnNDmSzubcxNtBOIncl63c2bDw2AEGN9g9PlENd+trR9kcd7DWsQ==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 11/14] oe-selftest: devtool ide-sdk: wait for gdbserver readiness Date: Sun, 16 Aug 2026 21:40:54 +0200 Message-ID: <20260816194127.86607-12-adrian.freihofer@siemens.com> In-Reply-To: <20260816194127.86607-1-adrian.freihofer@siemens.com> References: <20260816194127.86607-1-adrian.freihofer@siemens.com> MIME-Version: 1.0 X-Flowmailer-Platform: Siemens Feedback-ID: 519:519-1329275:519-21489:flowmailer List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Sun, 16 Aug 2026 19:41:42 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/243558 From: Adrian Freihofer 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 --- meta/lib/oeqa/selftest/cases/devtool.py | 33 +++++++++++++++++-------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py index a07214f8b3..493889aa51 100644 --- a/meta/lib/oeqa/selftest/cases/devtool.py +++ b/meta/lib/oeqa/selftest/cases/devtool.py @@ -2753,6 +2753,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() @@ -3533,16 +3547,15 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests): 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('\\$', '$') # Remove surrounding quotes 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"] + # Must exceed the target side budget (TARGET_START_RETRIES * 0.1s), + # otherwise this gives up while the target is still waiting and its + # diagnostics never make it into the failure. + self.assertTrue( + gdbserver.wait_for_output(ready_pattern, timeout=60), + "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 From patchwork Sun Aug 16 19:40:55 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 95458 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id A57FEC5DF70 for ; Sun, 16 Aug 2026 19:41:42 +0000 (UTC) Received: from mta-64-226.siemens.flowmailer.net (mta-64-226.siemens.flowmailer.net [185.136.64.226]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.16144.1786909295038863603 for ; Sun, 16 Aug 2026 12:41:37 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=MMnq/LgF; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.64.226, mailfrom: fm-1329275-202608161941338ee8f4f462000207f3-4cdbur@rts-flowmailer.siemens.com) Received: by mta-64-226.siemens.flowmailer.net with ESMTPSA id 202608161941338ee8f4f462000207f3 for ; Sun, 16 Aug 2026 21:41:33 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm2; d=siemens.com; i=adrian.freihofer@siemens.com; h=Date:From:Subject:To:Message-ID:MIME-Version:Content-Type:Content-Transfer-Encoding:Cc:References:In-Reply-To; bh=e90qBIz/EPFvdVPU2gw8TSxS0BFIhM9ckACsIXQGrxM=; b=MMnq/LgF9ZpclZP+aXnh1NzqBPeEnEUHcUcR89pP4yePdgBFOvqFijY8o8d1h4SEL9m8Sz 8XZBtlNSTFqUPPt3LCSW/T2q1GKRUimIM89dCh3fY8nkxZCnU4iLDq+wOgSjvyytwwy0xuoN kvaXeQttR/scm1zVPg58UAem6lSBpeDBsrjPzxz7pvGPx98K5UzvAuaBY+iWIhDnvxEhtVKp VWR7dTsqHzfYfd58iNTzDdo6ST+Ir098Yrnve29Stk9M+xVvi2raJzxESALHzMZhv+6zZYvq ya515LG56+UAhzrUumOuEFziE0I/93wta6x5uszI3uvUGg/UFngbnDkg==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 12/14] oe-selftest: devtool ide-sdk: verify debugger shutdown Date: Sun, 16 Aug 2026 21:40:55 +0200 Message-ID: <20260816194127.86607-13-adrian.freihofer@siemens.com> In-Reply-To: <20260816194127.86607-1-adrian.freihofer@siemens.com> References: <20260816194127.86607-1-adrian.freihofer@siemens.com> MIME-Version: 1.0 X-Flowmailer-Platform: Siemens Feedback-ID: 519:519-1329275:519-21489:flowmailer List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Sun, 16 Aug 2026 19:41:42 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/243559 From: Adrian Freihofer 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 --- 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 493889aa51..a16c7a93f6 100644 --- a/meta/lib/oeqa/selftest/cases/devtool.py +++ b/meta/lib/oeqa/selftest/cases/devtool.py @@ -3218,7 +3218,6 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests): 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( @@ -3228,19 +3227,18 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests): 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( @@ -3257,10 +3255,10 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests): 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) @OETestTag("runqemu") def test_devtool_ide_sdk_none_qemu(self): @@ -4424,7 +4422,6 @@ class DevtoolIdeSdkClangTests(DevtoolIdeSdkTests): 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( @@ -4438,19 +4435,16 @@ class DevtoolIdeSdkClangTests(DevtoolIdeSdkTests): 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 @@ -4465,11 +4459,10 @@ class DevtoolIdeSdkClangTests(DevtoolIdeSdkTests): 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(self): From patchwork Sun Aug 16 19:40:56 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 95464 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id 01A16C5DF80 for ; Sun, 16 Aug 2026 19:41:44 +0000 (UTC) Received: from mta-64-226.siemens.flowmailer.net (mta-64-226.siemens.flowmailer.net [185.136.64.226]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.15978.1786909295040249752 for ; Sun, 16 Aug 2026 12:41:36 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=VIyB+lTj; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.64.226, mailfrom: fm-1329275-20260816194133ca9ac35e2500020713-itmpmf@rts-flowmailer.siemens.com) Received: by mta-64-226.siemens.flowmailer.net with ESMTPSA id 20260816194133ca9ac35e2500020713 for ; Sun, 16 Aug 2026 21:41:33 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm2; d=siemens.com; i=adrian.freihofer@siemens.com; h=Date:From:Subject:To:Message-ID:MIME-Version:Content-Type:Content-Transfer-Encoding:Cc:References:In-Reply-To; bh=vLI9dq9b/VIpzoEP676wI0SilGyRypv7Zi6uiOOs9ns=; b=VIyB+lTjMb5LF59et8C17R1JqosH/XnlC40qPzhflRANxNa1bEehXE1M6Fp+bQr0aF5Pa/ ZTb2cyKFYc9krCi54owPpxW30PMnFRe8968JeNApQWshl4HaZ5bZ8k2mnik6U9W8/OUpSth9 XlSnzgAu/JWMU2TV6NPYhgfhMaovTBvhqh84Dz9GuPywCiJc1nEHT0wBR89tctLH4rM8ebZ0 xwbpNKgkoPTwWtuOBVJuqVzYbKq3cJu+nYjONIzWvL3QW4n6l8JPGOsn2KfJbqUzRi5iLEMt Kikvr8kKuOa0LpzX5Y1L3F7sgh1nh7UV1Agw4Nq2n4jsbgBnR7Yc6IbA==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 13/14] oe-selftest: devtool ide-sdk: use qemu.run for target checks Date: Sun, 16 Aug 2026 21:40:56 +0200 Message-ID: <20260816194127.86607-14-adrian.freihofer@siemens.com> In-Reply-To: <20260816194127.86607-1-adrian.freihofer@siemens.com> References: <20260816194127.86607-1-adrian.freihofer@siemens.com> MIME-Version: 1.0 X-Flowmailer-Platform: Siemens Feedback-ID: 519:519-1329275:519-21489:flowmailer List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Sun, 16 Aug 2026 19:41:44 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/243550 From: Adrian Freihofer Use QEMU target commands instead of spawning a separate ssh process for simple target-side executable checks in the ide=code selftest helper. This keeps the test consistent with the rest of the qemu-based cleanup and avoids duplicating SSH option handling in the test itself. Signed-off-by: Adrian Freihofer --- meta/lib/oeqa/selftest/cases/devtool.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py index a16c7a93f6..5301b6013e 100644 --- a/meta/lib/oeqa/selftest/cases/devtool.py +++ b/meta/lib/oeqa/selftest/cases/devtool.py @@ -3529,13 +3529,10 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests): 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 From patchwork Sun Aug 16 19:40:57 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: AdrianF X-Patchwork-Id: 95459 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id D0F8CC5DF66 for ; Sun, 16 Aug 2026 19:41:42 +0000 (UTC) Received: from mta-64-227.siemens.flowmailer.net (mta-64-227.siemens.flowmailer.net [185.136.64.227]) by mx.groups.io with SMTP id smtpd.msgproc01-g2.16143.1786909295038730740 for ; Sun, 16 Aug 2026 12:41:37 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=adrian.freihofer@siemens.com header.s=fm2 header.b=GuP4xUqq; spf=pass (domain: rts-flowmailer.siemens.com, ip: 185.136.64.227, mailfrom: fm-1329275-20260816194133183039de2100020751-jf2zpl@rts-flowmailer.siemens.com) Received: by mta-64-227.siemens.flowmailer.net with ESMTPSA id 20260816194133183039de2100020751 for ; Sun, 16 Aug 2026 21:41:33 +0200 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; s=fm2; d=siemens.com; i=adrian.freihofer@siemens.com; h=Date:From:Subject:To:Message-ID:MIME-Version:Content-Type:Content-Transfer-Encoding:Cc:References:In-Reply-To; bh=jr5FrpUu+jUtL4D2+jd2IOYubjPS2PB7HwN6uh0M4CY=; b=GuP4xUqqz3ycYAabaU3n+lYcbd1VyXYAJ8ReYwoyY2H/bdvD7bIWEDl9vuwuutl1A6GucZ RvD5RNyG/WHQ/Gu0Wns1OheL4vSjACzyWoEt4lpgsTa3wJ2h9nhr3RjzQKHobEBD0mY2ozod GF4Qjl2C4PA2qeKOXpEvZgBHn1ts1cIgd/moQsPd4v+ko6uP9PuhzNPeRgiVb7ZqeXavH2zv 1m4jS2LtAq3Q8dI6CGL/Z7/KZZ90/bmg8oxFTp0RS6snP82BxVAW69DhVArq6wzyQ05k72Ce JLVCEQHkre2t2vf/3n8UrYzjR4yfGFrzZl8dRWXCpnJ69gTU6nzAVXIw==; From: AdrianF To: openembedded-core@lists.openembedded.org Cc: Adrian Freihofer Subject: [PATCH 14/14] oe-selftest: devtool ide-sdk: verify attach debug workflow Date: Sun, 16 Aug 2026 21:40:57 +0200 Message-ID: <20260816194127.86607-15-adrian.freihofer@siemens.com> In-Reply-To: <20260816194127.86607-1-adrian.freihofer@siemens.com> References: <20260816194127.86607-1-adrian.freihofer@siemens.com> MIME-Version: 1.0 X-Flowmailer-Platform: Siemens Feedback-ID: 519:519-1329275:519-21489:flowmailer List-Id: X-Webhook-Received: from 45-33-107-173.ip.linodeusercontent.com [45.33.107.173] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Sun, 16 Aug 2026 19:41:42 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/243557 From: Adrian Freihofer Add attach-mode debug verification in ide=code selftests and document the intended execution flow: - Verify attach postDebugTask labels against tasks.json. - Run attach checks before once checks to avoid Build-ID mismatch from once/deploy side effects on the still-running service process. - In attach mode, set a temporary breakpoint on sleep(), continue once to hit it, then detach immediately. This verifies debugger control flow against the already-running loop without fragile inferior expression evaluation. Signed-off-by: Adrian Freihofer --- meta/lib/oeqa/selftest/cases/devtool.py | 152 +++++++++++++++++++----- 1 file changed, 119 insertions(+), 33 deletions(-) diff --git a/meta/lib/oeqa/selftest/cases/devtool.py b/meta/lib/oeqa/selftest/cases/devtool.py index 5301b6013e..52aa199319 100644 --- a/meta/lib/oeqa/selftest/cases/devtool.py +++ b/meta/lib/oeqa/selftest/cases/devtool.py @@ -3339,10 +3339,19 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests): def _verify_launch_json(self, tempdir): """Verify the launch.json file created is valid and contains proper debug configurations""" launch_json_path = os.path.join(tempdir, '.vscode', 'launch.json') + tasks_json_path = os.path.join(tempdir, '.vscode', 'tasks.json') self.assertTrue(os.path.exists(launch_json_path), "launch.json file should exist") + self.assertTrue(os.path.exists(tasks_json_path), "tasks.json file should exist") with open(launch_json_path) as launch_j: launch_d = json.load(launch_j) + with open(tasks_json_path) as tasks_j: + tasks_d = json.load(tasks_j) + task_labels = { + task.get("label") + for task in tasks_d.get("tasks", []) + if task.get("label") + } self.assertIn("configurations", launch_d) configurations = launch_d["configurations"] @@ -3418,6 +3427,8 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests): self.assertIn("_attach", task, f"attach configuration '{config_name}' should have attach preLaunchTask") # Verify postDebugTask exists for attach configurations (kill gdbserver) self.assertIn("postDebugTask", config, f"attach configuration '{config_name}' should have postDebugTask") + self.assertIn(config["postDebugTask"], task_labels, + f"attach configuration '{config_name}' postDebugTask should exist in tasks.json") # Categorize configurations config_name = config["name"] @@ -3431,34 +3442,11 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests): self.assertEqual(len(attach_configs), 1, f"Should have one attach configuration, found: {attach_configs}") def _verify_launch_json_debugging(self, tempdir, qemu, example_exe): - """Verify remote debugging and deployment works using launch.json configurations + """Verify launch.json debugging configs for one target executable. - This method tests the VSCode debug configurations by: - 1. Starting gdbserver on target using tasks.json commands - 2. Running gdb debugging session with batch commands - $BUILDDIR/tmp/work/x86_64-linux/gdb-cross-x86_64/16.3/recipe-sysroot-native/usr/bin/x86_64-poky-linux/x86_64-poky-linux-gdb --batch \ - -ex 'set sysroot $BUILDDIR/tmp/work/x86-64-v3-poky-linux/cmake-example/1.0/image' \ - -ex 'set substitute-path /usr/include $BUILDDIR/tmp/work/x86-64-v3-poky-linux/cmake-example/1.0/recipe-sysroot/usr/include' \ - -ex 'set substitute-path /usr/src/debug/cmake-example/1.0 $BUILDDIR/workspace/sources/cmake-example' \ - -ex 'set substitute-path /usr/src/debug $BUILDDIR/tmp/work/qemux86_64-poky-linux/oe-selftest-image/1.0/rootfs-dbg/usr/src/debug' \ - -ex 'set solib-search-path $BUILDDIR/tmp/work/qemux86_64-poky-linux/oe-selftest-image/1.0/rootfs-dbg/lib/.debug:\ - $BUILDDIR/tmp/work/qemux86_64-poky-linux/oe-selftest-image/1.0/rootfs-dbg/usr/lib/.debug:\ - $BUILDDIR/tmp/work/qemux86_64-poky-linux/oe-selftest-image/1.0/rootfs-dbg/usr/lib/debug:\ - $BUILDDIR/tmp/work/qemux86_64-poky-linux/oe-selftest-image/1.0/rootfs-dbg/lib:\ - $BUILDDIR/tmp/work/qemux86_64-poky-linux/oe-selftest-image/1.0/rootfs-dbg/usr/lib:\ - $BUILDDIR/tmp/work/qemux86_64-poky-linux/oe-selftest-image/1.0/rootfs/lib:\ - $BUILDDIR/tmp/work/qemux86_64-poky-linux/oe-selftest-image/1.0/rootfs/usr/lib' \ - -ex 'file $BUILDDIR/tmp/work/x86-64-v3-poky-linux/cmake-example/1.0/image/usr/bin/cmake-example' \ - -ex 'target remote 192.168.7.2:1234' \ - -ex 'break main' \ - -ex 'continue' \ - -ex 'break CppExample::print_json()' \ - -ex 'continue' \ - -ex 'print CppExample::test_string.compare("cpp-example-lib Magic: 123456789")' \ - -ex 'print CppExample::test_string.compare("cpp-example-lib Magic: 123456789aaa")' \ - -ex 'list cpp-example-lib.hpp:15,15' \ - -ex 'continue' - 3. Verifying debug output and stopping gdbserver + Runs attach-mode first against the already-running service process, + then validates the once configuration. This ordering avoids Build-ID + mismatch from once/deploy side effects before attach is tested. """ with open(os.path.join(tempdir, '.vscode', 'launch.json')) as launch_j: launch_d = json.load(launch_j) @@ -3474,18 +3462,116 @@ class DevtoolIdeSdkGccTests(DevtoolIdeSdkTests): # as the recipe name (e.g. meson-example installs a binary named # "mesonex"). once_config_count = 0 + attach_config_count = 0 + + # Run attach first. The attach test targets the already-running + # service process in the image; running a once configuration first can + # deploy/update binaries and trigger Build-ID mismatch warnings against + # that still-running process. + for config in configurations: + if f"usr-bin-{example_exe}_attach" in config["name"]: + attach_config_count += 1 + self._verify_launch_config_attach(tempdir, config, qemu, example_exe) + for config in configurations: if f"usr-bin-{example_exe}_once" in config["name"]: once_config_count += 1 self._verify_launch_config(tempdir, config, tasks, qemu, example_exe, self._gdb_debug_cpp_example, self._gdb_debug_cpp_example_check) - # It works but is not 100% reliable in VSCode - # This one: https://github.com/microsoft/vscode-cpptools/issues/4243 ? - # elif f"usr-bin-{example_exe}_attach" in config["name"] - # self._verify_launch_config(tempdir, config, tasks, qemu, example_exe) - else: - continue self.assertEqual(once_config_count, 1, f"Should have one once configuration, found: {once_config_count}") + self.assertEqual(attach_config_count, 1, f"Should have one attach configuration, found: {attach_config_count}") + + def _verify_launch_config_attach(self, tempdir, launch_config, qemu, example_exe): + """Verify attach-mode debugging by exercising debugger control flow. + + The attached service runs an endless loop, and each loop iteration + executes sleep(). We break on sleep() to prove that attach-mode gdb is + controlling the already-running process at a point that is expected to + be reached repeatedly during normal service execution, then detach. + """ + self.assertEqual(launch_config.get("request"), "attach") + + tasks_path = os.path.join(tempdir, '.vscode', 'tasks.json') + self.assertExists(tasks_path, 'tasks.json not found at %s' % tasks_path) + with open(tasks_path) as tasks_j: + tasks_d = json.load(tasks_j) + task_by_label = { + task.get('label'): task + for task in tasks_d.get('tasks', []) + if task.get('label') + } + + debugger_path = launch_config["miDebuggerPath"] + server_addr = launch_config["miDebuggerServerAddress"] + prelaunch_task_name = launch_config["preLaunchTask"] + post_debug_task_name = launch_config.get("postDebugTask") + program = launch_config["program"] + additional_so_lib_search_path = launch_config["additionalSOLibSearchPath"] + source_file_map = launch_config["sourceFileMap"] + setup_commands = launch_config["setupCommands"] + + self.assertTrue(post_debug_task_name, + "attach configuration should define postDebugTask") + self.assertIn(prelaunch_task_name, task_by_label) + self.assertIn(post_debug_task_name, task_by_label) + + 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") + + # The attach preLaunchTask has no dependsOn (it attaches to an already + # running service instead of building/deploying), so it can be run + # directly without a dependency chain runner. + prelaunch_task = task_by_label[prelaunch_task_name] + prelaunch_cmd = [prelaunch_task["command"]] + [str(arg) + for arg in prelaunch_task.get("args", [])] + # The tasks.json command is shell-quoted and escapes '$' for an + # intermediate shell, so undo that before passing argv to subprocess. + if prelaunch_cmd[-1].startswith('"') and prelaunch_cmd[-1].endswith('"'): + prelaunch_cmd[-1] = prelaunch_cmd[-1][1:-1].replace('\\$', '$') + runCmd(prelaunch_cmd, output_log=self._cmd_logger) + + # Attach to the service process currently running on the target. + status, output = qemu.run("pgrep '^%s$'" % example_exe) + self.assertEqual(status, 0, msg="%s service not running: %s" % + (example_exe, output)) + service_pid = output.strip() + self.assertRegex(service_pid, r'^\d+$') + + gdb_batch_cmd = debugger_path + " --batch" + for setup_command in setup_commands: + setup_cmd = setup_command["text"].strip() + if setup_cmd.startswith("-"): + continue + gdb_batch_cmd += ' -ex ' + shlex.quote(setup_cmd) + for k, v in source_file_map.items(): + gdb_batch_cmd += " -ex 'set substitute-path %s %s'" % (k, v.replace("${workspaceFolder}", tempdir)) + gdb_batch_cmd += " -ex 'set solib-search-path %s'" % additional_so_lib_search_path + gdb_batch_cmd += " -ex 'file %s'" % program + gdb_batch_cmd += " -ex 'target extended-remote %s'" % server_addr + gdb_batch_cmd += " -ex 'attach %s'" % service_pid + gdb_batch_cmd += " -ex 'set breakpoint pending on'" + gdb_batch_cmd += " -ex 'tbreak sleep'" + gdb_batch_cmd += " -ex 'continue'" + gdb_batch_cmd += " -ex 'detach'" + self.logger.debug(f"Starting attach gdb session with command: {gdb_batch_cmd}") + + r = runCmd(gdb_batch_cmd, output_log=self._cmd_logger) + self.assertEqual(r.status, 0) + self.assertIn("sleep", r.output) + + # Stop the persistent attach server and verify the service survives. + post_debug_task = task_by_label[post_debug_task_name] + post_debug_cmd = [post_debug_task["command"]] + [str(arg) + for arg in post_debug_task.get("args", [])] + if post_debug_cmd[-1].startswith('"') and post_debug_cmd[-1].endswith('"'): + post_debug_cmd[-1] = post_debug_cmd[-1][1:-1].replace('\\$', '$') + runCmd(post_debug_cmd, output_log=self._cmd_logger) + + status, output = qemu.run("pgrep '^%s$'" % example_exe) + self.assertEqual(status, 0, msg="%s service should still be running after attach stop: %s" % + (example_exe, output)) def _verify_launch_config(self, tempdir, launch_config, tasks, qemu, example_exe, debug_func=None, debug_check_func=None): self.assertIsNotNone(launch_config, "Should have at least one launch debug configuration")