|
| 1 | +--- |
| 2 | +title: Dlopen Metadata for ELF Files |
| 3 | +category: Interfaces |
| 4 | +layout: default |
| 5 | +version: 1.0 |
| 6 | +SPDX-License-Identifier: CC-BY-4.0 |
| 7 | +--- |
| 8 | + |
| 9 | +# `dlopen()` Metadata for ELF Files |
| 10 | + |
| 11 | +| Version | Changes | |
| 12 | +|---------|---------| |
| 13 | +| 1.0 | Initial Release | |
| 14 | + |
| 15 | +## Target Audience |
| 16 | + |
| 17 | +The target audience for this specification is: |
| 18 | + |
| 19 | +* Developers working on userspace subsystems that create ELF binaries that dynamically load libraries |
| 20 | +* Developers working on userspace subsystems that package ELF binaries that dynamically load libraries |
| 21 | + |
| 22 | +## Motivation |
| 23 | + |
| 24 | +Using `dlopen()` to load optional dependencies brings several advantages: programs can gracefully downgrade |
| 25 | +a feature when a library is not available, and the shared library is only loaded into the process (and its |
| 26 | +ELF constructors are run) only when the requested feature is actually used. But it also has some drawbacks, |
| 27 | +and the main one is that it is harder to track a program's dependencies, since unlike build-time dynamic |
| 28 | +linking there will not be a mention in the ELF metadata. This specification aims to solve this problem by |
| 29 | +providing a standardized specification for a custom ELF note that can be used to list `dlopen()` |
| 30 | +dependencies. |
| 31 | + |
| 32 | +## Implementation |
| 33 | + |
| 34 | +This document will attempt to define a common metadata format specification, so that multiple implementers |
| 35 | +might use it when coding upstream software, and packagers might use it when building packages and setting |
| 36 | +dependencies. |
| 37 | + |
| 38 | +The metadata will be embedded in a series of new, 4-byte-aligned, allocated, 0-padded, read-only ELF header |
| 39 | +sections, in a JSON array containing name-value objects, either one ELF note per dependency or as a single |
| 40 | +note listing multiple dependencies in the top-level array. Implementers working on parsing ELF files should |
| 41 | +not assume a specific list of names, but parse anything that is included in the section, and should look for |
| 42 | +the note using the `note type`. Implementers working on build tools should strive to use the same names, for |
| 43 | +consistency. The most common will be listed here. |
| 44 | + |
| 45 | +* Section header |
| 46 | + |
| 47 | +``` |
| 48 | +SECTION: `.note.dlopen` |
| 49 | +note type: `0x407c0c0a` |
| 50 | +Owner: `FDO` (FreeDesktop.org) |
| 51 | +Value: an array of JSON objects encoded as a zero-terminated UTF-8 string |
| 52 | +``` |
| 53 | + |
| 54 | +* JSON payload |
| 55 | + |
| 56 | +```json |
| 57 | +[ |
| 58 | + { |
| 59 | + "soname": ["libfoo.so.1"], |
| 60 | + "feature": "foo", |
| 61 | + "description": "Enables the foo feature", |
| 62 | + "priority": "recommended" |
| 63 | + } |
| 64 | +] |
| 65 | +``` |
| 66 | + |
| 67 | +The format is a single JSON array containing objects, encoded as a zero-terminated `UTF-8` string. Each key |
| 68 | +in each object shall be unique as per recommendations of [RFC8259](https://datatracker.ietf.org/doc/html/rfc8259#section-4). |
| 69 | +Strings shall not contain any control characters or use `\uXXX` escaping. |
| 70 | + |
| 71 | +Reference implementations of [packaging tools for `.deb` and `.rpm`](https://github.com/systemd/package-notes) |
| 72 | +are available, and provide macros/helpers to parse the note when building packages and adding dependencies. |
| 73 | + |
| 74 | +## Well-known keys |
| 75 | + |
| 76 | +The metadata format is intentionally extensible, so that upstreams and later revisions of this spec can add |
| 77 | +their own information. The 'soname' array is required, with at least one element, everything else is |
| 78 | +optional. If alternative soname versions for the same library are supported at the same time, an array can |
| 79 | +be used, listing the most preferred first, and parsers are expected to select only the first one that is |
| 80 | +available on the system, as it is a mechanism to specify alternatives. If the `priority` field is used, it |
| 81 | +must follow the specification and use one of the values specified in the table. If it is not specified, a |
| 82 | +parser should assume 'recommended' if a priority is needed. If the `feature` field is used, it will identify |
| 83 | +an individual feature, and multiple entries using the same `feature` denote functionality that requires all |
| 84 | +of the libraries they specify in order to be enabled. |
| 85 | + |
| 86 | +| Key name | Key type | Mandatory | Key description | Example value | |
| 87 | +|-------------|----------------------------|-----------|--------------------------------------------------------------------------|----------------------------------| |
| 88 | +| soname | array of strings | yes | The library names loaded by `dlopen()` | [ "libfoo.so.1", "libfoo.so.0" ] | |
| 89 | +| feature | string | no | A keyword identifying the feature that the library contributes to enable | "foo" | |
| 90 | +| description | string | no | A human-readable text string describing the feature | "Enables the foo feature" | |
| 91 | +| priority | string | no | The priority of the feature, one of: required, recommended, suggested | "recommended" | |
| 92 | + |
| 93 | +### Priority definition |
| 94 | + |
| 95 | +| Priority | Semantics | |
| 96 | +|-------------|--------------------------------------------------------------------------------------------------------------------------------------| |
| 97 | +| required | Core functionality needs the dependency, the binary will not work if it cannot be found | |
| 98 | +| recommended | Important functionality needs the dependency, the binary will work but in most cases the dependency should be provided | |
| 99 | +| suggested | Secondary functionality needs the dependency, the binary will work and the dependency is only needed for full-featured installations | |
| 100 | + |
| 101 | +### Displaying `dlopen()` notes |
| 102 | + |
| 103 | +The raw ELF section can be extracted using `objdump`: |
| 104 | +```console |
| 105 | +$ objdump -j .note.dlopen -s /usr/lib64/systemd/libsystemd-shared-257.so |
| 106 | + |
| 107 | +/usr/lib64/systemd/libsystemd-shared-257.so: file format elf64-x86-64 |
| 108 | + |
| 109 | +Contents of section .note.dlopen: |
| 110 | + 0334 04000000 8e000000 0a0c7c40 46444f00 ..........|@FDO. |
| 111 | + 0344 5b7b2266 65617475 7265223a 22627066 [{"feature":"bpf |
| 112 | + 0354 222c2264 65736372 69707469 6f6e223a ","description": |
| 113 | + 0364 22537570 706f7274 20666972 6577616c "Support firewal |
| 114 | + 0374 6c696e67 20616e64 2073616e 64626f78 ling and sandbox |
| 115 | + 0384 696e6720 77697468 20425046 222c2270 ing with BPF","p |
| 116 | + 0394 72696f72 69747922 3a227375 67676573 riority":"sugges |
| 117 | + 03a4 74656422 2c22736f 6e616d65 223a5b22 ted","soname":[" |
| 118 | + 03b4 6c696262 70662e73 6f2e3122 2c226c69 libbpf.so.1","li |
| 119 | + 03c4 62627066 2e736f2e 30225d7d 5d000000 bbpf.so.0"]}]... |
| 120 | + 03d4 04000000 9e000000 0a0c7c40 46444f00 ..........|@FDO. |
| 121 | +... |
| 122 | +``` |
| 123 | + |
| 124 | +It is more convenient to use a higher level tool: |
| 125 | +```console |
| 126 | +$ dlopen-notes /usr/lib64/systemd/libsystemd-shared-257.so |
| 127 | +# /usr/lib64/systemd/libsystemd-shared-257.so |
| 128 | +[ |
| 129 | + { |
| 130 | + "feature": "archive", |
| 131 | + "description": "Support for decompressing archive files", |
| 132 | + "priority": "suggested", |
| 133 | + "soname": [ |
| 134 | + "libarchive.so.13" |
| 135 | + ] |
| 136 | + }, |
| 137 | + { |
| 138 | + "feature": "bpf", |
| 139 | + "description": "Support firewalling and sandboxing with BPF", |
| 140 | + "priority": "suggested", |
| 141 | + "soname": [ |
| 142 | + "libbpf.so.1", |
| 143 | + "libbpf.so.0" |
| 144 | + ] |
| 145 | + }, |
| 146 | +... |
| 147 | +``` |
| 148 | + |
| 149 | +`dlopen-notes` can display the notes grouped in a few different ways. |
| 150 | +One option is to filter the libraries by "feature". This answers the |
| 151 | +question "what libraries are needed to provide specified features": |
| 152 | + |
| 153 | +```console |
| 154 | +$ dlopen-notes.py -f archive,bpf /usr/lib64/systemd/libsystemd-shared-257.so |
| 155 | +# grouped by feature |
| 156 | +{ |
| 157 | + "bpf": { |
| 158 | + "description": "Support firewalling and sandboxing with BPF", |
| 159 | + "sonames": { |
| 160 | + "libbpf.so.1": "suggested", |
| 161 | + "libbpf.so.0": "suggested" |
| 162 | + } |
| 163 | + }, |
| 164 | + "archive": { |
| 165 | + "description": "Support for decompressing archive files", |
| 166 | + "sonames": { |
| 167 | + "libarchive.so.13": "suggested" |
| 168 | + } |
| 169 | + } |
| 170 | +} |
| 171 | + |
| 172 | +The format that is used when building `deb` packages: |
| 173 | +```console |
| 174 | +$ dlopen-notes -s /usr/lib64/systemd/libsystemd-shared-257.so |
| 175 | +libarchive.so.13 suggested |
| 176 | +libbpf.so.0 suggested |
| 177 | +libbpf.so.1 suggested |
| 178 | +... |
| 179 | +``` |
| 180 | + |
| 181 | +The format that can be useful when building `rpm` packages: |
| 182 | +```console |
| 183 | +$ dlopen-notes --rpm-requires archive --rpm-recommends bpf /usr/lib64/systemd/libsystemd-shared-257.so |
| 184 | +Requires: libarchive.so.13()(64bit) |
| 185 | +Recommends: libbpf.so.1()(64bit) |
| 186 | +``` |
0 commit comments