diff mbox series

[v2] lib/oe/package: Add strip keep-section support

Message ID 20250204103744.27883-1-othacehe@gnu.org
State New
Headers show
Series [v2] lib/oe/package: Add strip keep-section support | expand

Commit Message

Mathieu Othacehe Feb. 4, 2025, 10:37 a.m. UTC
Add a 'PACKAGE_KEEP_SECTIONS' variable to keep some specific ELF sections
while stripping binaries and libraries.

That one can then be used to keep the .debug_frame section around for
example, this way:

PACKAGE_KEEP_SECTIONS = ".debug_frame"

By using libunwind + minidebuginfo, that provides a way for users to get
debug_frame based backtraces on target.

Signed-off-by: Mathieu Othacehe <othacehe@gnu.org>
---
v1: https://lists.openembedded.org/g/openembedded-core/message/209545
documentation: https://lists.yoctoproject.org/g/docs/message/6243

 meta/classes-global/staging.bbclass |  4 +++-
 meta/classes-recipe/kernel.bbclass  |  2 +-
 meta/lib/oe/package.py              | 23 +++++++++++++----------
 3 files changed, 17 insertions(+), 12 deletions(-)

Comments

Alexander Kanavin Feb. 4, 2025, 11:37 a.m. UTC | #1
On Tue, 4 Feb 2025 at 11:37, Mathieu Othacehe via
lists.openembedded.org <othacehe=gnu.org@lists.openembedded.org>
wrote:
> Add a 'PACKAGE_KEEP_SECTIONS' variable to keep some specific ELF sections
> while stripping binaries and libraries.
>
> That one can then be used to keep the .debug_frame section around for
> example, this way:
>
> PACKAGE_KEEP_SECTIONS = ".debug_frame"
>
> By using libunwind + minidebuginfo, that provides a way for users to get
> debug_frame based backtraces on target.

I don't understand. We already have support and tests for
minidebuginfo, so what additional functionality would the above
setting enable? Can you adjust the existing tests to showcase that?

Adding new variables has a high bar for acceptance, because the need
for them must be clearly shown, and they need to be tested and
documented too.

Also the patch is changing things all over the place, and so is
difficult to review. Can you split it up? E.g. first add some new
thing, then adjust code elsewhere to take it into use.

Alex
Alexander Kanavin Feb. 4, 2025, 11:39 a.m. UTC | #2
On Tue, 4 Feb 2025 at 12:37, Alexander Kanavin <alex.kanavin@gmail.com> wrote:

> I don't understand. We already have support and tests for
> minidebuginfo, so what additional functionality would the above
> setting enable? Can you adjust the existing tests to showcase that?

Specifically, this:

https://git.yoctoproject.org/poky/tree/meta/lib/oeqa/selftest/cases/minidebuginfo.py

Alex
Mathieu Othacehe Feb. 4, 2025, 12:36 p.m. UTC | #3
Hello Alexander,

> I don't understand. We already have support and tests for
> minidebuginfo, so what additional functionality would the above
> setting enable? Can you adjust the existing tests to showcase that?

When you want to display a backtrace on target you need basically two
things:

1. Symbols
2. Unwind information

The minidebuginfo functionality is covering the first part. It is
providing some symbols in a compressed way, that get appended to every
binary/library at build time.

Then the second part is about how to unwind the stack. That is
architecture dependant. On ARMv7, GCC is producing unwind information in
the .debug_frame section. That section is always removed by
'strip'. It prevents any unwinding based on .debug_frame on the target.

In the first version of that patch[1], I was proposing to introduce a
variable called PACKAGE_KEEP_DEBUG_FRAME to instruct `strip` to keep the
.debug_frame section around.

Quentin, pointed out, that we could be more generic and offer a
PACKAGE_KEEP_SECTIONS variable instead, to prevent specific sections
from being stripped.

That's what is done in this v2. I have proposed a documentation update
here: https://lists.yoctoproject.org/g/docs/message/6243

> Also the patch is changing things all over the place, and so is
> difficult to review. Can you split it up? E.g. first add some new
> thing, then adjust code elsewhere to take it into use.

It is only modifying the run_strip function and its callers, so "all over
the place" seems like an overstatement. I am not really sure how to
break that into more pieces. It's just about instructing "strip" to keep
some sections that are specified in the PACKAGE_KEEP_SECTIONS variable.

I would be glad to bring some more details on the topic if needed.

Thanks,

Mathieu
Alexander Kanavin Feb. 4, 2025, 12:50 p.m. UTC | #4
On Tue, 4 Feb 2025 at 13:36, Mathieu Othacehe <othacehe@gnu.org> wrote:
> Quentin, pointed out, that we could be more generic and offer a
> PACKAGE_KEEP_SECTIONS variable instead, to prevent specific sections
> from being stripped.
>
> That's what is done in this v2. I have proposed a documentation update
> here: https://lists.yoctoproject.org/g/docs/message/6243

