| #!/usr/bin/env python2.7 |
| # |
| # Copyright 2020 The Fuchsia Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| """Populates a .build-id directory of unstripped binaries cataloged by a list |
| of ids.txt files.""" |
| |
| import argparse |
| import os |
| import shutil |
| import subprocess |
| import sys |
| |
| |
| def populate_build_id_dir(ids_txt_path, output_dir, build_id_dirs, filenames): |
| """ |
| Processes an ids.txt file, populating the output .build-id directory. |
| |
| Each unstripped ELF binary is placed in a hierarchy keyed by the GNU build |
| ID. Each binary resides in a directory whose name is the first two characters |
| of the build ID, with the binary file itself named after the remaining |
| characters of the build ID. So, a binary file with the build ID "deadbeef" |
| would be located at the path 'output_dir/de/adbeef.debug'. |
| |
| See documentation at https://fedoraproject.org/wiki/Releases/FeatureBuildId. |
| |
| Args: |
| ids_txt_path (str): Path to ids.txt file to process. |
| output_dir (str): Path to output .build-id directory. |
| build_id_dirs (list of str): Paths to directory containing symbols for |
| prebuilts. |
| filenames (set): Set to store binary filenames which were hardlinked to |
| the output .build-id directory. |
| """ |
| for line in open(ids_txt_path, 'r'): |
| build_id, binary_path = line.strip().split(' ') |
| output_filename = os.path.join(build_id[:2], build_id[2:] + '.debug') |
| |
| # Check first if we have the debug binary under any |build_id_dirs|. |
| symbol_source_path = _find_binary_file(output_filename, build_id_dirs) |
| |
| # Otherwise, check in the ids.txt directory itself, assuming |
| # relative paths in ids.txt. |
| if not symbol_source_path: |
| symbol_source_path = os.path.abspath( |
| os.path.join(os.path.dirname(ids_txt_path), binary_path)) |
| |
| # Don't check zero length files, they exist as placeholders for prebuilts. |
| if os.path.getsize(symbol_source_path) == 0: |
| continue |
| |
| # Exclude stripped binaries (indicated by their lack of symbol tables). |
| readelf_args = ['readelf', '-S', symbol_source_path] |
| readelf_output = subprocess.check_output(readelf_args) |
| if not '.symtab' in readelf_output: |
| continue |
| |
| output_path = os.path.join(output_dir, output_filename) |
| if not os.path.exists(os.path.dirname(output_path)): |
| os.makedirs(os.path.dirname(output_path)) |
| if not os.path.exists(output_path): |
| os.link(symbol_source_path, output_path) |
| filenames.add(output_path) |
| |
| |
| def _find_binary_file(binary_file, build_id_dirs): |
| """Look for the binary_file in the list of build_id_dirs.""" |
| for dir in build_id_dirs: |
| filepath = os.path.join(dir, binary_file) |
| if os.path.exists(filepath): |
| return filepath |
| return None |
| |
| |
| def main(args): |
| parser = argparse.ArgumentParser() |
| parser.add_argument( |
| 'ids_txt_paths', |
| type=str, |
| help='Path to a file, which is a newline-separated list ' |
| 'of paths to ids.txt files.') |
| parser.add_argument( |
| '--output_dir', |
| type=str, |
| required=True, |
| help='Path to output .build-id dir.') |
| parser.add_argument( |
| '--build-id-dir', |
| type=str, |
| required=True, |
| action='append', |
| help='Directory containing symbols. Can be specified multiple times') |
| parser.add_argument( |
| '--depfile', type=str, required=True, help='Path to the depfile.') |
| parser.add_argument( |
| '--stamp', type=str, required=True, help='Path to stamp file.') |
| args = parser.parse_args(args) |
| |
| # If the output directory already exists, wipe it, so the directory does |
| # not accumulate over multiple invocations. This is important since the |
| # same executable will have a different name when its content changes. |
| if os.path.exists(args.output_dir): |
| shutil.rmtree(args.output_dir) |
| |
| with open(args.ids_txt_paths, 'r') as f: |
| ids_txt_paths = f.read().splitlines() |
| |
| filenames = set() |
| for ids_txt_path in ids_txt_paths: |
| populate_build_id_dir( |
| ids_txt_path, args.output_dir, args.build_id_dir, filenames) |
| |
| with open(args.depfile, 'w') as f: |
| f.writelines('%s: %s\n' % (args.stamp, ' '.join(sorted(filenames)))) |
| with open(args.stamp, 'w') as f: |
| os.utime(f.name, None) |
| |
| return 0 |
| |
| |
| if __name__ == '__main__': |
| sys.exit(main(sys.argv[1:])) |