diff options
| author | Dan Callaghan <dan.callaghan@opengear.com> | 2020-04-19 18:16:03 +1000 |
|---|---|---|
| committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2020-04-26 14:00:50 +0100 |
| commit | 6efa038c01e42af7602f805b9035df7ab4e0df73 (patch) | |
| tree | bb40f13d3a5e9694546dc8b9867147efd217b09d /meta/classes/package.bbclass | |
| parent | bf607afc7fb824e7b4aae7e423a781f672c4ffec (diff) | |
| download | poky-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>
Diffstat (limited to 'meta/classes/package.bbclass')
| -rw-r--r-- | meta/classes/package.bbclass | 84 |
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 | ||
| 464 | def 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 | |||
| 462 | def copydebugsources(debugsrcdir, sources, d): | 541 | def 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 | # |