Thanks. But you still need to look at existing minidebuginfo tests and
see if you can enhance them. Otherwise we have a variable that isn't
tested.

Also, if minidebuginfo distro feature is enabled, do we actually need
this level of control? Why not just always have the needed bits?

> It is only modifying the run_strip function and its callers, so "all over
> the place" seems like an overstatement. I am not really sure how to
> break that into more pieces. It's just about instructing "strip" to keep
> some sections that are specified in the PACKAGE_KEEP_SECTIONS variable.

You can split into at least four commits:

- change of runstrip signature
- change of strip_execs signature
- adding new variable to package.py
- adding new variable to staging class.

Alex
Mathieu Othacehe Feb. 4, 2025, 2:25 p.m. UTC | #5
> Thanks. But you still need to look at existing minidebuginfo tests and
> see if you can enhance them. Otherwise we have a variable that isn't
> tested.

OK, I will check want I can do.

> Also, if minidebuginfo distro feature is enabled, do we actually need
> this level of control? Why not just always have the needed bits?

On some architectures, such as x86_64 and aarch64, the .eh_frame section
that is *not* stripped because used for C++ exception handling, can be
used for DWARF-based stack unwinding purposes.

On those architectures, one can rely on .eh_frame + minidebuginfo to get
DWARF based backtraces. So no need for the PACKAGE_KEEP_SECTIONS there.

However, on ARMv7, the .eh_frame section is empty and the ARM specific .exidx
section is not enough to get full backtraces, as discussed here:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=117941

For that specific architecture, it then makes sense to instruct 'strip'
to leave the .debug_frame section. By setting for instance:

PACKAGE_KEEP_SECTIONS:armv7a = ".debug_frame"

So minidebuginfo and PACKAGE_KEEP_SECTIONS are two very different
things, serving different purposes. Even though the combination of both
is required on some architectures to get full backtraces, I don't think
that they should be tied together.

> You can split into at least four commits:
>
> - change of runstrip signature
> - change of strip_execs signature
> - adding new variable to package.py
> - adding new variable to staging class.

OK, thanks for the suggestion.

Mathieu
Alexander Kanavin Feb. 4, 2025, 7:02 p.m. UTC | #6
On Tue, 4 Feb 2025 at 15:25, Mathieu Othacehe <othacehe@gnu.org> wrote:

> So minidebuginfo and PACKAGE_KEEP_SECTIONS are two very different
> things, serving different purposes. Even though the combination of both
> is required on some architectures to get full backtraces, I don't think
> that they should be tied together.

Fair enough. What you say should be documented though, perhaps you
could revise your docs patch to include these things.

If you figure out how to test, that would be good as well. Ideally
without adding another image build to the test, but
adjusting/extending something that already exists.

Alex
diff mbox series

Patch

diff --git a/meta/classes-global/staging.bbclass b/meta/classes-global/staging.bbclass
index 1008867a6c..7083878c73 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/classes-recipe/kernel.bbclass b/meta/classes-recipe/kernel.bbclass
index 617727a989..d3d3d9fa65 100644
--- a/meta/classes-recipe/kernel.bbclass
+++ b/meta/classes-recipe/kernel.bbclass
@@ -770,7 +770,7 @@  python do_strip() {
     if (extra_sections and kernel_image.find(d.getVar('KERNEL_IMAGEDEST') + '/vmlinux') != -1):
         kernel_image_stripped = kernel_image + ".stripped"
         shutil.copy2(kernel_image, kernel_image_stripped)
-        oe.package.runstrip((kernel_image_stripped, 8, strip, extra_sections))
+        oe.package.runstrip((kernel_image_stripped, 8, strip), '', extra_sections)
         bb.debug(1, "KERNEL_IMAGE_STRIP_EXTRA_SECTIONS is set, stripping sections: " + \
             extra_sections)
 }
diff --git a/meta/lib/oe/package.py b/meta/lib/oe/package.py
index 1af10b7eb0..468f331fce 100644
--- a/meta/lib/oe/package.py
+++ b/meta/lib/oe/package.py
@@ -18,7 +18,7 @@  import shutil
 
 import oe.cachedpath
 
-def runstrip(arg):
+def runstrip(arg, keep_sections='', extra_strip_sections=''):
     # Function to strip a single file, called from split_and_strip_files below
     # A working 'file' (one which works on the target architecture)
     #
@@ -27,12 +27,7 @@  def runstrip(arg):
     # 4 - executable
     # 8 - shared library
     # 16 - kernel module
-
-    if len(arg) == 3:
-        (file, elftype, strip) = arg
-        extra_strip_sections = ''
-    else:
-        (file, elftype, strip, extra_strip_sections) = arg
+    (file, elftype, strip) = arg
 
     newmode = None
     if not os.access(file, os.W_OK) or os.access(file, os.R_OK):
@@ -60,6 +55,10 @@  def runstrip(arg):
             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)
 
@@ -115,7 +114,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
@@ -194,7 +194,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@"),
@@ -1305,7 +1306,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):