All of lore.kernel.org
 help / color / mirror / Atom feed
From: Mathieu Othacehe <othacehe@gnu.org>
To: openembedded-core@lists.openembedded.org
Cc: Alexander Kanavin <alex.kanavin@gmail.com>,
	Khem Raj <raj.khem@gmail.com>,
	Richard Purdie <richard.purdie@linuxfoundation.org>,
	Mathieu Othacehe <othacehe@gnu.org>
Subject: [PATCH 1/1] lib/oe/package: Add strip keep-section support
Date: Sat, 12 Sep 2026 00:01:38 +0200	[thread overview]
Message-ID: <20260911220138.32414-2-othacehe@gnu.org> (raw)
In-Reply-To: <20260911220138.32414-1-othacehe@gnu.org>

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 <othacehe@gnu.org>
---
 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")
-- 
2.34.1



  reply	other threads:[~2026-09-11 22:02 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-11 22:01 [PATCH v3 0/1] lib/oe/package: Add strip keep-section support Mathieu Othacehe
2026-09-11 22:01 ` Mathieu Othacehe [this message]
2026-09-12 12:18   ` [OE-core] [PATCH 1/1] " Mathieu Dubois-Briand

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260911220138.32414-2-othacehe@gnu.org \
    --to=othacehe@gnu.org \
    --cc=alex.kanavin@gmail.com \
    --cc=openembedded-core@lists.openembedded.org \
    --cc=raj.khem@gmail.com \
    --cc=richard.purdie@linuxfoundation.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.