autoPatchelfHook: add support for .note.dlopen
also retain libc in rpath if it was there originally
This commit is contained in:
committed by
Will Fancher
parent
ffbecb3c19
commit
80be92660e
@@ -5,16 +5,18 @@ import os
|
|||||||
import pprint
|
import pprint
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
import json
|
||||||
from fnmatch import fnmatch
|
from fnmatch import fnmatch
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from pathlib import Path, PurePath
|
from pathlib import Path, PurePath
|
||||||
from typing import DefaultDict, Iterator, Optional
|
from typing import DefaultDict, Generator, Iterator, Optional
|
||||||
|
|
||||||
from elftools.common.exceptions import ELFError # type: ignore
|
from elftools.common.exceptions import ELFError # type: ignore
|
||||||
from elftools.elf.dynamic import DynamicSection # type: ignore
|
from elftools.elf.dynamic import DynamicSection # type: ignore
|
||||||
|
from elftools.elf.sections import NoteSection # type: ignore
|
||||||
from elftools.elf.elffile import ELFFile # type: ignore
|
from elftools.elf.elffile import ELFFile # type: ignore
|
||||||
from elftools.elf.enums import ENUM_E_TYPE, ENUM_EI_OSABI # type: ignore
|
from elftools.elf.enums import ENUM_E_TYPE, ENUM_EI_OSABI # type: ignore
|
||||||
|
|
||||||
@@ -38,7 +40,7 @@ def is_dynamic_executable(elf: ELFFile) -> bool:
|
|||||||
return bool(elf.get_section_by_name(".interp"))
|
return bool(elf.get_section_by_name(".interp"))
|
||||||
|
|
||||||
|
|
||||||
def get_dependencies(elf: ELFFile) -> list[str]:
|
def get_dependencies(elf: ELFFile) -> list[list[Path]]:
|
||||||
dependencies = []
|
dependencies = []
|
||||||
# This convoluted code is here on purpose. For some reason, using
|
# This convoluted code is here on purpose. For some reason, using
|
||||||
# elf.get_section_by_name(".dynamic") does not always return an
|
# elf.get_section_by_name(".dynamic") does not always return an
|
||||||
@@ -46,12 +48,33 @@ def get_dependencies(elf: ELFFile) -> list[str]:
|
|||||||
for section in elf.iter_sections():
|
for section in elf.iter_sections():
|
||||||
if isinstance(section, DynamicSection):
|
if isinstance(section, DynamicSection):
|
||||||
for tag in section.iter_tags('DT_NEEDED'):
|
for tag in section.iter_tags('DT_NEEDED'):
|
||||||
dependencies.append(tag.needed)
|
dependencies.append([Path(tag.needed)])
|
||||||
break # There is only one dynamic section
|
break # There is only one dynamic section
|
||||||
|
|
||||||
return dependencies
|
return dependencies
|
||||||
|
|
||||||
|
|
||||||
|
def get_dlopen_dependencies(elf: ELFFile) -> list[list[Path]]:
|
||||||
|
"""
|
||||||
|
Extracts dependencies from the `.note.dlopen` section.
|
||||||
|
This is a FreeDesktop standard to annotate binaries with libraries that it may `dlopen`.
|
||||||
|
See https://systemd.io/ELF_DLOPEN_METADATA/
|
||||||
|
"""
|
||||||
|
dependencies = []
|
||||||
|
for section in elf.iter_sections():
|
||||||
|
if not isinstance(section, NoteSection) or section.name != ".note.dlopen":
|
||||||
|
continue
|
||||||
|
for note in section.iter_notes():
|
||||||
|
if note["n_type"] != 0x407C0C0A or note["n_name"] != "FDO":
|
||||||
|
continue
|
||||||
|
note_desc = note["n_desc"]
|
||||||
|
text = note_desc.decode("utf-8").rstrip("\0")
|
||||||
|
j = json.loads(text)
|
||||||
|
for d in j:
|
||||||
|
dependencies.append([Path(soname) for soname in d["soname"]])
|
||||||
|
return dependencies
|
||||||
|
|
||||||
|
|
||||||
def get_rpath(elf: ELFFile) -> list[str]:
|
def get_rpath(elf: ELFFile) -> list[str]:
|
||||||
# This convoluted code is here on purpose. For some reason, using
|
# This convoluted code is here on purpose. For some reason, using
|
||||||
# elf.get_section_by_name(".dynamic") does not always return an
|
# elf.get_section_by_name(".dynamic") does not always return an
|
||||||
@@ -204,7 +227,7 @@ def auto_patchelf_file(path: Path, runtime_deps: list[Path], append_rpaths: list
|
|||||||
|
|
||||||
file_is_dynamic_executable = is_dynamic_executable(elf)
|
file_is_dynamic_executable = is_dynamic_executable(elf)
|
||||||
|
|
||||||
file_dependencies = map(Path, get_dependencies(elf))
|
file_dependencies = get_dependencies(elf) + get_dlopen_dependencies(elf)
|
||||||
|
|
||||||
except ELFError:
|
except ELFError:
|
||||||
return []
|
return []
|
||||||
@@ -223,24 +246,44 @@ def auto_patchelf_file(path: Path, runtime_deps: list[Path], append_rpaths: list
|
|||||||
# failing at the first one, because it's more useful when working
|
# failing at the first one, because it's more useful when working
|
||||||
# on a new package where you don't yet know the dependencies.
|
# on a new package where you don't yet know the dependencies.
|
||||||
for dep in file_dependencies:
|
for dep in file_dependencies:
|
||||||
if dep.is_absolute() and dep.is_file():
|
was_found = False
|
||||||
# This is an absolute path. If it exists, just use it.
|
for candidate in dep:
|
||||||
# Otherwise, we probably want this to produce an error when
|
|
||||||
# checked (because just updating the rpath won't satisfy
|
|
||||||
# it).
|
|
||||||
continue
|
|
||||||
elif (libc_lib / dep).is_file():
|
|
||||||
# This library exists in libc, and will be correctly
|
|
||||||
# resolved by the linker.
|
|
||||||
continue
|
|
||||||
|
|
||||||
if found_dependency := find_dependency(dep.name, file_arch, file_osabi):
|
# This loop determines which candidate for a given
|
||||||
rpath.append(found_dependency)
|
# dependency can be found, and how. There may be multiple
|
||||||
dependencies.append(Dependency(path, dep, True))
|
# candidates for a dep because of '.note.dlopen'
|
||||||
print(f" {dep} -> found: {found_dependency}")
|
# dependencies.
|
||||||
else:
|
#
|
||||||
dependencies.append(Dependency(path, dep, False))
|
# 1. If a candidate is an absolute path, it is already a
|
||||||
print(f" {dep} -> not found!")
|
# valid dependency if that path exists, and nothing needs
|
||||||
|
# to be done. It should be an error if that path does not exist.
|
||||||
|
# 2. If a candidate is found in our library dependencies, that
|
||||||
|
# dependency should be added to rpath.
|
||||||
|
# 3. If a candidate is found in libc, it will be correctly
|
||||||
|
# resolved by the dynamic linker automatically.
|
||||||
|
#
|
||||||
|
# These conditions are checked in this order, because #2
|
||||||
|
# and #3 may both be true. In that case, we still want to
|
||||||
|
# add the dependency to rpath, as the original binary
|
||||||
|
# presumably had it and this should be preserved.
|
||||||
|
|
||||||
|
if candidate.is_absolute() and candidate.is_file():
|
||||||
|
was_found = True
|
||||||
|
break
|
||||||
|
elif found_dependency := find_dependency(candidate.name, file_arch, file_osabi):
|
||||||
|
rpath.append(found_dependency)
|
||||||
|
dependencies.append(Dependency(path, candidate, found=True))
|
||||||
|
print(f" {candidate} -> found: {found_dependency}")
|
||||||
|
was_found = True
|
||||||
|
break
|
||||||
|
elif (libc_lib / candidate).is_file():
|
||||||
|
was_found = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if not was_found:
|
||||||
|
dep_name = dep[0] if len(dep) == 1 else f"any({', '.join(map(str, dep))})"
|
||||||
|
dependencies.append(Dependency(path, dep_name, found=False))
|
||||||
|
print(f" {dep_name} -> not found!")
|
||||||
|
|
||||||
rpath.extend(append_rpaths)
|
rpath.extend(append_rpaths)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user