diff options
Diffstat (limited to 'meta/recipes-core/systemd/dlopen-deps.inc')
-rw-r--r-- | meta/recipes-core/systemd/dlopen-deps.inc | 81 |
1 files changed, 81 insertions, 0 deletions
diff --git a/meta/recipes-core/systemd/dlopen-deps.inc b/meta/recipes-core/systemd/dlopen-deps.inc new file mode 100644 index 0000000000..e0b333398c --- /dev/null +++ b/meta/recipes-core/systemd/dlopen-deps.inc | |||
@@ -0,0 +1,81 @@ | |||
1 | PACKAGEFUNCS =+ "package_generate_dlopen_deps" | ||
2 | |||
3 | python package_generate_dlopen_deps() { | ||
4 | # https://systemd.io/ELF_DLOPEN_METADATA/ | ||
5 | |||
6 | import struct, json | ||
7 | |||
8 | def extract_segment(filename, segment): | ||
9 | """ | ||
10 | Return the named segment from the ELF. | ||
11 | """ | ||
12 | import tempfile, subprocess | ||
13 | |||
14 | with tempfile.NamedTemporaryFile() as f: | ||
15 | try: | ||
16 | cmd = [d.getVar("OBJCOPY"), "--dump-section", f"{segment}={f.name}", filename] | ||
17 | subprocess.run(cmd, check=True) | ||
18 | with open(f.name, "rb") as f2: | ||
19 | return f2.read() | ||
20 | except subprocess.CalledProcessError as e: | ||
21 | # binutils-objcopy has 0 exit code if the segment can't be found, but llvm-objcopy | ||
22 | # does not. Assume the failure isn't critical and ignore errors. | ||
23 | if e.returncode == 1: | ||
24 | return b"" | ||
25 | raise e | ||
26 | |||
27 | def parse(buffer, is_little): | ||
28 | deps = [] | ||
29 | offset = 0 | ||
30 | while offset < len(buffer): | ||
31 | format = f"{'<' if is_little else '>'}iii" | ||
32 | name_size, desc_size, note_type = struct.unpack_from(format, buffer, offset) | ||
33 | offset += struct.calcsize(format) | ||
34 | |||
35 | format = f"{name_size}s0i{desc_size}s0i" | ||
36 | if note_type == 0x407c0c0a: | ||
37 | name_b, desc_b = struct.unpack_from(format, buffer, offset) | ||
38 | name = name_b.strip(b"\x00").decode("ascii") | ||
39 | if name == "FDO": | ||
40 | desc = desc_b.strip(b"\x00").decode("utf-8") | ||
41 | deps.append(*json.loads(desc)) | ||
42 | offset += struct.calcsize(format) | ||
43 | return deps | ||
44 | |||
45 | dep_map = { | ||
46 | "required": "RDEPENDS", | ||
47 | "recommended": "RRECOMMENDS", | ||
48 | "suggested": "RSUGGESTS" | ||
49 | } | ||
50 | |||
51 | shlibs = oe.package.read_shlib_providers(d) | ||
52 | |||
53 | for pkg, files in pkgfiles.items(): | ||
54 | # Skip -dbg packages as we won't need to generate dependencies for those | ||
55 | # but scanning can take time | ||
56 | if pkg.endswith("-dbg"): | ||
57 | continue | ||
58 | |||
59 | for f in files: | ||
60 | # Skip symlinks, just look for real libraries | ||
61 | if cpath.islink(f): | ||
62 | continue | ||
63 | |||
64 | if ".so." in f or f.endswith(".so"): | ||
65 | try: | ||
66 | elf = oe.qa.ELFFile(f) | ||
67 | elf.open() | ||
68 | for dep in parse(extract_segment(f, ".note.dlopen"), elf.isLittleEndian()): | ||
69 | for soname in dep["soname"]: | ||
70 | if soname in shlibs: | ||
71 | # TODO assumes the first match is good | ||
72 | package, version = list(shlibs[soname].values())[0] | ||
73 | dependency = dep_map[dep["priority"]] | ||
74 | bb.note(f"{pkg}: adding {dependency} on {package} via .note.dlopen") | ||
75 | d.appendVar(f"{dependency}:{pkg}", f" {package} (>= {version})") | ||
76 | else: | ||
77 | bb.warn(f"cannot find {soname}") | ||
78 | except oe.qa.NotELFFileError as e: | ||
79 | bb.note(f"Cannot extract ELF notes: {e}") | ||
80 | pass | ||
81 | } | ||