1# This Source Code Form is subject to the terms of the Mozilla Public 2# License, v. 2.0. If a copy of the MPL was not distributed with this 3# file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 5from __future__ import absolute_import, print_function, unicode_literals 6 7import os 8import struct 9import subprocess 10from io import BytesIO 11from mozpack.errors import errors 12 13MACHO_SIGNATURES = [ 14 0xFEEDFACE, # mach-o 32-bits big endian 15 0xCEFAEDFE, # mach-o 32-bits little endian 16 0xFEEDFACF, # mach-o 64-bits big endian 17 0xCFFAEDFE, # mach-o 64-bits little endian 18] 19 20FAT_SIGNATURE = 0xCAFEBABE # mach-o FAT binary 21 22ELF_SIGNATURE = 0x7F454C46 # Elf binary 23 24UNKNOWN = 0 25MACHO = 1 26ELF = 2 27 28 29def get_type(path_or_fileobj): 30 """ 31 Check the signature of the give file and returns what kind of executable 32 matches. 33 """ 34 if hasattr(path_or_fileobj, "peek"): 35 f = BytesIO(path_or_fileobj.peek(8)) 36 elif hasattr(path_or_fileobj, "read"): 37 f = path_or_fileobj 38 else: 39 f = open(path_or_fileobj, "rb") 40 signature = f.read(4) 41 if len(signature) < 4: 42 return UNKNOWN 43 signature = struct.unpack(">L", signature)[0] 44 if signature == ELF_SIGNATURE: 45 return ELF 46 if signature in MACHO_SIGNATURES: 47 return MACHO 48 if signature != FAT_SIGNATURE: 49 return UNKNOWN 50 # We have to sanity check the second four bytes, because Java class 51 # files use the same magic number as Mach-O fat binaries. 52 # This logic is adapted from file(1), which says that Mach-O uses 53 # these bytes to count the number of architectures within, while 54 # Java uses it for a version number. Conveniently, there are only 55 # 18 labelled Mach-O architectures, and Java's first released 56 # class format used the version 43.0. 57 num = f.read(4) 58 if len(num) < 4: 59 return UNKNOWN 60 num = struct.unpack(">L", num)[0] 61 if num < 20: 62 return MACHO 63 return UNKNOWN 64 65 66def is_executable(path): 67 """ 68 Return whether a given file path points to an executable or a library, 69 where an executable or library is identified by: 70 - the file extension on OS/2 and WINNT 71 - the file signature on OS/X and ELF systems (GNU/Linux, Android, BSD, 72 Solaris) 73 74 As this function is intended for use to choose between the ExecutableFile 75 and File classes in FileFinder, and choosing ExecutableFile only matters 76 on OS/2, OS/X, ELF and WINNT (in GCC build) systems, we don't bother 77 detecting other kind of executables. 78 """ 79 from buildconfig import substs 80 81 if not os.path.exists(path): 82 return False 83 84 if substs["OS_ARCH"] == "WINNT": 85 return path.lower().endswith((substs["DLL_SUFFIX"], substs["BIN_SUFFIX"])) 86 87 return get_type(path) != UNKNOWN 88 89 90def may_strip(path): 91 """ 92 Return whether strip() should be called 93 """ 94 from buildconfig import substs 95 96 # Bug 1658632: clang-11-based strip complains about d3dcompiler_47.dll. 97 # It's not clear why this happens, but as a quick fix just avoid stripping 98 # this DLL. It's not from our build anyway. 99 if "d3dcompiler" in path: 100 return False 101 return bool(substs.get("PKG_STRIP")) 102 103 104def strip(path): 105 """ 106 Execute the STRIP command with STRIP_FLAGS on the given path. 107 """ 108 from buildconfig import substs 109 110 strip = substs["STRIP"] 111 flags = substs.get("STRIP_FLAGS", []) 112 cmd = [strip] + flags + [path] 113 if subprocess.call(cmd) != 0: 114 errors.fatal("Error executing " + " ".join(cmd)) 115 116 117def may_elfhack(path): 118 """ 119 Return whether elfhack() should be called 120 """ 121 # elfhack only supports libraries. We should check the ELF header for 122 # the right flag, but checking the file extension works too. 123 from buildconfig import substs 124 125 return ( 126 "USE_ELF_HACK" in substs 127 and substs["USE_ELF_HACK"] 128 and path.endswith(substs["DLL_SUFFIX"]) 129 and "COMPILE_ENVIRONMENT" in substs 130 and substs["COMPILE_ENVIRONMENT"] 131 ) 132 133 134def elfhack(path): 135 """ 136 Execute the elfhack command on the given path. 137 """ 138 from buildconfig import topobjdir 139 140 cmd = [os.path.join(topobjdir, "build/unix/elfhack/elfhack"), path] 141 if subprocess.call(cmd) != 0: 142 errors.fatal("Error executing " + " ".join(cmd)) 143