Files
poky/meta/recipes-core/systemd/dlopen-deps.inc
Ross Burton e7d470c04f systemd: handle llvm-objcopy behaviour when reading .note.dlopen section
There are two behavioural differences between the objcopy in binutils
and llvm which resulted in build failures when building systemd with
clang:

1) If the section specified in --dump-section doesn't exist, binutils
set an exit code of 0 whereas llvm sets 1.  This means we need to handle
the exit code so that we raise exceptions on unexpected failures, but
return an empty byte string if the segment isn't found.

2) binutils writes the section to the file name directly, whereas llvm
writes to a temporary file and renames.  This means we can't read the
open fd directly, and instead need to re-open the file to read it.

(From OE-Core rev: 98879a8dbd1b7887b43a074193925bf1a55d44e7)

Signed-off-by: Ross Burton <ross.burton@arm.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
2024-11-23 14:44:54 +00:00

82 lines
3.1 KiB
C++

PACKAGEFUNCS =+ "package_generate_dlopen_deps"
python package_generate_dlopen_deps() {
# https://systemd.io/ELF_DLOPEN_METADATA/
import struct, json
def extract_segment(filename, segment):
"""
Return the named segment from the ELF.
"""
import tempfile, subprocess
with tempfile.NamedTemporaryFile() as f:
try:
cmd = [d.getVar("OBJCOPY"), "--dump-section", f"{segment}={f.name}", filename]
subprocess.run(cmd, check=True)
with open(f.name, "rb") as f2:
return f2.read()
except subprocess.CalledProcessError as e:
# binutils-objcopy has 0 exit code if the segment can't be found, but llvm-objcopy
# does not. Assume the failure isn't critical and ignore errors.
if e.returncode == 1:
return b""
raise e
def parse(buffer, is_little):
deps = []
offset = 0
while offset < len(buffer):
format = f"{'<' if is_little else '>'}iii"
name_size, desc_size, note_type = struct.unpack_from(format, buffer, offset)
offset += struct.calcsize(format)
format = f"{name_size}s0i{desc_size}s0i"
if note_type == 0x407c0c0a:
name_b, desc_b = struct.unpack_from(format, buffer, offset)
name = name_b.strip(b"\x00").decode("ascii")
if name == "FDO":
desc = desc_b.strip(b"\x00").decode("utf-8")
deps.append(*json.loads(desc))
offset += struct.calcsize(format)
return deps
dep_map = {
"required": "RDEPENDS",
"recommended": "RRECOMMENDS",
"suggested": "RSUGGESTS"
}
shlibs = oe.package.read_shlib_providers(d)
for pkg, files in pkgfiles.items():
# Skip -dbg packages as we won't need to generate dependencies for those
# but scanning can take time
if pkg.endswith("-dbg"):
continue
for f in files:
# Skip symlinks, just look for real libraries
if cpath.islink(f):
continue
if ".so." in f or f.endswith(".so"):
try:
elf = oe.qa.ELFFile(f)
elf.open()
for dep in parse(extract_segment(f, ".note.dlopen"), elf.isLittleEndian()):
for soname in dep["soname"]:
if soname in shlibs:
# TODO assumes the first match is good
package, version = list(shlibs[soname].values())[0]
dependency = dep_map[dep["priority"]]
bb.note(f"{pkg}: adding {dependency} on {package} via .note.dlopen")
d.appendVar(f"{dependency}:{pkg}", f" {package} (>= {version})")
else:
bb.warn(f"cannot find {soname}")
except oe.qa.NotELFFileError as e:
bb.note(f"Cannot extract ELF notes: {e}")
pass
}