|
|
|
@ -15,6 +15,7 @@ |
|
|
|
|
|
|
|
|
|
import sys, struct |
|
|
|
|
import shutil, subprocess |
|
|
|
|
import typing as T |
|
|
|
|
|
|
|
|
|
from ..mesonlib import OrderedSet |
|
|
|
|
|
|
|
|
@ -30,7 +31,7 @@ DT_MIPS_RLD_MAP_REL = 1879048245 |
|
|
|
|
INSTALL_NAME_TOOL = False |
|
|
|
|
|
|
|
|
|
class DataSizes: |
|
|
|
|
def __init__(self, ptrsize, is_le): |
|
|
|
|
def __init__(self, ptrsize: int, is_le: bool) -> None: |
|
|
|
|
if is_le: |
|
|
|
|
p = '<' |
|
|
|
|
else: |
|
|
|
@ -57,7 +58,7 @@ class DataSizes: |
|
|
|
|
self.OffSize = 4 |
|
|
|
|
|
|
|
|
|
class DynamicEntry(DataSizes): |
|
|
|
|
def __init__(self, ifile, ptrsize, is_le): |
|
|
|
|
def __init__(self, ifile: T.BinaryIO, ptrsize: int, is_le: bool) -> None: |
|
|
|
|
super().__init__(ptrsize, is_le) |
|
|
|
|
self.ptrsize = ptrsize |
|
|
|
|
if ptrsize == 64: |
|
|
|
@ -67,7 +68,7 @@ class DynamicEntry(DataSizes): |
|
|
|
|
self.d_tag = struct.unpack(self.Sword, ifile.read(self.SwordSize))[0] |
|
|
|
|
self.val = struct.unpack(self.Word, ifile.read(self.WordSize))[0] |
|
|
|
|
|
|
|
|
|
def write(self, ofile): |
|
|
|
|
def write(self, ofile: T.BinaryIO) -> None: |
|
|
|
|
if self.ptrsize == 64: |
|
|
|
|
ofile.write(struct.pack(self.Sxword, self.d_tag)) |
|
|
|
|
ofile.write(struct.pack(self.XWord, self.val)) |
|
|
|
@ -76,7 +77,7 @@ class DynamicEntry(DataSizes): |
|
|
|
|
ofile.write(struct.pack(self.Word, self.val)) |
|
|
|
|
|
|
|
|
|
class SectionHeader(DataSizes): |
|
|
|
|
def __init__(self, ifile, ptrsize, is_le): |
|
|
|
|
def __init__(self, ifile: T.BinaryIO, ptrsize: int, is_le: bool) -> None: |
|
|
|
|
super().__init__(ptrsize, is_le) |
|
|
|
|
if ptrsize == 64: |
|
|
|
|
is_64 = True |
|
|
|
@ -116,10 +117,12 @@ class SectionHeader(DataSizes): |
|
|
|
|
self.sh_entsize = struct.unpack(self.Word, ifile.read(self.WordSize))[0] |
|
|
|
|
|
|
|
|
|
class Elf(DataSizes): |
|
|
|
|
def __init__(self, bfile, verbose=True): |
|
|
|
|
def __init__(self, bfile: str, verbose: bool = True) -> None: |
|
|
|
|
self.bfile = bfile |
|
|
|
|
self.verbose = verbose |
|
|
|
|
self.bf = open(bfile, 'r+b') |
|
|
|
|
self.sections = [] # type: T.List[SectionHeader] |
|
|
|
|
self.dynamic = [] # type: T.List[DynamicEntry] |
|
|
|
|
try: |
|
|
|
|
(self.ptrsize, self.is_le) = self.detect_elf_type() |
|
|
|
|
super().__init__(self.ptrsize, self.is_le) |
|
|
|
@ -130,18 +133,18 @@ class Elf(DataSizes): |
|
|
|
|
self.bf.close() |
|
|
|
|
raise |
|
|
|
|
|
|
|
|
|
def __enter__(self): |
|
|
|
|
def __enter__(self) -> 'Elf': |
|
|
|
|
return self |
|
|
|
|
|
|
|
|
|
def __del__(self): |
|
|
|
|
def __del__(self) -> None: |
|
|
|
|
if self.bf: |
|
|
|
|
self.bf.close() |
|
|
|
|
|
|
|
|
|
def __exit__(self, exc_type, exc_value, traceback): |
|
|
|
|
def __exit__(self, exc_type: T.Any, exc_value: T.Any, traceback: T.Any) -> None: |
|
|
|
|
self.bf.close() |
|
|
|
|
self.bf = None |
|
|
|
|
|
|
|
|
|
def detect_elf_type(self): |
|
|
|
|
def detect_elf_type(self) -> T.Tuple[int, bool]: |
|
|
|
|
data = self.bf.read(6) |
|
|
|
|
if data[1:4] != b'ELF': |
|
|
|
|
# This script gets called to non-elf targets too |
|
|
|
@ -163,7 +166,7 @@ class Elf(DataSizes): |
|
|
|
|
sys.exit('File "%s" has unknown ELF endianness.' % self.bfile) |
|
|
|
|
return ptrsize, is_le |
|
|
|
|
|
|
|
|
|
def parse_header(self): |
|
|
|
|
def parse_header(self) -> None: |
|
|
|
|
self.bf.seek(0) |
|
|
|
|
self.e_ident = struct.unpack('16s', self.bf.read(16))[0] |
|
|
|
|
self.e_type = struct.unpack(self.Half, self.bf.read(self.HalfSize))[0] |
|
|
|
@ -180,13 +183,12 @@ class Elf(DataSizes): |
|
|
|
|
self.e_shnum = struct.unpack(self.Half, self.bf.read(self.HalfSize))[0] |
|
|
|
|
self.e_shstrndx = struct.unpack(self.Half, self.bf.read(self.HalfSize))[0] |
|
|
|
|
|
|
|
|
|
def parse_sections(self): |
|
|
|
|
def parse_sections(self) -> None: |
|
|
|
|
self.bf.seek(self.e_shoff) |
|
|
|
|
self.sections = [] |
|
|
|
|
for _ in range(self.e_shnum): |
|
|
|
|
self.sections.append(SectionHeader(self.bf, self.ptrsize, self.is_le)) |
|
|
|
|
|
|
|
|
|
def read_str(self): |
|
|
|
|
def read_str(self) -> bytes: |
|
|
|
|
arr = [] |
|
|
|
|
x = self.bf.read(1) |
|
|
|
|
while x != b'\0': |
|
|
|
@ -196,17 +198,17 @@ class Elf(DataSizes): |
|
|
|
|
raise RuntimeError('Tried to read past the end of the file') |
|
|
|
|
return b''.join(arr) |
|
|
|
|
|
|
|
|
|
def find_section(self, target_name): |
|
|
|
|
def find_section(self, target_name: bytes) -> T.Optional[SectionHeader]: |
|
|
|
|
section_names = self.sections[self.e_shstrndx] |
|
|
|
|
for i in self.sections: |
|
|
|
|
self.bf.seek(section_names.sh_offset + i.sh_name) |
|
|
|
|
name = self.read_str() |
|
|
|
|
if name == target_name: |
|
|
|
|
return i |
|
|
|
|
return None |
|
|
|
|
|
|
|
|
|
def parse_dynamic(self): |
|
|
|
|
def parse_dynamic(self) -> None: |
|
|
|
|
sec = self.find_section(b'.dynamic') |
|
|
|
|
self.dynamic = [] |
|
|
|
|
if sec is None: |
|
|
|
|
return |
|
|
|
|
self.bf.seek(sec.sh_offset) |
|
|
|
@ -216,14 +218,14 @@ class Elf(DataSizes): |
|
|
|
|
if e.d_tag == 0: |
|
|
|
|
break |
|
|
|
|
|
|
|
|
|
def print_section_names(self): |
|
|
|
|
def print_section_names(self) -> None: |
|
|
|
|
section_names = self.sections[self.e_shstrndx] |
|
|
|
|
for i in self.sections: |
|
|
|
|
self.bf.seek(section_names.sh_offset + i.sh_name) |
|
|
|
|
name = self.read_str() |
|
|
|
|
print(name.decode()) |
|
|
|
|
|
|
|
|
|
def print_soname(self): |
|
|
|
|
def print_soname(self) -> None: |
|
|
|
|
soname = None |
|
|
|
|
strtab = None |
|
|
|
|
for i in self.dynamic: |
|
|
|
@ -237,14 +239,16 @@ class Elf(DataSizes): |
|
|
|
|
self.bf.seek(strtab.val + soname.val) |
|
|
|
|
print(self.read_str()) |
|
|
|
|
|
|
|
|
|
def get_entry_offset(self, entrynum): |
|
|
|
|
def get_entry_offset(self, entrynum: int) -> T.Optional[int]: |
|
|
|
|
sec = self.find_section(b'.dynstr') |
|
|
|
|
for i in self.dynamic: |
|
|
|
|
if i.d_tag == entrynum: |
|
|
|
|
return sec.sh_offset + i.val |
|
|
|
|
res = sec.sh_offset + i.val |
|
|
|
|
assert isinstance(res, int) |
|
|
|
|
return res |
|
|
|
|
return None |
|
|
|
|
|
|
|
|
|
def print_rpath(self): |
|
|
|
|
def print_rpath(self) -> None: |
|
|
|
|
offset = self.get_entry_offset(DT_RPATH) |
|
|
|
|
if offset is None: |
|
|
|
|
print("This file does not have an rpath.") |
|
|
|
@ -252,7 +256,7 @@ class Elf(DataSizes): |
|
|
|
|
self.bf.seek(offset) |
|
|
|
|
print(self.read_str()) |
|
|
|
|
|
|
|
|
|
def print_runpath(self): |
|
|
|
|
def print_runpath(self) -> None: |
|
|
|
|
offset = self.get_entry_offset(DT_RUNPATH) |
|
|
|
|
if offset is None: |
|
|
|
|
print("This file does not have a runpath.") |
|
|
|
@ -260,7 +264,7 @@ class Elf(DataSizes): |
|
|
|
|
self.bf.seek(offset) |
|
|
|
|
print(self.read_str()) |
|
|
|
|
|
|
|
|
|
def print_deps(self): |
|
|
|
|
def print_deps(self) -> None: |
|
|
|
|
sec = self.find_section(b'.dynstr') |
|
|
|
|
deps = [] |
|
|
|
|
for i in self.dynamic: |
|
|
|
@ -272,7 +276,7 @@ class Elf(DataSizes): |
|
|
|
|
name = self.read_str() |
|
|
|
|
print(name) |
|
|
|
|
|
|
|
|
|
def fix_deps(self, prefix): |
|
|
|
|
def fix_deps(self, prefix: bytes) -> None: |
|
|
|
|
sec = self.find_section(b'.dynstr') |
|
|
|
|
deps = [] |
|
|
|
|
for i in self.dynamic: |
|
|
|
@ -290,15 +294,13 @@ class Elf(DataSizes): |
|
|
|
|
self.bf.seek(offset) |
|
|
|
|
self.bf.write(newname) |
|
|
|
|
|
|
|
|
|
def fix_rpath(self, rpath_dirs_to_remove, new_rpath): |
|
|
|
|
def fix_rpath(self, rpath_dirs_to_remove: T.List[bytes], new_rpath: bytes) -> None: |
|
|
|
|
# The path to search for can be either rpath or runpath. |
|
|
|
|
# Fix both of them to be sure. |
|
|
|
|
self.fix_rpathtype_entry(rpath_dirs_to_remove, new_rpath, DT_RPATH) |
|
|
|
|
self.fix_rpathtype_entry(rpath_dirs_to_remove, new_rpath, DT_RUNPATH) |
|
|
|
|
|
|
|
|
|
def fix_rpathtype_entry(self, rpath_dirs_to_remove, new_rpath, entrynum): |
|
|
|
|
if isinstance(new_rpath, str): |
|
|
|
|
new_rpath = new_rpath.encode('utf8') |
|
|
|
|
def fix_rpathtype_entry(self, rpath_dirs_to_remove: T.List[bytes], new_rpath: bytes, entrynum: int) -> None: |
|
|
|
|
rp_off = self.get_entry_offset(entrynum) |
|
|
|
|
if rp_off is None: |
|
|
|
|
if self.verbose: |
|
|
|
@ -326,7 +328,7 @@ class Elf(DataSizes): |
|
|
|
|
new_rpath = b':'.join(new_rpaths) |
|
|
|
|
|
|
|
|
|
if len(old_rpath) < len(new_rpath): |
|
|
|
|
msg = "New rpath must not be longer than the old one.\n Old: {}\n New: {}".format(old_rpath, new_rpath) |
|
|
|
|
msg = "New rpath must not be longer than the old one.\n Old: {!r}\n New: {!r}".format(old_rpath, new_rpath) |
|
|
|
|
sys.exit(msg) |
|
|
|
|
# The linker does read-only string deduplication. If there is a |
|
|
|
|
# string that shares a suffix with the rpath, they might get |
|
|
|
@ -343,7 +345,7 @@ class Elf(DataSizes): |
|
|
|
|
self.bf.write(new_rpath) |
|
|
|
|
self.bf.write(b'\0') |
|
|
|
|
|
|
|
|
|
def remove_rpath_entry(self, entrynum): |
|
|
|
|
def remove_rpath_entry(self, entrynum: int) -> None: |
|
|
|
|
sec = self.find_section(b'.dynamic') |
|
|
|
|
if sec is None: |
|
|
|
|
return None |
|
|
|
@ -363,7 +365,7 @@ class Elf(DataSizes): |
|
|
|
|
entry.write(self.bf) |
|
|
|
|
return None |
|
|
|
|
|
|
|
|
|
def fix_elf(fname, rpath_dirs_to_remove, new_rpath, verbose=True): |
|
|
|
|
def fix_elf(fname: str, rpath_dirs_to_remove: T.List[bytes], new_rpath: T.Optional[bytes], verbose: bool = True) -> None: |
|
|
|
|
with Elf(fname, verbose) as e: |
|
|
|
|
if new_rpath is None: |
|
|
|
|
e.print_rpath() |
|
|
|
@ -371,7 +373,7 @@ def fix_elf(fname, rpath_dirs_to_remove, new_rpath, verbose=True): |
|
|
|
|
else: |
|
|
|
|
e.fix_rpath(rpath_dirs_to_remove, new_rpath) |
|
|
|
|
|
|
|
|
|
def get_darwin_rpaths_to_remove(fname): |
|
|
|
|
def get_darwin_rpaths_to_remove(fname: str) -> T.List[str]: |
|
|
|
|
out = subprocess.check_output(['otool', '-l', fname], |
|
|
|
|
universal_newlines=True, |
|
|
|
|
stderr=subprocess.DEVNULL) |
|
|
|
@ -389,7 +391,7 @@ def get_darwin_rpaths_to_remove(fname): |
|
|
|
|
result.append(rp) |
|
|
|
|
return result |
|
|
|
|
|
|
|
|
|
def fix_darwin(fname, new_rpath, final_path, install_name_mappings): |
|
|
|
|
def fix_darwin(fname: str, new_rpath: str, final_path: str, install_name_mappings: T.Dict[str, str]) -> None: |
|
|
|
|
try: |
|
|
|
|
rpaths = get_darwin_rpaths_to_remove(fname) |
|
|
|
|
except subprocess.CalledProcessError: |
|
|
|
@ -439,7 +441,7 @@ def fix_darwin(fname, new_rpath, final_path, install_name_mappings): |
|
|
|
|
except Exception as err: |
|
|
|
|
raise SystemExit(err) |
|
|
|
|
|
|
|
|
|
def fix_jar(fname): |
|
|
|
|
def fix_jar(fname: str) -> None: |
|
|
|
|
subprocess.check_call(['jar', 'xfv', fname, 'META-INF/MANIFEST.MF']) |
|
|
|
|
with open('META-INF/MANIFEST.MF', 'r+') as f: |
|
|
|
|
lines = f.readlines() |
|
|
|
@ -450,7 +452,7 @@ def fix_jar(fname): |
|
|
|
|
f.truncate() |
|
|
|
|
subprocess.check_call(['jar', 'ufm', fname, 'META-INF/MANIFEST.MF']) |
|
|
|
|
|
|
|
|
|
def fix_rpath(fname, rpath_dirs_to_remove, new_rpath, final_path, install_name_mappings, verbose=True): |
|
|
|
|
def fix_rpath(fname: str, rpath_dirs_to_remove: T.List[bytes], new_rpath: T.Union[str, bytes], final_path: str, install_name_mappings: T.Dict[str, str], verbose: bool = True) -> None: |
|
|
|
|
global INSTALL_NAME_TOOL |
|
|
|
|
# Static libraries, import libraries, debug information, headers, etc |
|
|
|
|
# never have rpaths |
|
|
|
@ -461,6 +463,8 @@ def fix_rpath(fname, rpath_dirs_to_remove, new_rpath, final_path, install_name_m |
|
|
|
|
if fname.endswith('.jar'): |
|
|
|
|
fix_jar(fname) |
|
|
|
|
return |
|
|
|
|
if isinstance(new_rpath, str): |
|
|
|
|
new_rpath = new_rpath.encode('utf8') |
|
|
|
|
fix_elf(fname, rpath_dirs_to_remove, new_rpath, verbose) |
|
|
|
|
return |
|
|
|
|
except SystemExit as e: |
|
|
|
@ -473,6 +477,8 @@ def fix_rpath(fname, rpath_dirs_to_remove, new_rpath, final_path, install_name_m |
|
|
|
|
# (upto 30ms), which is significant with --only-changed. For details, see: |
|
|
|
|
# https://github.com/mesonbuild/meson/pull/6612#discussion_r378581401 |
|
|
|
|
if INSTALL_NAME_TOOL is False: |
|
|
|
|
INSTALL_NAME_TOOL = shutil.which('install_name_tool') |
|
|
|
|
INSTALL_NAME_TOOL = bool(shutil.which('install_name_tool')) |
|
|
|
|
if INSTALL_NAME_TOOL: |
|
|
|
|
if isinstance(new_rpath, bytes): |
|
|
|
|
new_rpath = new_rpath.decode('utf8') |
|
|
|
|
fix_darwin(fname, new_rpath, final_path, install_name_mappings) |
|
|
|
|