* [PATCH v3 0/1] lib/oe/package: Add strip keep-section support
@ 2026-09-11 22:01 Mathieu Othacehe
2026-09-11 22:01 ` [PATCH 1/1] " Mathieu Othacehe
0 siblings, 1 reply; 3+ messages in thread
From: Mathieu Othacehe @ 2026-09-11 22:01 UTC (permalink / raw)
To: openembedded-core
Cc: Alexander Kanavin, Khem Raj, Richard Purdie, Mathieu Othacehe
Hello,
This is v3 of the .debug_frame / keep-section patch, picking back up the
thread from last year:
v1 (PACKAGE_KEEP_DEBUG_FRAME):
https://lists.openembedded.org/g/openembedded-core/message/203797
v2 (PACKAGE_KEEP_SECTIONS):
https://patchwork.yoctoproject.org/project/oe-core/patch/20250204103744.27883-1-othacehe@gnu.org/
Quick recap of the motivation: on 32-bit Arm, GCC's default EHABI unwind
tables (.ARM.exidx / .ARM.extab) don't currently allow libunwind to
produce a full backtrace once execution reaches the C++ termination path
(https://gcc.gnu.org/bugzilla/show_bug.cgi?id=117941). GCC also emits
DWARF Call Frame Information for the same functions under .debug_frame,
which does allow a complete backtrace, but that section is unconditionally
stripped today. This series lets a recipe opt back in on a per-section
basis via PACKAGE_KEEP_SECTIONS, e.g.:
PACKAGE_KEEP_SECTIONS:pn-myrecipe = ".debug_frame"
What's new in v3:
- Rebased on master, in particular on top of the runstrip() argument
cleanup that landed since v2 -- no more tuple unpacking
- Added an oe-selftest case (package.PackageKeepSections) that builds
core-image-minimal for qemuarm and uses readelf to show busybox loses
.debug_frame by default and keeps it once PACKAGE_KEEP_SECTIONS is set,
per Alexander's request for test coverage on v2.
- A couple of open points from the v2 thread I'd like to address before
this goes further:
* Richard asked whether this should just be folded into minidebuginfo
instead of being a separate "magic" option. minidebuginfo only
injects compressed *symbol* information (.gnu_debugdata); it says
nothing about unwind data. PACKAGE_KEEP_SECTIONS is orthogonal --
you need both symbols and unwind information to get a readable
on-target backtrace, but a user may reasonably want only one of the
two (e.g. GDB against a remote symbol store vs. a fully standalone
coredump). I'd rather keep this as a separate, generic, opt-in knob
than silently grow every minidebuginfo image by ~70KB per Arm binary
that uses C++ exceptions.
* Khem asked whether minidebuginfo could reuse .ARM.exidx/.ARM.extab
the way it uses .eh_frame elsewhere. Per the GCC bug above, those
EHABI tables are exactly the ones that currently produce partial
backtraces on Arm -- that limitation is the reason this series exists.
If that gets fixed on the GCC side, PACKAGE_KEEP_SECTIONS simply
becomes unnecessary for this particular use case, but until then
.debug_frame is the only way to get complete backtraces on 32-bit Arm.
- The matching ref-manual documentation for PACKAGE_KEEP_SECTIONS has been
sent as a separate patch to the docs list.
Mathieu Othacehe (1):
lib/oe/package: Add strip keep-section support
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(-)
--
2.34.1
^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH 1/1] lib/oe/package: Add strip keep-section support
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
2026-09-12 12:18 ` [OE-core] " Mathieu Dubois-Briand
0 siblings, 1 reply; 3+ messages in thread
From: Mathieu Othacehe @ 2026-09-11 22:01 UTC (permalink / raw)
To: openembedded-core
Cc: Alexander Kanavin, Khem Raj, Richard Purdie, Mathieu Othacehe
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
^ permalink raw reply related [flat|nested] 3+ messages in thread
* Re: [OE-core] [PATCH 1/1] lib/oe/package: Add strip keep-section support
2026-09-11 22:01 ` [PATCH 1/1] " Mathieu Othacehe
@ 2026-09-12 12:18 ` Mathieu Dubois-Briand
0 siblings, 0 replies; 3+ messages in thread
From: Mathieu Dubois-Briand @ 2026-09-12 12:18 UTC (permalink / raw)
To: othacehe, openembedded-core; +Cc: Alexander Kanavin, Khem Raj, Richard Purdie
On Sat Sep 12, 2026 at 12:01 AM CEST, Mathieu Othacehe via lists.openembedded.org wrote:
> 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>
> ---
Hi Mathieu,
Thanks for your patch.
It looks like the added test is failing on the autobuilder:
2026-09-12 09:19:34,528 - oe-selftest - INFO - package.PackageKeepSections.test_package_keep_sections (subunit.RemotedTestCase)
2026-09-12 09:19:34,529 - oe-selftest - INFO - ... ERROR
...
2026-09-12 09:19:34,529 - oe-selftest - INFO - 5: 28/54 531/772 (122.95s) (0 failed) (package.PackageKeepSections.test_package_keep_sections)
2026-09-12 09:19:34,529 - oe-selftest - INFO - testtools.testresult.real._StringException: Traceback (most recent call last):
File "/srv/pokybuild/yocto-worker/oe-selftest-fedora/build/layers/openembedded-core/meta/lib/oeqa/selftest/cases/package.py", line 248, in test_package_keep_sections
self.assertFalse(has_section(".debug_frame"),
~~~~~~~~~~~^^^^^^^^^^^^^^^^
File "/srv/pokybuild/yocto-worker/oe-selftest-fedora/build/layers/openembedded-core/meta/lib/oeqa/selftest/cases/package.py", line 241, in has_section
tar.extract("./usr/bin/busybox.nosuid", path=unpackedfs)
~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/srv/pokybuild/yocto-worker/oe-selftest-fedora/build/buildtools/sysroots/x86_64-pokysdk-linux/usr/lib/python3.13/tarfile.py", line 2365, in extract
tarinfo = self._get_extract_tarinfo(member, filter_function, path)
File "/srv/pokybuild/yocto-worker/oe-selftest-fedora/build/buildtools/sysroots/x86_64-pokysdk-linux/usr/lib/python3.13/tarfile.py", line 2372, in _get_extract_tarinfo
tarinfo = self.getmember(member)
File "/srv/pokybuild/yocto-worker/oe-selftest-fedora/build/buildtools/sysroots/x86_64-pokysdk-linux/usr/lib/python3.13/tarfile.py", line 2041, in getmember
raise KeyError("filename %r not found" % name)
KeyError: "filename './usr/bin/busybox.nosuid' not found"
https://autobuilder.yoctoproject.org/valkyrie/#/builders/48/builds/4649
https://autobuilder.yoctoproject.org/valkyrie/#/builders/35/builds/4842
Can you have a look at the issue?
Thanks,
Mathieu
--
Mathieu Dubois-Briand, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-09-12 12:18 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-11 22:01 [PATCH v3 0/1] lib/oe/package: Add strip keep-section support Mathieu Othacehe
2026-09-11 22:01 ` [PATCH 1/1] " Mathieu Othacehe
2026-09-12 12:18 ` [OE-core] " Mathieu Dubois-Briand
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox