summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDan Callaghan <dan.callaghan@opengear.com>2020-04-19 18:16:03 +1000
committerRichard Purdie <richard.purdie@linuxfoundation.org>2020-04-26 14:00:50 +0100
commit6efa038c01e42af7602f805b9035df7ab4e0df73 (patch)
treebb40f13d3a5e9694546dc8b9867147efd217b09d
parentbf607afc7fb824e7b4aae7e423a781f672c4ffec (diff)
downloadpoky-6efa038c01e42af7602f805b9035df7ab4e0df73.tar.gz
package.bbclass: inject "minidebuginfo" into packaged binaries
"Mini debuginfo" is a special section in ELF executables containing minimal compressed debuginfo for non-exported symbols: https://sourceware.org/gdb/onlinedocs/gdb/MiniDebugInfo.html It lets debugging tools produce better stack traces, including local function names, without incurring the space overhead of full debuginfo. The feature was originally developed for Fedora: https://fedoraproject.org/wiki/Features/MiniDebugInfo but nowadays it is widely supported in the ecosystem, including in gdb and elfutils (and therefore also in tools which use elfutils, such as systemd-coredump). This patch adds an optional extra step in package.bbclass to inject minidebuginfo while stripping and splitting out debuginfo. It can be enabled by setting PACKAGE_MINIDEBUGINFO=1. In my testing, this increases the size of resulting binaries by roughly 5%. The code for producing and re-injecting the minidebuginfo is my own Python implementation but corresponds directly to the shell implementation that RPM uses for doing the same: https://github.com/rpm-software-management/rpm/blob/rpm-4.15.1-release/scripts/find-debuginfo.sh#L261 (From OE-Core rev: 4df992ce50c2d12e356b6d9fe7b23a6320c8b4df) Signed-off-by: Dan Callaghan <dan.callaghan@opengear.com> Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
-rw-r--r--meta/classes/package.bbclass84
1 files changed, 84 insertions, 0 deletions
diff --git a/meta/classes/package.bbclass b/meta/classes/package.bbclass
index d4c6a90e84..648297ad25 100644
--- a/meta/classes/package.bbclass
+++ b/meta/classes/package.bbclass
@@ -245,6 +245,8 @@ python () {
245 deps = "" 245 deps = ""
246 for dep in (d.getVar('PACKAGE_DEPENDS') or "").split(): 246 for dep in (d.getVar('PACKAGE_DEPENDS') or "").split():
247 deps += " %s:do_populate_sysroot" % dep 247 deps += " %s:do_populate_sysroot" % dep
248 if d.getVar('PACKAGE_MINIDEBUGINFO') == '1':
249 deps += ' xz-native:do_populate_sysroot'
248 d.appendVarFlag('do_package', 'depends', deps) 250 d.appendVarFlag('do_package', 'depends', deps)
249 251
250 # shlibs requires any DEPENDS to have already packaged for the *.list files 252 # shlibs requires any DEPENDS to have already packaged for the *.list files
@@ -459,6 +461,83 @@ def splitstaticdebuginfo(file, dvar, debugstaticdir, debugstaticlibdir, debugsta
459 461
460 return (file, sources) 462 return (file, sources)
461 463
464def inject_minidebuginfo(file, dvar, debugdir, debuglibdir, debugappend, debugsrcdir, d):
465 # Extract just the symbols from debuginfo into minidebuginfo,
466 # compress it with xz and inject it back into the binary in a .gnu_debugdata section.
467 # https://sourceware.org/gdb/onlinedocs/gdb/MiniDebugInfo.html
468
469 import subprocess
470
471 readelf = d.getVar('READELF')
472 nm = d.getVar('NM')
473 objcopy = d.getVar('OBJCOPY')
474
475 minidebuginfodir = d.expand('${WORKDIR}/minidebuginfo')
476
477 src = file[len(dvar):]
478 dest = debuglibdir + os.path.dirname(src) + debugdir + "/" + os.path.basename(src) + debugappend
479 debugfile = dvar + dest
480 minidebugfile = minidebuginfodir + src + '.minidebug'
481 bb.utils.mkdirhier(os.path.dirname(minidebugfile))
482
483 # If we didn't produce debuginfo for any reason, we can't produce minidebuginfo either
484 # so skip it.
485 if not os.path.exists(debugfile):
486 bb.debug(1, 'ELF file {} has no debuginfo, skipping minidebuginfo injection'.format(file))
487 return
488
489 # Find non-allocated PROGBITS, NOTE, and NOBITS sections in the debuginfo.
490 # We will exclude all of these from minidebuginfo to save space.
491 remove_section_names = []
492 for line in subprocess.check_output([readelf, '-W', '-S', debugfile], universal_newlines=True).splitlines():
493 fields = line.split()
494 if len(fields) < 8:
495 continue
496 name = fields[0]
497 type = fields[1]
498 flags = fields[7]
499 # .debug_ sections will be removed by objcopy -S so no need to explicitly remove them
500 if name.startswith('.debug_'):
501 continue
502 if 'A' not in flags and type in ['PROGBITS', 'NOTE', 'NOBITS']:
503 remove_section_names.append(name)
504
505 # List dynamic symbols in the binary. We can exclude these from minidebuginfo
506 # because they are always present in the binary.
507 dynsyms = set()
508 for line in subprocess.check_output([nm, '-D', file, '--format=posix', '--defined-only'], universal_newlines=True).splitlines():
509 dynsyms.add(line.split()[0])
510
511 # Find all function symbols from debuginfo which aren't in the dynamic symbols table.
512 # These are the ones we want to keep in minidebuginfo.
513 keep_symbols_file = minidebugfile + '.symlist'
514 found_any_symbols = False
515 with open(keep_symbols_file, 'w') as f:
516 for line in subprocess.check_output([nm, debugfile, '--format=sysv', '--defined-only'], universal_newlines=True).splitlines():
517 fields = line.split('|')
518 if len(fields) < 7:
519 continue
520 name = fields[0].strip()
521 type = fields[3].strip()
522 if type == 'FUNC' and name not in dynsyms:
523 f.write('{}\n'.format(name))
524 found_any_symbols = True
525
526 if not found_any_symbols:
527 bb.debug(1, 'ELF file {} contains no symbols, skipping minidebuginfo injection'.format(file))
528 return
529
530 bb.utils.remove(minidebugfile)
531 bb.utils.remove(minidebugfile + '.xz')
532
533 subprocess.check_call([objcopy, '-S'] +
534 ['--remove-section={}'.format(s) for s in remove_section_names] +
535 ['--keep-symbols={}'.format(keep_symbols_file), debugfile, minidebugfile])
536
537 subprocess.check_call(['xz', '--keep', minidebugfile])
538
539 subprocess.check_call([objcopy, '--add-section', '.gnu_debugdata={}.xz'.format(minidebugfile), file])
540
462def copydebugsources(debugsrcdir, sources, d): 541def copydebugsources(debugsrcdir, sources, d):
463 # The debug src information written out to sourcefile is further processed 542 # The debug src information written out to sourcefile is further processed
464 # and copied to the destination here. 543 # and copied to the destination here.
@@ -1185,6 +1264,11 @@ python split_and_strip_files () {
1185 1264
1186 oe.utils.multiprocess_launch(oe.package.runstrip, sfiles, d) 1265 oe.utils.multiprocess_launch(oe.package.runstrip, sfiles, d)
1187 1266
1267 # Build "minidebuginfo" and reinject it back into the stripped binaries
1268 if d.getVar('PACKAGE_MINIDEBUGINFO') == '1':
1269 oe.utils.multiprocess_launch(inject_minidebuginfo, list(elffiles), d,
1270 extraargs=(dvar, debugdir, debuglibdir, debugappend, debugsrcdir, d))
1271
1188 # 1272 #
1189 # End of strip 1273 # End of strip
1190 # 1274 #