1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
|
#!/usr/bin/env python3
# Copyright 2018 The Chromium Authors
# 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."""
import argparse
import logging
import os
def IsThinArchive(path):
"""Returns whether the given .a is a thin archive."""
with open(path, 'rb') as f:
return f.read(8) == b'!<thin>\n'
def CreateThinObjectPath(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 IterArchiveChunks(path):
"""For each .o embedded in the given .a file, yields (foo.o, foo_contents).
Args:
path: Path to .a file.
Returns:
A (str, bytes) tuple, specifying (.o file entry, None or payload data).
"""
# 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'!<thin>\n'
if not is_thin and header != b'!<arch>\n':
raise Exception('Invalid .a: ' + path)
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()
# Although entry_size exists for thin archives, no data exists in the .a.
entry_data = None if is_thin else read_payload(entry_size)
yield entry_name.rstrip(b'/').decode('ascii'), entry_data
def ExpandThinArchives(paths, output_directory):
"""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 thin archives replaced by .o paths.
* A set of all .a paths that were thin archives.
"""
expanded_paths = []
thin_paths = set()
num_archives = 0
for path in paths:
if not path.endswith('.a'):
expanded_paths.append(path)
continue
num_archives += 1
abs_path = os.path.join(output_directory, path)
if not os.path.exists(abs_path):
logging.warning('Linker input not found: %s', path)
continue
if not IsThinArchive(abs_path):
expanded_paths.append(path)
continue
thin_paths.add(path)
# Thin archive.
for subpath, _ in IterArchiveChunks(abs_path):
expanded_paths.append(CreateThinObjectPath(path, subpath))
logging.info('%d of %d .a files were thin archives',
len(thin_paths), num_archives)
return expanded_paths, thin_paths
def main():
parser = argparse.ArgumentParser()
parser.add_argument('ar_path')
parser.add_argument('--expand-thin', action='store_true')
parser.add_argument('--output-directory', default='.')
args = parser.parse_args()
if args.expand_thin:
expanded = ExpandThinArchives([args.ar_path], args.output_directory)[0]
print('\n'.join(expanded))
else:
for name, payload in IterArchiveChunks(args.ar_path):
print('{}: size={}'.format(name, len(payload) if payload else '<thin>'))
if __name__ == '__main__':
main()
|