From patchwork Fri Sep 11 22:01:38 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Mathieu Othacehe X-Patchwork-Id: 98058 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 2A177C88E50 for ; Fri, 11 Sep 2026 22:02:08 +0000 (UTC) Received: from eggs.gnu.org (eggs.gnu.org [209.51.188.92]) by mx.groups.io with SMTP id smtpd.msgproc02-g2.50712.1789164120771440678 for ; Fri, 11 Sep 2026 15:02:01 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=@gnu.org header.s=fencepost-gnu-org header.b=g0y1kYFY; spf=pass (domain: gnu.org, ip: 209.51.188.92, mailfrom: othacehe@gnu.org) Received: from fencepost.gnu.org ([2001:470:142:3::e]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x59Jn-0001WB-Fr; Fri, 11 Sep 2026 18:01:59 -0400 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=gnu.org; s=fencepost-gnu-org; h=MIME-Version:References:In-Reply-To:Date:Subject:To: From; bh=pX5yXkIWvHPbpoEdBjE4Ez6Y/WoiTt7QNG+jKF3zgnA=; b=g0y1kYFYFt5RRZXOsFq3 HZIzU6CX3A+tmoM+BiGr638T4JAXO4nfkbLuMwKAZfmHwT6rd08jSax2OtQZndCrdVgILoh33dqtE jUzRdDiznPxx+p/bMiydVFK9BtiItRdJxszdRrzbu3QsND/ycLqdt3d7yo6cpRZvSZt9w5P2WlrOp z0r4hD9hqEI/la61Ts7yfjzjfLlHtnwr68jL0RB8N+zS1AKwq/OB0MzuTbIKBz7d8THwAhn7Gl7Zf jrhfINFO6LJbI8wOEPgSj+Y1Ia28Gr3Y5fvT7Sv72x9QxXu5ephjJytpuD8pNlsG7e1KnU7pX5Lia SHtvJTK8bBvQ6A==; From: Mathieu Othacehe To: openembedded-core@lists.openembedded.org Cc: Alexander Kanavin , Khem Raj , Richard Purdie , Mathieu Othacehe Subject: [PATCH 1/1] lib/oe/package: Add strip keep-section support Date: Sat, 12 Sep 2026 00:01:38 +0200 Message-ID: <20260911220138.32414-2-othacehe@gnu.org> X-Mailer: git-send-email 2.54.0 In-Reply-To: <20260911220138.32414-1-othacehe@gnu.org> References: <20260911220138.32414-1-othacehe@gnu.org> MIME-Version: 1.0 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 ; Fri, 11 Sep 2026 22:02:08 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/245669 On 32-bit Arm, the .ARM.extab and .ARM.exidx unwinding sections do not always provide enough information to get a full backtrace on C++ exceptions: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=117941 In addition to those unwinding sections, GCC also emits unwinding instructions in DWARF format under the .debug_frame section. By instructing 'strip' not to remove that section, libunwind can use it to produce a full backtrace, for example when an unhandled C++ exception is thrown. Add a PACKAGE_KEEP_SECTIONS variable, listing space separated ELF section names that should be kept around instead of stripped, for example: PACKAGE_KEEP_SECTIONS:pn-myrecipe = ".debug_frame" This variable is never applied to kernel modules, which are covered by CONFIG_UNWINDER_* instead. Add a oe-selftest case building core-image-minimal for qemuarm, and checking with readelf that busybox loses its .debug_frame section by default, but keeps it once PACKAGE_KEEP_SECTIONS is set. Signed-off-by: Mathieu Othacehe --- meta/classes-global/staging.bbclass | 4 +- meta/lib/oe/package.py | 19 +++++++-- meta/lib/oeqa/selftest/cases/package.py | 52 ++++++++++++++++++++++++- 3 files changed, 69 insertions(+), 6 deletions(-) diff --git a/meta/classes-global/staging.bbclass b/meta/classes-global/staging.bbclass index 5833abebc8..9ea4c644db 100644 --- a/meta/classes-global/staging.bbclass +++ b/meta/classes-global/staging.bbclass @@ -91,10 +91,12 @@ python sysroot_strip () { base_libdir = d.getVar("base_libdir") qa_already_stripped = 'already-stripped' in (d.getVar('INSANE_SKIP:' + pn) or "").split() strip_cmd = d.getVar("STRIP") + keep_sections = d.getVar('PACKAGE_KEEP_SECTIONS') or "" max_process = oe.utils.get_bb_number_threads(d) oe.package.strip_execs(pn, dstdir, strip_cmd, libdir, base_libdir, max_process, - qa_already_stripped=qa_already_stripped) + qa_already_stripped=qa_already_stripped, + keep_sections=keep_sections) } do_populate_sysroot[dirs] = "${SYSROOT_DESTDIR}" diff --git a/meta/lib/oe/package.py b/meta/lib/oe/package.py index b7030643d2..620f9fefc8 100644 --- a/meta/lib/oe/package.py +++ b/meta/lib/oe/package.py @@ -19,7 +19,7 @@ import shutil import bb.parse import oe.cachedpath -def runstrip(file, elftype, strip, extra_strip_sections=''): +def runstrip(file, elftype, strip, extra_strip_sections='', keep_sections=''): # Function to strip a single file, called from split_and_strip_files below # A working 'file' (one which works on the target architecture) # @@ -49,6 +49,10 @@ def runstrip(file, elftype, strip, extra_strip_sections=''): for section in extra_strip_sections.split(): stripcmd.extend(["--remove-section=" + section]) + if keep_sections != '' and not elftype & 16: + for section in keep_sections.split(): + stripcmd.extend(["--keep-section=" + section]) + stripcmd.append(file) bb.debug(1, "runstrip: %s" % stripcmd) @@ -96,7 +100,8 @@ def is_static_lib(path): return start == magic return False -def strip_execs(pn, dstdir, strip_cmd, libdir, base_libdir, max_process, qa_already_stripped=False): +def strip_execs(pn, dstdir, strip_cmd, libdir, base_libdir, max_process, + qa_already_stripped=False, keep_sections=''): """ Strip executable code (like executables, shared libraries) _in_place_ - Based on sysroot_strip in staging.bbclass @@ -107,6 +112,9 @@ def strip_execs(pn, dstdir, strip_cmd, libdir, base_libdir, max_process, qa_alre :param max_process: number of stripping processes started in parallel :param qa_already_stripped: Set to True if already-stripped' in ${INSANE_SKIP} This is for proper logging and messages only. + :param keep_sections: Space separated list of ELF sections to keep even + though the file is being stripped, for example ".debug_frame" so that + libunwind can use it to generate backtraces. """ import stat, errno, oe.path, oe.utils @@ -175,7 +183,8 @@ def strip_execs(pn, dstdir, strip_cmd, libdir, base_libdir, max_process, qa_alre elf_file = int(elffiles[file]) sfiles.append((file, elf_file, strip_cmd)) - oe.utils.multiprocess_launch_mp(runstrip, sfiles, max_process) + oe.utils.multiprocess_launch_mp(runstrip, sfiles, max_process, + extraargs=('', keep_sections)) TRANSLATE = ( ("@", "@at@"), @@ -1359,7 +1368,9 @@ def process_split_and_strip_files(d): for f in staticlibs: sfiles.append((f, 16, strip)) - oe.utils.multiprocess_launch(oe.package.runstrip, sfiles, d) + keep_sections = d.getVar('PACKAGE_KEEP_SECTIONS') or "" + oe.utils.multiprocess_launch(oe.package.runstrip, sfiles, d, + extraargs=('', keep_sections)) # Build "minidebuginfo" and reinject it back into the stripped binaries if bb.utils.contains('DISTRO_FEATURES', 'minidebuginfo', True, False, d): diff --git a/meta/lib/oeqa/selftest/cases/package.py b/meta/lib/oeqa/selftest/cases/package.py index 38ed7173fe..96aee21177 100644 --- a/meta/lib/oeqa/selftest/cases/package.py +++ b/meta/lib/oeqa/selftest/cases/package.py @@ -5,10 +5,12 @@ # from oeqa.selftest.case import OESelftestTestCase -from oeqa.utils.commands import bitbake, get_bb_vars, get_bb_var, runqemu +from oeqa.utils.commands import bitbake, get_bb_vars, get_bb_var, runqemu, runCmd import subprocess, os import oe.path import re +import tempfile +import tarfile class VersionOrdering(OESelftestTestCase): # version1, version2, sort order @@ -208,3 +210,51 @@ class PackageTests(OESelftestTestCase): sysconfdir + "/selftest-chown/symlink", sysconfdir + "/selftest-chown/fifotest/fifo"]: check_ownership(qemu, "test", "test", path) + +class PackageKeepSections(OESelftestTestCase): + def test_package_keep_sections(self): + """ + Verify that PACKAGE_KEEP_SECTIONS prevents 'strip' from removing the + listed ELF sections, and that they are removed as usual when the + variable isn't set. + """ + # GCC only emits .debug_frame on targets that don't already rely on + # .eh_frame for unwinding, which in practice means 32-bit Arm: use + # qemuarm so the section actually exists before strip runs. Do this + # before querying any other variable below, as they all depend on + # MACHINE. + self.write_config(""" +MACHINE = "qemuarm" +IMAGE_FSTYPES = "tar.bz2" +""") + + target_sys = get_bb_var("TARGET_SYS") + bb_vars = get_bb_vars(['DEPLOY_DIR_IMAGE', 'IMAGE_LINK_NAME', 'READELF'], 'core-image-minimal') + binutils = "binutils-cross-{}".format(get_bb_var("TARGET_ARCH")) + bitbake("{}:do_addto_recipe_sysroot".format(binutils)) + native_sysroot = get_bb_var("RECIPE_SYSROOT_NATIVE", binutils) + + def has_section(section): + with tempfile.TemporaryDirectory(prefix = "unpackfs-") as unpackedfs: + filename = os.path.join(bb_vars['DEPLOY_DIR_IMAGE'], "{}.tar.bz2".format(bb_vars['IMAGE_LINK_NAME'])) + with tarfile.open(filename) as tar: + tar.extract("./usr/bin/busybox.nosuid", path=unpackedfs) + + r = runCmd([bb_vars['READELF'], "-W", "-S", os.path.join(unpackedfs, "usr", "bin", "busybox.nosuid")], + native_sysroot = native_sysroot, target_sys = target_sys) + return section in r.output + + bitbake("core-image-minimal") + self.assertFalse(has_section(".debug_frame"), + "busybox should not carry a .debug_frame section by default") + + self.write_config(""" +MACHINE = "qemuarm" +IMAGE_FSTYPES = "tar.bz2" +PACKAGE_KEEP_SECTIONS:pn-busybox = ".debug_frame" +""") + bitbake("busybox -c package -f") + bitbake("core-image-minimal") + self.assertTrue(has_section(".debug_frame"), + "busybox should carry a .debug_frame section when " + "PACKAGE_KEEP_SECTIONS is set")