| #!/usr/bin/env python3 | |
| # | |
| # Copyright (c) 2012 Intel Corporation | |
| # | |
| # SPDX-License-Identifier: GPL-2.0-only | |
| # | |
| # DESCRIPTION | |
| # This script is called by the SDK installer script. It replaces the dynamic | |
| # loader path in all binaries and also fixes the SYSDIR paths/lengths and the | |
| # location of ld.so.cache in the dynamic loader binary | |
| # | |
| # AUTHORS | |
| # Laurentiu Palcu <laurentiu.palcu@intel.com> | |
| # | |
| import struct | |
| import sys | |
| import stat | |
| import os | |
| import re | |
| import errno | |
| if sys.version < '3': | |
| def b(x): | |
| return x | |
| else: | |
| def b(x): | |
| return x.encode(sys.getfilesystemencoding()) | |
| old_prefix = re.compile(b("##DEFAULT_INSTALL_DIR##")) | |
| def get_arch(): | |
| global endian_prefix | |
| f.seek(0) | |
| e_ident =f.read(16) | |
| ei_mag0,ei_mag1_3,ei_class,ei_data,ei_version = struct.unpack("<B3sBBB9x", e_ident) | |
| # ei_data = 1 for little-endian & 0 for big-endian | |
| if ei_data == 1: | |
| endian_prefix = '<' | |
| else: | |
| endian_prefix = '>' | |
| if (ei_mag0 != 0x7f and ei_mag1_3 != "ELF") or ei_class == 0: | |
| return 0 | |
| if ei_class == 1: | |
| return 32 | |
| elif ei_class == 2: | |
| return 64 | |
| def get_dl_arch(dl_path): | |
| try: | |
| with open(dl_path, "r+b") as f: | |
| e_ident =f.read(16) | |
| except IOError: | |
| exctype, ioex = sys.exc_info()[:2] | |
| if ioex.errno == errno.ETXTBSY: | |
| print("Could not open %s. File used by another process.\nPlease "\ | |
| "make sure you exit all processes that might use any SDK "\ | |
| "binaries." % e) | |
| else: | |
| print("Could not open %s: %s(%d)" % (e, ioex.strerror, ioex.errno)) | |
| sys.exit(-1) | |
| ei_mag0,ei_mag1_3,ei_class,ei_data,ei_version = struct.unpack("<B3sBBB9x", e_ident) | |
| if (ei_mag0 != 0x7f and ei_mag1_3 != "ELF") or ei_class == 0: | |
| print("ERROR: unknow %s" % dl_path) | |
| sys.exit(-1) | |
| if ei_class == 1: | |
| arch = 32 | |
| elif ei_class == 2: | |
| arch = 64 | |
| return arch | |
| def parse_elf_header(): | |
| global e_type, e_machine, e_version, e_entry, e_phoff, e_shoff, e_flags,\ | |
| e_ehsize, e_phentsize, e_phnum, e_shentsize, e_shnum, e_shstrndx | |
| f.seek(0) | |
| elf_header = f.read(64) | |
| if arch == 32: | |
| # 32bit | |
| hdr_fmt = endian_prefix + "HHILLLIHHHHHH" | |
| hdr_size = 52 | |
| else: | |
| # 64bit | |
| hdr_fmt = endian_prefix + "HHIQQQIHHHHHH" | |
| hdr_size = 64 | |
| e_type, e_machine, e_version, e_entry, e_phoff, e_shoff, e_flags,\ | |
| e_ehsize, e_phentsize, e_phnum, e_shentsize, e_shnum, e_shstrndx =\ | |
| struct.unpack(hdr_fmt, elf_header[16:hdr_size]) | |
| def change_interpreter(elf_file_name): | |
| if arch == 32: | |
| ph_fmt = endian_prefix + "IIIIIIII" | |
| else: | |
| ph_fmt = endian_prefix + "IIQQQQQQ" | |
| """ look for PT_INTERP section """ | |
| for i in range(0,e_phnum): | |
| f.seek(e_phoff + i * e_phentsize) | |
| ph_hdr = f.read(e_phentsize) | |
| if arch == 32: | |
| # 32bit | |
| p_type, p_offset, p_vaddr, p_paddr, p_filesz,\ | |
| p_memsz, p_flags, p_align = struct.unpack(ph_fmt, ph_hdr) | |
| else: | |
| # 64bit | |
| p_type, p_flags, p_offset, p_vaddr, p_paddr, \ | |
| p_filesz, p_memsz, p_align = struct.unpack(ph_fmt, ph_hdr) | |
| """ change interpreter """ | |
| if p_type == 3: | |
| # PT_INTERP section | |
| f.seek(p_offset) | |
| # External SDKs with mixed pre-compiled binaries should not get | |
| # relocated so look for some variant of /lib | |
| fname = f.read(11) | |
| if fname.startswith(b("/lib/")) or fname.startswith(b("/lib64/")) or \ | |
| fname.startswith(b("/lib32/")) or fname.startswith(b("/usr/lib32/")) or \ | |
| fname.startswith(b("/usr/lib32/")) or fname.startswith(b("/usr/lib64/")): | |
| break | |
| if p_filesz == 0: | |
| break | |
| if (len(new_dl_path) >= p_filesz): | |
| print("ERROR: could not relocate %s, interp size = %i and %i is needed." \ | |
| % (elf_file_name, p_memsz, len(new_dl_path) + 1)) | |
| return False | |
| dl_path = new_dl_path + b("\0") * (p_filesz - len(new_dl_path)) | |
| f.seek(p_offset) | |
| f.write(dl_path) | |
| break | |
| return True | |
| def change_dl_sysdirs(elf_file_name): | |
| if arch == 32: | |
| sh_fmt = endian_prefix + "IIIIIIIIII" | |
| else: | |
| sh_fmt = endian_prefix + "IIQQQQIIQQ" | |
| """ read section string table """ | |
| f.seek(e_shoff + e_shstrndx * e_shentsize) | |
| sh_hdr = f.read(e_shentsize) | |
| if arch == 32: | |
| sh_offset, sh_size = struct.unpack(endian_prefix + "16xII16x", sh_hdr) | |
| else: | |
| sh_offset, sh_size = struct.unpack(endian_prefix + "24xQQ24x", sh_hdr) | |
| f.seek(sh_offset) | |
| sh_strtab = f.read(sh_size) | |
| sysdirs = sysdirs_len = "" | |
| """ change ld.so.cache path and default libs path for dynamic loader """ | |
| for i in range(0,e_shnum): | |
| f.seek(e_shoff + i * e_shentsize) | |
| sh_hdr = f.read(e_shentsize) | |
| sh_name, sh_type, sh_flags, sh_addr, sh_offset, sh_size, sh_link,\ | |
| sh_info, sh_addralign, sh_entsize = struct.unpack(sh_fmt, sh_hdr) | |
| name = sh_strtab[sh_name:sh_strtab.find(b("\0"), sh_name)] | |
| """ look only into SHT_PROGBITS sections """ | |
| if sh_type == 1: | |
| f.seek(sh_offset) | |
| """ default library paths cannot be changed on the fly because """ | |
| """ the string lengths have to be changed too. """ | |
| if name == b(".sysdirs"): | |
| sysdirs = f.read(sh_size) | |
| sysdirs_off = sh_offset | |
| sysdirs_sect_size = sh_size | |
| elif name == b(".sysdirslen"): | |
| sysdirslen = f.read(sh_size) | |
| sysdirslen_off = sh_offset | |
| elif name == b(".ldsocache"): | |
| ldsocache_path = f.read(sh_size) | |
| new_ldsocache_path = old_prefix.sub(new_prefix, ldsocache_path) | |
| new_ldsocache_path = new_ldsocache_path.rstrip(b("\0")) | |
| if (len(new_ldsocache_path) >= sh_size): | |
| print("ERROR: could not relocate %s, .ldsocache section size = %i and %i is needed." \ | |
| % (elf_file_name, sh_size, len(new_ldsocache_path))) | |
| sys.exit(-1) | |
| # pad with zeros | |
| new_ldsocache_path += b("\0") * (sh_size - len(new_ldsocache_path)) | |
| # write it back | |
| f.seek(sh_offset) | |
| f.write(new_ldsocache_path) | |
| elif name == b(".gccrelocprefix"): | |
| offset = 0 | |
| while (offset + 4096) <= sh_size: | |
| path = f.read(4096) | |
| new_path = old_prefix.sub(new_prefix, path) | |
| new_path = new_path.rstrip(b("\0")) | |
| if (len(new_path) >= 4096): | |
| print("ERROR: could not relocate %s, max path size = 4096 and %i is needed." \ | |
| % (elf_file_name, len(new_path))) | |
| sys.exit(-1) | |
| # pad with zeros | |
| new_path += b("\0") * (4096 - len(new_path)) | |
| #print "Changing %s to %s at %s" % (str(path), str(new_path), str(offset)) | |
| # write it back | |
| f.seek(sh_offset + offset) | |
| f.write(new_path) | |
| offset = offset + 4096 | |
| if sysdirs != "" and sysdirslen != "": | |
| paths = sysdirs.split(b("\0")) | |
| sysdirs = b("") | |
| sysdirslen = b("") | |
| for path in paths: | |
| """ exit the loop when we encounter first empty string """ | |
| if path == b(""): | |
| break | |
| new_path = old_prefix.sub(new_prefix, path) | |
| sysdirs += new_path + b("\0") | |
| if arch == 32: | |
| sysdirslen += struct.pack("<L", len(new_path)) | |
| else: | |
| sysdirslen += struct.pack("<Q", len(new_path)) | |
| """ pad with zeros """ | |
| sysdirs += b("\0") * (sysdirs_sect_size - len(sysdirs)) | |
| """ write the sections back """ | |
| f.seek(sysdirs_off) | |
| f.write(sysdirs) | |
| f.seek(sysdirslen_off) | |
| f.write(sysdirslen) | |
| # MAIN | |
| if len(sys.argv) < 4: | |
| sys.exit(-1) | |
| # In python > 3, strings may also contain Unicode characters. So, convert | |
| # them to bytes | |
| if sys.version_info < (3,): | |
| new_prefix = sys.argv[1] | |
| new_dl_path = sys.argv[2] | |
| else: | |
| new_prefix = sys.argv[1].encode() | |
| new_dl_path = sys.argv[2].encode() | |
| executables_list = sys.argv[3:] | |
| dl_arch = get_dl_arch(new_dl_path) | |
| errors = False | |
| for e in executables_list: | |
| perms = os.stat(e)[stat.ST_MODE] | |
| if os.access(e, os.W_OK|os.R_OK): | |
| perms = None | |
| else: | |
| os.chmod(e, perms|stat.S_IRWXU) | |
| try: | |
| f = open(e, "r+b") | |
| except IOError: | |
| exctype, ioex = sys.exc_info()[:2] | |
| if ioex.errno == errno.ETXTBSY: | |
| print("Could not open %s. File used by another process.\nPlease "\ | |
| "make sure you exit all processes that might use any SDK "\ | |
| "binaries." % e) | |
| else: | |
| print("Could not open %s: %s(%d)" % (e, ioex.strerror, ioex.errno)) | |
| sys.exit(-1) | |
| # Save old size and do a size check at the end. Just a safety measure. | |
| old_size = os.path.getsize(e) | |
| if old_size >= 64: | |
| arch = get_arch() | |
| if arch and arch == dl_arch: | |
| parse_elf_header() | |
| if not change_interpreter(e): | |
| errors = True | |
| change_dl_sysdirs(e) | |
| """ change permissions back """ | |
| if perms: | |
| os.chmod(e, perms) | |
| f.close() | |
| if old_size != os.path.getsize(e): | |
| print("New file size for %s is different. Looks like a relocation error!", e) | |
| sys.exit(-1) | |
| if errors: | |
| print("Relocation of one or more executables failed.") | |
| sys.exit(-1) |