| 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()): |
| # soname list contains alternatives; find the first available provider |
| found = False |
| for soname in dep["soname"]: |
| if soname in shlibs: |
| 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})") |
| found = True |
| break |
| if not found: |
| bb.warn(f"cannot find any provider for dlopen dependency: {dep['soname']}") |
| except oe.qa.NotELFFileError as e: |
| bb.note(f"Cannot extract ELF notes: {e}") |
| pass |
| } |