gcc-runtime: remove libz.so from libgfortran.so if present (#47812)
This commit is contained in:
		@@ -7,7 +7,7 @@
 | 
				
			|||||||
import re
 | 
					import re
 | 
				
			||||||
import struct
 | 
					import struct
 | 
				
			||||||
from struct import calcsize, unpack, unpack_from
 | 
					from struct import calcsize, unpack, unpack_from
 | 
				
			||||||
from typing import BinaryIO, Dict, List, NamedTuple, Optional, Pattern, Tuple
 | 
					from typing import BinaryIO, Callable, Dict, List, NamedTuple, Optional, Pattern, Tuple
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class ElfHeader(NamedTuple):
 | 
					class ElfHeader(NamedTuple):
 | 
				
			||||||
@@ -476,6 +476,31 @@ def get_interpreter(path: str) -> Optional[str]:
 | 
				
			|||||||
        return None
 | 
					        return None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _delete_dynamic_array_entry(
 | 
				
			||||||
 | 
					    f: BinaryIO, elf: ElfFile, should_delete: Callable[[int, int], bool]
 | 
				
			||||||
 | 
					) -> None:
 | 
				
			||||||
 | 
					    f.seek(elf.pt_dynamic_p_offset)
 | 
				
			||||||
 | 
					    dynamic_array_fmt = elf.byte_order + ("qQ" if elf.is_64_bit else "lL")
 | 
				
			||||||
 | 
					    dynamic_array_size = calcsize(dynamic_array_fmt)
 | 
				
			||||||
 | 
					    new_offset = elf.pt_dynamic_p_offset  # points to the new dynamic array
 | 
				
			||||||
 | 
					    old_offset = elf.pt_dynamic_p_offset  # points to the current dynamic array
 | 
				
			||||||
 | 
					    for _ in range(elf.pt_dynamic_p_filesz // dynamic_array_size):
 | 
				
			||||||
 | 
					        data = read_exactly(f, dynamic_array_size, "Malformed dynamic array entry")
 | 
				
			||||||
 | 
					        tag, val = unpack(dynamic_array_fmt, data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if tag == ELF_CONSTANTS.DT_NULL or not should_delete(tag, val):
 | 
				
			||||||
 | 
					            if new_offset != old_offset:
 | 
				
			||||||
 | 
					                f.seek(new_offset)
 | 
				
			||||||
 | 
					                f.write(data)
 | 
				
			||||||
 | 
					                f.seek(old_offset + dynamic_array_size)
 | 
				
			||||||
 | 
					            new_offset += dynamic_array_size
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if tag == ELF_CONSTANTS.DT_NULL:
 | 
				
			||||||
 | 
					            break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        old_offset += dynamic_array_size
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def delete_rpath(path: str) -> None:
 | 
					def delete_rpath(path: str) -> None:
 | 
				
			||||||
    """Modifies a binary to remove the rpath. It zeros out the rpath string and also drops the
 | 
					    """Modifies a binary to remove the rpath. It zeros out the rpath string and also drops the
 | 
				
			||||||
    DT_R(UN)PATH entry from the dynamic section, so it doesn't show up in 'readelf -d file', nor
 | 
					    DT_R(UN)PATH entry from the dynamic section, so it doesn't show up in 'readelf -d file', nor
 | 
				
			||||||
@@ -492,29 +517,22 @@ def delete_rpath(path: str) -> None:
 | 
				
			|||||||
        f.seek(rpath_offset)
 | 
					        f.seek(rpath_offset)
 | 
				
			||||||
        f.write(new_rpath_string)
 | 
					        f.write(new_rpath_string)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Next update the dynamic array
 | 
					        # Delete DT_RPATH / DT_RUNPATH entries from the dynamic section
 | 
				
			||||||
        f.seek(elf.pt_dynamic_p_offset)
 | 
					        _delete_dynamic_array_entry(
 | 
				
			||||||
        dynamic_array_fmt = elf.byte_order + ("qQ" if elf.is_64_bit else "lL")
 | 
					            f, elf, lambda tag, _: tag == ELF_CONSTANTS.DT_RPATH or tag == ELF_CONSTANTS.DT_RUNPATH
 | 
				
			||||||
        dynamic_array_size = calcsize(dynamic_array_fmt)
 | 
					        )
 | 
				
			||||||
        new_offset = elf.pt_dynamic_p_offset  # points to the new dynamic array
 | 
					 | 
				
			||||||
        old_offset = elf.pt_dynamic_p_offset  # points to the current dynamic array
 | 
					 | 
				
			||||||
        for _ in range(elf.pt_dynamic_p_filesz // dynamic_array_size):
 | 
					 | 
				
			||||||
            data = read_exactly(f, dynamic_array_size, "Malformed dynamic array entry")
 | 
					 | 
				
			||||||
            tag, _ = unpack(dynamic_array_fmt, data)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # Overwrite any entry that is not DT_RPATH or DT_RUNPATH, including DT_NULL
 | 
					 | 
				
			||||||
            if tag != ELF_CONSTANTS.DT_RPATH and tag != ELF_CONSTANTS.DT_RUNPATH:
 | 
					 | 
				
			||||||
                if new_offset != old_offset:
 | 
					 | 
				
			||||||
                    f.seek(new_offset)
 | 
					 | 
				
			||||||
                    f.write(data)
 | 
					 | 
				
			||||||
                    f.seek(old_offset + dynamic_array_size)
 | 
					 | 
				
			||||||
                new_offset += dynamic_array_size
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            # End of the dynamic array
 | 
					def delete_needed_from_elf(f: BinaryIO, elf: ElfFile, needed: bytes) -> None:
 | 
				
			||||||
            if tag == ELF_CONSTANTS.DT_NULL:
 | 
					    """Delete a needed library from the dynamic section of an ELF file"""
 | 
				
			||||||
                break
 | 
					    if not elf.has_needed or needed not in elf.dt_needed_strs:
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            old_offset += dynamic_array_size
 | 
					    offset = elf.dt_needed_strtab_offsets[elf.dt_needed_strs.index(needed)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    _delete_dynamic_array_entry(
 | 
				
			||||||
 | 
					        f, elf, lambda tag, val: tag == ELF_CONSTANTS.DT_NEEDED and val == offset
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class CStringType:
 | 
					class CStringType:
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,6 +3,7 @@
 | 
				
			|||||||
#
 | 
					#
 | 
				
			||||||
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
 | 
					# SPDX-License-Identifier: (Apache-2.0 OR MIT)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import glob
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
import re
 | 
					import re
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -11,7 +12,7 @@
 | 
				
			|||||||
from llnl.util import tty
 | 
					from llnl.util import tty
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from spack.package import *
 | 
					from spack.package import *
 | 
				
			||||||
from spack.util.elf import parse_elf
 | 
					from spack.util.elf import delete_needed_from_elf, parse_elf
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class GccRuntime(Package):
 | 
					class GccRuntime(Package):
 | 
				
			||||||
@@ -72,6 +73,9 @@ def install(self, spec, prefix):
 | 
				
			|||||||
        for path, name in libraries:
 | 
					        for path, name in libraries:
 | 
				
			||||||
            install(path, os.path.join(prefix.lib, name))
 | 
					            install(path, os.path.join(prefix.lib, name))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if spec.platform in ("linux", "freebsd"):
 | 
				
			||||||
 | 
					            _drop_libgfortran_zlib(prefix.lib)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _get_libraries_macho(self):
 | 
					    def _get_libraries_macho(self):
 | 
				
			||||||
        """Same as _get_libraries_elf but for Mach-O binaries"""
 | 
					        """Same as _get_libraries_elf but for Mach-O binaries"""
 | 
				
			||||||
        cc = Executable(self.compiler.cc)
 | 
					        cc = Executable(self.compiler.cc)
 | 
				
			||||||
@@ -124,6 +128,22 @@ def headers(self):
 | 
				
			|||||||
        return HeaderList([])
 | 
					        return HeaderList([])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _drop_libgfortran_zlib(lib_dir: str) -> None:
 | 
				
			||||||
 | 
					    """Due to a bug in GCC's autotools setup (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=87182),
 | 
				
			||||||
 | 
					    libz sometimes appears as a redundant system dependency of libgfortran. Delete it."""
 | 
				
			||||||
 | 
					    libraries = glob.glob(os.path.join(lib_dir, "libgfortran*.so*"))
 | 
				
			||||||
 | 
					    if len(libraries) == 0:
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					    with open(libraries[0], "rb+") as f:
 | 
				
			||||||
 | 
					        elf = parse_elf(f, dynamic_section=True)
 | 
				
			||||||
 | 
					        if not elf.has_needed:
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        libz = next((x for x in elf.dt_needed_strs if x.startswith(b"libz.so")), None)
 | 
				
			||||||
 | 
					        if libz is None:
 | 
				
			||||||
 | 
					            return
 | 
				
			||||||
 | 
					        delete_needed_from_elf(f, elf, libz)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def get_elf_libraries(compiler, libraries):
 | 
					def get_elf_libraries(compiler, libraries):
 | 
				
			||||||
    """Get the GCC runtime libraries for ELF binaries"""
 | 
					    """Get the GCC runtime libraries for ELF binaries"""
 | 
				
			||||||
    cc = Executable(compiler.cc)
 | 
					    cc = Executable(compiler.cc)
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user