| #!/usr/bin/env python3 | |
| # | |
| # Copyright 2013 The Flutter Authors. All rights reserved. | |
| # Use of this source code is governed by a BSD-style license that can be | |
| # found in the LICENSE file. | |
| """ Gather the build_id, prefix_dir, and exec_name given the path to executable | |
| also copies to the specified destination. | |
| The structure of debug symbols is as follows: | |
| .build-id/<prefix>/<exec_name>[.debug] | |
| """ | |
| import argparse | |
| import errno | |
| import hashlib | |
| import json | |
| import os | |
| import re | |
| import shutil | |
| import subprocess | |
| import sys | |
| import time | |
| def HashFile(filepath): | |
| """Calculates the hash of a file without reading it all in memory at once.""" | |
| digest = hashlib.sha1() | |
| with open(filepath, 'rb') as f: | |
| while True: | |
| chunk = f.read(1024 * 1024) | |
| if not chunk: | |
| break | |
| digest.update(chunk) | |
| return digest.hexdigest() | |
| def Touch(fname): | |
| with open(fname, 'a'): | |
| os.utime(fname, None) | |
| def GetBuildIdParts(exec_path, read_elf): | |
| sha1_pattern = re.compile(r'[0-9a-fA-F\-]+') | |
| file_out = subprocess.check_output([read_elf, '-n', exec_path]) | |
| build_id_line = file_out.splitlines()[-1].split() | |
| if (build_id_line[0] != b'Build' or build_id_line[1] != b'ID:' or | |
| not sha1_pattern.match(str(build_id_line[-1])) or not len(build_id_line[-1]) > 2): | |
| raise Exception( | |
| 'Expected the last line of llvm-readelf to match "Build ID <Hex String>" Got: %s' % file_out | |
| ) | |
| build_id = build_id_line[-1] | |
| return { | |
| 'build_id': build_id.decode('utf-8'), 'prefix_dir': build_id[:2].decode('utf-8'), | |
| 'exec_name': build_id[2:].decode('utf-8') | |
| } | |
| def main(): | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument( | |
| '--executable-name', | |
| dest='exec_name', | |
| action='store', | |
| required=True, | |
| help='This is the name of the executable that we wish to layout debug symbols for.' | |
| ) | |
| parser.add_argument( | |
| '--executable-path', | |
| dest='exec_path', | |
| action='store', | |
| required=True, | |
| help='Path to the executable on the filesystem.' | |
| ) | |
| parser.add_argument( | |
| '--destination-base', | |
| dest='dest', | |
| action='store', | |
| required=True, | |
| help='Path to the base directory where the debug symbols are to be laid out.' | |
| ) | |
| parser.add_argument( | |
| '--stripped', | |
| dest='stripped', | |
| action='store_true', | |
| default=True, | |
| help='Executable at the specified path is stripped.' | |
| ) | |
| parser.add_argument( | |
| '--unstripped', | |
| dest='stripped', | |
| action='store_false', | |
| help='Executable at the specified path is unstripped.' | |
| ) | |
| parser.add_argument( | |
| '--read-elf', | |
| dest='read_elf', | |
| action='store', | |
| required=True, | |
| help='Path to read-elf executable.' | |
| ) | |
| args = parser.parse_args() | |
| assert os.path.exists(args.exec_path), ('exec_path "%s" does not exist' % args.exec_path) | |
| assert os.path.exists(args.dest), ('dest "%s" does not exist' % args.dest) | |
| assert os.path.exists(args.read_elf), ('read_elf "%s" does not exist' % args.read_elf) | |
| parts = GetBuildIdParts(args.exec_path, args.read_elf) | |
| dbg_prefix_base = os.path.join(args.dest, parts['prefix_dir']) | |
| # Multiple processes may be trying to create the same directory. | |
| # TODO(dnfield): use exist_ok when we upgrade to python 3, rather than try | |
| try: | |
| os.makedirs(dbg_prefix_base) | |
| except OSError as e: | |
| if e.errno != errno.EEXIST: | |
| raise | |
| if not os.path.exists(dbg_prefix_base): | |
| print('Unable to create directory: %s.' % dbg_prefix_base) | |
| return 1 | |
| dbg_suffix = '' | |
| if not args.stripped: | |
| dbg_suffix = '.debug' | |
| dbg_file_name = '%s%s' % (parts['exec_name'], dbg_suffix) | |
| dbg_file_path = os.path.join(dbg_prefix_base, dbg_file_name) | |
| # If the debug file hasn't changed, don't rewrite the debug and completion | |
| # file, speeding up incremental builds. | |
| if os.path.exists(dbg_file_path) and HashFile(args.exec_path) == HashFile(dbg_file_path): | |
| return 0 | |
| shutil.copyfile(args.exec_path, dbg_file_path) | |
| # Note this needs to be in sync with fuchsia_debug_symbols.gni | |
| completion_file = os.path.join(args.dest, '.%s_dbg_success' % args.exec_name) | |
| Touch(completion_file) | |
| return 0 | |
| if __name__ == '__main__': | |
| sys.exit(main()) |