1# 2# SPDX-License-Identifier: GPL-2.0-only 3# 4 5import stat 6import mmap 7import subprocess 8 9def runstrip(arg): 10 # Function to strip a single file, called from split_and_strip_files below 11 # A working 'file' (one which works on the target architecture) 12 # 13 # The elftype is a bit pattern (explained in is_elf below) to tell 14 # us what type of file we're processing... 15 # 4 - executable 16 # 8 - shared library 17 # 16 - kernel module 18 19 if len(arg) == 3: 20 (file, elftype, strip) = arg 21 extra_strip_sections = '' 22 else: 23 (file, elftype, strip, extra_strip_sections) = arg 24 25 newmode = None 26 if not os.access(file, os.W_OK) or os.access(file, os.R_OK): 27 origmode = os.stat(file)[stat.ST_MODE] 28 newmode = origmode | stat.S_IWRITE | stat.S_IREAD 29 os.chmod(file, newmode) 30 31 stripcmd = [strip] 32 skip_strip = False 33 # kernel module 34 if elftype & 16: 35 if is_kernel_module_signed(file): 36 bb.debug(1, "Skip strip on signed module %s" % file) 37 skip_strip = True 38 else: 39 stripcmd.extend(["--strip-debug", "--remove-section=.comment", 40 "--remove-section=.note", "--preserve-dates"]) 41 # .so and shared library 42 elif ".so" in file and elftype & 8: 43 stripcmd.extend(["--remove-section=.comment", "--remove-section=.note", "--strip-unneeded"]) 44 # shared or executable: 45 elif elftype & 8 or elftype & 4: 46 stripcmd.extend(["--remove-section=.comment", "--remove-section=.note"]) 47 if extra_strip_sections != '': 48 for section in extra_strip_sections.split(): 49 stripcmd.extend(["--remove-section=" + section]) 50 51 stripcmd.append(file) 52 bb.debug(1, "runstrip: %s" % stripcmd) 53 54 if not skip_strip: 55 output = subprocess.check_output(stripcmd, stderr=subprocess.STDOUT) 56 57 if newmode: 58 os.chmod(file, origmode) 59 60# Detect .ko module by searching for "vermagic=" string 61def is_kernel_module(path): 62 with open(path) as f: 63 return mmap.mmap(f.fileno(), 0, prot=mmap.PROT_READ).find(b"vermagic=") >= 0 64 65# Detect if .ko module is signed 66def is_kernel_module_signed(path): 67 with open(path, "rb") as f: 68 f.seek(-28, 2) 69 module_tail = f.read() 70 return "Module signature appended" in "".join(chr(c) for c in bytearray(module_tail)) 71 72# Return type (bits): 73# 0 - not elf 74# 1 - ELF 75# 2 - stripped 76# 4 - executable 77# 8 - shared library 78# 16 - kernel module 79def is_elf(path): 80 exec_type = 0 81 result = subprocess.check_output(["file", "-b", path], stderr=subprocess.STDOUT).decode("utf-8") 82 83 if "ELF" in result: 84 exec_type |= 1 85 if "not stripped" not in result: 86 exec_type |= 2 87 if "executable" in result: 88 exec_type |= 4 89 if "shared" in result: 90 exec_type |= 8 91 if "relocatable" in result: 92 if path.endswith(".ko") and path.find("/lib/modules/") != -1 and is_kernel_module(path): 93 exec_type |= 16 94 return (path, exec_type) 95 96def is_static_lib(path): 97 if path.endswith('.a') and not os.path.islink(path): 98 with open(path, 'rb') as fh: 99 # The magic must include the first slash to avoid 100 # matching golang static libraries 101 magic = b'!<arch>\x0a/' 102 start = fh.read(len(magic)) 103 return start == magic 104 return False 105 106def strip_execs(pn, dstdir, strip_cmd, libdir, base_libdir, d, qa_already_stripped=False): 107 """ 108 Strip executable code (like executables, shared libraries) _in_place_ 109 - Based on sysroot_strip in staging.bbclass 110 :param dstdir: directory in which to strip files 111 :param strip_cmd: Strip command (usually ${STRIP}) 112 :param libdir: ${libdir} - strip .so files in this directory 113 :param base_libdir: ${base_libdir} - strip .so files in this directory 114 :param qa_already_stripped: Set to True if already-stripped' in ${INSANE_SKIP} 115 This is for proper logging and messages only. 116 """ 117 import stat, errno, oe.path, oe.utils 118 119 elffiles = {} 120 inodes = {} 121 libdir = os.path.abspath(dstdir + os.sep + libdir) 122 base_libdir = os.path.abspath(dstdir + os.sep + base_libdir) 123 exec_mask = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH 124 # 125 # First lets figure out all of the files we may have to process 126 # 127 checkelf = [] 128 inodecache = {} 129 for root, dirs, files in os.walk(dstdir): 130 for f in files: 131 file = os.path.join(root, f) 132 133 try: 134 ltarget = oe.path.realpath(file, dstdir, False) 135 s = os.lstat(ltarget) 136 except OSError as e: 137 (err, strerror) = e.args 138 if err != errno.ENOENT: 139 raise 140 # Skip broken symlinks 141 continue 142 if not s: 143 continue 144 # Check its an excutable 145 if s[stat.ST_MODE] & exec_mask \ 146 or ((file.startswith(libdir) or file.startswith(base_libdir)) and ".so" in f) \ 147 or file.endswith('.ko'): 148 # If it's a symlink, and points to an ELF file, we capture the readlink target 149 if os.path.islink(file): 150 continue 151 152 # It's a file (or hardlink), not a link 153 # ...but is it ELF, and is it already stripped? 154 checkelf.append(file) 155 inodecache[file] = s.st_ino 156 results = oe.utils.multiprocess_launch(is_elf, checkelf, d) 157 for (file, elf_file) in results: 158 #elf_file = is_elf(file) 159 if elf_file & 1: 160 if elf_file & 2: 161 if qa_already_stripped: 162 bb.note("Skipping file %s from %s for already-stripped QA test" % (file[len(dstdir):], pn)) 163 else: 164 bb.warn("File '%s' from %s was already stripped, this will prevent future debugging!" % (file[len(dstdir):], pn)) 165 continue 166 167 if inodecache[file] in inodes: 168 os.unlink(file) 169 os.link(inodes[inodecache[file]], file) 170 else: 171 # break hardlinks so that we do not strip the original. 172 inodes[inodecache[file]] = file 173 bb.utils.break_hardlinks(file) 174 elffiles[file] = elf_file 175 176 # 177 # Now strip them (in parallel) 178 # 179 sfiles = [] 180 for file in elffiles: 181 elf_file = int(elffiles[file]) 182 sfiles.append((file, elf_file, strip_cmd)) 183 184 oe.utils.multiprocess_launch(runstrip, sfiles, d) 185 186 187def file_translate(file): 188 ft = file.replace("@", "@at@") 189 ft = ft.replace(" ", "@space@") 190 ft = ft.replace("\t", "@tab@") 191 ft = ft.replace("[", "@openbrace@") 192 ft = ft.replace("]", "@closebrace@") 193 ft = ft.replace("_", "@underscore@") 194 return ft 195 196def filedeprunner(arg): 197 import re, subprocess, shlex 198 199 (pkg, pkgfiles, rpmdeps, pkgdest) = arg 200 provides = {} 201 requires = {} 202 203 file_re = re.compile(r'\s+\d+\s(.*)') 204 dep_re = re.compile(r'\s+(\S)\s+(.*)') 205 r = re.compile(r'[<>=]+\s+\S*') 206 207 def process_deps(pipe, pkg, pkgdest, provides, requires): 208 file = None 209 for line in pipe.split("\n"): 210 211 m = file_re.match(line) 212 if m: 213 file = m.group(1) 214 file = file.replace(pkgdest + "/" + pkg, "") 215 file = file_translate(file) 216 continue 217 218 m = dep_re.match(line) 219 if not m or not file: 220 continue 221 222 type, dep = m.groups() 223 224 if type == 'R': 225 i = requires 226 elif type == 'P': 227 i = provides 228 else: 229 continue 230 231 if dep.startswith("python("): 232 continue 233 234 # Ignore all perl(VMS::...) and perl(Mac::...) dependencies. These 235 # are typically used conditionally from the Perl code, but are 236 # generated as unconditional dependencies. 237 if dep.startswith('perl(VMS::') or dep.startswith('perl(Mac::'): 238 continue 239 240 # Ignore perl dependencies on .pl files. 241 if dep.startswith('perl(') and dep.endswith('.pl)'): 242 continue 243 244 # Remove perl versions and perl module versions since they typically 245 # do not make sense when used as package versions. 246 if dep.startswith('perl') and r.search(dep): 247 dep = dep.split()[0] 248 249 # Put parentheses around any version specifications. 250 dep = r.sub(r'(\g<0>)',dep) 251 252 if file not in i: 253 i[file] = [] 254 i[file].append(dep) 255 256 return provides, requires 257 258 output = subprocess.check_output(shlex.split(rpmdeps) + pkgfiles, stderr=subprocess.STDOUT).decode("utf-8") 259 provides, requires = process_deps(output, pkg, pkgdest, provides, requires) 260 261 return (pkg, provides, requires) 262 263 264def read_shlib_providers(d): 265 import re 266 267 shlib_provider = {} 268 shlibs_dirs = d.getVar('SHLIBSDIRS').split() 269 list_re = re.compile(r'^(.*)\.list$') 270 # Go from least to most specific since the last one found wins 271 for dir in reversed(shlibs_dirs): 272 bb.debug(2, "Reading shlib providers in %s" % (dir)) 273 if not os.path.exists(dir): 274 continue 275 for file in sorted(os.listdir(dir)): 276 m = list_re.match(file) 277 if m: 278 dep_pkg = m.group(1) 279 try: 280 fd = open(os.path.join(dir, file)) 281 except IOError: 282 # During a build unrelated shlib files may be deleted, so 283 # handle files disappearing between the listdirs and open. 284 continue 285 lines = fd.readlines() 286 fd.close() 287 for l in lines: 288 s = l.strip().split(":") 289 if s[0] not in shlib_provider: 290 shlib_provider[s[0]] = {} 291 shlib_provider[s[0]][s[1]] = (dep_pkg, s[2]) 292 return shlib_provider 293