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