| #!/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()) |