def find_debug_file(filename): # In the Zircon makefile build, the file to be installed is called # foo.strip and the unstripped file is called foo. In the new Zircon # GN build, the file to be installed is called foo and the unstripped # file is called foo.debug. In the Fuchsia GN build, the file to be # installed is called foo and the unstripped file has the same name in # the exe.unstripped or lib.unstripped subdirectory. if filename.endswith('.strip'): debugfile = filename[:-6] elif os.path.exists(filename + '.debug'): debugfile = filename + '.debug' elif (lib_dir := next( (dir for dir in toolchain_lib_dirs if os.path.realpath( filename).startswith(os.path.realpath(dir) + os.sep)), None)): build_id_dir = os.path.join(lib_dir, 'debug', '.build-id') if not os.path.exists(build_id_dir): return None info = elfinfo.get_elf_info(filename) debugfile = os.path.join(build_id_dir, info.build_id[:2], info.build_id[2:] + '.debug') if not os.path.exists(debugfile): return None # Pass filename as fallback so we don't fallback to the build-id entry name. return binary_info(debugfile, fallback_soname=os.path.basename(filename))
def __init__(self, path, size=None, libs=None): self.path = path self.size = size if size else os.path.getsize(path) if libs != None: self.libs = set(libs) else: info = elfinfo.get_elf_info(path) self.libs = info.needed if info else set()
def find_debug_file(filename): # In the Zircon makefile build, the file to be installed is called # foo.strip and the unstripped file is called foo. In the new Zircon # GN build, the file to be installed is called foo and the unstripped # file is called foo.debug. In the Fuchsia GN build, the file to be # installed is called foo and the unstripped file has the same name in # the exe.unstripped or lib.unstripped subdirectory. if filename.endswith('.strip'): debugfile = filename[:-6] elif os.path.exists(filename + '.debug'): debugfile = filename + '.debug' else: # Check for toolchain runtime libraries, which are stored under # {toolchain}/lib/.../libfoo.so, and whose unstripped file will # be under {toolchain}/lib/debug/.build-id/xx/xxxxxx.debug. lib_dir = None for dir in toolchain_lib_dirs: if os.path.realpath(filename).startswith( os.path.realpath(dir) + os.sep): lib_dir = dir break if lib_dir: build_id_dir = os.path.join(lib_dir, 'debug', '.build-id') if not os.path.exists(build_id_dir): return None info = elfinfo.get_elf_info(filename) debugfile = os.path.join(build_id_dir, info.build_id[:2], info.build_id[2:] + '.debug') if not os.path.exists(debugfile): return None # Pass filename as fallback so we don't fallback to the build-id entry name. return binary_info(debugfile, fallback_soname=os.path.basename(filename)) else: dir, file = os.path.split(filename) if file.endswith('.so') or '.so.' in file: subdir = 'lib.unstripped' else: subdir = 'exe.unstripped' debugfile = os.path.join(dir, subdir, file) while not os.path.exists(debugfile): # For dir/foo/bar, if dir/foo/exe.unstripped/bar # didn't exist, try dir/exe.unstripped/foo/bar. parent, dir = os.path.split(dir) if not parent or not dir: return None dir, file = parent, os.path.join(dir, file) debugfile = os.path.join(dir, subdir, file) if not os.path.exists(debugfile): debugfile = os.path.join(subdir, filename) if not os.path.exists(debugfile): return None debug = binary_info(debugfile) assert debug, ("Debug file '%s' for '%s' is invalid" % (debugfile, filename)) examined.add(debugfile) return debug
def _StripBinary(dry_run, bin_path): """Creates a stripped copy of the executable at |bin_path| and returns the path to the stripped copy.""" strip_path = bin_path + '.bootfs_stripped' if dry_run: print "Strip", bin_path, " to ", strip_path else: info = elfinfo.get_elf_info(bin_path) info.strip(strip_path) return strip_path
def _WriteBuildIdsTxt(binary_paths, ids_txt_path): """Writes an index text file that maps build IDs to the paths of unstripped binaries.""" with open(ids_txt_path, 'w') as ids_file: for binary_path in binary_paths: # Paths to the unstripped executables listed in "ids.txt" are specified # as relative paths to that file. relative_path = os.path.relpath( os.path.abspath(binary_path), os.path.dirname(os.path.abspath(ids_txt_path))) info = elfinfo.get_elf_info(_GetStrippedPath(binary_path)) ids_file.write(info.build_id + ' ' + relative_path + '\n')
def find_debug_file(filename): # In the Zircon makefile build, the file to be installed is called # foo.strip and the unstripped file is called foo. In the new Zircon # GN build, the file to be installed is called foo and the unstripped # file is called foo.debug. In the Fuchsia GN build, the file to be # installed is called foo and the unstripped file has the same name in # the exe.unstripped or lib.unstripped subdirectory. if filename.endswith('.strip'): debugfile = filename[:-6] elif os.path.exists(filename + '.debug'): debugfile = filename + '.debug' elif os.path.realpath(filename).startswith( os.path.realpath(clang_lib_dir)): build_id_dir = os.path.join(clang_lib_dir, 'debug', '.build-id') if not os.path.exists(build_id_dir): return None info = elfinfo.get_elf_info(filename) debugfile = os.path.join(build_id_dir, info.build_id[:2], info.build_id[2:] + '.debug') if not os.path.exists(debugfile): return None return binary_info(debugfile) else: dir, file = os.path.split(filename) if file.endswith('.so') or '.so.' in file: subdir = 'lib.unstripped' else: subdir = 'exe.unstripped' debugfile = os.path.join(dir, subdir, file) while not os.path.exists(debugfile): # For dir/foo/bar, if dir/foo/exe.unstripped/bar # didn't exist, try dir/exe.unstripped/foo/bar. parent, dir = os.path.split(dir) if not parent or not dir: return None dir, file = parent, os.path.join(dir, file) debugfile = os.path.join(dir, subdir, file) if not os.path.exists(debugfile): debugfile = os.path.join(subdir, filename) if not os.path.exists(debugfile): return None debug = binary_info(debugfile) assert debug, ("Debug file '%s' for '%s' is invalid" % (debugfile, filename)) examined.add(debugfile) return debug
def binary_info(filename, fallback_soname=None): info = elfinfo.get_elf_info(filename, [ZIRCON_DRIVER_IDENT]) if info and not info.soname: return info.with_soname(fallback_soname or os.path.basename(filename)) return info
def get_sdk_debug_path(binary): build_id = elfinfo.get_elf_info(binary).build_id return '.build-id/' + build_id[:2] + '/' + build_id[2:] + '.debug'
def binary_info(filename): return elfinfo.get_elf_info(filename, [ZIRCON_DRIVER_IDENT])
def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( '--source-dir', default='.', help='Root directory for source paths. Default is current directory.') parser.add_argument('--check-stripped', action='store_true', help='Verify that ELF binaries are stripped.') parser.add_argument( '--check-unstripped-files', action='store_true', help='Verify that each ELF binary has its own unstripped file available.' + \ 'this requires --toolchain-lib-dir to find toolchain-provided runtime debug files') parser.add_argument( '--toolchain-lib-dir', default=[], action='append', metavar='DIR', help= 'Path to toolchain-provided lib directory. Can be used multiple times.' ) group = parser.add_mutually_exclusive_group() group.add_argument('--fini-manifest', help='Input FINI manifest.') group.add_argument('--partial-manifest', help='Input partial distribution manifest.') parser.add_argument('--stamp', required=True, help='Output stamp file.') parser.add_argument('--depfile', help='Output Ninja depfile.') args = parser.parse_args() depfile_items = set() # Read the input manifest into a {target: source} dictionary. manifest_entries = {} elf_runtime_dir_map = None if args.fini_manifest: input_manifest = args.fini_manifest with open(args.fini_manifest) as f: for line in f: line = line.rstrip() target, _, source = line.partition('=') assert source is not None, ('Invalid manifest line: [%s]' % line) source_file = os.path.join(args.source_dir, source) # Duplicate entries happen in some manifests, but they will have the # same content, so assert otherwise. if target in manifest_entries: assert manifest_entries[target] == source_file, ( 'Duplicate entries for target "%s": %s vs %s' % (target, source_file, manifest_entries[target])) assert os.path.exists(source_file), ( 'Missing source file for manifest line: %s' % line) manifest_entries[target] = source_file else: input = args.input_manifest with open(args.partial_manifest) as f: partial_entries = json.load(f) result = distribution_manifest.expand_partial_manifest_items( partial_entries, depfile_items) if result.errors: print('ERRORS FOUND IN %s:\n%s' % (args.partial_manifest, '\n'.join(result.errors)), file=sys.stderr) manifest_entries = {e.destination: e.source for e in result.entries} elf_runtime_dir_map = result.elf_runtime_map # Filter the input manifest entries to keep only the ELF ones # that are not under data/, lib/firmware/ or meta/ elf_entries = {} for target, source_file in manifest_entries.items(): if target.startswith('data/') or target.startswith( 'lib/firmware/') or target.startswith('meta/'): continue depfile_items.add(source_file) info = elfinfo.get_elf_info(source_file) if info is not None: elf_entries[target] = info # errors contains a list of error strings corresponding to issues found in # the input manifest. # # extras contains non-error strings that are useful to dump when an error # occurs, and give more information about what was found in the manifest. # These should only be printed in case of errors, or ignored otherwise. errors = [] extras = [] # The set of all libraries visited when checking dependencies. visited_libraries = set() # Verify that libzircon.so or libc.so do not appear inside the package. for target, info in elf_entries.items(): filename = os.path.basename(target) if filename in ('libzircon.so', 'libc.so'): errors.append('%s should not be in this package (source=%s)!' % (target, info.filename)) # First verify all executables, since we can find their library directory # from their PT_INTERP value, then check their dependencies recursively. for target, info in elf_entries.items(): if elf_runtime_dir_map is not None: lib_dir = elf_runtime_dir_map.get(target) if not lib_dir: continue else: interp = info.interp if interp is None: continue interp_name = os.path.basename(interp) if interp_name != 'ld.so.1': errors.append( '%s has invalid or unsupported PT_INTERP value: %s' % (target, interp)) continue lib_dir = os.path.join('lib', os.path.dirname(interp)) extras.append('Binary %s has interp %s, lib_dir %s' % (target, interp, lib_dir)) binary_errors, binary_deps = verify_elf_dependencies( target, lib_dir, info.needed, elf_entries) errors += binary_errors visited_libraries.update(binary_deps) # Check that all binaries are stripped if necessary. if args.check_stripped: for target, info in elf_entries.items(): if not info.stripped: errors.append('%s is not stripped: %s' % (target, info.filename)) # Verify that all ELF files have their unstripped file available. if args.check_unstripped_files: for target, source_file in manifest_entries.items(): # Prebuilts are allowed to have missing debug files. # See: fxbug.dev/89174 if "/prebuilt/" in source_file: continue if target in elf_entries: unstripped = find_unstripped_file(source_file, depfile_items, args.toolchain_lib_dir) if unstripped is None: errors.append('No unstripped file found for ' + source_file) if errors: print('ERRORS FOUND IN %s:\n%s' % (input_manifest, '\n'.join(errors)), file=sys.stderr) if extras: print('\n'.join(extras), file=sys.stderr) return 1 if args.depfile: with open(args.depfile, 'w') as f: f.write('%s: %s\n' % (input_manifest, ' '.join(sorted(depfile_items)))) # Write the stamp file on success. with open(args.stamp, 'w') as f: f.write('') return 0
def find_unstripped_file(filename: str, depfile_items: Set[str], toolchain_lib_dirs: List[str] = []) -> Optional[str]: """Find the unstripped version of a given ELF binary. Args: filename: input file path in build directory. toolchain_lib_dirs: a list of toolchain-specific lib directories which will be used to look for debug/.build-id/xx/xxxxxxx.debug files corresponding to the input files's build-id value. depfile_items: the set of examined files to be updated if needed. Returns Path to the debug file, if it exists, or None. """ if os.path.exists(filename + '.debug'): # Zircon-specific toolchains currently write the unstripped files # for .../foo as .../foo.debug. debugfile = filename + '.debug' else: # Check for toolchain runtime libraries, which are stored under # {toolchain}/lib/.../libfoo.so, and whose unstripped file will # be under {toolchain}/lib/debug/.build-id/xx/xxxxxx.debug. lib_dir = None for dir in toolchain_lib_dirs: if os.path.realpath(filename).startswith( os.path.realpath(dir) + os.sep): lib_dir = dir break if lib_dir: build_id_dir = os.path.join(lib_dir, 'debug', '.build-id') if not os.path.exists(build_id_dir): # Local rust builds don't contain debug in the path, so fallback # to a path without debug. build_id_dir = os.path.join(lib_dir, '.build-id') if not os.path.exists(build_id_dir): return None build_id = elfinfo.get_elf_info(filename).build_id # The build-id value is an hexadecimal string, used to locate the # debug file under debug/.build-id/XX/YYYYYY.debug where XX are its # first two chars, and YYYYYY is the rest (typically longer than # 6 chars). debugfile = os.path.join(build_id_dir, build_id[:2], build_id[2:] + '.debug') if not os.path.exists(debugfile): return None # Pass filename as fallback so we don't fallback to the build-id entry name. return debugfile else: # Otherwise, the Fuchsia build places unstripped files under # .../lib.unstripped/foo.so (for shared library or loadable module # .../foo.so) or .../exe.unstripped/bar (for executable .../bar). dir, file = os.path.split(filename) if file.endswith('.so') or '.so.' in file: subdir = 'lib.unstripped' else: subdir = 'exe.unstripped' debugfile = os.path.join(dir, subdir, file) while not os.path.exists(debugfile): # For dir/foo/bar, if dir/foo/exe.unstripped/bar # didn't exist, try dir/exe.unstripped/foo/bar. parent, dir = os.path.split(dir) if not parent or not dir: return None dir, file = parent, os.path.join(dir, file) debugfile = os.path.join(dir, subdir, file) if not os.path.exists(debugfile): debugfile = os.path.join(subdir, filename) if not os.path.exists(debugfile): return None depfile_items.add(debugfile) return debugfile