├── .gitattributes ├── .github └── workflows │ └── tests.yml ├── .gitignore ├── Makefile ├── README.md ├── debian ├── changelog ├── control ├── copyright ├── debian-package-notes.specs ├── dh-dlopenlibdeps.install ├── dh-dlopenlibdeps.manpages ├── dh-package-notes.install ├── dh-package-notes.manpages ├── dh_dlopenlibdeps ├── dh_package_notes.1 ├── dlopenlibdeps.pm ├── package-notes.mk ├── rules └── source │ └── format ├── dlopen-notes.py ├── docs ├── dlopen-description.man └── dlopen-notes.1 ├── hello.spec ├── rpm ├── macros.package-notes-srpm └── redhat-package-notes.in └── test ├── Makefile ├── _notes.py ├── notes.c └── test.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Mark files as "generated", i.e. no license applies to them. 2 | # This includes output from programs, samples, and and anything else 3 | # where no copyright can be asserted. 4 | 5 | # Use 'git check-attr generated -- ' to query the attribute. 6 | [attr]generated 7 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | --- 2 | # SPDX-License-Identifier: CC0-1.0 3 | # vi: ts=2 sw=2 et: 4 | 5 | name: Run tests 6 | on: [pull_request] 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-22.04 14 | concurrency: 15 | group: ${{ github.workflow }}-${{ github.ref }} 16 | cancel-in-progress: true 17 | strategy: 18 | fail-fast: false 19 | steps: 20 | - name: Repository checkout 21 | uses: actions/checkout@v4 22 | - name: Install dependencies 23 | run: sudo apt -y update && sudo apt -y install python3-pyelftools python3-pytest 24 | - name: Run tests 25 | run: make check 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.sw* 2 | /*.src.rpm 3 | /*.log 4 | /test/notes 5 | !/debian/changelog 6 | !/debian/control 7 | !/debian/copyright 8 | !/debian/dh-package-notes.install 9 | !/debian/dh-package-notes.manpages 10 | !/debian/generate-package-notes.1 11 | !/debian/rules 12 | !/debian/source/format 13 | __pycache__/ 14 | /dlopen-notes.1 15 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | 3 | man: dlopen-notes.1 4 | 5 | dlopen-notes.1: dlopen-notes.py docs/dlopen-description.man Makefile 6 | argparse-manpage \ 7 | --output=docs/$@ \ 8 | --pyfile=$< \ 9 | --function=make_parser \ 10 | --project-name=package-notes \ 11 | --include=docs/dlopen-description.man 12 | 13 | install: 14 | install -m 755 -D dlopen-notes.py $(DESTDIR)/usr/bin/dlopen-notes 15 | install -m 644 -D docs/dlopen-notes.1 $(DESTDIR)/usr/share/man/man1/dlopen-notes.1 16 | 17 | check: 18 | make -C test check 19 | 20 | clean: 21 | make -C test clean 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ELF Package and Dlopen Notes Reference Implementation 2 | 3 | ## Description 4 | 5 | This repository provides RPM and DEB packaging tools to generate an 6 | `.note.package` ELF note that will be linked into compiled binaries (programs 7 | and shared libraries) to provide metadata about the package for which the 8 | binary was compiled. 9 | 10 | See [Package Metadata for Core Files](https://systemd.io/ELF_PACKAGE_METADATA/) 11 | for the overview and details. 12 | 13 | When building binaries, the `--package-metadata` option provided by 14 | `bfd`, `gold`, `mold`, and `lld` shall be used to inject the metadata 15 | into the binary. 16 | 17 | ### Displaying package notes 18 | 19 | Raw: 20 | ```console 21 | $ objdump -j .note.package -s /usr/bin/ls 22 | 23 | /usr/bin/ls: file format elf64-x86-64 24 | 25 | Contents of section .note.package: 26 | 03cc 04000000 7c000000 7e1afeca 46444f00 ....|...~...FDO. 27 | 03dc 7b227479 7065223a 2272706d 222c226e {"type":"rpm","n 28 | 03ec 616d6522 3a22636f 72657574 696c7322 ame":"coreutils" 29 | 03fc 2c227665 7273696f 6e223a22 392e342d ,"version":"9.4- 30 | 040c 372e6663 3430222c 22617263 68697465 7.fc40","archite 31 | 041c 63747572 65223a22 7838365f 3634222c cture":"x86_64", 32 | 042c 226f7343 7065223a 22637065 3a2f6f3a "osCpe":"cpe:/o: 33 | 043c 6665646f 72617072 6f6a6563 743a6665 fedoraproject:fe 34 | 044c 646f7261 3a343022 7d000000 dora:40"}... 35 | ``` 36 | 37 | Pretty: 38 | ```console 39 | $ systemd-analyze inspect-elf /usr/bin/ls 40 | path: /usr/bin/ls 41 | elfType: executable 42 | elfArchitecture: AMD x86-64 43 | 44 | type: rpm 45 | name: coreutils 46 | version: 9.4-7.fc40 47 | architecture: x86_64 48 | osCpe: cpe:/o:fedoraproject:fedora:40 49 | buildId: 40e5a1570a9d97fc48f5c61cfb7690fec0f872b2 50 | ``` 51 | 52 | ## `dlopen()` metadata 53 | 54 | This package also provides scripts to extract and display 55 | `.note.dlopen` ELF notes that are used to describe libraries loaded via `dlopen(3)`. 56 | 57 | See [`dlopen()` Metadata for ELF Files](https://systemd.io/ELF_DLOPEN_METADATA/) 58 | for the overview and details. 59 | 60 | ### Displaying `dlopen()` notes 61 | 62 | Raw: 63 | ```console 64 | $ objdump -j .note.dlopen -s /usr/lib64/systemd/libsystemd-shared-257.so 65 | 66 | /usr/lib64/systemd/libsystemd-shared-257.so: file format elf64-x86-64 67 | 68 | Contents of section .note.dlopen: 69 | 0334 04000000 8e000000 0a0c7c40 46444f00 ..........|@FDO. 70 | 0344 5b7b2266 65617475 7265223a 22627066 [{"feature":"bpf 71 | 0354 222c2264 65736372 69707469 6f6e223a ","description": 72 | 0364 22537570 706f7274 20666972 6577616c "Support firewal 73 | 0374 6c696e67 20616e64 2073616e 64626f78 ling and sandbox 74 | 0384 696e6720 77697468 20425046 222c2270 ing with BPF","p 75 | 0394 72696f72 69747922 3a227375 67676573 riority":"sugges 76 | 03a4 74656422 2c22736f 6e616d65 223a5b22 ted","soname":[" 77 | 03b4 6c696262 70662e73 6f2e3122 2c226c69 libbpf.so.1","li 78 | 03c4 62627066 2e736f2e 30225d7d 5d000000 bbpf.so.0"]}]... 79 | 03d4 04000000 9e000000 0a0c7c40 46444f00 ..........|@FDO. 80 | ... 81 | ``` 82 | 83 | Pretty: 84 | ```console 85 | $ dlopen-notes /usr/lib64/systemd/libsystemd-shared-257.so 86 | # /usr/lib64/systemd/libsystemd-shared-257.so 87 | [ 88 | { 89 | "feature": "bpf", 90 | "description": "Support firewalling and sandboxing with BPF", 91 | "priority": "suggested", 92 | "soname": [ 93 | "libbpf.so.1", 94 | "libbpf.so.0" 95 | ] 96 | }, 97 | ... 98 | ``` 99 | 100 | ## Requirements 101 | * binutils (>= 2.39) 102 | * mold (>= 1.3.0) 103 | * lld (>= 15.0.0) 104 | * python (>= 3.8) 105 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | package-notes (0.15) unstable; urgency=medium 2 | 3 | * d/package-notes.mk: also check Meson specific LD env var 4 | 5 | -- Luca Boccassi Sat, 13 Jul 2024 13:10:31 +0100 6 | 7 | package-notes (0.14) unstable; urgency=medium 8 | 9 | * d/package-notes.mk: change to no-op on Ubuntu 10 | 11 | -- Luca Boccassi Sun, 23 Jun 2024 17:18:48 +0100 12 | 13 | package-notes (0.13) unstable; urgency=medium 14 | 15 | * debian/package-notes.mk: skip when linking with llvm 16 | 17 | -- Luca Boccassi Sun, 02 Jun 2024 00:03:26 +0100 18 | 19 | package-notes (0.12) unstable; urgency=medium 20 | 21 | [ Luca Boccassi ] 22 | * d/control: mark python3 dependency with 'any' as per multiarch hinter 23 | 24 | [ Zbigniew Jędrzejewski-Szmek ] 25 | * dlopen-notes: make --raw the default action 26 | * dlopen-notes: add short option args and forbid abbrevs 27 | * dlopen-notes: generate man page using argparse-manpage 28 | 29 | [ Luca Boccassi ] 30 | * manpage: check in git 31 | * makefile: install manpage too 32 | * debian: install dlopen-notes manpage 33 | 34 | -- Luca Boccassi Wed, 22 May 2024 15:35:08 +0100 35 | 36 | package-notes (0.11) unstable; urgency=medium 37 | 38 | * d/control: drop 'native' from build dependencies 39 | * d/control: add doc link for dlopen spec 40 | 41 | -- Luca Boccassi Wed, 15 May 2024 18:22:46 +0100 42 | 43 | package-notes (0.10) unstable; urgency=medium 44 | 45 | * Add Python script to parse dlopen ELF notes 46 | * Add Debian tool for dlopen-notes 47 | * d/control: bump Standards-Version to 4.7.0, no changes 48 | 49 | -- Luca Boccassi Thu, 09 May 2024 10:17:51 +0100 50 | 51 | package-notes (0.9) unstable; urgency=medium 52 | 53 | * Switch to --package-metadata linker flag via specs file 54 | 55 | -- Luca Boccassi Sun, 27 Nov 2022 16:43:58 +0000 56 | 57 | package-notes (0.8) unstable; urgency=medium 58 | 59 | * use LONG instead of BYTEs for binary fields 60 | 61 | -- Luca Boccassi Mon, 04 Apr 2022 19:52:18 +0100 62 | 63 | package-notes (0.7) unstable; urgency=medium 64 | 65 | * dh: derive distro name at build time 66 | * dh: pick debuginfod from DEBUGINFOD_URLS env var if available 67 | * dh: install makefile include to automatically set LDFLAGS 68 | 69 | -- Luca Boccassi Mon, 07 Mar 2022 11:40:22 +0000 70 | 71 | package-notes (0.6) unstable; urgency=medium 72 | 73 | * Mark package as Multi-Arch: foreign 74 | 75 | -- Luca Boccassi Sun, 20 Feb 2022 22:16:03 +0000 76 | 77 | package-notes (0.5) unstable; urgency=medium 78 | 79 | [ Zbigniew Jędrzejewski-Szmek ] 80 | * generator: add a hint how to use the output to the output 81 | * generator: allow disabling READONLY 82 | * rpm: skip (READONLY) with older linker versions 83 | * Use -Wl,-dT 84 | * generator: add help strings 85 | 86 | [ Luca Boccassi ] 87 | * Initial packaging (Closes: #1004357) 88 | * Add architecture field 89 | * Skip fields without value 90 | * Debian: use -Wl,dT 91 | 92 | [ Zbigniew Jędrzejewski-Szmek ] 93 | * generator: add description in help output 94 | * generator: add --version argument 95 | * generator: disallow abbreviated option names 96 | * generator: rename program to not have .py suffix 97 | * generator: fix two minor issues reported by pylint 98 | * debian: stop using dh-exec 99 | 100 | [ Mark Wielaard ] 101 | * generate-package-notes.py: Add --debuginfod argument 102 | 103 | [ Victor Westerhuis ] 104 | * Add Debian debuginfod server in dh_package_notes 105 | * Provide dh-sequence-package_notes 106 | * Fix Python installation 107 | * Add manpage for generate-package-notes 108 | 109 | [ Zbigniew Jędrzejewski-Szmek ] 110 | * rpm: rename srpm macros file 111 | * rpm: fix filename in macro 112 | * rpm: conditionalize all macros on %_package_note_file being defined 113 | * rpm: make _generate_package_note_file always recreate the file 114 | * rpm: use $RPM_PACKAGE_VERSION variable to refer to the package version 115 | * rpm: also voidify the macros if we're on a noarch build 116 | * rpm: use %{buildsubdir} in %_package_note_file if defined 117 | * rpm: allow unsetting %_package_note_readonly to drop the READONLY 118 | attribute 119 | * rpm: add %_package_note_linker and document everything (rhbz#2043178, 120 | rhbz#2043368) 121 | * rpm: disable notes with linkers other than bfd 122 | * rpm: disable notes when clang toolchain is used on arm 123 | * generator: do not write zeroes to the linker script 124 | 125 | [ Frantisek Sumsal ] 126 | * ci: update repos before installing packages 127 | 128 | [ Zbigniew Jędrzejewski-Szmek ] 129 | * rpm: use named arguments 130 | 131 | [ Luca Boccassi ] 132 | * python: add --cpe auto 133 | * shell: add --cpe auto 134 | * man: document new --cpe auto option 135 | * shell: add --root option 136 | * python: add --root option 137 | * man: document new --root option 138 | * python: parse files in gather_data rather than parse_args 139 | * tests: add resources and tests for --auto-cpe and --root 140 | * rpm: use --cpe auto 141 | * debian: bump debhelper-compat version to 13 142 | * README: note we have both python and shell implementations 143 | 144 | -- Luca Boccassi Mon, 31 Jan 2022 23:43:02 +0000 145 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: package-notes 2 | Section: admin 3 | Priority: optional 4 | Rules-Requires-Root: no 5 | Maintainer: Luca Boccassi 6 | Build-Depends: debhelper-compat (= 13), 7 | python3 , 8 | python3-pyelftools , 9 | python3-pytest , 10 | Standards-Version: 4.7.0 11 | Homepage: https://systemd.io/ELF_PACKAGE_METADATA/ 12 | Vcs-Git: https://github.com/systemd/package-notes.git 13 | Vcs-Browser: https://github.com/systemd/package-notes 14 | 15 | Package: dh-package-notes 16 | Architecture: all 17 | Multi-Arch: foreign 18 | Enhances: debhelper 19 | Depends: ${misc:Depends}, debhelper, 20 | Description: Debian Helper for adding package metadata to ELF binaries 21 | Provides a linker specs file to add package metadata to the ELF binaries being 22 | built. See: https://systemd.io/ELF_PACKAGE_METADATA/ 23 | 24 | Package: dh-dlopenlibdeps 25 | Architecture: all 26 | Multi-Arch: foreign 27 | Enhances: debhelper 28 | Depends: ${misc:Depends}, 29 | ${perl:Depends}, 30 | debhelper, 31 | python3:any, 32 | python3-pyelftools, 33 | Provides: dh-sequence-dlopenlibdeps, 34 | Description: Debian Helper for parsing dlopen metadata from ELF binaries 35 | Parses dlopen ELF note and generates dependencies from it that can be used 36 | via ${dlopen:Depends|Recommends|Suggests} 37 | See: https://systemd.io/ELF_DLOPEN_METADATA/ 38 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | 3 | Files: * 4 | Copyright: 2021-2022 Zbigniew Jędrzejewski-Szmek 5 | 2021-2022 Luca Boccassi 6 | 2021 Victor Westerhuis 7 | 2021 Mark Wielaard 8 | 2022 Frantisek Sumsal 9 | License: CC0-1.0 10 | To the extent possible under law, the author(s) have dedicated all copyright 11 | and related and neighboring rights to this software to the public domain 12 | worldwide. This software is distributed without any warranty. 13 | . 14 | You should have received a copy of the CC0 Public Domain Dedication along with 15 | this software. If not, see . 16 | . 17 | On Debian systems, the complete text of the CC0 1.0 Universal license can be 18 | found in ‘/usr/share/common-licenses/CC0-1.0’. 19 | -------------------------------------------------------------------------------- /debian/debian-package-notes.specs: -------------------------------------------------------------------------------- 1 | *link: 2 | + --package-metadata={\"type\":\"deb\",\"os\":\"%:getenv(DEB_VENDOR \",\"name\":\"%:getenv(DEB_SOURCE_PACKAGE_NAME \",\"version\":\"%:getenv(DEB_SOURCE_PACKAGE_VERSION \",\"architecture\":\"%:getenv(DEB_HOST_ARCH \",\"debugInfoUrl\":\"%:getenv(DEBUGINFOD_URLS \"}))))) 3 | -------------------------------------------------------------------------------- /debian/dh-dlopenlibdeps.install: -------------------------------------------------------------------------------- 1 | debian/dlopenlibdeps.pm usr/share/perl5/Debian/Debhelper/Sequence 2 | debian/dh_dlopenlibdeps usr/bin 3 | usr/bin/dlopen-notes 4 | -------------------------------------------------------------------------------- /debian/dh-dlopenlibdeps.manpages: -------------------------------------------------------------------------------- 1 | debian/dh_dlopenlibdeps.1 2 | usr/share/man/man1/dlopen-notes.1 3 | -------------------------------------------------------------------------------- /debian/dh-package-notes.install: -------------------------------------------------------------------------------- 1 | debian/package-notes.mk usr/share/debhelper/dh_package_notes/ 2 | debian/debian-package-notes.specs usr/share/debhelper/dh_package_notes/ 3 | -------------------------------------------------------------------------------- /debian/dh-package-notes.manpages: -------------------------------------------------------------------------------- 1 | debian/dh_package_notes.1 2 | -------------------------------------------------------------------------------- /debian/dh_dlopenlibdeps: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | # SPDX-License-Identifier: CC0-1.0 3 | 4 | =head1 NAME 5 | 6 | dh_dlopenlibdeps - parse dlopen library dependencies from ELF notes 7 | 8 | =cut 9 | 10 | use strict; 11 | use warnings; 12 | use Debian::Debhelper::Dh_Lib; 13 | 14 | our $VERSION = DH_BUILTIN_VERSION; 15 | 16 | =head1 SYNOPSIS 17 | 18 | B [S>] [B<-X>I] 19 | 20 | =head1 DESCRIPTION 21 | 22 | B is a debhelper program that is responsible for calculating 23 | dlopen library dependencies for packages. 24 | 25 | This program follows the dlopen notes metadata specification as defined at 26 | https://systemd.io/ELF_DLOPEN_METADATA/ 27 | 28 | =head1 OPTIONS 29 | 30 | =over 4 31 | 32 | =item B<-X>I, B<--exclude=>I 33 | 34 | Exclude files that contain F anywhere in their filename from being 35 | parsed. This will make their dependencies be ignored. 36 | This may be useful in some situations, but use it with caution. This option 37 | may be used more than once to exclude more than one thing. 38 | 39 | =back 40 | 41 | =cut 42 | 43 | init(); 44 | 45 | on_pkgs_in_parallel { 46 | my $is_elf_file = sub { 47 | my ($file) = @_; 48 | my @file_args = Debian::Debhelper::Dh_Lib::_internal_optional_file_args(); 49 | my $ff = qx_cmd('file', @file_args, '--brief', '-e', 'apptype', '-e', 'ascii', 50 | '-e', 'encoding', '-e', 'cdf', '-e', 'compress', '-e', 'tar', '--', $file); 51 | return 1 if $ff =~ m/ELF/; 52 | return 0; 53 | }; 54 | 55 | foreach my $package (@_) { 56 | my $tmp = tmpdir($package); 57 | my $ext = pkgext($package); 58 | my (@filelist); 59 | my %required_packages; 60 | my %recommended_packages; 61 | my %suggested_packages; 62 | 63 | # Generate a list of ELF binaries in the package, ignoring any we were told to exclude. 64 | my $find_options=''; 65 | if (defined($dh{EXCLUDE_FIND}) && $dh{EXCLUDE_FIND} ne '') { 66 | $find_options="! \\( $dh{EXCLUDE_FIND} \\)"; 67 | } 68 | next if not -d $tmp; 69 | foreach my $file (split(/\n/, `find $tmp -type f \\( -perm /111 -or -name "*.so*" -or -name "*.cmxs" -or -name "*.node" \\) $find_options -print`)) { 70 | # Prune directories that contain separated debug symbols. 71 | # CAVEAT: There are files in /usr/lib/debug that are not detached debug symbols, which should be processed. (see #865982) 72 | next if $file =~ m!^\Q$tmp\E/usr/lib/debug/(lib|lib64|usr|bin|sbin|opt|dev|emul|\.build-id)/!; 73 | if ($is_elf_file->($file)) { 74 | push @filelist, $file; 75 | } 76 | } 77 | 78 | if (@filelist) { 79 | my $required_sonames = ''; 80 | my $recommended_sonames = ''; 81 | my $suggested_sonames = ''; 82 | 83 | my $sonames = `dlopen-notes --sonames @filelist`; 84 | foreach my $line (split(/\n/, $sonames)) { 85 | my ($soname, $priority) = split(' ', $line, 2); 86 | 87 | if ($priority eq 'required') { 88 | $required_sonames .= " $soname"; 89 | } elsif ($priority eq 'recommended') { 90 | $recommended_sonames .= " $soname"; 91 | } elsif ($priority eq 'suggested') { 92 | $suggested_sonames .= " $soname"; 93 | } else { 94 | warning("Unknown priority $priority for $soname"); 95 | } 96 | } 97 | 98 | if ($required_sonames) { 99 | my $dpkg_query = `dpkg-query --search -- $required_sonames`; 100 | foreach my $line (split(/\n/, $dpkg_query)) { 101 | chomp $line; 102 | if ($line =~ m/^local diversion |^diversion by/) { 103 | next; 104 | } 105 | if ($line =~ m/^([-a-z0-9+]+):/) { 106 | $required_packages{$1} = 1; 107 | } 108 | } 109 | } 110 | 111 | if ($recommended_sonames) { 112 | my $dpkg_query = `dpkg-query --search -- $recommended_sonames`; 113 | foreach my $line (split(/\n/, $dpkg_query)) { 114 | chomp $line; 115 | if ($line =~ m/^local diversion |^diversion by/) { 116 | next; 117 | } 118 | if ($line =~ m/^([-a-z0-9+]+):/) { 119 | $recommended_packages{$1} = 1; 120 | } 121 | } 122 | } 123 | 124 | if ($suggested_sonames) { 125 | my $dpkg_query = `dpkg-query --search -- $suggested_sonames`; 126 | foreach my $line (split(/\n/, $dpkg_query)) { 127 | chomp $line; 128 | if ($line =~ m/^local diversion |^diversion by/) { 129 | next; 130 | } 131 | if ($line =~ m/^([-a-z0-9+]+):/) { 132 | $suggested_packages{$1} = 1; 133 | } 134 | } 135 | } 136 | } 137 | 138 | # Always write the substvars file, even if it's empty, so that the variables are defined and 139 | # there are no warnings when using them in the control file. 140 | open(SV, ">>debian/${ext}substvars") || error("open debian/${ext}substvars: $!"); 141 | print SV "dlopen:Depends=" . join(", ", sort keys %required_packages) . "\n"; 142 | print SV "dlopen:Recommends=" . join(", ", sort keys %recommended_packages) . "\n"; 143 | print SV "dlopen:Suggests=" . join(", ", sort keys %suggested_packages) . "\n"; 144 | close(SV); 145 | } 146 | }; 147 | 148 | =head1 SEE ALSO 149 | 150 | L, L 151 | 152 | =head1 AUTHOR 153 | 154 | Luca Boccassi 155 | 156 | =cut 157 | -------------------------------------------------------------------------------- /debian/dh_package_notes.1: -------------------------------------------------------------------------------- 1 | .\" Automatically generated by Pod::Man 4.14 (Pod::Simple 3.43) 2 | .\" 3 | .\" Standard preamble: 4 | .\" ======================================================================== 5 | .de Sp \" Vertical space (when we can't use .PP) 6 | .if t .sp .5v 7 | .if n .sp 8 | .. 9 | .de Vb \" Begin verbatim text 10 | .ft CW 11 | .nf 12 | .ne \\$1 13 | .. 14 | .de Ve \" End verbatim text 15 | .ft R 16 | .fi 17 | .. 18 | .\" Set up some character translations and predefined strings. \*(-- will 19 | .\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left 20 | .\" double quote, and \*(R" will give a right double quote. \*(C+ will 21 | .\" give a nicer C++. Capital omega is used to do unbreakable dashes and 22 | .\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff, 23 | .\" nothing in troff, for use with C<>. 24 | .tr \(*W- 25 | .ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' 26 | .ie n \{\ 27 | . ds -- \(*W- 28 | . ds PI pi 29 | . if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch 30 | . if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch 31 | . ds L" "" 32 | . ds R" "" 33 | . ds C` "" 34 | . ds C' "" 35 | 'br\} 36 | .el\{\ 37 | . ds -- \|\(em\| 38 | . ds PI \(*p 39 | . ds L" `` 40 | . ds R" '' 41 | . ds C` 42 | . ds C' 43 | 'br\} 44 | .\" 45 | .\" Escape single quotes in literal strings from groff's Unicode transform. 46 | .ie \n(.g .ds Aq \(aq 47 | .el .ds Aq ' 48 | .\" 49 | .\" If the F register is >0, we'll generate index entries on stderr for 50 | .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index 51 | .\" entries marked with X<> in POD. Of course, you'll have to process the 52 | .\" output yourself in some meaningful fashion. 53 | .\" 54 | .\" Avoid warning from groff about undefined register 'F'. 55 | .de IX 56 | .. 57 | .nr rF 0 58 | .if \n(.g .if rF .nr rF 1 59 | .if (\n(rF:(\n(.g==0)) \{\ 60 | . if \nF \{\ 61 | . de IX 62 | . tm Index:\\$1\t\\n%\t"\\$2" 63 | .. 64 | . if !\nF==2 \{\ 65 | . nr % 0 66 | . nr F 2 67 | . \} 68 | . \} 69 | .\} 70 | .rr rF 71 | .\" 72 | .\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2). 73 | .\" Fear. Run. Save yourself. No user-serviceable parts. 74 | . \" fudge factors for nroff and troff 75 | .if n \{\ 76 | . ds #H 0 77 | . ds #V .8m 78 | . ds #F .3m 79 | . ds #[ \f1 80 | . ds #] \fP 81 | .\} 82 | .if t \{\ 83 | . ds #H ((1u-(\\\\n(.fu%2u))*.13m) 84 | . ds #V .6m 85 | . ds #F 0 86 | . ds #[ \& 87 | . ds #] \& 88 | .\} 89 | . \" simple accents for nroff and troff 90 | .if n \{\ 91 | . ds ' \& 92 | . ds ` \& 93 | . ds ^ \& 94 | . ds , \& 95 | . ds ~ ~ 96 | . ds / 97 | .\} 98 | .if t \{\ 99 | . ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u" 100 | . ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u' 101 | . ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u' 102 | . ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u' 103 | . ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u' 104 | . ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u' 105 | .\} 106 | . \" troff and (daisy-wheel) nroff accents 107 | .ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V' 108 | .ds 8 \h'\*(#H'\(*b\h'-\*(#H' 109 | .ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#] 110 | .ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H' 111 | .ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u' 112 | .ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#] 113 | .ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#] 114 | .ds ae a\h'-(\w'a'u*4/10)'e 115 | .ds Ae A\h'-(\w'A'u*4/10)'E 116 | . \" corrections for vroff 117 | .if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u' 118 | .if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u' 119 | . \" for low resolution devices (crt and lpr) 120 | .if \n(.H>23 .if \n(.V>19 \ 121 | \{\ 122 | . ds : e 123 | . ds 8 ss 124 | . ds o a 125 | . ds d- d\h'-1'\(ga 126 | . ds D- D\h'-1'\(hy 127 | . ds th \o'bp' 128 | . ds Th \o'LP' 129 | . ds ae ae 130 | . ds Ae AE 131 | .\} 132 | .rm #[ #] #H #V #F C 133 | .\" ======================================================================== 134 | .\" 135 | .IX Title "DH_PACKAGE_NOTES 1" 136 | .TH DH_PACKAGE_NOTES 1 "2022-11-27" 137 | .\" For nroff, turn off justification. Always turn off hyphenation; it makes 138 | .\" way too many mistakes in technical documents. 139 | .if n .ad l 140 | .nh 141 | .SH "NAME" 142 | dh_package_notes \- Add package metadata to ELF header 143 | .SH "SYNOPSIS" 144 | .IX Header "SYNOPSIS" 145 | \&\fBdh_package_notes\fR 146 | .SH "DESCRIPTION" 147 | .IX Header "DESCRIPTION" 148 | \&\fBdh_package_notes\fR provides a linker specs file to include package metadata in 149 | \&\s-1ELF\s0 binaries built by packages. 150 | .PP 151 | The package metadata specification for \s-1ELF\s0 binaries can be found at 152 | . 153 | .PP 154 | \&\fBdh_package_notes\fR provides a linker specs file at 155 | \&\fB/usr/share/debhelper/dh_package_notes/debian\-package\-notes.specs\fR which can be 156 | used by including \fB/usr/share/debhelper/dh_package_notes/package\-notes.mk\fR 157 | at the top of \fBdebian/rules\fR. This will export the right environment variables 158 | that will result in a package note \s-1ELF\s0 section being added to all packaged \s-1ELF\s0 159 | files, with the package type set to \fBdeb\fR, the package name and version set to 160 | the source package name and version respectively, the \s-1OS\s0 set to \fBDebian\fR and 161 | debuginfod \s-1URL\s0 set to \fBhttps://debuginfod.debian.net\fR. On Ubuntu, the \s-1OS\s0 162 | will be set to \fBUbuntu\fR and debuginfod \s-1URL\s0 will be set to 163 | \&\fBhttps://debuginfod.ubuntu.com\fR. 164 | .PP 165 | Note that it is not sufficient to build-depend on this package, the \fBinclude\fR 166 | must be used at the top of \fBdebian/rules\fR for this to be enabled. 167 | .SH "SEE ALSO" 168 | .IX Header "SEE ALSO" 169 | \&\fBdebhelper\fR\|(7) 170 | .SH "AUTHOR" 171 | .IX Header "AUTHOR" 172 | Luca Boccassi 173 | -------------------------------------------------------------------------------- /debian/dlopenlibdeps.pm: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | use strict; 4 | use warnings; 5 | use Debian::Debhelper::Dh_Lib; 6 | 7 | insert_after("dh_shlibdeps", "dh_dlopenlibdeps"); 8 | -------------------------------------------------------------------------------- /debian/package-notes.mk: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: CC0-1.0 2 | # Include from debian/rules to use with dh_package_notes. 3 | # See dh_package_notes(1) for details 4 | 5 | # llvm does not support spec files. Check for Meson-specific env vars too. 6 | ifeq ( ,$(filter lld, $(LD))) 7 | ifeq ( ,$(filter lld, $(CC_LD))) 8 | ifeq ( ,$(filter lld, $(CXX_LD))) 9 | # Ubuntu implemented this in dpkg-buildpackage, make this a no-op to avoid duplication 10 | include /usr/share/dpkg/vendor.mk 11 | ifneq ($(DEB_VENDOR),Ubuntu) 12 | # binutils 2.39 is required for --package-metadata= 13 | ifeq (0, $(shell dpkg --compare-versions $$(dpkg-query -f '$${Version}' -W binutils) ge 2.39; echo $$?)) 14 | ifeq (, $(filter nocheck, $(DEBUGINFOD_URLS))) 15 | export DEBUGINFOD_URLS=https://debuginfod.debian.net 16 | endif 17 | export DEB_SOURCE_PACKAGE_VERSION=$(shell dpkg-parsechangelog -S Version) 18 | export DEB_SOURCE_PACKAGE_NAME=$(shell dpkg-parsechangelog -S Source) 19 | # Set by /usr/share/dpkg/vendor.mk 20 | export DEB_VENDOR 21 | export DEB_LDFLAGS_MAINT_APPEND+= -specs=/usr/share/debhelper/dh_package_notes/debian-package-notes.specs 22 | endif 23 | endif 24 | endif 25 | endif 26 | endif 27 | 28 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | %: 4 | dh $@ 5 | 6 | execute_after_dh_auto_build: 7 | pod2man --utf8 $(CURDIR)/debian/dh_dlopenlibdeps > $(CURDIR)/debian/dh_dlopenlibdeps.1 8 | 9 | execute_after_dh_auto_clean: 10 | rm -f $(CURDIR)/debian/dh_dlopenlibdeps.1 11 | -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /dlopen-notes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # SPDX-License-Identifier: CC0-1.0 3 | 4 | """\ 5 | Read .note.dlopen notes from ELF files and report the contents. 6 | """ 7 | 8 | import argparse 9 | import enum 10 | import functools 11 | import json 12 | import sys 13 | from elftools.elf.elffile import ELFFile 14 | from elftools.elf.sections import NoteSection 15 | 16 | try: 17 | import rich 18 | print_json = rich.print_json 19 | except ImportError: 20 | print_json = print 21 | 22 | def listify(f): 23 | def wrap(*args, **kwargs): 24 | return list(f(*args, **kwargs)) 25 | return functools.update_wrapper(wrap, f) 26 | 27 | class ELFFileReader: 28 | def __init__(self, filename): 29 | self.filename = filename 30 | self.elffile = ELFFile(open(filename, 'rb')) 31 | 32 | @functools.cache 33 | @listify 34 | def notes(self): 35 | for section in self.elffile.iter_sections(): 36 | if not isinstance(section, NoteSection) or section.name != '.note.dlopen': 37 | continue 38 | 39 | for note in section.iter_notes(): 40 | if note['n_type'] != 0x407c0c0a or note['n_name'] != 'FDO': 41 | continue 42 | note_desc = note['n_desc'] 43 | 44 | try: 45 | # On older Python versions (e.g.: Ubuntu 22.04) we get a string, on 46 | # newer versions a bytestring 47 | if not isinstance(note_desc, str): 48 | text = note_desc.decode('utf-8').rstrip('\0') 49 | else: 50 | text = note_desc.rstrip('\0') 51 | except UnicodeDecodeError as e: 52 | raise ValueError(f'{self.filename}: Invalid UTF-8 in .note.dlopen n_desc') from e 53 | 54 | try: 55 | j = json.loads(text) 56 | except json.JSONDecodeError as e: 57 | raise ValueError(f'{self.filename}: Invalid JSON in .note.dlopen note_desc') from e 58 | 59 | if not isinstance(j, list): 60 | print(f'{self.filename}: ignoring .note.dlopen n_desc with JSON that is not a list', 61 | file=sys.stderr) 62 | continue 63 | 64 | yield from j 65 | 66 | def dictify(f): 67 | def wrap(*args, **kwargs): 68 | return dict(f(*args, **kwargs)) 69 | return functools.update_wrapper(wrap, f) 70 | 71 | @dictify 72 | def group_by_soname(elffiles): 73 | for elffile in elffiles: 74 | for element in elffile.notes(): 75 | priority = element.get('priority', 'recommended') 76 | for soname in element['soname']: 77 | yield soname, priority 78 | 79 | class Priority(enum.Enum): 80 | suggested = 1 81 | recommended = 2 82 | required = 3 83 | 84 | def __lt__(self, other): 85 | return self.value < other.value 86 | 87 | def group_by_feature(elffiles): 88 | features = {} 89 | 90 | # We expect each note to be in the format: 91 | # [ 92 | # { 93 | # "feature": "...", 94 | # "description": "...", 95 | # "priority": "required"|"recommended"|"suggested", 96 | # "soname": ["..."], 97 | # } 98 | # ] 99 | for elffiles in elffiles: 100 | for note in elffiles.notes(): 101 | prio = Priority[note.get('priority', 'recommened')] 102 | feature_name = note['feature'] 103 | 104 | try: 105 | feature = features[feature_name] 106 | except KeyError: 107 | # Create new 108 | feature = features[feature_name] = { 109 | 'description': note.get('description', ''), 110 | 'sonames': { soname:prio for soname in note['soname'] }, 111 | } 112 | else: 113 | # Merge 114 | if feature['description'] != note.get('description', ''): 115 | print(f"{note.filename}: feature {note['feature']!r} found with different description, ignoring", 116 | file=sys.stderr) 117 | 118 | for soname in note['soname']: 119 | highest = max(feature['sonames'].get(soname, Priority.suggested), 120 | prio) 121 | feature['sonames'][soname] = highest 122 | 123 | return features 124 | 125 | def filter_features(features, filter): 126 | if filter is None: 127 | return None 128 | ans = { name:feature for name,feature in features.items() 129 | if name in filter or not filter } 130 | if missing := set(filter) - set(ans): 131 | sys.exit('Some features not found:', ', '.join(missing)) 132 | return ans 133 | 134 | @listify 135 | def generate_rpm(elffiles, stanza, filter): 136 | # Produces output like: 137 | # Requires: libqrencode.so.4()(64bit) 138 | # Requires: libzstd.so.1()(64bit) 139 | for elffile in elffiles: 140 | suffix = '()(64bit)' if elffile.elffile.elfclass == 64 else '' 141 | for note in elffile.notes(): 142 | if note['feature'] in filter or not filter: 143 | soname = next(iter(note['soname'])) # we take the first — most recommended — soname 144 | yield f"{stanza}: {soname}{suffix}" 145 | 146 | def make_parser(): 147 | p = argparse.ArgumentParser( 148 | description=__doc__, 149 | allow_abbrev=False, 150 | add_help=False, 151 | epilog='If no option is specifed, --raw is the default.', 152 | ) 153 | p.add_argument( 154 | '-r', '--raw', 155 | action='store_true', 156 | help='Show the original JSON extracted from input files', 157 | ) 158 | p.add_argument( 159 | '-s', '--sonames', 160 | action='store_true', 161 | help='List all sonames and their priorities, one soname per line', 162 | ) 163 | p.add_argument( 164 | '-f', '--features', 165 | nargs='?', 166 | const=[], 167 | type=lambda s: s.split(','), 168 | action='extend', 169 | metavar='FEATURE1,FEATURE2', 170 | help='Describe features, can be specified multiple times', 171 | ) 172 | p.add_argument( 173 | '--rpm-requires', 174 | nargs='?', 175 | const=[], 176 | type=lambda s: s.split(','), 177 | action='extend', 178 | metavar='FEATURE1,FEATURE2', 179 | help='Generate rpm Requires for listed features', 180 | ) 181 | p.add_argument( 182 | '--rpm-recommends', 183 | nargs='?', 184 | const=[], 185 | type=lambda s: s.split(','), 186 | action='extend', 187 | metavar='FEATURE1,FEATURE2', 188 | help='Generate rpm Recommends for listed features', 189 | ) 190 | p.add_argument( 191 | 'filenames', 192 | nargs='+', 193 | metavar='filename', 194 | help='Library file to extract notes from', 195 | ) 196 | p.add_argument( 197 | '-h', '--help', 198 | action='help', 199 | help='Show this help message and exit', 200 | ) 201 | return p 202 | 203 | def parse_args(): 204 | args = make_parser().parse_args() 205 | 206 | if (not args.raw 207 | and not args.sonames 208 | and args.features is None 209 | and args.rpm_requires is None 210 | and args.rpm_recommends is None): 211 | # Make --raw the default if no action is specified. 212 | args.raw = True 213 | 214 | return args 215 | 216 | if __name__ == '__main__': 217 | args = parse_args() 218 | 219 | elffiles = [ELFFileReader(filename) for filename in args.filenames] 220 | features = group_by_feature(elffiles) 221 | 222 | if args.raw: 223 | for elffile in elffiles: 224 | print(f'# {elffile.filename}') 225 | print_json(json.dumps(elffile.notes(), indent=2)) 226 | 227 | if features_to_print := filter_features(features, args.features): 228 | print('# grouped by feature') 229 | print_json(json.dumps(features_to_print, 230 | indent=2, 231 | default=lambda prio: prio.name)) 232 | 233 | if args.rpm_requires is not None: 234 | lines = generate_rpm(elffiles, 'Requires', args.rpm_requires) 235 | print('\n'.join(lines)) 236 | 237 | if args.rpm_recommends is not None: 238 | lines = generate_rpm(elffiles, 'Recommends', args.rpm_recommends) 239 | print('\n'.join(lines)) 240 | 241 | if args.sonames: 242 | sonames = group_by_soname(elffiles) 243 | for soname in sorted(sonames.keys()): 244 | print(f"{soname} {sonames[soname]}") 245 | -------------------------------------------------------------------------------- /docs/dlopen-description.man: -------------------------------------------------------------------------------- 1 | /report the contents/ 2 | .PP 3 | ELF binaries store link-time dependencies in their headers, which can be parsed 4 | by various tools. There is no machine-readable metadata about dependencies 5 | loaded at build time via 6 | .BR \%dlopen (3) 7 | available by default. The ELF Dlopen Metadata specification aims to fill this 8 | gap by defining a common format. See https://systemd.io/ELF_DLOPEN_METADATA/. 9 | .PP 10 | This tool allows parsing such a note, and printing out the result in various 11 | formats. 12 | -------------------------------------------------------------------------------- /docs/dlopen-notes.1: -------------------------------------------------------------------------------- 1 | .TH DLOPEN\-NOTES.PY "1" "2024\-07\-23" "package\-notes" "Generated Python Manual" 2 | .SH NAME 3 | dlopen\-notes.py 4 | .SH SYNOPSIS 5 | .B dlopen\-notes.py 6 | [-r] [-s] [-f [FEATURE1,FEATURE2]] [-h] filename [filename ...] 7 | .SH DESCRIPTION 8 | Read .note.dlopen notes from ELF files and report the contents. 9 | .PP 10 | ELF binaries store link-time dependencies in their headers, which can be parsed 11 | by various tools. There is no machine-readable metadata about dependencies 12 | loaded at build time via 13 | .BR \%dlopen (3) 14 | available by default. The ELF Dlopen Metadata specification aims to fill this 15 | gap by defining a common format. See https://systemd.io/ELF_DLOPEN_METADATA/. 16 | .PP 17 | This tool allows parsing such a note, and printing out the result in various 18 | formats. 19 | 20 | .TP 21 | \fBfilename\fR 22 | Library file to extract notes from 23 | 24 | .SH OPTIONS 25 | .TP 26 | \fB\-r\fR, \fB\-\-raw\fR 27 | Show the original JSON extracted from input files 28 | 29 | .TP 30 | \fB\-s\fR, \fB\-\-sonames\fR 31 | List all sonames and their priorities, one soname per line 32 | 33 | .TP 34 | \fB\-f\fR \fI\,[FEATURE1,FEATURE2]\/\fR, \fB\-\-features\fR \fI\,[FEATURE1,FEATURE2]\/\fR 35 | Describe features, can be specified multiple times 36 | 37 | .SH COMMENTS 38 | If no option is specifed, \-\-raw is the default. 39 | -------------------------------------------------------------------------------- /hello.spec: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: CC0-1.0 2 | 3 | %bcond_without notes 4 | 5 | Name: hello 6 | Version: 0 7 | Release: 1%{?dist}%{!?with_notes:.nonotes} 8 | Summary: Aloha! 9 | 10 | License: CC0 11 | 12 | BuildRequires: binutils >= 2.39 13 | BuildRequires: gcc 14 | BuildRequires: rpmdevtools 15 | 16 | Source0: rpm/redhat-package-notes.in 17 | 18 | %description 19 | Test with: 20 | objdump -s -j .note.package %{_bindir}/hello 21 | objdump -s -j .note.package %{_libdir}/libhello.so 22 | 23 | %prep 24 | %setup -cT 25 | set -eo pipefail 26 | 27 | cat <libhello.c 28 | const char* greeting(void) { 29 | return "Hello"; 30 | } 31 | EOF 32 | cat <hello.c 33 | #include 34 | extern char* greeting(void); 35 | int main() { 36 | puts(greeting()); 37 | return 0; 38 | } 39 | EOF 40 | 41 | %build 42 | set -eo pipefail 43 | 44 | %if %{with notes} 45 | sed "s|@OSCPE@|$(cat /usr/lib/system-release-cpe)|" %{SOURCE0} >redhat-package-notes 46 | %endif 47 | 48 | LDFLAGS="%{build_ldflags} %{?with_notes:-specs=$PWD/redhat-package-notes}" 49 | CFLAGS="%{build_cflags}" 50 | 51 | gcc -Wall -fPIC -o libhello.so -shared libhello.c $CFLAGS $LDFLAGS 52 | gcc -Wall -o hello hello.c libhello.so $CFLAGS $LDFLAGS 53 | 54 | %install 55 | set -eo pipefail 56 | 57 | install -Dt %{buildroot}%{_libdir}/ libhello.so 58 | install -Dt %{buildroot}%{_bindir}/ hello 59 | 60 | %check 61 | set -eo pipefail 62 | 63 | %if %{with notes} 64 | objdump -s -j .note.package ./hello 65 | objdump -s -j .note.package ./libhello.so 66 | %endif 67 | 68 | %files 69 | %{_bindir}/hello 70 | %{_libdir}/libhello.so 71 | 72 | %changelog 73 | * Wed Feb 3 2021 Zbigniew Jędrzejewski-Szmek - 0-1 74 | - Test 75 | -------------------------------------------------------------------------------- /rpm/macros.package-notes-srpm: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: CC0-1.0 2 | # 3 | # This file is part of the package-notes package. 4 | # 5 | # Add an ELF note with information about the package the code was compiled for. 6 | # See https://fedoraproject.org/wiki/Changes/Package_information_on_ELF_objects 7 | # for details. 8 | # 9 | # To opt out of the use of this feature completely, include this in the spec 10 | # file: 11 | # 12 | # %undefine _package_note_flags 13 | # 14 | # Which linker will be used? This should be either "bfd", "gold", "mold", or "lld". 15 | # 16 | # (The default linker for clang on armv7hl is lld.) 17 | %_package_note_linker %["%_target_cpu" == "armv7hl" && "%{toolchain}" == "clang" ? "lld" : "bfd"] 18 | 19 | # These are defined for backwards compatibility. Do not use. 20 | %_package_note_file 1 21 | %_generate_package_note_file %{nil} 22 | 23 | # Overall status: 1 if looks like we can insert the note, 0 otherwise 24 | # Unfortunately "clang" does not support specs files so the note insertion is disabled when using it. 25 | %_package_note_status %{!?_package_note_flags:0}%{?_package_note_flags:%[0%{?_package_note_file:1} && 0%{?name:1} && "%_target_cpu" != "noarch" && "%{toolchain}" != "clang" ? 1 : 0]} 26 | 27 | 28 | # The linker flags to be passed to the compiler to insert the notes section will 29 | # be created by the spec file, to avoid issues with quoting and escaping across 30 | # different build systems and shells. 31 | %_package_note_flags %[%_package_note_status ? "-specs=/usr/lib/rpm/redhat/redhat-package-notes" : ""] 32 | -------------------------------------------------------------------------------- /rpm/redhat-package-notes.in: -------------------------------------------------------------------------------- 1 | *link: 2 | + --package-metadata={\"type\":\"rpm\",\"name\":\"%:getenv(RPM_PACKAGE_NAME \",\"version\":\"%:getenv(RPM_PACKAGE_VERSION -%:getenv(RPM_PACKAGE_RELEASE \",\"architecture\":\"%:getenv(RPM_ARCH \",\"osCpe\":\"@OSCPE@\"})))) 3 | -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | notes: notes.c 2 | $(CC) -o $@ $+ $(CFLAGS) $(LDFLAGS) $(LDLIBS) 3 | 4 | check: notes 5 | python3 -m pytest test.py 6 | 7 | clean: 8 | rm -f notes 9 | rm -rf __pycache__/ 10 | -------------------------------------------------------------------------------- /test/_notes.py: -------------------------------------------------------------------------------- 1 | ../dlopen-notes.py -------------------------------------------------------------------------------- /test/notes.c: -------------------------------------------------------------------------------- 1 | /* SPDX-License-Identifier: CC0-1.0 */ 2 | 3 | #include 4 | 5 | #define XCONCATENATE(x, y) x ## y 6 | #define CONCATENATE(x, y) XCONCATENATE(x, y) 7 | #define UNIQ_T(x, uniq) CONCATENATE(__unique_prefix_, CONCATENATE(x, uniq)) 8 | #define UNIQ __COUNTER__ 9 | 10 | #define ELF_NOTE_DLOPEN_VENDOR "FDO" 11 | #define ELF_NOTE_DLOPEN_TYPE 0x407c0c0a 12 | 13 | #define _ELF_NOTE_DLOPEN(module, variable_name) \ 14 | __attribute__((used, section(".note.dlopen"))) _Alignas(sizeof(uint32_t)) static const struct { \ 15 | struct { \ 16 | uint32_t n_namesz, n_descsz, n_type; \ 17 | } nhdr; \ 18 | char name[sizeof(ELF_NOTE_DLOPEN_VENDOR)]; \ 19 | _Alignas(sizeof(uint32_t)) char dlopen_module[sizeof(module)]; \ 20 | } variable_name = { \ 21 | .nhdr = { \ 22 | .n_namesz = sizeof(ELF_NOTE_DLOPEN_VENDOR), \ 23 | .n_descsz = sizeof(module), \ 24 | .n_type = ELF_NOTE_DLOPEN_TYPE, \ 25 | }, \ 26 | .name = ELF_NOTE_DLOPEN_VENDOR, \ 27 | .dlopen_module = module, \ 28 | } 29 | 30 | #define _SONAME_ARRAY1(a) "[\""a"\"]" 31 | #define _SONAME_ARRAY2(a, b) "[\""a"\",\""b"\"]" 32 | #define _SONAME_ARRAY3(a, b, c) "[\""a"\",\""b"\",\""c"\"]" 33 | #define _SONAME_ARRAY4(a, b, c, d) "[\""a"\",\""b"\",\""c"\"",\""d"\"]" 34 | #define _SONAME_ARRAY5(a, b, c, d, e) "[\""a"\",\""b"\",\""c"\"",\""d"\",\""e"\"]" 35 | #define _SONAME_ARRAY_GET(_1,_2,_3,_4,_5,NAME,...) NAME 36 | #define _SONAME_ARRAY(...) _SONAME_ARRAY_GET(__VA_ARGS__, _SONAME_ARRAY5, _SONAME_ARRAY4, _SONAME_ARRAY3, _SONAME_ARRAY2, _SONAME_ARRAY1)(__VA_ARGS__) 37 | 38 | #define ELF_NOTE_DLOPEN(feature, description, priority, ...) \ 39 | _ELF_NOTE_DLOPEN("[{\"feature\":\"" feature "\",\"description\":\"" description "\",\"priority\":\"" priority "\",\"soname\":" _SONAME_ARRAY(__VA_ARGS__) "}]", UNIQ_T(s, UNIQ)) 40 | 41 | #define ELF_NOTE_DLOPEN_DUAL(feature0, priority0, module0, feature1, priority1, module1) \ 42 | _ELF_NOTE_DLOPEN("[{\"feature\":\"" feature0 "\",\"priority\":\"" priority0 "\",\"soname\":[\"" module0 "\"]}, {\"feature\":\"" feature1 "\",\"priority\":\"" priority1 "\",\"soname\":[\"" module1 "\"]}]", UNIQ_T(s, UNIQ)) 43 | 44 | int main(int argc, char **argv) { 45 | ELF_NOTE_DLOPEN("fido2", "Support fido2 for encryption and authentication.", "required", "libfido2.so.1"); 46 | ELF_NOTE_DLOPEN("pcre2", "Support pcre2 for regex", "suggested", "libpcre2-8.so.0","libpcre2-8.so.1"); 47 | ELF_NOTE_DLOPEN("lz4", "Support lz4 decompression in journal and coredump files", "recommended", "liblz4.so.1"); 48 | ELF_NOTE_DLOPEN_DUAL("tpm", "recommended", "libtss2-mu.so.0", "tpm", "recommended", "libtss2-esys.so.0"); 49 | return 0; 50 | } 51 | -------------------------------------------------------------------------------- /test/test.py: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: CC0-1.0 2 | 3 | from _notes import ELFFileReader, group_by_soname, generate_rpm 4 | 5 | def test_sonames(): 6 | expected = { 7 | 'libfido2.so.1': 'required', 8 | 'liblz4.so.1': 'recommended', 9 | 'libpcre2-8.so.0': 'suggested', 10 | 'libpcre2-8.so.1': 'suggested', 11 | 'libtss2-esys.so.0': 'recommended', 12 | 'libtss2-mu.so.0': 'recommended', 13 | } 14 | notes = ELFFileReader('notes') 15 | assert group_by_soname([notes]) == expected 16 | 17 | def test_requires(): 18 | notes = ELFFileReader('notes') 19 | 20 | expected = { 21 | 32: [ 22 | 'Suggests: libpcre2-8.so.0', 23 | 'Suggests: libtss2-mu.so.0', 24 | 'Suggests: libtss2-esys.so.0', 25 | ], 26 | 64: [ 27 | 'Suggests: libpcre2-8.so.0()(64bit)', 28 | 'Suggests: libtss2-mu.so.0()(64bit)', 29 | 'Suggests: libtss2-esys.so.0()(64bit)', 30 | ], 31 | } 32 | 33 | lines = generate_rpm([notes], 'Suggests', ('pcre2', 'tpm')) 34 | expect = expected[notes.elffile.elfclass] 35 | assert lines == expect 36 | --------------------------------------------------------------------------------