#!/usr/bin/env python3 # Copyright 2018 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Logic for reading .a files. Copied from //tools/binary_size/libsupersize/ar.py""" import os def _ResolveThinObjectPath(archive_path, subpath): """Given the .a path and .o subpath, returns the .o path.""" # |subpath| is path complete under Gold, and incomplete under LLD. Check its # prefix to test completeness, and if not, use |archive_path| to supply the # required prefix. if subpath.startswith('obj/'): return subpath # .o subpaths in thin archives are relative to the directory of the .a. parent_path = os.path.dirname(archive_path) return os.path.normpath(os.path.join(parent_path, subpath)) def _IterThinPaths(path): """Given the .a path, yields all nested .o paths.""" # File format reference: # https://github.com/pathscale/binutils/blob/master/gold/archive.cc with open(path, 'rb') as f: header = f.read(8) is_thin = header == b'!\n' if not is_thin and header != b'!\n': raise Exception('Invalid .a: ' + path) if not is_thin: return def read_payload(size): ret = f.read(size) # Entries are 2-byte aligned. if size & 1 != 0: f.read(1) return ret while True: entry = f.read(60) if not entry: return entry_name = entry[:16].rstrip() entry_size = int(entry[48:58].rstrip()) if entry_name in (b'', b'/', b'//', b'/SYM64/'): payload = read_payload(entry_size) # Metadata sections we don't care about. if entry_name == b'//': name_list = payload continue if entry_name[0:1] == b'/': # Name is specified as location in name table. # E.g.: /123 name_offset = int(entry_name[1:]) # String table enties are delimited by \n (e.g. "browser.o/\n"). end_idx = name_list.index(b'\n', name_offset) entry_name = name_list[name_offset:end_idx] else: # Name specified inline with spaces for padding (e.g. "browser.o/ "). entry_name = entry_name.rstrip() yield entry_name.rstrip(b'/').decode('ascii') def ExpandThinArchives(paths): """Expands all thin archives found in |paths| into .o paths. Args: paths: List of paths relative to |output_directory|. output_directory: Output directory. Returns: * A new list of paths with all archives replaced by .o paths. """ expanded_paths = [] for path in paths: if not path.endswith('.a'): expanded_paths.append(path) continue with open(path, 'rb') as f: header = f.read(8) is_thin = header == b'!\n' if is_thin: for subpath in _IterThinPaths(path): expanded_paths.append(_ResolveThinObjectPath(path, subpath)) elif header != b'!\n': raise Exception('Invalid .a: ' + path) return expanded_paths