diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py index 91b0807e1..8161aa4a4 100644 --- a/mesonbuild/backend/backends.py +++ b/mesonbuild/backend/backends.py @@ -144,6 +144,7 @@ class TargetInstallData: # TODO: install_mode should just always be a FileMode object install_mode: T.Optional['FileMode'] subproject: str + system: str optional: bool = False tag: T.Optional[str] = None can_strip: bool = False @@ -1749,7 +1750,8 @@ class Backend: first_outdir_name, should_strip, mappings, t.rpath_dirs_to_remove, t.install_rpath, install_mode, t.subproject, - tag=tag, can_strip=can_strip) + tag=tag, can_strip=can_strip, + system=self.environment.machines[t.for_machine].system) d.targets.append(i) for alias, to, tag in t.get_aliases(): @@ -1774,7 +1776,8 @@ class Backend: implib_install_dir, first_outdir_name, False, {}, set(), '', install_mode, t.subproject, optional=isinstance(t, build.SharedModule), - tag='devel') + tag='devel', + system=self.environment.machines[t.for_machine].system) d.targets.append(i) if not should_strip and t.get_debug_filename(): @@ -1783,7 +1786,8 @@ class Backend: first_outdir_name, False, {}, set(), '', install_mode, t.subproject, - optional=True, tag='devel') + optional=True, tag='devel', + system=self.environment.machines[t.for_machine].system) d.targets.append(i) # Install secondary outputs. Only used for Vala right now. if num_outdirs > 1: @@ -1794,7 +1798,8 @@ class Backend: f = os.path.join(self.get_target_dir(t), output) i = TargetInstallData(f, outdir, outdir_name, False, {}, set(), None, install_mode, t.subproject, - tag=tag) + tag=tag, + system=self.environment.machines[t.for_machine].system) d.targets.append(i) elif isinstance(t, build.CustomTarget): # If only one install_dir is specified, assume that all @@ -1815,7 +1820,8 @@ class Backend: i = TargetInstallData(f, first_outdir, first_outdir_name, False, {}, set(), None, install_mode, t.subproject, optional=not t.build_by_default, - tag=tag) + tag=tag, + system=self.environment.machines[t.for_machine].system) d.targets.append(i) else: for output, outdir, outdir_name, tag in zip(t.get_outputs(), outdirs, install_dir_names, t.install_tag): @@ -1827,7 +1833,8 @@ class Backend: i = TargetInstallData(f, outdir, outdir_name, False, {}, set(), None, install_mode, t.subproject, optional=not t.build_by_default, - tag=tag) + tag=tag, + system=self.environment.machines[t.for_machine].system) d.targets.append(i) def generate_custom_install_script(self, d: InstallData) -> None: diff --git a/mesonbuild/linkers/linkers.py b/mesonbuild/linkers/linkers.py index cb8956adf..f3e9314e2 100644 --- a/mesonbuild/linkers/linkers.py +++ b/mesonbuild/linkers/linkers.py @@ -1698,14 +1698,24 @@ class AIXDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): def build_rpath_args(self, build_dir: str, from_dir: str, target: BuildTarget, extra_paths: T.Optional[T.List[str]] = None ) -> T.Tuple[T.List[str], T.Set[bytes]]: + # Extract rpath information from target + rpath_paths = target.determine_rpath_dirs() + install_rpath = target.install_rpath + build_rpath = target.build_rpath + all_paths: mesonlib.OrderedSet[str] = mesonlib.OrderedSet() + rpath_dirs_to_remove: T.Set[bytes] = set() # install_rpath first, followed by other paths, and the system path last - if target.install_rpath != '': - all_paths.add(target.install_rpath) - if target.build_rpath != '': - all_paths.add(target.build_rpath) - for p in target.determine_rpath_dirs(): - all_paths.add(os.path.join(build_dir, p)) + if install_rpath != '': + all_paths.add(install_rpath) + if build_rpath != '': + all_paths.add(build_rpath) + for p in build_rpath.split(':'): + rpath_dirs_to_remove.add(p.encode('utf8')) + for p in rpath_paths: + path = os.path.join(build_dir, p) + all_paths.add(path) + rpath_dirs_to_remove.add(path.encode('utf8')) # We should consider allowing the $LIBPATH environment variable # to override sys_path. sys_path = self.environment.get_compiler_system_lib_dirs(self.for_machine) @@ -1720,7 +1730,7 @@ class AIXDynamicLinker(PosixDynamicLinkerMixin, DynamicLinker): all_paths.add(p) if extra_paths: all_paths.update(extra_paths) - return (self._apply_prefix('-blibpath:' + ':'.join(all_paths)), set()) + return (self._apply_prefix('-blibpath:' + ':'.join(all_paths)), rpath_dirs_to_remove) def thread_flags(self) -> T.List[str]: return ['-pthread'] diff --git a/mesonbuild/minstall.py b/mesonbuild/minstall.py index 664c8fa12..39d966a2d 100644 --- a/mesonbuild/minstall.py +++ b/mesonbuild/minstall.py @@ -786,7 +786,7 @@ class Installer: self.did_install_something = True try: self.fix_rpath(outname, t.rpath_dirs_to_remove, install_rpath, final_path, - install_name_mappings, verbose=False) + install_name_mappings, t.system, verbose=False) except SystemExit as e: if isinstance(e.code, int) and e.code == 0: pass diff --git a/mesonbuild/scripts/depfixer.py b/mesonbuild/scripts/depfixer.py index 8adc35c4a..dd74c89ca 100644 --- a/mesonbuild/scripts/depfixer.py +++ b/mesonbuild/scripts/depfixer.py @@ -25,6 +25,277 @@ DT_MIPS_RLD_MAP_REL = 1879048245 # Global cache for tools INSTALL_NAME_TOOL = False +# -------------------AIX------------------------- +# Reference documents: +# https://www.ibm.com/docs/en/aix/7.1?topic=formats-xcoff-object-file-format +# https://www.ibm.com/docs/en/aix/7.2?topic=formats-ar-file-format-big + +# Size of magic number +XCOFF_MAGIC_SIZE = 2 + + +def align(value: int, align_bytes: int) -> int: + # Function used by AIX to align value to align_bytes + align_bytes = 1 << align_bytes + return ((value + align_bytes - 1) // align_bytes) * align_bytes + + +class XcoffFixedLengthHeader: + # AIX archive fixed length header (struct fl_hdr) + FL_HDR_SIZE = 128 + FL_HDR_FORMAT = '8s 20s 20s 20s 20s 20s 20s' + + def __init__(self, file: T.BinaryIO) -> None: + header_data = file.read(self.FL_HDR_SIZE) + fl_magic, fl_memoff, fl_gstoff, fl_gst64off, fl_fstmoff, fl_lstmoff, fl_freeoff = struct.unpack( + self.FL_HDR_FORMAT, header_data + ) + self.fl_magic = fl_magic + self.fl_fstmoff = int(fl_fstmoff) + + +class XcoffArchiveHeader: + # AIX archive header (struct ar_hdr) + AR_HDR_SIZE = 114 + AR_HDR_FORMAT = '20s 20s 20s 12s 12s 12s 12s 4s 2s' + + def __init__(self, file: T.BinaryIO) -> None: + header_data = file.read(self.AR_HDR_SIZE) + ar_size, ar_nxtmem, ar_prvmem, ar_date, ar_uid, ar_gid, ar_mode, ar_namlen, _ar_name = struct.unpack( + self.AR_HDR_FORMAT, header_data + ) + ar_namlen_int = int(ar_namlen.strip()) + # The Magic number always starts at the even byte boundary + self.ar_namlen = ar_namlen_int if ar_namlen_int % 2 == 0 else ar_namlen_int + 1 + # Seek past the archive member name + file.seek(self.ar_namlen, os.SEEK_CUR) + + +class XcoffCompositeFileHeader: + # XCOFF composite file header + CFH_HDR_SIZE = 24 + CFH_HDR_SIZE_32 = 20 + CFH_HDR_FORMAT = '2s 2s 4s 8s 2s 2s 4s' + CFH_HDR_FORMAT_32 = '2s 2s 4s 4s 4s 2s 2s' + + def __init__(self, file: T.BinaryIO, magic: int) -> None: + if magic == 0x01DF: + cfh_header_data = file.read(self.CFH_HDR_SIZE_32) + f_magic, f_nscns, f_timdat, f_symptr, f_nsyms, f_opthdr, f_flags = struct.unpack( + self.CFH_HDR_FORMAT_32, cfh_header_data + ) + else: + cfh_header_data = file.read(self.CFH_HDR_SIZE) + f_magic, f_nscns, f_timdat, f_symptr, f_opthdr, f_flags, f_nsyms = struct.unpack( + self.CFH_HDR_FORMAT, cfh_header_data + ) + self.f_flags = int.from_bytes(f_flags, byteorder='big') + self.f_opthdr = int.from_bytes(f_opthdr, byteorder='big') + + +class XcoffAuxiliaryHeader: + # XCOFF auxiliary header + AUX_HDR_SIZE = 120 + AUX_HDR_SIZE_32 = 72 + AUX_HDR_FORMAT = '2s 2s 4s 8s 8s 8s 2s 2s 2s 2s 2s 2s 2s 2s 2s 1s 1s 1s 1s 1s 1s 8s 8s 8s 8s 8s 8s 2s 2s 2s 10s' + AUX_HDR_FORMAT_32 = '2s 2s 4s 4s 4s 4s 4s 4s 4s 2s 2s 2s 2s 2s 2s 2s 2s 2s 1s 1s 4s 4s 4s 1s 1s 1s 1s 2s 2s' + + def __init__(self, file: T.BinaryIO, magic: int, opthdr_size: int) -> None: + if magic == 0x01DF: + if opthdr_size < self.AUX_HDR_SIZE_32: + sys.exit(f'Error: Auxiliary header size {opthdr_size} is too small for 32-bit XCOFF') + aux_header_data = file.read(self.AUX_HDR_SIZE_32) + if len(aux_header_data) < self.AUX_HDR_SIZE_32: + sys.exit(f'Error: Could not read auxiliary header, got {len(aux_header_data)} bytes, expected {self.AUX_HDR_SIZE_32}') + (o_mflags, o_vstamp, o_tsize, o_dsize, o_bsize, o_entry, o_text_start, o_data_start, o_toc, + o_snentry, o_sntext, o_sndata, o_sntoc, o_snloader, o_snbss, o_algntext, o_algndata, o_modtype, + o_cpuflag, o_cputype, o_maxstack, o_maxdata, o_debugger, o_textpsize, o_datapsize, o_stackpsize, + o_flags, o_sntdata, o_sntbss) = struct.unpack(self.AUX_HDR_FORMAT_32, aux_header_data) + else: + if opthdr_size < self.AUX_HDR_SIZE: + sys.exit(f'Error: Auxiliary header size {opthdr_size} is too small for 64-bit XCOFF') + aux_header_data = file.read(self.AUX_HDR_SIZE) + if len(aux_header_data) < self.AUX_HDR_SIZE: + sys.exit(f'Error: Could not read auxiliary header, got {len(aux_header_data)} bytes, expected {self.AUX_HDR_SIZE}') + (o_mflags, o_vstamp, o_debugger, o_text_start, o_data_start, o_toc, o_snentry, o_sntext, o_sndata, + o_sntoc, o_snloader, o_snbss, o_algntext, o_algndata, o_modtype, o_cpuflag, o_cputype, o_textpsize, + o_datapsize, o_stackpsize, o_flags, o_tsize, o_dsize, o_bsize, o_entry, o_maxstack, o_maxdata, + o_sntdata, o_sntbss, o_x64flags, dummy) = struct.unpack(self.AUX_HDR_FORMAT, aux_header_data) + + self.o_algntext = int.from_bytes(o_algntext, byteorder='big') + self.o_algndata = int.from_bytes(o_algndata, byteorder='big') + self.o_snloader = int.from_bytes(o_snloader, byteorder='big') + + +class XcoffSectionHeader: + # XCOFF section header + SCN_HDR_SIZE = 72 + SCN_HDR_SIZE_32 = 40 + SCN_HDR_FORMAT = '8s 8s 8s 8s 8s 8s 8s 4s 4s 4s 4s' + SCN_HDR_FORMAT_32 = '8s 4s 4s 4s 4s 4s 4s 2s 2s 2s 2s' + + def __init__(self, file: T.BinaryIO, magic: int) -> None: + if magic == 0x01DF: + scn_header_data = file.read(self.SCN_HDR_SIZE_32) + if len(scn_header_data) < self.SCN_HDR_SIZE_32: + raise EOFError('End of archive member reached') + (s_name, s_paddr, s_vaddr, s_size, s_scnptr, s_relptr, s_lnnoptr, s_nreloc, s_nlnno, s_flags, + dummy) = struct.unpack(self.SCN_HDR_FORMAT_32, scn_header_data) + else: + scn_header_data = file.read(self.SCN_HDR_SIZE) + if len(scn_header_data) < self.SCN_HDR_SIZE: + raise EOFError('End of archive member reached') + (s_name, s_paddr, s_vaddr, s_size, s_scnptr, s_relptr, s_lnnoptr, s_nreloc, s_nlnno, s_flags, + dummy) = struct.unpack(self.SCN_HDR_FORMAT, scn_header_data) + self.s_scnptr = int.from_bytes(s_scnptr, byteorder='big') + + +class XcoffLoaderHeader: + # XCOFF loader header + LDR_HDR_SIZE = 56 + LDR_HDR_SIZE_32 = 32 + LDR_HDR_FORMAT = '4s 4s 4s 4s 4s 4s 8s 8s 8s 8s' + LDR_HDR_FORMAT_32 = '4s 4s 4s 4s 4s 4s 4s 4s' + + def __init__(self, file: T.BinaryIO, magic: int) -> None: + if magic == 0x01DF: + ldr_header_data = file.read(self.LDR_HDR_SIZE_32) + (l_version, l_nsyms, l_nreloc, l_istlen, l_nimpid, l_impoff, l_stlen, + l_stoff) = struct.unpack(self.LDR_HDR_FORMAT_32, ldr_header_data) + else: + ldr_header_data = file.read(self.LDR_HDR_SIZE) + (l_version, l_nsyms, l_nreloc, l_istlen, l_nimpid, l_stlen, l_impoff, l_stoff, l_symoff, + l_rldoff) = struct.unpack(self.LDR_HDR_FORMAT, ldr_header_data) + self.l_impoff = int.from_bytes(l_impoff, byteorder='big') + self.l_istlen = int.from_bytes(l_istlen, byteorder='big') + + +def traverse_xcoff(file: T.BinaryIO, rpath_dirs_to_remove: T.Set[bytes], new_rpath: T.Optional[bytes], verbose: bool = True) -> None: + """Traverse XCOFF file or archive and modify Libpath entries. + + Handles both standalone XCOFF shared objects (.so) and XCOFF archives (.a). + Archives are detected by magic number and processed member by member. + """ + def dummy_print(*args: object) -> None: + pass + log_msg: T.Callable[..., None] + log_msg = print if verbose else dummy_print # type: ignore[assignment] + + # Detect if this is an archive by checking for magic number + magic_check = file.read(8) + file.seek(0) + + # Read archive headers if this is an archive + ar_header: T.Optional[XcoffArchiveHeader] = None + if magic_check == b'\n': + fl_header = XcoffFixedLengthHeader(file) + file.seek(fl_header.fl_fstmoff) + ar_header = XcoffArchiveHeader(file) + + composite_header_pos = file.tell() + # Read XCOFF magic number + magic_data = file.read(XCOFF_MAGIC_SIZE) + if len(magic_data) != XCOFF_MAGIC_SIZE: + sys.exit(0) + magic_number = struct.unpack('>H', magic_data)[0] # Big-endian 16-bit integer + if magic_number == 0x01DF: + log_msg(' Changing Libpath for a 32-bit Shared Object') + elif magic_number == 0x01F7: + log_msg(' Changing Libpath for a 64-bit Shared Object') + else: + log_msg(' Not a shared object') + sys.exit(0) + # Reposition to start of XCOFF headers + file.seek(composite_header_pos) + cfh_header = XcoffCompositeFileHeader(file, magic_number) + if not cfh_header.f_flags & 0x2000: + log_msg('Did not change rpath since not a shared library/archive') + return + # Read auxiliary header + aux_header = XcoffAuxiliaryHeader(file, magic_number, cfh_header.f_opthdr) + + # Calculate the bytes_to_align + bytes_to_align = max(aux_header.o_algntext, aux_header.o_algndata) + + # Seek to section header and read it + if magic_number == 0x01DF: + file.seek((aux_header.o_snloader - 1) * XcoffSectionHeader.SCN_HDR_SIZE_32, os.SEEK_CUR) + else: + file.seek((aux_header.o_snloader - 1) * XcoffSectionHeader.SCN_HDR_SIZE, os.SEEK_CUR) + try: + scn_header = XcoffSectionHeader(file, magic_number) + except EOFError: + return # End of archive member, nothing to fix + + header_len = 0 + if ar_header: + header_len = XcoffFixedLengthHeader.FL_HDR_SIZE + XcoffArchiveHeader.AR_HDR_SIZE + ar_header.ar_namlen + scnptrFromArchiveStart = scn_header.s_scnptr + align(header_len, bytes_to_align) + else: + scnptrFromArchiveStart = scn_header.s_scnptr + + file.seek(scnptrFromArchiveStart) + + # Read loader header + ldr_header = XcoffLoaderHeader(file, magic_number) + + if ar_header: + scnptrFromArchiveStartPlusOff = scn_header.s_scnptr + ldr_header.l_impoff + align(header_len, bytes_to_align) + else: + scnptrFromArchiveStartPlusOff = scn_header.s_scnptr + ldr_header.l_impoff + file.seek(scnptrFromArchiveStartPlusOff) + + # Read and update libpath + libpath = file.read(ldr_header.l_istlen) + build_libpath = libpath.split(b'\x00')[0] + # Build the new rpath by merging new_rpath with existing paths (excluding removed ones) + new_rpaths: OrderedSet[bytes] = OrderedSet() + if new_rpath: + new_rpaths.update(new_rpath.split(b':')) + + new_rpaths.update( + path + for path in build_libpath.split(b':') + if path and path not in rpath_dirs_to_remove + ) + + install_rpath = b':'.join(new_rpaths) + + # If the length of the build_rpath length is < install_rpath length then we do not have space to write + # the same hence exit. Otherwise, we can and the length has to be the same as build_rpath. + # If the install_rpath length is less than build_rpath length then pad the difference + if len(install_rpath) > len(build_libpath): + sys.exit('Error: install_rpath is bigger than build_rpath') + # Pad with colon bytes to match build_libpath length + install_rpath = install_rpath + (b':' * (len(build_libpath) - len(install_rpath))) + file.seek(scnptrFromArchiveStartPlusOff) + file.write(install_rpath) + log_msg(f'Successfully changed libpath from {build_libpath!r} to {install_rpath!r}') + + +def fix_aix(fname: str, rpath_dirs_to_remove: T.Set[bytes], new_rpath: T.Optional[bytes], verbose: bool = True) -> None: + """Writes Libpath to an xcoff shared object. + + In AIX, shared modules are .so files and shared libraries are in .a archives. + Follows the same calling convention as fix_elf: + - Errors: sys.exit('error message') + - Wrong file type: sys.exit(0) + - Success: return normally + """ + if new_rpath is None: + return + + try: + with open(fname, 'r+b') as file: + traverse_xcoff(file, rpath_dirs_to_remove, new_rpath, verbose) + except FileNotFoundError: + sys.exit(f'Error: File not found: {fname}') + except Exception as e: + sys.exit(f'Unexpected error processing {fname}: {e}') + + +# ----------------ELF------------------ + class DataSizes: def __init__(self, ptrsize: int, is_le: bool) -> None: if is_le: @@ -379,7 +650,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.decode('utf-8'), new_rpath.decode('utf-8')) + msg = 'New rpath must not be longer than the old one.\n Old: {}\n New: {}'.format(old_rpath.decode('utf-8'), new_rpath.decode('utf-8')) 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 @@ -534,20 +805,27 @@ def fix_jar(fname: str) -> None: # than the beginning, but the spec doesn't forbid that. subprocess.check_call(['jar', 'ufM', fname, 'META-INF/MANIFEST.MF']) -def fix_rpath(fname: str, rpath_dirs_to_remove: T.Set[bytes], new_rpath: T.Union[str, bytes], final_path: str, install_name_mappings: T.Dict[str, str], verbose: bool = True) -> None: +def fix_rpath(fname: str, rpath_dirs_to_remove: T.Set[bytes], new_rpath: T.Union[str, bytes], final_path: str, install_name_mappings: T.Dict[str, str], system: str, verbose: bool = True) -> None: global INSTALL_NAME_TOOL # pylint: disable=global-statement # Static libraries, import libraries, debug information, headers, etc # never have rpaths # DLLs and EXE currently do not need runtime path fixing - if fname.endswith(('.a', '.lib', '.pdb', '.h', '.hpp', '.dll', '.exe')): + + if fname.endswith(('.lib', '.pdb', '.h', '.hpp', '.dll', '.exe')): return + # Shared libraries are .a files on AIX + if fname.endswith('.a') and system != 'aix': + return + if isinstance(new_rpath, str): + new_rpath = new_rpath.encode('utf8') try: 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) + if system == 'aix': + fix_aix(fname, rpath_dirs_to_remove, new_rpath, verbose) + else: + fix_elf(fname, rpath_dirs_to_remove, new_rpath, verbose) return except SystemExit as e: if isinstance(e.code, int) and e.code == 0: